oh-my-opencode-slim 2.0.0-beta.9 → 2.0.1
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/README.ja-JP.md +121 -47
- package/README.ko-KR.md +708 -0
- package/README.md +149 -58
- package/README.zh-CN.md +142 -64
- package/dist/cli/background-subagents.d.ts +13 -0
- package/dist/cli/companion.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.js +651 -82
- package/dist/cli/install.d.ts +6 -1
- package/dist/cli/providers.d.ts +2 -1
- package/dist/cli/types.d.ts +8 -0
- package/dist/companion/manager.d.ts +38 -0
- package/dist/config/constants.d.ts +4 -1
- package/dist/config/fallback-chains.d.ts +1 -0
- package/dist/config/schema.d.ts +46 -40
- package/dist/hooks/auto-update-checker/checker.d.ts +7 -1
- package/dist/hooks/auto-update-checker/constants.d.ts +1 -0
- package/dist/hooks/auto-update-checker/types.d.ts +10 -0
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/json-error-recovery/hook.d.ts +1 -1
- package/dist/hooks/phase-reminder/index.d.ts +1 -1
- package/dist/index.js +1861 -2149
- package/dist/mcp/grep-app.d.ts +1 -1
- package/dist/multiplexer/zellij/index.d.ts +17 -3
- package/dist/tools/cancel-task.d.ts +6 -0
- package/dist/tools/preset-manager.d.ts +6 -7
- package/dist/tui.js +56 -31
- package/dist/utils/background-job-board.d.ts +39 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/task.d.ts +2 -0
- package/oh-my-opencode-slim.schema.json +34 -91
- package/package.json +4 -3
- package/src/skills/codemap.md +3 -1
- package/src/skills/deepwork/SKILL.md +25 -3
- package/src/skills/oh-my-opencode-slim/SKILL.md +326 -0
- package/dist/divoom/council.gif +0 -0
- package/dist/divoom/designer.gif +0 -0
- package/dist/divoom/explorer.gif +0 -0
- package/dist/divoom/fixer.gif +0 -0
- package/dist/divoom/input.gif +0 -0
- package/dist/divoom/intro.gif +0 -0
- package/dist/divoom/librarian.gif +0 -0
- package/dist/divoom/manager.d.ts +0 -57
- package/dist/divoom/oracle.gif +0 -0
- package/dist/divoom/orchestrator.gif +0 -0
- package/dist/hooks/goal/index.d.ts +0 -38
- package/dist/hooks/todo-continuation/index.d.ts +0 -55
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +0 -35
- package/dist/utils/session-manager.d.ts +0 -55
package/dist/cli/index.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __returnValue = (v) => v;
|
|
6
|
+
function __exportSetter(name, newValue) {
|
|
7
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
8
|
+
}
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
set: __exportSetter.bind(all, name)
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
20
|
|
|
6
|
-
// src/cli/doctor.ts
|
|
7
|
-
import * as fs2 from "node:fs";
|
|
8
|
-
import { z as z3 } from "zod";
|
|
9
|
-
|
|
10
|
-
// src/config/loader.ts
|
|
11
|
-
import * as fs from "node:fs";
|
|
12
|
-
import * as path from "node:path";
|
|
13
|
-
|
|
14
|
-
// src/cli/config-io.ts
|
|
15
|
-
import {
|
|
16
|
-
copyFileSync as copyFileSync2,
|
|
17
|
-
existsSync as existsSync3,
|
|
18
|
-
mkdirSync as mkdirSync3,
|
|
19
|
-
readFileSync,
|
|
20
|
-
renameSync,
|
|
21
|
-
statSync as statSync2,
|
|
22
|
-
writeFileSync
|
|
23
|
-
} from "node:fs";
|
|
24
|
-
import { homedir as homedir2 } from "node:os";
|
|
25
|
-
import { dirname as dirname3, join as join3 } from "node:path";
|
|
26
|
-
|
|
27
21
|
// src/utils/compat.ts
|
|
22
|
+
var exports_compat = {};
|
|
23
|
+
__export(exports_compat, {
|
|
24
|
+
isBun: () => isBun,
|
|
25
|
+
crossWrite: () => crossWrite,
|
|
26
|
+
crossSpawn: () => crossSpawn
|
|
27
|
+
});
|
|
28
28
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
29
|
-
|
|
29
|
+
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
30
30
|
function collectStream(stream) {
|
|
31
31
|
if (!stream)
|
|
32
32
|
return () => Promise.resolve("");
|
|
@@ -69,6 +69,124 @@ function crossSpawn(command, options) {
|
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
async function crossWrite(path, data) {
|
|
73
|
+
await fsWriteFile(path, Buffer.from(data));
|
|
74
|
+
}
|
|
75
|
+
var isBun;
|
|
76
|
+
var init_compat = __esm(() => {
|
|
77
|
+
isBun = typeof globalThis.Bun !== "undefined";
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// src/utils/zip-extractor.ts
|
|
81
|
+
var exports_zip_extractor = {};
|
|
82
|
+
__export(exports_zip_extractor, {
|
|
83
|
+
extractZip: () => extractZip
|
|
84
|
+
});
|
|
85
|
+
import { spawnSync } from "node:child_process";
|
|
86
|
+
import { release } from "node:os";
|
|
87
|
+
function getWindowsBuildNumber() {
|
|
88
|
+
if (process.platform !== "win32")
|
|
89
|
+
return null;
|
|
90
|
+
const parts = release().split(".");
|
|
91
|
+
if (parts.length >= 3) {
|
|
92
|
+
const build = parseInt(parts[2], 10);
|
|
93
|
+
if (!Number.isNaN(build))
|
|
94
|
+
return build;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function isPwshAvailable() {
|
|
99
|
+
if (process.platform !== "win32")
|
|
100
|
+
return false;
|
|
101
|
+
const result = spawnSync("where", ["pwsh"], {
|
|
102
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
103
|
+
});
|
|
104
|
+
return result.status === 0;
|
|
105
|
+
}
|
|
106
|
+
function escapePowerShellPath(path2) {
|
|
107
|
+
return path2.replace(/'/g, "''");
|
|
108
|
+
}
|
|
109
|
+
function getWindowsZipExtractor() {
|
|
110
|
+
const buildNumber = getWindowsBuildNumber();
|
|
111
|
+
if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
|
|
112
|
+
return "tar";
|
|
113
|
+
}
|
|
114
|
+
if (isPwshAvailable()) {
|
|
115
|
+
return "pwsh";
|
|
116
|
+
}
|
|
117
|
+
return "powershell";
|
|
118
|
+
}
|
|
119
|
+
async function extractZip(archivePath, destDir) {
|
|
120
|
+
let proc;
|
|
121
|
+
if (process.platform === "win32") {
|
|
122
|
+
const extractor = getWindowsZipExtractor();
|
|
123
|
+
switch (extractor) {
|
|
124
|
+
case "tar":
|
|
125
|
+
proc = crossSpawn(["tar", "-xf", archivePath, "-C", destDir], {
|
|
126
|
+
stdout: "ignore",
|
|
127
|
+
stderr: "pipe"
|
|
128
|
+
});
|
|
129
|
+
break;
|
|
130
|
+
case "pwsh":
|
|
131
|
+
proc = crossSpawn([
|
|
132
|
+
"pwsh",
|
|
133
|
+
"-Command",
|
|
134
|
+
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
135
|
+
], {
|
|
136
|
+
stdout: "ignore",
|
|
137
|
+
stderr: "pipe"
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
proc = crossSpawn([
|
|
142
|
+
"powershell",
|
|
143
|
+
"-Command",
|
|
144
|
+
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
145
|
+
], {
|
|
146
|
+
stdout: "ignore",
|
|
147
|
+
stderr: "pipe"
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
proc = crossSpawn(["unzip", "-o", archivePath, "-d", destDir], {
|
|
153
|
+
stdout: "ignore",
|
|
154
|
+
stderr: "pipe"
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const exitCode = await proc.exited;
|
|
158
|
+
if (exitCode !== 0) {
|
|
159
|
+
const stderr = await proc.stderr();
|
|
160
|
+
throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
var WINDOWS_BUILD_WITH_TAR = 17134;
|
|
164
|
+
var init_zip_extractor = __esm(() => {
|
|
165
|
+
init_compat();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// src/cli/doctor.ts
|
|
169
|
+
import * as fs2 from "node:fs";
|
|
170
|
+
import { z as z3 } from "zod";
|
|
171
|
+
|
|
172
|
+
// src/config/loader.ts
|
|
173
|
+
import * as fs from "node:fs";
|
|
174
|
+
import * as path from "node:path";
|
|
175
|
+
|
|
176
|
+
// src/cli/config-io.ts
|
|
177
|
+
init_compat();
|
|
178
|
+
import {
|
|
179
|
+
copyFileSync as copyFileSync2,
|
|
180
|
+
existsSync as existsSync3,
|
|
181
|
+
mkdirSync as mkdirSync3,
|
|
182
|
+
readFileSync,
|
|
183
|
+
renameSync,
|
|
184
|
+
rmSync,
|
|
185
|
+
statSync as statSync2,
|
|
186
|
+
writeFileSync
|
|
187
|
+
} from "node:fs";
|
|
188
|
+
import { homedir as homedir2 } from "node:os";
|
|
189
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
72
190
|
|
|
73
191
|
// src/cli/paths.ts
|
|
74
192
|
import { existsSync, mkdirSync } from "node:fs";
|
|
@@ -100,7 +218,7 @@ function getConfigSearchDirs() {
|
|
|
100
218
|
});
|
|
101
219
|
}
|
|
102
220
|
function getOpenCodeConfigPaths() {
|
|
103
|
-
const configDir =
|
|
221
|
+
const configDir = getConfigDir();
|
|
104
222
|
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
105
223
|
}
|
|
106
224
|
function getConfigJson() {
|
|
@@ -328,11 +446,13 @@ var MultiplexerLayoutSchema = z2.enum([
|
|
|
328
446
|
"even-horizontal",
|
|
329
447
|
"even-vertical"
|
|
330
448
|
]);
|
|
449
|
+
var ZellijPaneModeSchema = z2.enum(["agent-tab", "current-tab"]);
|
|
331
450
|
var TmuxLayoutSchema = MultiplexerLayoutSchema;
|
|
332
451
|
var MultiplexerConfigSchema = z2.object({
|
|
333
452
|
type: MultiplexerTypeSchema.default("none"),
|
|
334
453
|
layout: MultiplexerLayoutSchema.default("main-vertical"),
|
|
335
|
-
main_pane_size: z2.number().min(20).max(80).default(60)
|
|
454
|
+
main_pane_size: z2.number().min(20).max(80).default(60),
|
|
455
|
+
zellij_pane_mode: ZellijPaneModeSchema.default("agent-tab")
|
|
336
456
|
});
|
|
337
457
|
var TmuxConfigSchema = z2.object({
|
|
338
458
|
enabled: z2.boolean().default(false),
|
|
@@ -343,7 +463,7 @@ var PresetSchema = z2.record(z2.string(), AgentOverrideConfigSchema);
|
|
|
343
463
|
var WebsearchConfigSchema = z2.object({
|
|
344
464
|
provider: z2.enum(["exa", "tavily"]).default("exa")
|
|
345
465
|
});
|
|
346
|
-
var McpNameSchema = z2.enum(["websearch", "context7", "
|
|
466
|
+
var McpNameSchema = z2.enum(["websearch", "context7", "gh_grep"]);
|
|
347
467
|
var InterviewConfigSchema = z2.object({
|
|
348
468
|
maxQuestions: z2.number().int().min(1).max(10).default(2),
|
|
349
469
|
outputFolder: z2.string().min(1).default("interview"),
|
|
@@ -351,28 +471,11 @@ var InterviewConfigSchema = z2.object({
|
|
|
351
471
|
port: z2.number().int().min(0).max(65535).default(0),
|
|
352
472
|
dashboard: z2.boolean().default(false)
|
|
353
473
|
});
|
|
354
|
-
var
|
|
474
|
+
var BackgroundJobsConfigSchema = z2.object({
|
|
355
475
|
maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2),
|
|
356
476
|
readContextMinLines: z2.number().int().min(0).max(1000).default(10),
|
|
357
477
|
readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
|
|
358
478
|
});
|
|
359
|
-
var DivoomConfigSchema = z2.object({
|
|
360
|
-
enabled: z2.boolean().default(false),
|
|
361
|
-
python: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python"),
|
|
362
|
-
script: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py"),
|
|
363
|
-
size: z2.number().int().min(1).max(1024).default(128),
|
|
364
|
-
fps: z2.number().int().min(1).max(60).default(8),
|
|
365
|
-
speed: z2.number().int().min(1).max(1e4).default(125),
|
|
366
|
-
maxFrames: z2.number().int().min(1).max(500).default(24),
|
|
367
|
-
posterizeBits: z2.number().int().min(1).max(8).default(3),
|
|
368
|
-
gifs: z2.record(z2.string(), z2.string().min(1)).optional()
|
|
369
|
-
});
|
|
370
|
-
var TodoContinuationConfigSchema = z2.object({
|
|
371
|
-
maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
|
|
372
|
-
cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
|
|
373
|
-
autoEnable: z2.boolean().default(false).describe("Automatically enable auto-continue when the orchestrator session has enough todos"),
|
|
374
|
-
autoEnableThreshold: z2.number().int().min(1).max(50).default(4).describe("Number of todos that triggers auto-enable (only used when autoEnable is true)")
|
|
375
|
-
});
|
|
376
479
|
var FailoverConfigSchema = z2.object({
|
|
377
480
|
enabled: z2.boolean().default(true),
|
|
378
481
|
timeoutMs: z2.number().min(0).default(15000),
|
|
@@ -380,6 +483,11 @@ var FailoverConfigSchema = z2.object({
|
|
|
380
483
|
chains: FallbackChainsSchema.default({}),
|
|
381
484
|
retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
|
|
382
485
|
});
|
|
486
|
+
var CompanionConfigSchema = z2.object({
|
|
487
|
+
enabled: z2.boolean().optional(),
|
|
488
|
+
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
489
|
+
size: z2.enum(["small", "medium", "large"]).optional()
|
|
490
|
+
});
|
|
383
491
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
384
492
|
for (const [name, override] of Object.entries(overrides)) {
|
|
385
493
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -417,11 +525,10 @@ var PluginConfigSchema = z2.object({
|
|
|
417
525
|
tmux: TmuxConfigSchema.optional(),
|
|
418
526
|
websearch: WebsearchConfigSchema.optional(),
|
|
419
527
|
interview: InterviewConfigSchema.optional(),
|
|
420
|
-
|
|
421
|
-
divoom: DivoomConfigSchema.optional(),
|
|
422
|
-
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
528
|
+
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
423
529
|
fallback: FailoverConfigSchema.optional(),
|
|
424
|
-
council: CouncilConfigSchema.optional()
|
|
530
|
+
council: CouncilConfigSchema.optional(),
|
|
531
|
+
companion: CompanionConfigSchema.optional()
|
|
425
532
|
}).superRefine((value, ctx) => {
|
|
426
533
|
if (value.agents) {
|
|
427
534
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -437,7 +544,7 @@ var DEFAULT_AGENT_MCPS = {
|
|
|
437
544
|
orchestrator: ["*", "!context7"],
|
|
438
545
|
designer: [],
|
|
439
546
|
oracle: [],
|
|
440
|
-
librarian: ["websearch", "context7", "
|
|
547
|
+
librarian: ["websearch", "context7", "gh_grep"],
|
|
441
548
|
explorer: [],
|
|
442
549
|
fixer: [],
|
|
443
550
|
observer: [],
|
|
@@ -479,6 +586,12 @@ var CUSTOM_SKILLS = [
|
|
|
479
586
|
description: "Heavy/complex coding sessions and large modifications workflow",
|
|
480
587
|
allowedAgents: ["orchestrator"],
|
|
481
588
|
sourcePath: "src/skills/deepwork"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "oh-my-opencode-slim",
|
|
592
|
+
description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
|
|
593
|
+
allowedAgents: ["orchestrator"],
|
|
594
|
+
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
482
595
|
}
|
|
483
596
|
];
|
|
484
597
|
function getCustomSkillsDir() {
|
|
@@ -526,12 +639,12 @@ var SCHEMA_URL = "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-sl
|
|
|
526
639
|
var GENERATED_PRESETS = ["openai", "opencode-go"];
|
|
527
640
|
var MODEL_MAPPINGS = {
|
|
528
641
|
openai: {
|
|
529
|
-
orchestrator: { model: "openai/gpt-5.5" },
|
|
642
|
+
orchestrator: { model: "openai/gpt-5.5", variant: "medium" },
|
|
530
643
|
oracle: { model: "openai/gpt-5.5", variant: "high" },
|
|
531
644
|
librarian: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
532
645
|
explorer: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
533
646
|
designer: { model: "openai/gpt-5.4-mini", variant: "medium" },
|
|
534
|
-
fixer: { model: "openai/gpt-5.
|
|
647
|
+
fixer: { model: "openai/gpt-5.5", variant: "low" }
|
|
535
648
|
},
|
|
536
649
|
kimi: {
|
|
537
650
|
orchestrator: { model: "kimi-for-coding/k2p5" },
|
|
@@ -620,11 +733,24 @@ function generateLiteConfig(installConfig) {
|
|
|
620
733
|
main_pane_size: 60
|
|
621
734
|
};
|
|
622
735
|
}
|
|
736
|
+
if (installConfig.companion === "yes") {
|
|
737
|
+
config.companion = {
|
|
738
|
+
enabled: true,
|
|
739
|
+
position: "bottom-right",
|
|
740
|
+
size: "medium"
|
|
741
|
+
};
|
|
742
|
+
}
|
|
623
743
|
return config;
|
|
624
744
|
}
|
|
625
745
|
|
|
626
746
|
// src/cli/config-io.ts
|
|
627
747
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
748
|
+
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = [
|
|
749
|
+
"build",
|
|
750
|
+
"explore",
|
|
751
|
+
"general",
|
|
752
|
+
"plan"
|
|
753
|
+
];
|
|
628
754
|
function isString(value) {
|
|
629
755
|
return typeof value === "string";
|
|
630
756
|
}
|
|
@@ -705,17 +831,63 @@ function getPluginEntry() {
|
|
|
705
831
|
return PACKAGE_NAME;
|
|
706
832
|
}
|
|
707
833
|
}
|
|
708
|
-
function
|
|
834
|
+
function getPinnedVersionFromConfig() {
|
|
835
|
+
try {
|
|
836
|
+
const { config } = parseConfig(getExistingConfigPath());
|
|
837
|
+
if (!config)
|
|
838
|
+
return;
|
|
839
|
+
for (const entry of getPlugins(config)) {
|
|
840
|
+
const spec = getPluginSpec(entry);
|
|
841
|
+
if (!spec)
|
|
842
|
+
continue;
|
|
843
|
+
if (spec === PACKAGE_NAME)
|
|
844
|
+
return;
|
|
845
|
+
if (spec.startsWith(`${PACKAGE_NAME}@`)) {
|
|
846
|
+
const version = spec.slice(PACKAGE_NAME.length + 1);
|
|
847
|
+
if (version && version !== "latest")
|
|
848
|
+
return version;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
} catch {}
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
function getRequestedPackageTag(packageRoot) {
|
|
855
|
+
const normalizedPath = normalizePathForMatch(packageRoot);
|
|
856
|
+
const marker = `/bunx-`;
|
|
857
|
+
const markerIndex = normalizedPath.lastIndexOf(marker);
|
|
858
|
+
if (markerIndex === -1)
|
|
859
|
+
return;
|
|
860
|
+
const bunxSegment = normalizedPath.slice(markerIndex + marker.length).split("/")[0];
|
|
861
|
+
const packagePrefix = `${PACKAGE_NAME}@`;
|
|
862
|
+
const packageIndex = bunxSegment.lastIndexOf(packagePrefix);
|
|
863
|
+
if (packageIndex === -1)
|
|
864
|
+
return;
|
|
865
|
+
const tag = bunxSegment.slice(packageIndex + packagePrefix.length);
|
|
866
|
+
return tag || undefined;
|
|
867
|
+
}
|
|
868
|
+
function getVersionFromPackageRoot(packageRoot) {
|
|
869
|
+
try {
|
|
870
|
+
const packageJsonPath = join3(packageRoot, "package.json");
|
|
871
|
+
if (!existsSync3(packageJsonPath))
|
|
872
|
+
return;
|
|
873
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
874
|
+
return pkg.version;
|
|
875
|
+
} catch {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function getOpenCodePluginCacheDir(version) {
|
|
709
880
|
const cacheDir = process.env.XDG_CACHE_HOME?.trim() || join3(homedir2(), ".cache");
|
|
710
|
-
|
|
881
|
+
const suffix = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;
|
|
882
|
+
return join3(cacheDir, "opencode", "packages", suffix);
|
|
711
883
|
}
|
|
712
|
-
function writeOpenCodePluginCacheManifest(cacheDir) {
|
|
884
|
+
function writeOpenCodePluginCacheManifest(cacheDir, version = "latest") {
|
|
713
885
|
try {
|
|
714
886
|
writeFileSync(join3(cacheDir, "package.json"), JSON.stringify({
|
|
715
887
|
name: `${PACKAGE_NAME}-cache`,
|
|
716
888
|
private: true,
|
|
717
889
|
dependencies: {
|
|
718
|
-
[PACKAGE_NAME]:
|
|
890
|
+
[PACKAGE_NAME]: version
|
|
719
891
|
}
|
|
720
892
|
}, null, 2));
|
|
721
893
|
return null;
|
|
@@ -727,6 +899,14 @@ function writeOpenCodePluginCacheManifest(cacheDir) {
|
|
|
727
899
|
};
|
|
728
900
|
}
|
|
729
901
|
}
|
|
902
|
+
function removeOpenCodePluginCacheArtifacts(cacheDir) {
|
|
903
|
+
rmSync(join3(cacheDir, "node_modules", PACKAGE_NAME), {
|
|
904
|
+
recursive: true,
|
|
905
|
+
force: true
|
|
906
|
+
});
|
|
907
|
+
rmSync(join3(cacheDir, "bun.lock"), { force: true });
|
|
908
|
+
rmSync(join3(cacheDir, "bun.lockb"), { force: true });
|
|
909
|
+
}
|
|
730
910
|
function verifyOpenCodePluginCache(cacheDir) {
|
|
731
911
|
const pluginPackageJsonPath = join3(cacheDir, "node_modules", PACKAGE_NAME, "package.json");
|
|
732
912
|
if (!existsSync3(pluginPackageJsonPath)) {
|
|
@@ -763,7 +943,11 @@ async function warmOpenCodePluginCache() {
|
|
|
763
943
|
if (!packageRoot || !isPackageManagerInstall(packageRoot)) {
|
|
764
944
|
return null;
|
|
765
945
|
}
|
|
766
|
-
const
|
|
946
|
+
const pinnedVersion = getPinnedVersionFromConfig();
|
|
947
|
+
const runningVersion = getVersionFromPackageRoot(packageRoot);
|
|
948
|
+
const requestedTag = getRequestedPackageTag(packageRoot);
|
|
949
|
+
const cacheVersion = pinnedVersion ?? requestedTag ?? runningVersion;
|
|
950
|
+
const cacheDir = getOpenCodePluginCacheDir(cacheVersion);
|
|
767
951
|
try {
|
|
768
952
|
mkdirSync3(cacheDir, { recursive: true });
|
|
769
953
|
} catch (err) {
|
|
@@ -773,9 +957,10 @@ async function warmOpenCodePluginCache() {
|
|
|
773
957
|
error: `Failed to create OpenCode cache directory: ${err}`
|
|
774
958
|
};
|
|
775
959
|
}
|
|
776
|
-
const manifestError = writeOpenCodePluginCacheManifest(cacheDir);
|
|
960
|
+
const manifestError = writeOpenCodePluginCacheManifest(cacheDir, cacheVersion);
|
|
777
961
|
if (manifestError)
|
|
778
962
|
return manifestError;
|
|
963
|
+
removeOpenCodePluginCacheArtifacts(cacheDir);
|
|
779
964
|
try {
|
|
780
965
|
const proc = crossSpawn(["bun", "install", "--ignore-scripts"], {
|
|
781
966
|
cwd: cacheDir,
|
|
@@ -956,8 +1141,13 @@ function disableDefaultAgents() {
|
|
|
956
1141
|
}
|
|
957
1142
|
const config = parsedConfig ?? {};
|
|
958
1143
|
const agent = config.agent ?? {};
|
|
959
|
-
|
|
960
|
-
|
|
1144
|
+
for (const agentName of DEFAULT_OPENCODE_AGENTS_TO_DISABLE) {
|
|
1145
|
+
const existing = agent[agentName];
|
|
1146
|
+
agent[agentName] = {
|
|
1147
|
+
...existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {},
|
|
1148
|
+
disable: true
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
961
1151
|
config.agent = agent;
|
|
962
1152
|
writeConfig(configPath, config);
|
|
963
1153
|
return { success: true, configPath };
|
|
@@ -1085,10 +1275,10 @@ function mergePluginConfigs(base, override) {
|
|
|
1085
1275
|
tmux: deepMerge(base.tmux, override.tmux),
|
|
1086
1276
|
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
1087
1277
|
interview: deepMerge(base.interview, override.interview),
|
|
1088
|
-
|
|
1089
|
-
divoom: deepMerge(base.divoom, override.divoom),
|
|
1278
|
+
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
1090
1279
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
1091
|
-
council: deepMerge(base.council, override.council)
|
|
1280
|
+
council: deepMerge(base.council, override.council),
|
|
1281
|
+
companion: deepMerge(base.companion, override.companion)
|
|
1092
1282
|
};
|
|
1093
1283
|
}
|
|
1094
1284
|
function deepMerge(base, override) {
|
|
@@ -1304,16 +1494,253 @@ Options:
|
|
|
1304
1494
|
}
|
|
1305
1495
|
|
|
1306
1496
|
// src/cli/install.ts
|
|
1307
|
-
import { existsSync as
|
|
1497
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1308
1498
|
import { createInterface } from "node:readline/promises";
|
|
1499
|
+
|
|
1500
|
+
// src/cli/background-subagents.ts
|
|
1501
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1502
|
+
import { homedir as homedir3 } from "node:os";
|
|
1503
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
1504
|
+
var ENV_NAME = "OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS";
|
|
1505
|
+
var START_MARKER = "# >>> oh-my-opencode-slim background subagents >>>";
|
|
1506
|
+
var END_MARKER = "# <<< oh-my-opencode-slim background subagents <<<";
|
|
1507
|
+
function isBackgroundSubagentsEnabled(value) {
|
|
1508
|
+
if (!value)
|
|
1509
|
+
return false;
|
|
1510
|
+
const normalized = value.trim().toLowerCase();
|
|
1511
|
+
return normalized !== "" && !["0", "false", "no", "off"].includes(normalized);
|
|
1512
|
+
}
|
|
1513
|
+
function detectShellKind(shell) {
|
|
1514
|
+
const name = shell?.split("/").at(-1);
|
|
1515
|
+
if (name === "zsh" || name === "bash" || name === "fish")
|
|
1516
|
+
return name;
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
function detectBackgroundSubagentsTarget(env = process.env) {
|
|
1520
|
+
const shell = detectShellKind(env.SHELL);
|
|
1521
|
+
const home = env.HOME || homedir3();
|
|
1522
|
+
if (shell === "zsh")
|
|
1523
|
+
return join5(home, ".zshrc");
|
|
1524
|
+
if (shell === "bash")
|
|
1525
|
+
return join5(home, ".bashrc");
|
|
1526
|
+
if (shell === "fish") {
|
|
1527
|
+
const configHome = env.XDG_CONFIG_HOME || join5(home, ".config");
|
|
1528
|
+
return join5(configHome, "fish", "conf.d", "opencode-background-subagents.fish");
|
|
1529
|
+
}
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
function getBackgroundSubagentsBlock(targetPath) {
|
|
1533
|
+
const isFish = targetPath.endsWith(".fish");
|
|
1534
|
+
const command = isFish ? `set -gx ${ENV_NAME} true` : `export ${ENV_NAME}=true`;
|
|
1535
|
+
return `${START_MARKER}
|
|
1536
|
+
${command}
|
|
1537
|
+
${END_MARKER}`;
|
|
1538
|
+
}
|
|
1539
|
+
function manualBackgroundSubagentsInstructions(options) {
|
|
1540
|
+
const shell = options?.shell ?? (options?.targetPath?.endsWith(".fish") ? "fish" : undefined) ?? detectShellKind(options?.targetPath);
|
|
1541
|
+
const bashZshSnippet = `export ${ENV_NAME}=true`;
|
|
1542
|
+
const fishSnippet = `set -gx ${ENV_NAME} true`;
|
|
1543
|
+
if (shell === "fish") {
|
|
1544
|
+
return `Start OpenCode with background subagents enabled:
|
|
1545
|
+
env ${ENV_NAME}=true opencode
|
|
1546
|
+
|
|
1547
|
+
Or add this to your fish startup file:
|
|
1548
|
+
${fishSnippet}`;
|
|
1549
|
+
}
|
|
1550
|
+
if (shell === "bash" || shell === "zsh") {
|
|
1551
|
+
return `Start OpenCode with background subagents enabled:
|
|
1552
|
+
${ENV_NAME}=true opencode
|
|
1553
|
+
|
|
1554
|
+
Or add this to your shell startup file:
|
|
1555
|
+
${bashZshSnippet}`;
|
|
1556
|
+
}
|
|
1557
|
+
return `Start OpenCode with background subagents enabled:
|
|
1558
|
+
${ENV_NAME}=true opencode
|
|
1559
|
+
|
|
1560
|
+
Or add one of these to your shell startup file:
|
|
1561
|
+
bash/zsh: ${bashZshSnippet}
|
|
1562
|
+
fish: ${fishSnippet}`;
|
|
1563
|
+
}
|
|
1564
|
+
function expandHomePath(targetPath) {
|
|
1565
|
+
if (targetPath === "~")
|
|
1566
|
+
return homedir3();
|
|
1567
|
+
if (targetPath.startsWith("~/"))
|
|
1568
|
+
return join5(homedir3(), targetPath.slice(2));
|
|
1569
|
+
return targetPath;
|
|
1570
|
+
}
|
|
1571
|
+
function upsertBackgroundSubagentsBlock(content, block) {
|
|
1572
|
+
const start = content.indexOf(START_MARKER);
|
|
1573
|
+
const end = content.indexOf(END_MARKER);
|
|
1574
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
1575
|
+
const afterEnd = end + END_MARKER.length;
|
|
1576
|
+
return `${content.slice(0, start)}${block}${content.slice(afterEnd)}`;
|
|
1577
|
+
}
|
|
1578
|
+
const separator = content.length > 0 && !content.endsWith(`
|
|
1579
|
+
`) ? `
|
|
1580
|
+
|
|
1581
|
+
` : "";
|
|
1582
|
+
const prefix = content.length > 0 && content.endsWith(`
|
|
1583
|
+
`) ? `
|
|
1584
|
+
` : separator;
|
|
1585
|
+
return `${content}${prefix}${block}
|
|
1586
|
+
`;
|
|
1587
|
+
}
|
|
1588
|
+
function writeBackgroundSubagentsBlock(targetPath) {
|
|
1589
|
+
const block = getBackgroundSubagentsBlock(targetPath);
|
|
1590
|
+
const content = existsSync5(targetPath) ? readFileSync4(targetPath, "utf8") : "";
|
|
1591
|
+
const nextContent = upsertBackgroundSubagentsBlock(content, block);
|
|
1592
|
+
mkdirSync4(dirname4(targetPath), { recursive: true });
|
|
1593
|
+
writeFileSync2(targetPath, nextContent);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// src/cli/companion.ts
|
|
1597
|
+
import {
|
|
1598
|
+
chmodSync,
|
|
1599
|
+
copyFileSync as copyFileSync3,
|
|
1600
|
+
existsSync as existsSync6,
|
|
1601
|
+
mkdirSync as mkdirSync5,
|
|
1602
|
+
mkdtempSync,
|
|
1603
|
+
renameSync as renameSync2,
|
|
1604
|
+
rmSync as rmSync2,
|
|
1605
|
+
writeFileSync as writeFileSync3
|
|
1606
|
+
} from "node:fs";
|
|
1607
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
1608
|
+
import * as path2 from "node:path";
|
|
1609
|
+
var COMPANION_VERSION = "0.1.0";
|
|
1610
|
+
var COMPANION_TAG = "companion-v0.1.0";
|
|
1611
|
+
var GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
|
|
1612
|
+
function getCompanionTarget() {
|
|
1613
|
+
const p = process.platform;
|
|
1614
|
+
const a = process.arch;
|
|
1615
|
+
if (p === "darwin") {
|
|
1616
|
+
if (a === "arm64")
|
|
1617
|
+
return "aarch64-apple-darwin";
|
|
1618
|
+
if (a === "x64")
|
|
1619
|
+
return "x86_64-apple-darwin";
|
|
1620
|
+
} else if (p === "linux") {
|
|
1621
|
+
if (a === "x64")
|
|
1622
|
+
return "x86_64-unknown-linux-gnu";
|
|
1623
|
+
if (a === "arm64")
|
|
1624
|
+
return "aarch64-unknown-linux-gnu";
|
|
1625
|
+
} else if (p === "win32") {
|
|
1626
|
+
if (a === "x64")
|
|
1627
|
+
return "x86_64-pc-windows-msvc";
|
|
1628
|
+
}
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
function getCompanionBinaryPath() {
|
|
1632
|
+
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
1633
|
+
const base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(homedir4(), ".local", "share");
|
|
1634
|
+
return path2.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", process.platform === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion");
|
|
1635
|
+
}
|
|
1636
|
+
async function installCompanion(config) {
|
|
1637
|
+
const target = getCompanionTarget();
|
|
1638
|
+
const finalBinaryPath = getCompanionBinaryPath();
|
|
1639
|
+
if (!target) {
|
|
1640
|
+
return {
|
|
1641
|
+
success: false,
|
|
1642
|
+
configPath: finalBinaryPath,
|
|
1643
|
+
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
const isWindows = process.platform === "win32";
|
|
1647
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
1648
|
+
const archiveName = `oh-my-opencode-slim-companion-v${COMPANION_VERSION}-${target}.${ext}`;
|
|
1649
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${COMPANION_TAG}/${archiveName}`;
|
|
1650
|
+
if (config.dryRun) {
|
|
1651
|
+
console.log(` [dry-run] Detected companion target: ${target}`);
|
|
1652
|
+
console.log(` [dry-run] Would download archive: ${downloadUrl}`);
|
|
1653
|
+
console.log(` [dry-run] Would extract and install to: ${finalBinaryPath}`);
|
|
1654
|
+
return {
|
|
1655
|
+
success: true,
|
|
1656
|
+
configPath: finalBinaryPath
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
let buffer;
|
|
1660
|
+
try {
|
|
1661
|
+
const res = await fetch(downloadUrl);
|
|
1662
|
+
if (!res.ok) {
|
|
1663
|
+
return {
|
|
1664
|
+
success: false,
|
|
1665
|
+
configPath: finalBinaryPath,
|
|
1666
|
+
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
buffer = await res.arrayBuffer();
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
return {
|
|
1672
|
+
success: false,
|
|
1673
|
+
configPath: finalBinaryPath,
|
|
1674
|
+
error: `Failed to fetch companion archive: ${err instanceof Error ? err.message : String(err)}`
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
let tempDir = "";
|
|
1678
|
+
try {
|
|
1679
|
+
tempDir = mkdtempSync(path2.join(tmpdir(), "companion-install-"));
|
|
1680
|
+
const archivePath = path2.join(tempDir, archiveName);
|
|
1681
|
+
writeFileSync3(archivePath, Buffer.from(buffer));
|
|
1682
|
+
const extractedDir = path2.join(tempDir, "extracted");
|
|
1683
|
+
mkdirSync5(extractedDir, { recursive: true });
|
|
1684
|
+
if (isWindows) {
|
|
1685
|
+
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
1686
|
+
await extractZip2(archivePath, extractedDir);
|
|
1687
|
+
} else {
|
|
1688
|
+
const { crossSpawn: crossSpawn2 } = await Promise.resolve().then(() => (init_compat(), exports_compat));
|
|
1689
|
+
const proc = crossSpawn2(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1690
|
+
const exitCode = await proc.exited;
|
|
1691
|
+
if (exitCode !== 0) {
|
|
1692
|
+
const stderr = await proc.stderr();
|
|
1693
|
+
return {
|
|
1694
|
+
success: false,
|
|
1695
|
+
configPath: finalBinaryPath,
|
|
1696
|
+
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
const binaryName = isWindows ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
1701
|
+
const extractedBinaryPath = path2.join(extractedDir, binaryName);
|
|
1702
|
+
if (!existsSync6(extractedBinaryPath)) {
|
|
1703
|
+
return {
|
|
1704
|
+
success: false,
|
|
1705
|
+
configPath: finalBinaryPath,
|
|
1706
|
+
error: `Binary ${binaryName} not found in extracted archive`
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
const binDir = path2.dirname(finalBinaryPath);
|
|
1710
|
+
mkdirSync5(binDir, { recursive: true });
|
|
1711
|
+
const tmpFinalPath = `${finalBinaryPath}.tmp`;
|
|
1712
|
+
copyFileSync3(extractedBinaryPath, tmpFinalPath);
|
|
1713
|
+
if (!isWindows) {
|
|
1714
|
+
chmodSync(tmpFinalPath, 493);
|
|
1715
|
+
}
|
|
1716
|
+
renameSync2(tmpFinalPath, finalBinaryPath);
|
|
1717
|
+
return {
|
|
1718
|
+
success: true,
|
|
1719
|
+
configPath: finalBinaryPath
|
|
1720
|
+
};
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
return {
|
|
1723
|
+
success: false,
|
|
1724
|
+
configPath: finalBinaryPath,
|
|
1725
|
+
error: `Failed to install companion: ${err instanceof Error ? err.message : String(err)}`
|
|
1726
|
+
};
|
|
1727
|
+
} finally {
|
|
1728
|
+
if (tempDir) {
|
|
1729
|
+
try {
|
|
1730
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
1731
|
+
} catch {}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1309
1735
|
// src/cli/system.ts
|
|
1310
|
-
|
|
1736
|
+
init_compat();
|
|
1737
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1311
1738
|
import { statSync as statSync4 } from "node:fs";
|
|
1312
1739
|
var cachedOpenCodePath = null;
|
|
1313
1740
|
function resolvePathCommand(command) {
|
|
1314
1741
|
try {
|
|
1315
1742
|
const resolver = process.platform === "win32" ? "where" : "which";
|
|
1316
|
-
const result =
|
|
1743
|
+
const result = spawnSync2(resolver, [command], {
|
|
1317
1744
|
encoding: "utf-8",
|
|
1318
1745
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1319
1746
|
});
|
|
@@ -1328,7 +1755,7 @@ function resolvePathCommand(command) {
|
|
|
1328
1755
|
}
|
|
1329
1756
|
function canExecute(command, args) {
|
|
1330
1757
|
try {
|
|
1331
|
-
const result =
|
|
1758
|
+
const result = spawnSync2(command, args, {
|
|
1332
1759
|
stdio: "ignore"
|
|
1333
1760
|
});
|
|
1334
1761
|
return result.status === 0;
|
|
@@ -1430,8 +1857,8 @@ async function getOpenCodeVersion() {
|
|
|
1430
1857
|
return null;
|
|
1431
1858
|
}
|
|
1432
1859
|
function getOpenCodePath() {
|
|
1433
|
-
const
|
|
1434
|
-
return
|
|
1860
|
+
const path3 = resolveOpenCodePath();
|
|
1861
|
+
return path3 === "opencode" ? null : path3;
|
|
1435
1862
|
}
|
|
1436
1863
|
// src/cli/install.ts
|
|
1437
1864
|
var GREEN = "\x1B[32m";
|
|
@@ -1450,8 +1877,8 @@ var SYMBOLS = {
|
|
|
1450
1877
|
warn: `${YELLOW}[!]${RESET}`,
|
|
1451
1878
|
star: `${YELLOW}★${RESET}`
|
|
1452
1879
|
};
|
|
1453
|
-
var
|
|
1454
|
-
var GITHUB_URL = `https://github.com/${
|
|
1880
|
+
var GITHUB_REPO2 = "alvinunreal/oh-my-opencode-slim";
|
|
1881
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO2}`;
|
|
1455
1882
|
function printHeader(isUpdate) {
|
|
1456
1883
|
console.log();
|
|
1457
1884
|
console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
@@ -1467,6 +1894,9 @@ function printSuccess(message) {
|
|
|
1467
1894
|
function printError(message) {
|
|
1468
1895
|
console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
|
|
1469
1896
|
}
|
|
1897
|
+
function printWarning(message) {
|
|
1898
|
+
console.log(`${SYMBOLS.warn} ${YELLOW}${message}${RESET}`);
|
|
1899
|
+
}
|
|
1470
1900
|
function printInfo(message) {
|
|
1471
1901
|
console.log(`${SYMBOLS.info} ${message}`);
|
|
1472
1902
|
}
|
|
@@ -1491,7 +1921,7 @@ async function askToStarRepo(config) {
|
|
|
1491
1921
|
return;
|
|
1492
1922
|
try {
|
|
1493
1923
|
const { execFileSync } = await import("node:child_process");
|
|
1494
|
-
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${
|
|
1924
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO2}`], { stdio: "ignore", timeout: 1e4 });
|
|
1495
1925
|
printSuccess("Thanks for starring! ★");
|
|
1496
1926
|
} catch {
|
|
1497
1927
|
printInfo(`Couldn't star automatically. You can star manually:
|
|
@@ -1511,11 +1941,88 @@ async function checkOpenCodeInstalled() {
|
|
|
1511
1941
|
return { ok: false };
|
|
1512
1942
|
}
|
|
1513
1943
|
const version = await getOpenCodeVersion();
|
|
1514
|
-
const
|
|
1944
|
+
const path3 = getOpenCodePath();
|
|
1515
1945
|
const detectedVersion = version ?? "";
|
|
1516
|
-
const pathInfo =
|
|
1946
|
+
const pathInfo = path3 ? ` (${DIM}${path3}${RESET})` : "";
|
|
1517
1947
|
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
1518
|
-
return { ok: true, version: version ?? undefined, path:
|
|
1948
|
+
return { ok: true, version: version ?? undefined, path: path3 ?? undefined };
|
|
1949
|
+
}
|
|
1950
|
+
async function configureBackgroundSubagents(config) {
|
|
1951
|
+
if (isBackgroundSubagentsEnabled(process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS)) {
|
|
1952
|
+
printSuccess("OpenCode background subagents already enabled in environment");
|
|
1953
|
+
return { enabledNow: true };
|
|
1954
|
+
}
|
|
1955
|
+
const target = config.backgroundSubagentsTarget !== undefined ? expandHomePath(config.backgroundSubagentsTarget) : detectBackgroundSubagentsTarget();
|
|
1956
|
+
if (config.backgroundSubagents === "no") {
|
|
1957
|
+
printInfo("OpenCode background subagents shell setup skipped.");
|
|
1958
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1959
|
+
return { enabledNow: false };
|
|
1960
|
+
}
|
|
1961
|
+
if (!target) {
|
|
1962
|
+
printInfo("No safe shell startup file detected.");
|
|
1963
|
+
console.log(manualBackgroundSubagentsInstructions());
|
|
1964
|
+
return { enabledNow: false };
|
|
1965
|
+
}
|
|
1966
|
+
const block = getBackgroundSubagentsBlock(target);
|
|
1967
|
+
if (config.dryRun) {
|
|
1968
|
+
printInfo("Dry run mode - background subagents block that would be written:");
|
|
1969
|
+
console.log(`Target: ${target}`);
|
|
1970
|
+
console.log(`
|
|
1971
|
+
${block}
|
|
1972
|
+
`);
|
|
1973
|
+
return { enabledNow: false, configuredTarget: target };
|
|
1974
|
+
}
|
|
1975
|
+
if (config.backgroundSubagents === "ask") {
|
|
1976
|
+
if (!process.stdin.isTTY) {
|
|
1977
|
+
printInfo("Skipped background subagents shell setup in non-TTY mode.");
|
|
1978
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1979
|
+
return { enabledNow: false };
|
|
1980
|
+
}
|
|
1981
|
+
console.log();
|
|
1982
|
+
printInfo("V2 requires OpenCode background subagents for default orchestration.");
|
|
1983
|
+
printInfo(`The installer can add the required environment export to ${target}.`);
|
|
1984
|
+
const shouldWrite = await confirm("Add OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true now?", true);
|
|
1985
|
+
if (!shouldWrite) {
|
|
1986
|
+
printInfo("Skipped background subagents shell setup.");
|
|
1987
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1988
|
+
return { enabledNow: false };
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
try {
|
|
1992
|
+
writeBackgroundSubagentsBlock(target);
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1995
|
+
printError(`Could not write background subagents shell config: ${message}`);
|
|
1996
|
+
printInfo("Add the setting manually instead:");
|
|
1997
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1998
|
+
return { enabledNow: false };
|
|
1999
|
+
}
|
|
2000
|
+
printSuccess(`Background subagents enabled ${SYMBOLS.arrow} ${DIM}${target}${RESET}`);
|
|
2001
|
+
return { enabledNow: false, configuredTarget: target };
|
|
2002
|
+
}
|
|
2003
|
+
async function shouldInstallCompanion(config) {
|
|
2004
|
+
if (config.companion === "yes")
|
|
2005
|
+
return true;
|
|
2006
|
+
if (config.companion === "no")
|
|
2007
|
+
return false;
|
|
2008
|
+
if (config.dryRun) {
|
|
2009
|
+
printInfo("Dry run mode - would ask to install the desktop companion (default: yes).");
|
|
2010
|
+
config.companion = "yes";
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
if (!process.stdin.isTTY) {
|
|
2014
|
+
printInfo("Skipped desktop companion prompt in non-TTY mode. Use --companion=yes to install it.");
|
|
2015
|
+
config.companion = "no";
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
console.log();
|
|
2019
|
+
printInfo("The optional desktop companion shows live agent activity.");
|
|
2020
|
+
const shouldInstall = await confirm("Install and enable the desktop companion?", true);
|
|
2021
|
+
config.companion = shouldInstall ? "yes" : "no";
|
|
2022
|
+
if (!shouldInstall) {
|
|
2023
|
+
printInfo("Desktop companion install skipped.");
|
|
2024
|
+
}
|
|
2025
|
+
return shouldInstall;
|
|
1519
2026
|
}
|
|
1520
2027
|
function handleStepResult(result, successMsg) {
|
|
1521
2028
|
if (!result.success) {
|
|
@@ -1525,13 +2032,24 @@ function handleStepResult(result, successMsg) {
|
|
|
1525
2032
|
printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
|
|
1526
2033
|
return true;
|
|
1527
2034
|
}
|
|
2035
|
+
function handleOptionalCompanionResult(result) {
|
|
2036
|
+
if (result.success) {
|
|
2037
|
+
printSuccess(`Companion installed ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
printWarning(`Desktop companion install skipped: ${result.error}`);
|
|
2041
|
+
printInfo("The desktop companion is optional; continuing plugin installation without it.");
|
|
2042
|
+
}
|
|
1528
2043
|
async function runInstall(config) {
|
|
1529
2044
|
const detected = detectCurrentConfig();
|
|
1530
2045
|
const isUpdate = detected.isInstalled;
|
|
1531
2046
|
printHeader(isUpdate);
|
|
1532
|
-
|
|
2047
|
+
const companionInstall = await shouldInstallCompanion(config);
|
|
2048
|
+
let totalSteps = 7;
|
|
1533
2049
|
if (config.installCustomSkills)
|
|
1534
2050
|
totalSteps += 1;
|
|
2051
|
+
if (companionInstall)
|
|
2052
|
+
totalSteps += 1;
|
|
1535
2053
|
totalSteps += 1;
|
|
1536
2054
|
let step = 1;
|
|
1537
2055
|
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
@@ -1590,6 +2108,15 @@ async function runInstall(config) {
|
|
|
1590
2108
|
if (!handleStepResult(lspResult, "LSP enabled"))
|
|
1591
2109
|
return 1;
|
|
1592
2110
|
}
|
|
2111
|
+
printStep(step++, totalSteps, "Configuring OpenCode background subagents...");
|
|
2112
|
+
const backgroundSubagents = await configureBackgroundSubagents(config);
|
|
2113
|
+
if (companionInstall) {
|
|
2114
|
+
printStep(step++, totalSteps, "Installing desktop companion binary...");
|
|
2115
|
+
const companionResult = await installCompanion(config);
|
|
2116
|
+
handleOptionalCompanionResult(companionResult);
|
|
2117
|
+
if (!companionResult.success)
|
|
2118
|
+
config.companion = "no";
|
|
2119
|
+
}
|
|
1593
2120
|
printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...");
|
|
1594
2121
|
if (config.dryRun) {
|
|
1595
2122
|
const liteConfig = generateLiteConfig(config);
|
|
@@ -1599,7 +2126,7 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1599
2126
|
`);
|
|
1600
2127
|
} else {
|
|
1601
2128
|
const configPath2 = getExistingLiteConfigPath();
|
|
1602
|
-
const configExists =
|
|
2129
|
+
const configExists = existsSync7(configPath2);
|
|
1603
2130
|
if (configExists && !config.reset) {
|
|
1604
2131
|
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
1605
2132
|
} else {
|
|
@@ -1646,7 +2173,15 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1646
2173
|
console.log(` ${BLUE}${configPath}${RESET}`);
|
|
1647
2174
|
console.log();
|
|
1648
2175
|
console.log(" 4. Start OpenCode:");
|
|
1649
|
-
|
|
2176
|
+
if (backgroundSubagents.enabledNow) {
|
|
2177
|
+
console.log(` ${BLUE}$ opencode${RESET}`);
|
|
2178
|
+
} else if (backgroundSubagents.configuredTarget) {
|
|
2179
|
+
console.log(` ${BLUE}$ source ${backgroundSubagents.configuredTarget}${RESET}`);
|
|
2180
|
+
console.log(` ${BLUE}$ opencode${RESET}`);
|
|
2181
|
+
console.log(` ${DIM}Or restart your terminal before running opencode.${RESET}`);
|
|
2182
|
+
} else {
|
|
2183
|
+
console.log(` ${BLUE}$ OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode${RESET}`);
|
|
2184
|
+
}
|
|
1650
2185
|
console.log();
|
|
1651
2186
|
console.log(" 5. Verify the agents are responding:");
|
|
1652
2187
|
console.log(` ${BLUE}> ping all agents${RESET}`);
|
|
@@ -1668,7 +2203,10 @@ async function install(args) {
|
|
|
1668
2203
|
preset: args.preset,
|
|
1669
2204
|
promptForStar: args.tui,
|
|
1670
2205
|
dryRun: args.dryRun,
|
|
1671
|
-
reset: args.reset ?? false
|
|
2206
|
+
reset: args.reset ?? false,
|
|
2207
|
+
backgroundSubagents: args.backgroundSubagents ?? "ask",
|
|
2208
|
+
backgroundSubagentsTarget: args.backgroundSubagentsTarget,
|
|
2209
|
+
companion: args.companion
|
|
1672
2210
|
};
|
|
1673
2211
|
return runInstall(config);
|
|
1674
2212
|
}
|
|
@@ -1686,13 +2224,21 @@ function getGeneratedPresetNames2() {
|
|
|
1686
2224
|
function parseArgs(args) {
|
|
1687
2225
|
const result = {
|
|
1688
2226
|
tui: true,
|
|
1689
|
-
skills: "yes"
|
|
2227
|
+
skills: "yes",
|
|
2228
|
+
companion: "ask"
|
|
1690
2229
|
};
|
|
1691
2230
|
for (const arg of args) {
|
|
1692
2231
|
if (arg === "--no-tui") {
|
|
1693
2232
|
result.tui = false;
|
|
1694
2233
|
} else if (arg.startsWith("--skills=")) {
|
|
1695
2234
|
result.skills = arg.split("=")[1];
|
|
2235
|
+
} else if (arg.startsWith("--companion=")) {
|
|
2236
|
+
const mode = arg.split("=")[1];
|
|
2237
|
+
if (!["ask", "yes", "no"].includes(mode)) {
|
|
2238
|
+
console.error("Unsupported --companion value: use ask, yes, or no");
|
|
2239
|
+
process.exit(1);
|
|
2240
|
+
}
|
|
2241
|
+
result.companion = mode;
|
|
1696
2242
|
} else if (arg.startsWith("--preset=")) {
|
|
1697
2243
|
const preset = arg.split("=")[1];
|
|
1698
2244
|
if (!isGeneratedPresetName2(preset)) {
|
|
@@ -1700,6 +2246,15 @@ function parseArgs(args) {
|
|
|
1700
2246
|
process.exit(1);
|
|
1701
2247
|
}
|
|
1702
2248
|
result.preset = preset;
|
|
2249
|
+
} else if (arg.startsWith("--background-subagents=")) {
|
|
2250
|
+
const mode = arg.split("=")[1];
|
|
2251
|
+
if (!["ask", "yes", "no"].includes(mode)) {
|
|
2252
|
+
console.error("Unsupported --background-subagents value: use ask, yes, or no");
|
|
2253
|
+
process.exit(1);
|
|
2254
|
+
}
|
|
2255
|
+
result.backgroundSubagents = mode;
|
|
2256
|
+
} else if (arg.startsWith("--background-subagents-target=")) {
|
|
2257
|
+
result.backgroundSubagentsTarget = arg.split("=")[1];
|
|
1703
2258
|
} else if (arg === "--dry-run") {
|
|
1704
2259
|
result.dryRun = true;
|
|
1705
2260
|
} else if (arg === "--reset") {
|
|
@@ -1709,6 +2264,7 @@ function parseArgs(args) {
|
|
|
1709
2264
|
process.exit(0);
|
|
1710
2265
|
}
|
|
1711
2266
|
}
|
|
2267
|
+
result.backgroundSubagents ??= "ask";
|
|
1712
2268
|
return result;
|
|
1713
2269
|
}
|
|
1714
2270
|
function printHelp() {
|
|
@@ -1721,7 +2277,14 @@ Usage:
|
|
|
1721
2277
|
|
|
1722
2278
|
Options:
|
|
1723
2279
|
--skills=yes|no Install bundled skills (default: yes)
|
|
2280
|
+
--companion=ask|yes|no Install desktop companion binary and enable config
|
|
2281
|
+
(default: ask; prompt defaults to yes)
|
|
1724
2282
|
--preset=<name> Active generated config preset (default: openai)
|
|
2283
|
+
--background-subagents=ask|yes|no
|
|
2284
|
+
Persist required OpenCode background subagent env
|
|
2285
|
+
(default: ask; prompt defaults to yes)
|
|
2286
|
+
--background-subagents-target=<path>
|
|
2287
|
+
Shell startup file to update
|
|
1725
2288
|
--no-tui Non-interactive mode
|
|
1726
2289
|
--dry-run Simulate install without writing files
|
|
1727
2290
|
--reset Force overwrite of existing configuration
|
|
@@ -1739,6 +2302,7 @@ For the full config reference, see docs/configuration.md.
|
|
|
1739
2302
|
Examples:
|
|
1740
2303
|
bunx oh-my-opencode-slim install
|
|
1741
2304
|
bunx oh-my-opencode-slim install --no-tui --skills=yes
|
|
2305
|
+
bunx oh-my-opencode-slim install --background-subagents=yes
|
|
1742
2306
|
bunx oh-my-opencode-slim install --preset=opencode-go
|
|
1743
2307
|
bunx oh-my-opencode-slim install --reset
|
|
1744
2308
|
bunx oh-my-opencode-slim doctor
|
|
@@ -1764,7 +2328,12 @@ async function main() {
|
|
|
1764
2328
|
process.exit(1);
|
|
1765
2329
|
}
|
|
1766
2330
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2331
|
+
if (__require.main == __require.module) {
|
|
2332
|
+
main().catch((err) => {
|
|
2333
|
+
console.error("Fatal error:", err);
|
|
2334
|
+
process.exit(1);
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
export {
|
|
2338
|
+
parseArgs
|
|
2339
|
+
};
|