oh-my-opencode-slim 2.0.0-beta.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +86 -32
- package/README.ko-KR.md +690 -0
- package/README.md +118 -45
- package/README.zh-CN.md +97 -37
- 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 +626 -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,123 @@ 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
|
+
statSync as statSync2,
|
|
185
|
+
writeFileSync
|
|
186
|
+
} from "node:fs";
|
|
187
|
+
import { homedir as homedir2 } from "node:os";
|
|
188
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
72
189
|
|
|
73
190
|
// src/cli/paths.ts
|
|
74
191
|
import { existsSync, mkdirSync } from "node:fs";
|
|
@@ -100,7 +217,7 @@ function getConfigSearchDirs() {
|
|
|
100
217
|
});
|
|
101
218
|
}
|
|
102
219
|
function getOpenCodeConfigPaths() {
|
|
103
|
-
const configDir =
|
|
220
|
+
const configDir = getConfigDir();
|
|
104
221
|
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
105
222
|
}
|
|
106
223
|
function getConfigJson() {
|
|
@@ -328,11 +445,13 @@ var MultiplexerLayoutSchema = z2.enum([
|
|
|
328
445
|
"even-horizontal",
|
|
329
446
|
"even-vertical"
|
|
330
447
|
]);
|
|
448
|
+
var ZellijPaneModeSchema = z2.enum(["agent-tab", "current-tab"]);
|
|
331
449
|
var TmuxLayoutSchema = MultiplexerLayoutSchema;
|
|
332
450
|
var MultiplexerConfigSchema = z2.object({
|
|
333
451
|
type: MultiplexerTypeSchema.default("none"),
|
|
334
452
|
layout: MultiplexerLayoutSchema.default("main-vertical"),
|
|
335
|
-
main_pane_size: z2.number().min(20).max(80).default(60)
|
|
453
|
+
main_pane_size: z2.number().min(20).max(80).default(60),
|
|
454
|
+
zellij_pane_mode: ZellijPaneModeSchema.default("agent-tab")
|
|
336
455
|
});
|
|
337
456
|
var TmuxConfigSchema = z2.object({
|
|
338
457
|
enabled: z2.boolean().default(false),
|
|
@@ -343,7 +462,7 @@ var PresetSchema = z2.record(z2.string(), AgentOverrideConfigSchema);
|
|
|
343
462
|
var WebsearchConfigSchema = z2.object({
|
|
344
463
|
provider: z2.enum(["exa", "tavily"]).default("exa")
|
|
345
464
|
});
|
|
346
|
-
var McpNameSchema = z2.enum(["websearch", "context7", "
|
|
465
|
+
var McpNameSchema = z2.enum(["websearch", "context7", "gh_grep"]);
|
|
347
466
|
var InterviewConfigSchema = z2.object({
|
|
348
467
|
maxQuestions: z2.number().int().min(1).max(10).default(2),
|
|
349
468
|
outputFolder: z2.string().min(1).default("interview"),
|
|
@@ -351,28 +470,11 @@ var InterviewConfigSchema = z2.object({
|
|
|
351
470
|
port: z2.number().int().min(0).max(65535).default(0),
|
|
352
471
|
dashboard: z2.boolean().default(false)
|
|
353
472
|
});
|
|
354
|
-
var
|
|
473
|
+
var BackgroundJobsConfigSchema = z2.object({
|
|
355
474
|
maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2),
|
|
356
475
|
readContextMinLines: z2.number().int().min(0).max(1000).default(10),
|
|
357
476
|
readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
|
|
358
477
|
});
|
|
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
478
|
var FailoverConfigSchema = z2.object({
|
|
377
479
|
enabled: z2.boolean().default(true),
|
|
378
480
|
timeoutMs: z2.number().min(0).default(15000),
|
|
@@ -380,6 +482,11 @@ var FailoverConfigSchema = z2.object({
|
|
|
380
482
|
chains: FallbackChainsSchema.default({}),
|
|
381
483
|
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
484
|
});
|
|
485
|
+
var CompanionConfigSchema = z2.object({
|
|
486
|
+
enabled: z2.boolean().optional(),
|
|
487
|
+
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
488
|
+
size: z2.enum(["small", "medium", "large"]).optional()
|
|
489
|
+
});
|
|
383
490
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
384
491
|
for (const [name, override] of Object.entries(overrides)) {
|
|
385
492
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -417,11 +524,10 @@ var PluginConfigSchema = z2.object({
|
|
|
417
524
|
tmux: TmuxConfigSchema.optional(),
|
|
418
525
|
websearch: WebsearchConfigSchema.optional(),
|
|
419
526
|
interview: InterviewConfigSchema.optional(),
|
|
420
|
-
|
|
421
|
-
divoom: DivoomConfigSchema.optional(),
|
|
422
|
-
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
527
|
+
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
423
528
|
fallback: FailoverConfigSchema.optional(),
|
|
424
|
-
council: CouncilConfigSchema.optional()
|
|
529
|
+
council: CouncilConfigSchema.optional(),
|
|
530
|
+
companion: CompanionConfigSchema.optional()
|
|
425
531
|
}).superRefine((value, ctx) => {
|
|
426
532
|
if (value.agents) {
|
|
427
533
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -437,7 +543,7 @@ var DEFAULT_AGENT_MCPS = {
|
|
|
437
543
|
orchestrator: ["*", "!context7"],
|
|
438
544
|
designer: [],
|
|
439
545
|
oracle: [],
|
|
440
|
-
librarian: ["websearch", "context7", "
|
|
546
|
+
librarian: ["websearch", "context7", "gh_grep"],
|
|
441
547
|
explorer: [],
|
|
442
548
|
fixer: [],
|
|
443
549
|
observer: [],
|
|
@@ -479,6 +585,12 @@ var CUSTOM_SKILLS = [
|
|
|
479
585
|
description: "Heavy/complex coding sessions and large modifications workflow",
|
|
480
586
|
allowedAgents: ["orchestrator"],
|
|
481
587
|
sourcePath: "src/skills/deepwork"
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: "oh-my-opencode-slim",
|
|
591
|
+
description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
|
|
592
|
+
allowedAgents: ["orchestrator"],
|
|
593
|
+
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
482
594
|
}
|
|
483
595
|
];
|
|
484
596
|
function getCustomSkillsDir() {
|
|
@@ -526,12 +638,12 @@ var SCHEMA_URL = "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-sl
|
|
|
526
638
|
var GENERATED_PRESETS = ["openai", "opencode-go"];
|
|
527
639
|
var MODEL_MAPPINGS = {
|
|
528
640
|
openai: {
|
|
529
|
-
orchestrator: { model: "openai/gpt-5.5" },
|
|
641
|
+
orchestrator: { model: "openai/gpt-5.5", variant: "medium" },
|
|
530
642
|
oracle: { model: "openai/gpt-5.5", variant: "high" },
|
|
531
643
|
librarian: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
532
644
|
explorer: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
533
645
|
designer: { model: "openai/gpt-5.4-mini", variant: "medium" },
|
|
534
|
-
fixer: { model: "openai/gpt-5.
|
|
646
|
+
fixer: { model: "openai/gpt-5.5", variant: "low" }
|
|
535
647
|
},
|
|
536
648
|
kimi: {
|
|
537
649
|
orchestrator: { model: "kimi-for-coding/k2p5" },
|
|
@@ -620,11 +732,24 @@ function generateLiteConfig(installConfig) {
|
|
|
620
732
|
main_pane_size: 60
|
|
621
733
|
};
|
|
622
734
|
}
|
|
735
|
+
if (installConfig.companion === "yes") {
|
|
736
|
+
config.companion = {
|
|
737
|
+
enabled: true,
|
|
738
|
+
position: "bottom-right",
|
|
739
|
+
size: "medium"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
623
742
|
return config;
|
|
624
743
|
}
|
|
625
744
|
|
|
626
745
|
// src/cli/config-io.ts
|
|
627
746
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
747
|
+
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = [
|
|
748
|
+
"build",
|
|
749
|
+
"explore",
|
|
750
|
+
"general",
|
|
751
|
+
"plan"
|
|
752
|
+
];
|
|
628
753
|
function isString(value) {
|
|
629
754
|
return typeof value === "string";
|
|
630
755
|
}
|
|
@@ -705,17 +830,49 @@ function getPluginEntry() {
|
|
|
705
830
|
return PACKAGE_NAME;
|
|
706
831
|
}
|
|
707
832
|
}
|
|
708
|
-
function
|
|
833
|
+
function getPinnedVersionFromConfig() {
|
|
834
|
+
try {
|
|
835
|
+
const { config } = parseConfig(getExistingConfigPath());
|
|
836
|
+
if (!config)
|
|
837
|
+
return;
|
|
838
|
+
for (const entry of getPlugins(config)) {
|
|
839
|
+
const spec = getPluginSpec(entry);
|
|
840
|
+
if (!spec)
|
|
841
|
+
continue;
|
|
842
|
+
if (spec === PACKAGE_NAME)
|
|
843
|
+
return;
|
|
844
|
+
if (spec.startsWith(`${PACKAGE_NAME}@`)) {
|
|
845
|
+
const version = spec.slice(PACKAGE_NAME.length + 1);
|
|
846
|
+
if (version && version !== "latest")
|
|
847
|
+
return version;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
} catch {}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
function getVersionFromPackageRoot(packageRoot) {
|
|
854
|
+
try {
|
|
855
|
+
const packageJsonPath = join3(packageRoot, "package.json");
|
|
856
|
+
if (!existsSync3(packageJsonPath))
|
|
857
|
+
return;
|
|
858
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
859
|
+
return pkg.version;
|
|
860
|
+
} catch {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function getOpenCodePluginCacheDir(version) {
|
|
709
865
|
const cacheDir = process.env.XDG_CACHE_HOME?.trim() || join3(homedir2(), ".cache");
|
|
710
|
-
|
|
866
|
+
const suffix = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;
|
|
867
|
+
return join3(cacheDir, "opencode", "packages", suffix);
|
|
711
868
|
}
|
|
712
|
-
function writeOpenCodePluginCacheManifest(cacheDir) {
|
|
869
|
+
function writeOpenCodePluginCacheManifest(cacheDir, version = "latest") {
|
|
713
870
|
try {
|
|
714
871
|
writeFileSync(join3(cacheDir, "package.json"), JSON.stringify({
|
|
715
872
|
name: `${PACKAGE_NAME}-cache`,
|
|
716
873
|
private: true,
|
|
717
874
|
dependencies: {
|
|
718
|
-
[PACKAGE_NAME]:
|
|
875
|
+
[PACKAGE_NAME]: version
|
|
719
876
|
}
|
|
720
877
|
}, null, 2));
|
|
721
878
|
return null;
|
|
@@ -763,7 +920,10 @@ async function warmOpenCodePluginCache() {
|
|
|
763
920
|
if (!packageRoot || !isPackageManagerInstall(packageRoot)) {
|
|
764
921
|
return null;
|
|
765
922
|
}
|
|
766
|
-
const
|
|
923
|
+
const pinnedVersion = getPinnedVersionFromConfig();
|
|
924
|
+
const runningVersion = getVersionFromPackageRoot(packageRoot);
|
|
925
|
+
const cacheVersion = pinnedVersion ?? runningVersion;
|
|
926
|
+
const cacheDir = getOpenCodePluginCacheDir(cacheVersion);
|
|
767
927
|
try {
|
|
768
928
|
mkdirSync3(cacheDir, { recursive: true });
|
|
769
929
|
} catch (err) {
|
|
@@ -773,7 +933,7 @@ async function warmOpenCodePluginCache() {
|
|
|
773
933
|
error: `Failed to create OpenCode cache directory: ${err}`
|
|
774
934
|
};
|
|
775
935
|
}
|
|
776
|
-
const manifestError = writeOpenCodePluginCacheManifest(cacheDir);
|
|
936
|
+
const manifestError = writeOpenCodePluginCacheManifest(cacheDir, cacheVersion);
|
|
777
937
|
if (manifestError)
|
|
778
938
|
return manifestError;
|
|
779
939
|
try {
|
|
@@ -956,8 +1116,13 @@ function disableDefaultAgents() {
|
|
|
956
1116
|
}
|
|
957
1117
|
const config = parsedConfig ?? {};
|
|
958
1118
|
const agent = config.agent ?? {};
|
|
959
|
-
|
|
960
|
-
|
|
1119
|
+
for (const agentName of DEFAULT_OPENCODE_AGENTS_TO_DISABLE) {
|
|
1120
|
+
const existing = agent[agentName];
|
|
1121
|
+
agent[agentName] = {
|
|
1122
|
+
...existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {},
|
|
1123
|
+
disable: true
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
961
1126
|
config.agent = agent;
|
|
962
1127
|
writeConfig(configPath, config);
|
|
963
1128
|
return { success: true, configPath };
|
|
@@ -1085,10 +1250,10 @@ function mergePluginConfigs(base, override) {
|
|
|
1085
1250
|
tmux: deepMerge(base.tmux, override.tmux),
|
|
1086
1251
|
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
1087
1252
|
interview: deepMerge(base.interview, override.interview),
|
|
1088
|
-
|
|
1089
|
-
divoom: deepMerge(base.divoom, override.divoom),
|
|
1253
|
+
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
1090
1254
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
1091
|
-
council: deepMerge(base.council, override.council)
|
|
1255
|
+
council: deepMerge(base.council, override.council),
|
|
1256
|
+
companion: deepMerge(base.companion, override.companion)
|
|
1092
1257
|
};
|
|
1093
1258
|
}
|
|
1094
1259
|
function deepMerge(base, override) {
|
|
@@ -1304,16 +1469,253 @@ Options:
|
|
|
1304
1469
|
}
|
|
1305
1470
|
|
|
1306
1471
|
// src/cli/install.ts
|
|
1307
|
-
import { existsSync as
|
|
1472
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1308
1473
|
import { createInterface } from "node:readline/promises";
|
|
1474
|
+
|
|
1475
|
+
// src/cli/background-subagents.ts
|
|
1476
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1477
|
+
import { homedir as homedir3 } from "node:os";
|
|
1478
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
1479
|
+
var ENV_NAME = "OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS";
|
|
1480
|
+
var START_MARKER = "# >>> oh-my-opencode-slim background subagents >>>";
|
|
1481
|
+
var END_MARKER = "# <<< oh-my-opencode-slim background subagents <<<";
|
|
1482
|
+
function isBackgroundSubagentsEnabled(value) {
|
|
1483
|
+
if (!value)
|
|
1484
|
+
return false;
|
|
1485
|
+
const normalized = value.trim().toLowerCase();
|
|
1486
|
+
return normalized !== "" && !["0", "false", "no", "off"].includes(normalized);
|
|
1487
|
+
}
|
|
1488
|
+
function detectShellKind(shell) {
|
|
1489
|
+
const name = shell?.split("/").at(-1);
|
|
1490
|
+
if (name === "zsh" || name === "bash" || name === "fish")
|
|
1491
|
+
return name;
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
function detectBackgroundSubagentsTarget(env = process.env) {
|
|
1495
|
+
const shell = detectShellKind(env.SHELL);
|
|
1496
|
+
const home = env.HOME || homedir3();
|
|
1497
|
+
if (shell === "zsh")
|
|
1498
|
+
return join5(home, ".zshrc");
|
|
1499
|
+
if (shell === "bash")
|
|
1500
|
+
return join5(home, ".bashrc");
|
|
1501
|
+
if (shell === "fish") {
|
|
1502
|
+
const configHome = env.XDG_CONFIG_HOME || join5(home, ".config");
|
|
1503
|
+
return join5(configHome, "fish", "conf.d", "opencode-background-subagents.fish");
|
|
1504
|
+
}
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
function getBackgroundSubagentsBlock(targetPath) {
|
|
1508
|
+
const isFish = targetPath.endsWith(".fish");
|
|
1509
|
+
const command = isFish ? `set -gx ${ENV_NAME} true` : `export ${ENV_NAME}=true`;
|
|
1510
|
+
return `${START_MARKER}
|
|
1511
|
+
${command}
|
|
1512
|
+
${END_MARKER}`;
|
|
1513
|
+
}
|
|
1514
|
+
function manualBackgroundSubagentsInstructions(options) {
|
|
1515
|
+
const shell = options?.shell ?? (options?.targetPath?.endsWith(".fish") ? "fish" : undefined) ?? detectShellKind(options?.targetPath);
|
|
1516
|
+
const bashZshSnippet = `export ${ENV_NAME}=true`;
|
|
1517
|
+
const fishSnippet = `set -gx ${ENV_NAME} true`;
|
|
1518
|
+
if (shell === "fish") {
|
|
1519
|
+
return `Start OpenCode with background subagents enabled:
|
|
1520
|
+
env ${ENV_NAME}=true opencode
|
|
1521
|
+
|
|
1522
|
+
Or add this to your fish startup file:
|
|
1523
|
+
${fishSnippet}`;
|
|
1524
|
+
}
|
|
1525
|
+
if (shell === "bash" || shell === "zsh") {
|
|
1526
|
+
return `Start OpenCode with background subagents enabled:
|
|
1527
|
+
${ENV_NAME}=true opencode
|
|
1528
|
+
|
|
1529
|
+
Or add this to your shell startup file:
|
|
1530
|
+
${bashZshSnippet}`;
|
|
1531
|
+
}
|
|
1532
|
+
return `Start OpenCode with background subagents enabled:
|
|
1533
|
+
${ENV_NAME}=true opencode
|
|
1534
|
+
|
|
1535
|
+
Or add one of these to your shell startup file:
|
|
1536
|
+
bash/zsh: ${bashZshSnippet}
|
|
1537
|
+
fish: ${fishSnippet}`;
|
|
1538
|
+
}
|
|
1539
|
+
function expandHomePath(targetPath) {
|
|
1540
|
+
if (targetPath === "~")
|
|
1541
|
+
return homedir3();
|
|
1542
|
+
if (targetPath.startsWith("~/"))
|
|
1543
|
+
return join5(homedir3(), targetPath.slice(2));
|
|
1544
|
+
return targetPath;
|
|
1545
|
+
}
|
|
1546
|
+
function upsertBackgroundSubagentsBlock(content, block) {
|
|
1547
|
+
const start = content.indexOf(START_MARKER);
|
|
1548
|
+
const end = content.indexOf(END_MARKER);
|
|
1549
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
1550
|
+
const afterEnd = end + END_MARKER.length;
|
|
1551
|
+
return `${content.slice(0, start)}${block}${content.slice(afterEnd)}`;
|
|
1552
|
+
}
|
|
1553
|
+
const separator = content.length > 0 && !content.endsWith(`
|
|
1554
|
+
`) ? `
|
|
1555
|
+
|
|
1556
|
+
` : "";
|
|
1557
|
+
const prefix = content.length > 0 && content.endsWith(`
|
|
1558
|
+
`) ? `
|
|
1559
|
+
` : separator;
|
|
1560
|
+
return `${content}${prefix}${block}
|
|
1561
|
+
`;
|
|
1562
|
+
}
|
|
1563
|
+
function writeBackgroundSubagentsBlock(targetPath) {
|
|
1564
|
+
const block = getBackgroundSubagentsBlock(targetPath);
|
|
1565
|
+
const content = existsSync5(targetPath) ? readFileSync4(targetPath, "utf8") : "";
|
|
1566
|
+
const nextContent = upsertBackgroundSubagentsBlock(content, block);
|
|
1567
|
+
mkdirSync4(dirname4(targetPath), { recursive: true });
|
|
1568
|
+
writeFileSync2(targetPath, nextContent);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/cli/companion.ts
|
|
1572
|
+
import {
|
|
1573
|
+
chmodSync,
|
|
1574
|
+
copyFileSync as copyFileSync3,
|
|
1575
|
+
existsSync as existsSync6,
|
|
1576
|
+
mkdirSync as mkdirSync5,
|
|
1577
|
+
mkdtempSync,
|
|
1578
|
+
renameSync as renameSync2,
|
|
1579
|
+
rmSync,
|
|
1580
|
+
writeFileSync as writeFileSync3
|
|
1581
|
+
} from "node:fs";
|
|
1582
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
1583
|
+
import * as path2 from "node:path";
|
|
1584
|
+
var COMPANION_VERSION = "0.1.0";
|
|
1585
|
+
var COMPANION_TAG = "companion-v0.1.0";
|
|
1586
|
+
var GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
|
|
1587
|
+
function getCompanionTarget() {
|
|
1588
|
+
const p = process.platform;
|
|
1589
|
+
const a = process.arch;
|
|
1590
|
+
if (p === "darwin") {
|
|
1591
|
+
if (a === "arm64")
|
|
1592
|
+
return "aarch64-apple-darwin";
|
|
1593
|
+
if (a === "x64")
|
|
1594
|
+
return "x86_64-apple-darwin";
|
|
1595
|
+
} else if (p === "linux") {
|
|
1596
|
+
if (a === "x64")
|
|
1597
|
+
return "x86_64-unknown-linux-gnu";
|
|
1598
|
+
if (a === "arm64")
|
|
1599
|
+
return "aarch64-unknown-linux-gnu";
|
|
1600
|
+
} else if (p === "win32") {
|
|
1601
|
+
if (a === "x64")
|
|
1602
|
+
return "x86_64-pc-windows-msvc";
|
|
1603
|
+
}
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
function getCompanionBinaryPath() {
|
|
1607
|
+
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
1608
|
+
const base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(homedir4(), ".local", "share");
|
|
1609
|
+
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");
|
|
1610
|
+
}
|
|
1611
|
+
async function installCompanion(config) {
|
|
1612
|
+
const target = getCompanionTarget();
|
|
1613
|
+
const finalBinaryPath = getCompanionBinaryPath();
|
|
1614
|
+
if (!target) {
|
|
1615
|
+
return {
|
|
1616
|
+
success: false,
|
|
1617
|
+
configPath: finalBinaryPath,
|
|
1618
|
+
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
const isWindows = process.platform === "win32";
|
|
1622
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
1623
|
+
const archiveName = `oh-my-opencode-slim-companion-v${COMPANION_VERSION}-${target}.${ext}`;
|
|
1624
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${COMPANION_TAG}/${archiveName}`;
|
|
1625
|
+
if (config.dryRun) {
|
|
1626
|
+
console.log(` [dry-run] Detected companion target: ${target}`);
|
|
1627
|
+
console.log(` [dry-run] Would download archive: ${downloadUrl}`);
|
|
1628
|
+
console.log(` [dry-run] Would extract and install to: ${finalBinaryPath}`);
|
|
1629
|
+
return {
|
|
1630
|
+
success: true,
|
|
1631
|
+
configPath: finalBinaryPath
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
let buffer;
|
|
1635
|
+
try {
|
|
1636
|
+
const res = await fetch(downloadUrl);
|
|
1637
|
+
if (!res.ok) {
|
|
1638
|
+
return {
|
|
1639
|
+
success: false,
|
|
1640
|
+
configPath: finalBinaryPath,
|
|
1641
|
+
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
buffer = await res.arrayBuffer();
|
|
1645
|
+
} catch (err) {
|
|
1646
|
+
return {
|
|
1647
|
+
success: false,
|
|
1648
|
+
configPath: finalBinaryPath,
|
|
1649
|
+
error: `Failed to fetch companion archive: ${err instanceof Error ? err.message : String(err)}`
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
let tempDir = "";
|
|
1653
|
+
try {
|
|
1654
|
+
tempDir = mkdtempSync(path2.join(tmpdir(), "companion-install-"));
|
|
1655
|
+
const archivePath = path2.join(tempDir, archiveName);
|
|
1656
|
+
writeFileSync3(archivePath, Buffer.from(buffer));
|
|
1657
|
+
const extractedDir = path2.join(tempDir, "extracted");
|
|
1658
|
+
mkdirSync5(extractedDir, { recursive: true });
|
|
1659
|
+
if (isWindows) {
|
|
1660
|
+
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
1661
|
+
await extractZip2(archivePath, extractedDir);
|
|
1662
|
+
} else {
|
|
1663
|
+
const { crossSpawn: crossSpawn2 } = await Promise.resolve().then(() => (init_compat(), exports_compat));
|
|
1664
|
+
const proc = crossSpawn2(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1665
|
+
const exitCode = await proc.exited;
|
|
1666
|
+
if (exitCode !== 0) {
|
|
1667
|
+
const stderr = await proc.stderr();
|
|
1668
|
+
return {
|
|
1669
|
+
success: false,
|
|
1670
|
+
configPath: finalBinaryPath,
|
|
1671
|
+
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const binaryName = isWindows ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
1676
|
+
const extractedBinaryPath = path2.join(extractedDir, binaryName);
|
|
1677
|
+
if (!existsSync6(extractedBinaryPath)) {
|
|
1678
|
+
return {
|
|
1679
|
+
success: false,
|
|
1680
|
+
configPath: finalBinaryPath,
|
|
1681
|
+
error: `Binary ${binaryName} not found in extracted archive`
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
const binDir = path2.dirname(finalBinaryPath);
|
|
1685
|
+
mkdirSync5(binDir, { recursive: true });
|
|
1686
|
+
const tmpFinalPath = `${finalBinaryPath}.tmp`;
|
|
1687
|
+
copyFileSync3(extractedBinaryPath, tmpFinalPath);
|
|
1688
|
+
if (!isWindows) {
|
|
1689
|
+
chmodSync(tmpFinalPath, 493);
|
|
1690
|
+
}
|
|
1691
|
+
renameSync2(tmpFinalPath, finalBinaryPath);
|
|
1692
|
+
return {
|
|
1693
|
+
success: true,
|
|
1694
|
+
configPath: finalBinaryPath
|
|
1695
|
+
};
|
|
1696
|
+
} catch (err) {
|
|
1697
|
+
return {
|
|
1698
|
+
success: false,
|
|
1699
|
+
configPath: finalBinaryPath,
|
|
1700
|
+
error: `Failed to install companion: ${err instanceof Error ? err.message : String(err)}`
|
|
1701
|
+
};
|
|
1702
|
+
} finally {
|
|
1703
|
+
if (tempDir) {
|
|
1704
|
+
try {
|
|
1705
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
1706
|
+
} catch {}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1309
1710
|
// src/cli/system.ts
|
|
1310
|
-
|
|
1711
|
+
init_compat();
|
|
1712
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1311
1713
|
import { statSync as statSync4 } from "node:fs";
|
|
1312
1714
|
var cachedOpenCodePath = null;
|
|
1313
1715
|
function resolvePathCommand(command) {
|
|
1314
1716
|
try {
|
|
1315
1717
|
const resolver = process.platform === "win32" ? "where" : "which";
|
|
1316
|
-
const result =
|
|
1718
|
+
const result = spawnSync2(resolver, [command], {
|
|
1317
1719
|
encoding: "utf-8",
|
|
1318
1720
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1319
1721
|
});
|
|
@@ -1328,7 +1730,7 @@ function resolvePathCommand(command) {
|
|
|
1328
1730
|
}
|
|
1329
1731
|
function canExecute(command, args) {
|
|
1330
1732
|
try {
|
|
1331
|
-
const result =
|
|
1733
|
+
const result = spawnSync2(command, args, {
|
|
1332
1734
|
stdio: "ignore"
|
|
1333
1735
|
});
|
|
1334
1736
|
return result.status === 0;
|
|
@@ -1430,8 +1832,8 @@ async function getOpenCodeVersion() {
|
|
|
1430
1832
|
return null;
|
|
1431
1833
|
}
|
|
1432
1834
|
function getOpenCodePath() {
|
|
1433
|
-
const
|
|
1434
|
-
return
|
|
1835
|
+
const path3 = resolveOpenCodePath();
|
|
1836
|
+
return path3 === "opencode" ? null : path3;
|
|
1435
1837
|
}
|
|
1436
1838
|
// src/cli/install.ts
|
|
1437
1839
|
var GREEN = "\x1B[32m";
|
|
@@ -1450,8 +1852,8 @@ var SYMBOLS = {
|
|
|
1450
1852
|
warn: `${YELLOW}[!]${RESET}`,
|
|
1451
1853
|
star: `${YELLOW}★${RESET}`
|
|
1452
1854
|
};
|
|
1453
|
-
var
|
|
1454
|
-
var GITHUB_URL = `https://github.com/${
|
|
1855
|
+
var GITHUB_REPO2 = "alvinunreal/oh-my-opencode-slim";
|
|
1856
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO2}`;
|
|
1455
1857
|
function printHeader(isUpdate) {
|
|
1456
1858
|
console.log();
|
|
1457
1859
|
console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
@@ -1467,6 +1869,9 @@ function printSuccess(message) {
|
|
|
1467
1869
|
function printError(message) {
|
|
1468
1870
|
console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
|
|
1469
1871
|
}
|
|
1872
|
+
function printWarning(message) {
|
|
1873
|
+
console.log(`${SYMBOLS.warn} ${YELLOW}${message}${RESET}`);
|
|
1874
|
+
}
|
|
1470
1875
|
function printInfo(message) {
|
|
1471
1876
|
console.log(`${SYMBOLS.info} ${message}`);
|
|
1472
1877
|
}
|
|
@@ -1491,7 +1896,7 @@ async function askToStarRepo(config) {
|
|
|
1491
1896
|
return;
|
|
1492
1897
|
try {
|
|
1493
1898
|
const { execFileSync } = await import("node:child_process");
|
|
1494
|
-
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${
|
|
1899
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO2}`], { stdio: "ignore", timeout: 1e4 });
|
|
1495
1900
|
printSuccess("Thanks for starring! ★");
|
|
1496
1901
|
} catch {
|
|
1497
1902
|
printInfo(`Couldn't star automatically. You can star manually:
|
|
@@ -1511,11 +1916,88 @@ async function checkOpenCodeInstalled() {
|
|
|
1511
1916
|
return { ok: false };
|
|
1512
1917
|
}
|
|
1513
1918
|
const version = await getOpenCodeVersion();
|
|
1514
|
-
const
|
|
1919
|
+
const path3 = getOpenCodePath();
|
|
1515
1920
|
const detectedVersion = version ?? "";
|
|
1516
|
-
const pathInfo =
|
|
1921
|
+
const pathInfo = path3 ? ` (${DIM}${path3}${RESET})` : "";
|
|
1517
1922
|
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
1518
|
-
return { ok: true, version: version ?? undefined, path:
|
|
1923
|
+
return { ok: true, version: version ?? undefined, path: path3 ?? undefined };
|
|
1924
|
+
}
|
|
1925
|
+
async function configureBackgroundSubagents(config) {
|
|
1926
|
+
if (isBackgroundSubagentsEnabled(process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS)) {
|
|
1927
|
+
printSuccess("OpenCode background subagents already enabled in environment");
|
|
1928
|
+
return { enabledNow: true };
|
|
1929
|
+
}
|
|
1930
|
+
const target = config.backgroundSubagentsTarget !== undefined ? expandHomePath(config.backgroundSubagentsTarget) : detectBackgroundSubagentsTarget();
|
|
1931
|
+
if (config.backgroundSubagents === "no") {
|
|
1932
|
+
printInfo("OpenCode background subagents shell setup skipped.");
|
|
1933
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1934
|
+
return { enabledNow: false };
|
|
1935
|
+
}
|
|
1936
|
+
if (!target) {
|
|
1937
|
+
printInfo("No safe shell startup file detected.");
|
|
1938
|
+
console.log(manualBackgroundSubagentsInstructions());
|
|
1939
|
+
return { enabledNow: false };
|
|
1940
|
+
}
|
|
1941
|
+
const block = getBackgroundSubagentsBlock(target);
|
|
1942
|
+
if (config.dryRun) {
|
|
1943
|
+
printInfo("Dry run mode - background subagents block that would be written:");
|
|
1944
|
+
console.log(`Target: ${target}`);
|
|
1945
|
+
console.log(`
|
|
1946
|
+
${block}
|
|
1947
|
+
`);
|
|
1948
|
+
return { enabledNow: false, configuredTarget: target };
|
|
1949
|
+
}
|
|
1950
|
+
if (config.backgroundSubagents === "ask") {
|
|
1951
|
+
if (!process.stdin.isTTY) {
|
|
1952
|
+
printInfo("Skipped background subagents shell setup in non-TTY mode.");
|
|
1953
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1954
|
+
return { enabledNow: false };
|
|
1955
|
+
}
|
|
1956
|
+
console.log();
|
|
1957
|
+
printInfo("V2 requires OpenCode background subagents for default orchestration.");
|
|
1958
|
+
printInfo(`The installer can add the required environment export to ${target}.`);
|
|
1959
|
+
const shouldWrite = await confirm("Add OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true now?", true);
|
|
1960
|
+
if (!shouldWrite) {
|
|
1961
|
+
printInfo("Skipped background subagents shell setup.");
|
|
1962
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1963
|
+
return { enabledNow: false };
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
try {
|
|
1967
|
+
writeBackgroundSubagentsBlock(target);
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1970
|
+
printError(`Could not write background subagents shell config: ${message}`);
|
|
1971
|
+
printInfo("Add the setting manually instead:");
|
|
1972
|
+
console.log(manualBackgroundSubagentsInstructions({ targetPath: target }));
|
|
1973
|
+
return { enabledNow: false };
|
|
1974
|
+
}
|
|
1975
|
+
printSuccess(`Background subagents enabled ${SYMBOLS.arrow} ${DIM}${target}${RESET}`);
|
|
1976
|
+
return { enabledNow: false, configuredTarget: target };
|
|
1977
|
+
}
|
|
1978
|
+
async function shouldInstallCompanion(config) {
|
|
1979
|
+
if (config.companion === "yes")
|
|
1980
|
+
return true;
|
|
1981
|
+
if (config.companion === "no")
|
|
1982
|
+
return false;
|
|
1983
|
+
if (config.dryRun) {
|
|
1984
|
+
printInfo("Dry run mode - would ask to install the desktop companion (default: yes).");
|
|
1985
|
+
config.companion = "yes";
|
|
1986
|
+
return true;
|
|
1987
|
+
}
|
|
1988
|
+
if (!process.stdin.isTTY) {
|
|
1989
|
+
printInfo("Skipped desktop companion prompt in non-TTY mode. Use --companion=yes to install it.");
|
|
1990
|
+
config.companion = "no";
|
|
1991
|
+
return false;
|
|
1992
|
+
}
|
|
1993
|
+
console.log();
|
|
1994
|
+
printInfo("The optional desktop companion shows live agent activity.");
|
|
1995
|
+
const shouldInstall = await confirm("Install and enable the desktop companion?", true);
|
|
1996
|
+
config.companion = shouldInstall ? "yes" : "no";
|
|
1997
|
+
if (!shouldInstall) {
|
|
1998
|
+
printInfo("Desktop companion install skipped.");
|
|
1999
|
+
}
|
|
2000
|
+
return shouldInstall;
|
|
1519
2001
|
}
|
|
1520
2002
|
function handleStepResult(result, successMsg) {
|
|
1521
2003
|
if (!result.success) {
|
|
@@ -1525,13 +2007,24 @@ function handleStepResult(result, successMsg) {
|
|
|
1525
2007
|
printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
|
|
1526
2008
|
return true;
|
|
1527
2009
|
}
|
|
2010
|
+
function handleOptionalCompanionResult(result) {
|
|
2011
|
+
if (result.success) {
|
|
2012
|
+
printSuccess(`Companion installed ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
printWarning(`Desktop companion install skipped: ${result.error}`);
|
|
2016
|
+
printInfo("The desktop companion is optional; continuing plugin installation without it.");
|
|
2017
|
+
}
|
|
1528
2018
|
async function runInstall(config) {
|
|
1529
2019
|
const detected = detectCurrentConfig();
|
|
1530
2020
|
const isUpdate = detected.isInstalled;
|
|
1531
2021
|
printHeader(isUpdate);
|
|
1532
|
-
|
|
2022
|
+
const companionInstall = await shouldInstallCompanion(config);
|
|
2023
|
+
let totalSteps = 7;
|
|
1533
2024
|
if (config.installCustomSkills)
|
|
1534
2025
|
totalSteps += 1;
|
|
2026
|
+
if (companionInstall)
|
|
2027
|
+
totalSteps += 1;
|
|
1535
2028
|
totalSteps += 1;
|
|
1536
2029
|
let step = 1;
|
|
1537
2030
|
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
@@ -1590,6 +2083,15 @@ async function runInstall(config) {
|
|
|
1590
2083
|
if (!handleStepResult(lspResult, "LSP enabled"))
|
|
1591
2084
|
return 1;
|
|
1592
2085
|
}
|
|
2086
|
+
printStep(step++, totalSteps, "Configuring OpenCode background subagents...");
|
|
2087
|
+
const backgroundSubagents = await configureBackgroundSubagents(config);
|
|
2088
|
+
if (companionInstall) {
|
|
2089
|
+
printStep(step++, totalSteps, "Installing desktop companion binary...");
|
|
2090
|
+
const companionResult = await installCompanion(config);
|
|
2091
|
+
handleOptionalCompanionResult(companionResult);
|
|
2092
|
+
if (!companionResult.success)
|
|
2093
|
+
config.companion = "no";
|
|
2094
|
+
}
|
|
1593
2095
|
printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...");
|
|
1594
2096
|
if (config.dryRun) {
|
|
1595
2097
|
const liteConfig = generateLiteConfig(config);
|
|
@@ -1599,7 +2101,7 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1599
2101
|
`);
|
|
1600
2102
|
} else {
|
|
1601
2103
|
const configPath2 = getExistingLiteConfigPath();
|
|
1602
|
-
const configExists =
|
|
2104
|
+
const configExists = existsSync7(configPath2);
|
|
1603
2105
|
if (configExists && !config.reset) {
|
|
1604
2106
|
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
1605
2107
|
} else {
|
|
@@ -1646,7 +2148,15 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1646
2148
|
console.log(` ${BLUE}${configPath}${RESET}`);
|
|
1647
2149
|
console.log();
|
|
1648
2150
|
console.log(" 4. Start OpenCode:");
|
|
1649
|
-
|
|
2151
|
+
if (backgroundSubagents.enabledNow) {
|
|
2152
|
+
console.log(` ${BLUE}$ opencode${RESET}`);
|
|
2153
|
+
} else if (backgroundSubagents.configuredTarget) {
|
|
2154
|
+
console.log(` ${BLUE}$ source ${backgroundSubagents.configuredTarget}${RESET}`);
|
|
2155
|
+
console.log(` ${BLUE}$ opencode${RESET}`);
|
|
2156
|
+
console.log(` ${DIM}Or restart your terminal before running opencode.${RESET}`);
|
|
2157
|
+
} else {
|
|
2158
|
+
console.log(` ${BLUE}$ OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode${RESET}`);
|
|
2159
|
+
}
|
|
1650
2160
|
console.log();
|
|
1651
2161
|
console.log(" 5. Verify the agents are responding:");
|
|
1652
2162
|
console.log(` ${BLUE}> ping all agents${RESET}`);
|
|
@@ -1668,7 +2178,10 @@ async function install(args) {
|
|
|
1668
2178
|
preset: args.preset,
|
|
1669
2179
|
promptForStar: args.tui,
|
|
1670
2180
|
dryRun: args.dryRun,
|
|
1671
|
-
reset: args.reset ?? false
|
|
2181
|
+
reset: args.reset ?? false,
|
|
2182
|
+
backgroundSubagents: args.backgroundSubagents ?? "ask",
|
|
2183
|
+
backgroundSubagentsTarget: args.backgroundSubagentsTarget,
|
|
2184
|
+
companion: args.companion
|
|
1672
2185
|
};
|
|
1673
2186
|
return runInstall(config);
|
|
1674
2187
|
}
|
|
@@ -1686,13 +2199,21 @@ function getGeneratedPresetNames2() {
|
|
|
1686
2199
|
function parseArgs(args) {
|
|
1687
2200
|
const result = {
|
|
1688
2201
|
tui: true,
|
|
1689
|
-
skills: "yes"
|
|
2202
|
+
skills: "yes",
|
|
2203
|
+
companion: "ask"
|
|
1690
2204
|
};
|
|
1691
2205
|
for (const arg of args) {
|
|
1692
2206
|
if (arg === "--no-tui") {
|
|
1693
2207
|
result.tui = false;
|
|
1694
2208
|
} else if (arg.startsWith("--skills=")) {
|
|
1695
2209
|
result.skills = arg.split("=")[1];
|
|
2210
|
+
} else if (arg.startsWith("--companion=")) {
|
|
2211
|
+
const mode = arg.split("=")[1];
|
|
2212
|
+
if (!["ask", "yes", "no"].includes(mode)) {
|
|
2213
|
+
console.error("Unsupported --companion value: use ask, yes, or no");
|
|
2214
|
+
process.exit(1);
|
|
2215
|
+
}
|
|
2216
|
+
result.companion = mode;
|
|
1696
2217
|
} else if (arg.startsWith("--preset=")) {
|
|
1697
2218
|
const preset = arg.split("=")[1];
|
|
1698
2219
|
if (!isGeneratedPresetName2(preset)) {
|
|
@@ -1700,6 +2221,15 @@ function parseArgs(args) {
|
|
|
1700
2221
|
process.exit(1);
|
|
1701
2222
|
}
|
|
1702
2223
|
result.preset = preset;
|
|
2224
|
+
} else if (arg.startsWith("--background-subagents=")) {
|
|
2225
|
+
const mode = arg.split("=")[1];
|
|
2226
|
+
if (!["ask", "yes", "no"].includes(mode)) {
|
|
2227
|
+
console.error("Unsupported --background-subagents value: use ask, yes, or no");
|
|
2228
|
+
process.exit(1);
|
|
2229
|
+
}
|
|
2230
|
+
result.backgroundSubagents = mode;
|
|
2231
|
+
} else if (arg.startsWith("--background-subagents-target=")) {
|
|
2232
|
+
result.backgroundSubagentsTarget = arg.split("=")[1];
|
|
1703
2233
|
} else if (arg === "--dry-run") {
|
|
1704
2234
|
result.dryRun = true;
|
|
1705
2235
|
} else if (arg === "--reset") {
|
|
@@ -1709,6 +2239,7 @@ function parseArgs(args) {
|
|
|
1709
2239
|
process.exit(0);
|
|
1710
2240
|
}
|
|
1711
2241
|
}
|
|
2242
|
+
result.backgroundSubagents ??= "ask";
|
|
1712
2243
|
return result;
|
|
1713
2244
|
}
|
|
1714
2245
|
function printHelp() {
|
|
@@ -1721,7 +2252,14 @@ Usage:
|
|
|
1721
2252
|
|
|
1722
2253
|
Options:
|
|
1723
2254
|
--skills=yes|no Install bundled skills (default: yes)
|
|
2255
|
+
--companion=ask|yes|no Install desktop companion binary and enable config
|
|
2256
|
+
(default: ask; prompt defaults to yes)
|
|
1724
2257
|
--preset=<name> Active generated config preset (default: openai)
|
|
2258
|
+
--background-subagents=ask|yes|no
|
|
2259
|
+
Persist required OpenCode background subagent env
|
|
2260
|
+
(default: ask; prompt defaults to yes)
|
|
2261
|
+
--background-subagents-target=<path>
|
|
2262
|
+
Shell startup file to update
|
|
1725
2263
|
--no-tui Non-interactive mode
|
|
1726
2264
|
--dry-run Simulate install without writing files
|
|
1727
2265
|
--reset Force overwrite of existing configuration
|
|
@@ -1739,6 +2277,7 @@ For the full config reference, see docs/configuration.md.
|
|
|
1739
2277
|
Examples:
|
|
1740
2278
|
bunx oh-my-opencode-slim install
|
|
1741
2279
|
bunx oh-my-opencode-slim install --no-tui --skills=yes
|
|
2280
|
+
bunx oh-my-opencode-slim install --background-subagents=yes
|
|
1742
2281
|
bunx oh-my-opencode-slim install --preset=opencode-go
|
|
1743
2282
|
bunx oh-my-opencode-slim install --reset
|
|
1744
2283
|
bunx oh-my-opencode-slim doctor
|
|
@@ -1764,7 +2303,12 @@ async function main() {
|
|
|
1764
2303
|
process.exit(1);
|
|
1765
2304
|
}
|
|
1766
2305
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2306
|
+
if (__require.main == __require.module) {
|
|
2307
|
+
main().catch((err) => {
|
|
2308
|
+
console.error("Fatal error:", err);
|
|
2309
|
+
process.exit(1);
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
export {
|
|
2313
|
+
parseArgs
|
|
2314
|
+
};
|