Sfoglia il codice sorgente

Add OpenCode Go installer preset

Alvin Unreal 3 mesi fa
parent
commit
96fe7f0647

+ 3 - 3
README.md

@@ -45,7 +45,7 @@ replacing OpenCode's built-in TUI footer. For manual setups, add
 
 ### Getting Started
 
-The installer generates an OpenAI preset by default, using `openai/gpt-5.5` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents.
+The installer generates both OpenAI and OpenCode Go presets, with OpenAI active by default. OpenAI uses `openai/gpt-5.5` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents. To make OpenCode Go active during install, run `bunx oh-my-opencode-slim@latest install --preset=opencode-go`.
 
 Then:
 
@@ -66,7 +66,7 @@ Then:
 > [!TIP]
 > Want to understand how automatic delegation works in practice? Review the **[Orchestrator prompt](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/src/agents/orchestrator.ts#L28)** — it contains the delegation rules, specialist routing logic, and the thresholds for when the main agent should hand work off to subagents.
 
-The default generated configuration looks like this:
+The default generated configuration includes both `openai` and `opencode-go` presets. Abbreviated:
 
 ```jsonc
 {
@@ -91,7 +91,7 @@ want to customize how many resumable child-agent sessions are remembered.
 
 ### For Alternative Providers
 
-To use Kimi, GitHub Copilot, ZAI Coding Plan, or a mixed-provider setup, use **[Configuration](docs/configuration.md)** for the full reference. If you want a ready-made starting point, check the **[Author's Preset](docs/authors-preset.md)** and **[$30 Preset](docs/thirty-dollars-preset.md)** - the `$30` preset is the best cheap setup.
+To use OpenCode Go, Kimi, GitHub Copilot, ZAI Coding Plan, or a mixed-provider setup, use **[Configuration](docs/configuration.md)** for the full reference. If you want a ready-made starting point, check the **[Author's Preset](docs/authors-preset.md)** and **[$30 Preset](docs/thirty-dollars-preset.md)** - the `$30` preset is the best cheap setup.
 
 The configuration guide also covers custom subagents via `agents.<name>`, where
 you can define both a normal `prompt` and an `orchestratorPrompt` block for

+ 7 - 3
docs/installation.md

@@ -34,6 +34,7 @@ The installer supports the following options:
 | Option | Description |
 |--------|-------------|
 | `--skills=yes|no` | Install recommended and bundled skills (default: yes) |
+| `--preset=<name>` | Active generated config preset: `openai`, `opencode-go`, `kimi`, `copilot`, or `zai-plan` (default: `openai`) |
 | `--no-tui` | Non-interactive mode |
 | `--dry-run` | Simulate install without writing files |
 | `--reset` | Force overwrite of existing configuration |
@@ -56,7 +57,7 @@ bunx oh-my-opencode-slim@latest install --reset
 
 ### After Installation
 
-The installer generates an OpenAI configuration by default (using `gpt-5.5` and `gpt-5.4-mini` models). To switch providers or build a mixed setup, use **[Configuration Reference](configuration.md)** for the full option reference and the preset docs for copyable examples.
+The installer generates both OpenAI and OpenCode Go presets, with OpenAI active by default (using `gpt-5.5` and `gpt-5.4-mini` models). To make OpenCode Go active during install, run `bunx oh-my-opencode-slim@latest install --preset=opencode-go`. To switch providers later or build a mixed setup, use **[Configuration Reference](configuration.md)** for the full option reference and the preset docs for copyable examples.
 
 Then:
 
@@ -105,7 +106,7 @@ If not installed, direct the user to https://opencode.ai/docs first.
 
 ### Step 2: Run the Installer
 
-The installer generates an OpenAI configuration by default:
+The installer generates OpenAI and OpenCode Go presets, with OpenAI active by default:
 
 ```bash
 bunx oh-my-opencode-slim@latest install --no-tui --skills=yes
@@ -119,6 +120,9 @@ bunx oh-my-opencode-slim@latest install
 # Non-interactive with default skills
 bunx oh-my-opencode-slim@latest install --no-tui --skills=yes
 
+# Make the generated OpenCode Go preset active
+bunx oh-my-opencode-slim@latest install --preset=opencode-go
+
 # Non-interactive without skills
 bunx oh-my-opencode-slim@latest install --no-tui --skills=no
 
@@ -154,7 +158,7 @@ Verify all agents respond successfully.
 
 **Crucial Advice for the User:**
 - They can easily assign **different models to different agents** by editing `~/.config/opencode/oh-my-opencode-slim.json` (or `.jsonc`).
-- If they want to add a different provider later (Kimi, GitHub Copilot, ZAI), they can update this file manually. See **[Configuration Reference](configuration.md)** and the preset docs for examples.
+- If they want to add a different provider later (OpenCode Go, Kimi, GitHub Copilot, ZAI), they can update this file manually. See **[Configuration Reference](configuration.md)** and the preset docs for examples.
 - Read the generated `~/.config/opencode/oh-my-opencode-slim.json` (or `.jsonc`) file to understand the current configuration.
 
 ---

+ 5 - 3
src/cli/codemap.md

@@ -19,6 +19,7 @@ Current responsibilities:
 - `src/cli/index.ts` only dispatches:
   - `install` subcommand and flags
     - `--skills=yes|no`
+    - `--preset=<name>`
     - `--no-tui`
     - `--dry-run`
     - `--reset`
@@ -54,8 +55,9 @@ CLI install command
 
 `generateLiteConfig(installConfig)` behavior:
 
-- sets `$schema`, `preset: openai`, empty `presets`
-- always materializes preset `openai`
+- sets `$schema`, a selected `preset` that defaults to `openai`
+- always materializes generated presets `openai` and `opencode-go`
+- materializes other selected built-in presets when explicitly requested
 - maps each built-in agent name to provider-specific model/variant
 - injects skill list from recommended + custom skill registries and ensures `agent-browser` for designer
 - injects default MCP sets from `DEFAULT_AGENT_MCPS`
@@ -73,4 +75,4 @@ CLI install command
 
 - The previous TUI references are stale; no dedicated interactive flow exists in current sources.
 - `installSkills` in config covers both recommended external and bundled/custom skills as separate paths.
-- Provider support in code is effectively 4 providers (`openai`, `kimi`, `copilot`, `zai-plan`).
+- Built-in preset support includes `openai`, `opencode-go`, `kimi`, `copilot`, and `zai-plan`.

+ 22 - 0
src/cli/config-io.test.ts

@@ -411,9 +411,31 @@ describe('config-io', () => {
     );
     expect(saved.preset).toBe('openai');
     expect(saved.presets.openai).toBeDefined();
+    expect(saved.presets['opencode-go']).toBeDefined();
     expect(saved.tmux.enabled).toBe(true);
   });
 
