opencode-dux 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/agents/descriptions.d.ts +6 -0
- package/dist/agents/designer.d.ts +2 -0
- package/dist/agents/explorer.d.ts +2 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/index.d.ts +22 -0
- package/dist/agents/interpreter.d.ts +2 -0
- package/dist/agents/librarian.d.ts +2 -0
- package/dist/agents/oracle.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +27 -0
- package/dist/agents/overrides.d.ts +18 -0
- package/dist/agents/prompt-blocks.d.ts +97 -0
- package/dist/agents/steward.d.ts +3 -0
- package/dist/cli/config-io.d.ts +24 -0
- package/dist/cli/config-manager.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1006 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/mcps.d.ts +13 -0
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/paths.d.ts +35 -0
- package/dist/cli/providers.d.ts +137 -0
- package/dist/cli/skills.d.ts +22 -0
- package/dist/cli/system.d.ts +5 -0
- package/dist/cli/types.d.ts +38 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/utils.d.ts +10 -0
- package/dist/discovery/local/types.d.ts +79 -0
- package/dist/discovery/local.d.ts +73 -0
- package/dist/discovery/mcp-servers.d.ts +88 -0
- package/dist/discovery/skills.d.ts +94 -0
- package/dist/hooks/apply-patch/codec.d.ts +7 -0
- package/dist/hooks/apply-patch/errors.d.ts +25 -0
- package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
- package/dist/hooks/apply-patch/index.d.ts +15 -0
- package/dist/hooks/apply-patch/matching.d.ts +26 -0
- package/dist/hooks/apply-patch/operations.d.ts +3 -0
- package/dist/hooks/apply-patch/patch.d.ts +2 -0
- package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
- package/dist/hooks/apply-patch/resolution.d.ts +19 -0
- package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
- package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
- package/dist/hooks/apply-patch/types.d.ts +80 -0
- package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
- package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
- package/dist/hooks/auto-update-checker/index.d.ts +18 -0
- package/dist/hooks/auto-update-checker/types.d.ts +22 -0
- package/dist/hooks/chat-headers.d.ts +16 -0
- package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/foreground-fallback/index.d.ts +72 -0
- package/dist/hooks/image-hook.d.ts +5 -0
- package/dist/hooks/index.d.ts +14 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +26 -0
- package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
- package/dist/hooks/task-session-manager/index.d.ts +52 -0
- package/dist/hooks/todo-continuation/index.d.ts +53 -0
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31782 -0
- package/dist/mcp/context7.d.ts +6 -0
- package/dist/mcp/grep-app.d.ts +6 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/websearch.d.ts +9 -0
- package/dist/skills/registry.d.ts +29 -0
- package/dist/subscriptions/accounts-store.d.ts +57 -0
- package/dist/subscriptions/index.d.ts +13 -0
- package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
- package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
- package/dist/subscriptions/types.d.ts +115 -0
- package/dist/subscriptions/usage-service.d.ts +74 -0
- package/dist/tools/ast-grep/cli.d.ts +15 -0
- package/dist/tools/ast-grep/constants.d.ts +25 -0
- package/dist/tools/ast-grep/downloader.d.ts +5 -0
- package/dist/tools/ast-grep/index.d.ts +10 -0
- package/dist/tools/ast-grep/tools.d.ts +3 -0
- package/dist/tools/ast-grep/types.d.ts +30 -0
- package/dist/tools/ast-grep/utils.d.ts +4 -0
- package/dist/tools/delegate.d.ts +14 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/dist/tools/smartfetch/binary.d.ts +3 -0
- package/dist/tools/smartfetch/cache.d.ts +6 -0
- package/dist/tools/smartfetch/constants.d.ts +12 -0
- package/dist/tools/smartfetch/index.d.ts +3 -0
- package/dist/tools/smartfetch/network.d.ts +38 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
- package/dist/tools/smartfetch/tool.d.ts +3 -0
- package/dist/tools/smartfetch/types.d.ts +122 -0
- package/dist/tools/smartfetch/utils.d.ts +18 -0
- package/dist/tui-state.d.ts +168 -0
- package/dist/tui.d.ts +37 -0
- package/dist/tui.js +1896 -0
- package/dist/utils/agent-variant.d.ts +63 -0
- package/dist/utils/compat.d.ts +30 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/internal-initiator.d.ts +6 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/polling.d.ts +21 -0
- package/dist/utils/session-manager.d.ts +55 -0
- package/dist/utils/session.d.ts +90 -0
- package/dist/utils/subagent-depth.d.ts +35 -0
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/dist/utils/zip-extractor.d.ts +1 -0
- package/index.ts +1 -0
- package/opencode-dux.schema.json +634 -0
- package/package.json +103 -0
- package/src/agents/descriptions.ts +55 -0
- package/src/agents/designer.test.ts +86 -0
- package/src/agents/designer.ts +154 -0
- package/src/agents/display-name.test.ts +186 -0
- package/src/agents/explorer.test.ts +79 -0
- package/src/agents/explorer.ts +144 -0
- package/src/agents/fixer.test.ts +79 -0
- package/src/agents/fixer.ts +145 -0
- package/src/agents/index.test.ts +472 -0
- package/src/agents/index.ts +248 -0
- package/src/agents/interpreter.ts +136 -0
- package/src/agents/librarian.test.ts +80 -0
- package/src/agents/librarian.ts +145 -0
- package/src/agents/oracle.test.ts +89 -0
- package/src/agents/oracle.ts +184 -0
- package/src/agents/orchestrator.test.ts +116 -0
- package/src/agents/orchestrator.ts +574 -0
- package/src/agents/overrides.ts +95 -0
- package/src/agents/prompt-blocks.test.ts +114 -0
- package/src/agents/prompt-blocks.ts +640 -0
- package/src/agents/steward.ts +146 -0
- package/src/cli/config-io.test.ts +536 -0
- package/src/cli/config-io.ts +473 -0
- package/src/cli/config-manager.test.ts +141 -0
- package/src/cli/config-manager.ts +4 -0
- package/src/cli/index.ts +88 -0
- package/src/cli/install.ts +282 -0
- package/src/cli/mcps.test.ts +62 -0
- package/src/cli/mcps.ts +39 -0
- package/src/cli/model-key-normalization.test.ts +21 -0
- package/src/cli/model-key-normalization.ts +60 -0
- package/src/cli/paths.test.ts +167 -0
- package/src/cli/paths.ts +144 -0
- package/src/cli/providers.test.ts +118 -0
- package/src/cli/providers.ts +141 -0
- package/src/cli/skills.test.ts +111 -0
- package/src/cli/skills.ts +103 -0
- package/src/cli/system.test.ts +91 -0
- package/src/cli/system.ts +180 -0
- package/src/cli/types.ts +43 -0
- package/src/config/constants.ts +58 -0
- package/src/config/index.ts +4 -0
- package/src/config/loader.test.ts +1194 -0
- package/src/config/loader.ts +269 -0
- package/src/config/model-resolution.test.ts +176 -0
- package/src/config/runtime-preset.test.ts +61 -0
- package/src/config/runtime-preset.ts +37 -0
- package/src/config/schema.ts +248 -0
- package/src/config/utils.test.ts +41 -0
- package/src/config/utils.ts +23 -0
- package/src/discovery/local/types.ts +85 -0
- package/src/discovery/local.ts +322 -0
- package/src/discovery/mcp-servers.ts +804 -0
- package/src/discovery/skills.ts +959 -0
- package/src/hooks/apply-patch/codec.test.ts +184 -0
- package/src/hooks/apply-patch/codec.ts +352 -0
- package/src/hooks/apply-patch/errors.ts +117 -0
- package/src/hooks/apply-patch/execution-context.ts +432 -0
- package/src/hooks/apply-patch/hook.test.ts +768 -0
- package/src/hooks/apply-patch/index.ts +126 -0
- package/src/hooks/apply-patch/matching.test.ts +215 -0
- package/src/hooks/apply-patch/matching.ts +586 -0
- package/src/hooks/apply-patch/operations.test.ts +1535 -0
- package/src/hooks/apply-patch/operations.ts +3 -0
- package/src/hooks/apply-patch/patch.ts +9 -0
- package/src/hooks/apply-patch/prepared-changes.ts +400 -0
- package/src/hooks/apply-patch/resolution.test.ts +420 -0
- package/src/hooks/apply-patch/resolution.ts +437 -0
- package/src/hooks/apply-patch/rewrite.ts +496 -0
- package/src/hooks/apply-patch/test-helpers.ts +52 -0
- package/src/hooks/apply-patch/types.ts +111 -0
- package/src/hooks/auto-update-checker/cache.test.ts +179 -0
- package/src/hooks/auto-update-checker/cache.ts +188 -0
- package/src/hooks/auto-update-checker/checker.test.ts +159 -0
- package/src/hooks/auto-update-checker/checker.ts +308 -0
- package/src/hooks/auto-update-checker/constants.ts +33 -0
- package/src/hooks/auto-update-checker/index.test.ts +282 -0
- package/src/hooks/auto-update-checker/index.ts +225 -0
- package/src/hooks/auto-update-checker/types.ts +26 -0
- package/src/hooks/chat-headers.test.ts +236 -0
- package/src/hooks/chat-headers.ts +97 -0
- package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
- package/src/hooks/context-pressure-reminder/index.ts +137 -0
- package/src/hooks/delegate-task-retry/guidance.ts +41 -0
- package/src/hooks/delegate-task-retry/hook.ts +23 -0
- package/src/hooks/delegate-task-retry/index.test.ts +38 -0
- package/src/hooks/delegate-task-retry/index.ts +7 -0
- package/src/hooks/delegate-task-retry/patterns.ts +79 -0
- package/src/hooks/filter-available-skills/index.test.ts +297 -0
- package/src/hooks/filter-available-skills/index.ts +160 -0
- package/src/hooks/foreground-fallback/index.test.ts +624 -0
- package/src/hooks/foreground-fallback/index.ts +374 -0
- package/src/hooks/image-hook.ts +6 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/json-error-recovery/hook.ts +73 -0
- package/src/hooks/json-error-recovery/index.test.ts +111 -0
- package/src/hooks/json-error-recovery/index.ts +6 -0
- package/src/hooks/phase-reminder/index.test.ts +74 -0
- package/src/hooks/phase-reminder/index.ts +85 -0
- package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
- package/src/hooks/post-file-tool-nudge/index.ts +63 -0
- package/src/hooks/task-session-manager/index.test.ts +833 -0
- package/src/hooks/task-session-manager/index.ts +434 -0
- package/src/hooks/todo-continuation/index.test.ts +3026 -0
- package/src/hooks/todo-continuation/index.ts +878 -0
- package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
- package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
- package/src/index.ts +1672 -0
- package/src/mcp/context7.ts +14 -0
- package/src/mcp/grep-app.ts +11 -0
- package/src/mcp/index.test.ts +96 -0
- package/src/mcp/index.ts +66 -0
- package/src/mcp/types.ts +16 -0
- package/src/mcp/websearch.ts +47 -0
- package/src/skills/codemap/README.md +60 -0
- package/src/skills/codemap/SKILL.md +174 -0
- package/src/skills/codemap/scripts/codemap.mjs +483 -0
- package/src/skills/codemap/scripts/codemap.test.ts +129 -0
- package/src/skills/registry.ts +218 -0
- package/src/skills/simplify/README.md +19 -0
- package/src/skills/simplify/SKILL.md +138 -0
- package/src/subscriptions/accounts-store.test.ts +236 -0
- package/src/subscriptions/accounts-store.ts +184 -0
- package/src/subscriptions/index.ts +30 -0
- package/src/subscriptions/neuralwatt-scraper.ts +108 -0
- package/src/subscriptions/opencode-go-scraper.ts +301 -0
- package/src/subscriptions/types.ts +145 -0
- package/src/subscriptions/usage-service.test.ts +202 -0
- package/src/subscriptions/usage-service.ts +651 -0
- package/src/tools/ast-grep/cli.ts +257 -0
- package/src/tools/ast-grep/constants.ts +214 -0
- package/src/tools/ast-grep/downloader.ts +131 -0
- package/src/tools/ast-grep/index.ts +24 -0
- package/src/tools/ast-grep/tools.ts +117 -0
- package/src/tools/ast-grep/types.ts +51 -0
- package/src/tools/ast-grep/utils.ts +126 -0
- package/src/tools/delegate-handoff.test.ts +18 -0
- package/src/tools/delegate.ts +508 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/preset-manager.test.ts +795 -0
- package/src/tools/preset-manager.ts +332 -0
- package/src/tools/smartfetch/binary.ts +58 -0
- package/src/tools/smartfetch/cache.test.ts +34 -0
- package/src/tools/smartfetch/cache.ts +112 -0
- package/src/tools/smartfetch/constants.ts +29 -0
- package/src/tools/smartfetch/index.ts +8 -0
- package/src/tools/smartfetch/network.test.ts +178 -0
- package/src/tools/smartfetch/network.ts +614 -0
- package/src/tools/smartfetch/secondary-model.test.ts +85 -0
- package/src/tools/smartfetch/secondary-model.ts +276 -0
- package/src/tools/smartfetch/tool.test.ts +60 -0
- package/src/tools/smartfetch/tool.ts +832 -0
- package/src/tools/smartfetch/types.ts +135 -0
- package/src/tools/smartfetch/utils.test.ts +24 -0
- package/src/tools/smartfetch/utils.ts +456 -0
- package/src/tui-state.test.ts +867 -0
- package/src/tui-state.ts +1255 -0
- package/src/tui.test.ts +336 -0
- package/src/tui.ts +1539 -0
- package/src/utils/agent-variant.test.ts +244 -0
- package/src/utils/agent-variant.ts +187 -0
- package/src/utils/compat.ts +91 -0
- package/src/utils/env.ts +12 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/internal-initiator.ts +28 -0
- package/src/utils/logger.test.ts +220 -0
- package/src/utils/logger.ts +136 -0
- package/src/utils/polling.test.ts +191 -0
- package/src/utils/polling.ts +67 -0
- package/src/utils/session-manager.test.ts +173 -0
- package/src/utils/session-manager.ts +356 -0
- package/src/utils/session.test.ts +110 -0
- package/src/utils/session.ts +389 -0
- package/src/utils/subagent-depth.test.ts +170 -0
- package/src/utils/subagent-depth.ts +75 -0
- package/src/utils/system-collapse.test.ts +86 -0
- package/src/utils/system-collapse.ts +24 -0
- package/src/utils/task.test.ts +24 -0
- package/src/utils/task.ts +20 -0
- package/src/utils/zip-extractor.ts +102 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import {
|
|
4
|
+
addPluginToOpenCodeConfig,
|
|
5
|
+
addPluginToOpenCodeTuiConfig,
|
|
6
|
+
detectCurrentConfig,
|
|
7
|
+
disableDefaultAgents,
|
|
8
|
+
enableLspByDefault,
|
|
9
|
+
generateLiteConfig,
|
|
10
|
+
getOpenCodePath,
|
|
11
|
+
getOpenCodeVersion,
|
|
12
|
+
isOpenCodeInstalled,
|
|
13
|
+
writeLiteConfig,
|
|
14
|
+
} from './config-manager';
|
|
15
|
+
import { getExistingLiteConfigPath } from './paths';
|
|
16
|
+
import type { ConfigMergeResult, InstallArgs, InstallConfig } from './types';
|
|
17
|
+
|
|
18
|
+
// Colors
|
|
19
|
+
const GREEN = '\x1b[32m';
|
|
20
|
+
const BLUE = '\x1b[34m';
|
|
21
|
+
const YELLOW = '\x1b[33m';
|
|
22
|
+
const RED = '\x1b[31m';
|
|
23
|
+
const BOLD = '\x1b[1m';
|
|
24
|
+
const DIM = '\x1b[2m';
|
|
25
|
+
const RESET = '\x1b[0m';
|
|
26
|
+
|
|
27
|
+
const SYMBOLS = {
|
|
28
|
+
check: `${GREEN}[ok]${RESET}`,
|
|
29
|
+
cross: `${RED}[x]${RESET}`,
|
|
30
|
+
arrow: `${BLUE}->${RESET}`,
|
|
31
|
+
bullet: `${DIM}-${RESET}`,
|
|
32
|
+
info: `${BLUE}[i]${RESET}`,
|
|
33
|
+
warn: `${YELLOW}[!]${RESET}`,
|
|
34
|
+
star: `${YELLOW}★${RESET}`,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const GITHUB_REPO = 'bakhtiar-personal-work/opencode-dux';
|
|
38
|
+
const GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
|
|
39
|
+
|
|
40
|
+
function printHeader(isUpdate: boolean): void {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(
|
|
43
|
+
`${BOLD}opencode-dux ${isUpdate ? 'Update' : 'Install'}${RESET}`,
|
|
44
|
+
);
|
|
45
|
+
console.log('='.repeat(30));
|
|
46
|
+
console.log();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function printStep(step: number, total: number, message: string): void {
|
|
50
|
+
console.log(`${DIM}[${step}/${total}]${RESET} ${message}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printSuccess(message: string): void {
|
|
54
|
+
console.log(`${SYMBOLS.check} ${message}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printError(message: string): void {
|
|
58
|
+
console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printInfo(message: string): void {
|
|
62
|
+
console.log(`${SYMBOLS.info} ${message}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function confirm(message: string, defaultYes = true): Promise<boolean> {
|
|
66
|
+
const suffix = defaultYes ? ' (Y/n) ' : ' (y/N) ';
|
|
67
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const answer = (await rl.question(`${message}${suffix}`))
|
|
71
|
+
.trim()
|
|
72
|
+
.toLowerCase();
|
|
73
|
+
if (!answer) return defaultYes;
|
|
74
|
+
return answer === 'y' || answer === 'yes';
|
|
75
|
+
} finally {
|
|
76
|
+
rl.close();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function askToStarRepo(config: InstallConfig): Promise<void> {
|
|
81
|
+
if (!config.promptForStar || config.dryRun || !process.stdin.isTTY) return;
|
|
82
|
+
|
|
83
|
+
console.log();
|
|
84
|
+
const shouldStar = await confirm(
|
|
85
|
+
`${SYMBOLS.star} Star the repo on GitHub?`,
|
|
86
|
+
true,
|
|
87
|
+
);
|
|
88
|
+
if (!shouldStar) return;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const { execFileSync } = await import('node:child_process');
|
|
92
|
+
execFileSync(
|
|
93
|
+
'gh',
|
|
94
|
+
['api', '--silent', '--method', 'PUT', `/user/starred/${GITHUB_REPO}`],
|
|
95
|
+
{ stdio: 'ignore', timeout: 10_000 },
|
|
96
|
+
);
|
|
97
|
+
printSuccess('Thanks for starring! ★');
|
|
98
|
+
} catch {
|
|
99
|
+
printInfo(
|
|
100
|
+
`Couldn't star automatically. You can star manually:\n ${BLUE}${GITHUB_URL}${RESET}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function checkOpenCodeInstalled(): Promise<{
|
|
106
|
+
ok: boolean;
|
|
107
|
+
version?: string;
|
|
108
|
+
path?: string;
|
|
109
|
+
}> {
|
|
110
|
+
const installed = await isOpenCodeInstalled();
|
|
111
|
+
if (!installed) {
|
|
112
|
+
printError('OpenCode is not installed on this system.');
|
|
113
|
+
printInfo('Install it with:');
|
|
114
|
+
console.log(
|
|
115
|
+
` ${BLUE}curl -fsSL https://opencode.ai/install | bash${RESET}`,
|
|
116
|
+
);
|
|
117
|
+
console.log();
|
|
118
|
+
printInfo('Or if already installed, add it to your PATH:');
|
|
119
|
+
console.log(` ${BLUE}export PATH="$HOME/.local/bin:$PATH"${RESET}`);
|
|
120
|
+
console.log(` ${BLUE}export PATH="$HOME/.opencode/bin:$PATH"${RESET}`);
|
|
121
|
+
return { ok: false };
|
|
122
|
+
}
|
|
123
|
+
const version = await getOpenCodeVersion();
|
|
124
|
+
const path = getOpenCodePath();
|
|
125
|
+
const detectedVersion = version ?? '';
|
|
126
|
+
const pathInfo = path ? ` (${DIM}${path}${RESET})` : '';
|
|
127
|
+
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
128
|
+
return { ok: true, version: version ?? undefined, path: path ?? undefined };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handleStepResult(
|
|
132
|
+
result: ConfigMergeResult,
|
|
133
|
+
successMsg: string,
|
|
134
|
+
): boolean {
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
printError(`Failed: ${result.error}`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
printSuccess(
|
|
140
|
+
`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`,
|
|
141
|
+
);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function runInstall(config: InstallConfig): Promise<number> {
|
|
146
|
+
const detected = detectCurrentConfig();
|
|
147
|
+
const isUpdate = detected.isInstalled;
|
|
148
|
+
|
|
149
|
+
printHeader(isUpdate);
|
|
150
|
+
|
|
151
|
+
const totalSteps = 6;
|
|
152
|
+
|
|
153
|
+
let step = 1;
|
|
154
|
+
|
|
155
|
+
printStep(step++, totalSteps, 'Checking OpenCode installation...');
|
|
156
|
+
if (config.dryRun) {
|
|
157
|
+
printInfo('Dry run mode - skipping OpenCode check');
|
|
158
|
+
} else {
|
|
159
|
+
const { ok } = await checkOpenCodeInstalled();
|
|
160
|
+
if (!ok) return 1;
|
|
161
|
+
}
|
|
162
|
+
printStep(step++, totalSteps, 'Adding opencode-dux plugin...');
|
|
163
|
+
if (config.dryRun) {
|
|
164
|
+
printInfo('Dry run mode - skipping plugin installation');
|
|
165
|
+
} else {
|
|
166
|
+
const pluginResult = await addPluginToOpenCodeConfig();
|
|
167
|
+
if (!handleStepResult(pluginResult, 'Plugin added')) return 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
printStep(step++, totalSteps, 'Adding TUI version badge...');
|
|
171
|
+
if (config.dryRun) {
|
|
172
|
+
printInfo('Dry run mode - skipping TUI plugin installation');
|
|
173
|
+
} else {
|
|
174
|
+
const tuiResult = await addPluginToOpenCodeTuiConfig();
|
|
175
|
+
if (!tuiResult.success) {
|
|
176
|
+
printInfo(`Skipped TUI badge: ${tuiResult.error}`);
|
|
177
|
+
} else {
|
|
178
|
+
handleStepResult(tuiResult, 'TUI badge added');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
printStep(step++, totalSteps, 'Disabling OpenCode default agents...');
|
|
183
|
+
if (config.dryRun) {
|
|
184
|
+
printInfo('Dry run mode - skipping agent disabling');
|
|
185
|
+
} else {
|
|
186
|
+
const agentResult = disableDefaultAgents();
|
|
187
|
+
if (!handleStepResult(agentResult, 'Default agents disabled')) return 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
printStep(step++, totalSteps, 'Enabling OpenCode LSP integration...');
|
|
191
|
+
if (config.dryRun) {
|
|
192
|
+
printInfo('Dry run mode - skipping LSP configuration');
|
|
193
|
+
} else {
|
|
194
|
+
const lspResult = enableLspByDefault();
|
|
195
|
+
if (!handleStepResult(lspResult, 'LSP enabled')) return 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
printStep(step++, totalSteps, 'Writing opencode-dux configuration...');
|
|
199
|
+
if (config.dryRun) {
|
|
200
|
+
const liteConfig = generateLiteConfig(config);
|
|
201
|
+
printInfo('Dry run mode - configuration that would be written:');
|
|
202
|
+
console.log(`\n${JSON.stringify(liteConfig, null, 2)}\n`);
|
|
203
|
+
} else {
|
|
204
|
+
const configPath = getExistingLiteConfigPath();
|
|
205
|
+
const configExists = existsSync(configPath);
|
|
206
|
+
|
|
207
|
+
if (configExists && !config.reset) {
|
|
208
|
+
printInfo(
|
|
209
|
+
`Configuration already exists at ${configPath}. ` +
|
|
210
|
+
'Use --reset to overwrite.',
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
const liteResult = writeLiteConfig(
|
|
214
|
+
config,
|
|
215
|
+
configExists ? configPath : undefined,
|
|
216
|
+
);
|
|
217
|
+
if (
|
|
218
|
+
!handleStepResult(
|
|
219
|
+
liteResult,
|
|
220
|
+
configExists ? 'Config reset' : 'Config written',
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const statusMsg = isUpdate
|
|
228
|
+
? 'Configuration updated!'
|
|
229
|
+
: 'Installation complete!';
|
|
230
|
+
console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${statusMsg}${RESET}`);
|
|
231
|
+
console.log();
|
|
232
|
+
console.log(`${BOLD}Next steps:${RESET}`);
|
|
233
|
+
console.log();
|
|
234
|
+
|
|
235
|
+
const configPath = getExistingLiteConfigPath();
|
|
236
|
+
|
|
237
|
+
console.log(' 1. Log in to the provider(s) you want to use:');
|
|
238
|
+
console.log(` ${BLUE}$ opencode auth login${RESET}`);
|
|
239
|
+
console.log();
|
|
240
|
+
console.log(' 2. Refresh the models OpenCode can see:');
|
|
241
|
+
console.log(` ${BLUE}$ opencode models --refresh${RESET}`);
|
|
242
|
+
console.log();
|
|
243
|
+
console.log(' 3. Review your generated config:');
|
|
244
|
+
console.log(` ${BLUE}${configPath}${RESET}`);
|
|
245
|
+
console.log();
|
|
246
|
+
console.log(' 4. Start OpenCode:');
|
|
247
|
+
console.log(` ${BLUE}$ opencode${RESET}`);
|
|
248
|
+
console.log();
|
|
249
|
+
console.log(' 5. Verify the agents are responding:');
|
|
250
|
+
console.log(` ${BLUE}> ping all agents${RESET}`);
|
|
251
|
+
console.log();
|
|
252
|
+
|
|
253
|
+
const modelsInfo =
|
|
254
|
+
config.preset && config.preset !== 'openai'
|
|
255
|
+
? `Generated OpenAI and OpenCode Go presets; ${config.preset} is active.`
|
|
256
|
+
: 'Generated OpenAI and OpenCode Go presets; OpenAI is active by default.';
|
|
257
|
+
console.log(`${modelsInfo}`);
|
|
258
|
+
const altProviders = 'For the full configuration reference, see:';
|
|
259
|
+
console.log(altProviders);
|
|
260
|
+
const docsUrl =
|
|
261
|
+
'https://github.com/bakhtiar-personal-work/opencode-dux/' +
|
|
262
|
+
'blob/master/docs/configuration.md';
|
|
263
|
+
console.log(` ${BLUE}${docsUrl}${RESET}`);
|
|
264
|
+
console.log();
|
|
265
|
+
|
|
266
|
+
await askToStarRepo(config);
|
|
267
|
+
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function install(args: InstallArgs): Promise<number> {
|
|
272
|
+
const config: InstallConfig = {
|
|
273
|
+
installSkills: false,
|
|
274
|
+
installCustomSkills: false,
|
|
275
|
+
preset: args.preset,
|
|
276
|
+
promptForStar: args.tui,
|
|
277
|
+
dryRun: args.dryRun,
|
|
278
|
+
reset: args.reset ?? false,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return runInstall(config);
|
|
282
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { getMcpPermissionsForAgent } from './mcps';
|
|
3
|
+
|
|
4
|
+
describe('MCP permissions', () => {
|
|
5
|
+
it('should allow all MCPs by default', async () => {
|
|
6
|
+
const permissions = await getMcpPermissionsForAgent('oracle');
|
|
7
|
+
expect(permissions['*']).toBe('allow');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should handle empty array', async () => {
|
|
11
|
+
const permissions = await getMcpPermissionsForAgent('oracle', []);
|
|
12
|
+
expect(permissions['*']).toBe('deny');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should handle explicit MCP list', async () => {
|
|
16
|
+
const permissions = await getMcpPermissionsForAgent('oracle', [
|
|
17
|
+
'websearch',
|
|
18
|
+
'context7',
|
|
19
|
+
]);
|
|
20
|
+
expect(permissions['*']).toBe('deny');
|
|
21
|
+
expect(permissions.websearch).toBe('allow');
|
|
22
|
+
expect(permissions.context7).toBe('allow');
|
|
23
|
+
expect(permissions.grep_app).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle wildcard in array', async () => {
|
|
27
|
+
const permissions = await getMcpPermissionsForAgent('oracle', ['*']);
|
|
28
|
+
expect(permissions['*']).toBe('allow');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle exclusion in array', async () => {
|
|
32
|
+
const permissions = await getMcpPermissionsForAgent('oracle', [
|
|
33
|
+
'*',
|
|
34
|
+
'!grep_app',
|
|
35
|
+
]);
|
|
36
|
+
expect(permissions['*']).toBe('allow');
|
|
37
|
+
expect(permissions.grep_app).toBe('deny');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle object syntax with always-load', async () => {
|
|
41
|
+
const permissions = await getMcpPermissionsForAgent('oracle', {
|
|
42
|
+
'always-load': ['websearch'],
|
|
43
|
+
});
|
|
44
|
+
expect(permissions['*']).toBe('deny');
|
|
45
|
+
expect(permissions.websearch).toBe('allow');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle deprecated mandatory fallback', async () => {
|
|
49
|
+
const permissions = await getMcpPermissionsForAgent('oracle', {
|
|
50
|
+
mandatory: ['websearch'],
|
|
51
|
+
});
|
|
52
|
+
expect(permissions['*']).toBe('deny');
|
|
53
|
+
expect(permissions.websearch).toBe('allow');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle object syntax with wildcard', async () => {
|
|
57
|
+
const permissions = await getMcpPermissionsForAgent('oracle', {
|
|
58
|
+
wildcard: true,
|
|
59
|
+
});
|
|
60
|
+
expect(permissions['*']).toBe('allow');
|
|
61
|
+
});
|
|
62
|
+
});
|
package/src/cli/mcps.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SkillOrMcpConfig } from '../config';
|
|
2
|
+
import { type NormalizedSkillConfig, normalizeSkillConfig } from './skills';
|
|
3
|
+
|
|
4
|
+
export type { NormalizedSkillConfig as NormalizedMcpConfig };
|
|
5
|
+
// Reuse normalizeSkillConfig for McpConfig since it has the same shape
|
|
6
|
+
export { normalizeSkillConfig as normalizeMcpConfig };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get MCP permissions for a specific agent.
|
|
10
|
+
* MCP permission rules use 'allow'/'deny' per MCP name.
|
|
11
|
+
*
|
|
12
|
+
* @param agentName - The agent name
|
|
13
|
+
* @param mcpList - Optional MCP config (object or array syntax)
|
|
14
|
+
* @returns Permission rules keyed by MCP name
|
|
15
|
+
*/
|
|
16
|
+
export async function getMcpPermissionsForAgent(
|
|
17
|
+
_agentName: string,
|
|
18
|
+
mcpList?: SkillOrMcpConfig,
|
|
19
|
+
): Promise<Record<string, 'allow' | 'ask' | 'deny'>> {
|
|
20
|
+
const permissions: Record<string, 'allow' | 'ask' | 'deny'> = {};
|
|
21
|
+
|
|
22
|
+
if (mcpList === undefined) {
|
|
23
|
+
// Default: all builtin MCPs allowed for all agents
|
|
24
|
+
permissions['*'] = 'allow';
|
|
25
|
+
return permissions;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const normalized = normalizeSkillConfig(mcpList);
|
|
29
|
+
permissions['*'] = normalized.wildcard ? 'allow' : 'deny';
|
|
30
|
+
|
|
31
|
+
for (const name of normalized.alwaysLoad) {
|
|
32
|
+
permissions[name] = 'allow';
|
|
33
|
+
}
|
|
34
|
+
for (const name of normalized.excluded) {
|
|
35
|
+
permissions[name] = 'deny';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return permissions;
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { buildModelKeyAliases } from './model-key-normalization';
|
|
5
|
+
|
|
6
|
+
describe('model key normalization', () => {
|
|
7
|
+
test('normalizes multi-segment chutes model ids', () => {
|
|
8
|
+
const aliases = buildModelKeyAliases(
|
|
9
|
+
'chutes/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE',
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
expect(aliases).toContain('qwen/qwen3-coder-480b-a35b-instruct');
|
|
13
|
+
expect(aliases).toContain('qwen3-coder-480b-a35b-instruct');
|
|
14
|
+
expect(aliases).not.toContain('qwen3-coder-480b-a35b-instruct-fp8-tee');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('treats spaces and hyphens as equivalent aliases', () => {
|
|
18
|
+
const aliases = buildModelKeyAliases('Qwen3 Coder 480B A35B Instruct');
|
|
19
|
+
expect(aliases).toContain('qwen3-coder-480b-a35b-instruct');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
function cleanupAlias(input: string, preserveSlash: boolean): string {
|
|
2
|
+
let value = input.toLowerCase().trim();
|
|
3
|
+
value = value.replace(/\bfp[a-z0-9.-]*\b/g, ' ');
|
|
4
|
+
value = value.replace(/\btee\b/g, ' ');
|
|
5
|
+
|
|
6
|
+
if (preserveSlash) {
|
|
7
|
+
value = value.replace(/[_\s]+/g, '-');
|
|
8
|
+
value = value.replace(/-+/g, '-');
|
|
9
|
+
value = value.replace(/\/+/g, '/');
|
|
10
|
+
value = value.replace(/\/-+/g, '/');
|
|
11
|
+
value = value.replace(/-+\//g, '/');
|
|
12
|
+
value = value.replace(/^\/+|\/+$/g, '');
|
|
13
|
+
value = value.replace(/^-+|-+$/g, '');
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
value = value.replace(/[/_\s]+/g, '-');
|
|
18
|
+
value = value.replace(/-+/g, '-');
|
|
19
|
+
value = value.replace(/^-+|-+$/g, '');
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function addDerivedAliases(seed: string, aliases: Set<string>): void {
|
|
24
|
+
const slashAlias = cleanupAlias(seed, true);
|
|
25
|
+
const flatAlias = cleanupAlias(seed, false);
|
|
26
|
+
|
|
27
|
+
if (slashAlias) aliases.add(slashAlias);
|
|
28
|
+
if (flatAlias) aliases.add(flatAlias);
|
|
29
|
+
|
|
30
|
+
if (slashAlias) {
|
|
31
|
+
aliases.add(slashAlias.replace(/-(free|flash)$/i, ''));
|
|
32
|
+
}
|
|
33
|
+
if (flatAlias) {
|
|
34
|
+
aliases.add(flatAlias.replace(/-(free|flash)$/i, ''));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (slashAlias.includes('/')) {
|
|
38
|
+
aliases.add(cleanupAlias(slashAlias.replace(/\//g, ' '), false));
|
|
39
|
+
aliases.add(cleanupAlias(slashAlias.replace(/\//g, '-'), false));
|
|
40
|
+
const lastPart = slashAlias.split('/').at(-1);
|
|
41
|
+
if (lastPart) {
|
|
42
|
+
addDerivedAliases(lastPart, aliases);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildModelKeyAliases(input: string): string[] {
|
|
48
|
+
const normalized = input.trim().toLowerCase();
|
|
49
|
+
if (!normalized) return [];
|
|
50
|
+
|
|
51
|
+
const aliases = new Set<string>();
|
|
52
|
+
const slashIndex = normalized.indexOf('/');
|
|
53
|
+
const afterProvider =
|
|
54
|
+
slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized;
|
|
55
|
+
|
|
56
|
+
addDerivedAliases(normalized, aliases);
|
|
57
|
+
addDerivedAliases(afterProvider, aliases);
|
|
58
|
+
|
|
59
|
+
return [...aliases].filter((alias) => alias.length > 0);
|
|
60
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
4
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { homedir, tmpdir } from 'node:os';
|
|
6
|
+
import { join, normalize } from 'node:path';
|
|
7
|
+
import { platform } from 'node:os';
|
|
8
|
+
import {
|
|
9
|
+
ensureConfigDir,
|
|
10
|
+
getConfigDir,
|
|
11
|
+
getConfigJson,
|
|
12
|
+
getConfigJsonc,
|
|
13
|
+
getConfigSearchDirs,
|
|
14
|
+
getExistingConfigPath,
|
|
15
|
+
getLiteConfig,
|
|
16
|
+
getOpenCodeConfigPaths,
|
|
17
|
+
} from './paths';
|
|
18
|
+
|
|
19
|
+
function p(path: string): string {
|
|
20
|
+
return normalize(path);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('paths', () => {
|
|
24
|
+
const originalEnv = { ...process.env };
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
process.env = { ...originalEnv };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('getConfigDir() uses OPENCODE_CONFIG_DIR when set', () => {
|
|
35
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/directory';
|
|
36
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
37
|
+
expect(getConfigDir()).toBe('/custom/directory');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('getConfigDir() uses XDG_CONFIG_HOME when set', () => {
|
|
41
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
42
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
43
|
+
expect(getConfigDir()).toBe(p('/tmp/xdg-config/opencode'));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('getConfigDir() falls back to ~/.config when XDG_CONFIG_HOME is unset', () => {
|
|
47
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
48
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
49
|
+
const expected = join(homedir(), '.config', 'opencode');
|
|
50
|
+
expect(getConfigDir()).toBe(expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('getConfigSearchDirs() returns custom dir first, then default dir', () => {
|
|
54
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/directory';
|
|
55
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
56
|
+
|
|
57
|
+
expect(getConfigSearchDirs()).toEqual([
|
|
58
|
+
'/custom/directory',
|
|
59
|
+
p('/tmp/xdg-config/opencode'),
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const testDedup = platform() === 'win32' ? test.skip : test;
|
|
64
|
+
testDedup('getConfigSearchDirs() de-duplicates identical dirs', () => {
|
|
65
|
+
process.env.OPENCODE_CONFIG_DIR = '/tmp/xdg-config/opencode';
|
|
66
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
67
|
+
|
|
68
|
+
expect(getConfigSearchDirs()).toEqual([p('/tmp/xdg-config/opencode')]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('getOpenCodeConfigPaths() returns both json and jsonc paths', () => {
|
|
72
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
73
|
+
expect(getOpenCodeConfigPaths()).toEqual([
|
|
74
|
+
p('/tmp/xdg-config/opencode/opencode.json'),
|
|
75
|
+
p('/tmp/xdg-config/opencode/opencode.jsonc'),
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('getOpenCodeConfigPaths() ignores OPENCODE_CONFIG_DIR', () => {
|
|
80
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/directory';
|
|
81
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
82
|
+
expect(getOpenCodeConfigPaths()).toEqual([
|
|
83
|
+
p('/tmp/xdg-config/opencode/opencode.json'),
|
|
84
|
+
p('/tmp/xdg-config/opencode/opencode.jsonc'),
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('getConfigJson() returns correct path', () => {
|
|
89
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
90
|
+
expect(getConfigJson()).toBe(p('/tmp/xdg-config/opencode/opencode.json'));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('getConfigJsonc() returns correct path', () => {
|
|
94
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
95
|
+
expect(getConfigJsonc()).toBe(p('/tmp/xdg-config/opencode/opencode.jsonc'));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('getLiteConfig() returns correct path', () => {
|
|
99
|
+
process.env.XDG_CONFIG_HOME = '/tmp/xdg-config';
|
|
100
|
+
expect(getLiteConfig()).toBe(
|
|
101
|
+
p('/tmp/xdg-config/opencode/opencode-dux.json'),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('getLiteConfig() respects OPENCODE_CONFIG_DIR', () => {
|
|
106
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/directory';
|
|
107
|
+
expect(getLiteConfig()).toBe(
|
|
108
|
+
p('/custom/directory/opencode-dux.json'),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('getExistingConfigPath()', () => {
|
|
113
|
+
let tmpDir: string;
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
if (tmpDir && existsSync(tmpDir)) {
|
|
117
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('returns .json if it exists', () => {
|
|
122
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'opencode-test-'));
|
|
123
|
+
process.env.XDG_CONFIG_HOME = tmpDir;
|
|
124
|
+
|
|
125
|
+
const configDir = join(tmpDir, 'opencode');
|
|
126
|
+
ensureConfigDir();
|
|
127
|
+
|
|
128
|
+
const jsonPath = join(configDir, 'opencode.json');
|
|
129
|
+
writeFileSync(jsonPath, '{}');
|
|
130
|
+
|
|
131
|
+
expect(getExistingConfigPath()).toBe(jsonPath);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("returns .jsonc if .json doesn't exist but .jsonc does", () => {
|
|
135
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'opencode-test-'));
|
|
136
|
+
process.env.XDG_CONFIG_HOME = tmpDir;
|
|
137
|
+
|
|
138
|
+
const configDir = join(tmpDir, 'opencode');
|
|
139
|
+
ensureConfigDir();
|
|
140
|
+
|
|
141
|
+
const jsoncPath = join(configDir, 'opencode.jsonc');
|
|
142
|
+
writeFileSync(jsoncPath, '{}');
|
|
143
|
+
|
|
144
|
+
expect(getExistingConfigPath()).toBe(jsoncPath);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('returns default .json if neither exists', () => {
|
|
148
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'opencode-test-'));
|
|
149
|
+
process.env.XDG_CONFIG_HOME = tmpDir;
|
|
150
|
+
|
|
151
|
+
const jsonPath = join(tmpDir, 'opencode', 'opencode.json');
|
|
152
|
+
expect(getExistingConfigPath()).toBe(jsonPath);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("ensureConfigDir() creates directory if it doesn't exist", () => {
|
|
157
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'opencode-test-'));
|
|
158
|
+
process.env.XDG_CONFIG_HOME = tmpDir;
|
|
159
|
+
const configDir = join(tmpDir, 'opencode');
|
|
160
|
+
|
|
161
|
+
expect(existsSync(configDir)).toBe(false);
|
|
162
|
+
ensureConfigDir();
|
|
163
|
+
expect(existsSync(configDir)).toBe(true);
|
|
164
|
+
|
|
165
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
166
|
+
});
|
|
167
|
+
});
|