diff --git a/shared/assets/oh-my-opencode-slim.schema.json b/shared/assets/oh-my-opencode-slim.schema.json new file mode 100644 index 0000000..fad68c4 --- /dev/null +++ b/shared/assets/oh-my-opencode-slim.schema.json @@ -0,0 +1,237 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/alvinunreal/oh-my-opencode-slim/config-schema", + "title": "oh-my-opencode-slim Plugin Configuration", + "description": "Configuration schema for the oh-my-opencode-slim PluginConfig.", + "type": "object", + "properties": { + "preset": { + "type": "string" + }, + "presets": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/presetConfig" + } + }, + "scoringEngineVersion": { + "type": "string", + "enum": ["v1", "v2-shadow", "v2"] + }, + "balanceProviderUsage": { + "type": "boolean" + }, + "manualPlan": { + "$ref": "#/definitions/manualPlan" + }, + "agents": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/agentConfig" + } + }, + "disabled_mcps": { + "type": "array", + "items": { + "type": "string" + } + }, + "tmux": { + "$ref": "#/definitions/tmuxConfig" + }, + "background": { + "$ref": "#/definitions/backgroundTaskConfig" + }, + "fallback": { + "$ref": "#/definitions/failoverConfig" + } + }, + "definitions": { + "providerModelId": { + "type": "string", + "pattern": "^[^/\\s]+/[^\\s]+$" + }, + "manualAgentPlan": { + "type": "object", + "properties": { + "primary": { + "$ref": "#/definitions/providerModelId" + }, + "fallback1": { + "$ref": "#/definitions/providerModelId" + }, + "fallback2": { + "$ref": "#/definitions/providerModelId" + }, + "fallback3": { + "$ref": "#/definitions/providerModelId" + } + }, + "required": ["primary", "fallback1", "fallback2", "fallback3"] + }, + "manualPlan": { + "type": "object", + "properties": { + "orchestrator": { + "$ref": "#/definitions/manualAgentPlan" + }, + "oracle": { + "$ref": "#/definitions/manualAgentPlan" + }, + "designer": { + "$ref": "#/definitions/manualAgentPlan" + }, + "explorer": { + "$ref": "#/definitions/manualAgentPlan" + }, + "librarian": { + "$ref": "#/definitions/manualAgentPlan" + }, + "fixer": { + "$ref": "#/definitions/manualAgentPlan" + } + }, + "required": [ + "orchestrator", + "oracle", + "designer", + "explorer", + "librarian", + "fixer" + ], + "additionalProperties": false + }, + + "agentConfig": { + "type": "object", + "description": "AgentOverrideConfigSchema (record value for presets/agents).", + "properties": { + "model": { + "type": "string" + }, + "temperature": { + "type": "number", + "minimum": 0, + "maximum": 2 + }, + "variant": { + "type": "string" + }, + "skills": { + "type": "array", + "items": { + "type": "string" + } + }, + "mcps": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + + "presetConfig": { + "type": "object", + "description": "PresetSchema: record of string -> agentConfig.", + "additionalProperties": { + "$ref": "#/definitions/agentConfig" + } + }, + + "backgroundTaskConfig": { + "type": "object", + "properties": { + "maxConcurrentStarts": { + "type": "number", + "minimum": 1, + "maximum": 50, + "default": 10 + } + } + }, + + "tmuxLayout": { + "type": "string", + "enum": [ + "main-horizontal", + "main-vertical", + "tiled", + "even-horizontal", + "even-vertical" + ] + }, + "tmuxConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "layout": { + "$ref": "#/definitions/tmuxLayout", + "default": "main-vertical" + }, + "main_pane_size": { + "type": "number", + "minimum": 20, + "maximum": 80, + "default": 60 + } + } + }, + + "agentModelChain": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "fallbackChains": { + "type": "object", + "properties": { + "orchestrator": { + "$ref": "#/definitions/agentModelChain" + }, + "oracle": { + "$ref": "#/definitions/agentModelChain" + }, + "designer": { + "$ref": "#/definitions/agentModelChain" + }, + "explorer": { + "$ref": "#/definitions/agentModelChain" + }, + "librarian": { + "$ref": "#/definitions/agentModelChain" + }, + "fixer": { + "$ref": "#/definitions/agentModelChain" + } + }, + "additionalProperties": { + "$ref": "#/definitions/agentModelChain" + } + }, + "failoverConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "timeoutMs": { + "type": "number", + "minimum": 0, + "default": 15000 + }, + "chains": { + "$ref": "#/definitions/fallbackChains", + "default": {} + } + } + } + } +} diff --git a/src/commands/add.test.ts b/src/commands/add.test.ts index dc22433..f8c43c8 100644 --- a/src/commands/add.test.ts +++ b/src/commands/add.test.ts @@ -55,6 +55,18 @@ vi.mock("../utils/validator", () => { }; }); +// Mock OmosValidator for slim mode tests +vi.mock("../utils/omos-validator", () => { + const mockValidate = vi.fn(() => ({ valid: true, errors: [] })); + return { + OmosValidator: class { + validate = mockValidate; + constructor(_schemaPath: string) {} + }, + __mockValidate: mockValidate, + }; +}); + vi.mock("../utils/scope-resolver", async () => { const actual = await vi.importActual("../utils/scope-resolver"); return { @@ -141,6 +153,9 @@ vi.mock("../store", () => { createBackup() { return null; } + getTargetPath() { + return "/config/opencode/oh-my-opencode-slim.json"; + } }, __createdStoreInstances, __createdProjectStoreInstances, @@ -216,6 +231,9 @@ describe("addCommand", () => { if (name === "oh-my-opencode.schema.json") { return '{"$schema":"test"}'; } + if (name === "oh-my-opencode-slim.schema.json") { + return '{"$schema":"test-slim"}'; + } return null; }); // fs exists/read behavior: normalize paths so tests are robust on Windows and Unix @@ -224,6 +242,7 @@ describe("addCommand", () => { const base = path.basename(s).toLowerCase(); // force download of schema by default if (base === "oh-my-opencode.schema.json") return false; + if (base === "oh-my-opencode-slim.schema.json") return false; // treat common test files as present if (["config.json", "config.jsonc", "valid.json", "config.txt", "invalid.json"].includes(base)) return true; // pretend store/index files do not exist @@ -476,4 +495,108 @@ describe("addCommand", () => { expect(mockSpinner.fail).toHaveBeenCalled(); expect(chalk.red).toHaveBeenCalled(); }); + + // --- Slim mode tests --- + + it("adds preset in slim mode", async () => { + const storeModule = await import("../store"); + vi.spyOn(storeModule.SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.spyOn(storeModule.OmosConfigManager.prototype, "addPreset").mockImplementation(() => {}); + vi.spyOn(storeModule.OmosConfigManager.prototype, "createBackup").mockReturnValue(null); + vi.spyOn(storeModule.OmosConfigManager.prototype, "getPreset").mockReturnValue(null); + vi.spyOn(storeModule.OmosConfigManager.prototype, "getTargetPath").mockReturnValue("/config/opencode/oh-my-opencode-slim.json"); + vi.mocked(fs.existsSync).mockImplementation((p: any) => { + const s = path.normalize(String(p || "")); + const base = path.basename(s).toLowerCase(); + if (base === "preset.json") return true; + // Mock schema file exists in cache + if (base === "oh-my-opencode-slim.schema.json") return true; + return false; + }); + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ orchestrator: { model: "test/model" } })); + vi.mocked(select).mockResolvedValue("user" as any); + + await runAdd("/path/to/preset.json", { scope: "user" }); + + expect(storeModule.OmosConfigManager.prototype.addPreset).toHaveBeenCalled(); + expect(mockSpinner.succeed).toHaveBeenCalledWith(expect.stringContaining("Added preset")); + }); + + it("fails when slim schema not in cache", async () => { + const storeModule = await import("../store"); + vi.spyOn(storeModule.SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.mocked(fs.existsSync).mockImplementation((p: any) => { + const s = path.normalize(String(p || "")); + const base = path.basename(s).toLowerCase(); + if (base === "preset.json") return true; + return false; + }); + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ orchestrator: { model: "test/model" } })); + vi.mocked(readBundledAsset).mockReturnValue(null); + + await runAdd("/path/to/preset.json", { scope: "user" }); + + expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("schema not found in cache")); + }); + + it("uses bundled slim schema fallback when not in cache", async () => { + const storeModule = await import("../store"); + vi.spyOn(storeModule.SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.spyOn(storeModule.OmosConfigManager.prototype, "addPreset").mockImplementation(() => {}); + vi.spyOn(storeModule.OmosConfigManager.prototype, "createBackup").mockReturnValue(null); + vi.spyOn(storeModule.OmosConfigManager.prototype, "getPreset").mockReturnValue(null); + vi.spyOn(storeModule.OmosConfigManager.prototype, "getTargetPath").mockReturnValue("/config/opencode/oh-my-opencode-slim.json"); + vi.mocked(readBundledAsset).mockImplementation((name: string) => { + if (name === "oh-my-opencode-slim.schema.json") return '{"$schema":"test-slim"}'; + return null; + }); + vi.mocked(fs.existsSync).mockImplementation((p: any) => { + const s = path.normalize(String(p || "")); + const base = path.basename(s).toLowerCase(); + if (base === "preset.json") return true; + return false; + }); + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ orchestrator: { model: "test/model" } })); + vi.mocked(select).mockResolvedValue("user" as any); + + await runAdd("/path/to/preset.json", { scope: "user" }); + + expect(readBundledAsset).toHaveBeenCalledWith("oh-my-opencode-slim.schema.json"); + expect(storeModule.OmosConfigManager.prototype.addPreset).toHaveBeenCalled(); + }); + + it("rejects .jsonc files in slim mode", async () => { + const storeModule = await import("../store"); + vi.spyOn(storeModule.SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.mocked(fs.existsSync).mockImplementation((p: any) => { + const s = path.normalize(String(p || "")); + const base = path.basename(s).toLowerCase(); + if (base === "preset.jsonc") return true; + return false; + }); + + await runAdd("/path/to/preset.jsonc", { scope: "user" }); + + expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("Invalid file extension")); + }); + + it("exits when slim preset exists without --force", async () => { + const storeModule = await import("../store"); + vi.spyOn(storeModule.SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.spyOn(storeModule.OmosConfigManager.prototype, "getPreset").mockReturnValue({ orchestrator: { model: "test/model" } }); + vi.spyOn(storeModule.OmosConfigManager.prototype, "getTargetPath").mockReturnValue("/config/opencode/oh-my-opencode-slim.json"); + vi.mocked(fs.existsSync).mockImplementation((p: any) => { + const s = path.normalize(String(p || "")); + const base = path.basename(s).toLowerCase(); + if (base === "preset.json") return true; + if (base === "oh-my-opencode-slim.schema.json") return true; + return false; + }); + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ orchestrator: { model: "test/model" } })); + vi.mocked(select).mockResolvedValue("user" as any); + + await runAdd("/path/to/preset.json", { scope: "user", id: "my-preset" }); + + expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("already exists")); + }); }); diff --git a/src/commands/add.ts b/src/commands/add.ts index be24c1d..ef3ba70 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -5,14 +5,12 @@ import * as fs from "fs"; import * as path from "path"; import JSON5 from "json5"; import { select } from "@inquirer/prompts"; -import { StoreManager, ProjectStoreManager, Scope, Profile, OmosConfigManager, SettingsManager, OmosPresetConfig } from "../store"; +import { StoreManager, ProjectStoreManager, Scope, Profile, OmosConfigManager, SettingsManager, OmosPresetConfig, OmosConfig } from "../store"; import { Validator } from "../utils/validator"; import { OmosValidator } from "../utils/omos-validator"; -import { downloadFile, readBundledAsset } from "../utils/downloader"; +import { readBundledAsset } from "../utils/downloader"; import { resolveProjectRoot, findProjectRoot } from "../utils/scope-resolver"; -const OMO_SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json"; - async function ensureOmoSchemaAvailable(store: StoreManager): Promise { const schemaPath = path.join(store.getCacheSchemaPath(), "oh-my-opencode.schema.json"); @@ -20,22 +18,16 @@ async function ensureOmoSchemaAvailable(store: StoreManager): Promise { return schemaPath; } - try { - await downloadFile( - OMO_SCHEMA_URL, - store.getCacheSchemaPath(), - "oh-my-opencode.schema.json", - { source: "github" } - ); + // Schema not in cache - try bundled fallback + const bundledSchema = readBundledAsset("oh-my-opencode.schema.json"); + if (bundledSchema) { + store.saveCacheFile(store.getCacheSchemaPath(), "oh-my-opencode.schema.json", bundledSchema, { source: "bundled" }); return schemaPath; - } catch { - const bundledSchema = readBundledAsset("oh-my-opencode.schema.json"); - if (bundledSchema) { - store.saveCacheFile(store.getCacheSchemaPath(), "oh-my-opencode.schema.json", bundledSchema, { source: "bundled" }); - return schemaPath; - } - throw new Error("Failed to download or find bundled schema"); } + + throw new Error( + "Schema not found in cache. Run 'omo-switch init' or 'omo-switch schema refresh' to download the schema first." + ); } async function ensureOmosSchemaAvailable(store: StoreManager): Promise { @@ -45,14 +37,16 @@ async function ensureOmosSchemaAvailable(store: StoreManager): Promise { return schemaPath; } - // For OMOS, we use the bundled schema directly (no remote URL currently) + // Schema not in cache - try bundled fallback const bundledSchema = readBundledAsset("oh-my-opencode-slim.schema.json"); if (bundledSchema) { store.saveCacheFile(store.getCacheSchemaPath(), "oh-my-opencode-slim.schema.json", bundledSchema, { source: "bundled" }); return schemaPath; } - throw new Error("OMOS schema not found in bundled assets"); + throw new Error( + "Slim schema not found in cache. Run 'omo-switch init' or 'omo-switch schema refresh' to download the schema first." + ); } function deriveIdFromName(name: string): string { @@ -110,7 +104,7 @@ async function handleOmosAdd( spinner.text = "Validating preset configuration..."; const schemaPath = await ensureOmosSchemaAvailable(globalStore); const validator = new OmosValidator(schemaPath); - const validation = validator.validatePreset(presetConfig); + const validation = validator.validate(presetConfig as OmosConfig); if (!validation.valid) { spinner.fail("Preset validation failed"); diff --git a/src/commands/init.test.ts b/src/commands/init.test.ts index 818f7df..5f8b590 100644 --- a/src/commands/init.test.ts +++ b/src/commands/init.test.ts @@ -33,6 +33,15 @@ vi.mock("../store", async () => { const actual = await vi.importActual("../store"); return { ...actual, + SettingsManager: class { + getEffectiveType() { return "omo"; } + loadSettings() { return { activeType: "omo" }; } + }, + OmosConfigManager: class { + configExists() { return false; } + saveConfig() {} + getTargetPath() { return "/config/opencode/oh-my-opencode-slim.json"; } + }, }; }); @@ -47,10 +56,14 @@ vi.mock("../utils/config-path", () => ({ ensureConfigDir: vi.fn(), })); +vi.mock("../utils/omos-config-path", () => ({ + getOmosConfigTargetPath: vi.fn(() => ({ path: "/config/opencode/oh-my-opencode-slim.json", isPreferred: true })), +})); + import * as fs from "fs"; import ora from "ora"; import chalk from "chalk"; -import { StoreManager } from "../store"; +import { StoreManager, SettingsManager, OmosConfigManager } from "../store"; import { downloadFile, readBundledAsset } from "../utils/downloader"; import { findExistingConfigPath, getConfigTargetDir } from "../utils/config-path"; @@ -59,7 +72,7 @@ describe("initCommand", () => { let mockSpinner: any; beforeEach(async () => { - vi.clearAllMocks(); + vi.restoreAllMocks(); mockProcessExit(); vi.spyOn(StoreManager.prototype, "ensureDirectories"); @@ -190,4 +203,81 @@ describe("initCommand", () => { expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("No bundled schema")); }); + + // --- Slim mode tests --- + + it("initializes slim mode when activeType is slim", async () => { + vi.spyOn(SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.mocked(readBundledAsset).mockImplementation((name: string) => { + if (name === "default-template-slim.json") { + return JSON.stringify({ preset: "zen-free", presets: { "zen-free": { orchestrator: { model: "test/model" } } } }); + } + if (name === "oh-my-opencode.schema.json") { + return '{"$schema":"test"}'; + } + return null; + }); + + await runInit(); + + expect(downloadFile).toHaveBeenCalledWith( + expect.stringContaining("oh-my-opencode-slim.schema.json"), + "/cache/schema", + "oh-my-opencode-slim.schema.json", + { source: "github" } + ); + expect(mockSpinner.succeed).toHaveBeenCalled(); + }); + + it("uses bundled slim schema when GitHub download fails in slim mode", async () => { + vi.spyOn(SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.mocked(downloadFile).mockImplementation(async (_url: string, _dir: string, fileName: string) => { + if (fileName === "oh-my-opencode-slim.schema.json") { + throw new Error("Network error"); + } + return true; + }); + vi.mocked(readBundledAsset).mockImplementation((name: string) => { + if (name === "default-template-slim.json") { + return JSON.stringify({ preset: "zen-free", presets: {} }); + } + if (name === "oh-my-opencode-slim.schema.json") { + return '{"$schema":"test-slim"}'; + } + if (name === "oh-my-opencode.schema.json") { + return '{"$schema":"test"}'; + } + return null; + }); + + await runInit(); + + expect(mockSpinner.warn).toHaveBeenCalled(); + expect(mockSpinner.succeed).toHaveBeenCalled(); + }); + + it("skips slim profile creation when config already exists", async () => { + vi.spyOn(SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.spyOn(OmosConfigManager.prototype, "configExists").mockReturnValue(true); + vi.spyOn(OmosConfigManager.prototype, "getTargetPath").mockReturnValue("/config/opencode/oh-my-opencode-slim.json"); + + await runInit(); + + expect(mockSpinner.info).toHaveBeenCalledWith(expect.stringContaining("Using existing config")); + expect(mockSpinner.succeed).toHaveBeenCalled(); + }); + + it("fails when no slim template is available", async () => { + vi.spyOn(SettingsManager.prototype, "getEffectiveType").mockReturnValue("slim"); + vi.mocked(readBundledAsset).mockImplementation((name: string) => { + if (name === "oh-my-opencode.schema.json") { + return '{"$schema":"test"}'; + } + return null; + }); + + await runInit(); + + expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("Default slim template not found")); + }); }); diff --git a/src/commands/init.ts b/src/commands/init.ts index c470c7e..ce2d81d 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,14 +1,49 @@ import { Command } from "commander"; import chalk from "chalk"; -import ora from "ora"; +import ora, { type Ora } from "ora"; import * as path from "path"; -import { StoreManager } from "../store"; -import { STORE_VERSION } from "../store/types"; +import { StoreManager, SettingsManager, OmosConfigManager } from "../store"; import { downloadFile, readBundledAsset } from "../utils/downloader"; import { findExistingConfigPath, getConfigTargetDir, ensureConfigDir } from "../utils/config-path"; +import { getOmosConfigTargetPath } from "../utils/omos-config-path"; import * as fs from "fs"; const SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json"; +const SLIM_SCHEMA_URL = "https://raw.githubusercontent.com/Poorgramer-Zack/omo-switch/main/shared/assets/oh-my-opencode-slim.schema.json"; + +async function ensureSchema( + store: StoreManager, + spinner: Ora, + url: string, + filename: string, + label: string, +): Promise { + const cachePath = path.join(store.getCacheSchemaPath(), filename); + + try { + await downloadFile(url, store.getCacheSchemaPath(), filename, { source: "github" }); + spinner.succeed(`${label} downloaded from GitHub`); + } catch (err) { + const errMsg = err instanceof Error ? err.message : "Unknown error"; + + if (fs.existsSync(cachePath)) { + spinner.warn(`Failed to download ${label.toLowerCase()} from GitHub: ${errMsg}`); + spinner.text = `Using cached ${label.toLowerCase()}...`; + spinner.succeed(`Using cached ${label.toLowerCase()}`); + } else { + spinner.warn(`Failed to download ${label.toLowerCase()} from GitHub: ${errMsg}`); + spinner.text = `Falling back to bundled ${label.toLowerCase()}...`; + const bundledSchema = readBundledAsset(filename); + if (bundledSchema) { + store.saveCacheFile(store.getCacheSchemaPath(), filename, bundledSchema, { source: "bundled" }); + spinner.succeed(`Using bundled ${label.toLowerCase()}`); + } else { + spinner.fail(`No bundled ${label.toLowerCase()} available`); + throw new Error(`Failed to download or find bundled ${label.toLowerCase()}`); + } + } + } +} export const initCommand = new Command("init") .description("Initialize omo-switch store with default profile") @@ -17,43 +52,43 @@ export const initCommand = new Command("init") try { const store = new StoreManager(); + const settings = new SettingsManager(); + const activeType = settings.getEffectiveType(); spinner.text = "Creating directory structure..."; store.ensureDirectories(); spinner.text = "Downloading schema..."; - const schemaCachePath = path.join(store.getCacheSchemaPath(), "oh-my-opencode.schema.json"); - - try { - await downloadFile( - SCHEMA_URL, - store.getCacheSchemaPath(), - "oh-my-opencode.schema.json", - { source: "github" } - ); - spinner.succeed("Schema downloaded from GitHub"); - } catch (err) { - const metaPath = path.join(store.getCacheSchemaPath(), "meta.json"); - - if (fs.existsSync(schemaCachePath)) { - spinner.warn(`Failed to download schema from GitHub: ${err instanceof Error ? err.message : "Unknown error"}`); - spinner.text = "Using cached schema..."; - spinner.succeed("Using cached schema"); + await ensureSchema(store, spinner, SCHEMA_URL, "oh-my-opencode.schema.json", "Schema"); + + if (activeType === "slim") { + spinner.text = "Downloading slim schema..."; + await ensureSchema(store, spinner, SLIM_SCHEMA_URL, "oh-my-opencode-slim.schema.json", "Slim schema"); + } + + spinner.text = "Creating default profile..."; + + if (activeType === "slim") { + const omosManager = new OmosConfigManager("user"); + + if (omosManager.configExists()) { + spinner.info(`Using existing config: ${omosManager.getTargetPath()}`); + spinner.text = "Skipping default profile creation..."; } else { - spinner.warn(`Failed to download schema from GitHub: ${err instanceof Error ? err.message : "Unknown error"}`); - spinner.text = "Falling back to bundled schema..."; - const bundledSchema = readBundledAsset("oh-my-opencode.schema.json"); - if (bundledSchema) { - store.saveCacheFile(store.getCacheSchemaPath(), "oh-my-opencode.schema.json", bundledSchema, { source: "bundled" }); - spinner.succeed("Using bundled schema"); - } else { - spinner.fail("No bundled schema available"); - throw new Error("Failed to download or find bundled schema"); + const defaultTemplate = readBundledAsset("default-template-slim.json"); + if (!defaultTemplate) { + spinner.fail("Default slim template not found"); + throw new Error("Default slim template not found in shared assets"); } + + const defaultConfig = JSON.parse(defaultTemplate) as Record; + omosManager.saveConfig(defaultConfig); + spinner.succeed(`Default slim config written to ${chalk.cyan(getOmosConfigTargetPath().path)}`); } - } - spinner.text = "Creating default profile..."; + spinner.succeed(`omo-switch initialized at ${chalk.cyan(store.getStorePath())}`); + return; + } // Check if user config already exists (jsonc or json) const existingConfig = findExistingConfigPath(); diff --git a/src/utils/downloader.ts b/src/utils/downloader.ts index aca0671..4393bee 100644 --- a/src/utils/downloader.ts +++ b/src/utils/downloader.ts @@ -35,7 +35,7 @@ export async function downloadFile(url: string, cacheDir: string, fileName: stri } export function readBundledAsset(relativePath: string): string | null { - const projectRoot = path.resolve(__dirname, "../../../"); + const projectRoot = path.resolve(__dirname, "../../"); const assetPath = path.join(projectRoot, "shared", "assets", relativePath); if (!fs.existsSync(assetPath)) { return null;