oh-my-opencode-lite 0.1.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +365 -0
  3. package/dist/agents/deep.d.ts +2 -0
  4. package/dist/agents/designer.d.ts +2 -0
  5. package/dist/agents/explorer.d.ts +2 -0
  6. package/dist/agents/index.d.ts +8 -0
  7. package/dist/agents/librarian.d.ts +2 -0
  8. package/dist/agents/oracle.d.ts +2 -0
  9. package/dist/agents/orchestrator.d.ts +15 -0
  10. package/dist/agents/prompt-utils.d.ts +10 -0
  11. package/dist/agents/quick.d.ts +2 -0
  12. package/dist/background/background-manager.d.ts +196 -0
  13. package/dist/background/index.d.ts +2 -0
  14. package/dist/background/tmux-session-manager.d.ts +63 -0
  15. package/dist/cli/config-io.d.ts +22 -0
  16. package/dist/cli/config-manager.d.ts +4 -0
  17. package/dist/cli/custom-skills.d.ts +48 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +1178 -0
  20. package/dist/cli/install.d.ts +3 -0
  21. package/dist/cli/model-key-normalization.d.ts +1 -0
  22. package/dist/cli/paths.d.ts +21 -0
  23. package/dist/cli/providers.d.ts +120 -0
  24. package/dist/cli/skill-manifest.d.ts +32 -0
  25. package/dist/cli/skills.d.ts +26 -0
  26. package/dist/cli/system.d.ts +6 -0
  27. package/dist/cli/types.d.ts +38 -0
  28. package/dist/config/constants.d.ts +19 -0
  29. package/dist/config/index.d.ts +5 -0
  30. package/dist/config/loader.d.ts +33 -0
  31. package/dist/config/schema.d.ts +313 -0
  32. package/dist/config/utils.d.ts +10 -0
  33. package/dist/delegation/delegation-manager.d.ts +25 -0
  34. package/dist/delegation/index.d.ts +4 -0
  35. package/dist/delegation/paths.d.ts +15 -0
  36. package/dist/delegation/project-id.d.ts +1 -0
  37. package/dist/delegation/types.d.ts +39 -0
  38. package/dist/hooks/auto-update-checker/cache.d.ts +6 -0
  39. package/dist/hooks/auto-update-checker/checker.d.ts +28 -0
  40. package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
  41. package/dist/hooks/auto-update-checker/index.d.ts +17 -0
  42. package/dist/hooks/auto-update-checker/types.d.ts +23 -0
  43. package/dist/hooks/chat-headers.d.ts +16 -0
  44. package/dist/hooks/clarification-gate/index.d.ts +30 -0
  45. package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
  46. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  47. package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
  48. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  49. package/dist/hooks/foreground-fallback/index.d.ts +72 -0
  50. package/dist/hooks/index.d.ts +11 -0
  51. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  52. package/dist/hooks/json-error-recovery/index.d.ts +1 -0
  53. package/dist/hooks/phase-reminder/index.d.ts +26 -0
  54. package/dist/hooks/post-read-nudge/index.d.ts +18 -0
  55. package/dist/hooks/skill-sync.d.ts +10 -0
  56. package/dist/hooks/thoth-mem/index.d.ts +46 -0
  57. package/dist/hooks/thoth-mem/protocol.d.ts +6 -0
  58. package/dist/index.d.ts +5 -0
  59. package/dist/index.js +36210 -0
  60. package/dist/mcp/context7.d.ts +6 -0
  61. package/dist/mcp/grep-app.d.ts +6 -0
  62. package/dist/mcp/index.d.ts +7 -0
  63. package/dist/mcp/thoth.d.ts +3 -0
  64. package/dist/mcp/types.d.ts +12 -0
  65. package/dist/mcp/websearch.d.ts +6 -0
  66. package/dist/thoth/client.d.ts +14 -0
  67. package/dist/thoth/index.d.ts +2 -0
  68. package/dist/tools/ast-grep/cli.d.ts +15 -0
  69. package/dist/tools/ast-grep/constants.d.ts +25 -0
  70. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  71. package/dist/tools/ast-grep/index.d.ts +10 -0
  72. package/dist/tools/ast-grep/tools.d.ts +3 -0
  73. package/dist/tools/ast-grep/types.d.ts +30 -0
  74. package/dist/tools/ast-grep/utils.d.ts +4 -0
  75. package/dist/tools/background.d.ts +13 -0
  76. package/dist/tools/index.d.ts +3 -0
  77. package/dist/tools/lsp/client.d.ts +42 -0
  78. package/dist/tools/lsp/config-store.d.ts +29 -0
  79. package/dist/tools/lsp/config.d.ts +4 -0
  80. package/dist/tools/lsp/constants.d.ts +24 -0
  81. package/dist/tools/lsp/index.d.ts +4 -0
  82. package/dist/tools/lsp/tools.d.ts +5 -0
  83. package/dist/tools/lsp/types.d.ts +35 -0
  84. package/dist/tools/lsp/utils.d.ts +34 -0
  85. package/dist/utils/agent-variant.d.ts +47 -0
  86. package/dist/utils/env.d.ts +1 -0
  87. package/dist/utils/index.d.ts +7 -0
  88. package/dist/utils/internal-initiator.d.ts +6 -0
  89. package/dist/utils/logger.d.ts +1 -0
  90. package/dist/utils/polling.d.ts +21 -0
  91. package/dist/utils/tmux.d.ts +32 -0
  92. package/dist/utils/zip-extractor.d.ts +1 -0
  93. package/oh-my-opencode-lite.schema.json +556 -0
  94. package/package.json +74 -0
  95. package/src/skills/_shared/openspec-convention.md +92 -0
  96. package/src/skills/_shared/persistence-contract.md +78 -0
  97. package/src/skills/_shared/thoth-mem-convention.md +80 -0
  98. package/src/skills/brainstorming/SKILL.md +120 -0
  99. package/src/skills/cartography/README.md +57 -0
  100. package/src/skills/cartography/SKILL.md +160 -0
  101. package/src/skills/cartography/scripts/cartographer.py +460 -0
  102. package/src/skills/cartography/scripts/test_cartographer.py +87 -0
  103. package/src/skills/executing-plans/SKILL.md +211 -0
  104. package/src/skills/plan-reviewer/SKILL.md +100 -0
  105. package/src/skills/sdd-apply/SKILL.md +101 -0
  106. package/src/skills/sdd-archive/SKILL.md +94 -0
  107. package/src/skills/sdd-design/SKILL.md +104 -0
  108. package/src/skills/sdd-propose/SKILL.md +99 -0
  109. package/src/skills/sdd-spec/SKILL.md +105 -0
  110. package/src/skills/sdd-tasks/SKILL.md +116 -0
  111. package/src/skills/sdd-verify/SKILL.md +102 -0
