oh-my-opencode-slim 2.0.0-beta.8 → 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 +624 -96
- 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 +1996 -2132
- 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 +16 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/preset-manager.d.ts +6 -7
- package/dist/tui.js +71 -32
- package/dist/utils/background-job-board.d.ts +40 -0
- 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,48 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
var __create = Object.create;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
4
|
var __defProp = Object.defineProperty;
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
for (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
});
|
|
19
17
|
};
|
|
18
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
20
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
21
20
|
|
|
22
|
-
// src/cli/doctor.ts
|
|
23
|
-
import * as fs2 from "node:fs";
|
|
24
|
-
import { z as z3 } from "zod";
|
|
25
|
-
|
|
26
|
-
// src/config/loader.ts
|
|
27
|
-
import * as fs from "node:fs";
|
|
28
|
-
import * as path from "node:path";
|
|
29
|
-
|
|
30
|
-
// src/cli/config-io.ts
|
|
31
|
-
import {
|
|
32
|
-
copyFileSync as copyFileSync2,
|
|
33
|
-
existsSync as existsSync3,
|
|
34
|
-
mkdirSync as mkdirSync3,
|
|
35
|
-
readFileSync,
|
|
36
|
-
renameSync,
|
|
37
|
-
statSync as statSync2,
|
|
38
|
-
writeFileSync
|
|
39
|
-
} from "node:fs";
|
|
40
|
-
import { homedir as homedir2 } from "node:os";
|
|
41
|
-
import { dirname as dirname3, join as join3 } from "node:path";
|
|
42
|
-
|
|
43
21
|
// src/utils/compat.ts
|
|
22
|
+
var exports_compat = {};
|
|
23
|
+
__export(exports_compat, {
|
|
24
|
+
isBun: () => isBun,
|
|
25
|
+
crossWrite: () => crossWrite,
|
|
26
|
+
crossSpawn: () => crossSpawn
|
|
27
|
+
});
|
|
44
28
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
45
|
-
|
|
29
|
+
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
46
30
|
function collectStream(stream) {
|
|
47
31
|
if (!stream)
|
|
48
32
|
return () => Promise.resolve("");
|
|
@@ -85,6 +69,123 @@ function crossSpawn(command, options) {
|
|
|
85
69
|
}
|
|
86
70
|
};
|
|
87
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";
|
|
88
189
|
|
|
89
190
|
// src/cli/paths.ts
|
|
90
191
|
import { existsSync, mkdirSync } from "node:fs";
|
|
@@ -116,7 +217,7 @@ function getConfigSearchDirs() {
|
|
|
116
217
|
});
|
|
117
218
|
}
|
|
118
219
|
function getOpenCodeConfigPaths() {
|
|
119
|
-
const configDir =
|
|
220
|
+
const configDir = getConfigDir();
|
|
120
221
|
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
121
222
|
}
|
|
122
223
|
function getConfigJson() {
|
|
@@ -344,11 +445,13 @@ var MultiplexerLayoutSchema = z2.enum([
|
|
|
344
445
|
"even-horizontal",
|
|
345
446
|
"even-vertical"
|
|
346
447
|
]);
|
|
448
|
+
var ZellijPaneModeSchema = z2.enum(["agent-tab", "current-tab"]);
|
|
347
449
|
var TmuxLayoutSchema = MultiplexerLayoutSchema;
|
|
348
450
|
var MultiplexerConfigSchema = z2.object({
|
|
349
451
|
type: MultiplexerTypeSchema.default("none"),
|
|
350
452
|
layout: MultiplexerLayoutSchema.default("main-vertical"),
|
|
351
|
-
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")
|
|
352
455
|
});
|
|
353
456
|
var TmuxConfigSchema = z2.object({
|
|
354
457
|
enabled: z2.boolean().default(false),
|
|
@@ -359,7 +462,7 @@ var PresetSchema = z2.record(z2.string(), AgentOverrideConfigSchema);
|
|
|
359
462
|
var WebsearchConfigSchema = z2.object({
|
|
360
463
|
provider: z2.enum(["exa", "tavily"]).default("exa")
|
|
361
464
|
});
|
|
362
|
-
var McpNameSchema = z2.enum(["websearch", "context7", "
|
|
465
|
+
var McpNameSchema = z2.enum(["websearch", "context7", "gh_grep"]);
|
|
363
466
|
var InterviewConfigSchema = z2.object({
|
|
364
467
|
maxQuestions: z2.number().int().min(1).max(10).default(2),
|
|
365
468
|
outputFolder: z2.string().min(1).default("interview"),
|
|
@@ -367,28 +470,11 @@ var InterviewConfigSchema = z2.object({
|
|
|
367
470
|
port: z2.number().int().min(0).max(65535).default(0),
|
|
368
471
|
dashboard: z2.boolean().default(false)
|
|
369
472
|
});
|
|
370
|
-
var
|
|
473
|
+
var BackgroundJobsConfigSchema = z2.object({
|
|
371
474
|
maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2),
|
|
372
475
|
readContextMinLines: z2.number().int().min(0).max(1000).default(10),
|
|
373
476
|
readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
|
|
374
477
|
});
|
|
375
|
-
var DivoomConfigSchema = z2.object({
|
|
376
|
-
enabled: z2.boolean().default(false),
|
|
377
|
-
python: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python"),
|
|
378
|
-
script: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py"),
|
|
379
|
-
size: z2.number().int().min(1).max(1024).default(128),
|
|
380
|
-
fps: z2.number().int().min(1).max(60).default(8),
|
|
381
|
-
speed: z2.number().int().min(1).max(1e4).default(125),
|
|
382
|
-
maxFrames: z2.number().int().min(1).max(500).default(24),
|
|
383
|
-
posterizeBits: z2.number().int().min(1).max(8).default(3),
|
|
384
|
-
gifs: z2.record(z2.string(), z2.string().min(1)).optional()
|
|
385
|
-
});
|
|
386
|
-
var TodoContinuationConfigSchema = z2.object({
|
|
387
|
-
maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
|
|
388
|
-
cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
|
|
389
|
-
autoEnable: z2.boolean().default(false).describe("Automatically enable auto-continue when the orchestrator session has enough todos"),
|
|
390
|
-
autoEnableThreshold: z2.number().int().min(1).max(50).default(4).describe("Number of todos that triggers auto-enable (only used when autoEnable is true)")
|
|
391
|
-
});
|
|
392
478
|
var FailoverConfigSchema = z2.object({
|
|
393
479
|
enabled: z2.boolean().default(true),
|
|
394
480
|
timeoutMs: z2.number().min(0).default(15000),
|
|
@@ -396,6 +482,11 @@ var FailoverConfigSchema = z2.object({
|
|
|
396
482
|
chains: FallbackChainsSchema.default({}),
|
|
397
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.")
|
|
398
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
|
+
});
|
|
399
490
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
400
491
|
for (const [name, override] of Object.entries(overrides)) {
|
|
401
492
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -433,11 +524,10 @@ var PluginConfigSchema = z2.object({
|
|
|
433
524
|
tmux: TmuxConfigSchema.optional(),
|
|
434
525
|
websearch: WebsearchConfigSchema.optional(),
|
|
435
526
|
interview: InterviewConfigSchema.optional(),
|
|
436
|
-
|
|
437
|
-
divoom: DivoomConfigSchema.optional(),
|
|
438
|
-
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
527
|
+
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
439
528
|
fallback: FailoverConfigSchema.optional(),
|
|
440
|
-
council: CouncilConfigSchema.optional()
|
|
529
|
+
council: CouncilConfigSchema.optional(),
|
|
530
|
+
companion: CompanionConfigSchema.optional()
|
|
441
531
|
}).superRefine((value, ctx) => {
|
|
442
532
|
if (value.agents) {
|
|
443
533
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -453,7 +543,7 @@ var DEFAULT_AGENT_MCPS = {
|
|
|
453
543
|
orchestrator: ["*", "!context7"],
|
|
454
544
|
designer: [],
|
|
455
545
|
oracle: [],
|
|
456
|
-
librarian: ["websearch", "context7", "
|
|
546
|
+
librarian: ["websearch", "context7", "gh_grep"],
|
|
457
547
|
explorer: [],
|
|
458
548
|
fixer: [],
|
|
459
549
|
observer: [],
|
|
@@ -495,6 +585,12 @@ var CUSTOM_SKILLS = [
|
|
|
495
585
|
description: "Heavy/complex coding sessions and large modifications workflow",
|
|
496
586
|
allowedAgents: ["orchestrator"],
|
|
497
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"
|
|
498
594
|
}
|
|
499
595
|
];
|
|
500
596
|
function getCustomSkillsDir() {
|
|
@@ -542,12 +638,12 @@ var SCHEMA_URL = "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-sl
|
|
|
542
638
|
var GENERATED_PRESETS = ["openai", "opencode-go"];
|
|
543
639
|
var MODEL_MAPPINGS = {
|
|
544
640
|
openai: {
|
|
545
|
-
orchestrator: { model: "openai/gpt-5.5" },
|
|
641
|
+
orchestrator: { model: "openai/gpt-5.5", variant: "medium" },
|
|
546
642
|
oracle: { model: "openai/gpt-5.5", variant: "high" },
|
|
547
643
|
librarian: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
548
644
|
explorer: { model: "openai/gpt-5.4-mini", variant: "low" },
|
|
549
645
|
designer: { model: "openai/gpt-5.4-mini", variant: "medium" },
|
|
550
|
-
fixer: { model: "openai/gpt-5.
|
|
646
|
+
fixer: { model: "openai/gpt-5.5", variant: "low" }
|
|
551
647
|
},
|
|
552
648
|
kimi: {
|
|
553
649
|
orchestrator: { model: "kimi-for-coding/k2p5" },
|
|
@@ -636,11 +732,24 @@ function generateLiteConfig(installConfig) {
|
|
|
636
732
|
main_pane_size: 60
|
|
637
733
|
};
|
|
638
734
|
}
|
|
735
|
+
if (installConfig.companion === "yes") {
|
|
736
|
+
config.companion = {
|
|
737
|
+
enabled: true,
|
|
738
|
+
position: "bottom-right",
|
|
739
|
+
size: "medium"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
639
742
|
return config;
|
|
640
743
|
}
|
|
641
744
|
|
|
642
745
|
// src/cli/config-io.ts
|
|
643
746
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
747
|
+
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = [
|
|
748
|
+
"build",
|
|
749
|
+
"explore",
|
|
750
|
+
"general",
|
|
751
|
+
"plan"
|
|
752
|
+
];
|
|
644
753
|
function isString(value) {
|
|
645
754
|
return typeof value === "string";
|
|
646
755
|
}
|
|
@@ -721,17 +830,49 @@ function getPluginEntry() {
|
|
|
721
830
|
return PACKAGE_NAME;
|
|
722
831
|
}
|
|
723
832
|
}
|
|
724
|
-
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) {
|
|
725
865
|
const cacheDir = process.env.XDG_CACHE_HOME?.trim() || join3(homedir2(), ".cache");
|
|
726
|
-
|
|
866
|
+
const suffix = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;
|
|
867
|
+
return join3(cacheDir, "opencode", "packages", suffix);
|
|
727
868
|
}
|
|
728
|
-
function writeOpenCodePluginCacheManifest(cacheDir) {
|
|
869
|
+
function writeOpenCodePluginCacheManifest(cacheDir, version = "latest") {
|
|
729
870
|
try {
|
|
730
871
|
writeFileSync(join3(cacheDir, "package.json"), JSON.stringify({
|
|
731
872
|
name: `${PACKAGE_NAME}-cache`,
|
|
732
873
|
private: true,
|
|
733
874
|
dependencies: {
|
|
734
|
-
[PACKAGE_NAME]:
|
|
875
|
+
[PACKAGE_NAME]: version
|
|
735
876
|
}
|
|
736
877
|
}, null, 2));
|
|
737
878
|
return null;
|
|
@@ -779,7 +920,10 @@ async function warmOpenCodePluginCache() {
|
|
|
779
920
|
if (!packageRoot || !isPackageManagerInstall(packageRoot)) {
|
|
780
921
|
return null;
|
|
781
922
|
}
|
|
782
|
-
const
|
|
923
|
+
const pinnedVersion = getPinnedVersionFromConfig();
|
|
924
|
+
const runningVersion = getVersionFromPackageRoot(packageRoot);
|
|
925
|
+
const cacheVersion = pinnedVersion ?? runningVersion;
|
|
926
|
+
const cacheDir = getOpenCodePluginCacheDir(cacheVersion);
|
|
783
927
|
try {
|
|
784
928
|
mkdirSync3(cacheDir, { recursive: true });
|
|
785
929
|
} catch (err) {
|
|
@@ -789,7 +933,7 @@ async function warmOpenCodePluginCache() {
|
|
|
789
933
|
error: `Failed to create OpenCode cache directory: ${err}`
|
|
790
934
|
};
|
|
791
935
|
}
|
|
792
|
-
const manifestError = writeOpenCodePluginCacheManifest(cacheDir);
|
|
936
|
+
const manifestError = writeOpenCodePluginCacheManifest(cacheDir, cacheVersion);
|
|
793
937
|
if (manifestError)
|
|
794
938
|
return manifestError;
|
|
795
939
|
try {
|
|
@@ -972,8 +1116,13 @@ function disableDefaultAgents() {
|
|
|
972
1116
|
}
|
|
973
1117
|
const config = parsedConfig ?? {};
|
|
974
1118
|
const agent = config.agent ?? {};
|
|
975
|
-
|
|
976
|
-
|
|
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
|
+
}
|
|
977
1126
|
config.agent = agent;
|
|
978
1127
|
writeConfig(configPath, config);
|
|
979
1128
|
return { success: true, configPath };
|
|
@@ -1101,10 +1250,10 @@ function mergePluginConfigs(base, override) {
|
|
|
1101
1250
|
tmux: deepMerge(base.tmux, override.tmux),
|
|
1102
1251
|
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
1103
1252
|
interview: deepMerge(base.interview, override.interview),
|
|
1104
|
-
|
|
1105
|
-
divoom: deepMerge(base.divoom, override.divoom),
|
|
1253
|
+
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
1106
1254
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
1107
|
-
council: deepMerge(base.council, override.council)
|
|
1255
|
+
council: deepMerge(base.council, override.council),
|
|
1256
|
+
companion: deepMerge(base.companion, override.companion)
|
|
1108
1257
|
};
|
|
1109
1258
|
}
|
|
1110
1259
|
function deepMerge(base, override) {
|
|
@@ -1320,16 +1469,253 @@ Options:
|
|
|
1320
1469
|
}
|
|
1321
1470
|
|
|
1322
1471
|
// src/cli/install.ts
|
|
1323
|
-
import { existsSync as
|
|
1472
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1324
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
|
+
}
|
|
1325
1710
|
// src/cli/system.ts
|
|
1326
|
-
|
|
1711
|
+
init_compat();
|
|
1712
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1327
1713
|
import { statSync as statSync4 } from "node:fs";
|
|
1328
1714
|
var cachedOpenCodePath = null;
|
|
1329
1715
|
function resolvePathCommand(command) {
|
|
1330
1716
|
try {
|
|
1331
1717
|
const resolver = process.platform === "win32" ? "where" : "which";
|
|
1332
|
-
const result =
|
|
1718
|
+
const result = spawnSync2(resolver, [command], {
|
|
1333
1719
|
encoding: "utf-8",
|
|
1334
1720
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1335
1721
|
});
|
|
@@ -1344,7 +1730,7 @@ function resolvePathCommand(command) {
|
|
|
1344
1730
|
}
|
|
1345
1731
|
function canExecute(command, args) {
|
|
1346
1732
|
try {
|
|
1347
|
-
const result =
|
|
1733
|
+
const result = spawnSync2(command, args, {
|
|
1348
1734
|
stdio: "ignore"
|
|
1349
1735
|
});
|
|
1350
1736
|
return result.status === 0;
|
|
@@ -1446,8 +1832,8 @@ async function getOpenCodeVersion() {
|
|
|
1446
1832
|
return null;
|
|
1447
1833
|
}
|
|
1448
1834
|
function getOpenCodePath() {
|
|
1449
|
-
const
|
|
1450
|
-
return
|
|
1835
|
+
const path3 = resolveOpenCodePath();
|
|
1836
|
+
return path3 === "opencode" ? null : path3;
|
|
1451
1837
|
}
|
|
1452
1838
|
// src/cli/install.ts
|
|
1453
1839
|
var GREEN = "\x1B[32m";
|
|
@@ -1466,8 +1852,8 @@ var SYMBOLS = {
|
|
|
1466
1852
|
warn: `${YELLOW}[!]${RESET}`,
|
|
1467
1853
|
star: `${YELLOW}★${RESET}`
|
|
1468
1854
|
};
|
|
1469
|
-
var
|
|
1470
|
-
var GITHUB_URL = `https://github.com/${
|
|
1855
|
+
var GITHUB_REPO2 = "alvinunreal/oh-my-opencode-slim";
|
|
1856
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO2}`;
|
|
1471
1857
|
function printHeader(isUpdate) {
|
|
1472
1858
|
console.log();
|
|
1473
1859
|
console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
@@ -1483,6 +1869,9 @@ function printSuccess(message) {
|
|
|
1483
1869
|
function printError(message) {
|
|
1484
1870
|
console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
|
|
1485
1871
|
}
|
|
1872
|
+
function printWarning(message) {
|
|
1873
|
+
console.log(`${SYMBOLS.warn} ${YELLOW}${message}${RESET}`);
|
|
1874
|
+
}
|
|
1486
1875
|
function printInfo(message) {
|
|
1487
1876
|
console.log(`${SYMBOLS.info} ${message}`);
|
|
1488
1877
|
}
|
|
@@ -1507,7 +1896,7 @@ async function askToStarRepo(config) {
|
|
|
1507
1896
|
return;
|
|
1508
1897
|
try {
|
|
1509
1898
|
const { execFileSync } = await import("node:child_process");
|
|
1510
|
-
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${
|
|
1899
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO2}`], { stdio: "ignore", timeout: 1e4 });
|
|
1511
1900
|
printSuccess("Thanks for starring! ★");
|
|
1512
1901
|
} catch {
|
|
1513
1902
|
printInfo(`Couldn't star automatically. You can star manually:
|
|
@@ -1527,11 +1916,88 @@ async function checkOpenCodeInstalled() {
|
|
|
1527
1916
|
return { ok: false };
|
|
1528
1917
|
}
|
|
1529
1918
|
const version = await getOpenCodeVersion();
|
|
1530
|
-
const
|
|
1919
|
+
const path3 = getOpenCodePath();
|
|
1531
1920
|
const detectedVersion = version ?? "";
|
|
1532
|
-
const pathInfo =
|
|
1921
|
+
const pathInfo = path3 ? ` (${DIM}${path3}${RESET})` : "";
|
|
1533
1922
|
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
1534
|
-
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;
|
|
1535
2001
|
}
|
|
1536
2002
|
function handleStepResult(result, successMsg) {
|
|
1537
2003
|
if (!result.success) {
|
|
@@ -1541,13 +2007,24 @@ function handleStepResult(result, successMsg) {
|
|
|
1541
2007
|
printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
|
|
1542
2008
|
return true;
|
|
1543
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
|
+
}
|
|
1544
2018
|
async function runInstall(config) {
|
|
1545
2019
|
const detected = detectCurrentConfig();
|
|
1546
2020
|
const isUpdate = detected.isInstalled;
|
|
1547
2021
|
printHeader(isUpdate);
|
|
1548
|
-
|
|
2022
|
+
const companionInstall = await shouldInstallCompanion(config);
|
|
2023
|
+
let totalSteps = 7;
|
|
1549
2024
|
if (config.installCustomSkills)
|
|
1550
2025
|
totalSteps += 1;
|
|
2026
|
+
if (companionInstall)
|
|
2027
|
+
totalSteps += 1;
|
|
1551
2028
|
totalSteps += 1;
|
|
1552
2029
|
let step = 1;
|
|
1553
2030
|
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
@@ -1606,6 +2083,15 @@ async function runInstall(config) {
|
|
|
1606
2083
|
if (!handleStepResult(lspResult, "LSP enabled"))
|
|
1607
2084
|
return 1;
|
|
1608
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
|
+
}
|
|
1609
2095
|
printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...");
|
|
1610
2096
|
if (config.dryRun) {
|
|
1611
2097
|
const liteConfig = generateLiteConfig(config);
|
|
@@ -1615,7 +2101,7 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1615
2101
|
`);
|
|
1616
2102
|
} else {
|
|
1617
2103
|
const configPath2 = getExistingLiteConfigPath();
|
|
1618
|
-
const configExists =
|
|
2104
|
+
const configExists = existsSync7(configPath2);
|
|
1619
2105
|
if (configExists && !config.reset) {
|
|
1620
2106
|
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
1621
2107
|
} else {
|
|
@@ -1662,7 +2148,15 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1662
2148
|
console.log(` ${BLUE}${configPath}${RESET}`);
|
|
1663
2149
|
console.log();
|
|
1664
2150
|
console.log(" 4. Start OpenCode:");
|
|
1665
|
-
|
|
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
|
+
}
|
|
1666
2160
|
console.log();
|
|
1667
2161
|
console.log(" 5. Verify the agents are responding:");
|
|
1668
2162
|
console.log(` ${BLUE}> ping all agents${RESET}`);
|
|
@@ -1684,7 +2178,10 @@ async function install(args) {
|
|
|
1684
2178
|
preset: args.preset,
|
|
1685
2179
|
promptForStar: args.tui,
|
|
1686
2180
|
dryRun: args.dryRun,
|
|
1687
|
-
reset: args.reset ?? false
|
|
2181
|
+
reset: args.reset ?? false,
|
|
2182
|
+
backgroundSubagents: args.backgroundSubagents ?? "ask",
|
|
2183
|
+
backgroundSubagentsTarget: args.backgroundSubagentsTarget,
|
|
2184
|
+
companion: args.companion
|
|
1688
2185
|
};
|
|
1689
2186
|
return runInstall(config);
|
|
1690
2187
|
}
|
|
@@ -1702,13 +2199,21 @@ function getGeneratedPresetNames2() {
|
|
|
1702
2199
|
function parseArgs(args) {
|
|
1703
2200
|
const result = {
|
|
1704
2201
|
tui: true,
|
|
1705
|
-
skills: "yes"
|
|
2202
|
+
skills: "yes",
|
|
2203
|
+
companion: "ask"
|
|
1706
2204
|
};
|
|
1707
2205
|
for (const arg of args) {
|
|
1708
2206
|
if (arg === "--no-tui") {
|
|
1709
2207
|
result.tui = false;
|
|
1710
2208
|
} else if (arg.startsWith("--skills=")) {
|
|
1711
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;
|
|
1712
2217
|
} else if (arg.startsWith("--preset=")) {
|
|
1713
2218
|
const preset = arg.split("=")[1];
|
|
1714
2219
|
if (!isGeneratedPresetName2(preset)) {
|
|
@@ -1716,6 +2221,15 @@ function parseArgs(args) {
|
|
|
1716
2221
|
process.exit(1);
|
|
1717
2222
|
}
|
|
1718
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];
|
|
1719
2233
|
} else if (arg === "--dry-run") {
|
|
1720
2234
|
result.dryRun = true;
|
|
1721
2235
|
} else if (arg === "--reset") {
|
|
@@ -1725,6 +2239,7 @@ function parseArgs(args) {
|
|
|
1725
2239
|
process.exit(0);
|
|
1726
2240
|
}
|
|
1727
2241
|
}
|
|
2242
|
+
result.backgroundSubagents ??= "ask";
|
|
1728
2243
|
return result;
|
|
1729
2244
|
}
|
|
1730
2245
|
function printHelp() {
|
|
@@ -1737,7 +2252,14 @@ Usage:
|
|
|
1737
2252
|
|
|
1738
2253
|
Options:
|
|
1739
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)
|
|
1740
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
|
|
1741
2263
|
--no-tui Non-interactive mode
|
|
1742
2264
|
--dry-run Simulate install without writing files
|
|
1743
2265
|
--reset Force overwrite of existing configuration
|
|
@@ -1755,6 +2277,7 @@ For the full config reference, see docs/configuration.md.
|
|
|
1755
2277
|
Examples:
|
|
1756
2278
|
bunx oh-my-opencode-slim install
|
|
1757
2279
|
bunx oh-my-opencode-slim install --no-tui --skills=yes
|
|
2280
|
+
bunx oh-my-opencode-slim install --background-subagents=yes
|
|
1758
2281
|
bunx oh-my-opencode-slim install --preset=opencode-go
|
|
1759
2282
|
bunx oh-my-opencode-slim install --reset
|
|
1760
2283
|
bunx oh-my-opencode-slim doctor
|
|
@@ -1780,7 +2303,12 @@ async function main() {
|
|
|
1780
2303
|
process.exit(1);
|
|
1781
2304
|
}
|
|
1782
2305
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
+
};
|