oh-my-opencode-serverlocal 0.1.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 (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja-JP.md +775 -0
  3. package/README.ko-KR.md +796 -0
  4. package/README.md +792 -0
  5. package/README.zh-CN.md +765 -0
  6. package/dist/agents/council.d.ts +27 -0
  7. package/dist/agents/councillor.d.ts +2 -0
  8. package/dist/agents/designer.d.ts +2 -0
  9. package/dist/agents/explorer.d.ts +2 -0
  10. package/dist/agents/fixer.d.ts +2 -0
  11. package/dist/agents/index.d.ts +31 -0
  12. package/dist/agents/librarian.d.ts +2 -0
  13. package/dist/agents/observer.d.ts +2 -0
  14. package/dist/agents/oracle.d.ts +2 -0
  15. package/dist/agents/orchestrator.d.ts +28 -0
  16. package/dist/agents/permissions.d.ts +10 -0
  17. package/dist/cli/background-subagents.d.ts +13 -0
  18. package/dist/cli/companion.d.ts +4 -0
  19. package/dist/cli/config-io.d.ts +25 -0
  20. package/dist/cli/config-manager.d.ts +4 -0
  21. package/dist/cli/custom-skills-registry.d.ts +18 -0
  22. package/dist/cli/custom-skills.d.ts +13 -0
  23. package/dist/cli/doctor.d.ts +38 -0
  24. package/dist/cli/index.d.ts +3 -0
  25. package/dist/cli/index.js +3565 -0
  26. package/dist/cli/install.d.ts +7 -0
  27. package/dist/cli/model-key-normalization.d.ts +1 -0
  28. package/dist/cli/paths.d.ts +35 -0
  29. package/dist/cli/providers.d.ts +145 -0
  30. package/dist/cli/skills.d.ts +24 -0
  31. package/dist/cli/system.d.ts +6 -0
  32. package/dist/cli/types.d.ts +47 -0
  33. package/dist/companion/manager.d.ts +50 -0
  34. package/dist/companion/updater.d.ts +36 -0
  35. package/dist/config/agent-mcps.d.ts +11 -0
  36. package/dist/config/constants.d.ts +38 -0
  37. package/dist/config/council-schema.d.ts +137 -0
  38. package/dist/config/index.d.ts +5 -0
  39. package/dist/config/loader.d.ts +88 -0
  40. package/dist/config/runtime-preset.d.ts +11 -0
  41. package/dist/config/schema.d.ts +1107 -0
  42. package/dist/config/strip-orchestrator-model.d.ts +9 -0
  43. package/dist/config/utils.d.ts +18 -0
  44. package/dist/council/council-manager.d.ts +53 -0
  45. package/dist/council/index.d.ts +1 -0
  46. package/dist/hooks/apply-patch/codec.d.ts +7 -0
  47. package/dist/hooks/apply-patch/errors.d.ts +25 -0
  48. package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
  49. package/dist/hooks/apply-patch/index.d.ts +15 -0
  50. package/dist/hooks/apply-patch/matching.d.ts +26 -0
  51. package/dist/hooks/apply-patch/operations.d.ts +3 -0
  52. package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
  53. package/dist/hooks/apply-patch/resolution.d.ts +19 -0
  54. package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
  55. package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
  56. package/dist/hooks/apply-patch/types.d.ts +80 -0
  57. package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
  58. package/dist/hooks/auto-update-checker/checker.d.ts +38 -0
  59. package/dist/hooks/auto-update-checker/constants.d.ts +12 -0
  60. package/dist/hooks/auto-update-checker/index.d.ts +18 -0
  61. package/dist/hooks/auto-update-checker/skill-sync.d.ts +67 -0
  62. package/dist/hooks/auto-update-checker/types.d.ts +34 -0
  63. package/dist/hooks/chat-headers.d.ts +16 -0
  64. package/dist/hooks/command-hook-utils.d.ts +5 -0
  65. package/dist/hooks/deepwork/index.d.ts +13 -0
  66. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  67. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  68. package/dist/hooks/filter-available-skills/index.d.ts +19 -0
  69. package/dist/hooks/foreground-fallback/index.d.ts +122 -0
  70. package/dist/hooks/image-hook.d.ts +8 -0
  71. package/dist/hooks/index.d.ts +16 -0
  72. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  73. package/dist/hooks/loop-command/index.d.ts +13 -0
  74. package/dist/hooks/phase-reminder/index.d.ts +25 -0
  75. package/dist/hooks/post-file-tool-nudge/index.d.ts +23 -0
  76. package/dist/hooks/reflect/index.d.ts +13 -0
  77. package/dist/hooks/session-lifecycle.d.ts +10 -0
  78. package/dist/hooks/task-session-manager/index.d.ts +58 -0
  79. package/dist/hooks/task-session-manager/pending-call-tracker.d.ts +13 -0
  80. package/dist/hooks/task-session-manager/task-context-tracker.d.ts +14 -0
  81. package/dist/hooks/types.d.ts +26 -0
  82. package/dist/index.d.ts +5 -0
  83. package/dist/index.js +39519 -0
  84. package/dist/interview/dashboard-manager.d.ts +21 -0
  85. package/dist/interview/dashboard.d.ts +67 -0
  86. package/dist/interview/document.d.ts +29 -0
  87. package/dist/interview/helpers.d.ts +10 -0
  88. package/dist/interview/index.d.ts +1 -0
  89. package/dist/interview/manager.d.ts +21 -0
  90. package/dist/interview/parser.d.ts +11 -0
  91. package/dist/interview/prompts.d.ts +7 -0
  92. package/dist/interview/server.d.ts +15 -0
  93. package/dist/interview/service.d.ts +45 -0
  94. package/dist/interview/session-server.d.ts +21 -0
  95. package/dist/interview/types.d.ts +111 -0
  96. package/dist/interview/ui.d.ts +11 -0
  97. package/dist/loop/loop-session.d.ts +64 -0
  98. package/dist/mcp/context7.d.ts +6 -0
  99. package/dist/mcp/grep-app.d.ts +6 -0
  100. package/dist/mcp/index.d.ts +8 -0
  101. package/dist/mcp/types.d.ts +12 -0
  102. package/dist/mcp/websearch.d.ts +9 -0
  103. package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
  104. package/dist/multiplexer/cmux/index.d.ts +102 -0
  105. package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
  106. package/dist/multiplexer/cmux/session-state.d.ts +45 -0
  107. package/dist/multiplexer/factory.d.ts +17 -0
  108. package/dist/multiplexer/herdr/index.d.ts +35 -0
  109. package/dist/multiplexer/index.d.ts +12 -0
  110. package/dist/multiplexer/session-manager.d.ts +69 -0
  111. package/dist/multiplexer/shared.d.ts +31 -0
  112. package/dist/multiplexer/tmux/index.d.ts +26 -0
  113. package/dist/multiplexer/types.d.ts +57 -0
  114. package/dist/multiplexer/zellij/index.d.ts +47 -0
  115. package/dist/tools/acp-run.d.ts +3 -0
  116. package/dist/tools/ast-grep/cli.d.ts +15 -0
  117. package/dist/tools/ast-grep/constants.d.ts +25 -0
  118. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  119. package/dist/tools/ast-grep/index.d.ts +10 -0
  120. package/dist/tools/ast-grep/tools.d.ts +3 -0
  121. package/dist/tools/ast-grep/types.d.ts +30 -0
  122. package/dist/tools/ast-grep/utils.d.ts +4 -0
  123. package/dist/tools/cancel-task.d.ts +16 -0
  124. package/dist/tools/council.d.ts +10 -0
  125. package/dist/tools/index.d.ts +7 -0
  126. package/dist/tools/preset-manager.d.ts +26 -0
  127. package/dist/tools/smartfetch/binary.d.ts +3 -0
  128. package/dist/tools/smartfetch/cache.d.ts +6 -0
  129. package/dist/tools/smartfetch/constants.d.ts +12 -0
  130. package/dist/tools/smartfetch/index.d.ts +3 -0
  131. package/dist/tools/smartfetch/network.d.ts +38 -0
  132. package/dist/tools/smartfetch/secondary-model.d.ts +35 -0
  133. package/dist/tools/smartfetch/tool.d.ts +3 -0
  134. package/dist/tools/smartfetch/types.d.ts +122 -0
  135. package/dist/tools/smartfetch/utils.d.ts +20 -0
  136. package/dist/tui-state.d.ts +18 -0
  137. package/dist/tui.d.ts +13 -0
  138. package/dist/tui.js +1112 -0
  139. package/dist/utils/agent-variant.d.ts +23 -0
  140. package/dist/utils/background-job-board.d.ts +113 -0
  141. package/dist/utils/background-job-coordinator.d.ts +72 -0
  142. package/dist/utils/background-job-store.d.ts +46 -0
  143. package/dist/utils/compat.d.ts +29 -0
  144. package/dist/utils/councillor-models.d.ts +20 -0
  145. package/dist/utils/env.d.ts +3 -0
  146. package/dist/utils/escape-html.d.ts +1 -0
  147. package/dist/utils/frontmatter.d.ts +6 -0
  148. package/dist/utils/guards.d.ts +4 -0
  149. package/dist/utils/index.d.ts +10 -0
  150. package/dist/utils/internal-initiator.d.ts +11 -0
  151. package/dist/utils/logger.d.ts +6 -0
  152. package/dist/utils/polling.d.ts +21 -0
  153. package/dist/utils/session.d.ts +75 -0
  154. package/dist/utils/subagent-depth.d.ts +35 -0
  155. package/dist/utils/system-collapse.d.ts +6 -0
  156. package/dist/utils/task.d.ts +20 -0
  157. package/dist/utils/zip-extractor.d.ts +1 -0
  158. package/package.json +108 -0
  159. package/src/companion/companion-manifest.json +12 -0
  160. package/src/skills/clonedeps/README.md +23 -0
  161. package/src/skills/clonedeps/SKILL.md +240 -0
  162. package/src/skills/clonedeps/codemap.md +32 -0
  163. package/src/skills/codemap/README.md +59 -0
  164. package/src/skills/codemap/SKILL.md +163 -0
  165. package/src/skills/codemap/codemap.md +36 -0
  166. package/src/skills/codemap/scripts/codemap.mjs +483 -0
  167. package/src/skills/codemap/scripts/codemap.test.ts +129 -0
  168. package/src/skills/codemap.md +78 -0
  169. package/src/skills/deepwork/SKILL.md +122 -0
  170. package/src/skills/loop-engineering/SKILL.md +30 -0
  171. package/src/skills/oh-my-opencode-slim/SKILL.md +326 -0
  172. package/src/skills/reflect/SKILL.md +326 -0
  173. package/src/skills/simplify/README.md +19 -0
  174. package/src/skills/simplify/SKILL.md +138 -0
  175. package/src/skills/simplify/codemap.md +36 -0
  176. package/src/skills/verification-planning/SKILL.md +102 -0
  177. package/src/skills/worktrees/SKILL.md +171 -0
package/dist/tui.js ADDED
@@ -0,0 +1,1112 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name, newValue) {
35
+ this[name] = __returnValue.bind(null, newValue);
36
+ }
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, {
40
+ get: all[name],
41
+ enumerable: true,
42
+ configurable: true,
43
+ set: __exportSetter.bind(all, name)
44
+ });
45
+ };
46
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
+
49
+ // src/utils/compat.ts
50
+ import { spawn as nodeSpawn } from "node:child_process";
51
+ import { writeFile as fsWriteFile } from "node:fs/promises";
52
+ function collectStream(stream) {
53
+ if (!stream)
54
+ return () => Promise.resolve("");
55
+ const chunks = [];
56
+ stream.on("data", (chunk) => chunks.push(chunk));
57
+ return () => new Promise((resolve, reject) => {
58
+ if (!stream.readable) {
59
+ resolve(Buffer.concat(chunks).toString("utf-8"));
60
+ return;
61
+ }
62
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
63
+ stream.on("error", reject);
64
+ });
65
+ }
66
+ function crossSpawn(command, options) {
67
+ const [cmd, ...args] = command;
68
+ const proc = nodeSpawn(cmd, args, {
69
+ stdio: [
70
+ options?.stdin ?? "ignore",
71
+ options?.stdout ?? "pipe",
72
+ options?.stderr ?? "pipe"
73
+ ],
74
+ cwd: options?.cwd,
75
+ env: options?.env
76
+ });
77
+ const stdoutCollector = collectStream(proc.stdout);
78
+ const stderrCollector = collectStream(proc.stderr);
79
+ const exited = new Promise((resolve, reject) => {
80
+ proc.on("error", reject);
81
+ proc.on("close", (code) => resolve(code ?? 1));
82
+ });
83
+ return {
84
+ proc,
85
+ stdout: stdoutCollector,
86
+ stderr: stderrCollector,
87
+ exited,
88
+ kill: (signal) => proc.kill(signal),
89
+ get exitCode() {
90
+ return proc.exitCode;
91
+ }
92
+ };
93
+ }
94
+ async function crossWrite(path, data) {
95
+ await fsWriteFile(path, Buffer.from(data));
96
+ }
97
+ var init_compat = () => {};
98
+
99
+ // src/tui.ts
100
+ import { createElement, insert, setProp } from "@opentui/solid";
101
+
102
+ // src/config/constants.ts
103
+ var AGENT_ALIASES = {
104
+ explore: "explorer",
105
+ "frontend-ui-ux-engineer": "designer"
106
+ };
107
+ var SUBAGENT_NAMES = [
108
+ "explorer",
109
+ "librarian",
110
+ "oracle",
111
+ "designer",
112
+ "fixer",
113
+ "observer",
114
+ "council",
115
+ "councillor"
116
+ ];
117
+ var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
118
+ var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
119
+ var DEFAULT_MODELS = {
120
+ orchestrator: undefined,
121
+ oracle: undefined,
122
+ librarian: undefined,
123
+ explorer: undefined,
124
+ designer: undefined,
125
+ fixer: undefined,
126
+ observer: undefined,
127
+ council: undefined,
128
+ councillor: undefined
129
+ };
130
+ var POLL_INTERVAL_BACKGROUND_MS = 2000;
131
+ var MAX_POLL_TIME_MS = 5 * 60 * 1000;
132
+ var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
133
+ var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: First choose the lightest workflow that fits the work. If direct execution is justified, complete it and verify proportionately. Otherwise: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
134
+ function formatSystemReminder(text) {
135
+ return `<system-reminder>
136
+ ${text}
137
+ </system-reminder>`;
138
+ }
139
+ var PHASE_REMINDER = formatSystemReminder(PHASE_REMINDER_TEXT);
140
+ var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
141
+ - Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
142
+ - Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
143
+ - Shell is acceptable for bulk or mechanical filesystem changes when it is clearer or safer than many individual edits (for example: truncate generated logs, remove build artifacts, batch rename/move files), especially when the user explicitly asks for that shell operation.
144
+ - Before destructive or broad shell operations, verify the target set and quote paths. Prefer a dry-run/listing first when practical.
145
+ - Do not use cat/head/tail/sed/awk only to read code into context; use read/grep unless a shell pipeline is genuinely the better diagnostic.`;
146
+ var READONLY_FILE_OPERATIONS_RULES = `**File Operations Rules**:
147
+ - READ-ONLY: inspect and report; do not modify files.
148
+ - Prefer dedicated file tools for codebase inspection: glob/grep/ast_grep_search for discovery and read for file contents.
149
+ - Bash is allowed for non-mutating diagnostics and shell-native inspection when it is the clearest tool, but not for modifying files.
150
+ - Do not use cat/head/tail/sed/awk only to read code into context; use read/grep unless a shell pipeline is genuinely the better diagnostic.`;
151
+ var NO_SHELL_READONLY_FILE_OPERATIONS_RULES = `**File Operations Rules**:
152
+ - READ-ONLY: inspect and report; do not modify files.
153
+ - Use glob/grep/ast_grep_search for discovery and read for file contents.
154
+ - Do not use bash or shell commands.`;
155
+ var TMUX_SPAWN_DELAY_MS = 500;
156
+ var COUNCILLOR_STAGGER_MS = 250;
157
+ var DEFAULT_DISABLED_AGENTS = ["observer"];
158
+ var DEFAULT_MAX_SESSIONS_PER_AGENT = 2;
159
+ var DEFAULT_READ_CONTEXT_MIN_LINES = 10;
160
+ var DEFAULT_READ_CONTEXT_MAX_FILES = 8;
161
+ var DEFAULT_IMAGE_ROUTING = "auto";
162
+ function resolveImageRouting(imageRouting) {
163
+ return imageRouting ?? DEFAULT_IMAGE_ROUTING;
164
+ }
165
+
166
+ // src/config/loader.ts
167
+ import * as fs from "node:fs";
168
+ import * as path from "node:path";
169
+
170
+ // src/cli/config-io.ts
171
+ init_compat();
172
+
173
+ // src/cli/paths.ts
174
+ import { homedir } from "node:os";
175
+ import { dirname, join } from "node:path";
176
+ function getDefaultOpenCodeConfigDir() {
177
+ const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
178
+ return join(userConfigDir, "opencode");
179
+ }
180
+ function getCustomOpenCodeConfigDir() {
181
+ const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
182
+ return configDir || undefined;
183
+ }
184
+ function getConfigDir() {
185
+ const customConfigDir = getCustomOpenCodeConfigDir();
186
+ if (customConfigDir) {
187
+ return customConfigDir;
188
+ }
189
+ return getDefaultOpenCodeConfigDir();
190
+ }
191
+ function getConfigSearchDirs() {
192
+ const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
193
+ return dirs.filter((dir, index) => {
194
+ return Boolean(dir) && dirs.indexOf(dir) === index;
195
+ });
196
+ }
197
+ function getOpenCodeConfigPaths() {
198
+ const configDir = getConfigDir();
199
+ return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
200
+ }
201
+ // src/config/council-schema.ts
202
+ import { z } from "zod";
203
+
204
+ // src/utils/councillor-models.ts
205
+ function normalizeCouncillorModels(model, fallbackVariant) {
206
+ const raw = Array.isArray(model) ? model : [model];
207
+ return raw.map((entry) => typeof entry === "string" ? { id: entry, variant: fallbackVariant } : { id: entry.id, variant: entry.variant ?? fallbackVariant });
208
+ }
209
+
210
+ // src/config/council-schema.ts
211
+ var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.6-luna")');
212
+ var CouncillorModelEntrySchema = z.object({
213
+ id: ModelIdSchema,
214
+ variant: z.string().optional()
215
+ });
216
+ var CouncillorModelSchema = z.union([
217
+ ModelIdSchema,
218
+ z.array(z.union([ModelIdSchema, CouncillorModelEntrySchema])).min(1)
219
+ ]).describe('Model ID in provider/model format (e.g. "openai/gpt-5.6-luna"), or an ' + "ordered fallback chain (array of model IDs or { id, variant } entries) " + "tried in order until one responds.");
220
+ var CouncillorConfigSchema = z.object({
221
+ model: CouncillorModelSchema,
222
+ variant: z.string().optional(),
223
+ prompt: z.string().optional().describe("Optional role/guidance injected into the councillor user prompt")
224
+ }).transform((c) => {
225
+ const models = normalizeCouncillorModels(c.model, c.variant);
226
+ return {
227
+ model: models[0].id,
228
+ variant: c.variant,
229
+ prompt: c.prompt,
230
+ models
231
+ };
232
+ });
233
+ var CouncilPresetSchema = z.record(z.string(), z.record(z.string(), z.unknown())).transform((entries, ctx) => {
234
+ const councillors = {};
235
+ for (const [key, raw] of Object.entries(entries)) {
236
+ if (key === "master")
237
+ continue;
238
+ if (key === "councillors" && typeof raw === "object" && raw !== null) {
239
+ for (const [innerKey, innerRaw] of Object.entries(raw)) {
240
+ const innerParsed = CouncillorConfigSchema.safeParse(innerRaw);
241
+ if (!innerParsed.success) {
242
+ ctx.addIssue({
243
+ code: z.ZodIssueCode.custom,
244
+ message: `Invalid councillor "${innerKey}" (nested under legacy "councillors" key): ${innerParsed.error.issues.map((i) => i.message).join(", ")}`
245
+ });
246
+ return z.NEVER;
247
+ }
248
+ councillors[innerKey] = innerParsed.data;
249
+ }
250
+ continue;
251
+ }
252
+ const parsed = CouncillorConfigSchema.safeParse(raw);
253
+ if (!parsed.success) {
254
+ ctx.addIssue({
255
+ code: z.ZodIssueCode.custom,
256
+ message: `Invalid councillor "${key}": ${parsed.error.issues.map((i) => i.message).join(", ")}`
257
+ });
258
+ return z.NEVER;
259
+ }
260
+ councillors[key] = parsed.data;
261
+ }
262
+ return councillors;
263
+ });
264
+ var CouncillorExecutionModeSchema = z.enum(["parallel", "serial"]).default("parallel").describe('Execution mode for councillors. Use "serial" for single-model systems to avoid conflicts. ' + 'Use "parallel" for multi-model systems for faster execution.');
265
+ var CouncilConfigSchema = z.object({
266
+ presets: z.record(z.string(), CouncilPresetSchema),
267
+ timeout: z.number().min(0).default(180000),
268
+ default_preset: z.string().default("default"),
269
+ councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
270
+ councillor_retries: z.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries."),
271
+ master: z.unknown().optional().describe("DEPRECATED - ignored. Council agent synthesizes directly.")
272
+ }).transform((data) => {
273
+ const deprecated = [];
274
+ if (data.master !== undefined)
275
+ deprecated.push("master");
276
+ const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
277
+ return {
278
+ presets: data.presets,
279
+ timeout: data.timeout,
280
+ default_preset: data.default_preset,
281
+ councillor_execution_mode: data.councillor_execution_mode,
282
+ councillor_retries: data.councillor_retries,
283
+ _deprecated: deprecated.length > 0 ? deprecated : undefined,
284
+ _legacyMasterModel: legacyMasterModel
285
+ };
286
+ });
287
+ // src/config/schema.ts
288
+ import { z as z2 } from "zod";
289
+ var ProviderModelIdSchema = z2.string().regex(/^[^/\s]+\/[^\s]+$/, "Expected provider/model format (provider/.../model)");
290
+ var ManualAgentPlanSchema = z2.object({
291
+ primary: ProviderModelIdSchema,
292
+ fallback1: ProviderModelIdSchema,
293
+ fallback2: ProviderModelIdSchema,
294
+ fallback3: ProviderModelIdSchema
295
+ }).superRefine((value, ctx) => {
296
+ const unique = new Set([
297
+ value.primary,
298
+ value.fallback1,
299
+ value.fallback2,
300
+ value.fallback3
301
+ ]);
302
+ if (unique.size !== 4) {
303
+ ctx.addIssue({
304
+ code: z2.ZodIssueCode.custom,
305
+ message: "primary and fallbacks must be unique per agent"
306
+ });
307
+ }
308
+ });
309
+ var ManualPlanSchema = z2.object({
310
+ orchestrator: ManualAgentPlanSchema,
311
+ oracle: ManualAgentPlanSchema,
312
+ designer: ManualAgentPlanSchema,
313
+ explorer: ManualAgentPlanSchema,
314
+ librarian: ManualAgentPlanSchema,
315
+ fixer: ManualAgentPlanSchema
316
+ }).strict();
317
+ var PermissionActionSchema = z2.enum(["ask", "allow", "deny"]);
318
+ var PermissionRuleSchema = z2.union([
319
+ PermissionActionSchema,
320
+ z2.record(z2.string(), PermissionActionSchema)
321
+ ]);
322
+ var PermissionObjectSchema = z2.object({
323
+ read: PermissionRuleSchema.optional(),
324
+ edit: PermissionRuleSchema.optional(),
325
+ glob: PermissionRuleSchema.optional(),
326
+ grep: PermissionRuleSchema.optional(),
327
+ list: PermissionRuleSchema.optional(),
328
+ bash: PermissionRuleSchema.optional(),
329
+ task: PermissionRuleSchema.optional(),
330
+ external_directory: PermissionRuleSchema.optional(),
331
+ lsp: PermissionRuleSchema.optional(),
332
+ skill: PermissionRuleSchema.optional(),
333
+ todowrite: PermissionActionSchema.optional(),
334
+ question: PermissionActionSchema.optional(),
335
+ webfetch: PermissionActionSchema.optional(),
336
+ websearch: PermissionActionSchema.optional(),
337
+ codesearch: PermissionActionSchema.optional(),
338
+ doom_loop: PermissionActionSchema.optional()
339
+ }).catchall(PermissionRuleSchema);
340
+ var PermissionConfigSchema = z2.union([
341
+ PermissionActionSchema,
342
+ PermissionObjectSchema
343
+ ]);
344
+ var AgentOverrideConfigSchema = z2.object({
345
+ model: z2.union([
346
+ z2.string(),
347
+ z2.array(z2.union([
348
+ z2.string(),
349
+ z2.object({
350
+ id: z2.string(),
351
+ variant: z2.string().optional()
352
+ })
353
+ ])).min(1)
354
+ ]).optional(),
355
+ temperature: z2.number().min(0).max(2).optional(),
356
+ variant: z2.string().optional().catch(undefined),
357
+ skills: z2.array(z2.string()).optional(),
358
+ mcps: z2.array(z2.string()).optional(),
359
+ prompt: z2.string().min(1).optional(),
360
+ orchestratorPrompt: z2.string().min(1).optional(),
361
+ options: z2.record(z2.string(), z2.unknown()).optional(),
362
+ displayName: z2.string().min(1).optional(),
363
+ permission: PermissionConfigSchema.optional()
364
+ }).strict();
365
+ var MultiplexerTypeSchema = z2.enum([
366
+ "auto",
367
+ "tmux",
368
+ "zellij",
369
+ "herdr",
370
+ "cmux",
371
+ "none"
372
+ ]);
373
+ var MultiplexerLayoutSchema = z2.enum([
374
+ "main-horizontal",
375
+ "main-vertical",
376
+ "tiled",
377
+ "even-horizontal",
378
+ "even-vertical"
379
+ ]);
380
+ var ZellijPaneModeSchema = z2.enum(["agent-tab", "current-tab"]);
381
+ var TmuxLayoutSchema = MultiplexerLayoutSchema;
382
+ var MultiplexerConfigSchema = z2.object({
383
+ type: MultiplexerTypeSchema.default("none"),
384
+ layout: MultiplexerLayoutSchema.default("main-vertical"),
385
+ main_pane_size: z2.number().min(20).max(80).default(60),
386
+ zellij_pane_mode: ZellijPaneModeSchema.default("agent-tab")
387
+ });
388
+ var TmuxConfigSchema = z2.object({
389
+ enabled: z2.boolean().default(false),
390
+ layout: TmuxLayoutSchema.default("main-vertical"),
391
+ main_pane_size: z2.number().min(20).max(80).default(60)
392
+ });
393
+ var PresetSchema = z2.record(z2.string(), AgentOverrideConfigSchema);
394
+ var WebsearchConfigSchema = z2.object({
395
+ provider: z2.enum(["exa", "tavily"]).default("exa")
396
+ });
397
+ var McpNameSchema = z2.enum(["websearch", "context7", "gh_grep"]);
398
+ var InterviewConfigSchema = z2.object({
399
+ maxQuestions: z2.number().int().min(1).max(10).default(2),
400
+ outputFolder: z2.string().min(1).default("interview"),
401
+ autoOpenBrowser: z2.boolean().default(true).describe("Automatically open the interview UI in your default browser during interactive runs. Disabled automatically in tests and CI."),
402
+ port: z2.number().int().min(0).max(65535).default(0),
403
+ dashboard: z2.boolean().default(false)
404
+ });
405
+ var BackgroundJobsConfigSchema = z2.object({
406
+ maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2),
407
+ readContextMinLines: z2.number().int().min(0).max(1000).default(10),
408
+ readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
409
+ });
410
+ var FailoverConfigSchema = z2.object({
411
+ enabled: z2.boolean().default(true),
412
+ timeoutMs: z2.number().min(0).default(15000),
413
+ retryDelayMs: z2.number().min(0).default(500),
414
+ maxRetries: z2.number().int().min(0).default(3).describe("Number of consecutive 429/rate-limit responses tolerated on the " + "same model before aborting (or swapping to the next fallback " + "model when a chain is configured)."),
415
+ 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."),
416
+ runtimeOverride: z2.boolean().optional().describe("DEPRECATED: no longer used. Previously controlled whether out-of-chain " + "runtime model picks triggered fallback. Fallback is now always " + "disabled when a user explicitly selects a model via /model.")
417
+ }).strict();
418
+ var CompanionConfigSchema = z2.object({
419
+ enabled: z2.boolean().optional(),
420
+ binaryPath: z2.string().min(1).optional().describe("Path to a custom companion binary to launch."),
421
+ position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
422
+ size: z2.enum(["small", "medium", "large"]).optional(),
423
+ gifPack: z2.enum(["default"]).optional().describe("Bundled companion animation pack to use."),
424
+ loopStyle: z2.enum(["classic", "smooth"]).optional().describe("Companion animation playback style: classic loops or smooth ping-pong playback."),
425
+ speed: z2.number().min(0.25).max(4).optional().describe("Companion animation playback speed multiplier. Defaults to 1."),
426
+ debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
427
+ });
428
+ var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
429
+ var MAX_ACP_TIMEOUT_MS = 2147483647;
430
+ var AcpAgentConfigSchema = z2.object({
431
+ command: z2.string().min(1),
432
+ args: z2.array(z2.string()).default([]),
433
+ env: z2.record(z2.string(), z2.string()).default({}),
434
+ cwd: z2.string().min(1).optional(),
435
+ description: z2.string().min(1).optional(),
436
+ prompt: z2.string().min(1).optional(),
437
+ orchestratorPrompt: z2.string().min(1).optional(),
438
+ wrapperModel: ProviderModelIdSchema.optional(),
439
+ timeoutMs: z2.number().int().min(0).max(MAX_ACP_TIMEOUT_MS).default(0).describe("Timeout for a single ACP run in milliseconds. Set to 0 to disable the timeout."),
440
+ permissionMode: AcpAgentPermissionModeSchema.default("ask")
441
+ }).strict();
442
+ var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
443
+ function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
444
+ for (const [name, override] of Object.entries(overrides)) {
445
+ if (name === "orchestrator" && override.orchestratorPrompt !== undefined) {
446
+ ctx.addIssue({
447
+ code: z2.ZodIssueCode.custom,
448
+ path: [...pathPrefix, name, "orchestratorPrompt"],
449
+ message: "orchestratorPrompt is not supported for the orchestrator agent"
450
+ });
451
+ }
452
+ }
453
+ }
454
+ var PluginConfigSchema = z2.object({
455
+ preset: z2.string().optional(),
456
+ setDefaultAgent: z2.boolean().optional(),
457
+ compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout."),
458
+ stripOrchestratorModel: z2.boolean().optional().describe("When true, omit orchestrator.model and orchestrator.variant from the SDK config so OpenCode uses the session model selected with /model after subagent dispatch. An explicitly selected preset that sets orchestrator.model is preserved. Defaults to false."),
459
+ autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
460
+ presets: z2.record(z2.string(), PresetSchema).optional(),
461
+ agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
462
+ disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
463
+ image_routing: z2.enum(["auto", "direct"]).optional().describe("How image attachments are handled. " + "When omitted, preserves legacy conditional behavior: intercept " + 'attachments only when observer is enabled. "auto": requires ' + "observer to be enabled and saves attachments to disk before " + 'nudging delegation to @observer. "direct": always passes ' + "attachments to the orchestrator untouched."),
464
+ disabled_mcps: z2.array(z2.string()).optional().describe("MCP server names to disable completely. Disabled servers are not " + "started and cannot be used by agents."),
465
+ disabled_tools: z2.array(z2.string()).optional().describe("Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents."),
466
+ disabled_skills: z2.array(z2.string()).optional().describe("Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides."),
467
+ multiplexer: MultiplexerConfigSchema.optional(),
468
+ tmux: TmuxConfigSchema.optional(),
469
+ websearch: WebsearchConfigSchema.optional(),
470
+ interview: InterviewConfigSchema.optional(),
471
+ backgroundJobs: BackgroundJobsConfigSchema.optional(),
472
+ fallback: FailoverConfigSchema.optional(),
473
+ council: CouncilConfigSchema.optional(),
474
+ companion: CompanionConfigSchema.optional(),
475
+ acpAgents: AcpAgentsConfigSchema.optional()
476
+ }).superRefine((value, ctx) => {
477
+ if (value.agents) {
478
+ rejectOrchestratorPromptOnOrchestrator(value.agents, ctx, ["agents"]);
479
+ }
480
+ if (value.presets) {
481
+ for (const [presetName, preset] of Object.entries(value.presets)) {
482
+ rejectOrchestratorPromptOnOrchestrator(preset, ctx, [
483
+ "presets",
484
+ presetName
485
+ ]);
486
+ }
487
+ }
488
+ });
489
+ // src/config/utils.ts
490
+ function getAgentOverride(config, name) {
491
+ const overrides = config?.agents ?? {};
492
+ return overrides[name] ?? overrides[Object.keys(AGENT_ALIASES).find((k) => AGENT_ALIASES[k] === name) ?? ""];
493
+ }
494
+ function getCustomAgentNames(config) {
495
+ const overrides = config?.agents ?? {};
496
+ return Object.keys(overrides).filter((name) => {
497
+ if (AGENT_ALIASES[name] !== undefined) {
498
+ return false;
499
+ }
500
+ return !ALL_AGENT_NAMES.includes(name);
501
+ });
502
+ }
503
+ function getAcpAgentNames(config) {
504
+ return Object.keys(config?.acpAgents ?? {});
505
+ }
506
+ // src/config/agent-mcps.ts
507
+ var DEFAULT_AGENT_MCPS = {
508
+ orchestrator: ["*", "!context7"],
509
+ designer: [],
510
+ oracle: [],
511
+ librarian: ["websearch", "context7", "gh_grep"],
512
+ explorer: [],
513
+ fixer: [],
514
+ observer: [],
515
+ council: [],
516
+ councillor: []
517
+ };
518
+ function parseList(items, allAvailable) {
519
+ if (!items || items.length === 0) {
520
+ return [];
521
+ }
522
+ const allow = items.filter((i) => !i.startsWith("!"));
523
+ const deny = items.filter((i) => i.startsWith("!")).map((i) => i.slice(1));
524
+ if (deny.includes("*")) {
525
+ return [];
526
+ }
527
+ if (allow.includes("*")) {
528
+ return allAvailable.filter((item) => !deny.includes(item));
529
+ }
530
+ return allow.filter((item) => !deny.includes(item) && allAvailable.includes(item));
531
+ }
532
+ function getAgentMcpList(agentName, config) {
533
+ const agentConfig = getAgentOverride(config, agentName);
534
+ if (agentConfig?.mcps !== undefined) {
535
+ return agentConfig.mcps;
536
+ }
537
+ const defaultMcps = DEFAULT_AGENT_MCPS[agentName];
538
+ return defaultMcps ?? [];
539
+ }
540
+
541
+ // src/cli/custom-skills-registry.ts
542
+ var CUSTOM_SKILLS = [
543
+ {
544
+ name: "simplify",
545
+ description: "Code simplification and readability-focused refactoring",
546
+ allowedAgents: ["oracle"],
547
+ sourcePath: "src/skills/simplify"
548
+ },
549
+ {
550
+ name: "codemap",
551
+ description: "Repository understanding and hierarchical codemap generation",
552
+ allowedAgents: ["orchestrator"],
553
+ sourcePath: "src/skills/codemap"
554
+ },
555
+ {
556
+ name: "clonedeps",
557
+ description: "Clone important dependency source for local inspection",
558
+ allowedAgents: ["orchestrator"],
559
+ sourcePath: "src/skills/clonedeps"
560
+ },
561
+ {
562
+ name: "deepwork",
563
+ description: "Heavy/complex coding sessions and large modifications workflow",
564
+ allowedAgents: ["orchestrator"],
565
+ sourcePath: "src/skills/deepwork"
566
+ },
567
+ {
568
+ name: "verification-planning",
569
+ description: "Plan credible, proportionate evidence before non-trivial implementation",
570
+ allowedAgents: ["orchestrator"],
571
+ sourcePath: "src/skills/verification-planning"
572
+ },
573
+ {
574
+ name: "reflect",
575
+ description: "Review repeated work and suggest reusable workflow improvements",
576
+ allowedAgents: ["orchestrator"],
577
+ sourcePath: "src/skills/reflect"
578
+ },
579
+ {
580
+ name: "oh-my-opencode-slim",
581
+ description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
582
+ allowedAgents: ["orchestrator"],
583
+ sourcePath: "src/skills/oh-my-opencode-slim"
584
+ },
585
+ {
586
+ name: "worktrees",
587
+ description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
588
+ allowedAgents: ["orchestrator"],
589
+ sourcePath: "src/skills/worktrees"
590
+ }
591
+ ];
592
+
593
+ // src/cli/config-io.ts
594
+ function stripJsonComments(json) {
595
+ const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
596
+ const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
597
+ return json.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
598
+ }
599
+
600
+ // src/config/loader.ts
601
+ var PROMPTS_DIR_NAME = "oh-my-opencode-slim";
602
+ function loadConfigFromPath(configPath, options) {
603
+ try {
604
+ const content = fs.readFileSync(configPath, "utf-8");
605
+ let rawConfig;
606
+ try {
607
+ const stripped = stripJsonComments(content);
608
+ const interpolated = stripped.replace(/\{env:([^}]+)\}/g, (_, varName) => process.env[varName] ?? "");
609
+ rawConfig = JSON.parse(interpolated);
610
+ } catch (error) {
611
+ const message = error instanceof Error ? error.message : String(error);
612
+ options?.onWarning?.({
613
+ path: configPath,
614
+ kind: "invalid-json",
615
+ message
616
+ });
617
+ if (!options?.silent) {
618
+ console.warn(`[oh-my-opencode-slim] Invalid JSON in ${configPath}:`, message);
619
+ }
620
+ return null;
621
+ }
622
+ const result = PluginConfigSchema.safeParse(rawConfig);
623
+ if (!result.success) {
624
+ options?.onWarning?.({
625
+ path: configPath,
626
+ kind: "invalid-schema",
627
+ message: "Config does not match schema",
628
+ formatted: result.error.format()
629
+ });
630
+ if (!options?.silent) {
631
+ console.warn(`[oh-my-opencode-slim] Invalid config at ${configPath}:`);
632
+ console.warn(result.error.format());
633
+ }
634
+ return null;
635
+ }
636
+ return result.data;
637
+ } catch (error) {
638
+ if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
639
+ options?.onWarning?.({
640
+ path: configPath,
641
+ kind: "read-error",
642
+ message: error.message
643
+ });
644
+ if (!options?.silent) {
645
+ console.warn(`[oh-my-opencode-slim] Error reading config from ${configPath}:`, error.message);
646
+ }
647
+ }
648
+ return null;
649
+ }
650
+ }
651
+ function findConfigPath(basePath) {
652
+ const jsoncPath = `${basePath}.jsonc`;
653
+ const jsonPath = `${basePath}.json`;
654
+ if (fs.existsSync(jsoncPath)) {
655
+ return jsoncPath;
656
+ }
657
+ if (fs.existsSync(jsonPath)) {
658
+ return jsonPath;
659
+ }
660
+ return null;
661
+ }
662
+ function findConfigPathInDirs(configDirs, baseName) {
663
+ for (const configDir of configDirs) {
664
+ const configPath = findConfigPath(path.join(configDir, baseName));
665
+ if (configPath) {
666
+ return configPath;
667
+ }
668
+ }
669
+ return null;
670
+ }
671
+ function validateFinalImageRouting(config, configPath, options) {
672
+ if (config.image_routing !== "auto")
673
+ return true;
674
+ const disabledAgents = config.disabled_agents ?? DEFAULT_DISABLED_AGENTS;
675
+ if (!disabledAgents.includes("observer"))
676
+ return true;
677
+ const message = 'image_routing "auto" requires observer to be enabled. ' + 'Remove "observer" from disabled_agents.';
678
+ options?.onWarning?.({
679
+ path: configPath,
680
+ kind: "invalid-schema",
681
+ message
682
+ });
683
+ if (!options?.silent) {
684
+ console.warn(`[oh-my-opencode-slim] Invalid config: ${message}`);
685
+ }
686
+ return false;
687
+ }
688
+ function findPluginConfigPaths(directory) {
689
+ const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
690
+ const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
691
+ const projectConfigPath = findConfigPath(projectConfigBasePath);
692
+ return { userConfigPath, projectConfigPath };
693
+ }
694
+ function mergePluginConfigs(base, override) {
695
+ return {
696
+ ...base,
697
+ ...override,
698
+ agents: deepMerge(base.agents, override.agents),
699
+ presets: deepMerge(base.presets, override.presets),
700
+ tmux: deepMerge(base.tmux, override.tmux),
701
+ multiplexer: deepMerge(base.multiplexer, override.multiplexer),
702
+ interview: deepMerge(base.interview, override.interview),
703
+ backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
704
+ fallback: deepMerge(base.fallback, override.fallback),
705
+ council: deepMerge(base.council, override.council),
706
+ acpAgents: deepMerge(base.acpAgents, override.acpAgents),
707
+ companion: deepMerge(base.companion, override.companion)
708
+ };
709
+ }
710
+ function deepMerge(base, override) {
711
+ if (!base)
712
+ return override;
713
+ if (!override)
714
+ return base;
715
+ const result = { ...base };
716
+ for (const key of Object.keys(override)) {
717
+ const baseVal = base[key];
718
+ const overrideVal = override[key];
719
+ if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
720
+ result[key] = deepMerge(baseVal, overrideVal);
721
+ } else {
722
+ result[key] = overrideVal;
723
+ }
724
+ }
725
+ return result;
726
+ }
727
+ function loadPluginConfig(directory, options) {
728
+ const { userConfigPath, projectConfigPath } = findPluginConfigPaths(directory);
729
+ let config = userConfigPath ? loadConfigFromPath(userConfigPath, options) ?? {} : {};
730
+ const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath, options) : null;
731
+ if (projectConfig) {
732
+ config = mergePluginConfigs(config, projectConfig);
733
+ }
734
+ config = migrateTmuxToMultiplexer(config);
735
+ const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
736
+ if (envPreset) {
737
+ config.preset = envPreset;
738
+ }
739
+ if (config.preset) {
740
+ const preset = config.presets?.[config.preset];
741
+ if (preset) {
742
+ config.agents = deepMerge(preset, config.agents);
743
+ } else {
744
+ const presetSource = envPreset === config.preset ? "environment variable" : "config file";
745
+ const availablePresets = config.presets ? Object.keys(config.presets).join(", ") : "none";
746
+ const message = `Preset "${config.preset}" not found (from ${presetSource}). Available presets: ${availablePresets}`;
747
+ options?.onWarning?.({
748
+ path: projectConfigPath ?? userConfigPath ?? "",
749
+ kind: "missing-preset",
750
+ message
751
+ });
752
+ if (!options?.silent) {
753
+ console.warn(`[oh-my-opencode-slim] ${message}`);
754
+ }
755
+ }
756
+ }
757
+ if (config.companion) {
758
+ config.companion = {
759
+ enabled: config.companion.enabled ?? false,
760
+ binaryPath: config.companion.binaryPath,
761
+ position: config.companion.position ?? "bottom-right",
762
+ size: config.companion.size ?? "medium",
763
+ gifPack: config.companion.gifPack ?? "default",
764
+ loopStyle: config.companion.loopStyle ?? "classic",
765
+ speed: config.companion.speed ?? 1,
766
+ debug: config.companion.debug ?? false
767
+ };
768
+ }
769
+ validateFinalImageRouting(config, projectConfigPath ?? userConfigPath ?? "", options);
770
+ return config;
771
+ }
772
+ function loadAgentPrompt(agentName, optionsOrPreset) {
773
+ let preset;
774
+ let projectDirectory;
775
+ if (typeof optionsOrPreset === "string") {
776
+ preset = optionsOrPreset;
777
+ } else if (optionsOrPreset && typeof optionsOrPreset === "object") {
778
+ preset = optionsOrPreset.preset;
779
+ projectDirectory = optionsOrPreset.projectDirectory;
780
+ }
781
+ const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
782
+ const searchDirs = [];
783
+ if (projectDirectory && presetDirName) {
784
+ searchDirs.push(path.join(projectDirectory, ".opencode", PROMPTS_DIR_NAME, presetDirName));
785
+ }
786
+ if (projectDirectory) {
787
+ searchDirs.push(path.join(projectDirectory, ".opencode", PROMPTS_DIR_NAME));
788
+ }
789
+ if (presetDirName) {
790
+ for (const userDir of getConfigSearchDirs()) {
791
+ searchDirs.push(path.join(userDir, PROMPTS_DIR_NAME, presetDirName));
792
+ }
793
+ }
794
+ for (const userDir of getConfigSearchDirs()) {
795
+ searchDirs.push(path.join(userDir, PROMPTS_DIR_NAME));
796
+ }
797
+ const readFirstPrompt = (fileName, errorPrefix) => {
798
+ for (const dir of searchDirs) {
799
+ const promptPath = path.join(dir, fileName);
800
+ if (!fs.existsSync(promptPath)) {
801
+ continue;
802
+ }
803
+ try {
804
+ return fs.readFileSync(promptPath, "utf-8");
805
+ } catch (error) {
806
+ console.warn(`[oh-my-opencode-slim] ${errorPrefix} ${promptPath}:`, error instanceof Error ? error.message : String(error));
807
+ }
808
+ }
809
+ return;
810
+ };
811
+ const result = {};
812
+ result.prompt = readFirstPrompt(`${agentName}.md`, "Error reading prompt file");
813
+ result.appendPrompt = readFirstPrompt(`${agentName}_append.md`, "Error reading append prompt file");
814
+ return result;
815
+ }
816
+ function migrateTmuxToMultiplexer(config) {
817
+ if (config.multiplexer?.type && config.multiplexer.type !== "none") {
818
+ return config;
819
+ }
820
+ if (config.tmux?.enabled) {
821
+ return {
822
+ ...config,
823
+ multiplexer: {
824
+ type: "tmux",
825
+ layout: config.tmux.layout ?? "main-vertical",
826
+ main_pane_size: config.tmux.main_pane_size ?? 60,
827
+ zellij_pane_mode: "agent-tab"
828
+ }
829
+ };
830
+ }
831
+ return config;
832
+ }
833
+
834
+ // src/tui-state.ts
835
+ import { createHash } from "node:crypto";
836
+ import * as fs2 from "node:fs";
837
+ import * as os from "node:os";
838
+ import * as path2 from "node:path";
839
+ var STATE_DIR = "oh-my-opencode-slim";
840
+ var STATE_FILE = "tui-state.json";
841
+ function dataDir() {
842
+ return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
843
+ }
844
+ function projectScope(projectDir) {
845
+ return createHash("sha256").update(path2.resolve(projectDir)).digest("hex").slice(0, 12);
846
+ }
847
+ function getTuiStatePath(projectDir) {
848
+ return path2.join(dataDir(), "opencode", "storage", STATE_DIR, projectScope(projectDir), STATE_FILE);
849
+ }
850
+ function emptySnapshot() {
851
+ return {
852
+ version: 1,
853
+ updatedAt: Date.now(),
854
+ agentModels: {},
855
+ agentVariants: {}
856
+ };
857
+ }
858
+ function parseSnapshot(value) {
859
+ const parsed = JSON.parse(value);
860
+ if (parsed?.version !== 1)
861
+ return emptySnapshot();
862
+ return {
863
+ version: 1,
864
+ updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
865
+ agentModels: parsed.agentModels ?? {},
866
+ agentVariants: parsed.agentVariants ?? {}
867
+ };
868
+ }
869
+ function readTuiSnapshot(projectDir) {
870
+ try {
871
+ return parseSnapshot(fs2.readFileSync(getTuiStatePath(projectDir), "utf8"));
872
+ } catch {
873
+ return emptySnapshot();
874
+ }
875
+ }
876
+ async function readTuiSnapshotAsync(projectDir) {
877
+ try {
878
+ return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(projectDir), "utf8"));
879
+ } catch {
880
+ return emptySnapshot();
881
+ }
882
+ }
883
+ function writeTuiSnapshot(snapshot, projectDir) {
884
+ try {
885
+ const filePath = getTuiStatePath(projectDir);
886
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
887
+ fs2.writeFileSync(filePath, `${JSON.stringify(snapshot)}
888
+ `);
889
+ } catch {}
890
+ }
891
+ function updateSnapshot(projectDir, mutator) {
892
+ const snapshot = readTuiSnapshot(projectDir);
893
+ mutator(snapshot);
894
+ snapshot.updatedAt = Date.now();
895
+ writeTuiSnapshot(snapshot, projectDir);
896
+ }
897
+ function recordTuiAgentModels(input, projectDir) {
898
+ updateSnapshot(projectDir, (snapshot) => {
899
+ snapshot.agentModels = { ...input.agentModels };
900
+ snapshot.agentVariants = { ...input.agentVariants ?? {} };
901
+ });
902
+ }
903
+ function recordTuiAgentModel(input, projectDir) {
904
+ updateSnapshot(projectDir, (snapshot) => {
905
+ snapshot.agentModels[input.agentName] = input.model;
906
+ if (input.variant !== undefined) {
907
+ if (input.variant === null) {
908
+ delete snapshot.agentVariants[input.agentName];
909
+ } else {
910
+ snapshot.agentVariants[input.agentName] = input.variant;
911
+ }
912
+ }
913
+ });
914
+ }
915
+
916
+ // src/utils/env.ts
917
+ var TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
918
+ var PLUGIN_DISABLE_ENV = "OH_MY_OPENCODE_SLIM_DISABLE";
919
+ function isTruthyEnvValue(value) {
920
+ return TRUTHY_ENV_VALUES.has(value?.trim().toLowerCase() ?? "");
921
+ }
922
+ function isPluginDisabledByEnv(env = process.env) {
923
+ return isTruthyEnvValue(env[PLUGIN_DISABLE_ENV]);
924
+ }
925
+
926
+ // src/tui.ts
927
+ var PLUGIN_NAME = "oh-my-opencode-slim";
928
+ var CONFIG_WARNING_COLOR = "orange";
929
+ var FALLBACK_SIDEBAR_AGENTS = SUBAGENT_NAMES.filter((agent) => agent !== "councillor" && agent !== "council" && !DEFAULT_DISABLED_AGENTS.includes(agent));
930
+ var BORDER = { type: "single" };
931
+ async function readPackageVersion() {
932
+ try {
933
+ const packageJson = await Bun.file(new URL("../package.json", import.meta.url)).json();
934
+ return typeof packageJson.version === "string" ? packageJson.version : undefined;
935
+ } catch {
936
+ return;
937
+ }
938
+ }
939
+ function element(tag, props, children = []) {
940
+ const node = createElement(tag);
941
+ for (const [key, value] of Object.entries(props)) {
942
+ if (value !== undefined)
943
+ setProp(node, key, value);
944
+ }
945
+ for (const child of children) {
946
+ if (child === null || child === undefined || child === false)
947
+ continue;
948
+ insert(node, child);
949
+ }
950
+ return node;
951
+ }
952
+ function text(props, children) {
953
+ return element("text", props, children);
954
+ }
955
+ function box(props, children = []) {
956
+ return element("box", props, children);
957
+ }
958
+ function getTuiDirectory(api) {
959
+ return api.state?.path?.directory ?? process.cwd();
960
+ }
961
+ function splitSidebarModelId(model) {
962
+ const slashIndex = model.indexOf("/");
963
+ if (slashIndex === -1) {
964
+ return { model };
965
+ }
966
+ return {
967
+ provider: model.slice(0, slashIndex),
968
+ model: model.slice(slashIndex + 1)
969
+ };
970
+ }
971
+ function getSidebarAgentNames(snapshot) {
972
+ const configuredAgents = Object.keys(snapshot.agentModels);
973
+ return configuredAgents.length > 0 ? configuredAgents : FALLBACK_SIDEBAR_AGENTS;
974
+ }
975
+ function agentRow(label, model, variant, theme) {
976
+ const modelParts = splitSidebarModelId(model);
977
+ const detailRows = [];
978
+ function detailRow(fieldLabel, value) {
979
+ return box({ width: "100%", flexDirection: "row", paddingLeft: 2 }, [
980
+ text({ fg: theme.textMuted, width: 9 }, [fieldLabel]),
981
+ text({ fg: theme.textMuted }, [value])
982
+ ]);
983
+ }
984
+ if (modelParts.provider) {
985
+ detailRows.push(detailRow("provider", modelParts.provider));
986
+ }
987
+ detailRows.push(detailRow("model", modelParts.model));
988
+ if (variant) {
989
+ detailRows.push(detailRow("variant", variant));
990
+ }
991
+ return box({ width: "100%", flexDirection: "column", marginBottom: 1 }, [
992
+ text({ fg: theme.textMuted }, [label]),
993
+ ...detailRows
994
+ ]);
995
+ }
996
+ function compactAgentRow(label, model, _variant, theme) {
997
+ const modelName = splitSidebarModelId(model).model;
998
+ return box({
999
+ width: "100%",
1000
+ flexDirection: "row",
1001
+ justifyContent: "space-between"
1002
+ }, [
1003
+ text({ fg: theme.textMuted, width: 14 }, [label]),
1004
+ text({ fg: theme.textMuted }, [modelName])
1005
+ ]);
1006
+ }
1007
+ function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar) {
1008
+ const configStatusRow = buildConfigStatusRow(configInvalid, theme);
1009
+ return box({
1010
+ width: "100%",
1011
+ flexDirection: "column",
1012
+ border: BORDER,
1013
+ borderColor: theme.borderActive,
1014
+ paddingTop: 1,
1015
+ paddingBottom: 1,
1016
+ paddingLeft: 1,
1017
+ paddingRight: 1
1018
+ }, [
1019
+ box({
1020
+ width: "100%",
1021
+ flexDirection: "row",
1022
+ justifyContent: "space-between",
1023
+ alignItems: "center"
1024
+ }, [
1025
+ box({ paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent }, [text({ fg: theme.text }, ["OMO-Slim"])]),
1026
+ text({ fg: theme.textMuted }, [`v${version}`])
1027
+ ]),
1028
+ configStatusRow,
1029
+ box({ width: "100%", marginTop: 1 }, [
1030
+ text({ fg: theme.text }, ["Agents"])
1031
+ ]),
1032
+ ...getSidebarAgentNames(snapshot).map((agentName) => {
1033
+ const model = snapshot.agentModels[agentName] ?? "pending";
1034
+ const variant = snapshot.agentVariants[agentName];
1035
+ if (compactSidebar) {
1036
+ return compactAgentRow(agentName, model, variant, theme);
1037
+ }
1038
+ return agentRow(agentName, model, variant, theme);
1039
+ })
1040
+ ]);
1041
+ }
1042
+ function buildConfigStatusRow(configInvalid, theme) {
1043
+ if (!configInvalid)
1044
+ return null;
1045
+ return box({
1046
+ width: "100%",
1047
+ flexDirection: "column",
1048
+ marginTop: 1,
1049
+ marginBottom: 1
1050
+ }, [
1051
+ text({ fg: CONFIG_WARNING_COLOR }, ["Config invalid"]),
1052
+ text({ fg: theme.textMuted }, ["Run doctor for details"])
1053
+ ]);
1054
+ }
1055
+ function readConfigState(directory) {
1056
+ let configInvalid = false;
1057
+ const config = loadPluginConfig(directory, {
1058
+ silent: true,
1059
+ onWarning: () => {
1060
+ configInvalid = true;
1061
+ }
1062
+ });
1063
+ const compactSidebar = config.compactSidebar ?? true;
1064
+ return { configInvalid, compactSidebar };
1065
+ }
1066
+ function readConfigInvalid(directory) {
1067
+ return readConfigState(directory).configInvalid;
1068
+ }
1069
+ function readCompactSidebar(directory) {
1070
+ return readConfigState(directory).compactSidebar;
1071
+ }
1072
+ var plugin = {
1073
+ id: `${PLUGIN_NAME}:tui`,
1074
+ tui: async (api, _options, meta) => {
1075
+ if (isPluginDisabledByEnv())
1076
+ return;
1077
+ const version = meta.version ?? await readPackageVersion() ?? "dev";
1078
+ let configDirectory = getTuiDirectory(api);
1079
+ let { configInvalid, compactSidebar } = readConfigState(configDirectory);
1080
+ let snapshot = readTuiSnapshot(configDirectory);
1081
+ const renderTimer = setInterval(async () => {
1082
+ try {
1083
+ const currentDirectory = getTuiDirectory(api);
1084
+ snapshot = await readTuiSnapshotAsync(currentDirectory);
1085
+ if (currentDirectory !== configDirectory) {
1086
+ configDirectory = currentDirectory;
1087
+ ({ configInvalid, compactSidebar } = readConfigState(configDirectory));
1088
+ }
1089
+ api.renderer.requestRender();
1090
+ } catch {}
1091
+ }, 1000);
1092
+ api.lifecycle.onDispose(() => {
1093
+ clearInterval(renderTimer);
1094
+ });
1095
+ api.slots.register({
1096
+ order: 900,
1097
+ slots: {
1098
+ sidebar_content() {
1099
+ return renderSidebar(snapshot, version, api.theme.current, configInvalid, compactSidebar);
1100
+ }
1101
+ }
1102
+ });
1103
+ }
1104
+ };
1105
+ var tui_default = plugin;
1106
+ export {
1107
+ splitSidebarModelId,
1108
+ readConfigInvalid,
1109
+ readCompactSidebar,
1110
+ getSidebarAgentNames,
1111
+ tui_default as default
1112
+ };