@@ -0,0 +1,1178 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/cli/install.ts
5
+ import { existsSync as existsSync5 } from "fs";
6
+
7
+ // src/cli/config-io.ts
8
+ import {
9
+ copyFileSync,
10
+ existsSync as existsSync2,
11
+ readFileSync,
12
+ renameSync,
13
+ statSync,
14
+ writeFileSync
15
+ } from "fs";
16
+
17
+ // src/cli/paths.ts
18
+ import { existsSync, mkdirSync } from "fs";
19
+ import { homedir } from "os";
20
+ import { dirname, join } from "path";
21
+ function getDefaultOpenCodeConfigDir() {
22
+ const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
23
+ return join(userConfigDir, "opencode");
24
+ }
25
+ function getCustomOpenCodeConfigDir() {
26
+ const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
27
+ return configDir || undefined;
28
+ }
29
+ function getConfigDir() {
30
+ const customConfigDir = getCustomOpenCodeConfigDir();
31
+ if (customConfigDir) {
32
+ return customConfigDir;
33
+ }
34
+ return getDefaultOpenCodeConfigDir();
35
+ }
36
+ function getOpenCodeConfigPaths() {
37
+ const configDir = getDefaultOpenCodeConfigDir();
38
+ return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
39
+ }
40
+ function getConfigJson() {
41
+ return getOpenCodeConfigPaths()[0];
42
+ }
43
+ function getConfigJsonc() {
44
+ return getOpenCodeConfigPaths()[1];
45
+ }
46
+ function getLiteConfig() {
47
+ return join(getConfigDir(), "oh-my-opencode-lite.json");
48
+ }
49
+ function getLiteConfigJsonc() {
50
+ return join(getConfigDir(), "oh-my-opencode-lite.jsonc");
51
+ }
52
+ function getExistingLiteConfigPath() {
53
+ const jsonPath = getLiteConfig();
54
+ if (existsSync(jsonPath))
55
+ return jsonPath;
56
+ const jsoncPath = getLiteConfigJsonc();
57
+ if (existsSync(jsoncPath))
58
+ return jsoncPath;
59
+ return jsonPath;
60
+ }
61
+ function getExistingConfigPath() {
62
+ const jsonPath = getConfigJson();
63
+ if (existsSync(jsonPath))
64
+ return jsonPath;
65
+ const jsoncPath = getConfigJsonc();
66
+ if (existsSync(jsoncPath))
67
+ return jsoncPath;
68
+ return jsonPath;
69
+ }
70
+ function ensureConfigDir() {
71
+ const configDir = getConfigDir();
72
+ if (!existsSync(configDir)) {
73
+ mkdirSync(configDir, { recursive: true });
74
+ }
75
+ }
76
+ function ensureOpenCodeConfigDir() {
77
+ const configDir = dirname(getConfigJson());
78
+ if (!existsSync(configDir)) {
79
+ mkdirSync(configDir, { recursive: true });
80
+ }
81
+ }
82
+
83
+ // src/cli/providers.ts
84
+ var MODEL_MAPPINGS = {
85
+ openai: {
86
+ orchestrator: { model: "openai/gpt-5.4" },
87
+ oracle: { model: "openai/gpt-5.4", variant: "high" },
88
+ librarian: { model: "openai/gpt-5.4-mini", variant: "low" },
89
+ explorer: { model: "openai/gpt-5.4-mini", variant: "low" },
90
+ designer: { model: "openai/gpt-5.4-mini", variant: "medium" },
91
+ quick: { model: "openai/gpt-5.4-mini", variant: "low" },
92
+ deep: { model: "openai/gpt-5.4", variant: "high" }
93
+ },
94
+ kimi: {
95
+ orchestrator: { model: "kimi-for-coding/k2p5" },
96
+ oracle: { model: "kimi-for-coding/k2p5", variant: "high" },
97
+ librarian: { model: "kimi-for-coding/k2p5", variant: "low" },
98
+ explorer: { model: "kimi-for-coding/k2p5", variant: "low" },
99
+ designer: { model: "kimi-for-coding/k2p5", variant: "medium" },
100
+ quick: { model: "kimi-for-coding/k2p5", variant: "low" },
101
+ deep: { model: "kimi-for-coding/k2p5", variant: "high" }
102
+ },
103
+ copilot: {
104
+ orchestrator: { model: "github-copilot/claude-opus-4.6" },
105
+ oracle: { model: "github-copilot/claude-opus-4.6", variant: "high" },
106
+ librarian: { model: "github-copilot/grok-code-fast-1", variant: "low" },
107
+ explorer: { model: "github-copilot/grok-code-fast-1", variant: "low" },
108
+ designer: {
109
+ model: "github-copilot/gemini-3.1-pro-preview",
110
+ variant: "medium"
111
+ },
112
+ quick: { model: "github-copilot/claude-sonnet-4.6", variant: "low" },
113
+ deep: { model: "github-copilot/claude-opus-4.6", variant: "high" }
114
+ },
115
+ "zai-plan": {
116
+ orchestrator: { model: "zai-coding-plan/glm-5" },
117
+ oracle: { model: "zai-coding-plan/glm-5", variant: "high" },
118
+ librarian: { model: "zai-coding-plan/glm-5", variant: "low" },
119
+ explorer: { model: "zai-coding-plan/glm-5", variant: "low" },
120
+ designer: { model: "zai-coding-plan/glm-5", variant: "medium" },
121
+ quick: { model: "zai-coding-plan/glm-5", variant: "low" },
122
+ deep: { model: "zai-coding-plan/glm-5", variant: "high" }
123
+ }
124
+ };
125
+ function generateLiteConfig(installConfig) {
126
+ const config = {
127
+ preset: "openai",
128
+ presets: {}
129
+ };
130
+ const createAgentConfig = (modelInfo) => {
131
+ return {
132
+ model: modelInfo.model,
133
+ variant: modelInfo.variant
134
+ };
135
+ };
136
+ const buildPreset = (mappingName) => {
137
+ const mapping = MODEL_MAPPINGS[mappingName];
138
+ return Object.fromEntries(Object.entries(mapping).map(([agentName, modelInfo]) => [
139
+ agentName,
140
+ createAgentConfig(modelInfo)
141
+ ]));
142
+ };
143
+ config.presets.openai = buildPreset("openai");
144
+ if (installConfig.hasTmux) {
145
+ config.tmux = {
146
+ enabled: true,
147
+ layout: "main-vertical",
148
+ main_pane_size: 60
149
+ };
150
+ }
151
+ return config;
152
+ }
153
+
154
+ // src/cli/config-io.ts
155
+ var PACKAGE_NAME = "oh-my-opencode-lite";
156
+ function stripJsonComments(json) {
157
+ const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
158
+ const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
159
+ return json.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
160
+ }
161
+ function parseConfigFile(path) {
162
+ try {
163
+ if (!existsSync2(path))
164
+ return { config: null };
165
+ const stat = statSync(path);
166
+ if (stat.size === 0)
167
+ return { config: null };
168
+ const content = readFileSync(path, "utf-8");
169
+ if (content.trim().length === 0)
170
+ return { config: null };
171
+ return { config: JSON.parse(stripJsonComments(content)) };
172
+ } catch (err) {
173
+ return { config: null, error: String(err) };
174
+ }
175
+ }
176
+ function parseConfig(path) {
177
+ const result = parseConfigFile(path);
178
+ if (result.config || result.error)
179
+ return result;
180
+ if (path.endsWith(".json")) {
181
+ const jsoncPath = path.replace(/\.json$/, ".jsonc");
182
+ return parseConfigFile(jsoncPath);
183
+ }
184
+ return { config: null };
185
+ }
186
+ function writeConfig(configPath, config) {
187
+ if (configPath.endsWith(".jsonc")) {
188
+ console.warn("[config-manager] Writing to .jsonc file - comments will not be preserved");
189
+ }
190
+ const tmpPath = `${configPath}.tmp`;
191
+ const bakPath = `${configPath}.bak`;
192
+ const content = `${JSON.stringify(config, null, 2)}
193
+ `;
194
+ if (existsSync2(configPath)) {
195
+ copyFileSync(configPath, bakPath);
196
+ }
197
+ writeFileSync(tmpPath, content);
198
+ renameSync(tmpPath, configPath);
199
+ }
200
+ async function addPluginToOpenCodeConfig() {
201
+ const configPath = getExistingConfigPath();
202
+ try {
203
+ ensureOpenCodeConfigDir();
204
+ } catch (err) {
205
+ return {
206
+ success: false,
207
+ configPath,
208
+ error: `Failed to create config directory: ${err}`
209
+ };
210
+ }
211
+ try {
212
+ const { config: parsedConfig, error } = parseConfig(configPath);
213
+ if (error) {
214
+ return {
215
+ success: false,
216
+ configPath,
217
+ error: `Failed to parse config: ${error}`
218
+ };
219
+ }
220
+ const config = parsedConfig ?? {};
221
+ const plugins = config.plugin ?? [];
222
+ const filteredPlugins = plugins.filter((p) => p !== PACKAGE_NAME && !p.startsWith(`${PACKAGE_NAME}@`));
223
+ filteredPlugins.push(PACKAGE_NAME);
224
+ config.plugin = filteredPlugins;
225
+ writeConfig(configPath, config);
226
+ return { success: true, configPath };
227
+ } catch (err) {
228
+ return {
229
+ success: false,
230
+ configPath,
231
+ error: `Failed to update opencode config: ${err}`
232
+ };
233
+ }
234
+ }
235
+ function writeLiteConfig(installConfig, targetPath) {
236
+ const configPath = targetPath ?? getLiteConfig();
237
+ try {
238
+ ensureConfigDir();
239
+ const config = generateLiteConfig(installConfig);
240
+ const tmpPath = `${configPath}.tmp`;
241
+ const bakPath = `${configPath}.bak`;
242
+ const content = `${JSON.stringify(config, null, 2)}
243
+ `;
244
+ if (existsSync2(configPath)) {
245
+ copyFileSync(configPath, bakPath);
246
+ }
247
+ writeFileSync(tmpPath, content);
248
+ renameSync(tmpPath, configPath);
249
+ return { success: true, configPath };
250
+ } catch (err) {
251
+ return {
252
+ success: false,
253
+ configPath,
254
+ error: `Failed to write lite config: ${err}`
255
+ };
256
+ }
257
+ }
258
+ function disableDefaultAgents() {
259
+ const configPath = getExistingConfigPath();
260
+ try {
261
+ ensureOpenCodeConfigDir();
262
+ const { config: parsedConfig, error } = parseConfig(configPath);
263
+ if (error) {
264
+ return {
265
+ success: false,
266
+ configPath,
267
+ error: `Failed to parse config: ${error}`
268
+ };
269
+ }
270
+ const config = parsedConfig ?? {};
271
+ const agent = config.agent ?? {};
272
+ agent.explore = { disable: true };
273
+ agent.general = { disable: true };
274
+ config.agent = agent;
275
+ writeConfig(configPath, config);
276
+ return { success: true, configPath };
277
+ } catch (err) {
278
+ return {
279
+ success: false,
280
+ configPath,
281
+ error: `Failed to disable default agents: ${err}`
282
+ };
283
+ }
284
+ }
285
+ function detectCurrentConfig() {
286
+ const result = {
287
+ isInstalled: false,
288
+ hasKimi: false,
289
+ hasOpenAI: false,
290
+ hasAnthropic: false,
291
+ hasCopilot: false,
292
+ hasZaiPlan: false,
293
+ hasAntigravity: false,
294
+ hasChutes: false,
295
+ hasOpencodeZen: false,
296
+ hasTmux: false
297
+ };
298
+ const { config } = parseConfig(getExistingConfigPath());
299
+ if (!config)
300
+ return result;
301
+ const plugins = config.plugin ?? [];
302
+ result.isInstalled = plugins.some((p) => p.startsWith(PACKAGE_NAME));
303
+ result.hasAntigravity = plugins.some((p) => p.startsWith("opencode-antigravity-auth"));
304
+ const providers = config.provider;
305
+ result.hasKimi = !!providers?.kimi;
306
+ result.hasAnthropic = !!providers?.anthropic;
307
+ result.hasCopilot = !!providers?.["github-copilot"];
308
+ result.hasZaiPlan = !!providers?.["zai-coding-plan"];
309
+ result.hasChutes = !!providers?.chutes;
310
+ if (providers?.google)
311
+ result.hasAntigravity = true;
312
+ const { config: liteConfig } = parseConfig(getLiteConfig());
313
+ if (liteConfig && typeof liteConfig === "object") {
314
+ const configObj = liteConfig;
315
+ const presetName = configObj.preset;
316
+ const presets = configObj.presets;
317
+ const agents = presets?.[presetName];
318
+ if (agents) {
319
+ const models = Object.values(agents).map((a) => a?.model).filter(Boolean);
320
+ result.hasOpenAI = models.some((m) => m?.startsWith("openai/"));
321
+ result.hasAnthropic = models.some((m) => m?.startsWith("anthropic/"));
322
+ result.hasCopilot = models.some((m) => m?.startsWith("github-copilot/"));
323
+ result.hasZaiPlan = models.some((m) => m?.startsWith("zai-coding-plan/"));
324
+ result.hasOpencodeZen = models.some((m) => m?.startsWith("opencode/"));
325
+ if (models.some((m) => m?.startsWith("google/"))) {
326
+ result.hasAntigravity = true;
327
+ }
328
+ if (models.some((m) => m?.startsWith("chutes/"))) {
329
+ result.hasChutes = true;
330
+ }
331
+ }
332
+ if (configObj.tmux && typeof configObj.tmux === "object") {
333
+ const tmuxConfig = configObj.tmux;
334
+ result.hasTmux = tmuxConfig.enabled === true;
335
+ }
336
+ }
337
+ return result;
338
+ }
339
+ // src/cli/system.ts
340
+ import { statSync as statSync2 } from "fs";
341
+ var cachedOpenCodePath = null;
342
+ function getOpenCodePaths() {
343
+ const home = process.env.HOME || process.env.USERPROFILE || "";
344
+ return [
345
+ "opencode",
346
+ `${home}/.local/bin/opencode`,
347
+ `${home}/.opencode/bin/opencode`,
348
+ `${home}/bin/opencode`,
349
+ "/usr/local/bin/opencode",
350
+ "/opt/opencode/bin/opencode",
351
+ "/usr/bin/opencode",
352
+ "/bin/opencode",
353
+ "/Applications/OpenCode.app/Contents/MacOS/opencode",
354
+ `${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
355
+ "/opt/homebrew/bin/opencode",
356
+ "/home/linuxbrew/.linuxbrew/bin/opencode",
357
+ `${home}/homebrew/bin/opencode`,
358
+ `${home}/Library/Application Support/opencode/bin/opencode`,
359
+ "/snap/bin/opencode",
360
+ "/var/snap/opencode/current/bin/opencode",
361
+ "/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
362
+ `${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
363
+ "/nix/store/opencode/bin/opencode",
364
+ `${home}/.nix-profile/bin/opencode`,
365
+ "/run/current-system/sw/bin/opencode",
366
+ `${home}/.cargo/bin/opencode`,
367
+ `${home}/.npm-global/bin/opencode`,
368
+ "/usr/local/lib/node_modules/opencode/bin/opencode",
369
+ `${home}/.yarn/bin/opencode`,
370
+ `${home}/.pnpm-global/bin/opencode`
371
+ ];
372
+ }
373
+ function resolveOpenCodePath() {
374
+ if (cachedOpenCodePath) {
375
+ return cachedOpenCodePath;
376
+ }
377
+ const paths = getOpenCodePaths();
378
+ for (const opencodePath of paths) {
379
+ if (opencodePath === "opencode")
380
+ continue;
381
+ try {
382
+ const stat = statSync2(opencodePath);
383
+ if (stat.isFile()) {
384
+ cachedOpenCodePath = opencodePath;
385
+ return opencodePath;
386
+ }
387
+ } catch {}
388
+ }
389
+ return "opencode";
390
+ }
391
+ async function isOpenCodeInstalled() {
392
+ const paths = getOpenCodePaths();
393
+ for (const opencodePath of paths) {
394
+ try {
395
+ const proc = Bun.spawn([opencodePath, "--version"], {
396
+ stdout: "pipe",
397
+ stderr: "pipe"
398
+ });
399
+ await proc.exited;
400
+ if (proc.exitCode === 0) {
401
+ cachedOpenCodePath = opencodePath;
402
+ return true;
403
+ }
404
+ } catch {}
405
+ }
406
+ return false;
407
+ }
408
+ async function getOpenCodeVersion() {
409
+ const opencodePath = resolveOpenCodePath();
410
+ try {
411
+ const proc = Bun.spawn([opencodePath, "--version"], {
412
+ stdout: "pipe",
413
+ stderr: "pipe"
414
+ });
415
+ const output = await new Response(proc.stdout).text();
416
+ await proc.exited;
417
+ if (proc.exitCode === 0) {
418
+ return output.trim();
419
+ }
420
+ } catch {}
421
+ return null;
422
+ }
423
+ function getOpenCodePath() {
424
+ const path = resolveOpenCodePath();
425
+ return path === "opencode" ? null : path;
426
+ }
427
+ // src/cli/custom-skills.ts
428
+ import {
429
+ copyFileSync as copyFileSync2,
430
+ existsSync as existsSync4,
431
+ mkdirSync as mkdirSync3,
432
+ readdirSync as readdirSync2,
433
+ rmSync,
434
+ statSync as statSync4
435
+ } from "fs";
436
+ import { dirname as dirname2, join as join3, parse } from "path";
437
+ import { fileURLToPath } from "url";
438
+
439
+ // src/cli/skill-manifest.ts
440
+ import { createHash } from "crypto";
441
+ import {
442
+ existsSync as existsSync3,
443
+ mkdirSync as mkdirSync2,
444
+ readdirSync,
445
+ readFileSync as readFileSync2,
446
+ statSync as statSync3,
447
+ writeFileSync as writeFileSync2
448
+ } from "fs";
449
+ import { join as join2, relative } from "path";
450
+ var SHARED_SKILL_DIRECTORY = "_shared";
451
+ var SKILLS_SOURCE_ROOT = join2("src", "skills");
452
+ var MANIFEST_FILE_NAME = ".skill-manifest.json";
453
+ function getManifestPath() {
454
+ return join2(getCustomSkillsDir(), MANIFEST_FILE_NAME);
455
+ }
456
+ function listFilesRecursive(dirPath) {
457
+ if (!existsSync3(dirPath)) {
458
+ return [];
459
+ }
460
+ const files = [];
461
+ const entries = readdirSync(dirPath).sort((left, right) => left.localeCompare(right));
462
+ for (const entry of entries) {
463
+ const entryPath = join2(dirPath, entry);
464
+ const stat = statSync3(entryPath);
465
+ if (stat.isDirectory()) {
466
+ files.push(...listFilesRecursive(entryPath));
467
+ continue;
468
+ }
469
+ files.push(entryPath);
470
+ }
471
+ return files;
472
+ }
473
+ function readPackageVersion(packageRoot) {
474
+ const packageJsonPath = join2(packageRoot, "package.json");
475
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
476
+ if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
477
+ throw new Error(`Invalid package version in ${packageJsonPath}`);
478
+ }
479
+ return packageJson.version;
480
+ }
481
+ function readManifest() {
482
+ const manifestPath = getManifestPath();
483
+ if (!existsSync3(manifestPath)) {
484
+ return null;
485
+ }
486
+ try {
487
+ return JSON.parse(readFileSync2(manifestPath, "utf-8"));
488
+ } catch (error) {
489
+ console.warn(`Failed to read skill manifest: ${manifestPath}`, error);
490
+ return null;
491
+ }
492
+ }
493
+ function writeManifest(manifest) {
494
+ const manifestPath = getManifestPath();
495
+ mkdirSync2(getCustomSkillsDir(), { recursive: true });
496
+ writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
497
+ `);
498
+ }
499
+ function computeSkillHash(skillDirPath) {
500
+ const hash = createHash("sha256");
501
+ for (const filePath of listFilesRecursive(skillDirPath)) {
502
+ hash.update(`${relative(skillDirPath, filePath)}
503
+ `);
504
+ hash.update(readFileSync2(filePath));
505
+ hash.update(`
506
+ `);
507
+ }
508
+ return hash.digest("hex");
509
+ }
510
+ function findRemovedSkills(manifest) {
511
+ const currentSkillNames = new Set(CUSTOM_SKILLS.map((skill) => skill.name));
512
+ return Object.keys(manifest.skills).filter((skillName) => !currentSkillNames.has(skillName));
513
+ }
514
+ function checkSkillsNeedUpdate(packageRoot) {
515
+ const manifest = readManifest();
516
+ const pluginVersion = readPackageVersion(packageRoot);
517
+ const sharedHash = computeSkillHash(join2(packageRoot, SKILLS_SOURCE_ROOT, SHARED_SKILL_DIRECTORY));
518
+ const versionChanged = manifest !== null && manifest.pluginVersion !== pluginVersion;
519
+ const sharedChanged = manifest !== null && manifest.sharedHash !== sharedHash;
520
+ const removedSkills = manifest ? findRemovedSkills(manifest) : [];
521
+ const skillsNeedingUpdate = CUSTOM_SKILLS.flatMap((skill) => {
522
+ const sourcePath = join2(packageRoot, skill.sourcePath);
523
+ const targetPath = join2(getCustomSkillsDir(), skill.name);
524
+ const sourceHash = computeSkillHash(sourcePath);
525
+ const manifestEntry = manifest?.skills[skill.name];
526
+ const reasons = [];
527
+ if (!manifest) {
528
+ reasons.push("manifest-missing");
529
+ }
530
+ if (versionChanged) {
531
+ reasons.push("version-change");
532
+ }
533
+ if (sharedChanged) {
534
+ reasons.push("shared-hash-mismatch");
535
+ }
536
+ if (!manifestEntry) {
537
+ reasons.push("new-skill");
538
+ } else if (manifestEntry.hash !== sourceHash) {
539
+ reasons.push("hash-mismatch");
540
+ }
541
+ if (!existsSync3(targetPath)) {
542
+ reasons.push("missing-install");
543
+ }
544
+ if (reasons.length === 0) {
545
+ return [];
546
+ }
547
+ return [
548
+ {
549
+ skill,
550
+ sourceHash,
551
+ targetPath,
552
+ reasons
553
+ }
554
+ ];
555
+ });
556
+ return {
557
+ pluginVersion,
558
+ sharedHash,
559
+ manifest,
560
+ versionChanged,
561
+ sharedChanged,
562
+ needsUpdate: skillsNeedingUpdate.length > 0 || removedSkills.length > 0,
563
+ skillsNeedingUpdate,
564
+ removedSkills
565
+ };
566
+ }
567
+
568
+ // src/cli/custom-skills.ts
569
+ var SHARED_SKILL_DIRECTORY2 = "_shared";
570
+ var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
571
+ var CUSTOM_SKILLS = [
572
+ {
573
+ name: "brainstorming",
574
+ description: "Understand user intent and scope through structured clarification before implementation",
575
+ allowedAgents: ["orchestrator"],
576
+ sourcePath: "src/skills/brainstorming"
577
+ },
578
+ {
579
+ name: "cartography",
580
+ description: "Repository understanding and hierarchical codemap generation",
581
+ allowedAgents: ["orchestrator", "explorer"],
582
+ sourcePath: "src/skills/cartography"
583
+ },
584
+ {
585
+ name: "plan-reviewer",
586
+ description: "Review SDD task plans for execution blockers and valid references",
587
+ allowedAgents: ["orchestrator", "oracle"],
588
+ sourcePath: "src/skills/plan-reviewer"
589
+ },
590
+ {
591
+ name: "sdd-propose",
592
+ description: "Create change proposals for OpenSpec workflows",
593
+ allowedAgents: ["orchestrator"],
594
+ sourcePath: "src/skills/sdd-propose"
595
+ },
596
+ {
597
+ name: "sdd-spec",
598
+ description: "Write OpenSpec delta specifications",
599
+ allowedAgents: ["orchestrator"],
600
+ sourcePath: "src/skills/sdd-spec"
601
+ },
602
+ {
603
+ name: "sdd-design",
604
+ description: "Create technical design artifacts for changes",
605
+ allowedAgents: ["orchestrator"],
606
+ sourcePath: "src/skills/sdd-design"
607
+ },
608
+ {
609
+ name: "sdd-tasks",
610
+ description: "Generate phased implementation task checklists",
611
+ allowedAgents: ["orchestrator"],
612
+ sourcePath: "src/skills/sdd-tasks"
613
+ },
614
+ {
615
+ name: "sdd-apply",
616
+ description: "Execute tasks and persist implementation progress",
617
+ allowedAgents: ["orchestrator"],
618
+ sourcePath: "src/skills/sdd-apply"
619
+ },
620
+ {
621
+ name: "executing-plans",
622
+ description: "Execute SDD task lists with real-time progress tracking, sub-agent dispatch, and verification checkpoints",
623
+ allowedAgents: ["orchestrator"],
624
+ sourcePath: "src/skills/executing-plans"
625
+ },
626
+ {
627
+ name: "sdd-verify",
628
+ description: "Build verification reports and compliance matrices",
629
+ allowedAgents: ["orchestrator"],
630
+ sourcePath: "src/skills/sdd-verify"
631
+ },
632
+ {
633
+ name: "sdd-archive",
634
+ description: "Archive completed OpenSpec changes with audit trails",
635
+ allowedAgents: ["orchestrator"],
636
+ sourcePath: "src/skills/sdd-archive"
637
+ }
638
+ ];
639
+ function getCustomSkillsDir() {
640
+ return join3(getConfigDir(), "skills");
641
+ }
642
+ function copyDirRecursive(src, dest) {
643
+ if (!existsSync4(dest)) {
644
+ mkdirSync3(dest, { recursive: true });
645
+ }
646
+ const entries = readdirSync2(src);
647
+ for (const entry of entries) {
648
+ const srcPath = join3(src, entry);
649
+ const destPath = join3(dest, entry);
650
+ const stat = statSync4(srcPath);
651
+ if (stat.isDirectory()) {
652
+ copyDirRecursive(srcPath, destPath);
653
+ } else {
654
+ const destDir = dirname2(destPath);
655
+ if (!existsSync4(destDir)) {
656
+ mkdirSync3(destDir, { recursive: true });
657
+ }
658
+ copyFileSync2(srcPath, destPath);
659
+ }
660
+ }
661
+ }
662
+ function installSharedSkillAssets(packageRoot) {
663
+ const sharedSourcePath = join3(packageRoot, SHARED_SKILL_SOURCE_PATH);
664
+ const sharedTargetPath = join3(getCustomSkillsDir(), SHARED_SKILL_DIRECTORY2);
665
+ if (!existsSync4(sharedSourcePath)) {
666
+ console.error(`Custom skill shared assets not found: ${sharedSourcePath}`);
667
+ return false;
668
+ }
669
+ rmSync(sharedTargetPath, { recursive: true, force: true });
670
+ copyDirRecursive(sharedSourcePath, sharedTargetPath);
671
+ return true;
672
+ }
673
+ function findPackageRoot(startDir) {
674
+ let currentDir = startDir;
675
+ const filesystemRoot = parse(startDir).root;
676
+ while (true) {
677
+ if (existsSync4(join3(currentDir, "package.json")) && existsSync4(join3(currentDir, "src", "skills"))) {
678
+ return currentDir;
679
+ }
680
+ if (currentDir === filesystemRoot) {
681
+ return null;
682
+ }
683
+ const parentDir = dirname2(currentDir);
684
+ if (parentDir === currentDir) {
685
+ return null;
686
+ }
687
+ currentDir = parentDir;
688
+ }
689
+ }
690
+ function resolvePackageRoot(packageRoot) {
691
+ if (packageRoot) {
692
+ return packageRoot;
693
+ }
694
+ const moduleDir = fileURLToPath(new URL(".", import.meta.url));
695
+ return findPackageRoot(moduleDir) ?? fileURLToPath(new URL("../..", import.meta.url));
696
+ }
697
+ function installCustomSkillFiles(skill, packageRoot) {
698
+ const sourcePath = join3(packageRoot, skill.sourcePath);
699
+ const targetPath = join3(getCustomSkillsDir(), skill.name);
700
+ if (!existsSync4(sourcePath)) {
701
+ console.error(`Custom skill source not found: ${sourcePath}`);
702
+ return false;
703
+ }
704
+ rmSync(targetPath, { recursive: true, force: true });
705
+ copyDirRecursive(sourcePath, targetPath);
706
+ return true;
707
+ }
708
+ function removeObsoleteSkills(removedSkillNames) {
709
+ const removedSkills = [];
710
+ for (const skillName of removedSkillNames) {
711
+ const targetPath = join3(getCustomSkillsDir(), skillName);
712
+ try {
713
+ console.log(`Removing obsolete bundled skill: ${skillName}`);
714
+ rmSync(targetPath, { recursive: true, force: true });
715
+ removedSkills.push(skillName);
716
+ } catch (error) {
717
+ console.warn(`Failed to remove obsolete bundled skill: ${skillName}`);
718
+ console.warn(error);
719
+ }
720
+ }
721
+ return removedSkills;
722
+ }
723
+ function buildManifest(packageRoot, updatedSkills, previousManifest, pluginVersion, sharedHash) {
724
+ const installedAt = new Date().toISOString();
725
+ const updatedSkillNames = new Set(updatedSkills.map(({ skill }) => skill.name));
726
+ const skills = Object.fromEntries(CUSTOM_SKILLS.map((skill) => {
727
+ const previousEntry = previousManifest?.skills[skill.name];
728
+ const nextInstalledAt = updatedSkillNames.has(skill.name) ? installedAt : previousEntry?.installedAt ?? installedAt;
729
+ return [
730
+ skill.name,
731
+ {
732
+ hash: computeSkillHash(join3(packageRoot, skill.sourcePath)),
733
+ installedAt: nextInstalledAt
734
+ }
735
+ ];
736
+ }));
737
+ return {
738
+ pluginVersion,
739
+ sharedHash,
740
+ skills
741
+ };
742
+ }
743
+ function pruneRemovedSkillsFromManifest(manifest, removedSkills) {
744
+ const removedSkillNames = new Set(removedSkills);
745
+ return {
746
+ ...manifest,
747
+ skills: Object.fromEntries(Object.entries(manifest.skills).filter(([skillName]) => !removedSkillNames.has(skillName)))
748
+ };
749
+ }
750
+ function installCustomSkills(packageRoot = resolvePackageRoot()) {
751
+ const updateCheck = checkSkillsNeedUpdate(packageRoot);
752
+ if (!updateCheck.needsUpdate) {
753
+ return {
754
+ success: true,
755
+ updatedSkills: [],
756
+ skippedSkills: [...CUSTOM_SKILLS],
757
+ failedSkills: [],
758
+ removedSkills: []
759
+ };
760
+ }
761
+ const removedSkills = removeObsoleteSkills(updateCheck.removedSkills);
762
+ if (removedSkills.length > 0 && updateCheck.manifest) {
763
+ writeManifest(pruneRemovedSkillsFromManifest(updateCheck.manifest, removedSkills));
764
+ }
765
+ if (updateCheck.skillsNeedingUpdate.length === 0) {
766
+ writeManifest(buildManifest(packageRoot, [], updateCheck.manifest, updateCheck.pluginVersion, updateCheck.sharedHash));
767
+ return {
768
+ success: true,
769
+ updatedSkills: [],
770
+ skippedSkills: [...CUSTOM_SKILLS],
771
+ failedSkills: [],
772
+ removedSkills
773
+ };
774
+ }
775
+ if (!installSharedSkillAssets(packageRoot)) {
776
+ return {
777
+ success: false,
778
+ updatedSkills: [],
779
+ skippedSkills: [],
780
+ failedSkills: updateCheck.skillsNeedingUpdate.map(({ skill, reasons }) => ({
781
+ skill,
782
+ reasons
783
+ })),
784
+ removedSkills
785
+ };
786
+ }
787
+ const updatesBySkillName = new Map(updateCheck.skillsNeedingUpdate.map((entry) => [entry.skill.name, entry]));
788
+ const updatedSkills = [];
789
+ const skippedSkills = [];
790
+ const failedSkills = [];
791
+ for (const skill of CUSTOM_SKILLS) {
792
+ const pendingUpdate = updatesBySkillName.get(skill.name);
793
+ if (!pendingUpdate) {
794
+ skippedSkills.push(skill);
795
+ continue;
796
+ }
797
+ if (installCustomSkillFiles(skill, packageRoot)) {
798
+ updatedSkills.push({
799
+ skill,
800
+ reasons: pendingUpdate.reasons
801
+ });
802
+ continue;
803
+ }
804
+ failedSkills.push({
805
+ skill,
806
+ reasons: pendingUpdate.reasons
807
+ });
808
+ }
809
+ if (failedSkills.length > 0) {
810
+ return {
811
+ success: false,
812
+ updatedSkills,
813
+ skippedSkills,
814
+ failedSkills,
815
+ removedSkills
816
+ };
817
+ }
818
+ writeManifest(buildManifest(packageRoot, updatedSkills, updateCheck.manifest, updateCheck.pluginVersion, updateCheck.sharedHash));
819
+ return {
820
+ success: true,
821
+ updatedSkills,
822
+ skippedSkills,
823
+ failedSkills,
824
+ removedSkills
825
+ };
826
+ }
827
+
828
+ // src/cli/skills.ts
829
+ import { spawnSync } from "child_process";
830
+ var RECOMMENDED_SKILLS = [
831
+ {
832
+ name: "simplify",
833
+ repo: "https://github.com/brianlovin/claude-config",
834
+ skillName: "simplify",
835
+ description: "YAGNI code simplification expert"
836
+ },
837
+ {
838
+ name: "agent-browser",
839
+ repo: "https://github.com/vercel-labs/agent-browser",
840
+ skillName: "agent-browser",
841
+ description: "High-performance browser automation",
842
+ postInstallCommands: [
843
+ "npm install -g agent-browser",
844
+ "agent-browser install"
845
+ ]
846
+ }
847
+ ];
848
+ function installSkill(skill) {
849
+ const args = [
850
+ "skills",
851
+ "add",
852
+ skill.repo,
853
+ "--skill",
854
+ skill.skillName,
855
+ "-a",
856
+ "opencode",
857
+ "-y",
858
+ "--global"
859
+ ];
860
+ try {
861
+ const result = spawnSync("npx", args, { stdio: "inherit" });
862
+ if (result.status !== 0) {
863
+ return false;
864
+ }
865
+ if (skill.postInstallCommands && skill.postInstallCommands.length > 0) {
866
+ console.log(`Running post-install commands for ${skill.name}...`);
867
+ for (const cmd of skill.postInstallCommands) {
868
+ console.log(`> ${cmd}`);
869
+ const [command, ...cmdArgs] = cmd.split(" ");
870
+ const cmdResult = spawnSync(command, cmdArgs, { stdio: "inherit" });
871
+ if (cmdResult.status !== 0) {
872
+ console.warn(`Post-install command failed: ${cmd}`);
873
+ }
874
+ }
875
+ }
876
+ return true;
877
+ } catch (error) {
878
+ console.error(`Failed to install skill: ${skill.name}`, error);
879
+ return false;
880
+ }
881
+ }
882
+
883
+ // src/cli/install.ts
884
+ var GREEN = "\x1B[32m";
885
+ var BLUE = "\x1B[34m";
886
+ var YELLOW = "\x1B[33m";
887
+ var RED = "\x1B[31m";
888
+ var BOLD = "\x1B[1m";
889
+ var DIM = "\x1B[2m";
890
+ var RESET = "\x1B[0m";
891
+ var SYMBOLS = {
892
+ check: `${GREEN}\u2713${RESET}`,
893
+ cross: `${RED}\u2717${RESET}`,
894
+ arrow: `${BLUE}\u2192${RESET}`,
895
+ bullet: `${DIM}\u2022${RESET}`,
896
+ info: `${BLUE}\u2139${RESET}`,
897
+ warn: `${YELLOW}\u26A0${RESET}`,
898
+ star: `${YELLOW}\u2605${RESET}`
899
+ };
900
+ function printHeader(isUpdate) {
901
+ console.log();
902
+ console.log(`${BOLD}oh-my-opencode-lite ${isUpdate ? "Update" : "Install"}${RESET}`);
903
+ console.log("=".repeat(30));
904
+ console.log();
905
+ }
906
+ function printStep(step, total, message) {
907
+ console.log(`${DIM}[${step}/${total}]${RESET} ${message}`);
908
+ }
909
+ function printSuccess(message) {
910
+ console.log(`${SYMBOLS.check} ${message}`);
911
+ }
912
+ function printError(message) {
913
+ console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
914
+ }
915
+ function printInfo(message) {
916
+ console.log(`${SYMBOLS.info} ${message}`);
917
+ }
918
+ function formatCustomSkillReasons(report) {
919
+ if (report.updatedSkills.length === 0 && report.removedSkills.length === 0) {
920
+ return "all bundled skills already up to date";
921
+ }
922
+ return `${report.updatedSkills.length} updated, ${report.removedSkills.length} removed, ${report.skippedSkills.length} unchanged`;
923
+ }
924
+ function formatSkillReasons(reasons) {
925
+ return reasons.join(", ");
926
+ }
927
+ async function checkOpenCodeInstalled() {
928
+ const installed = await isOpenCodeInstalled();
929
+ if (!installed) {
930
+ printError("OpenCode is not installed on this system.");
931
+ printInfo("Install it with:");
932
+ console.log(` ${BLUE}curl -fsSL https://opencode.ai/install | bash${RESET}`);
933
+ console.log();
934
+ printInfo("Or if already installed, add it to your PATH:");
935
+ console.log(` ${BLUE}export PATH="$HOME/.local/bin:$PATH"${RESET}`);
936
+ console.log(` ${BLUE}export PATH="$HOME/.opencode/bin:$PATH"${RESET}`);
937
+ return { ok: false };
938
+ }
939
+ const version = await getOpenCodeVersion();
940
+ const path = getOpenCodePath();
941
+ const detectedVersion = version ?? "";
942
+ const pathInfo = path ? ` (${DIM}${path}${RESET})` : "";
943
+ printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
944
+ return { ok: true, version: version ?? undefined, path: path ?? undefined };
945
+ }
946
+ function handleStepResult(result, successMsg) {
947
+ if (!result.success) {
948
+ printError(`Failed: ${result.error}`);
949
+ return false;
950
+ }
951
+ printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
952
+ return true;
953
+ }
954
+ function formatConfigSummary() {
955
+ const lines = [];
956
+ lines.push(`${BOLD}Configuration Summary${RESET}`);
957
+ lines.push("");
958
+ lines.push(` ${BOLD}Preset:${RESET} ${BLUE}openai${RESET}`);
959
+ lines.push(` ${SYMBOLS.check} Seven-agent oh-my-opencode-lite roster`);
960
+ lines.push(` ${SYMBOLS.check} OpenAI models by default`);
961
+ lines.push(` ${SYMBOLS.check} thoth-mem enabled for orchestrator memory`);
962
+ lines.push(` ${SYMBOLS.check} Delegation results persisted to disk`);
963
+ lines.push(` ${SYMBOLS.check} Bundled SDD skills ready for install`);
964
+ const seeDocs = "see docs/provider-configurations.md";
965
+ lines.push(` ${DIM}\u25CB Kimi \u2014 ${seeDocs}${RESET}`);
966
+ lines.push(` ${DIM}\u25CB GitHub Copilot \u2014 ${seeDocs}${RESET}`);
967
+ lines.push(` ${DIM}\u25CB ZAI Coding Plan \u2014 ${seeDocs}${RESET}`);
968
+ return lines.join(`
969
+ `);
970
+ }
971
+ async function runInstall(config) {
972
+ const detected = detectCurrentConfig();
973
+ const isUpdate = detected.isInstalled;
974
+ printHeader(isUpdate);
975
+ let totalSteps = 4;
976
+ if (config.installSkills)
977
+ totalSteps += 1;
978
+ if (config.installCustomSkills)
979
+ totalSteps += 1;
980
+ let step = 1;
981
+ printStep(step++, totalSteps, "Checking OpenCode installation...");
982
+ if (config.dryRun) {
983
+ printInfo("Dry run mode - skipping OpenCode check");
984
+ } else {
985
+ const { ok } = await checkOpenCodeInstalled();
986
+ if (!ok)
987
+ return 1;
988
+ }
989
+ printStep(step++, totalSteps, "Adding oh-my-opencode-lite plugin...");
990
+ if (config.dryRun) {
991
+ printInfo("Dry run mode - skipping plugin installation");
992
+ } else {
993
+ const pluginResult = await addPluginToOpenCodeConfig();
994
+ if (!handleStepResult(pluginResult, "Plugin added"))
995
+ return 1;
996
+ }
997
+ printStep(step++, totalSteps, "Disabling OpenCode default agents...");
998
+ if (config.dryRun) {
999
+ printInfo("Dry run mode - skipping agent disabling");
1000
+ } else {
1001
+ const agentResult = disableDefaultAgents();
1002
+ if (!handleStepResult(agentResult, "Default agents disabled"))
1003
+ return 1;
1004
+ }
1005
+ printStep(step++, totalSteps, "Writing oh-my-opencode-lite configuration...");
1006
+ if (config.dryRun) {
1007
+ const liteConfig = generateLiteConfig(config);
1008
+ printInfo("Dry run mode - configuration that would be written:");
1009
+ console.log(`
1010
+ ${JSON.stringify(liteConfig, null, 2)}
1011
+ `);
1012
+ } else {
1013
+ const configPath = getExistingLiteConfigPath();
1014
+ const configExists = existsSync5(configPath);
1015
+ if (configExists && !config.reset) {
1016
+ printInfo(`Configuration already exists at ${configPath}. ` + "Use --reset to overwrite.");
1017
+ } else {
1018
+ const liteResult = writeLiteConfig(config, configExists ? configPath : undefined);
1019
+ if (!handleStepResult(liteResult, configExists ? "Config reset" : "Config written"))
1020
+ return 1;
1021
+ }
1022
+ }
1023
+ if (config.installSkills) {
1024
+ printStep(step++, totalSteps, "Installing recommended external skills...");
1025
+ if (config.dryRun) {
1026
+ printInfo("Dry run mode - would install skills:");
1027
+ for (const skill of RECOMMENDED_SKILLS) {
1028
+ printInfo(` - ${skill.name}`);
1029
+ }
1030
+ } else {
1031
+ let skillsInstalled = 0;
1032
+ for (const skill of RECOMMENDED_SKILLS) {
1033
+ printInfo(`Installing ${skill.name}...`);
1034
+ if (installSkill(skill)) {
1035
+ printSuccess(`Installed: ${skill.name}`);
1036
+ skillsInstalled++;
1037
+ } else {
1038
+ printInfo(`Skipped: ${skill.name} (already installed)`);
1039
+ }
1040
+ }
1041
+ printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills processed`);
1042
+ }
1043
+ }
1044
+ if (config.installCustomSkills) {
1045
+ printStep(step++, totalSteps, "Installing bundled oh-my-opencode-lite skills...");
1046
+ if (config.dryRun) {
1047
+ printInfo("Dry run mode - would install bundled skills:");
1048
+ for (const skill of CUSTOM_SKILLS) {
1049
+ printInfo(` - ${skill.name}`);
1050
+ }
1051
+ } else {
1052
+ const report = installCustomSkills();
1053
+ for (const updatedSkill of report.updatedSkills) {
1054
+ printSuccess(`Installed: ${updatedSkill.skill.name} (${formatSkillReasons(updatedSkill.reasons)})`);
1055
+ }
1056
+ for (const skippedSkill of report.skippedSkills) {
1057
+ printInfo(`Up to date: ${skippedSkill.name}`);
1058
+ }
1059
+ for (const removedSkill of report.removedSkills) {
1060
+ printInfo(`Removed obsolete skill: ${removedSkill}`);
1061
+ }
1062
+ for (const failedSkill of report.failedSkills) {
1063
+ printError(`Failed: ${failedSkill.skill.name} (${formatSkillReasons(failedSkill.reasons)})`);
1064
+ }
1065
+ if (!report.success) {
1066
+ return 1;
1067
+ }
1068
+ printSuccess(`Bundled skills processed: ${formatCustomSkillReasons(report)}`);
1069
+ }
1070
+ }
1071
+ console.log();
1072
+ console.log(formatConfigSummary());
1073
+ console.log();
1074
+ const statusMsg = isUpdate ? "oh-my-opencode-lite updated!" : "oh-my-opencode-lite installation complete!";
1075
+ console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${statusMsg}${RESET}`);
1076
+ console.log();
1077
+ console.log(`${BOLD}Next steps:${RESET}`);
1078
+ console.log();
1079
+ console.log(` 1. Start OpenCode:`);
1080
+ console.log(` ${BLUE}$ opencode${RESET}`);
1081
+ console.log();
1082
+ const modelsInfo = "Default configuration uses OpenAI models (gpt-5.4 / gpt-5.4-mini).";
1083
+ console.log(`${BOLD}${modelsInfo}${RESET}`);
1084
+ console.log(` ${DIM}Includes the seven-agent roster, thoth-mem memory defaults,${RESET}`);
1085
+ console.log(` ${DIM}delegation persistence, and bundled SDD skills.${RESET}`);
1086
+ const altProviders = "For alternative providers (Kimi, GitHub Copilot, ZAI Coding Plan)";
1087
+ console.log(`${BOLD}${altProviders}, see:${RESET}`);
1088
+ const docsUrl = "https://github.com/EremesNG/oh-my-opencode-lite/" + "blob/master/docs/provider-configurations.md";
1089
+ console.log(` ${BLUE}${docsUrl}${RESET}`);
1090
+ console.log();
1091
+ return 0;
1092
+ }
1093
+ function createInstallConfig(args) {
1094
+ return {
1095
+ hasTmux: args.tmux === "yes",
1096
+ installSkills: args.skills === "yes",
1097
+ installCustomSkills: true,
1098
+ dryRun: args.dryRun,
1099
+ reset: args.reset ?? false
1100
+ };
1101
+ }
1102
+ async function install(args) {
1103
+ return runInstall(createInstallConfig(args));
1104
+ }
1105
+
1106
+ // src/cli/index.ts
1107
+ function parseArgs(args) {
1108
+ const result = {
1109
+ tui: true
1110
+ };
1111
+ for (const arg of args) {
1112
+ if (arg === "--no-tui") {
1113
+ result.tui = false;
1114
+ } else if (arg.startsWith("--tmux=")) {
1115
+ result.tmux = arg.split("=")[1];
1116
+ } else if (arg.startsWith("--skills=")) {
1117
+ result.skills = arg.split("=")[1];
1118
+ } else if (arg === "--dry-run") {
1119
+ result.dryRun = true;
1120
+ } else if (arg === "--reset") {
1121
+ result.reset = true;
1122
+ } else if (arg === "-h" || arg === "--help") {
1123
+ printHelp();
1124
+ process.exit(0);
1125
+ }
1126
+ }
1127
+ return result;
1128
+ }
1129
+ function printHelp() {
1130
+ console.log(`
1131
+ oh-my-opencode-lite installer (oh-my-opencode-lite)
1132
+
1133
+ Usage: bunx oh-my-opencode-lite install [OPTIONS]
1134
+
1135
+ Options:
1136
+ --tmux=yes|no Enable tmux integration (yes/no)
1137
+ --skills=yes|no Install recommended external skills
1138
+ --no-tui Non-interactive mode
1139
+ --dry-run Simulate install without writing files
1140
+ --reset Force overwrite of existing configuration
1141
+ -h, --help Show this help message
1142
+
1143
+ oh-my-opencode-lite installs the seven-agent roster, thoth-mem defaults,
1144
+ delegation persistence, and bundled SDD skills for OpenCode.
1145
+
1146
+ Bundled oh-my-opencode-lite skills are always installed.
1147
+ Use --skills=no to skip only recommended external skills.
1148
+
1149
+ The generated config uses OpenAI by default.
1150
+ For alternative providers, see docs/provider-configurations.md.
1151
+
1152
+ Examples:
1153
+ bunx oh-my-opencode-lite@latest install
1154
+ bunx oh-my-opencode-lite install --no-tui --tmux=no --skills=yes
1155
+ bunx oh-my-opencode-lite install --dry-run
1156
+ bunx oh-my-opencode-lite install --reset
1157
+ `);
1158
+ }
1159
+ async function main() {
1160
+ const args = process.argv.slice(2);
1161
+ if (args.length === 0 || args[0] === "install") {
1162
+ const hasSubcommand = args[0] === "install";
1163
+ const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
1164
+ const exitCode = await install(installArgs);
1165
+ process.exit(exitCode);
1166
+ } else if (args[0] === "-h" || args[0] === "--help") {
1167
+ printHelp();
1168
+ process.exit(0);
1169
+ } else {
1170
+ console.error(`Unknown command: ${args[0]}`);
1171
+ console.error("Run with --help for usage information");
1172
+ process.exit(1);
1173
+ }
1174
+ }
1175
+ main().catch((err) => {
1176
+ console.error("Fatal error:", err);
1177
+ process.exit(1);
1178
+ });