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.
Files changed (50) 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 +624 -96
  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 +1996 -2132
  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 +16 -0
  26. package/dist/tools/index.d.ts +1 -0
  27. package/dist/tools/preset-manager.d.ts +6 -7
  28. package/dist/tui.js +71 -32
  29. package/dist/utils/background-job-board.d.ts +40 -0
  30. package/dist/utils/index.d.ts +0 -1
  31. package/dist/utils/task.d.ts +2 -0
  32. package/oh-my-opencode-slim.schema.json +34 -91
  33. package/package.json +4 -3
  34. package/src/skills/codemap.md +3 -1
  35. package/src/skills/deepwork/SKILL.md +25 -3
  36. package/src/skills/oh-my-opencode-slim/SKILL.md +326 -0
  37. package/dist/divoom/council.gif +0 -0
  38. package/dist/divoom/designer.gif +0 -0
  39. package/dist/divoom/explorer.gif +0 -0
  40. package/dist/divoom/fixer.gif +0 -0
  41. package/dist/divoom/input.gif +0 -0
  42. package/dist/divoom/intro.gif +0 -0
  43. package/dist/divoom/librarian.gif +0 -0
  44. package/dist/divoom/manager.d.ts +0 -57
  45. package/dist/divoom/oracle.gif +0 -0
  46. package/dist/divoom/orchestrator.gif +0 -0
  47. package/dist/hooks/goal/index.d.ts +0 -38
  48. package/dist/hooks/todo-continuation/index.d.ts +0 -55
  49. package/dist/hooks/todo-continuation/todo-hygiene.d.ts +0 -35
  50. 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 __getOwnPropNames = Object.getOwnPropertyNames;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __toESM = (mod, isNodeMode, target) => {
10
- target = mod != null ? __create(__getProtoOf(mod)) : {};
11
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
12
- for (let key of __getOwnPropNames(mod))
13
- if (!__hasOwnProp.call(to, key))
14
- __defProp(to, key, {
15
- get: () => mod[key],
16
- enumerable: true
17
- });
18
- return to;
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
- var isBun = typeof globalThis.Bun !== "undefined";
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 = getDefaultOpenCodeConfigDir();
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", "grep_app"]);
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 SessionManagerConfigSchema = z2.object({
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
- sessionManager: SessionManagerConfigSchema.optional(),
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", "grep_app"],
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.4-mini", variant: "low" }
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 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) {
725
865
  const cacheDir = process.env.XDG_CACHE_HOME?.trim() || join3(homedir2(), ".cache");
726
- 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);
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]: "latest"
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 cacheDir = getOpenCodePluginCacheDir();
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
- agent.explore = { disable: true };
976
- 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
+ }
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
- sessionManager: deepMerge(base.sessionManager, override.sessionManager),
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 existsSync5 } from "node:fs";
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
- import { spawnSync } from "node:child_process";
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 = spawnSync(resolver, [command], {
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 = spawnSync(command, args, {
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 path2 = resolveOpenCodePath();
1450
- return path2 === "opencode" ? null : path2;
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 GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
1470
- 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}`;
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/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
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 path2 = getOpenCodePath();
1919
+ const path3 = getOpenCodePath();
1531
1920
  const detectedVersion = version ?? "";
1532
- const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
1921
+ const pathInfo = path3 ? ` (${DIM}${path3}${RESET})` : "";
1533
1922
  printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
1534
- 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;
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
- let totalSteps = 6;
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 = existsSync5(configPath2);
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
- 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
+ }
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
- main().catch((err) => {
1784
- console.error("Fatal error:", err);
1785
- process.exit(1);
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
+ };