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,473 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from 'node:fs';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import {
|
|
11
|
+
ensureConfigDir,
|
|
12
|
+
ensureOpenCodeConfigDir,
|
|
13
|
+
ensureTuiConfigDir,
|
|
14
|
+
getExistingConfigPath,
|
|
15
|
+
getExistingTuiConfigPath,
|
|
16
|
+
getLiteConfig,
|
|
17
|
+
} from './paths';
|
|
18
|
+
import { generateLiteConfig } from './providers';
|
|
19
|
+
import type {
|
|
20
|
+
ConfigMergeResult,
|
|
21
|
+
DetectedConfig,
|
|
22
|
+
InstallConfig,
|
|
23
|
+
OpenCodeConfig,
|
|
24
|
+
} from './types';
|
|
25
|
+
|
|
26
|
+
const PACKAGE_NAME = 'opencode-dux';
|
|
27
|
+
|
|
28
|
+
function isString(value: unknown): value is string {
|
|
29
|
+
return typeof value === 'string';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getPlugins(config: OpenCodeConfig): unknown[] {
|
|
33
|
+
return Array.isArray(config.plugin) ? config.plugin : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getPluginEntries(config: OpenCodeConfig): string[] {
|
|
37
|
+
return getPlugins(config).filter(isString);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getPluginSpec(entry: unknown): string | undefined {
|
|
41
|
+
if (isString(entry)) return entry;
|
|
42
|
+
if (!Array.isArray(entry)) return undefined;
|
|
43
|
+
|
|
44
|
+
const spec = entry[0];
|
|
45
|
+
return isString(spec) ? spec : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizePathForMatch(path: string): string {
|
|
49
|
+
return path.replaceAll('\\', '/');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function findPackageRoot(startPath: string): string | null {
|
|
53
|
+
let currentPath = dirname(startPath);
|
|
54
|
+
|
|
55
|
+
while (true) {
|
|
56
|
+
const packageJsonPath = join(currentPath, 'package.json');
|
|
57
|
+
|
|
58
|
+
if (existsSync(packageJsonPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const packageJson = JSON.parse(
|
|
61
|
+
readFileSync(packageJsonPath, 'utf-8'),
|
|
62
|
+
) as {
|
|
63
|
+
name?: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (packageJson.name === PACKAGE_NAME) {
|
|
67
|
+
return currentPath;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore invalid package.json while walking upward.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parentPath = dirname(currentPath);
|
|
75
|
+
if (parentPath === currentPath) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
currentPath = parentPath;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isPackageManagerInstall(path: string): boolean {
|
|
83
|
+
const normalizedPath = normalizePathForMatch(path);
|
|
84
|
+
return normalizedPath.includes(`/node_modules/${PACKAGE_NAME}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isLocalPackageRootEntry(entry: string): boolean {
|
|
88
|
+
if (!entry || entry.startsWith('file://')) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const packageJsonPath = join(entry, 'package.json');
|
|
93
|
+
if (!existsSync(packageJsonPath)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
|
|
99
|
+
name?: string;
|
|
100
|
+
};
|
|
101
|
+
return packageJson.name === PACKAGE_NAME;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isPluginEntry(entry: string): boolean {
|
|
108
|
+
return (
|
|
109
|
+
entry === PACKAGE_NAME ||
|
|
110
|
+
entry.startsWith(`${PACKAGE_NAME}@`) ||
|
|
111
|
+
(entry.startsWith('file://') && entry.includes(PACKAGE_NAME)) ||
|
|
112
|
+
isLocalPackageRootEntry(entry)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isMatchingPluginEntry(entry: unknown): boolean {
|
|
117
|
+
const spec = getPluginSpec(entry);
|
|
118
|
+
return spec ? isPluginEntry(spec) : false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getPluginEntry(): string {
|
|
122
|
+
const cliEntryPath = process.argv[1];
|
|
123
|
+
|
|
124
|
+
if (!cliEntryPath) {
|
|
125
|
+
return PACKAGE_NAME;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const packageRoot = findPackageRoot(cliEntryPath);
|
|
130
|
+
|
|
131
|
+
if (!packageRoot || isPackageManagerInstall(packageRoot)) {
|
|
132
|
+
return PACKAGE_NAME;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return packageRoot;
|
|
136
|
+
} catch {
|
|
137
|
+
return PACKAGE_NAME;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Strip JSON comments (single-line // and multi-line) and trailing commas for JSONC support.
|
|
143
|
+
*/
|
|
144
|
+
export function stripJsonComments(json: string): string {
|
|
145
|
+
const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
|
|
146
|
+
const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
|
|
147
|
+
|
|
148
|
+
return json
|
|
149
|
+
.replace(commentPattern, (match, commentGroup) =>
|
|
150
|
+
commentGroup ? '' : match,
|
|
151
|
+
)
|
|
152
|
+
.replace(trailingCommaPattern, (match, comma, closing) =>
|
|
153
|
+
comma ? closing : match,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function parseConfigFile(path: string): {
|
|
158
|
+
config: OpenCodeConfig | null;
|
|
159
|
+
error?: string;
|
|
160
|
+
} {
|
|
161
|
+
try {
|
|
162
|
+
if (!existsSync(path)) return { config: null };
|
|
163
|
+
const stat = statSync(path);
|
|
164
|
+
if (stat.size === 0) return { config: null };
|
|
165
|
+
const content = readFileSync(path, 'utf-8');
|
|
166
|
+
if (content.trim().length === 0) return { config: null };
|
|
167
|
+
return { config: JSON.parse(stripJsonComments(content)) as OpenCodeConfig };
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return { config: null, error: String(err) };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function parseConfig(path: string): {
|
|
174
|
+
config: OpenCodeConfig | null;
|
|
175
|
+
error?: string;
|
|
176
|
+
} {
|
|
177
|
+
const result = parseConfigFile(path);
|
|
178
|
+
if (result.config || result.error) return result;
|
|
179
|
+
|
|
180
|
+
if (path.endsWith('.json')) {
|
|
181
|
+
const jsoncPath = path.replace(/\.json$/, '.jsonc');
|
|
182
|
+
return parseConfigFile(jsoncPath);
|
|
183
|
+
}
|
|
184
|
+
return { config: null };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Write config to file atomically.
|
|
189
|
+
*/
|
|
190
|
+
export function writeConfig(configPath: string, config: OpenCodeConfig): void {
|
|
191
|
+
if (configPath.endsWith('.jsonc')) {
|
|
192
|
+
console.warn(
|
|
193
|
+
'[config-manager] Writing to .jsonc file - comments will not be preserved',
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const tmpPath = `${configPath}.tmp`;
|
|
198
|
+
const bakPath = `${configPath}.bak`;
|
|
199
|
+
const content = `${JSON.stringify(config, null, 2)}\n`;
|
|
200
|
+
|
|
201
|
+
// Backup existing config if it exists
|
|
202
|
+
if (existsSync(configPath)) {
|
|
203
|
+
copyFileSync(configPath, bakPath);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Atomic write pattern: write to tmp, then rename
|
|
207
|
+
writeFileSync(tmpPath, content);
|
|
208
|
+
renameSync(tmpPath, configPath);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function addPluginToOpenCodeConfig(): Promise<ConfigMergeResult> {
|
|
212
|
+
const configPath = getExistingConfigPath();
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
ensureOpenCodeConfigDir();
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
configPath,
|
|
220
|
+
error: `Failed to create config directory: ${err}`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
226
|
+
if (error) {
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
configPath,
|
|
230
|
+
error: `Failed to parse config: ${error}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const config = parsedConfig ?? {};
|
|
234
|
+
const plugins = getPlugins(config);
|
|
235
|
+
|
|
236
|
+
const pluginEntry = getPluginEntry();
|
|
237
|
+
|
|
238
|
+
// Remove existing opencode-dux entries
|
|
239
|
+
const filteredPlugins = plugins.filter(
|
|
240
|
+
(plugin) => !isMatchingPluginEntry(plugin),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Add fresh entry
|
|
244
|
+
filteredPlugins.push(pluginEntry);
|
|
245
|
+
config.plugin = filteredPlugins;
|
|
246
|
+
|
|
247
|
+
writeConfig(configPath, config);
|
|
248
|
+
return { success: true, configPath };
|
|
249
|
+
} catch (err) {
|
|
250
|
+
return {
|
|
251
|
+
success: false,
|
|
252
|
+
configPath,
|
|
253
|
+
error: `Failed to update opencode config: ${err}`,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function addPluginToOpenCodeTuiConfig(): Promise<ConfigMergeResult> {
|
|
259
|
+
const configPath = getExistingTuiConfigPath();
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
ensureTuiConfigDir();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
configPath,
|
|
267
|
+
error: `Failed to create config directory: ${err}`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
273
|
+
if (error) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
configPath,
|
|
277
|
+
error: `Failed to parse TUI config: ${error}`,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const config = parsedConfig ?? {};
|
|
281
|
+
const plugins = getPlugins(config);
|
|
282
|
+
const pluginEntry = getPluginEntry();
|
|
283
|
+
const filteredPlugins = plugins.filter(
|
|
284
|
+
(plugin) => !isMatchingPluginEntry(plugin),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
filteredPlugins.push(pluginEntry);
|
|
288
|
+
config.plugin = filteredPlugins;
|
|
289
|
+
|
|
290
|
+
writeConfig(configPath, config);
|
|
291
|
+
return { success: true, configPath };
|
|
292
|
+
} catch (err) {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
configPath,
|
|
296
|
+
error: `Failed to update opencode TUI config: ${err}`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Removed: addAuthPlugins - no longer needed with cliproxy
|
|
302
|
+
// Removed: addProviderConfig - default opencode now has kimi provider config
|
|
303
|
+
|
|
304
|
+
export function writeLiteConfig(
|
|
305
|
+
installConfig: InstallConfig,
|
|
306
|
+
targetPath?: string,
|
|
307
|
+
): ConfigMergeResult {
|
|
308
|
+
const configPath = targetPath ?? getLiteConfig();
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
ensureConfigDir();
|
|
312
|
+
const config = generateLiteConfig(installConfig);
|
|
313
|
+
|
|
314
|
+
// Atomic write for lite config too
|
|
315
|
+
const tmpPath = `${configPath}.tmp`;
|
|
316
|
+
const bakPath = `${configPath}.bak`;
|
|
317
|
+
const content = `${JSON.stringify(config, null, 2)}\n`;
|
|
318
|
+
|
|
319
|
+
// Backup existing config if it exists
|
|
320
|
+
if (existsSync(configPath)) {
|
|
321
|
+
copyFileSync(configPath, bakPath);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
writeFileSync(tmpPath, content);
|
|
325
|
+
renameSync(tmpPath, configPath);
|
|
326
|
+
|
|
327
|
+
return { success: true, configPath };
|
|
328
|
+
} catch (err) {
|
|
329
|
+
return {
|
|
330
|
+
success: false,
|
|
331
|
+
configPath,
|
|
332
|
+
error: `Failed to write lite config: ${err}`,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function disableDefaultAgents(): ConfigMergeResult {
|
|
338
|
+
const configPath = getExistingConfigPath();
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
ensureOpenCodeConfigDir();
|
|
342
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
343
|
+
if (error) {
|
|
344
|
+
return {
|
|
345
|
+
success: false,
|
|
346
|
+
configPath,
|
|
347
|
+
error: `Failed to parse config: ${error}`,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const config = parsedConfig ?? {};
|
|
351
|
+
|
|
352
|
+
const agent = (config.agent ?? {}) as Record<string, unknown>;
|
|
353
|
+
agent.explore = { disable: true };
|
|
354
|
+
agent.general = { disable: true };
|
|
355
|
+
config.agent = agent;
|
|
356
|
+
|
|
357
|
+
writeConfig(configPath, config);
|
|
358
|
+
return { success: true, configPath };
|
|
359
|
+
} catch (err) {
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
configPath,
|
|
363
|
+
error: `Failed to disable default agents: ${err}`,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function enableLspByDefault(): ConfigMergeResult {
|
|
369
|
+
const configPath = getExistingConfigPath();
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
ensureOpenCodeConfigDir();
|
|
373
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
374
|
+
if (error) {
|
|
375
|
+
return {
|
|
376
|
+
success: false,
|
|
377
|
+
configPath,
|
|
378
|
+
error: `Failed to parse config: ${error}`,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const config = parsedConfig ?? {};
|
|
382
|
+
|
|
383
|
+
if (config.lsp === undefined) {
|
|
384
|
+
config.lsp = true;
|
|
385
|
+
writeConfig(configPath, config);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return { success: true, configPath };
|
|
389
|
+
} catch (err) {
|
|
390
|
+
return {
|
|
391
|
+
success: false,
|
|
392
|
+
configPath,
|
|
393
|
+
error: `Failed to enable LSP: ${err}`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function canModifyOpenCodeConfig(): boolean {
|
|
399
|
+
try {
|
|
400
|
+
const configPath = getExistingConfigPath();
|
|
401
|
+
if (!existsSync(configPath)) return true; // Will be created
|
|
402
|
+
const stat = statSync(configPath);
|
|
403
|
+
// Check if writable - simple check for now
|
|
404
|
+
return !!(stat.mode & 0o200);
|
|
405
|
+
} catch {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Antigravity, Google provider, and Chutes provider functions removed in simplification refactor.
|
|
411
|
+
|
|
412
|
+
export function detectCurrentConfig(): DetectedConfig {
|
|
413
|
+
const result: DetectedConfig = {
|
|
414
|
+
isInstalled: false,
|
|
415
|
+
hasKimi: false,
|
|
416
|
+
hasOpenAI: false,
|
|
417
|
+
hasAnthropic: false,
|
|
418
|
+
hasCopilot: false,
|
|
419
|
+
hasZaiPlan: false,
|
|
420
|
+
hasAntigravity: false,
|
|
421
|
+
hasChutes: false,
|
|
422
|
+
hasOpencodeZen: false,
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const { config } = parseConfig(getExistingConfigPath());
|
|
426
|
+
if (!config) return result;
|
|
427
|
+
|
|
428
|
+
const plugins = getPluginEntries(config);
|
|
429
|
+
result.isInstalled = plugins.some((p) => isPluginEntry(p));
|
|
430
|
+
result.hasAntigravity = plugins.some((p) =>
|
|
431
|
+
p.startsWith('opencode-antigravity-auth'),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
// Check for providers
|
|
435
|
+
const providers = config.provider as Record<string, unknown> | undefined;
|
|
436
|
+
result.hasKimi = !!providers?.kimi;
|
|
437
|
+
result.hasAnthropic = !!providers?.anthropic;
|
|
438
|
+
result.hasCopilot = !!providers?.['github-copilot'];
|
|
439
|
+
result.hasZaiPlan = !!providers?.['zai-coding-plan'];
|
|
440
|
+
result.hasChutes = !!providers?.chutes;
|
|
441
|
+
if (providers?.google) result.hasAntigravity = true;
|
|
442
|
+
|
|
443
|
+
// Try to detect from lite config
|
|
444
|
+
const { config: liteConfig } = parseConfig(getLiteConfig());
|
|
445
|
+
if (liteConfig && typeof liteConfig === 'object') {
|
|
446
|
+
const configObj = liteConfig as Record<string, unknown>;
|
|
447
|
+
const presetName = configObj.preset as string;
|
|
448
|
+
const presets = configObj.presets as Record<string, unknown>;
|
|
449
|
+
const agents = presets?.[presetName] as
|
|
450
|
+
| Record<string, { model?: string }>
|
|
451
|
+
| undefined;
|
|
452
|
+
|
|
453
|
+
if (agents) {
|
|
454
|
+
const models = Object.values(agents)
|
|
455
|
+
.map((a) => a?.model)
|
|
456
|
+
.filter(Boolean);
|
|
457
|
+
result.hasOpenAI = models.some((m) => m?.startsWith('openai/'));
|
|
458
|
+
result.hasAnthropic = models.some((m) => m?.startsWith('anthropic/'));
|
|
459
|
+
result.hasCopilot = models.some((m) => m?.startsWith('github-copilot/'));
|
|
460
|
+
result.hasZaiPlan = models.some((m) => m?.startsWith('zai-coding-plan/'));
|
|
461
|
+
result.hasOpencodeZen = models.some((m) => m?.startsWith('opencode/'));
|
|
462
|
+
if (models.some((m) => m?.startsWith('google/'))) {
|
|
463
|
+
result.hasAntigravity = true;
|
|
464
|
+
}
|
|
465
|
+
if (models.some((m) => m?.startsWith('chutes/'))) {
|
|
466
|
+
result.hasChutes = true;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { stripJsonComments } from './config-manager';
|
|
5
|
+
|
|
6
|
+
describe('config-manager (barrel)', () => {
|
|
7
|
+
describe('stripJsonComments', () => {
|
|
8
|
+
test('returns unchanged JSON without comments', () => {
|
|
9
|
+
const json = '{"key": "value"}';
|
|
10
|
+
expect(stripJsonComments(json)).toBe(json);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('strips single-line comments', () => {
|
|
14
|
+
const json = `{
|
|
15
|
+
"key": "value" // this is a comment
|
|
16
|
+
}`;
|
|
17
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ key: 'value' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('strips multi-line comments', () => {
|
|
21
|
+
const json = `{
|
|
22
|
+
/* this is a
|
|
23
|
+
multi-line comment */
|
|
24
|
+
"key": "value"
|
|
25
|
+
}`;
|
|
26
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ key: 'value' });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('strips trailing commas', () => {
|
|
30
|
+
const json = `{
|
|
31
|
+
"key": "value",
|
|
32
|
+
}`;
|
|
33
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ key: 'value' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('strips trailing commas in arrays', () => {
|
|
37
|
+
const json = `{
|
|
38
|
+
"arr": [1, 2, 3,]
|
|
39
|
+
}`;
|
|
40
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ arr: [1, 2, 3] });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('preserves URLs with double slashes', () => {
|
|
44
|
+
const json = '{"url": "https://example.com"}';
|
|
45
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({
|
|
46
|
+
url: 'https://example.com',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('preserves strings containing comment-like patterns', () => {
|
|
51
|
+
const json = '{"code": "// not a comment", "block": "/* also not */"}';
|
|
52
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({
|
|
53
|
+
code: '// not a comment',
|
|
54
|
+
block: '/* also not */',
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('handles complex JSONC with mixed comments and trailing commas', () => {
|
|
59
|
+
const json = `{
|
|
60
|
+
// Configuration for the plugin
|
|
61
|
+
"plugin": ["opencode-dux"],
|
|
62
|
+
/* Provider settings
|
|
63
|
+
with multiple lines */
|
|
64
|
+
"provider": {
|
|
65
|
+
"google": {
|
|
66
|
+
"name": "Google", // inline comment
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}`;
|
|
70
|
+
const result = JSON.parse(stripJsonComments(json));
|
|
71
|
+
expect(result).toEqual({
|
|
72
|
+
plugin: ['opencode-dux'],
|
|
73
|
+
provider: {
|
|
74
|
+
google: {
|
|
75
|
+
name: 'Google',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('handles escaped quotes in strings', () => {
|
|
82
|
+
const json = '{"message": "He said \\"hello\\""}';
|
|
83
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({
|
|
84
|
+
message: 'He said "hello"',
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('handles empty input', () => {
|
|
89
|
+
expect(stripJsonComments('')).toBe('');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('handles whitespace-only input', () => {
|
|
93
|
+
expect(stripJsonComments(' ')).toBe(' ');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('handles single-line comment at start of file', () => {
|
|
97
|
+
const json = `// comment at start
|
|
98
|
+
{"key": "value"}`;
|
|
99
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ key: 'value' });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('handles comment-only lines between properties', () => {
|
|
103
|
+
const json = `{
|
|
104
|
+
"a": 1,
|
|
105
|
+
// comment line
|
|
106
|
+
"b": 2
|
|
107
|
+
}`;
|
|
108
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ a: 1, b: 2 });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('handles multiple trailing commas in nested structures', () => {
|
|
112
|
+
const json = `{"nested": {"a": 1,},}`;
|
|
113
|
+
expect(JSON.parse(stripJsonComments(json))).toEqual({ nested: { a: 1 } });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('handles unclosed string gracefully without throwing', () => {
|
|
117
|
+
const json = '{"key": "unclosed';
|
|
118
|
+
expect(() => stripJsonComments(json)).not.toThrow();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('preserves comma-bracket patterns inside strings', () => {
|
|
122
|
+
const json = '{"script": "test [,]", "json": "{,}"}';
|
|
123
|
+
const result = JSON.parse(stripJsonComments(json));
|
|
124
|
+
expect(result.script).toBe('test [,]');
|
|
125
|
+
expect(result.json).toBe('{,}');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('preserves comma-brace patterns inside strings', () => {
|
|
129
|
+
const json = '{"glob": "*.{js,ts}", "arr": "[a,]"}';
|
|
130
|
+
const result = JSON.parse(stripJsonComments(json));
|
|
131
|
+
expect(result.glob).toBe('*.{js,ts}');
|
|
132
|
+
expect(result.arr).toBe('[a,]');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('handles Windows CRLF line endings', () => {
|
|
136
|
+
const json = '{\r\n "key": "value", // comment\r\n}';
|
|
137
|
+
const result = JSON.parse(stripJsonComments(json));
|
|
138
|
+
expect(result).toEqual({ key: 'value' });
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { install } from './install';
|
|
3
|
+
import { getGeneratedPresetNames, isGeneratedPresetName } from './providers';
|
|
4
|
+
import type { BooleanArg, InstallArgs } from './types';
|
|
5
|
+
|
|
6
|
+
function parseArgs(args: string[]): InstallArgs {
|
|
7
|
+
const result: InstallArgs = {
|
|
8
|
+
tui: true,
|
|
9
|
+
skills: 'yes',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
for (const arg of args) {
|
|
13
|
+
if (arg === '--no-tui') {
|
|
14
|
+
result.tui = false;
|
|
15
|
+
} else if (arg.startsWith('--skills=')) {
|
|
16
|
+
result.skills = arg.split('=')[1] as BooleanArg;
|
|
17
|
+
} else if (arg.startsWith('--preset=')) {
|
|
18
|
+
const preset = arg.split('=')[1];
|
|
19
|
+
if (!isGeneratedPresetName(preset)) {
|
|
20
|
+
console.error(
|
|
21
|
+
`Unsupported preset: ${preset}. Available presets: ${getGeneratedPresetNames().join(', ')}`,
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
result.preset = preset;
|
|
26
|
+
} else if (arg === '--dry-run') {
|
|
27
|
+
result.dryRun = true;
|
|
28
|
+
} else if (arg === '--reset') {
|
|
29
|
+
result.reset = true;
|
|
30
|
+
} else if (arg === '-h' || arg === '--help') {
|
|
31
|
+
printHelp();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printHelp(): void {
|
|
40
|
+
console.log(`
|
|
41
|
+
opencode-dux installer
|
|
42
|
+
|
|
43
|
+
Usage: bunx opencode-dux install [OPTIONS]
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
--skills=yes|no Install recommended and bundled skills (default: yes)
|
|
47
|
+
--preset=<name> Active generated config preset (default: openai)
|
|
48
|
+
--no-tui Non-interactive mode
|
|
49
|
+
--dry-run Simulate install without writing files
|
|
50
|
+
--reset Force overwrite of existing configuration
|
|
51
|
+
-h, --help Show this help message
|
|
52
|
+
|
|
53
|
+
Available presets: ${getGeneratedPresetNames().join(', ')}
|
|
54
|
+
|
|
55
|
+
The installer generates OpenAI and OpenCode Go presets by default.
|
|
56
|
+
OpenAI is active unless --preset selects another generated preset.
|
|
57
|
+
For the full config reference, see docs/configuration.md.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
bunx opencode-dux install
|
|
61
|
+
bunx opencode-dux install --no-tui --skills=yes
|
|
62
|
+
bunx opencode-dux install --preset=opencode-go
|
|
63
|
+
bunx opencode-dux install --reset
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main(): Promise<void> {
|
|
68
|
+
const args = process.argv.slice(2);
|
|
69
|
+
|
|
70
|
+
if (args.length === 0 || args[0] === 'install') {
|
|
71
|
+
const hasSubcommand = args[0] === 'install';
|
|
72
|
+
const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
|
|
73
|
+
const exitCode = await install(installArgs);
|
|
74
|
+
process.exit(exitCode);
|
|
75
|
+
} else if (args[0] === '-h' || args[0] === '--help') {
|
|
76
|
+
printHelp();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
} else {
|
|
79
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
80
|
+
console.error('Run with --help for usage information');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
main().catch((err) => {
|
|
86
|
+
console.error('Fatal error:', err);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|