@zcy2nn/agent-forge 1.0.1

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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/agent-forge.schema.json +675 -0
  4. package/dist/agents/council.d.ts +27 -0
  5. package/dist/agents/councillor.d.ts +2 -0
  6. package/dist/agents/implementer.d.ts +2 -0
  7. package/dist/agents/index.d.ts +30 -0
  8. package/dist/agents/orchestrator.d.ts +30 -0
  9. package/dist/agents/researcher.d.ts +2 -0
  10. package/dist/agents/reviewer.d.ts +2 -0
  11. package/dist/cli/config-io.d.ts +24 -0
  12. package/dist/cli/config-manager.d.ts +4 -0
  13. package/dist/cli/custom-skills.d.ts +29 -0
  14. package/dist/cli/doctor.d.ts +38 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +1932 -0
  17. package/dist/cli/install.d.ts +2 -0
  18. package/dist/cli/migration.d.ts +46 -0
  19. package/dist/cli/model-key-normalization.d.ts +1 -0
  20. package/dist/cli/paths.d.ts +35 -0
  21. package/dist/cli/providers.d.ts +99 -0
  22. package/dist/cli/skills.d.ts +52 -0
  23. package/dist/cli/system.d.ts +6 -0
  24. package/dist/cli/types.d.ts +40 -0
  25. package/dist/config/agent-mcps.d.ts +15 -0
  26. package/dist/config/constants.d.ts +28 -0
  27. package/dist/config/council-schema.d.ts +127 -0
  28. package/dist/config/index.d.ts +5 -0
  29. package/dist/config/loader.d.ts +57 -0
  30. package/dist/config/runtime-preset.d.ts +12 -0
  31. package/dist/config/schema.d.ts +371 -0
  32. package/dist/config/utils.d.ts +15 -0
  33. package/dist/council/council-manager.d.ts +49 -0
  34. package/dist/council/index.d.ts +1 -0
  35. package/dist/divoom/council.gif +0 -0
  36. package/dist/divoom/designer.gif +0 -0
  37. package/dist/divoom/explorer.gif +0 -0
  38. package/dist/divoom/fixer.gif +0 -0
  39. package/dist/divoom/input.gif +0 -0
  40. package/dist/divoom/intro.gif +0 -0
  41. package/dist/divoom/librarian.gif +0 -0
  42. package/dist/divoom/manager.d.ts +57 -0
  43. package/dist/divoom/oracle.gif +0 -0
  44. package/dist/divoom/orchestrator.gif +0 -0
  45. package/dist/hooks/apply-patch/codec.d.ts +7 -0
  46. package/dist/hooks/apply-patch/errors.d.ts +25 -0
  47. package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
  48. package/dist/hooks/apply-patch/index.d.ts +15 -0
  49. package/dist/hooks/apply-patch/matching.d.ts +26 -0
  50. package/dist/hooks/apply-patch/operations.d.ts +3 -0
  51. package/dist/hooks/apply-patch/patch.d.ts +2 -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 +32 -0
  59. package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
  60. package/dist/hooks/auto-update-checker/index.d.ts +18 -0
  61. package/dist/hooks/auto-update-checker/types.d.ts +22 -0
  62. package/dist/hooks/chat-headers.d.ts +16 -0
  63. package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
  64. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  65. package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
  66. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  67. package/dist/hooks/filter-available-skills/index.d.ts +32 -0
  68. package/dist/hooks/foreground-fallback/index.d.ts +72 -0
  69. package/dist/hooks/image-hook.d.ts +19 -0
  70. package/dist/hooks/index.d.ts +13 -0
  71. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  72. package/dist/hooks/json-error-recovery/index.d.ts +1 -0
  73. package/dist/hooks/phase-reminder/index.d.ts +26 -0
  74. package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
  75. package/dist/hooks/task-session-manager/index.d.ts +52 -0
  76. package/dist/hooks/todo-continuation/index.d.ts +53 -0
  77. package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
  78. package/dist/index.d.ts +5 -0
  79. package/dist/index.js +32981 -0
  80. package/dist/interview/dashboard.d.ts +62 -0
  81. package/dist/interview/document.d.ts +25 -0
  82. package/dist/interview/helpers.d.ts +10 -0
  83. package/dist/interview/index.d.ts +1 -0
  84. package/dist/interview/manager.d.ts +35 -0
  85. package/dist/interview/parser.d.ts +11 -0
  86. package/dist/interview/prompts.d.ts +7 -0
  87. package/dist/interview/server.d.ts +13 -0
  88. package/dist/interview/service.d.ts +34 -0
  89. package/dist/interview/types.d.ts +96 -0
  90. package/dist/interview/ui.d.ts +12 -0
  91. package/dist/mcp/context7.d.ts +6 -0
  92. package/dist/mcp/grep-app.d.ts +6 -0
  93. package/dist/mcp/index.d.ts +8 -0
  94. package/dist/mcp/types.d.ts +12 -0
  95. package/dist/mcp/websearch.d.ts +9 -0
  96. package/dist/multiplexer/factory.d.ts +26 -0
  97. package/dist/multiplexer/index.d.ts +9 -0
  98. package/dist/multiplexer/session-manager.d.ts +53 -0
  99. package/dist/multiplexer/tmux/index.d.ts +22 -0
  100. package/dist/multiplexer/types.d.ts +54 -0
  101. package/dist/multiplexer/zellij/index.d.ts +34 -0
  102. package/dist/tools/ast-grep/cli.d.ts +15 -0
  103. package/dist/tools/ast-grep/constants.d.ts +25 -0
  104. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  105. package/dist/tools/ast-grep/index.d.ts +10 -0
  106. package/dist/tools/ast-grep/tools.d.ts +3 -0
  107. package/dist/tools/ast-grep/types.d.ts +30 -0
  108. package/dist/tools/ast-grep/utils.d.ts +4 -0
  109. package/dist/tools/council.d.ts +10 -0
  110. package/dist/tools/index.d.ts +6 -0
  111. package/dist/tools/preset-manager.d.ts +27 -0
  112. package/dist/tools/skill.d.ts +9 -0
  113. package/dist/tools/smartfetch/binary.d.ts +3 -0
  114. package/dist/tools/smartfetch/cache.d.ts +6 -0
  115. package/dist/tools/smartfetch/constants.d.ts +12 -0
  116. package/dist/tools/smartfetch/index.d.ts +3 -0
  117. package/dist/tools/smartfetch/network.d.ts +38 -0
  118. package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
  119. package/dist/tools/smartfetch/tool.d.ts +3 -0
  120. package/dist/tools/smartfetch/types.d.ts +122 -0
  121. package/dist/tools/smartfetch/utils.d.ts +18 -0
  122. package/dist/tui-state.d.ts +15 -0
  123. package/dist/tui.d.ts +8 -0
  124. package/dist/tui.js +248 -0
  125. package/dist/utils/agent-variant.d.ts +63 -0
  126. package/dist/utils/compat.d.ts +30 -0
  127. package/dist/utils/env.d.ts +1 -0
  128. package/dist/utils/index.d.ts +9 -0
  129. package/dist/utils/internal-initiator.d.ts +6 -0
  130. package/dist/utils/logger.d.ts +8 -0
  131. package/dist/utils/polling.d.ts +21 -0
  132. package/dist/utils/session-manager.d.ts +55 -0
  133. package/dist/utils/session.d.ts +74 -0
  134. package/dist/utils/subagent-depth.d.ts +35 -0
  135. package/dist/utils/system-collapse.d.ts +6 -0
  136. package/dist/utils/task.d.ts +4 -0
  137. package/dist/utils/zip-extractor.d.ts +1 -0
  138. package/package.json +104 -0
  139. package/src/skills/brainstorming/SKILL.md +177 -0
  140. package/src/skills/brainstorming/scripts/frame-template.html +214 -0
  141. package/src/skills/brainstorming/scripts/helper.js +88 -0
  142. package/src/skills/brainstorming/scripts/server.cjs +354 -0
  143. package/src/skills/brainstorming/scripts/start-server.sh +148 -0
  144. package/src/skills/brainstorming/scripts/stop-server.sh +56 -0
  145. package/src/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  146. package/src/skills/brainstorming/visual-companion.md +279 -0
  147. package/src/skills/codemap/README.md +59 -0
  148. package/src/skills/codemap/SKILL.md +163 -0
  149. package/src/skills/codemap/codemap.md +36 -0
  150. package/src/skills/codemap/scripts/codemap.mjs +483 -0
  151. package/src/skills/codemap/scripts/codemap.test.ts +129 -0
  152. package/src/skills/codemap.md +40 -0
  153. package/src/skills/dispatching-parallel-agents/SKILL.md +193 -0
  154. package/src/skills/executing-plans/SKILL.md +78 -0
  155. package/src/skills/finishing-a-development-branch/SKILL.md +211 -0
  156. package/src/skills/receiving-code-review/SKILL.md +224 -0
  157. package/src/skills/requesting-code-review/SKILL.md +113 -0
  158. package/src/skills/requesting-code-review/code-reviewer.md +146 -0
  159. package/src/skills/simplify/README.md +19 -0
  160. package/src/skills/simplify/SKILL.md +138 -0
  161. package/src/skills/simplify/codemap.md +36 -0
  162. package/src/skills/subagent-driven-development/SKILL.md +288 -0
  163. package/src/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
  164. package/src/skills/subagent-driven-development/implementer-prompt.md +113 -0
  165. package/src/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  166. package/src/skills/systematic-debugging/CREATION-LOG.md +119 -0
  167. package/src/skills/systematic-debugging/SKILL.md +308 -0
  168. package/src/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  169. package/src/skills/systematic-debugging/condition-based-waiting.md +115 -0
  170. package/src/skills/systematic-debugging/defense-in-depth.md +122 -0
  171. package/src/skills/systematic-debugging/find-polluter.sh +63 -0
  172. package/src/skills/systematic-debugging/root-cause-tracing.md +169 -0
  173. package/src/skills/systematic-debugging/test-academic.md +14 -0
  174. package/src/skills/systematic-debugging/test-pressure-1.md +58 -0
  175. package/src/skills/systematic-debugging/test-pressure-2.md +68 -0
  176. package/src/skills/systematic-debugging/test-pressure-3.md +69 -0
  177. package/src/skills/test-driven-development/SKILL.md +383 -0
  178. package/src/skills/test-driven-development/testing-anti-patterns.md +299 -0
  179. package/src/skills/using-git-worktrees/SKILL.md +226 -0
  180. package/src/skills/verification-before-completion/SKILL.md +147 -0
  181. package/src/skills/writing-plans/SKILL.md +165 -0
  182. package/src/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  183. package/src/skills/writing-skills/SKILL.md +666 -0
  184. package/src/skills/writing-skills/anthropic-best-practices.md +1150 -0
  185. package/src/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  186. package/src/skills/writing-skills/graphviz-conventions.dot +172 -0
  187. package/src/skills/writing-skills/persuasion-principles.md +187 -0
  188. package/src/skills/writing-skills/render-graphs.js +168 -0
  189. package/src/skills/writing-skills/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,1932 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ import { createRequire } from "node:module";
