| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690 |
- /// <reference types="bun-types" />
- import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
- import {
- existsSync,
- mkdirSync,
- mkdtempSync,
- readFileSync,
- rmSync,
- writeFileSync,
- } from 'node:fs';
- import { tmpdir } from 'node:os';
- import { join } from 'node:path';
- import {
- addPluginToOpenCodeConfig,
- addPluginToOpenCodeTuiConfig,
- detectCurrentConfig,
- disableDefaultAgents,
- enableLspByDefault,
- parseConfig,
- parseConfigFile,
- stripJsonComments,
- writeConfig,
- writeLiteConfig,
- } from './config-io';
- import * as paths from './paths';
- describe('config-io', () => {
- let tmpDir: string;
- const originalEnv = { ...process.env };
- const originalArgv = [...process.argv];
- beforeEach(() => {
- tmpDir = mkdtempSync(join(tmpdir(), 'opencode-io-test-'));
- delete process.env.OPENCODE_CONFIG_DIR;
- delete process.env.OPENCODE_TUI_CONFIG;
- process.env.XDG_CONFIG_HOME = tmpDir;
- });
- afterEach(() => {
- process.env = { ...originalEnv };
- process.argv = [...originalArgv];
- if (tmpDir && existsSync(tmpDir)) {
- rmSync(tmpDir, { recursive: true, force: true });
- }
- mock.restore();
- });
- function writePackageJson(dir: string, version?: string): void {
- mkdirSync(dir, { recursive: true });
- writeFileSync(
- join(dir, 'package.json'),
- JSON.stringify({
- name: 'oh-my-opencode-slim',
- ...(version ? { version } : {}),
- }),
- );
- }
- test('stripJsonComments strips comments and trailing commas', () => {
- const jsonc = `{
- // comment
- "a": 1, /* multi
- line */
- "b": [2,],
- }`;
- const stripped = stripJsonComments(jsonc);
- expect(JSON.parse(stripped)).toEqual({ a: 1, b: [2] });
- });
- test('parseConfigFile parses valid JSON', () => {
- const path = join(tmpDir, 'test.json');
- writeFileSync(path, '{"a": 1}');
- const result = parseConfigFile(path);
- expect(result.config).toEqual({ a: 1 } as any);
- expect(result.error).toBeUndefined();
- });
- test('parseConfigFile returns null for non-existent file', () => {
- const result = parseConfigFile(join(tmpDir, 'nonexistent.json'));
- expect(result.config).toBeNull();
- });
- test('parseConfigFile returns null for empty or whitespace-only file', () => {
- const emptyPath = join(tmpDir, 'empty.json');
- writeFileSync(emptyPath, '');
- expect(parseConfigFile(emptyPath).config).toBeNull();
- const whitespacePath = join(tmpDir, 'whitespace.json');
- writeFileSync(whitespacePath, ' \n ');
- expect(parseConfigFile(whitespacePath).config).toBeNull();
- });
- test('parseConfigFile returns error for invalid JSON', () => {
- const path = join(tmpDir, 'invalid.json');
- writeFileSync(path, '{"a": 1');
- const result = parseConfigFile(path);
- expect(result.config).toBeNull();
- expect(result.error).toBeDefined();
- });
- test('parseConfig tries .jsonc if .json is missing', () => {
- const jsoncPath = join(tmpDir, 'test.jsonc');
- writeFileSync(jsoncPath, '{"a": 1}');
- // We pass .json path, it should try .jsonc
- const result = parseConfig(join(tmpDir, 'test.json'));
- expect(result.config).toEqual({ a: 1 } as any);
- });
- test('writeConfig writes JSON and creates backup', () => {
- const path = join(tmpDir, 'test.json');
- writeFileSync(path, '{"old": true}');
- writeConfig(path, { new: true } as any);
- expect(JSON.parse(readFileSync(path, 'utf-8'))).toEqual({ new: true });
- expect(JSON.parse(readFileSync(`${path}.bak`, 'utf-8'))).toEqual({
- old: true,
- });
- });
- test('addPluginToOpenCodeConfig adds plugin and removes duplicates', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(
- configPath,
- JSON.stringify({ plugin: ['other', 'oh-my-opencode-slim@1.0.0'] }),
- );
- process.argv[1] = '';
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toContain('oh-my-opencode-slim');
- expect(saved.plugin).not.toContain('oh-my-opencode-slim@1.0.0');
- expect(saved.plugin.length).toBe(2);
- });
- test('addPluginToOpenCodeConfig respects OPENCODE_CONFIG_DIR', async () => {
- const customConfigDir = join(tmpDir, 'custom-opencode');
- const defaultConfigDir = join(tmpDir, 'opencode');
- const customConfigPath = join(customConfigDir, 'opencode.jsonc');
- const defaultConfigPath = join(defaultConfigDir, 'opencode.json');
- process.env.OPENCODE_CONFIG_DIR = customConfigDir;
- mkdirSync(customConfigDir, { recursive: true });
- mkdirSync(defaultConfigDir, { recursive: true });
- writeFileSync(
- customConfigPath,
- JSON.stringify({ plugin: ['other', 'oh-my-opencode-slim@1.0.0'] }),
- );
- writeFileSync(defaultConfigPath, JSON.stringify({ plugin: ['default'] }));
- process.argv[1] = '';
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- expect(result.configPath).toBe(customConfigPath);
- const customSaved = JSON.parse(readFileSync(customConfigPath, 'utf-8'));
- const defaultSaved = JSON.parse(readFileSync(defaultConfigPath, 'utf-8'));
- expect(customSaved.plugin).toEqual(['other', 'oh-my-opencode-slim']);
- expect(defaultSaved.plugin).toEqual(['default']);
- });
- test('addPluginToOpenCodeConfig stores package name for bunx temp paths', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(
- tmpDir,
- 'bunx-1000-oh-my-opencode-slim@latest',
- 'node_modules',
- 'oh-my-opencode-slim',
- );
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot);
- process.argv[1] = join(packageRoot, 'dist', 'cli', 'index.js');
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual(['oh-my-opencode-slim']);
- });
- test('addPluginToOpenCodeConfig leaves @latest bunx invocations unpinned', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(
- tmpDir,
- 'bunx-1000-oh-my-opencode-slim@latest',
- 'node_modules',
- 'oh-my-opencode-slim',
- );
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot, '1.2.3');
- process.argv[1] = join(packageRoot, 'dist', 'cli', 'index.js');
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual(['oh-my-opencode-slim']);
- });
- test('addPluginToOpenCodeConfig writes the resolved version as an installer-managed tuple', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(
- tmpDir,
- 'bunx-1000-oh-my-opencode-slim@beta',
- 'node_modules',
- 'oh-my-opencode-slim',
- );
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot, '1.2.3');
- process.argv[1] = join(packageRoot, 'dist', 'cli', 'index.js');
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual([
- [
- 'oh-my-opencode-slim@1.2.3',
- { __ohMyOpencodeSlimManagedByInstaller: true },
- ],
- ]);
- });
- test('addPluginToOpenCodeConfig stores local repo path for local dev paths', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(tmpDir, 'repo');
- const localCliPath = join(packageRoot, 'dist', 'cli', 'index.js');
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot);
- process.argv[1] = localCliPath;
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual([packageRoot]);
- });
- test('addPluginToOpenCodeConfig stores local repo path for local paths containing bunx-', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(tmpDir, 'repo', 'bunx-tools');
- const localCliPath = join(packageRoot, 'dist', 'cli', 'index.js');
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot);
- process.argv[1] = localCliPath;
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual([packageRoot]);
- });
- test('addPluginToOpenCodeConfig deduplicates existing local repo path entries', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(tmpDir, 'repo');
- const localCliPath = join(packageRoot, 'dist', 'cli', 'index.js');
- paths.ensureConfigDir();
- writePackageJson(packageRoot);
- writeFileSync(
- configPath,
- JSON.stringify({ plugin: ['other', packageRoot] }),
- );
- process.argv[1] = localCliPath;
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual(['other', packageRoot]);
- });
- test('addPluginToOpenCodeConfig preserves non-string plugin entries when refreshing', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- process.argv[1] = '';
- const objectPlugin = { name: 'some-config-plugin', enabled: true };
- writeFileSync(
- configPath,
- JSON.stringify({
- plugin: ['other-plugin', objectPlugin, 'oh-my-opencode-slim@1.0.0'],
- }),
- );
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toContain('oh-my-opencode-slim');
- expect(saved.plugin).toContain('other-plugin');
- expect(saved.plugin).not.toContain('oh-my-opencode-slim@1.0.0');
- // Non-string entries (objects) must survive the plugin refresh
- expect(saved.plugin).toContainEqual(objectPlugin);
- expect(saved.plugin.length).toBe(3);
- });
- test('addPluginToOpenCodeConfig removes tuple plugin entries', async () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(
- configPath,
- JSON.stringify({
- plugin: ['other', ['oh-my-opencode-slim', { enabled: true }]],
- }),
- );
- process.argv[1] = '';
- const result = await addPluginToOpenCodeConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.plugin).toEqual(['other', 'oh-my-opencode-slim']);
- });
- test('addPluginToOpenCodeTuiConfig adds plugin to tui.json and removes duplicates', async () => {
- const tuiPath = join(tmpDir, 'opencode', 'tui.json');
- paths.ensureConfigDir();
- writeFileSync(
- tuiPath,
- JSON.stringify({ plugin: ['other', 'oh-my-opencode-slim@1.0.0'] }),
- );
- process.argv[1] = '';
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toContain('oh-my-opencode-slim');
- expect(saved.plugin).not.toContain('oh-my-opencode-slim@1.0.0');
- expect(saved.plugin.length).toBe(2);
- });
- test('addPluginToOpenCodeTuiConfig stores package name for bunx temp paths', async () => {
- const tuiPath = join(tmpDir, 'opencode', 'tui.json');
- const packageRoot = join(
- tmpDir,
- 'bunx-1000-oh-my-opencode-slim@latest',
- 'node_modules',
- 'oh-my-opencode-slim',
- );
- paths.ensureConfigDir();
- writeFileSync(tuiPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot);
- process.argv[1] = join(packageRoot, 'dist', 'cli', 'index.js');
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toEqual(['oh-my-opencode-slim']);
- });
- test('addPluginToOpenCodeTuiConfig removes tuple plugin entries', async () => {
- const tuiPath = join(tmpDir, 'opencode', 'tui.json');
- paths.ensureConfigDir();
- writeFileSync(
- tuiPath,
- JSON.stringify({
- plugin: ['other', ['oh-my-opencode-slim', { enabled: true }]],
- }),
- );
- process.argv[1] = '';
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toEqual(['other', 'oh-my-opencode-slim']);
- });
- test('addPluginToOpenCodeTuiConfig honors OPENCODE_TUI_CONFIG', async () => {
- const tuiPath = join(tmpDir, 'custom', 'tui.custom.json');
- process.env.OPENCODE_TUI_CONFIG = tuiPath;
- process.argv[1] = '';
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- expect(result.configPath).toBe(tuiPath);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toEqual(['oh-my-opencode-slim']);
- });
- test('addPluginToOpenCodeTuiConfig does not bypass OPENCODE_TUI_CONFIG for existing default config', async () => {
- const defaultTuiPath = join(tmpDir, 'opencode', 'tui.jsonc');
- const customTuiPath = join(tmpDir, 'custom', 'tui.json');
- paths.ensureConfigDir();
- writeFileSync(defaultTuiPath, JSON.stringify({ plugin: ['default'] }));
- process.env.OPENCODE_TUI_CONFIG = customTuiPath;
- process.argv[1] = '';
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- expect(result.configPath).toBe(customTuiPath);
- const custom = JSON.parse(readFileSync(customTuiPath, 'utf-8'));
- const original = JSON.parse(readFileSync(defaultTuiPath, 'utf-8'));
- expect(custom.plugin).toEqual(['oh-my-opencode-slim']);
- expect(original.plugin).toEqual(['default']);
- });
- test('addPluginToOpenCodeTuiConfig stores local repo path for local dev paths', async () => {
- const tuiPath = join(tmpDir, 'opencode', 'tui.json');
- const packageRoot = join(tmpDir, 'repo');
- const localCliPath = join(packageRoot, 'dist', 'cli', 'index.js');
- paths.ensureConfigDir();
- writeFileSync(tuiPath, JSON.stringify({ plugin: [] }));
- writePackageJson(packageRoot);
- process.argv[1] = localCliPath;
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toEqual([packageRoot]);
- });
- test('addPluginToOpenCodeTuiConfig deduplicates existing local repo path entries', async () => {
- const tuiPath = join(tmpDir, 'opencode', 'tui.json');
- const packageRoot = join(tmpDir, 'repo');
- const localCliPath = join(packageRoot, 'dist', 'cli', 'index.js');
- paths.ensureConfigDir();
- writePackageJson(packageRoot);
- writeFileSync(tuiPath, JSON.stringify({ plugin: ['other', packageRoot] }));
- process.argv[1] = localCliPath;
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toEqual(['other', packageRoot]);
- });
- test('addPluginToOpenCodeTuiConfig preserves non-string plugin entries when refreshing', async () => {
- const tuiPath = join(tmpDir, 'opencode', 'tui.json');
- paths.ensureConfigDir();
- process.argv[1] = '';
- const objectPlugin = { name: 'some-tui-plugin', enabled: true };
- writeFileSync(
- tuiPath,
- JSON.stringify({
- plugin: ['other-plugin', objectPlugin, 'oh-my-opencode-slim@1.0.0'],
- }),
- );
- const result = await addPluginToOpenCodeTuiConfig();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(tuiPath, 'utf-8'));
- expect(saved.plugin).toContain('oh-my-opencode-slim');
- expect(saved.plugin).toContain('other-plugin');
- expect(saved.plugin).not.toContain('oh-my-opencode-slim@1.0.0');
- // Non-string entries (objects) must survive the plugin refresh
- expect(saved.plugin).toContainEqual(objectPlugin);
- expect(saved.plugin.length).toBe(3);
- });
- test('writeLiteConfig writes lite config with OpenAI preset', () => {
- const litePath = join(tmpDir, 'opencode', 'oh-my-opencode-slim.json');
- paths.ensureConfigDir();
- const result = writeLiteConfig({
- installCustomSkills: false,
- reset: false,
- });
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(litePath, 'utf-8'));
- expect(saved.$schema).toBe(
- 'https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json',
- );
- expect(saved.preset).toBe('openai');
- expect(saved.presets.openai).toBeDefined();
- expect(saved.presets['opencode-go']).toBeDefined();
- });
- test('writeLiteConfig writes selected preset', () => {
- const litePath = join(tmpDir, 'opencode', 'oh-my-opencode-slim.json');
- paths.ensureConfigDir();
- const result = writeLiteConfig({
- 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.disabled_agents).toEqual([]);
- expect(saved.presets.openai).toBeDefined();
- expect(saved.presets['opencode-go'].orchestrator.model).toBe(
- 'opencode-go/minimax-m3',
- );
- expect(saved.presets['opencode-go'].orchestrator.variant).toBe('thinking');
- expect(saved.presets['opencode-go'].observer.model).toBe(
- 'opencode-go/mimo-v2.5',
- );
- expect(saved.presets['opencode-go'].observer.variant).toBeUndefined();
- });
- test('disableDefaultAgents disables conflicting OpenCode built-in agents', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({}));
- const result = disableDefaultAgents();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.agent.explore.disable).toBe(true);
- expect(saved.agent.general.disable).toBe(true);
- expect(saved.agent.build).toBeUndefined();
- expect(saved.agent.plan).toBeUndefined();
- });
- test('disableDefaultAgents preserves existing build and plan agent config', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(
- configPath,
- JSON.stringify({
- agent: {
- build: { description: 'custom build agent' },
- plan: { permission: { edit: 'deny' } },
- },
- }),
- );
- const result = disableDefaultAgents();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.agent.build).toEqual({ description: 'custom build agent' });
- expect(saved.agent.plan).toEqual({ permission: { edit: 'deny' } });
- expect(saved.agent.explore.disable).toBe(true);
- expect(saved.agent.general.disable).toBe(true);
- });
- test('enableLspByDefault sets lsp true when missing', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ plugin: ['other'] }));
- const result = enableLspByDefault();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.lsp).toBe(true);
- expect(saved.plugin).toEqual(['other']);
- });
- test('enableLspByDefault preserves explicit lsp config', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ lsp: false }));
- const result = enableLspByDefault();
- expect(result.success).toBe(true);
- const saved = JSON.parse(readFileSync(configPath, 'utf-8'));
- expect(saved.lsp).toBe(false);
- });
- test('enableLspByDefault does not write when lsp exists', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(configPath, JSON.stringify({ lsp: false }));
- const result = enableLspByDefault();
- expect(result.success).toBe(true);
- expect(existsSync(`${configPath}.bak`)).toBe(false);
- });
- test('detectCurrentConfig detects installed status', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const litePath = join(tmpDir, 'opencode', 'oh-my-opencode-slim.json');
- paths.ensureConfigDir();
- writeFileSync(
- configPath,
- JSON.stringify({
- plugin: ['oh-my-opencode-slim'],
- provider: {
- kimi: {
- npm: '@ai-sdk/openai-compatible',
- },
- },
- }),
- );
- writeFileSync(
- litePath,
- JSON.stringify({
- preset: 'openai',
- presets: {
- openai: {
- orchestrator: { model: 'openai/gpt-4' },
- oracle: { model: 'anthropic/claude-opus-4-6' },
- explorer: { model: 'github-copilot/grok-code-fast-1' },
- librarian: { model: 'zai-coding-plan/glm-4.7' },
- },
- },
- }),
- );
- const detected = detectCurrentConfig();
- expect(detected.isInstalled).toBe(true);
- expect(detected.hasKimi).toBe(true);
- expect(detected.hasOpenAI).toBe(true);
- expect(detected.hasAnthropic).toBe(true);
- expect(detected.hasCopilot).toBe(true);
- expect(detected.hasZaiPlan).toBe(true);
- });
- test('detectCurrentConfig detects installed status for installer-managed tuple', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- paths.ensureConfigDir();
- writeFileSync(
- configPath,
- JSON.stringify({
- plugin: [
- [
- 'oh-my-opencode-slim@1.2.3',
- { __ohMyOpencodeSlimManagedByInstaller: true },
- ],
- ],
- }),
- );
- const detected = detectCurrentConfig();
- expect(detected.isInstalled).toBe(true);
- });
- test('detectCurrentConfig detects provider models in arrays', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const litePath = join(tmpDir, 'opencode', 'oh-my-opencode-slim.json');
- paths.ensureConfigDir();
- writeFileSync(
- configPath,
- JSON.stringify({ plugin: ['oh-my-opencode-slim'] }),
- );
- writeFileSync(
- litePath,
- JSON.stringify({
- preset: 'dev',
- presets: {
- dev: {
- orchestrator: {
- model: [
- 'openai/gpt-5.6-luna',
- { id: 'anthropic/claude-opus-4-6' },
- ],
- },
- },
- },
- }),
- );
- const detected = detectCurrentConfig();
- expect(detected.hasOpenAI).toBe(true);
- expect(detected.hasAnthropic).toBe(true);
- });
- test('detectCurrentConfig treats local repo path entries as installed', () => {
- const configPath = join(tmpDir, 'opencode', 'opencode.json');
- const packageRoot = join(tmpDir, 'repo');
- paths.ensureConfigDir();
- writePackageJson(packageRoot);
- writeFileSync(configPath, JSON.stringify({ plugin: [packageRoot] }));
- const detected = detectCurrentConfig();
- expect(detected.isInstalled).toBe(true);
- });
- });
|