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.
Files changed (49) hide show
  1. package/README.ja-JP.md +86 -32
  2. package/README.ko-KR.md +690 -0
  3. package/README.md +118 -45
  4. package/README.zh-CN.md +97 -37
  5. package/dist/cli/background-subagents.d.ts +13 -0
  6. package/dist/cli/companion.d.ts +4 -0
  7. package/dist/cli/index.d.ts +2 -1
  8. package/dist/cli/index.js +626 -82
  9. package/dist/cli/install.d.ts +6 -1
  10. package/dist/cli/providers.d.ts +2 -1
  11. package/dist/cli/types.d.ts +8 -0
  12. package/dist/companion/manager.d.ts +38 -0
  13. package/dist/config/constants.d.ts +4 -1
  14. package/dist/config/fallback-chains.d.ts +1 -0
  15. package/dist/config/schema.d.ts +46 -40
  16. package/dist/hooks/auto-update-checker/checker.d.ts +7 -1
  17. package/dist/hooks/auto-update-checker/constants.d.ts +1 -0
  18. package/dist/hooks/auto-update-checker/types.d.ts +10 -0
  19. package/dist/hooks/index.d.ts +0 -2
  20. package/dist/hooks/json-error-recovery/hook.d.ts +1 -1
  21. package/dist/hooks/phase-reminder/index.d.ts +1 -1
  22. package/dist/index.js +1861 -2149
  23. package/dist/mcp/grep-app.d.ts +1 -1
  24. package/dist/multiplexer/zellij/index.d.ts +17 -3
  25. package/dist/tools/cancel-task.d.ts +6 -0
  26. package/dist/tools/preset-manager.d.ts +6 -7
  27. package/dist/tui.js +56 -31
  28. package/dist/utils/background-job-board.d.ts +39 -1
  29. package/dist/utils/index.d.ts +0 -1
  30. package/dist/utils/task.d.ts +2 -0
  31. package/oh-my-opencode-slim.schema.json +34 -91
  32. package/package.json +4 -3
  33. package/src/skills/codemap.md +3 -1
  34. package/src/skills/deepwork/SKILL.md +25 -3
  35. package/src/skills/oh-my-opencode-slim/SKILL.md +326 -0
  36. package/dist/divoom/council.gif +0 -0
  37. package/dist/divoom/designer.gif +0 -0
  38. package/dist/divoom/explorer.gif +0 -0
  39. package/dist/divoom/fixer.gif +0 -0
  40. package/dist/divoom/input.gif +0 -0
  41. package/dist/divoom/intro.gif +0 -0
  42. package/dist/divoom/librarian.gif +0 -0
  43. package/dist/divoom/manager.d.ts +0 -57
  44. package/dist/divoom/oracle.gif +0 -0
  45. package/dist/divoom/orchestrator.gif +0 -0
  46. package/dist/hooks/goal/index.d.ts +0 -38
  47. package/dist/hooks/todo-continuation/index.d.ts +0 -55
  48. package/dist/hooks/todo-continuation/todo-hygiene.d.ts +0 -35
  49. 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
- var isBun = typeof globalThis.Bun !== "undefined";
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 = getDefaultOpenCodeConfigDir();
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", "grep_app"]);
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 SessionManagerConfigSchema = z2.object({
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
- sessionManager: SessionManagerConfigSchema.optional(),
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", "grep_app"],
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.4-mini", variant: "low" }
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 getOpenCodePluginCacheDir() {
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
- return join3(cacheDir, "opencode", "packages", `${PACKAGE_NAME}@latest`);
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]: "latest"
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 cacheDir = getOpenCodePluginCacheDir();
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
- agent.explore = { disable: true };
960
- agent.general = { disable: true };
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
- sessionManager: deepMerge(base.sessionManager, override.sessionManager),
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 existsSync5 } from "node:fs";
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
- import { spawnSync } from "node:child_process";
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 = spawnSync(resolver, [command], {
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 = spawnSync(command, args, {
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 path2 = resolveOpenCodePath();
1434
- return path2 === "opencode" ? null : path2;
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 GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
1454
- var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
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/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
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 path2 = getOpenCodePath();
1919
+ const path3 = getOpenCodePath();
1515
1920
  const detectedVersion = version ?? "";
1516
- const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
1921
+ const pathInfo = path3 ? ` (${DIM}${path3}${RESET})` : "";
1517
1922
  printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
1518
- return { ok: true, version: version ?? undefined, path: path2 ?? undefined };
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
- let totalSteps = 6;
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 = existsSync5(configPath2);
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
- console.log(` ${BLUE}$ opencode${RESET}`);
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
- main().catch((err) => {
1768
- console.error("Fatal error:", err);
1769
- process.exit(1);
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
+ };