+  test('writeLiteConfig writes selected preset', () => {
+    const litePath = join(tmpDir, 'opencode', 'oh-my-opencode-slim.json');
+    paths.ensureConfigDir();
+
+    const result = writeLiteConfig({
+      hasTmux: false,
+      installSkills: false,
+      installCustomSkills: false,
+      preset: 'opencode-go',
+      reset: false,
+    });
+    expect(result.success).toBe(true);
+
+    const saved = JSON.parse(readFileSync(litePath, 'utf-8'));
+    expect(saved.preset).toBe('opencode-go');
+    expect(saved.presets.openai).toBeDefined();
+    expect(saved.presets['opencode-go'].orchestrator.model).toBe(
+      'opencode-go/glm-5.1',
+    );
+  });
+
   test('disableDefaultAgents disables explore and general agents', () => {
     const configPath = join(tmpDir, 'opencode', 'opencode.json');
     paths.ensureConfigDir();

+ 16 - 1
src/cli/index.ts

@@ -1,5 +1,6 @@
 #!/usr/bin/env bun
 import { install } from './install';
+import { getPresetNames, isPresetName } from './providers';
 import type { BooleanArg, InstallArgs } from './types';
 
 function parseArgs(args: string[]): InstallArgs {
@@ -13,6 +14,15 @@ function parseArgs(args: string[]): InstallArgs {
       result.tui = false;
     } else if (arg.startsWith('--skills=')) {
       result.skills = arg.split('=')[1] as BooleanArg;
+    } else if (arg.startsWith('--preset=')) {
+      const preset = arg.split('=')[1];
+      if (!isPresetName(preset)) {
+        console.error(
+          `Unsupported preset: ${preset}. Available presets: ${getPresetNames().join(', ')}`,
+        );
+        process.exit(1);
+      }
+      result.preset = preset;
     } else if (arg === '--dry-run') {
       result.dryRun = true;
     } else if (arg === '--reset') {
@@ -34,17 +44,22 @@ Usage: bunx oh-my-opencode-slim install [OPTIONS]
 
 Options:
   --skills=yes|no        Install recommended and bundled skills (default: yes)
+  --preset=<name>        Active generated config preset (default: openai)
   --no-tui               Non-interactive mode
   --dry-run              Simulate install without writing files
   --reset                Force overwrite of existing configuration
   -h, --help             Show this help message
 
-The installer generates an OpenAI configuration by default.
+Available presets: ${getPresetNames().join(', ')}
+
+The installer generates OpenAI and OpenCode Go presets by default.
+OpenAI is active unless --preset selects another generated preset.
 For the full config reference, see docs/configuration.md.
 
 Examples:
   bunx oh-my-opencode-slim install
   bunx oh-my-opencode-slim install --no-tui --skills=yes
+  bunx oh-my-opencode-slim install --preset=opencode-go
   bunx oh-my-opencode-slim install --reset
 `);
 }

+ 4 - 1
src/cli/install.ts

@@ -306,7 +306,9 @@ async function runInstall(config: InstallConfig): Promise<number> {
   console.log();
 
   const modelsInfo =
-    'Default configuration uses OpenAI models (gpt-5.5 / gpt-5.4-mini).';
+    config.preset && config.preset !== 'openai'
+      ? `Generated OpenAI and OpenCode Go presets; ${config.preset} is active.`
+      : 'Generated OpenAI and OpenCode Go presets; OpenAI is active by default.';
   console.log(`${modelsInfo}`);
   const altProviders = 'For the full configuration reference, see:';
   console.log(altProviders);
@@ -326,6 +328,7 @@ export async function install(args: InstallArgs): Promise<number> {
     hasTmux: false,
     installSkills: args.skills === 'yes',
     installCustomSkills: args.skills === 'yes',
+    preset: args.preset,
     promptForStar: args.tui,
     dryRun: args.dryRun,
     reset: args.reset ?? false,

+ 90 - 34
src/cli/providers.test.ts

@@ -1,15 +1,21 @@
 /// <reference types="bun-types" />
 
-import { describe, expect, test } from "bun:test";
-import { generateLiteConfig, MODEL_MAPPINGS } from "./providers";
+import { describe, expect, test } from 'bun:test';
+import { generateLiteConfig, MODEL_MAPPINGS } from './providers';
 
-describe("providers", () => {
-  test("MODEL_MAPPINGS has exactly 4 providers", () => {
+describe('providers', () => {
+  test('MODEL_MAPPINGS includes supported providers', () => {
     const keys = Object.keys(MODEL_MAPPINGS);
-    expect(keys.sort()).toEqual(["copilot", "kimi", "openai", "zai-plan"]);
+    expect(keys.sort()).toEqual([
+      'copilot',
+      'kimi',
+      'openai',
+      'opencode-go',
+      'zai-plan',
+    ]);
   });
 
-  test("generateLiteConfig always generates openai preset", () => {
+  test('generateLiteConfig defaults to openai and includes generated presets', () => {
     const config = generateLiteConfig({
       hasTmux: false,
       installSkills: false,
@@ -18,18 +24,19 @@ describe("providers", () => {
     });
 
     expect(config.$schema).toBe(
-      "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json"
+      'https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json',
     );
-    expect(config.preset).toBe("openai");
+    expect(config.preset).toBe('openai');
+    expect((config.presets as any)['opencode-go']).toBeDefined();
     const agents = (config.presets as any).openai;
     expect(agents).toBeDefined();
-    expect(agents.orchestrator.model).toBe("openai/gpt-5.5");
+    expect(agents.orchestrator.model).toBe('openai/gpt-5.5');
     expect(agents.orchestrator.variant).toBeUndefined();
-    expect(agents.fixer.model).toBe("openai/gpt-5.4-mini");
-    expect(agents.fixer.variant).toBe("low");
+    expect(agents.fixer.model).toBe('openai/gpt-5.4-mini');
+    expect(agents.fixer.variant).toBe('low');
   });
 
-  test("generateLiteConfig uses correct OpenAI models", () => {
+  test('generateLiteConfig uses correct OpenAI models', () => {
     const config = generateLiteConfig({
       hasTmux: false,
       installSkills: false,
@@ -39,19 +46,68 @@ describe("providers", () => {
 
     const agents = (config.presets as any).openai;
     expect(agents.orchestrator.model).toBe(
-      MODEL_MAPPINGS.openai.orchestrator.model
+      MODEL_MAPPINGS.openai.orchestrator.model,
     );
-    expect(agents.oracle.model).toBe("openai/gpt-5.5");
-    expect(agents.oracle.variant).toBe("high");
-    expect(agents.librarian.model).toBe("openai/gpt-5.4-mini");
-    expect(agents.librarian.variant).toBe("low");
-    expect(agents.explorer.model).toBe("openai/gpt-5.4-mini");
-    expect(agents.explorer.variant).toBe("low");
-    expect(agents.designer.model).toBe("openai/gpt-5.4-mini");
-    expect(agents.designer.variant).toBe("medium");
+    expect(agents.oracle.model).toBe('openai/gpt-5.5');
+    expect(agents.oracle.variant).toBe('high');
+    expect(agents.librarian.model).toBe('openai/gpt-5.4-mini');
+    expect(agents.librarian.variant).toBe('low');
+    expect(agents.explorer.model).toBe('openai/gpt-5.4-mini');
+    expect(agents.explorer.variant).toBe('low');
+    expect(agents.designer.model).toBe('openai/gpt-5.4-mini');
+    expect(agents.designer.variant).toBe('medium');
   });
 
-  test("generateLiteConfig enables tmux when requested", () => {
+  test('generateLiteConfig can set opencode-go as active preset', () => {
+    const config = generateLiteConfig({
+      hasTmux: false,
+      installSkills: false,
+      installCustomSkills: false,
+      preset: 'opencode-go',
+      reset: false,
+    });
+
+    expect(config.preset).toBe('opencode-go');
+    expect((config.presets as any).openai).toBeDefined();
+    const agents = (config.presets as any)['opencode-go'];
+    expect(agents).toBeDefined();
+    expect(agents.orchestrator.model).toBe('opencode-go/glm-5.1');
+    expect(agents.oracle.model).toBe('opencode-go/deepseek-v4-pro');
+    expect(agents.oracle.variant).toBe('max');
+    expect(agents.council.model).toBe('opencode-go/deepseek-v4-pro');
+    expect(agents.council.variant).toBe('high');
+    expect(agents.librarian.model).toBe('opencode-go/minimax-m2.7');
+    expect(agents.explorer.model).toBe('opencode-go/minimax-m2.7');
+    expect(agents.designer.model).toBe('opencode-go/kimi-k2.6');
+    expect(agents.fixer.model).toBe('opencode-go/deepseek-v4-flash');
+    expect(agents.fixer.variant).toBe('high');
+  });
+
+  test('generateLiteConfig rejects unsupported preset', () => {
+    expect(() =>
+      generateLiteConfig({
+        hasTmux: false,
+        installSkills: false,
+        installCustomSkills: false,
+        preset: 'not-real',
+        reset: false,
+      }),
+    ).toThrow('Unsupported preset "not-real"');
+  });
+
+  test('generateLiteConfig rejects inherited property names as presets', () => {
+    expect(() =>
+      generateLiteConfig({
+        hasTmux: false,
+        installSkills: false,
+        installCustomSkills: false,
+        preset: 'toString',
+        reset: false,
+      }),
+    ).toThrow('Unsupported preset "toString"');
+  });
+
+  test('generateLiteConfig enables tmux when requested', () => {
     const config = generateLiteConfig({
       hasTmux: true,
       installSkills: false,
@@ -61,10 +117,10 @@ describe("providers", () => {
 
     expect(config.tmux).toBeDefined();
     expect((config.tmux as any).enabled).toBe(true);
-    expect((config.tmux as any).layout).toBe("main-vertical");
+    expect((config.tmux as any).layout).toBe('main-vertical');
   });
 
-  test("generateLiteConfig includes default skills", () => {
+  test('generateLiteConfig includes default skills', () => {
     const config = generateLiteConfig({
       hasTmux: false,
       installSkills: true,
@@ -74,16 +130,16 @@ describe("providers", () => {
 
     const agents = (config.presets as any).openai;
     // Orchestrator should always have '*'
-    expect(agents.orchestrator.skills).toEqual(["*"]);
+    expect(agents.orchestrator.skills).toEqual(['*']);
 
     // Oracle should have bundled simplify
-    expect(agents.oracle.skills).toContain("simplify");
+    expect(agents.oracle.skills).toContain('simplify');
 
     // Orchestrator should implicitly cover bundled codemap via '*'
-    expect(agents.orchestrator.skills).toContain("*");
+    expect(agents.orchestrator.skills).toContain('*');
 
     // Designer should have 'agent-browser'
-    expect(agents.designer.skills).toContain("agent-browser");
+    expect(agents.designer.skills).toContain('agent-browser');
 
     // Explorer should have no bundled skills by default
     expect(agents.explorer.skills).toEqual([]);
@@ -92,7 +148,7 @@ describe("providers", () => {
     expect(agents.fixer.skills).toEqual([]);
   });
 
-  test("generateLiteConfig includes mcps field", () => {
+  test('generateLiteConfig includes mcps field', () => {
     const config = generateLiteConfig({
       hasTmux: false,
       installSkills: false,
@@ -107,7 +163,7 @@ describe("providers", () => {
     expect(Array.isArray(agents.librarian.mcps)).toBe(true);
   });
 
-  test("generateLiteConfig openai includes correct mcps", () => {
+  test('generateLiteConfig openai includes correct mcps', () => {
     const config = generateLiteConfig({
       hasTmux: false,
       installSkills: false,
@@ -116,10 +172,10 @@ describe("providers", () => {
     });
 
     const agents = (config.presets as any).openai;
-    expect(agents.orchestrator.mcps).toEqual(["*", "!context7"]);
-    expect(agents.librarian.mcps).toContain("websearch");
-    expect(agents.librarian.mcps).toContain("context7");
-    expect(agents.librarian.mcps).toContain("grep_app");
+    expect(agents.orchestrator.mcps).toEqual(['*', '!context7']);
+    expect(agents.librarian.mcps).toContain('websearch');
+    expect(agents.librarian.mcps).toContain('context7');
+    expect(agents.librarian.mcps).toContain('grep_app');
     expect(agents.designer.mcps).toEqual([]);
   });
 });

+ 82 - 48
src/cli/providers.ts

@@ -1,82 +1,110 @@
-import { DEFAULT_AGENT_MCPS } from "../config/agent-mcps";
-import { CUSTOM_SKILLS } from "./custom-skills";
-import { RECOMMENDED_SKILLS } from "./skills";
-import type { InstallConfig } from "./types";
+import { DEFAULT_AGENT_MCPS } from '../config/agent-mcps';
+import { CUSTOM_SKILLS } from './custom-skills';
+import { RECOMMENDED_SKILLS } from './skills';
+import type { InstallConfig } from './types';
 
 const SCHEMA_URL =
-  "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json";
+  'https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json';
 
-// Model mappings by provider - only 4 supported providers
+const GENERATED_PRESETS = ['openai', 'opencode-go'] as const;
+
+// Model mappings by provider/preset.
 export const MODEL_MAPPINGS = {
   openai: {
-    orchestrator: { model: "openai/gpt-5.5" },
-    oracle: { model: "openai/gpt-5.5", variant: "high" },
-    librarian: { model: "openai/gpt-5.4-mini", variant: "low" },
-    explorer: { model: "openai/gpt-5.4-mini", variant: "low" },
-    designer: { model: "openai/gpt-5.4-mini", variant: "medium" },
-    fixer: { model: "openai/gpt-5.4-mini", variant: "low" },
+    orchestrator: { model: 'openai/gpt-5.5' },
+    oracle: { model: 'openai/gpt-5.5', variant: 'high' },
+    librarian: { model: 'openai/gpt-5.4-mini', variant: 'low' },
+    explorer: { model: 'openai/gpt-5.4-mini', variant: 'low' },
+    designer: { model: 'openai/gpt-5.4-mini', variant: 'medium' },
+    fixer: { model: 'openai/gpt-5.4-mini', variant: 'low' },
   },
   kimi: {
-    orchestrator: { model: "kimi-for-coding/k2p5" },
-    oracle: { model: "kimi-for-coding/k2p5", variant: "high" },
-    librarian: { model: "kimi-for-coding/k2p5", variant: "low" },
-    explorer: { model: "kimi-for-coding/k2p5", variant: "low" },
-    designer: { model: "kimi-for-coding/k2p5", variant: "medium" },
-    fixer: { model: "kimi-for-coding/k2p5", variant: "low" },
+    orchestrator: { model: 'kimi-for-coding/k2p5' },
+    oracle: { model: 'kimi-for-coding/k2p5', variant: 'high' },
+    librarian: { model: 'kimi-for-coding/k2p5', variant: 'low' },
+    explorer: { model: 'kimi-for-coding/k2p5', variant: 'low' },
+    designer: { model: 'kimi-for-coding/k2p5', variant: 'medium' },
+    fixer: { model: 'kimi-for-coding/k2p5', variant: 'low' },
   },
   copilot: {
-    orchestrator: { model: "github-copilot/claude-opus-4.6" },
-    oracle: { model: "github-copilot/claude-opus-4.6", variant: "high" },
-    librarian: { model: "github-copilot/grok-code-fast-1", variant: "low" },
-    explorer: { model: "github-copilot/grok-code-fast-1", variant: "low" },
+    orchestrator: { model: 'github-copilot/claude-opus-4.6' },
+    oracle: { model: 'github-copilot/claude-opus-4.6', variant: 'high' },
+    librarian: { model: 'github-copilot/grok-code-fast-1', variant: 'low' },
+    explorer: { model: 'github-copilot/grok-code-fast-1', variant: 'low' },
     designer: {
-      model: "github-copilot/gemini-3.1-pro-preview",
-      variant: "medium",
+      model: 'github-copilot/gemini-3.1-pro-preview',
+      variant: 'medium',
     },
-    fixer: { model: "github-copilot/claude-sonnet-4.6", variant: "low" },
+    fixer: { model: 'github-copilot/claude-sonnet-4.6', variant: 'low' },
+  },
+  'zai-plan': {
+    orchestrator: { model: 'zai-coding-plan/glm-5' },
+    oracle: { model: 'zai-coding-plan/glm-5', variant: 'high' },
+    librarian: { model: 'zai-coding-plan/glm-5', variant: 'low' },
+    explorer: { model: 'zai-coding-plan/glm-5', variant: 'low' },
+    designer: { model: 'zai-coding-plan/glm-5', variant: 'medium' },
+    fixer: { model: 'zai-coding-plan/glm-5', variant: 'low' },
   },
-  "zai-plan": {
-    orchestrator: { model: "zai-coding-plan/glm-5" },
-    oracle: { model: "zai-coding-plan/glm-5", variant: "high" },
-    librarian: { model: "zai-coding-plan/glm-5", variant: "low" },
-    explorer: { model: "zai-coding-plan/glm-5", variant: "low" },
-    designer: { model: "zai-coding-plan/glm-5", variant: "medium" },
-    fixer: { model: "zai-coding-plan/glm-5", variant: "low" },
+  'opencode-go': {
+    orchestrator: { model: 'opencode-go/glm-5.1' },
+    oracle: { model: 'opencode-go/deepseek-v4-pro', variant: 'max' },
+    council: { model: 'opencode-go/deepseek-v4-pro', variant: 'high' },
+    librarian: { model: 'opencode-go/minimax-m2.7' },
+    explorer: { model: 'opencode-go/minimax-m2.7' },
+    designer: { model: 'opencode-go/kimi-k2.6', variant: 'medium' },
+    fixer: { model: 'opencode-go/deepseek-v4-flash', variant: 'high' },
   },
 } as const;
 
+export type PresetName = keyof typeof MODEL_MAPPINGS;
+
+export function isPresetName(value: string): value is PresetName {
+  return Object.hasOwn(MODEL_MAPPINGS, value);
+}
+
+export function getPresetNames(): PresetName[] {
+  return Object.keys(MODEL_MAPPINGS) as PresetName[];
+}
+
 export function generateLiteConfig(
-  installConfig: InstallConfig
+  installConfig: InstallConfig,
 ): Record<string, unknown> {
+  const preset = installConfig.preset ?? 'openai';
+  if (!isPresetName(preset)) {
+    throw new Error(
+      `Unsupported preset "${preset}". Available presets: ${getPresetNames().join(', ')}`,
+    );
+  }
+
   const config: Record<string, unknown> = {
     $schema: SCHEMA_URL,
-    preset: "openai",
+    preset,
     presets: {},
   };
 
   const createAgentConfig = (
     agentName: string,
-    modelInfo: { model: string; variant?: string }
+    modelInfo: { model: string; variant?: string },
   ) => {
-    const isOrchestrator = agentName === "orchestrator";
+    const isOrchestrator = agentName === 'orchestrator';
 
     const skills = isOrchestrator
-      ? ["*"]
+      ? ['*']
       : [
           ...RECOMMENDED_SKILLS.filter(
             (s) =>
-              s.allowedAgents.includes("*") ||
-              s.allowedAgents.includes(agentName)
+              s.allowedAgents.includes('*') ||
+              s.allowedAgents.includes(agentName),
           ).map((s) => s.skillName),
           ...CUSTOM_SKILLS.filter(
             (s) =>
-              s.allowedAgents.includes("*") ||
-              s.allowedAgents.includes(agentName)
+              s.allowedAgents.includes('*') ||
+              s.allowedAgents.includes(agentName),
           ).map((s) => s.name),
         ];
 
-    if (agentName === "designer" && !skills.includes("agent-browser")) {
-      skills.push("agent-browser");
+    if (agentName === 'designer' && !skills.includes('agent-browser')) {
+      skills.push('agent-browser');
     }
 
     return {
@@ -88,23 +116,29 @@ export function generateLiteConfig(
     };
   };
 
-  const buildPreset = (mappingName: keyof typeof MODEL_MAPPINGS) => {
+  const buildPreset = (mappingName: PresetName) => {
     const mapping = MODEL_MAPPINGS[mappingName];
     return Object.fromEntries(
       Object.entries(mapping).map(([agentName, modelInfo]) => [
         agentName,
         createAgentConfig(agentName, modelInfo),
-      ])
+      ]),
     );
   };
 
-  // Always use OpenAI as default
-  (config.presets as Record<string, unknown>).openai = buildPreset("openai");
+  const presets = config.presets as Record<string, unknown>;
+  for (const presetName of GENERATED_PRESETS) {
+    presets[presetName] = buildPreset(presetName);
+  }
+
+  if (!presets[preset]) {
+    presets[preset] = buildPreset(preset);
+  }
 
   if (installConfig.hasTmux) {
     config.tmux = {
       enabled: true,
-      layout: "main-vertical",
+      layout: 'main-vertical',
       main_pane_size: 60,
     };
   }

+ 2 - 0
src/cli/types.ts

@@ -3,6 +3,7 @@ export type BooleanArg = 'yes' | 'no';
 export interface InstallArgs {
   tui: boolean;
   skills?: BooleanArg;
+  preset?: string;
   dryRun?: boolean;
   reset?: boolean;
 }
@@ -18,6 +19,7 @@ export interface InstallConfig {
   hasTmux: boolean;
   installSkills: boolean;
   installCustomSkills: boolean;
+  preset?: string;
   promptForStar?: boolean;
   dryRun?: boolean;
   reset: boolean;