4
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
+
6
+ // src/cli/doctor.ts
7
+ import * as fs2 from "node:fs";
8
+ import { z as z3 } from "zod";
9
+
10
+ // src/config/loader.ts
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+
14
+ // src/cli/config-io.ts
15
+ import {
16
+ copyFileSync as copyFileSync2,
17
+ existsSync as existsSync3,
18
+ readFileSync,
19
+ renameSync,
20
+ statSync as statSync2,
21
+ writeFileSync
22
+ } from "node:fs";
23
+ import { dirname as dirname3, join as join3 } from "node:path";
24
+
25
+ // src/cli/paths.ts
26
+ import { existsSync, mkdirSync } from "node:fs";
27
+ import { homedir } from "node:os";
28
+ import { dirname, join } from "node:path";
29
+ function getDefaultOpenCodeConfigDir() {
30
+ const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
31
+ return join(userConfigDir, "opencode");
32
+ }
33
+ function getCustomOpenCodeConfigDir() {
34
+ const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
35
+ return configDir || undefined;
36
+ }
37
+ function getCustomTuiConfigPath() {
38
+ const configPath = process.env.OPENCODE_TUI_CONFIG?.trim();
39
+ return configPath || undefined;
40
+ }
41
+ function getConfigDir() {
42
+ const customConfigDir = getCustomOpenCodeConfigDir();
43
+ if (customConfigDir) {
44
+ return customConfigDir;
45
+ }
46
+ return getDefaultOpenCodeConfigDir();
47
+ }
48
+ function getConfigSearchDirs() {
49
+ const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
50
+ return dirs.filter((dir, index) => {
51
+ return Boolean(dir) && dirs.indexOf(dir) === index;
52
+ });
53
+ }
54
+ function getOpenCodeConfigPaths() {
55
+ const configDir = getDefaultOpenCodeConfigDir();
56
+ return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
57
+ }
58
+ function getConfigJson() {
59
+ return getOpenCodeConfigPaths()[0];
60
+ }
61
+ function getConfigJsonc() {
62
+ return getOpenCodeConfigPaths()[1];
63
+ }
64
+ function getLiteConfig() {
65
+ return join(getConfigDir(), "agent-forge.json");
66
+ }
67
+ function getLiteConfigJsonc() {
68
+ return join(getConfigDir(), "agent-forge.jsonc");
69
+ }
70
+ function getTuiConfig() {
71
+ const customConfigPath = getCustomTuiConfigPath();
72
+ if (customConfigPath)
73
+ return customConfigPath;
74
+ return join(getConfigDir(), "tui.json");
75
+ }
76
+ function getTuiConfigJsonc() {
77
+ return join(getConfigDir(), "tui.jsonc");
78
+ }
79
+ function getExistingLiteConfigPath() {
80
+ const jsonPath = getLiteConfig();
81
+ if (existsSync(jsonPath))
82
+ return jsonPath;
83
+ const jsoncPath = getLiteConfigJsonc();
84
+ if (existsSync(jsoncPath))
85
+ return jsoncPath;
86
+ return jsonPath;
87
+ }
88
+ function getExistingTuiConfigPath() {
89
+ const customConfigPath = getCustomTuiConfigPath();
90
+ if (customConfigPath)
91
+ return customConfigPath;
92
+ const jsonPath = join(getConfigDir(), "tui.json");
93
+ if (existsSync(jsonPath))
94
+ return jsonPath;
95
+ const jsoncPath = getTuiConfigJsonc();
96
+ if (existsSync(jsoncPath))
97
+ return jsoncPath;
98
+ return jsonPath;
99
+ }
100
+ function getExistingConfigPath() {
101
+ const jsonPath = getConfigJson();
102
+ if (existsSync(jsonPath))
103
+ return jsonPath;
104
+ const jsoncPath = getConfigJsonc();
105
+ if (existsSync(jsoncPath))
106
+ return jsoncPath;
107
+ return jsonPath;
108
+ }
109
+ function ensureConfigDir() {
110
+ const configDir = getConfigDir();
111
+ if (!existsSync(configDir)) {
112
+ mkdirSync(configDir, { recursive: true });
113
+ }
114
+ }
115
+ function ensureTuiConfigDir() {
116
+ const configDir = dirname(getTuiConfig());
117
+ if (!existsSync(configDir)) {
118
+ mkdirSync(configDir, { recursive: true });
119
+ }
120
+ }
121
+ function ensureOpenCodeConfigDir() {
122
+ const configDir = dirname(getConfigJson());
123
+ if (!existsSync(configDir)) {
124
+ mkdirSync(configDir, { recursive: true });
125
+ }
126
+ }
127
+
128
+ // src/config/constants.ts
129
+ var SUBAGENT_NAMES = [
130
+ "researcher",
131
+ "reviewer",
132
+ "implementer",
133
+ "council",
134
+ "councillor"
135
+ ];
136
+ var ORCHESTRATOR_NAME = "orchestrator";
137
+ var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
138
+ var PROTECTED_AGENTS = new Set([
139
+ "orchestrator",
140
+ "researcher",
141
+ "reviewer",
142
+ "implementer",
143
+ "councillor"
144
+ ]);
145
+ var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
146
+ var MAX_POLL_TIME_MS = 5 * 60 * 1000;
147
+ // src/config/council-schema.ts
148
+ import { z } from "zod";
149
+ var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.4-mini")');
150
+ var CouncillorConfigSchema = z.object({
151
+ model: ModelIdSchema.describe('Model ID in provider/model format (e.g. "openai/gpt-5.4-mini")'),
152
+ variant: z.string().optional(),
153
+ prompt: z.string().optional().describe("Optional role/guidance injected into the councillor user prompt")
154
+ });
155
+ var CouncilPresetSchema = z.record(z.string(), z.record(z.string(), z.unknown())).transform((entries, ctx) => {
156
+ const councillors = {};
157
+ for (const [key, raw] of Object.entries(entries)) {
158
+ if (key === "master")
159
+ continue;
160
+ if (key === "councillors" && typeof raw === "object" && raw !== null) {
161
+ for (const [innerKey, innerRaw] of Object.entries(raw)) {
162
+ const innerParsed = CouncillorConfigSchema.safeParse(innerRaw);
163
+ if (!innerParsed.success) {
164
+ ctx.addIssue({
165
+ code: z.ZodIssueCode.custom,
166
+ message: `Invalid councillor "${innerKey}" (nested under legacy "councillors" key): ${innerParsed.error.issues.map((i) => i.message).join(", ")}`
167
+ });
168
+ return z.NEVER;
169
+ }
170
+ councillors[innerKey] = innerParsed.data;
171
+ }
172
+ continue;
173
+ }
174
+ const parsed = CouncillorConfigSchema.safeParse(raw);
175
+ if (!parsed.success) {
176
+ ctx.addIssue({
177
+ code: z.ZodIssueCode.custom,
178
+ message: `Invalid councillor "${key}": ${parsed.error.issues.map((i) => i.message).join(", ")}`
179
+ });
180
+ return z.NEVER;
181
+ }
182
+ councillors[key] = parsed.data;
183
+ }
184
+ return councillors;
185
+ });
186
+ 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.');
187
+ var CouncilConfigSchema = z.object({
188
+ presets: z.record(z.string(), CouncilPresetSchema),
189
+ timeout: z.number().min(0).default(180000),
190
+ default_preset: z.string().default("default"),
191
+ 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).'),
192
+ 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."),
193
+ master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly."),
194
+ master_timeout: z.unknown().optional().describe('DEPRECATED — ignored. Use "timeout" instead.'),
195
+ master_fallback: z.unknown().optional().describe("DEPRECATED — ignored. No separate master session.")
196
+ }).transform((data) => {
197
+ const deprecated = [];
198
+ if (data.master !== undefined)
199
+ deprecated.push("master");
200
+ if (data.master_timeout !== undefined)
201
+ deprecated.push("master_timeout");
202
+ if (data.master_fallback !== undefined)
203
+ deprecated.push("master_fallback");
204
+ const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
205
+ return {
206
+ presets: data.presets,
207
+ timeout: data.timeout,
208
+ default_preset: data.default_preset,
209
+ councillor_execution_mode: data.councillor_execution_mode,
210
+ councillor_retries: data.councillor_retries,
211
+ _deprecated: deprecated.length > 0 ? deprecated : undefined,
212
+ _legacyMasterModel: legacyMasterModel
213
+ };
214
+ });
215
+ // src/config/schema.ts
216
+ import { z as z2 } from "zod";
217
+ var ProviderModelIdSchema = z2.string().regex(/^[^/\s]+\/[^\s]+$/, "Expected provider/model format (provider/.../model)");
218
+ var ManualAgentPlanSchema = z2.object({
219
+ primary: ProviderModelIdSchema,
220
+ fallback1: ProviderModelIdSchema,
221
+ fallback2: ProviderModelIdSchema,
222
+ fallback3: ProviderModelIdSchema
223
+ }).superRefine((value, ctx) => {
224
+ const unique = new Set([
225
+ value.primary,
226
+ value.fallback1,
227
+ value.fallback2,
228
+ value.fallback3
229
+ ]);
230
+ if (unique.size !== 4) {
231
+ ctx.addIssue({
232
+ code: z2.ZodIssueCode.custom,
233
+ message: "primary and fallbacks must be unique per agent"
234
+ });
235
+ }
236
+ });
237
+ var ManualPlanSchema = z2.object({
238
+ orchestrator: ManualAgentPlanSchema,
239
+ researcher: ManualAgentPlanSchema,
240
+ reviewer: ManualAgentPlanSchema,
241
+ implementer: ManualAgentPlanSchema
242
+ }).strict();
243
+ var AgentModelChainSchema = z2.array(z2.string()).min(1);
244
+ var FallbackChainsSchema = z2.object({
245
+ orchestrator: AgentModelChainSchema.optional(),
246
+ researcher: AgentModelChainSchema.optional(),
247
+ reviewer: AgentModelChainSchema.optional(),
248
+ implementer: AgentModelChainSchema.optional()
249
+ }).catchall(AgentModelChainSchema);
250
+ var AgentOverrideConfigSchema = z2.object({
251
+ model: z2.union([
252
+ z2.string(),
253
+ z2.array(z2.union([
254
+ z2.string(),
255
+ z2.object({
256
+ id: z2.string(),
257
+ variant: z2.string().optional()
258
+ })
259
+ ])).min(1)
260
+ ]).optional(),
261
+ temperature: z2.number().min(0).max(2).optional(),
262
+ variant: z2.string().optional().catch(undefined),
263
+ skills: z2.array(z2.string()).optional(),
264
+ mcps: z2.array(z2.string()).optional(),
265
+ prompt: z2.string().min(1).optional(),
266
+ orchestratorPrompt: z2.string().min(1).optional(),
267
+ options: z2.record(z2.string(), z2.unknown()).optional(),
268
+ displayName: z2.string().min(1).optional()
269
+ }).strict();
270
+ var MultiplexerTypeSchema = z2.enum(["auto", "tmux", "zellij", "none"]);
271
+ var MultiplexerLayoutSchema = z2.enum([
272
+ "main-horizontal",
273
+ "main-vertical",
274
+ "tiled",
275
+ "even-horizontal",
276
+ "even-vertical"
277
+ ]);
278
+ var TmuxLayoutSchema = MultiplexerLayoutSchema;
279
+ var MultiplexerConfigSchema = z2.object({
280
+ type: MultiplexerTypeSchema.default("none"),
281
+ layout: MultiplexerLayoutSchema.default("main-vertical"),
282
+ main_pane_size: z2.number().min(20).max(80).default(60)
283
+ });
284
+ var TmuxConfigSchema = z2.object({
285
+ enabled: z2.boolean().default(false),
286
+ layout: TmuxLayoutSchema.default("main-vertical"),
287
+ main_pane_size: z2.number().min(20).max(80).default(60)
288
+ });
289
+ var PresetSchema = z2.record(z2.string(), AgentOverrideConfigSchema);
290
+ var WebsearchConfigSchema = z2.object({
291
+ provider: z2.enum(["exa", "tavily"]).default("exa")
292
+ });
293
+ var McpNameSchema = z2.enum(["websearch", "context7", "grep_app"]);
294
+ var InterviewConfigSchema = z2.object({
295
+ maxQuestions: z2.number().int().min(1).max(10).default(2),
296
+ outputFolder: z2.string().min(1).default("interview"),
297
+ autoOpenBrowser: z2.boolean().default(true).describe("Automatically open the interview UI in your default browser during interactive runs. Disabled automatically in tests and CI."),
298
+ port: z2.number().int().min(0).max(65535).default(0),
299
+ dashboard: z2.boolean().default(false)
300
+ });
301
+ var SessionManagerConfigSchema = z2.object({
302
+ maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2),
303
+ readContextMinLines: z2.number().int().min(0).max(1000).default(10),
304
+ readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
305
+ });
306
+ var DivoomConfigSchema = z2.object({
307
+ enabled: z2.boolean().default(false),
308
+ python: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python"),
309
+ script: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py"),
310
+ size: z2.number().int().min(1).max(1024).default(128),
311
+ fps: z2.number().int().min(1).max(60).default(8),
312
+ speed: z2.number().int().min(1).max(1e4).default(125),
313
+ maxFrames: z2.number().int().min(1).max(500).default(24),
314
+ posterizeBits: z2.number().int().min(1).max(8).default(3),
315
+ gifs: z2.record(z2.string(), z2.string().min(1)).optional()
316
+ });
317
+ var TodoContinuationConfigSchema = z2.object({
318
+ maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
319
+ cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
320
+ autoEnable: z2.boolean().default(false).describe("Automatically enable auto-continue when the orchestrator session has enough todos"),
321
+ autoEnableThreshold: z2.number().int().min(1).max(50).default(4).describe("Number of todos that triggers auto-enable (only used when autoEnable is true)")
322
+ });
323
+ var FailoverConfigSchema = z2.object({
324
+ enabled: z2.boolean().default(true),
325
+ timeoutMs: z2.number().min(0).default(15000),
326
+ retryDelayMs: z2.number().min(0).default(500),
327
+ chains: FallbackChainsSchema.default({}),
328
+ 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.")
329
+ });
330
+ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
331
+ for (const [name, override] of Object.entries(overrides)) {
332
+ const isBuiltIn = ALL_AGENT_NAMES.includes(name);
333
+ if (!isBuiltIn) {
334
+ continue;
335
+ }
336
+ if (override.prompt !== undefined) {
337
+ ctx.addIssue({
338
+ code: z2.ZodIssueCode.custom,
339
+ path: [...pathPrefix, name, "prompt"],
340
+ message: "prompt is only supported for custom agents"
341
+ });
342
+ }
343
+ if (override.orchestratorPrompt !== undefined) {
344
+ ctx.addIssue({
345
+ code: z2.ZodIssueCode.custom,
346
+ path: [...pathPrefix, name, "orchestratorPrompt"],
347
+ message: "orchestratorPrompt is only supported for custom agents"
348
+ });
349
+ }
350
+ }
351
+ }
352
+ var PluginConfigSchema = z2.object({
353
+ preset: z2.string().optional(),
354
+ setDefaultAgent: z2.boolean().optional(),
355
+ scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
356
+ balanceProviderUsage: z2.boolean().optional(),
357
+ autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
358
+ manualPlan: ManualPlanSchema.optional(),
359
+ presets: z2.record(z2.string(), PresetSchema).optional(),
360
+ agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
361
+ disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator, researcher, reviewer, implementer and council internal agents (councillor) cannot be disabled. " + "By default, only council is disabled."),
362
+ disabled_mcps: z2.array(z2.string()).optional(),
363
+ multiplexer: MultiplexerConfigSchema.optional(),
364
+ tmux: TmuxConfigSchema.optional(),
365
+ websearch: WebsearchConfigSchema.optional(),
366
+ interview: InterviewConfigSchema.optional(),
367
+ sessionManager: SessionManagerConfigSchema.optional(),
368
+ divoom: DivoomConfigSchema.optional(),
369
+ todoContinuation: TodoContinuationConfigSchema.optional(),
370
+ fallback: FailoverConfigSchema.optional(),
371
+ council: CouncilConfigSchema.optional()
372
+ }).superRefine((value, ctx) => {
373
+ if (value.agents) {
374
+ validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
375
+ }
376
+ if (value.presets) {
377
+ for (const [presetName, preset] of Object.entries(value.presets)) {
378
+ validateCustomOnlyPromptFields(preset, ctx, ["presets", presetName]);
379
+ }
380
+ }
381
+ });
382
+ // src/config/agent-mcps.ts
383
+ var DEFAULT_AGENT_MCPS = {
384
+ orchestrator: ["*", "!context7"],
385
+ researcher: ["websearch", "context7", "grep_app"],
386
+ reviewer: [],
387
+ implementer: [],
388
+ council: [],
389
+ councillor: []
390
+ };
391
+
392
+ // src/cli/custom-skills.ts
393
+ import {
394
+ copyFileSync,
395
+ existsSync as existsSync2,
396
+ mkdirSync as mkdirSync2,
397
+ readdirSync,
398
+ statSync
399
+ } from "node:fs";
400
+ import { dirname as dirname2, join as join2 } from "node:path";
401
+ import { fileURLToPath } from "node:url";
402
+ var CUSTOM_SKILLS = [
403
+ {
404
+ name: "simplify",
405
+ description: "Code simplification and readability-focused refactoring",
406
+ allowedAgents: ["reviewer"],
407
+ sourcePath: "src/skills/simplify"
408
+ },
409
+ {
410
+ name: "codemap",
411
+ description: "Repository understanding and hierarchical codemap generation",
412
+ allowedAgents: ["orchestrator"],
413
+ sourcePath: "src/skills/codemap"
414
+ },
415
+ {
416
+ name: "brainstorming",
417
+ description: "Turn ideas into designs through collaborative dialogue before implementation",
418
+ allowedAgents: ["orchestrator"],
419
+ sourcePath: "src/skills/brainstorming"
420
+ },
421
+ {
422
+ name: "writing-plans",
423
+ description: "Create comprehensive implementation plans from specs before touching code",
424
+ allowedAgents: ["orchestrator"],
425
+ sourcePath: "src/skills/writing-plans"
426
+ },
427
+ {
428
+ name: "executing-plans",
429
+ description: "Execute written implementation plans with review checkpoints",
430
+ allowedAgents: ["orchestrator"],
431
+ sourcePath: "src/skills/executing-plans"
432
+ },
433
+ {
434
+ name: "subagent-driven-development",
435
+ description: "Execute plans by dispatching fresh subagent per task with two-stage review",
436
+ allowedAgents: ["orchestrator"],
437
+ sourcePath: "src/skills/subagent-driven-development"
438
+ },
439
+ {
440
+ name: "dispatching-parallel-agents",
441
+ description: "Dispatch parallel agents for independent tasks without shared state",
442
+ allowedAgents: ["orchestrator"],
443
+ sourcePath: "src/skills/dispatching-parallel-agents"
444
+ },
445
+ {
446
+ name: "using-git-worktrees",
447
+ description: "Create isolated git worktrees before executing implementation plans",
448
+ allowedAgents: ["orchestrator"],
449
+ sourcePath: "src/skills/using-git-worktrees"
450
+ },
451
+ {
452
+ name: "finishing-a-development-branch",
453
+ description: "Guide completion of development work with structured merge/PR options",
454
+ allowedAgents: ["orchestrator"],
455
+ sourcePath: "src/skills/finishing-a-development-branch"
456
+ },
457
+ {
458
+ name: "requesting-code-review",
459
+ description: "Dispatch code reviewer subagent to catch issues before merging",
460
+ allowedAgents: ["orchestrator"],
461
+ sourcePath: "src/skills/requesting-code-review"
462
+ },
463
+ {
464
+ name: "test-driven-development",
465
+ description: "Write failing tests first, then minimal code to pass, before any implementation",
466
+ allowedAgents: ["implementer"],
467
+ sourcePath: "src/skills/test-driven-development"
468
+ },
469
+ {
470
+ name: "verification-before-completion",
471
+ description: "Run verification commands and confirm output before claiming work is complete",
472
+ allowedAgents: ["implementer"],
473
+ sourcePath: "src/skills/verification-before-completion"
474
+ },
475
+ {
476
+ name: "systematic-debugging",
477
+ description: "Find root cause before attempting fixes for any bug or test failure",
478
+ allowedAgents: ["reviewer"],
479
+ sourcePath: "src/skills/systematic-debugging"
480
+ },
481
+ {
482
+ name: "receiving-code-review",
483
+ description: "Evaluate code review feedback with technical rigor before implementing suggestions",
484
+ allowedAgents: ["reviewer"],
485
+ sourcePath: "src/skills/receiving-code-review"
486
+ },
487
+ {
488
+ name: "writing-skills",
489
+ description: "Create and edit skills using TDD methodology for process documentation",
490
+ allowedAgents: ["orchestrator"],
491
+ sourcePath: "src/skills/writing-skills"
492
+ }
493
+ ];
494
+ function getCustomSkillsDir() {
495
+ return join2(getConfigDir(), "skills");
496
+ }
497
+ function copyDirRecursive(src, dest) {
498
+ if (!existsSync2(dest)) {
499
+ mkdirSync2(dest, { recursive: true });
500
+ }
501
+ const entries = readdirSync(src);
502
+ for (const entry of entries) {
503
+ const srcPath = join2(src, entry);
504
+ const destPath = join2(dest, entry);
505
+ const stat = statSync(srcPath);
506
+ if (stat.isDirectory()) {
507
+ copyDirRecursive(srcPath, destPath);
508
+ } else {
509
+ const destDir = dirname2(destPath);
510
+ if (!existsSync2(destDir)) {
511
+ mkdirSync2(destDir, { recursive: true });
512
+ }
513
+ copyFileSync(srcPath, destPath);
514
+ }
515
+ }
516
+ }
517
+ function installCustomSkill(skill) {
518
+ try {
519
+ const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
520
+ const sourcePath = join2(packageRoot, skill.sourcePath);
521
+ const targetPath = join2(getCustomSkillsDir(), skill.name);
522
+ if (!existsSync2(sourcePath)) {
523
+ console.error(`Custom skill source not found: ${sourcePath}`);
524
+ return false;
525
+ }
526
+ copyDirRecursive(sourcePath, targetPath);
527
+ return true;
528
+ } catch (error) {
529
+ console.error(`Failed to install custom skill: ${skill.name}`, error);
530
+ return false;
531
+ }
532
+ }
533
+
534
+ // src/cli/skills.ts
535
+ import { spawnSync } from "node:child_process";
536
+ var RECOMMENDED_SKILLS = [
537
+ {
538
+ name: "agent-browser",
539
+ repo: "https://github.com/vercel-labs/agent-browser",
540
+ skillName: "agent-browser",
541
+ allowedAgents: ["implementer"],
542
+ description: "High-performance browser automation",
543
+ postInstallCommands: [
544
+ "npm install -g agent-browser",
545
+ "agent-browser install"
546
+ ]
547
+ }
548
+ ];
549
+ function installSkill(skill) {
550
+ const args = [
551
+ "skills",
552
+ "add",
553
+ skill.repo,
554
+ "--skill",
555
+ skill.skillName,
556
+ "-a",
557
+ "opencode",
558
+ "-y",
559
+ "--global"
560
+ ];
561
+ try {
562
+ const result = spawnSync("npx", args, { stdio: "inherit" });
563
+ if (result.status !== 0) {
564
+ return false;
565
+ }
566
+ if (skill.postInstallCommands && skill.postInstallCommands.length > 0) {
567
+ console.log(`Running post-install commands for ${skill.name}...`);
568
+ for (const cmd of skill.postInstallCommands) {
569
+ console.log(`> ${cmd}`);
570
+ const [command, ...cmdArgs] = cmd.split(" ");
571
+ const cmdResult = spawnSync(command, cmdArgs, { stdio: "inherit" });
572
+ if (cmdResult.status !== 0) {
573
+ console.warn(`Post-install command failed: ${cmd}`);
574
+ }
575
+ }
576
+ }
577
+ return true;
578
+ } catch (error) {
579
+ console.error(`Failed to install skill: ${skill.name}`, error);
580
+ return false;
581
+ }
582
+ }
583
+
584
+ // src/cli/providers.ts
585
+ var SCHEMA_URL = "https://cnb.cool/zcyoop/ai-assistant/agent-forge/-/git/raw/master/agent-forge.schema.json";
586
+ var GENERATED_PRESETS = ["openai", "opencode-go"];
587
+ var MODEL_MAPPINGS = {
588
+ openai: {
589
+ orchestrator: { model: "openai/gpt-5.5" },
590
+ reviewer: { model: "openai/gpt-5.5", variant: "high" },
591
+ researcher: { model: "openai/gpt-5.4-mini", variant: "low" },
592
+ implementer: { model: "openai/gpt-5.4-mini", variant: "low" }
593
+ },
594
+ kimi: {
595
+ orchestrator: { model: "kimi-for-coding/k2p5" },
596
+ reviewer: { model: "kimi-for-coding/k2p5", variant: "high" },
597
+ researcher: { model: "kimi-for-coding/k2p5", variant: "low" },
598
+ implementer: { model: "kimi-for-coding/k2p5", variant: "low" }
599
+ },
600
+ copilot: {
601
+ orchestrator: { model: "github-copilot/claude-opus-4.6" },
602
+ reviewer: { model: "github-copilot/claude-opus-4.6", variant: "high" },
603
+ researcher: { model: "github-copilot/grok-code-fast-1", variant: "low" },
604
+ implementer: { model: "github-copilot/claude-sonnet-4.6", variant: "low" }
605
+ },
606
+ "zai-plan": {
607
+ orchestrator: { model: "zai-coding-plan/glm-5" },
608
+ reviewer: { model: "zai-coding-plan/glm-5", variant: "high" },
609
+ researcher: { model: "zai-coding-plan/glm-5", variant: "low" },
610
+ implementer: { model: "zai-coding-plan/glm-5", variant: "low" }
611
+ },
612
+ "opencode-go": {
613
+ orchestrator: { model: "opencode-go/glm-5.1" },
614
+ reviewer: { model: "opencode-go/deepseek-v4-pro", variant: "max" },
615
+ council: { model: "opencode-go/deepseek-v4-pro", variant: "high" },
616
+ researcher: { model: "opencode-go/minimax-m2.7" },
617
+ implementer: { model: "opencode-go/deepseek-v4-flash", variant: "high" }
618
+ }
619
+ };
620
+ function isGeneratedPresetName(value) {
621
+ return GENERATED_PRESETS.includes(value);
622
+ }
623
+ function getGeneratedPresetNames() {
624
+ return [...GENERATED_PRESETS];
625
+ }
626
+ function generateLiteConfig(installConfig) {
627
+ const preset = installConfig.preset ?? "openai";
628
+ if (!isGeneratedPresetName(preset)) {
629
+ throw new Error(`Unsupported preset "${preset}". Available generated presets: ${getGeneratedPresetNames().join(", ")}`);
630
+ }
631
+ const config = {
632
+ $schema: SCHEMA_URL,
633
+ preset,
634
+ presets: {}
635
+ };
636
+ if (preset === "opencode-go") {
637
+ config.disabled_agents = [];
638
+ }
639
+ const createAgentConfig = (agentName, modelInfo) => {
640
+ const isOrchestrator = agentName === "orchestrator";
641
+ const skills = isOrchestrator ? ["*"] : [
642
+ ...RECOMMENDED_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.skillName),
643
+ ...CUSTOM_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.name)
644
+ ];
645
+ if (agentName === "implementer" && !skills.includes("agent-browser")) {
646
+ skills.push("agent-browser");
647
+ }
648
+ return {
649
+ model: modelInfo.model,
650
+ variant: modelInfo.variant,
651
+ skills,
652
+ mcps: DEFAULT_AGENT_MCPS[agentName] ?? []
653
+ };
654
+ };
655
+ const buildPreset = (mappingName) => {
656
+ const mapping = MODEL_MAPPINGS[mappingName];
657
+ return Object.fromEntries(Object.entries(mapping).map(([agentName, modelInfo]) => [
658
+ agentName,
659
+ createAgentConfig(agentName, modelInfo)
660
+ ]));
661
+ };
662
+ const presets = config.presets;
663
+ for (const presetName of GENERATED_PRESETS) {
664
+ presets[presetName] = buildPreset(presetName);
665
+ }
666
+ if (installConfig.hasTmux) {
667
+ config.tmux = {
668
+ enabled: true,
669
+ layout: "main-vertical",
670
+ main_pane_size: 60
671
+ };
672
+ }
673
+ return config;
674
+ }
675
+
676
+ // src/cli/config-io.ts
677
+ var PACKAGE_NAME = "@zcy2nn/agent-forge";
678
+ function isString(value) {
679
+ return typeof value === "string";
680
+ }
681
+ function getPlugins(config) {
682
+ return Array.isArray(config.plugin) ? config.plugin : [];
683
+ }
684
+ function getPluginEntries(config) {
685
+ return getPlugins(config).filter(isString);
686
+ }
687
+ function getPluginSpec(entry) {
688
+ if (isString(entry))
689
+ return entry;
690
+ if (!Array.isArray(entry))
691
+ return;
692
+ const spec = entry[0];
693
+ return isString(spec) ? spec : undefined;
694
+ }
695
+ function normalizePathForMatch(path) {
696
+ return path.replaceAll("\\", "/");
697
+ }
698
+ function findPackageRoot(startPath) {
699
+ let currentPath = dirname3(startPath);
700
+ while (true) {
701
+ const packageJsonPath = join3(currentPath, "package.json");
702
+ if (existsSync3(packageJsonPath)) {
703
+ try {
704
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
705
+ if (packageJson.name === PACKAGE_NAME) {
706
+ return currentPath;
707
+ }
708
+ } catch {}
709
+ }
710
+ const parentPath = dirname3(currentPath);
711
+ if (parentPath === currentPath) {
712
+ return null;
713
+ }
714
+ currentPath = parentPath;
715
+ }
716
+ }
717
+ function isPackageManagerInstall(path) {
718
+ const normalizedPath = normalizePathForMatch(path);
719
+ return normalizedPath.includes(`/node_modules/${PACKAGE_NAME}`);
720
+ }
721
+ function isLocalPackageRootEntry(entry) {
722
+ if (!entry || entry.startsWith("file://")) {
723
+ return false;
724
+ }
725
+ const packageJsonPath = join3(entry, "package.json");
726
+ if (!existsSync3(packageJsonPath)) {
727
+ return false;
728
+ }
729
+ try {
730
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
731
+ return packageJson.name === PACKAGE_NAME;
732
+ } catch {
733
+ return false;
734
+ }
735
+ }
736
+ function isPluginEntry(entry) {
737
+ return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.startsWith("file://") && entry.includes(PACKAGE_NAME) || isLocalPackageRootEntry(entry);
738
+ }
739
+ function isMatchingPluginEntry(entry) {
740
+ const spec = getPluginSpec(entry);
741
+ return spec ? isPluginEntry(spec) : false;
742
+ }
743
+ function getPluginEntry() {
744
+ const cliEntryPath = process.argv[1];
745
+ if (!cliEntryPath) {
746
+ return PACKAGE_NAME;
747
+ }
748
+ try {
749
+ const packageRoot = findPackageRoot(cliEntryPath);
750
+ if (!packageRoot || isPackageManagerInstall(packageRoot)) {
751
+ return PACKAGE_NAME;
752
+ }
753
+ return packageRoot;
754
+ } catch {
755
+ return PACKAGE_NAME;
756
+ }
757
+ }
758
+ function stripJsonComments(json) {
759
+ const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
760
+ const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
761
+ return json.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
762
+ }
763
+ function parseConfigFile(path) {
764
+ try {
765
+ if (!existsSync3(path))
766
+ return { config: null };
767
+ const stat = statSync2(path);
768
+ if (stat.size === 0)
769
+ return { config: null };
770
+ const content = readFileSync(path, "utf-8");
771
+ if (content.trim().length === 0)
772
+ return { config: null };
773
+ return { config: JSON.parse(stripJsonComments(content)) };
774
+ } catch (err) {
775
+ return { config: null, error: String(err) };
776
+ }
777
+ }
778
+ function parseConfig(path) {
779
+ const result = parseConfigFile(path);
780
+ if (result.config || result.error)
781
+ return result;
782
+ if (path.endsWith(".json")) {
783
+ const jsoncPath = path.replace(/\.json$/, ".jsonc");
784
+ return parseConfigFile(jsoncPath);
785
+ }
786
+ return { config: null };
787
+ }
788
+ function writeConfig(configPath, config) {
789
+ if (configPath.endsWith(".jsonc")) {
790
+ console.warn("[config-manager] Writing to .jsonc file - comments will not be preserved");
791
+ }
792
+ const tmpPath = `${configPath}.tmp`;
793
+ const bakPath = `${configPath}.bak`;
794
+ const content = `${JSON.stringify(config, null, 2)}
795
+ `;
796
+ if (existsSync3(configPath)) {
797
+ copyFileSync2(configPath, bakPath);
798
+ }
799
+ writeFileSync(tmpPath, content);
800
+ renameSync(tmpPath, configPath);
801
+ }
802
+ async function addPluginToOpenCodeConfig() {
803
+ const configPath = getExistingConfigPath();
804
+ try {
805
+ ensureOpenCodeConfigDir();
806
+ } catch (err) {
807
+ return {
808
+ success: false,
809
+ configPath,
810
+ error: `Failed to create config directory: ${err}`
811
+ };
812
+ }
813
+ try {
814
+ const { config: parsedConfig, error } = parseConfig(configPath);
815
+ if (error) {
816
+ return {
817
+ success: false,
818
+ configPath,
819
+ error: `Failed to parse config: ${error}`
820
+ };
821
+ }
822
+ const config = parsedConfig ?? {};
823
+ const plugins = getPlugins(config);
824
+ const pluginEntry = getPluginEntry();
825
+ const filteredPlugins = plugins.filter((plugin) => !isMatchingPluginEntry(plugin));
826
+ filteredPlugins.push(pluginEntry);
827
+ config.plugin = filteredPlugins;
828
+ writeConfig(configPath, config);
829
+ return { success: true, configPath };
830
+ } catch (err) {
831
+ return {
832
+ success: false,
833
+ configPath,
834
+ error: `Failed to update opencode config: ${err}`
835
+ };
836
+ }
837
+ }
838
+ async function addPluginToOpenCodeTuiConfig() {
839
+ const configPath = getExistingTuiConfigPath();
840
+ try {
841
+ ensureTuiConfigDir();
842
+ } catch (err) {
843
+ return {
844
+ success: false,
845
+ configPath,
846
+ error: `Failed to create config directory: ${err}`
847
+ };
848
+ }
849
+ try {
850
+ const { config: parsedConfig, error } = parseConfig(configPath);
851
+ if (error) {
852
+ return {
853
+ success: false,
854
+ configPath,
855
+ error: `Failed to parse TUI config: ${error}`
856
+ };
857
+ }
858
+ const config = parsedConfig ?? {};
859
+ const plugins = getPlugins(config);
860
+ const pluginEntry = getPluginEntry();
861
+ const filteredPlugins = plugins.filter((plugin) => !isMatchingPluginEntry(plugin));
862
+ filteredPlugins.push(pluginEntry);
863
+ config.plugin = filteredPlugins;
864
+ writeConfig(configPath, config);
865
+ return { success: true, configPath };
866
+ } catch (err) {
867
+ return {
868
+ success: false,
869
+ configPath,
870
+ error: `Failed to update opencode TUI config: ${err}`
871
+ };
872
+ }
873
+ }
874
+ function writeLiteConfig(installConfig, targetPath) {
875
+ const configPath = targetPath ?? getLiteConfig();
876
+ try {
877
+ ensureConfigDir();
878
+ const config = generateLiteConfig(installConfig);
879
+ const tmpPath = `${configPath}.tmp`;
880
+ const bakPath = `${configPath}.bak`;
881
+ const content = `${JSON.stringify(config, null, 2)}
882
+ `;
883
+ if (existsSync3(configPath)) {
884
+ copyFileSync2(configPath, bakPath);
885
+ }
886
+ writeFileSync(tmpPath, content);
887
+ renameSync(tmpPath, configPath);
888
+ return { success: true, configPath };
889
+ } catch (err) {
890
+ return {
891
+ success: false,
892
+ configPath,
893
+ error: `Failed to write lite config: ${err}`
894
+ };
895
+ }
896
+ }
897
+ function disableDefaultAgents() {
898
+ const configPath = getExistingConfigPath();
899
+ try {
900
+ ensureOpenCodeConfigDir();
901
+ const { config: parsedConfig, error } = parseConfig(configPath);
902
+ if (error) {
903
+ return {
904
+ success: false,
905
+ configPath,
906
+ error: `Failed to parse config: ${error}`
907
+ };
908
+ }
909
+ const config = parsedConfig ?? {};
910
+ const agent = config.agent ?? {};
911
+ agent.explore = { disable: true };
912
+ agent.general = { disable: true };
913
+ config.agent = agent;
914
+ writeConfig(configPath, config);
915
+ return { success: true, configPath };
916
+ } catch (err) {
917
+ return {
918
+ success: false,
919
+ configPath,
920
+ error: `Failed to disable default agents: ${err}`
921
+ };
922
+ }
923
+ }
924
+ function enableLspByDefault() {
925
+ const configPath = getExistingConfigPath();
926
+ try {
927
+ ensureOpenCodeConfigDir();
928
+ const { config: parsedConfig, error } = parseConfig(configPath);
929
+ if (error) {
930
+ return {
931
+ success: false,
932
+ configPath,
933
+ error: `Failed to parse config: ${error}`
934
+ };
935
+ }
936
+ const config = parsedConfig ?? {};
937
+ if (config.lsp === undefined) {
938
+ config.lsp = true;
939
+ writeConfig(configPath, config);
940
+ }
941
+ return { success: true, configPath };
942
+ } catch (err) {
943
+ return {
944
+ success: false,
945
+ configPath,
946
+ error: `Failed to enable LSP: ${err}`
947
+ };
948
+ }
949
+ }
950
+ function detectCurrentConfig() {
951
+ const result = {
952
+ isInstalled: false,
953
+ hasKimi: false,
954
+ hasOpenAI: false,
955
+ hasAnthropic: false,
956
+ hasCopilot: false,
957
+ hasZaiPlan: false,
958
+ hasAntigravity: false,
959
+ hasChutes: false,
960
+ hasOpencodeZen: false,
961
+ hasTmux: false
962
+ };
963
+ const { config } = parseConfig(getExistingConfigPath());
964
+ if (!config)
965
+ return result;
966
+ const plugins = getPluginEntries(config);
967
+ result.isInstalled = plugins.some((p) => isPluginEntry(p));
968
+ result.hasAntigravity = plugins.some((p) => p.startsWith("opencode-antigravity-auth"));
969
+ const providers = config.provider;
970
+ result.hasKimi = !!providers?.kimi;
971
+ result.hasAnthropic = !!providers?.anthropic;
972
+ result.hasCopilot = !!providers?.["github-copilot"];
973
+ result.hasZaiPlan = !!providers?.["zai-coding-plan"];
974
+ result.hasChutes = !!providers?.chutes;
975
+ if (providers?.google)
976
+ result.hasAntigravity = true;
977
+ const { config: liteConfig } = parseConfig(getLiteConfig());
978
+ if (liteConfig && typeof liteConfig === "object") {
979
+ const configObj = liteConfig;
980
+ const presetName = configObj.preset;
981
+ const presets = configObj.presets;
982
+ const agents = presets?.[presetName];
983
+ if (agents) {
984
+ const models = Object.values(agents).map((a) => a?.model).filter(Boolean);
985
+ result.hasOpenAI = models.some((m) => m?.startsWith("openai/"));
986
+ result.hasAnthropic = models.some((m) => m?.startsWith("anthropic/"));
987
+ result.hasCopilot = models.some((m) => m?.startsWith("github-copilot/"));
988
+ result.hasZaiPlan = models.some((m) => m?.startsWith("zai-coding-plan/"));
989
+ result.hasOpencodeZen = models.some((m) => m?.startsWith("opencode/"));
990
+ if (models.some((m) => m?.startsWith("google/"))) {
991
+ result.hasAntigravity = true;
992
+ }
993
+ if (models.some((m) => m?.startsWith("chutes/"))) {
994
+ result.hasChutes = true;
995
+ }
996
+ }
997
+ if (configObj.tmux && typeof configObj.tmux === "object") {
998
+ const tmuxConfig = configObj.tmux;
999
+ result.hasTmux = tmuxConfig.enabled === true;
1000
+ }
1001
+ }
1002
+ return result;
1003
+ }
1004
+
1005
+ // src/config/loader.ts
1006
+ function findConfigPath(basePath) {
1007
+ const jsoncPath = `${basePath}.jsonc`;
1008
+ const jsonPath = `${basePath}.json`;
1009
+ if (fs.existsSync(jsoncPath)) {
1010
+ return jsoncPath;
1011
+ }
1012
+ if (fs.existsSync(jsonPath)) {
1013
+ return jsonPath;
1014
+ }
1015
+ return null;
1016
+ }
1017
+ function findConfigPathInDirs(configDirs, baseName) {
1018
+ for (const configDir of configDirs) {
1019
+ const configPath = findConfigPath(path.join(configDir, baseName));
1020
+ if (configPath) {
1021
+ return configPath;
1022
+ }
1023
+ }
1024
+ return null;
1025
+ }
1026
+ function findPluginConfigPaths(directory) {
1027
+ const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "agent-forge");
1028
+ const projectConfigBasePath = path.join(directory, ".opencode", "agent-forge");
1029
+ const projectConfigPath = findConfigPath(projectConfigBasePath);
1030
+ return { userConfigPath, projectConfigPath };
1031
+ }
1032
+ function mergePluginConfigs(base, override) {
1033
+ return {
1034
+ ...base,
1035
+ ...override,
1036
+ agents: deepMerge(base.agents, override.agents),
1037
+ tmux: deepMerge(base.tmux, override.tmux),
1038
+ multiplexer: deepMerge(base.multiplexer, override.multiplexer),
1039
+ interview: deepMerge(base.interview, override.interview),
1040
+ sessionManager: deepMerge(base.sessionManager, override.sessionManager),
1041
+ divoom: deepMerge(base.divoom, override.divoom),
1042
+ fallback: deepMerge(base.fallback, override.fallback),
1043
+ council: deepMerge(base.council, override.council)
1044
+ };
1045
+ }
1046
+ function deepMerge(base, override) {
1047
+ if (!base)
1048
+ return override;
1049
+ if (!override)
1050
+ return base;
1051
+ const result = { ...base };
1052
+ for (const key of Object.keys(override)) {
1053
+ const baseVal = base[key];
1054
+ const overrideVal = override[key];
1055
+ if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
1056
+ result[key] = deepMerge(baseVal, overrideVal);
1057
+ } else {
1058
+ result[key] = overrideVal;
1059
+ }
1060
+ }
1061
+ return result;
1062
+ }
1063
+
1064
+ // src/cli/doctor.ts
1065
+ function parseDoctorArgs(args) {
1066
+ const result = {};
1067
+ for (const arg of args) {
1068
+ if (arg === "--json") {
1069
+ result.json = true;
1070
+ } else if (arg === "--help" || arg === "-h") {
1071
+ result.help = true;
1072
+ } else {
1073
+ result.error ??= `Unknown doctor option: ${arg}`;
1074
+ }
1075
+ }
1076
+ return result;
1077
+ }
1078
+ function checkConfigFile(scope, configPath) {
1079
+ if (configPath === null) {
1080
+ return { scope, path: null, exists: false, ok: true };
1081
+ }
1082
+ try {
1083
+ const stat = fs2.statSync(configPath);
1084
+ if (stat.size === 0) {
1085
+ return {
1086
+ scope,
1087
+ path: configPath,
1088
+ exists: true,
1089
+ ok: false,
1090
+ error: {
1091
+ kind: "invalid-json",
1092
+ message: "Empty file is not valid JSON"
1093
+ }
1094
+ };
1095
+ }
1096
+ const content = fs2.readFileSync(configPath, "utf-8");
1097
+ const rawConfig = JSON.parse(stripJsonComments(content));
1098
+ const parseResult = PluginConfigSchema.safeParse(rawConfig);
1099
+ if (!parseResult.success) {
1100
+ return {
1101
+ scope,
1102
+ path: configPath,
1103
+ exists: true,
1104
+ ok: false,
1105
+ error: {
1106
+ kind: "invalid-schema",
1107
+ message: z3.prettifyError(parseResult.error),
1108
+ issues: parseResult.error.issues
1109
+ }
1110
+ };
1111
+ }
1112
+ return {
1113
+ scope,
1114
+ path: configPath,
1115
+ exists: true,
1116
+ ok: true,
1117
+ config: parseResult.data
1118
+ };
1119
+ } catch (err) {
1120
+ if (err instanceof SyntaxError) {
1121
+ return {
1122
+ scope,
1123
+ path: configPath,
1124
+ exists: true,
1125
+ ok: false,
1126
+ error: {
1127
+ kind: "invalid-json",
1128
+ message: err.message
1129
+ }
1130
+ };
1131
+ } else if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1132
+ return {
1133
+ scope,
1134
+ path: configPath,
1135
+ exists: false,
1136
+ ok: false,
1137
+ error: {
1138
+ kind: "read-error",
1139
+ message: "File was not found while reading"
1140
+ }
1141
+ };
1142
+ }
1143
+ return {
1144
+ scope,
1145
+ path: configPath,
1146
+ exists: true,
1147
+ ok: false,
1148
+ error: {
1149
+ kind: "read-error",
1150
+ message: err instanceof Error ? err.message : String(err)
1151
+ }
1152
+ };
1153
+ }
1154
+ }
1155
+ function checkPreset(mergedConfig) {
1156
+ const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
1157
+ const presetName = envPreset || mergedConfig.preset;
1158
+ if (presetName === undefined) {
1159
+ return;
1160
+ }
1161
+ if (!mergedConfig.presets?.[presetName]) {
1162
+ return {
1163
+ preset: presetName,
1164
+ ok: false,
1165
+ error: {
1166
+ kind: "missing-preset",
1167
+ message: `Preset "${presetName}" not found in config`
1168
+ }
1169
+ };
1170
+ }
1171
+ return { preset: presetName, ok: true };
1172
+ }
1173
+ function getMergedConfig(userConfig, projectConfig) {
1174
+ return projectConfig ? mergePluginConfigs(userConfig ?? {}, projectConfig) : userConfig ?? {};
1175
+ }
1176
+ function runDoctorCheck(cwd) {
1177
+ const { userConfigPath, projectConfigPath } = findPluginConfigPaths(cwd);
1178
+ const userCheck = checkConfigFile("user", userConfigPath);
1179
+ const projectCheck = checkConfigFile("project", projectConfigPath);
1180
+ const configs = [userCheck, projectCheck];
1181
+ const hasInvalidConfig = configs.some((c) => !c.ok);
1182
+ let presetCheckResult;
1183
+ if (!hasInvalidConfig) {
1184
+ const mergedConfig = getMergedConfig(userCheck.config, projectCheck.config);
1185
+ presetCheckResult = checkPreset(mergedConfig);
1186
+ }
1187
+ return {
1188
+ ok: configs.every((c) => c.ok) && (!presetCheckResult || presetCheckResult.ok),
1189
+ project: cwd,
1190
+ configs,
1191
+ presetCheck: presetCheckResult
1192
+ };
1193
+ }
1194
+ function formatHumanDoctorResult(result) {
1195
+ const lines = [];
1196
+ lines.push(`Project: ${result.project}`);
1197
+ lines.push("");
1198
+ for (const config of result.configs) {
1199
+ if (config.path === null) {
1200
+ lines.push(`[${config.scope}] No config file found`);
1201
+ } else {
1202
+ const status = config.ok ? "✓" : "✗";
1203
+ lines.push(`[${config.scope}] ${config.path} ${status}`);
1204
+ if (!config.ok && config.error) {
1205
+ if (config.error.kind === "invalid-json") {
1206
+ lines.push(` Invalid JSON: ${config.error.message}`);
1207
+ } else if (config.error.kind === "invalid-schema") {
1208
+ lines.push(" Schema error:");
1209
+ for (const line of config.error.message.split(`
1210
+ `)) {
1211
+ lines.push(` ${line}`);
1212
+ }
1213
+ } else if (config.error.kind === "read-error") {
1214
+ lines.push(` Read error: ${config.error.message}`);
1215
+ }
1216
+ }
1217
+ }
1218
+ }
1219
+ if (result.presetCheck) {
1220
+ lines.push("");
1221
+ const status = result.presetCheck.ok ? "✓" : "✗";
1222
+ lines.push(`[preset] ${result.presetCheck.preset} ${status}`);
1223
+ if (result.presetCheck.error) {
1224
+ lines.push(` ${result.presetCheck.error.message}`);
1225
+ }
1226
+ }
1227
+ return lines.join(`
1228
+ `);
1229
+ }
1230
+ function formatJsonDoctorResult(result) {
1231
+ return JSON.stringify({
1232
+ ...result,
1233
+ configs: result.configs.map(({ config: _config, ...config }) => config)
1234
+ }, null, 2);
1235
+ }
1236
+ async function doctor(args) {
1237
+ if (args.help) {
1238
+ console.log(`Usage: agent-forge doctor [OPTIONS]
1239
+
1240
+ Options:
1241
+ --json Print diagnostics as JSON
1242
+ -h, --help Show this help message`);
1243
+ return 0;
1244
+ }
1245
+ if (args.error) {
1246
+ console.error(args.error);
1247
+ return 1;
1248
+ }
1249
+ const result = runDoctorCheck(process.cwd());
1250
+ if (args.json) {
1251
+ console.log(formatJsonDoctorResult(result));
1252
+ } else {
1253
+ console.log(formatHumanDoctorResult(result));
1254
+ }
1255
+ return result.ok ? 0 : 1;
1256
+ }
1257
+
1258
+ // src/cli/install.ts
1259
+ import { existsSync as existsSync6 } from "node:fs";
1260
+ import { createInterface } from "node:readline/promises";
1261
+ // src/cli/system.ts
1262
+ import { spawnSync as spawnSync2 } from "node:child_process";
1263
+ import { statSync as statSync4 } from "node:fs";
1264
+
1265
+ // src/utils/compat.ts
1266
+ import { spawn as nodeSpawn } from "node:child_process";
1267
+ var isBun = typeof globalThis.Bun !== "undefined";
1268
+ function collectStream(stream) {
1269
+ if (!stream)
1270
+ return () => Promise.resolve("");
1271
+ const chunks = [];
1272
+ stream.on("data", (chunk) => chunks.push(chunk));
1273
+ return () => new Promise((resolve, reject) => {
1274
+ if (!stream.readable) {
1275
+ resolve(Buffer.concat(chunks).toString("utf-8"));
1276
+ return;
1277
+ }
1278
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1279
+ stream.on("error", reject);
1280
+ });
1281
+ }
1282
+ function crossSpawn(command, options) {
1283
+ const [cmd, ...args] = command;
1284
+ const proc = nodeSpawn(cmd, args, {
1285
+ stdio: [
1286
+ options?.stdin ?? "ignore",
1287
+ options?.stdout ?? "pipe",
1288
+ options?.stderr ?? "pipe"
1289
+ ],
1290
+ cwd: options?.cwd,
1291
+ env: options?.env
1292
+ });
1293
+ const stdoutCollector = collectStream(proc.stdout);
1294
+ const stderrCollector = collectStream(proc.stderr);
1295
+ const exited = new Promise((resolve, reject) => {
1296
+ proc.on("error", reject);
1297
+ proc.on("close", (code) => resolve(code ?? 1));
1298
+ });
1299
+ return {
1300
+ proc,
1301
+ stdout: stdoutCollector,
1302
+ stderr: stderrCollector,
1303
+ exited,
1304
+ kill: (signal) => proc.kill(signal),
1305
+ get exitCode() {
1306
+ return proc.exitCode;
1307
+ }
1308
+ };
1309
+ }
1310
+
1311
+ // src/cli/system.ts
1312
+ var cachedOpenCodePath = null;
1313
+ function resolvePathCommand(command) {
1314
+ try {
1315
+ const resolver = process.platform === "win32" ? "where" : "which";
1316
+ const result = spawnSync2(resolver, [command], {
1317
+ encoding: "utf-8",
1318
+ stdio: ["ignore", "pipe", "ignore"]
1319
+ });
1320
+ if (result.status !== 0) {
1321
+ return null;
1322
+ }
1323
+ const resolved = result.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1324
+ return resolved ?? null;
1325
+ } catch {
1326
+ return null;
1327
+ }
1328
+ }
1329
+ function canExecute(command, args) {
1330
+ try {
1331
+ const result = spawnSync2(command, args, {
1332
+ stdio: "ignore"
1333
+ });
1334
+ return result.status === 0;
1335
+ } catch {
1336
+ return false;
1337
+ }
1338
+ }
1339
+ function getOpenCodePaths() {
1340
+ const home = process.env.HOME || process.env.USERPROFILE || "";
1341
+ return [
1342
+ "opencode",
1343
+ `${home}/.local/bin/opencode`,
1344
+ `${home}/.opencode/bin/opencode`,
1345
+ `${home}/bin/opencode`,
1346
+ "/usr/local/bin/opencode",
1347
+ "/opt/opencode/bin/opencode",
1348
+ "/usr/bin/opencode",
1349
+ "/bin/opencode",
1350
+ "/Applications/OpenCode.app/Contents/MacOS/opencode",
1351
+ `${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
1352
+ "/opt/homebrew/bin/opencode",
1353
+ "/home/linuxbrew/.linuxbrew/bin/opencode",
1354
+ `${home}/homebrew/bin/opencode`,
1355
+ `${home}/Library/Application Support/opencode/bin/opencode`,
1356
+ "/snap/bin/opencode",
1357
+ "/var/snap/opencode/current/bin/opencode",
1358
+ "/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
1359
+ `${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
1360
+ "/nix/store/opencode/bin/opencode",
1361
+ `${home}/.nix-profile/bin/opencode`,
1362
+ "/run/current-system/sw/bin/opencode",
1363
+ `${home}/.cargo/bin/opencode`,
1364
+ `${home}/.npm-global/bin/opencode`,
1365
+ "/usr/local/lib/node_modules/opencode/bin/opencode",
1366
+ `${home}/.yarn/bin/opencode`,
1367
+ `${home}/.pnpm-global/bin/opencode`
1368
+ ];
1369
+ }
1370
+ function resolveOpenCodePath() {
1371
+ if (cachedOpenCodePath) {
1372
+ return cachedOpenCodePath;
1373
+ }
1374
+ const pathOpenCodePath = resolvePathCommand("opencode");
1375
+ if (pathOpenCodePath) {
1376
+ cachedOpenCodePath = pathOpenCodePath;
1377
+ return pathOpenCodePath;
1378
+ }
1379
+ const paths = getOpenCodePaths();
1380
+ for (const opencodePath of paths) {
1381
+ if (opencodePath === "opencode")
1382
+ continue;
1383
+ try {
1384
+ const stat = statSync4(opencodePath);
1385
+ if (stat.isFile()) {
1386
+ cachedOpenCodePath = opencodePath;
1387
+ return opencodePath;
1388
+ }
1389
+ } catch {}
1390
+ }
1391
+ return "opencode";
1392
+ }
1393
+ async function isOpenCodeInstalled() {
1394
+ const pathOpenCodePath = resolvePathCommand("opencode");
1395
+ if (pathOpenCodePath && canExecute(pathOpenCodePath, ["--version"])) {
1396
+ cachedOpenCodePath = pathOpenCodePath;
1397
+ return true;
1398
+ }
1399
+ const paths = getOpenCodePaths();
1400
+ for (const opencodePath of paths) {
1401
+ if (opencodePath === "opencode")
1402
+ continue;
1403
+ try {
1404
+ const proc = crossSpawn([opencodePath, "--version"], {
1405
+ stdout: "pipe",
1406
+ stderr: "pipe"
1407
+ });
1408
+ await proc.exited;
1409
+ if (proc.exitCode === 0) {
1410
+ cachedOpenCodePath = opencodePath;
1411
+ return true;
1412
+ }
1413
+ } catch {}
1414
+ }
1415
+ return false;
1416
+ }
1417
+ async function getOpenCodeVersion() {
1418
+ const opencodePath = resolveOpenCodePath();
1419
+ try {
1420
+ const proc = crossSpawn([opencodePath, "--version"], {
1421
+ stdout: "pipe",
1422
+ stderr: "pipe"
1423
+ });
1424
+ const outputPromise = proc.stdout();
1425
+ await proc.exited;
1426
+ if (proc.exitCode === 0) {
1427
+ return (await outputPromise).trim();
1428
+ }
1429
+ } catch {}
1430
+ return null;
1431
+ }
1432
+ function getOpenCodePath() {
1433
+ const path2 = resolveOpenCodePath();
1434
+ return path2 === "opencode" ? null : path2;
1435
+ }
1436
+ // src/cli/migration.ts
1437
+ import { existsSync as existsSync5, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
1438
+ import { join as join5 } from "node:path";
1439
+ var LEGACY_BASE_NAME = "oh-my-opencode-slim";
1440
+ var PRIMARY_AGENT_MAP = {
1441
+ explorer: "researcher",
1442
+ oracle: "reviewer",
1443
+ designer: "implementer"
1444
+ };
1445
+ var SECONDARY_AGENT_MAP = {
1446
+ librarian: "researcher",
1447
+ fixer: "implementer"
1448
+ };
1449
+ var REMOVED_AGENTS = new Set(["observer"]);
1450
+ var ALL_LEGACY_NAMES = new Set([
1451
+ ...Object.keys(PRIMARY_AGENT_MAP),
1452
+ ...Object.keys(SECONDARY_AGENT_MAP),
1453
+ ...REMOVED_AGENTS
1454
+ ]);
1455
+ function getLegacyConfigPaths() {
1456
+ const configDir = getConfigDir();
1457
+ return [
1458
+ join5(configDir, `${LEGACY_BASE_NAME}.json`),
1459
+ join5(configDir, `${LEGACY_BASE_NAME}.jsonc`)
1460
+ ];
1461
+ }
1462
+ function detectLegacyConfig() {
1463
+ const paths2 = getLegacyConfigPaths();
1464
+ for (const p of paths2) {
1465
+ if (existsSync5(p))
1466
+ return p;
1467
+ }
1468
+ return;
1469
+ }
1470
+ function migrateAgentNames(config) {
1471
+ const result = { ...config };
1472
+ const presets = result.presets;
1473
+ if (presets) {
1474
+ const migratedPresets = {};
1475
+ for (const [presetName, preset] of Object.entries(presets)) {
1476
+ migratedPresets[presetName] = migrateRecord(preset);
1477
+ }
1478
+ result.presets = migratedPresets;
1479
+ }
1480
+ const agents = result.agents;
1481
+ if (agents) {
1482
+ result.agents = migrateRecord(agents);
1483
+ }
1484
+ const disabled = result.disabled_agents;
1485
+ if (Array.isArray(disabled)) {
1486
+ result.disabled_agents = disabled.filter((name) => !REMOVED_AGENTS.has(name));
1487
+ }
1488
+ return result;
1489
+ }
1490
+ function migrateRecord(record) {
1491
+ const result = {};
1492
+ const usedTargets = new Set;
1493
+ for (const [key, value] of Object.entries(record)) {
1494
+ if (REMOVED_AGENTS.has(key)) {
1495
+ continue;
1496
+ }
1497
+ const primaryTarget = PRIMARY_AGENT_MAP[key];
1498
+ if (primaryTarget) {
1499
+ result[primaryTarget] = value;
1500
+ usedTargets.add(primaryTarget);
1501
+ continue;
1502
+ }
1503
+ if (!ALL_LEGACY_NAMES.has(key)) {
1504
+ result[key] = value;
1505
+ }
1506
+ }
1507
+ for (const [key, value] of Object.entries(record)) {
1508
+ const secondaryTarget = SECONDARY_AGENT_MAP[key];
1509
+ if (secondaryTarget && !usedTargets.has(secondaryTarget)) {
1510
+ result[secondaryTarget] = value;
1511
+ usedTargets.add(secondaryTarget);
1512
+ }
1513
+ }
1514
+ return result;
1515
+ }
1516
+ function performMigration(legacyPath) {
1517
+ try {
1518
+ const content = readFileSync4(legacyPath, "utf-8");
1519
+ if (content.trim().length === 0) {
1520
+ return {
1521
+ detected: true,
1522
+ migrated: false,
1523
+ legacyPath,
1524
+ error: "Legacy config file is empty"
1525
+ };
1526
+ }
1527
+ const config = JSON.parse(stripJsonComments(content));
1528
+ const migrated = migrateAgentNames(config);
1529
+ const configDir = getConfigDir();
1530
+ const newPath = join5(configDir, "agent-forge.json");
1531
+ const newContent = `${JSON.stringify(migrated, null, 2)}
1532
+ `;
1533
+ const tmpPath = `${newPath}.tmp`;
1534
+ writeFileSync2(tmpPath, newContent);
1535
+ renameSync2(tmpPath, newPath);
1536
+ const bakPath = `${legacyPath}.bak`;
1537
+ renameSync2(legacyPath, bakPath);
1538
+ return {
1539
+ detected: true,
1540
+ migrated: true,
1541
+ newPath,
1542
+ legacyPath
1543
+ };
1544
+ } catch (err) {
1545
+ return {
1546
+ detected: true,
1547
+ migrated: false,
1548
+ legacyPath,
1549
+ error: `Migration failed: ${err}`
1550
+ };
1551
+ }
1552
+ }
1553
+
1554
+ // src/cli/install.ts
1555
+ var GREEN = "\x1B[32m";
1556
+ var BLUE = "\x1B[34m";
1557
+ var YELLOW = "\x1B[33m";
1558
+ var RED = "\x1B[31m";
1559
+ var BOLD = "\x1B[1m";
1560
+ var DIM = "\x1B[2m";
1561
+ var RESET = "\x1B[0m";
1562
+ var SYMBOLS = {
1563
+ check: `${GREEN}[ok]${RESET}`,
1564
+ cross: `${RED}[x]${RESET}`,
1565
+ arrow: `${BLUE}->${RESET}`,
1566
+ bullet: `${DIM}-${RESET}`,
1567
+ info: `${BLUE}[i]${RESET}`,
1568
+ warn: `${YELLOW}[!]${RESET}`,
1569
+ star: `${YELLOW}★${RESET}`
1570
+ };
1571
+ var GITHUB_REPO = "zcyoop/ai-assistant/agent-forge";
1572
+ var GITHUB_URL = `https://cnb.cool/${GITHUB_REPO}`;
1573
+ function printHeader(isUpdate) {
1574
+ console.log();
1575
+ console.log(`${BOLD}agent-forge ${isUpdate ? "Update" : "Install"}${RESET}`);
1576
+ console.log("=".repeat(30));
1577
+ console.log();
1578
+ }
1579
+ function printStep(step, total, message) {
1580
+ console.log(`${DIM}[${step}/${total}]${RESET} ${message}`);
1581
+ }
1582
+ function printSuccess(message) {
1583
+ console.log(`${SYMBOLS.check} ${message}`);
1584
+ }
1585
+ function printError(message) {
1586
+ console.log(`${SYMBOLS.cross} ${RED}${message}${RESET}`);
1587
+ }
1588
+ function printInfo(message) {
1589
+ console.log(`${SYMBOLS.info} ${message}`);
1590
+ }
1591
+ async function confirm(message, defaultYes = true) {
1592
+ const suffix = defaultYes ? " (Y/n) " : " (y/N) ";
1593
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1594
+ try {
1595
+ const answer = (await rl.question(`${message}${suffix}`)).trim().toLowerCase();
1596
+ if (!answer)
1597
+ return defaultYes;
1598
+ return answer === "y" || answer === "yes";
1599
+ } finally {
1600
+ rl.close();
1601
+ }
1602
+ }
1603
+ async function askToStarRepo(config) {
1604
+ if (!config.promptForStar || config.dryRun || !process.stdin.isTTY)
1605
+ return;
1606
+ console.log();
1607
+ const shouldStar = await confirm(`${SYMBOLS.star} Star the repo on GitHub?`, true);
1608
+ if (!shouldStar)
1609
+ return;
1610
+ try {
1611
+ const { execFileSync } = await import("node:child_process");
1612
+ execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
1613
+ printSuccess("Thanks for starring! ★");
1614
+ } catch {
1615
+ printInfo(`Couldn't star automatically. You can star manually:
1616
+ ${BLUE}${GITHUB_URL}${RESET}`);
1617
+ }
1618
+ }
1619
+ async function checkOpenCodeInstalled() {
1620
+ const installed = await isOpenCodeInstalled();
1621
+ if (!installed) {
1622
+ printError("OpenCode is not installed on this system.");
1623
+ printInfo("Install it with:");
1624
+ console.log(` ${BLUE}curl -fsSL https://opencode.ai/install | bash${RESET}`);
1625
+ console.log();
1626
+ printInfo("Or if already installed, add it to your PATH:");
1627
+ console.log(` ${BLUE}export PATH="$HOME/.local/bin:$PATH"${RESET}`);
1628
+ console.log(` ${BLUE}export PATH="$HOME/.opencode/bin:$PATH"${RESET}`);
1629
+ return { ok: false };
1630
+ }
1631
+ const version = await getOpenCodeVersion();
1632
+ const path2 = getOpenCodePath();
1633
+ const detectedVersion = version ?? "";
1634
+ const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
1635
+ printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
1636
+ return { ok: true, version: version ?? undefined, path: path2 ?? undefined };
1637
+ }
1638
+ function handleStepResult(result, successMsg) {
1639
+ if (!result.success) {
1640
+ printError(`Failed: ${result.error}`);
1641
+ return false;
1642
+ }
1643
+ printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
1644
+ return true;
1645
+ }
1646
+ async function runInstall(config) {
1647
+ const detected = detectCurrentConfig();
1648
+ const isUpdate = detected.isInstalled;
1649
+ printHeader(isUpdate);
1650
+ let totalSteps = 6;
1651
+ if (config.installSkills)
1652
+ totalSteps += 1;
1653
+ if (config.installCustomSkills)
1654
+ totalSteps += 1;
1655
+ const legacyPath = detectLegacyConfig();
1656
+ const hasLegacy = legacyPath !== undefined;
1657
+ if (hasLegacy)
1658
+ totalSteps += 1;
1659
+ let step = 1;
1660
+ printStep(step++, totalSteps, "Checking OpenCode installation...");
1661
+ if (config.dryRun) {
1662
+ printInfo("Dry run mode - skipping OpenCode check");
1663
+ } else {
1664
+ const { ok } = await checkOpenCodeInstalled();
1665
+ if (!ok)
1666
+ return 1;
1667
+ }
1668
+ printStep(step++, totalSteps, "Adding agent-forge plugin...");
1669
+ if (config.dryRun) {
1670
+ printInfo("Dry run mode - skipping plugin installation");
1671
+ } else {
1672
+ const pluginResult = await addPluginToOpenCodeConfig();
1673
+ if (!handleStepResult(pluginResult, "Plugin added"))
1674
+ return 1;
1675
+ }
1676
+ printStep(step++, totalSteps, "Adding TUI version badge...");
1677
+ if (config.dryRun) {
1678
+ printInfo("Dry run mode - skipping TUI plugin installation");
1679
+ } else {
1680
+ const tuiResult = await addPluginToOpenCodeTuiConfig();
1681
+ if (!tuiResult.success) {
1682
+ printInfo(`Skipped TUI badge: ${tuiResult.error}`);
1683
+ } else {
1684
+ handleStepResult(tuiResult, "TUI badge added");
1685
+ }
1686
+ }
1687
+ printStep(step++, totalSteps, "Disabling OpenCode default agents...");
1688
+ if (config.dryRun) {
1689
+ printInfo("Dry run mode - skipping agent disabling");
1690
+ } else {
1691
+ const agentResult = disableDefaultAgents();
1692
+ if (!handleStepResult(agentResult, "Default agents disabled"))
1693
+ return 1;
1694
+ }
1695
+ printStep(step++, totalSteps, "Enabling OpenCode LSP integration...");
1696
+ if (config.dryRun) {
1697
+ printInfo("Dry run mode - skipping LSP configuration");
1698
+ } else {
1699
+ const lspResult = enableLspByDefault();
1700
+ if (!handleStepResult(lspResult, "LSP enabled"))
1701
+ return 1;
1702
+ }
1703
+ if (hasLegacy && legacyPath) {
1704
+ printStep(step++, totalSteps, "Migrating legacy configuration...");
1705
+ if (config.dryRun) {
1706
+ printInfo(`Dry run mode - detected legacy config: ${legacyPath}`);
1707
+ printInfo("Would migrate agent names and rename to agent-forge.json");
1708
+ } else if (config.reset) {
1709
+ const migrationResult = performMigration(legacyPath);
1710
+ if (migrationResult.migrated) {
1711
+ printSuccess(`Legacy config migrated ${SYMBOLS.arrow} ${migrationResult.newPath}`);
1712
+ } else if (migrationResult.error) {
1713
+ printInfo(`Legacy config migration skipped: ${migrationResult.error}`);
1714
+ }
1715
+ } else {
1716
+ printInfo(`Legacy config detected: ${DIM}${legacyPath}${RESET}`);
1717
+ printInfo("This is an oh-my-opencode-slim config with old agent names (explorer, librarian, oracle, designer, fixer, observer).");
1718
+ printInfo("New agent names: researcher (explorer+librarian), reviewer (oracle), implementer (designer+fixer).");
1719
+ const shouldMigrate = await confirm("Migrate to agent-forge.json?", true);
1720
+ if (shouldMigrate) {
1721
+ const migrationResult = performMigration(legacyPath);
1722
+ if (migrationResult.migrated) {
1723
+ printSuccess(`Migrated ${SYMBOLS.arrow} ${DIM}${migrationResult.newPath}${RESET}`);
1724
+ printInfo(`Legacy config backed up to ${DIM}${legacyPath}.bak${RESET}`);
1725
+ } else {
1726
+ printError(migrationResult.error ?? "Unknown migration error");
1727
+ }
1728
+ } else {
1729
+ printInfo("Skipped migration. You can manually rename oh-my-opencode-slim.json to agent-forge.json and " + "update agent names: explorer→researcher, " + "oracle→reviewer, designer→implementer.");
1730
+ }
1731
+ }
1732
+ }
1733
+ printStep(step++, totalSteps, "Writing agent-forge configuration...");
1734
+ if (config.dryRun) {
1735
+ const liteConfig = generateLiteConfig(config);
1736
+ printInfo("Dry run mode - configuration that would be written:");
1737
+ console.log(`
1738
+ ${JSON.stringify(liteConfig, null, 2)}
1739
+ `);
1740
+ } else {
1741
+ const configPath2 = getExistingLiteConfigPath();
1742
+ const configExists = existsSync6(configPath2);
1743
+ if (configExists && !config.reset) {
1744
+ printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
1745
+ } else {
1746
+ const liteResult = writeLiteConfig(config, configExists ? configPath2 : undefined);
1747
+ if (!handleStepResult(liteResult, configExists ? "Config reset" : "Config written"))
1748
+ return 1;
1749
+ }
1750
+ }
1751
+ if (config.installSkills) {
1752
+ printStep(step++, totalSteps, "Installing recommended skills...");
1753
+ if (config.dryRun) {
1754
+ printInfo("Dry run mode - would install skills:");
1755
+ for (const skill of RECOMMENDED_SKILLS) {
1756
+ printInfo(` - ${skill.name}`);
1757
+ }
1758
+ } else {
1759
+ let skillsInstalled = 0;
1760
+ for (const skill of RECOMMENDED_SKILLS) {
1761
+ printInfo(`Installing ${skill.name}...`);
1762
+ if (installSkill(skill)) {
1763
+ printSuccess(`Installed: ${skill.name}`);
1764
+ skillsInstalled++;
1765
+ } else {
1766
+ printInfo(`Skipped: ${skill.name} (already installed)`);
1767
+ }
1768
+ }
1769
+ printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills processed`);
1770
+ }
1771
+ }
1772
+ if (config.installCustomSkills) {
1773
+ printStep(step++, totalSteps, "Installing custom skills...");
1774
+ if (config.dryRun) {
1775
+ printInfo("Dry run mode - would install custom skills:");
1776
+ for (const skill of CUSTOM_SKILLS) {
1777
+ printInfo(` - ${skill.name}`);
1778
+ }
1779
+ } else {
1780
+ let customSkillsInstalled = 0;
1781
+ for (const skill of CUSTOM_SKILLS) {
1782
+ printInfo(`Installing ${skill.name}...`);
1783
+ if (installCustomSkill(skill)) {
1784
+ printSuccess(`Installed: ${skill.name}`);
1785
+ customSkillsInstalled++;
1786
+ } else {
1787
+ printInfo(`Skipped: ${skill.name} (already installed)`);
1788
+ }
1789
+ }
1790
+ const totalCustom = CUSTOM_SKILLS.length;
1791
+ printSuccess(`${customSkillsInstalled}/${totalCustom} custom skills processed`);
1792
+ }
1793
+ }
1794
+ const statusMsg = isUpdate ? "Configuration updated!" : "Installation complete!";
1795
+ console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${statusMsg}${RESET}`);
1796
+ console.log();
1797
+ console.log(`${BOLD}Next steps:${RESET}`);
1798
+ console.log();
1799
+ const configPath = getExistingLiteConfigPath();
1800
+ console.log(" 1. Log in to the provider(s) you want to use:");
1801
+ console.log(` ${BLUE}$ opencode auth login${RESET}`);
1802
+ console.log();
1803
+ console.log(" 2. Refresh the models OpenCode can see:");
1804
+ console.log(` ${BLUE}$ opencode models --refresh${RESET}`);
1805
+ console.log();
1806
+ console.log(" 3. Review your generated config:");
1807
+ console.log(` ${BLUE}${configPath}${RESET}`);
1808
+ console.log();
1809
+ console.log(" 4. Start OpenCode:");
1810
+ console.log(` ${BLUE}$ opencode${RESET}`);
1811
+ console.log();
1812
+ console.log(" 5. Verify the agents are responding:");
1813
+ console.log(` ${BLUE}> ping all agents${RESET}`);
1814
+ console.log();
1815
+ const modelsInfo = config.preset && config.preset !== "openai" ? `Generated OpenAI and OpenCode Go presets; ${config.preset} is active.` : "Generated OpenAI and OpenCode Go presets; OpenAI is active by default.";
1816
+ console.log(`${modelsInfo}`);
1817
+ const altProviders = "For the full configuration reference, see:";
1818
+ console.log(altProviders);
1819
+ const docsUrl = "https://cnb.cool/zcyoop/ai-assistant/agent-forge/blob/master/docs/configuration.md";
1820
+ console.log(` ${BLUE}${docsUrl}${RESET}`);
1821
+ console.log();
1822
+ await askToStarRepo(config);
1823
+ return 0;
1824
+ }
1825
+ async function install(args) {
1826
+ const config = {
1827
+ hasTmux: false,
1828
+ installSkills: args.skills === "yes",
1829
+ installCustomSkills: args.skills === "yes",
1830
+ preset: args.preset,
1831
+ promptForStar: args.tui,
1832
+ dryRun: args.dryRun,
1833
+ reset: args.reset ?? false
1834
+ };
1835
+ return runInstall(config);
1836
+ }
1837
+
1838
+ // src/cli/providers.ts
1839
+ var GENERATED_PRESETS2 = ["openai", "opencode-go"];
1840
+ function isGeneratedPresetName2(value) {
1841
+ return GENERATED_PRESETS2.includes(value);
1842
+ }
1843
+ function getGeneratedPresetNames2() {
1844
+ return [...GENERATED_PRESETS2];
1845
+ }
1846
+
1847
+ // src/cli/index.ts
1848
+ function parseArgs(args) {
1849
+ const result = {
1850
+ tui: true,
1851
+ skills: "yes"
1852
+ };
1853
+ for (const arg of args) {
1854
+ if (arg === "--no-tui") {
1855
+ result.tui = false;
1856
+ } else if (arg.startsWith("--skills=")) {
1857
+ result.skills = arg.split("=")[1];
1858
+ } else if (arg.startsWith("--preset=")) {
1859
+ const preset = arg.split("=")[1];
1860
+ if (!isGeneratedPresetName2(preset)) {
1861
+ console.error(`Unsupported preset: ${preset}. Available presets: ${getGeneratedPresetNames2().join(", ")}`);
1862
+ process.exit(1);
1863
+ }
1864
+ result.preset = preset;
1865
+ } else if (arg === "--dry-run") {
1866
+ result.dryRun = true;
1867
+ } else if (arg === "--reset") {
1868
+ result.reset = true;
1869
+ } else if (arg === "-h" || arg === "--help") {
1870
+ printHelp();
1871
+ process.exit(0);
1872
+ }
1873
+ }
1874
+ return result;
1875
+ }
1876
+ function printHelp() {
1877
+ console.log(`
1878
+ @zcy2nn/agent-forge installer
1879
+
1880
+ Usage:
1881
+ bunx @zcy2nn/agent-forge install [OPTIONS]
1882
+ bunx @zcy2nn/agent-forge doctor [OPTIONS]
1883
+
1884
+ Options:
1885
+ --skills=yes|no Install recommended and bundled skills (default: yes)
1886
+ --preset=<name> Active generated config preset (default: openai)
1887
+ --no-tui Non-interactive mode
1888
+ --dry-run Simulate install without writing files
1889
+ --reset Force overwrite of existing configuration
1890
+ -h, --help Show this help message
1891
+
1892
+ Doctor options:
1893
+ --json Print diagnostics as JSON
1894
+
1895
+ Available presets: ${getGeneratedPresetNames2().join(", ")}
1896
+
1897
+ The installer generates OpenAI and OpenCode Go presets by default.
1898
+ OpenAI is active unless --preset selects another generated preset.
1899
+ For the full config reference, see docs/configuration.md.
1900
+
1901
+ Examples:
1902
+ bunx @zcy2nn/agent-forge install
1903
+ bunx @zcy2nn/agent-forge install --no-tui --skills=yes
1904
+ bunx @zcy2nn/agent-forge install --preset=opencode-go
1905
+ bunx @zcy2nn/agent-forge install --reset
1906
+ bunx @zcy2nn/agent-forge doctor
1907
+ `);
1908
+ }
1909
+ async function main() {
1910
+ const args = process.argv.slice(2);
1911
+ if (args.length === 0 || args[0] === "install") {
1912
+ const hasSubcommand = args[0] === "install";
1913
+ const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
1914
+ const exitCode = await install(installArgs);
1915
+ process.exit(exitCode);
1916
+ } else if (args[0] === "doctor") {
1917
+ const doctorArgs = parseDoctorArgs(args.slice(1));
1918
+ const exitCode = await doctor(doctorArgs);
1919
+ process.exit(exitCode);
1920
+ } else if (args[0] === "-h" || args[0] === "--help") {
1921
+ printHelp();
1922
+ process.exit(0);
1923
+ } else {
1924
+ console.error(`Unknown command: ${args[0]}`);
1925
+ console.error("Run with --help for usage information");
1926
+ process.exit(1);
1927
+ }
1928
+ }
1929
+ main().catch((err) => {
1930
+ console.error("Fatal error:", err);
1931
+ process.exit(1);
1932
+ });