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,1006 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// src/cli/install.ts
|
|
7
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
8
|
+
import { createInterface } from "node:readline/promises";
|
|
9
|
+
|
|
10
|
+
// src/cli/config-io.ts
|
|
11
|
+
import {
|
|
12
|
+
copyFileSync,
|
|
13
|
+
existsSync as existsSync2,
|
|
14
|
+
readFileSync,
|
|
15
|
+
renameSync,
|
|
16
|
+
statSync,
|
|
17
|
+
writeFileSync
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
20
|
+
|
|
21
|
+
// src/cli/paths.ts
|
|
22
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { dirname, join } from "node:path";
|
|
25
|
+
function getDefaultOpenCodeConfigDir() {
|
|
26
|
+
const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
|
|
27
|
+
return join(userConfigDir, "opencode");
|
|
28
|
+
}
|
|
29
|
+
function getCustomOpenCodeConfigDir() {
|
|
30
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
31
|
+
return configDir || undefined;
|
|
32
|
+
}
|
|
33
|
+
function getCustomTuiConfigPath() {
|
|
34
|
+
const configPath = process.env.OPENCODE_TUI_CONFIG?.trim();
|
|
35
|
+
return configPath || undefined;
|
|
36
|
+
}
|
|
37
|
+
function getConfigDir() {
|
|
38
|
+
const customConfigDir = getCustomOpenCodeConfigDir();
|
|
39
|
+
if (customConfigDir) {
|
|
40
|
+
return customConfigDir;
|
|
41
|
+
}
|
|
42
|
+
return getDefaultOpenCodeConfigDir();
|
|
43
|
+
}
|
|
44
|
+
function getOpenCodeConfigPaths() {
|
|
45
|
+
const configDir = getDefaultOpenCodeConfigDir();
|
|
46
|
+
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
47
|
+
}
|
|
48
|
+
function getConfigJson() {
|
|
49
|
+
return getOpenCodeConfigPaths()[0];
|
|
50
|
+
}
|
|
51
|
+
function getConfigJsonc() {
|
|
52
|
+
return getOpenCodeConfigPaths()[1];
|
|
53
|
+
}
|
|
54
|
+
function getLiteConfig() {
|
|
55
|
+
return join(getConfigDir(), "opencode-dux.json");
|
|
56
|
+
}
|
|
57
|
+
function getLiteConfigJsonc() {
|
|
58
|
+
return join(getConfigDir(), "opencode-dux.jsonc");
|
|
59
|
+
}
|
|
60
|
+
function getTuiConfig() {
|
|
61
|
+
const customConfigPath = getCustomTuiConfigPath();
|
|
62
|
+
if (customConfigPath)
|
|
63
|
+
return customConfigPath;
|
|
64
|
+
return join(getConfigDir(), "tui.json");
|
|
65
|
+
}
|
|
66
|
+
function getTuiConfigJsonc() {
|
|
67
|
+
return join(getConfigDir(), "tui.jsonc");
|
|
68
|
+
}
|
|
69
|
+
function getExistingLiteConfigPath() {
|
|
70
|
+
const jsonPath = getLiteConfig();
|
|
71
|
+
if (existsSync(jsonPath))
|
|
72
|
+
return jsonPath;
|
|
73
|
+
const jsoncPath = getLiteConfigJsonc();
|
|
74
|
+
if (existsSync(jsoncPath))
|
|
75
|
+
return jsoncPath;
|
|
76
|
+
return jsonPath;
|
|
77
|
+
}
|
|
78
|
+
function getExistingTuiConfigPath() {
|
|
79
|
+
const customConfigPath = getCustomTuiConfigPath();
|
|
80
|
+
if (customConfigPath)
|
|
81
|
+
return customConfigPath;
|
|
82
|
+
const jsonPath = join(getConfigDir(), "tui.json");
|
|
83
|
+
if (existsSync(jsonPath))
|
|
84
|
+
return jsonPath;
|
|
85
|
+
const jsoncPath = getTuiConfigJsonc();
|
|
86
|
+
if (existsSync(jsoncPath))
|
|
87
|
+
return jsoncPath;
|
|
88
|
+
return jsonPath;
|
|
89
|
+
}
|
|
90
|
+
function getExistingConfigPath() {
|
|
91
|
+
const jsonPath = getConfigJson();
|
|
92
|
+
if (existsSync(jsonPath))
|
|
93
|
+
return jsonPath;
|
|
94
|
+
const jsoncPath = getConfigJsonc();
|
|
95
|
+
if (existsSync(jsoncPath))
|
|
96
|
+
return jsoncPath;
|
|
97
|
+
return jsonPath;
|
|
98
|
+
}
|
|
99
|
+
function ensureConfigDir() {
|
|
100
|
+
const configDir = getConfigDir();
|
|
101
|
+
if (!existsSync(configDir)) {
|
|
102
|
+
mkdirSync(configDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function ensureTuiConfigDir() {
|
|
106
|
+
const configDir = dirname(getTuiConfig());
|
|
107
|
+
if (!existsSync(configDir)) {
|
|
108
|
+
mkdirSync(configDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function ensureOpenCodeConfigDir() {
|
|
112
|
+
const configDir = dirname(getConfigJson());
|
|
113
|
+
if (!existsSync(configDir)) {
|
|
114
|
+
mkdirSync(configDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/cli/providers.ts
|
|
119
|
+
var SCHEMA_URL = "https://unpkg.com/opencode-dux@latest/opencode-dux.schema.json";
|
|
120
|
+
var GENERATED_PRESETS = ["openai", "opencode-go"];
|
|
121
|
+
var MODEL_MAPPINGS = {
|
|
122
|
+
openai: {
|
|
123
|
+
orchestrator: { model: "openai/gpt-5.5" },
|
|
124
|
+
oracle: { model: "openai/gpt-5.5", variant: "high" },
|
|
125
|
+
librarian: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
126
|
+
explorer: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
127
|
+
designer: { model: "openai/gpt-5.4-mini", variant: "medium" },
|
|
128
|
+
fixer: { model: "openai/gpt-5.4-mini", variant: "low" }
|
|
129
|
+
},
|
|
130
|
+
kimi: {
|
|
131
|
+
orchestrator: { model: "kimi-for-coding/k2p5" },
|
|
132
|
+
oracle: { model: "kimi-for-coding/k2p5", variant: "high" },
|
|
133
|
+
librarian: { model: "kimi-for-coding/k2p5", variant: "low" },
|
|
134
|
+
explorer: { model: "kimi-for-coding/k2p5", variant: "low" },
|
|
135
|
+
designer: { model: "kimi-for-coding/k2p5", variant: "medium" },
|
|
136
|
+
fixer: { model: "kimi-for-coding/k2p5", variant: "low" }
|
|
137
|
+
},
|
|
138
|
+
copilot: {
|
|
139
|
+
orchestrator: { model: "github-copilot/claude-opus-4.6" },
|
|
140
|
+
oracle: { model: "github-copilot/claude-opus-4.6", variant: "high" },
|
|
141
|
+
librarian: { model: "github-copilot/grok-code-fast-1", variant: "low" },
|
|
142
|
+
explorer: { model: "github-copilot/grok-code-fast-1", variant: "low" },
|
|
143
|
+
designer: {
|
|
144
|
+
model: "github-copilot/gemini-3.1-pro-preview",
|
|
145
|
+
variant: "medium"
|
|
146
|
+
},
|
|
147
|
+
fixer: { model: "github-copilot/claude-sonnet-4.6", variant: "low" }
|
|
148
|
+
},
|
|
149
|
+
"zai-plan": {
|
|
150
|
+
orchestrator: { model: "zai-coding-plan/glm-5" },
|
|
151
|
+
oracle: { model: "zai-coding-plan/glm-5", variant: "high" },
|
|
152
|
+
librarian: { model: "zai-coding-plan/glm-5", variant: "low" },
|
|
153
|
+
explorer: { model: "zai-coding-plan/glm-5", variant: "low" },
|
|
154
|
+
designer: { model: "zai-coding-plan/glm-5", variant: "medium" },
|
|
155
|
+
fixer: { model: "zai-coding-plan/glm-5", variant: "low" }
|
|
156
|
+
},
|
|
157
|
+
"opencode-go": {
|
|
158
|
+
orchestrator: {
|
|
159
|
+
model: "neuralwatt/zai-org/GLM-5.1-FP8",
|
|
160
|
+
variant: "medium"
|
|
161
|
+
},
|
|
162
|
+
oracle: { model: "opencode-go/deepseek-v4-flash", variant: "medium" },
|
|
163
|
+
librarian: { model: "opencode-go/deepseek-v4-flash", variant: "low" },
|
|
164
|
+
explorer: { model: "neuralwatt/qwen3.5-397b-fast", variant: "low" },
|
|
165
|
+
designer: { model: "opencode-go/mimo-v2.5-pro", variant: "medium" },
|
|
166
|
+
fixer: { model: "opencode-go/deepseek-v4-flash", variant: "low" }
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function isGeneratedPresetName(value) {
|
|
170
|
+
return GENERATED_PRESETS.includes(value);
|
|
171
|
+
}
|
|
172
|
+
function getGeneratedPresetNames() {
|
|
173
|
+
return [...GENERATED_PRESETS];
|
|
174
|
+
}
|
|
175
|
+
function generateLiteConfig(installConfig) {
|
|
176
|
+
const preset = installConfig.preset ?? "openai";
|
|
177
|
+
if (!isGeneratedPresetName(preset)) {
|
|
178
|
+
throw new Error(`Unsupported preset "${preset}". Available generated presets: ${getGeneratedPresetNames().join(", ")}`);
|
|
179
|
+
}
|
|
180
|
+
const config = {
|
|
181
|
+
$schema: SCHEMA_URL,
|
|
182
|
+
preset,
|
|
183
|
+
presets: {}
|
|
184
|
+
};
|
|
185
|
+
const createAgentConfig = (agentName, modelInfo) => {
|
|
186
|
+
return {
|
|
187
|
+
model: modelInfo.model,
|
|
188
|
+
variant: modelInfo.variant
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const buildPreset = (mappingName) => {
|
|
192
|
+
const mapping = MODEL_MAPPINGS[mappingName];
|
|
193
|
+
return Object.fromEntries(Object.entries(mapping).map(([agentName, modelInfo]) => [
|
|
194
|
+
agentName,
|
|
195
|
+
createAgentConfig(agentName, modelInfo)
|
|
196
|
+
]));
|
|
197
|
+
};
|
|
198
|
+
const presets = config.presets;
|
|
199
|
+
for (const presetName of GENERATED_PRESETS) {
|
|
200
|
+
presets[presetName] = buildPreset(presetName);
|
|
201
|
+
}
|
|
202
|
+
config.agents = {
|
|
203
|
+
orchestrator: {
|
|
204
|
+
skills: { "always-load": ["find-skills"], wildcard: true }
|
|
205
|
+
},
|
|
206
|
+
designer: {
|
|
207
|
+
skills: {
|
|
208
|
+
"always-load": ["agent-browser", "web-design-guidelines"],
|
|
209
|
+
wildcard: false
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
oracle: {
|
|
213
|
+
skills: { "always-load": ["requesting-code-review"], wildcard: false }
|
|
214
|
+
},
|
|
215
|
+
librarian: {
|
|
216
|
+
skills: { "always-load": ["find-skills"], wildcard: false }
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
return config;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/cli/config-io.ts
|
|
223
|
+
var PACKAGE_NAME = "opencode-dux";
|
|
224
|
+
function isString(value) {
|
|
225
|
+
return typeof value === "string";
|
|
226
|
+
}
|
|
227
|
+
function getPlugins(config) {
|
|
228
|
+
return Array.isArray(config.plugin) ? config.plugin : [];
|
|
229
|
+
}
|
|
230
|
+
function getPluginEntries(config) {
|
|
231
|
+
return getPlugins(config).filter(isString);
|
|
232
|
+
}
|
|
233
|
+
function getPluginSpec(entry) {
|
|
234
|
+
if (isString(entry))
|
|
235
|
+
return entry;
|
|
236
|
+
if (!Array.isArray(entry))
|
|
237
|
+
return;
|
|
238
|
+
const spec = entry[0];
|
|
239
|
+
return isString(spec) ? spec : undefined;
|
|
240
|
+
}
|
|
241
|
+
function normalizePathForMatch(path) {
|
|
242
|
+
return path.replaceAll("\\", "/");
|
|
243
|
+
}
|
|
244
|
+
function findPackageRoot(startPath) {
|
|
245
|
+
let currentPath = dirname2(startPath);
|
|
246
|
+
while (true) {
|
|
247
|
+
const packageJsonPath = join2(currentPath, "package.json");
|
|
248
|
+
if (existsSync2(packageJsonPath)) {
|
|
249
|
+
try {
|
|
250
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
251
|
+
if (packageJson.name === PACKAGE_NAME) {
|
|
252
|
+
return currentPath;
|
|
253
|
+
}
|
|
254
|
+
} catch {}
|
|
255
|
+
}
|
|
256
|
+
const parentPath = dirname2(currentPath);
|
|
257
|
+
if (parentPath === currentPath) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
currentPath = parentPath;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function isPackageManagerInstall(path) {
|
|
264
|
+
const normalizedPath = normalizePathForMatch(path);
|
|
265
|
+
return normalizedPath.includes(`/node_modules/${PACKAGE_NAME}`);
|
|
266
|
+
}
|
|
267
|
+
function isLocalPackageRootEntry(entry) {
|
|
268
|
+
if (!entry || entry.startsWith("file://")) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
const packageJsonPath = join2(entry, "package.json");
|
|
272
|
+
if (!existsSync2(packageJsonPath)) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
277
|
+
return packageJson.name === PACKAGE_NAME;
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isPluginEntry(entry) {
|
|
283
|
+
return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.startsWith("file://") && entry.includes(PACKAGE_NAME) || isLocalPackageRootEntry(entry);
|
|
284
|
+
}
|
|
285
|
+
function isMatchingPluginEntry(entry) {
|
|
286
|
+
const spec = getPluginSpec(entry);
|
|
287
|
+
return spec ? isPluginEntry(spec) : false;
|
|
288
|
+
}
|
|
289
|
+
function getPluginEntry() {
|
|
290
|
+
const cliEntryPath = process.argv[1];
|
|
291
|
+
if (!cliEntryPath) {
|
|
292
|
+
return PACKAGE_NAME;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const packageRoot = findPackageRoot(cliEntryPath);
|
|
296
|
+
if (!packageRoot || isPackageManagerInstall(packageRoot)) {
|
|
297
|
+
return PACKAGE_NAME;
|
|
298
|
+
}
|
|
299
|
+
return packageRoot;
|
|
300
|
+
} catch {
|
|
301
|
+
return PACKAGE_NAME;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function stripJsonComments(json) {
|
|
305
|
+
const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
|
|
306
|
+
const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
|
|
307
|
+
return json.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
|
|
308
|
+
}
|
|
309
|
+
function parseConfigFile(path) {
|
|
310
|
+
try {
|
|
311
|
+
if (!existsSync2(path))
|
|
312
|
+
return { config: null };
|
|
313
|
+
const stat = statSync(path);
|
|
314
|
+
if (stat.size === 0)
|
|
315
|
+
return { config: null };
|
|
316
|
+
const content = readFileSync(path, "utf-8");
|
|
317
|
+
if (content.trim().length === 0)
|
|
318
|
+
return { config: null };
|
|
319
|
+
return { config: JSON.parse(stripJsonComments(content)) };
|
|
320
|
+
} catch (err) {
|
|
321
|
+
return { config: null, error: String(err) };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function parseConfig(path) {
|
|
325
|
+
const result = parseConfigFile(path);
|
|
326
|
+
if (result.config || result.error)
|
|
327
|
+
return result;
|
|
328
|
+
if (path.endsWith(".json")) {
|
|
329
|
+
const jsoncPath = path.replace(/\.json$/, ".jsonc");
|
|
330
|
+
return parseConfigFile(jsoncPath);
|
|
331
|
+
}
|
|
332
|
+
return { config: null };
|
|
333
|
+
}
|
|
334
|
+
function writeConfig(configPath, config) {
|
|
335
|
+
if (configPath.endsWith(".jsonc")) {
|
|
336
|
+
console.warn("[config-manager] Writing to .jsonc file - comments will not be preserved");
|
|
337
|
+
}
|
|
338
|
+
const tmpPath = `${configPath}.tmp`;
|
|
339
|
+
const bakPath = `${configPath}.bak`;
|
|
340
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
341
|
+
`;
|
|
342
|
+
if (existsSync2(configPath)) {
|
|
343
|
+
copyFileSync(configPath, bakPath);
|
|
344
|
+
}
|
|
345
|
+
writeFileSync(tmpPath, content);
|
|
346
|
+
renameSync(tmpPath, configPath);
|
|
347
|
+
}
|
|
348
|
+
async function addPluginToOpenCodeConfig() {
|
|
349
|
+
const configPath = getExistingConfigPath();
|
|
350
|
+
try {
|
|
351
|
+
ensureOpenCodeConfigDir();
|
|
352
|
+
} catch (err) {
|
|
353
|
+
return {
|
|
354
|
+
success: false,
|
|
355
|
+
configPath,
|
|
356
|
+
error: `Failed to create config directory: ${err}`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
361
|
+
if (error) {
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
configPath,
|
|
365
|
+
error: `Failed to parse config: ${error}`
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const config = parsedConfig ?? {};
|
|
369
|
+
const plugins = getPlugins(config);
|
|
370
|
+
const pluginEntry = getPluginEntry();
|
|
371
|
+
const filteredPlugins = plugins.filter((plugin) => !isMatchingPluginEntry(plugin));
|
|
372
|
+
filteredPlugins.push(pluginEntry);
|
|
373
|
+
config.plugin = filteredPlugins;
|
|
374
|
+
writeConfig(configPath, config);
|
|
375
|
+
return { success: true, configPath };
|
|
376
|
+
} catch (err) {
|
|
377
|
+
return {
|
|
378
|
+
success: false,
|
|
379
|
+
configPath,
|
|
380
|
+
error: `Failed to update opencode config: ${err}`
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function addPluginToOpenCodeTuiConfig() {
|
|
385
|
+
const configPath = getExistingTuiConfigPath();
|
|
386
|
+
try {
|
|
387
|
+
ensureTuiConfigDir();
|
|
388
|
+
} catch (err) {
|
|
389
|
+
return {
|
|
390
|
+
success: false,
|
|
391
|
+
configPath,
|
|
392
|
+
error: `Failed to create config directory: ${err}`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
397
|
+
if (error) {
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
configPath,
|
|
401
|
+
error: `Failed to parse TUI config: ${error}`
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
const config = parsedConfig ?? {};
|
|
405
|
+
const plugins = getPlugins(config);
|
|
406
|
+
const pluginEntry = getPluginEntry();
|
|
407
|
+
const filteredPlugins = plugins.filter((plugin) => !isMatchingPluginEntry(plugin));
|
|
408
|
+
filteredPlugins.push(pluginEntry);
|
|
409
|
+
config.plugin = filteredPlugins;
|
|
410
|
+
writeConfig(configPath, config);
|
|
411
|
+
return { success: true, configPath };
|
|
412
|
+
} catch (err) {
|
|
413
|
+
return {
|
|
414
|
+
success: false,
|
|
415
|
+
configPath,
|
|
416
|
+
error: `Failed to update opencode TUI config: ${err}`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function writeLiteConfig(installConfig, targetPath) {
|
|
421
|
+
const configPath = targetPath ?? getLiteConfig();
|
|
422
|
+
try {
|
|
423
|
+
ensureConfigDir();
|
|
424
|
+
const config = generateLiteConfig(installConfig);
|
|
425
|
+
const tmpPath = `${configPath}.tmp`;
|
|
426
|
+
const bakPath = `${configPath}.bak`;
|
|
427
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
428
|
+
`;
|
|
429
|
+
if (existsSync2(configPath)) {
|
|
430
|
+
copyFileSync(configPath, bakPath);
|
|
431
|
+
}
|
|
432
|
+
writeFileSync(tmpPath, content);
|
|
433
|
+
renameSync(tmpPath, configPath);
|
|
434
|
+
return { success: true, configPath };
|
|
435
|
+
} catch (err) {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
configPath,
|
|
439
|
+
error: `Failed to write lite config: ${err}`
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function disableDefaultAgents() {
|
|
444
|
+
const configPath = getExistingConfigPath();
|
|
445
|
+
try {
|
|
446
|
+
ensureOpenCodeConfigDir();
|
|
447
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
448
|
+
if (error) {
|
|
449
|
+
return {
|
|
450
|
+
success: false,
|
|
451
|
+
configPath,
|
|
452
|
+
error: `Failed to parse config: ${error}`
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
const config = parsedConfig ?? {};
|
|
456
|
+
const agent = config.agent ?? {};
|
|
457
|
+
agent.explore = { disable: true };
|
|
458
|
+
agent.general = { disable: true };
|
|
459
|
+
config.agent = agent;
|
|
460
|
+
writeConfig(configPath, config);
|
|
461
|
+
return { success: true, configPath };
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return {
|
|
464
|
+
success: false,
|
|
465
|
+
configPath,
|
|
466
|
+
error: `Failed to disable default agents: ${err}`
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function enableLspByDefault() {
|
|
471
|
+
const configPath = getExistingConfigPath();
|
|
472
|
+
try {
|
|
473
|
+
ensureOpenCodeConfigDir();
|
|
474
|
+
const { config: parsedConfig, error } = parseConfig(configPath);
|
|
475
|
+
if (error) {
|
|
476
|
+
return {
|
|
477
|
+
success: false,
|
|
478
|
+
configPath,
|
|
479
|
+
error: `Failed to parse config: ${error}`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
const config = parsedConfig ?? {};
|
|
483
|
+
if (config.lsp === undefined) {
|
|
484
|
+
config.lsp = true;
|
|
485
|
+
writeConfig(configPath, config);
|
|
486
|
+
}
|
|
487
|
+
return { success: true, configPath };
|
|
488
|
+
} catch (err) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
configPath,
|
|
492
|
+
error: `Failed to enable LSP: ${err}`
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function detectCurrentConfig() {
|
|
497
|
+
const result = {
|
|
498
|
+
isInstalled: false,
|
|
499
|
+
hasKimi: false,
|
|
500
|
+
hasOpenAI: false,
|
|
501
|
+
hasAnthropic: false,
|
|
502
|
+
hasCopilot: false,
|
|
503
|
+
hasZaiPlan: false,
|
|
504
|
+
hasAntigravity: false,
|
|
505
|
+
hasChutes: false,
|
|
506
|
+
hasOpencodeZen: false
|
|
507
|
+
};
|
|
508
|
+
const { config } = parseConfig(getExistingConfigPath());
|
|
509
|
+
if (!config)
|
|
510
|
+
return result;
|
|
511
|
+
const plugins = getPluginEntries(config);
|
|
512
|
+
result.isInstalled = plugins.some((p) => isPluginEntry(p));
|
|
513
|
+
result.hasAntigravity = plugins.some((p) => p.startsWith("opencode-antigravity-auth"));
|
|
514
|
+
const providers = config.provider;
|
|
515
|
+
result.hasKimi = !!providers?.kimi;
|
|
516
|
+
result.hasAnthropic = !!providers?.anthropic;
|
|
517
|
+
result.hasCopilot = !!providers?.["github-copilot"];
|
|
518
|
+
result.hasZaiPlan = !!providers?.["zai-coding-plan"];
|
|
519
|
+
result.hasChutes = !!providers?.chutes;
|
|
520
|
+
if (providers?.google)
|
|
521
|
+
result.hasAntigravity = true;
|
|
522
|
+
const { config: liteConfig } = parseConfig(getLiteConfig());
|
|
523
|
+
if (liteConfig && typeof liteConfig === "object") {
|
|
524
|
+
const configObj = liteConfig;
|
|
525
|
+
const presetName = configObj.preset;
|
|
526
|
+
const presets = configObj.presets;
|
|
527
|
+
const agents = presets?.[presetName];
|
|
528
|
+
if (agents) {
|
|
529
|
+
const models = Object.values(agents).map((a) => a?.model).filter(Boolean);
|
|
530
|
+
result.hasOpenAI = models.some((m) => m?.startsWith("openai/"));
|
|
531
|
+
result.hasAnthropic = models.some((m) => m?.startsWith("anthropic/"));
|
|
532
|
+
result.hasCopilot = models.some((m) => m?.startsWith("github-copilot/"));
|
|
533
|
+
result.hasZaiPlan = models.some((m) => m?.startsWith("zai-coding-plan/"));
|
|
534
|
+
result.hasOpencodeZen = models.some((m) => m?.startsWith("opencode/"));
|
|
535
|
+
if (models.some((m) => m?.startsWith("google/"))) {
|
|
536
|
+
result.hasAntigravity = true;
|
|
537
|
+
}
|
|
538
|
+
if (models.some((m) => m?.startsWith("chutes/"))) {
|
|
539
|
+
result.hasChutes = true;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
// src/cli/system.ts
|
|
546
|
+
import { spawnSync } from "node:child_process";
|
|
547
|
+
import { statSync as statSync2 } from "node:fs";
|
|
548
|
+
|
|
549
|
+
// src/utils/compat.ts
|
|
550
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
551
|
+
var isBun = typeof globalThis.Bun !== "undefined";
|
|
552
|
+
function collectStream(stream) {
|
|
553
|
+
if (!stream)
|
|
554
|
+
return () => Promise.resolve("");
|
|
555
|
+
const chunks = [];
|
|
556
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
557
|
+
return () => new Promise((resolve, reject) => {
|
|
558
|
+
if (!stream.readable) {
|
|
559
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
563
|
+
stream.on("error", reject);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
function crossSpawn(command, options) {
|
|
567
|
+
const [cmd, ...args] = command;
|
|
568
|
+
const proc = nodeSpawn(cmd, args, {
|
|
569
|
+
stdio: [
|
|
570
|
+
options?.stdin ?? "ignore",
|
|
571
|
+
options?.stdout ?? "pipe",
|
|
572
|
+
options?.stderr ?? "pipe"
|
|
573
|
+
],
|
|
574
|
+
cwd: options?.cwd,
|
|
575
|
+
env: options?.env
|
|
576
|
+
});
|
|
577
|
+
const stdoutCollector = collectStream(proc.stdout);
|
|
578
|
+
const stderrCollector = collectStream(proc.stderr);
|
|
579
|
+
const exited = new Promise((resolve, reject) => {
|
|
580
|
+
proc.on("error", reject);
|
|
581
|
+
proc.on("close", (code) => resolve(code ?? 1));
|
|
582
|
+
});
|
|
583
|
+
return {
|
|
584
|
+
proc,
|
|
585
|
+
stdout: stdoutCollector,
|
|
586
|
+
stderr: stderrCollector,
|
|
587
|
+
exited,
|
|
588
|
+
kill: (signal) => proc.kill(signal),
|
|
589
|
+
get exitCode() {
|
|
590
|
+
return proc.exitCode;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/cli/system.ts
|
|
596
|
+
var cachedOpenCodePath = null;
|
|
597
|
+
function resolvePathCommand(command) {
|
|
598
|
+
try {
|
|
599
|
+
const resolver = process.platform === "win32" ? "where" : "which";
|
|
600
|
+
const result = spawnSync(resolver, [command], {
|
|
601
|
+
encoding: "utf-8",
|
|
602
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
603
|
+
});
|
|
604
|
+
if (result.status !== 0) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
const resolved = result.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
608
|
+
return resolved ?? null;
|
|
609
|
+
} catch {
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function canExecute(command, args) {
|
|
614
|
+
try {
|
|
615
|
+
const result = spawnSync(command, args, {
|
|
616
|
+
stdio: "ignore"
|
|
617
|
+
});
|
|
618
|
+
return result.status === 0;
|
|
619
|
+
} catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function getOpenCodePaths() {
|
|
624
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
625
|
+
return [
|
|
626
|
+
"opencode",
|
|
627
|
+
`${home}/.local/bin/opencode`,
|
|
628
|
+
`${home}/.opencode/bin/opencode`,
|
|
629
|
+
`${home}/bin/opencode`,
|
|
630
|
+
"/usr/local/bin/opencode",
|
|
631
|
+
"/opt/opencode/bin/opencode",
|
|
632
|
+
"/usr/bin/opencode",
|
|
633
|
+
"/bin/opencode",
|
|
634
|
+
"/Applications/OpenCode.app/Contents/MacOS/opencode",
|
|
635
|
+
`${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
|
|
636
|
+
"/opt/homebrew/bin/opencode",
|
|
637
|
+
"/home/linuxbrew/.linuxbrew/bin/opencode",
|
|
638
|
+
`${home}/homebrew/bin/opencode`,
|
|
639
|
+
`${home}/Library/Application Support/opencode/bin/opencode`,
|
|
640
|
+
"/snap/bin/opencode",
|
|
641
|
+
"/var/snap/opencode/current/bin/opencode",
|
|
642
|
+
"/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
|
|
643
|
+
`${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
|
|
644
|
+
"/nix/store/opencode/bin/opencode",
|
|
645
|
+
`${home}/.nix-profile/bin/opencode`,
|
|
646
|
+
"/run/current-system/sw/bin/opencode",
|
|
647
|
+
`${home}/.cargo/bin/opencode`,
|
|
648
|
+
`${home}/.npm-global/bin/opencode`,
|
|
649
|
+
"/usr/local/lib/node_modules/opencode/bin/opencode",
|
|
650
|
+
`${home}/.yarn/bin/opencode`,
|
|
651
|
+
`${home}/.pnpm-global/bin/opencode`
|
|
652
|
+
];
|
|
653
|
+
}
|
|
654
|
+
function resolveOpenCodePath() {
|
|
655
|
+
if (cachedOpenCodePath) {
|
|
656
|
+
return cachedOpenCodePath;
|
|
657
|
+
}
|
|
658
|
+
const pathOpenCodePath = resolvePathCommand("opencode");
|
|
659
|
+
if (pathOpenCodePath) {
|
|
660
|
+
cachedOpenCodePath = pathOpenCodePath;
|
|
661
|
+
return pathOpenCodePath;
|
|
662
|
+
}
|
|
663
|
+
const paths = getOpenCodePaths();
|
|
664
|
+
for (const opencodePath of paths) {
|
|
665
|
+
if (opencodePath === "opencode")
|
|
666
|
+
continue;
|
|
667
|
+
try {
|
|
668
|
+
const stat = statSync2(opencodePath);
|
|
669
|
+
if (stat.isFile()) {
|
|
670
|
+
cachedOpenCodePath = opencodePath;
|
|
671
|
+
return opencodePath;
|
|
672
|
+
}
|
|
673
|
+
} catch {}
|
|
674
|
+
}
|
|
675
|
+
return "opencode";
|
|
676
|
+
}
|
|
677
|
+
async function isOpenCodeInstalled() {
|
|
678
|
+
const pathOpenCodePath = resolvePathCommand("opencode");
|
|
679
|
+
if (pathOpenCodePath && canExecute(pathOpenCodePath, ["--version"])) {
|
|
680
|
+
cachedOpenCodePath = pathOpenCodePath;
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
const paths = getOpenCodePaths();
|
|
684
|
+
for (const opencodePath of paths) {
|
|
685
|
+
if (opencodePath === "opencode")
|
|
686
|
+
continue;
|
|
687
|
+
try {
|
|
688
|
+
const proc = crossSpawn([opencodePath, "--version"], {
|
|
689
|
+
stdout: "pipe",
|
|
690
|
+
stderr: "pipe"
|
|
691
|
+
});
|
|
692
|
+
await proc.exited;
|
|
693
|
+
if (proc.exitCode === 0) {
|
|
694
|
+
cachedOpenCodePath = opencodePath;
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
} catch {}
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
async function getOpenCodeVersion() {
|
|
702
|
+
const opencodePath = resolveOpenCodePath();
|
|
703
|
+
try {
|
|
704
|
+
const proc = crossSpawn([opencodePath, "--version"], {
|
|
705
|
+
stdout: "pipe",
|
|
706
|
+
stderr: "pipe"
|
|
707
|
+
});
|
|
708
|
+
const outputPromise = proc.stdout();
|
|
709
|
+
await proc.exited;
|
|
710
|
+
if (proc.exitCode === 0) {
|
|
711
|
+
return (await outputPromise).trim();
|
|
712
|
+
}
|
|
713
|
+
} catch {}
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
function getOpenCodePath() {
|
|
717
|
+
const path = resolveOpenCodePath();
|
|
718
|
+
return path === "opencode" ? null : path;
|
|
719
|
+
}
|
|
720
|
+
// src/cli/install.ts
|
|
721
|
+
var GREEN = "\x1B[32m";
|
|
722
|
+
var BLUE = "\x1B[34m";
|
|
723
|
+
var YELLOW = "\x1B[33m";
|
|
724
|
+
var RED = "\x1B[31m";
|
|
725
|
+
var BOLD = "\x1B[1m";
|
|
726
|
+
var DIM = "\x1B[2m";
|
|
727
|
+
var RESET = "\x1B[0m";
|
|
728
|
+
var SYMBOLS = {
|
|
729
|
+
check: `${GREEN}[ok]${RESET}`,
|
|
730
|
+
cross: `${RED}[x]${RESET}`,
|
|
731
|
+
arrow: `${BLUE}->${RESET}`,
|
|
732
|
+
bullet: `${DIM}-${RESET}`,
|
|
733
|
+
info: `${BLUE}[i]${RESET}`,
|
|
734
|
+
warn: `${YELLOW}[!]${RESET}`,
|
|
735
|
+
star: `${YELLOW}★${RESET}`
|
|
736
|
+
};
|
|
737
|
+
var GITHUB_REPO = "bakhtiar-personal-work/opencode-dux";
|
|
738
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
|
|
739
|
+
function printHeader(isUpdate) {
|
|
740
|
+
console.log();
|
|
741
|
+
console.log(`${BOLD}opencode-dux ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
742
|
+
console.log("=".repeat(30));
|
|
743
|
+
console.log();
|
|
744
|
+
}
|
|
745
|
+
function printStep(step, total, message) {
|
|
746
|
+
console.log(`${DIM}[${step}/${total}]${RESET} ${message}`);
|
|
747
|
+
}
|
|
748
|
+
function printSuccess(message) {
|
|
749
|
+
console.log(`${SYMBOLS.check} ${message}`);
|
|
750
|
+
}
|
|
751
|
+
function printError(message) {
|
|
752
|
+
console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
|
|
753
|
+
}
|
|
754
|
+
function printInfo(message) {
|
|
755
|
+
console.log(`${SYMBOLS.info} ${message}`);
|
|
756
|
+
}
|
|
757
|
+
async function confirm(message, defaultYes = true) {
|
|
758
|
+
const suffix = defaultYes ? " (Y/n) " : " (y/N) ";
|
|
759
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
760
|
+
try {
|
|
761
|
+
const answer = (await rl.question(`${message}${suffix}`)).trim().toLowerCase();
|
|
762
|
+
if (!answer)
|
|
763
|
+
return defaultYes;
|
|
764
|
+
return answer === "y" || answer === "yes";
|
|
765
|
+
} finally {
|
|
766
|
+
rl.close();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function askToStarRepo(config) {
|
|
770
|
+
if (!config.promptForStar || config.dryRun || !process.stdin.isTTY)
|
|
771
|
+
return;
|
|
772
|
+
console.log();
|
|
773
|
+
const shouldStar = await confirm(`${SYMBOLS.star} Star the repo on GitHub?`, true);
|
|
774
|
+
if (!shouldStar)
|
|
775
|
+
return;
|
|
776
|
+
try {
|
|
777
|
+
const { execFileSync } = await import("node:child_process");
|
|
778
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
|
|
779
|
+
printSuccess("Thanks for starring! ★");
|
|
780
|
+
} catch {
|
|
781
|
+
printInfo(`Couldn't star automatically. You can star manually:
|
|
782
|
+
${BLUE}${GITHUB_URL}${RESET}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async function checkOpenCodeInstalled() {
|
|
786
|
+
const installed = await isOpenCodeInstalled();
|
|
787
|
+
if (!installed) {
|
|
788
|
+
printError("OpenCode is not installed on this system.");
|
|
789
|
+
printInfo("Install it with:");
|
|
790
|
+
console.log(` ${BLUE}curl -fsSL https://opencode.ai/install | bash${RESET}`);
|
|
791
|
+
console.log();
|
|
792
|
+
printInfo("Or if already installed, add it to your PATH:");
|
|
793
|
+
console.log(` ${BLUE}export PATH="$HOME/.local/bin:$PATH"${RESET}`);
|
|
794
|
+
console.log(` ${BLUE}export PATH="$HOME/.opencode/bin:$PATH"${RESET}`);
|
|
795
|
+
return { ok: false };
|
|
796
|
+
}
|
|
797
|
+
const version = await getOpenCodeVersion();
|
|
798
|
+
const path = getOpenCodePath();
|
|
799
|
+
const detectedVersion = version ?? "";
|
|
800
|
+
const pathInfo = path ? ` (${DIM}${path}${RESET})` : "";
|
|
801
|
+
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
802
|
+
return { ok: true, version: version ?? undefined, path: path ?? undefined };
|
|
803
|
+
}
|
|
804
|
+
function handleStepResult(result, successMsg) {
|
|
805
|
+
if (!result.success) {
|
|
806
|
+
printError(`Failed: ${result.error}`);
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
async function runInstall(config) {
|
|
813
|
+
const detected = detectCurrentConfig();
|
|
814
|
+
const isUpdate = detected.isInstalled;
|
|
815
|
+
printHeader(isUpdate);
|
|
816
|
+
const totalSteps = 6;
|
|
817
|
+
let step = 1;
|
|
818
|
+
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
819
|
+
if (config.dryRun) {
|
|
820
|
+
printInfo("Dry run mode - skipping OpenCode check");
|
|
821
|
+
} else {
|
|
822
|
+
const { ok } = await checkOpenCodeInstalled();
|
|
823
|
+
if (!ok)
|
|
824
|
+
return 1;
|
|
825
|
+
}
|
|
826
|
+
printStep(step++, totalSteps, "Adding opencode-dux plugin...");
|
|
827
|
+
if (config.dryRun) {
|
|
828
|
+
printInfo("Dry run mode - skipping plugin installation");
|
|
829
|
+
} else {
|
|
830
|
+
const pluginResult = await addPluginToOpenCodeConfig();
|
|
831
|
+
if (!handleStepResult(pluginResult, "Plugin added"))
|
|
832
|
+
return 1;
|
|
833
|
+
}
|
|
834
|
+
printStep(step++, totalSteps, "Adding TUI version badge...");
|
|
835
|
+
if (config.dryRun) {
|
|
836
|
+
printInfo("Dry run mode - skipping TUI plugin installation");
|
|
837
|
+
} else {
|
|
838
|
+
const tuiResult = await addPluginToOpenCodeTuiConfig();
|
|
839
|
+
if (!tuiResult.success) {
|
|
840
|
+
printInfo(`Skipped TUI badge: ${tuiResult.error}`);
|
|
841
|
+
} else {
|
|
842
|
+
handleStepResult(tuiResult, "TUI badge added");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
printStep(step++, totalSteps, "Disabling OpenCode default agents...");
|
|
846
|
+
if (config.dryRun) {
|
|
847
|
+
printInfo("Dry run mode - skipping agent disabling");
|
|
848
|
+
} else {
|
|
849
|
+
const agentResult = disableDefaultAgents();
|
|
850
|
+
if (!handleStepResult(agentResult, "Default agents disabled"))
|
|
851
|
+
return 1;
|
|
852
|
+
}
|
|
853
|
+
printStep(step++, totalSteps, "Enabling OpenCode LSP integration...");
|
|
854
|
+
if (config.dryRun) {
|
|
855
|
+
printInfo("Dry run mode - skipping LSP configuration");
|
|
856
|
+
} else {
|
|
857
|
+
const lspResult = enableLspByDefault();
|
|
858
|
+
if (!handleStepResult(lspResult, "LSP enabled"))
|
|
859
|
+
return 1;
|
|
860
|
+
}
|
|
861
|
+
printStep(step++, totalSteps, "Writing opencode-dux configuration...");
|
|
862
|
+
if (config.dryRun) {
|
|
863
|
+
const liteConfig = generateLiteConfig(config);
|
|
864
|
+
printInfo("Dry run mode - configuration that would be written:");
|
|
865
|
+
console.log(`
|
|
866
|
+
${JSON.stringify(liteConfig, null, 2)}
|
|
867
|
+
`);
|
|
868
|
+
} else {
|
|
869
|
+
const configPath2 = getExistingLiteConfigPath();
|
|
870
|
+
const configExists = existsSync3(configPath2);
|
|
871
|
+
if (configExists && !config.reset) {
|
|
872
|
+
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
873
|
+
} else {
|
|
874
|
+
const liteResult = writeLiteConfig(config, configExists ? configPath2 : undefined);
|
|
875
|
+
if (!handleStepResult(liteResult, configExists ? "Config reset" : "Config written"))
|
|
876
|
+
return 1;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
const statusMsg = isUpdate ? "Configuration updated!" : "Installation complete!";
|
|
880
|
+
console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${statusMsg}${RESET}`);
|
|
881
|
+
console.log();
|
|
882
|
+
console.log(`${BOLD}Next steps:${RESET}`);
|
|
883
|
+
console.log();
|
|
884
|
+
const configPath = getExistingLiteConfigPath();
|
|
885
|
+
console.log(" 1. Log in to the provider(s) you want to use:");
|
|
886
|
+
console.log(` ${BLUE}$ opencode auth login${RESET}`);
|
|
887
|
+
console.log();
|
|
888
|
+
console.log(" 2. Refresh the models OpenCode can see:");
|
|
889
|
+
console.log(` ${BLUE}$ opencode models --refresh${RESET}`);
|
|
890
|
+
console.log();
|
|
891
|
+
console.log(" 3. Review your generated config:");
|
|
892
|
+
console.log(` ${BLUE}${configPath}${RESET}`);
|
|
893
|
+
console.log();
|
|
894
|
+
console.log(" 4. Start OpenCode:");
|
|
895
|
+
console.log(` ${BLUE}$ opencode${RESET}`);
|
|
896
|
+
console.log();
|
|
897
|
+
console.log(" 5. Verify the agents are responding:");
|
|
898
|
+
console.log(` ${BLUE}> ping all agents${RESET}`);
|
|
899
|
+
console.log();
|
|
900
|
+
const modelsInfo = config.preset && config.preset !== "openai" ? `Generated OpenAI and OpenCode Go presets; ${config.preset} is active.` : "Generated OpenAI and OpenCode Go presets; OpenAI is active by default.";
|
|
901
|
+
console.log(`${modelsInfo}`);
|
|
902
|
+
const altProviders = "For the full configuration reference, see:";
|
|
903
|
+
console.log(altProviders);
|
|
904
|
+
const docsUrl = "https://github.com/bakhtiar-personal-work/opencode-dux/blob/master/docs/configuration.md";
|
|
905
|
+
console.log(` ${BLUE}${docsUrl}${RESET}`);
|
|
906
|
+
console.log();
|
|
907
|
+
await askToStarRepo(config);
|
|
908
|
+
return 0;
|
|
909
|
+
}
|
|
910
|
+
async function install(args) {
|
|
911
|
+
const config = {
|
|
912
|
+
installSkills: false,
|
|
913
|
+
installCustomSkills: false,
|
|
914
|
+
preset: args.preset,
|
|
915
|
+
promptForStar: args.tui,
|
|
916
|
+
dryRun: args.dryRun,
|
|
917
|
+
reset: args.reset ?? false
|
|
918
|
+
};
|
|
919
|
+
return runInstall(config);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/cli/providers.ts
|
|
923
|
+
var GENERATED_PRESETS2 = ["openai", "opencode-go"];
|
|
924
|
+
function isGeneratedPresetName2(value) {
|
|
925
|
+
return GENERATED_PRESETS2.includes(value);
|
|
926
|
+
}
|
|
927
|
+
function getGeneratedPresetNames2() {
|
|
928
|
+
return [...GENERATED_PRESETS2];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/cli/index.ts
|
|
932
|
+
function parseArgs(args) {
|
|
933
|
+
const result = {
|
|
934
|
+
tui: true,
|
|
935
|
+
skills: "yes"
|
|
936
|
+
};
|
|
937
|
+
for (const arg of args) {
|
|
938
|
+
if (arg === "--no-tui") {
|
|
939
|
+
result.tui = false;
|
|
940
|
+
} else if (arg.startsWith("--skills=")) {
|
|
941
|
+
result.skills = arg.split("=")[1];
|
|
942
|
+
} else if (arg.startsWith("--preset=")) {
|
|
943
|
+
const preset = arg.split("=")[1];
|
|
944
|
+
if (!isGeneratedPresetName2(preset)) {
|
|
945
|
+
console.error(`Unsupported preset: ${preset}. Available presets: ${getGeneratedPresetNames2().join(", ")}`);
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
result.preset = preset;
|
|
949
|
+
} else if (arg === "--dry-run") {
|
|
950
|
+
result.dryRun = true;
|
|
951
|
+
} else if (arg === "--reset") {
|
|
952
|
+
result.reset = true;
|
|
953
|
+
} else if (arg === "-h" || arg === "--help") {
|
|
954
|
+
printHelp();
|
|
955
|
+
process.exit(0);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return result;
|
|
959
|
+
}
|
|
960
|
+
function printHelp() {
|
|
961
|
+
console.log(`
|
|
962
|
+
opencode-dux installer
|
|
963
|
+
|
|
964
|
+
Usage: bunx opencode-dux install [OPTIONS]
|
|
965
|
+
|
|
966
|
+
Options:
|
|
967
|
+
--skills=yes|no Install recommended and bundled skills (default: yes)
|
|
968
|
+
--preset=<name> Active generated config preset (default: openai)
|
|
969
|
+
--no-tui Non-interactive mode
|
|
970
|
+
--dry-run Simulate install without writing files
|
|
971
|
+
--reset Force overwrite of existing configuration
|
|
972
|
+
-h, --help Show this help message
|
|
973
|
+
|
|
974
|
+
Available presets: ${getGeneratedPresetNames2().join(", ")}
|
|
975
|
+
|
|
976
|
+
The installer generates OpenAI and OpenCode Go presets by default.
|
|
977
|
+
OpenAI is active unless --preset selects another generated preset.
|
|
978
|
+
For the full config reference, see docs/configuration.md.
|
|
979
|
+
|
|
980
|
+
Examples:
|
|
981
|
+
bunx opencode-dux install
|
|
982
|
+
bunx opencode-dux install --no-tui --skills=yes
|
|
983
|
+
bunx opencode-dux install --preset=opencode-go
|
|
984
|
+
bunx opencode-dux install --reset
|
|
985
|
+
`);
|
|
986
|
+
}
|
|
987
|
+
async function main() {
|
|
988
|
+
const args = process.argv.slice(2);
|
|
989
|
+
if (args.length === 0 || args[0] === "install") {
|
|
990
|
+
const hasSubcommand = args[0] === "install";
|
|
991
|
+
const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
|
|
992
|
+
const exitCode = await install(installArgs);
|
|
993
|
+
process.exit(exitCode);
|
|
994
|
+
} else if (args[0] === "-h" || args[0] === "--help") {
|
|
995
|
+
printHelp();
|
|
996
|
+
process.exit(0);
|
|
997
|
+
} else {
|
|
998
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
999
|
+
console.error("Run with --help for usage information");
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
main().catch((err) => {
|
|
1004
|
+
console.error("Fatal error:", err);
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
});
|