opencode-matrixx 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -8
- package/dist/agents/dynamic-agent-prompt-builder.d.ts +3 -0
- package/dist/cli/doctor/checks/auth.d.ts +2 -0
- package/dist/cli/doctor/checks/config.d.ts +2 -0
- package/dist/cli/doctor/checks/index.d.ts +10 -0
- package/dist/cli/doctor/checks/mcp.d.ts +2 -0
- package/dist/cli/doctor/checks/optional.d.ts +2 -0
- package/dist/cli/doctor/checks/plugin.d.ts +2 -0
- package/dist/cli/doctor/checks/runtime.d.ts +2 -0
- package/dist/cli/doctor/format.d.ts +3 -0
- package/dist/cli/doctor/index.d.ts +9 -0
- package/dist/cli/doctor/types.d.ts +24 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/install/index.d.ts +12 -0
- package/dist/cli.js +927 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/evolution.d.ts +89 -0
- package/dist/config/schema/headroom.d.ts +23 -0
- package/dist/config/schema/hooks.d.ts +6 -0
- package/dist/config/schema/matrixx-config.d.ts +54 -6
- package/dist/config/schema.d.ts +3 -3
- package/dist/create-hooks.d.ts +5 -0
- package/dist/features/background-agent/error-helpers.d.ts +0 -2
- package/dist/features/builtin-commands/templates/evolution.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/evolution/compressor/index.d.ts +5 -0
- package/dist/features/evolution/compressor/interface.d.ts +5 -0
- package/dist/features/evolution/compressor/llm.d.ts +12 -0
- package/dist/features/evolution/evaluator.d.ts +11 -0
- package/dist/features/evolution/index.d.ts +7 -0
- package/dist/features/evolution/pipeline.d.ts +7 -0
- package/dist/features/evolution/store.d.ts +22 -0
- package/dist/features/evolution/types.d.ts +51 -0
- package/dist/features/evolution/writer.d.ts +20 -0
- package/dist/features/task-storage/types.d.ts +2 -2
- package/dist/features/task-toast-manager/types.d.ts +2 -2
- package/dist/hooks/anthropic-context-window-limit-recovery/message-builder.d.ts +0 -1
- package/dist/hooks/anthropic-context-window-limit-recovery/tool-part-types.d.ts +2 -1
- package/dist/hooks/evolution-compressor/index.d.ts +15 -0
- package/dist/hooks/evolution-hitl/index.d.ts +12 -0
- package/dist/hooks/evolution-quality-gate/index.d.ts +6 -0
- package/dist/hooks/evolution-watcher/index.d.ts +21 -0
- package/dist/hooks/evolution-watcher/utils.d.ts +5 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/keyword-detector/analyze/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/constants.d.ts +1 -1
- package/dist/hooks/keyword-detector/search/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/deepseek.d.ts +16 -0
- package/dist/hooks/keyword-detector/ultrawork/default.d.ts +5 -2
- package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +12 -0
- package/dist/hooks/keyword-detector/ultrawork/glm.d.ts +11 -0
- package/dist/hooks/keyword-detector/ultrawork/gpt5.2.d.ts +4 -7
- package/dist/hooks/keyword-detector/ultrawork/index.d.ts +11 -3
- package/dist/hooks/keyword-detector/ultrawork/mimo.d.ts +16 -0
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +17 -5
- package/dist/hooks/matrix-loop/with-timeout.d.ts +1 -1
- package/dist/hooks/mcp-startup-notification/index.d.ts +9 -0
- package/dist/hooks/session-recovery/types.d.ts +2 -1
- package/dist/hooks/task-todo-mirror/constants.d.ts +3 -0
- package/dist/hooks/task-todo-mirror/hook.d.ts +43 -0
- package/dist/hooks/task-todo-mirror/index.d.ts +2 -0
- package/dist/hooks/think-mode/types.d.ts +3 -0
- package/dist/index.js +6136 -3867
- package/dist/matrixx.schema.json +5412 -0
- package/dist/mcp/index.d.ts +10 -1
- package/dist/mcp/mcp-startup-state.d.ts +4 -0
- package/dist/mcp/mcp-validator.d.ts +13 -0
- package/dist/plugin/hooks/create-continuation-hooks.d.ts +3 -1
- package/dist/plugin/hooks/create-core-hooks.d.ts +3 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +3 -1
- package/dist/plugin-handlers/agent-config-handler.d.ts +1 -0
- package/dist/shared/delay.d.ts +1 -0
- package/dist/shared/error-formatting.d.ts +1 -0
- package/dist/shared/format-bytes.d.ts +1 -0
- package/dist/shared/format-bytes.test.d.ts +1 -0
- package/dist/shared/index.d.ts +7 -9
- package/dist/shared/is-abort-error.test.d.ts +1 -0
- package/dist/shared/model-resolution-pipeline.d.ts +26 -1
- package/dist/shared/opencode-config-dir.d.ts +13 -2
- package/dist/shared/sentinels.d.ts +2 -0
- package/dist/shared/session-state.d.ts +15 -0
- package/dist/shared/status-types.d.ts +1 -0
- package/dist/shared/with-timeout.d.ts +1 -0
- package/dist/shared/with-timeout.test.d.ts +1 -0
- package/dist/tools/background-task/delay.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/pdf-extract-figures/index.d.ts +1 -0
- package/dist/tools/pdf-extract-figures/tools.d.ts +11 -0
- package/dist/tools/task/todo-sync.d.ts +1 -1
- package/dist/tools/task/types.d.ts +5 -5
- package/package.json +15 -4
- package/dist/config/schema/model-capabilities.d.ts +0 -8
- package/dist/hooks/runtime-fallback/is-abort-error.d.ts +0 -1
- package/dist/shared/is-object.d.ts +0 -1
- package/dist/shared/model-resolution-types.d.ts +0 -27
- package/dist/shared/model-resolver.d.ts +0 -24
- package/dist/shared/opencode-config-dir-types.d.ts +0 -13
- package/dist/shared/session-model-state.d.ts +0 -6
- package/dist/shared/session-temperature-store.d.ts +0 -3
- package/dist/shared/session-tools-store.d.ts +0 -3
- /package/dist/{hooks/architect → shared}/is-abort-error.d.ts +0 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/cli/index.ts
|
|
5
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
6
|
+
import { dirname, join as join5 } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
// src/cli/doctor/checks/auth.ts
|
|
10
|
+
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
var OPENCODE_CONFIG_PATHS = [
|
|
14
|
+
join(homedir(), ".config", "opencode", "opencode.json"),
|
|
15
|
+
join(homedir(), ".config", "opencode", "opencode.jsonc")
|
|
16
|
+
];
|
|
17
|
+
var AUTH_JSON_PATHS = [
|
|
18
|
+
join(homedir(), ".local", "share", "opencode", "auth.json"),
|
|
19
|
+
join(homedir(), ".config", "opencode", "auth.json")
|
|
20
|
+
];
|
|
21
|
+
function stripJsonComments(text) {
|
|
22
|
+
return text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
23
|
+
}
|
|
24
|
+
function collectConfigText() {
|
|
25
|
+
let combined = "";
|
|
26
|
+
for (const p of OPENCODE_CONFIG_PATHS) {
|
|
27
|
+
if (!existsSync(p))
|
|
28
|
+
continue;
|
|
29
|
+
try {
|
|
30
|
+
const raw = readFileSync(p, "utf-8");
|
|
31
|
+
combined += ` ${stripJsonComments(raw)}`;
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
return combined.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
function collectAuthText() {
|
|
37
|
+
let combined = "";
|
|
38
|
+
for (const p of AUTH_JSON_PATHS) {
|
|
39
|
+
if (!existsSync(p))
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
const raw = readFileSync(p, "utf-8");
|
|
43
|
+
combined += ` ${raw}`;
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const result = Bun.spawnSync(["opencode", "auth", "list", "--json"], { stdout: "pipe", stderr: "pipe" });
|
|
48
|
+
if (result.exitCode === 0)
|
|
49
|
+
combined += ` ${result.stdout.toString()}`;
|
|
50
|
+
} catch {}
|
|
51
|
+
return combined.toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
function hasProvider(haystack, keywords) {
|
|
54
|
+
return keywords.some((k) => haystack.includes(k));
|
|
55
|
+
}
|
|
56
|
+
function getProviderStatuses() {
|
|
57
|
+
const results = [
|
|
58
|
+
{ provider: "Anthropic", configured: false },
|
|
59
|
+
{ provider: "OpenAI", configured: false },
|
|
60
|
+
{ provider: "Google", configured: false }
|
|
61
|
+
];
|
|
62
|
+
const configText = collectConfigText();
|
|
63
|
+
const authText = collectAuthText();
|
|
64
|
+
const combinedAuthConfig = `${configText} ${authText}`;
|
|
65
|
+
const hasAnyConfigFile = OPENCODE_CONFIG_PATHS.some(existsSync) || AUTH_JSON_PATHS.some(existsSync);
|
|
66
|
+
if (!hasAnyConfigFile && !combinedAuthConfig.trim()) {
|
|
67
|
+
if (process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {} else {
|
|
68
|
+
return results.map((r) => ({ ...r, detail: "No opencode config or auth storage found \u2014 run `opencode auth login`" }));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const providerChecks = {
|
|
72
|
+
Anthropic: ["anthropic", "claude"],
|
|
73
|
+
OpenAI: ["openai"],
|
|
74
|
+
Google: ["google", "gemini", "vertex"]
|
|
75
|
+
};
|
|
76
|
+
for (const r of results) {
|
|
77
|
+
const keywords = providerChecks[r.provider] ?? [r.provider.toLowerCase()];
|
|
78
|
+
const inConfig = hasProvider(configText, keywords);
|
|
79
|
+
const inAuth = hasProvider(authText, keywords);
|
|
80
|
+
if (inConfig || inAuth) {
|
|
81
|
+
r.configured = true;
|
|
82
|
+
r.detail = inAuth ? "Provider found in auth storage" : "Provider found in opencode config";
|
|
83
|
+
} else {
|
|
84
|
+
r.detail = "Provider not configured \u2014 run `opencode auth login` or set env var";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
88
|
+
results[0].configured = true;
|
|
89
|
+
results[0].detail = "ANTHROPIC_API_KEY env var set";
|
|
90
|
+
}
|
|
91
|
+
if (process.env.OPENAI_API_KEY) {
|
|
92
|
+
results[1].configured = true;
|
|
93
|
+
results[1].detail = "OPENAI_API_KEY env var set";
|
|
94
|
+
}
|
|
95
|
+
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
96
|
+
results[2].configured = true;
|
|
97
|
+
results[2].detail = "Google/Gemini API key env var set";
|
|
98
|
+
}
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
var authCheck = {
|
|
102
|
+
name: "authentication",
|
|
103
|
+
category: "authentication",
|
|
104
|
+
check: () => {
|
|
105
|
+
const providers = getProviderStatuses();
|
|
106
|
+
const configured = providers.filter((p) => p.configured);
|
|
107
|
+
const unconfigured = providers.filter((p) => !p.configured);
|
|
108
|
+
if (configured.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
name: "authentication",
|
|
111
|
+
status: "fail",
|
|
112
|
+
message: "No API providers configured",
|
|
113
|
+
detail: providers.map((p) => ` ${p.provider}: ${p.detail ?? "not configured"}`).join(`
|
|
114
|
+
`)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const configuredList = configured.map((p) => p.provider).join(", ");
|
|
118
|
+
const warnings = unconfigured.map((p) => p.provider).join(", ");
|
|
119
|
+
if (unconfigured.length > 0) {
|
|
120
|
+
return {
|
|
121
|
+
name: "authentication",
|
|
122
|
+
status: "warn",
|
|
123
|
+
message: `Configured: ${configuredList}. Missing: ${warnings}`,
|
|
124
|
+
detail: `Missing providers:
|
|
125
|
+
${unconfigured.map((p) => ` ${p.provider}: ${p.detail ?? "not configured"}`).join(`
|
|
126
|
+
`)}`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
name: "authentication",
|
|
131
|
+
status: "pass",
|
|
132
|
+
message: `All providers configured: ${configuredList}`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/cli/doctor/checks/config.ts
|
|
138
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
139
|
+
import { homedir as homedir2 } from "os";
|
|
140
|
+
import { join as join2 } from "path";
|
|
141
|
+
var MATRIX_CONFIG_PATHS = [
|
|
142
|
+
join2(process.cwd(), "matrixx.json"),
|
|
143
|
+
join2(process.cwd(), "matrixx.jsonc"),
|
|
144
|
+
join2(process.cwd(), ".opencode", "matrixx.json"),
|
|
145
|
+
join2(homedir2(), ".config", "opencode", "matrixx.json"),
|
|
146
|
+
join2(homedir2(), ".config", "opencode", "matrixx.jsonc")
|
|
147
|
+
];
|
|
148
|
+
function findMatrixxConfig() {
|
|
149
|
+
for (const p of MATRIX_CONFIG_PATHS) {
|
|
150
|
+
if (existsSync2(p)) {
|
|
151
|
+
try {
|
|
152
|
+
const content = readFileSync2(p, "utf-8");
|
|
153
|
+
return { path: p, content };
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
function tryParseJSON(text) {
|
|
162
|
+
try {
|
|
163
|
+
JSON.parse(text);
|
|
164
|
+
return { valid: true };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return { valid: false, error: String(err) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
var configValidationCheck = {
|
|
170
|
+
name: "config-validation",
|
|
171
|
+
category: "configuration",
|
|
172
|
+
check: () => {
|
|
173
|
+
const found = findMatrixxConfig();
|
|
174
|
+
if (!found) {
|
|
175
|
+
return {
|
|
176
|
+
name: "config-validation",
|
|
177
|
+
status: "warn",
|
|
178
|
+
message: "No matrixx configuration file found",
|
|
179
|
+
detail: "Create matrixx.jsonc in your project or ~/.config/opencode/"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const parseResult = tryParseJSON(found.content);
|
|
183
|
+
if (!parseResult.valid) {
|
|
184
|
+
const hasJsoncSigns = found.content.includes("//") || found.content.includes("/*") || found.content.includes(",,");
|
|
185
|
+
if (hasJsoncSigns) {
|
|
186
|
+
return {
|
|
187
|
+
name: "config-validation",
|
|
188
|
+
status: "warn",
|
|
189
|
+
message: `Config file has comments/trailing commas (JSONC), may need JSONC parser`,
|
|
190
|
+
detail: `Path: ${found.path}`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
name: "config-validation",
|
|
195
|
+
status: "fail",
|
|
196
|
+
message: `Invalid JSON in config: ${parseResult.error}`,
|
|
197
|
+
detail: `Path: ${found.path}`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
name: "config-validation",
|
|
202
|
+
status: "pass",
|
|
203
|
+
message: `Configuration file is valid at ${found.path}`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/cli/doctor/checks/mcp.ts
|
|
209
|
+
function checkUvxVersion() {
|
|
210
|
+
try {
|
|
211
|
+
const r = Bun.spawnSync(["uvx", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
212
|
+
if (r.exitCode === 0) {
|
|
213
|
+
return r.stdout.toString().trim().split(`
|
|
214
|
+
`)[0]?.replace(/^uvx\s+/i, "") || null;
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
var mcpPrerequisitesCheck = {
|
|
220
|
+
name: "mcp-prerequisites",
|
|
221
|
+
category: "dependencies",
|
|
222
|
+
check: () => {
|
|
223
|
+
const uvxVersion = checkUvxVersion();
|
|
224
|
+
if (!uvxVersion) {
|
|
225
|
+
return {
|
|
226
|
+
name: "mcp-prerequisites",
|
|
227
|
+
status: "fail",
|
|
228
|
+
message: "uvx (uv) not found \u2014 required for the document_reader MCP",
|
|
229
|
+
detail: `Install uv/uvx: curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
230
|
+
` + `Then run once to warm the markitdown-mcp cache:
|
|
231
|
+
` + " uvx --from markitdown-mcp markitdown-mcp --help"
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const warm = Bun.spawnSync(["uvx", "--from", "markitdown-mcp", "markitdown-mcp", "--help"], { stdout: "pipe", stderr: "pipe", timeout: 120000 });
|
|
236
|
+
const output = `${warm.stdout.toString()}${warm.stderr.toString()}`;
|
|
237
|
+
if (warm.exitCode === 0 || output.includes("MarkItDown")) {
|
|
238
|
+
return {
|
|
239
|
+
name: "mcp-prerequisites",
|
|
240
|
+
status: "pass",
|
|
241
|
+
message: `uvx ${uvxVersion} found; markitdown-mcp cache warm`,
|
|
242
|
+
detail: output.trim() || undefined
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
name: "mcp-prerequisites",
|
|
247
|
+
status: "warn",
|
|
248
|
+
message: `uvx ${uvxVersion} found but markitdown-mcp could not resolve`,
|
|
249
|
+
detail: output.trim() || undefined
|
|
250
|
+
};
|
|
251
|
+
} catch {
|
|
252
|
+
return {
|
|
253
|
+
name: "mcp-prerequisites",
|
|
254
|
+
status: "warn",
|
|
255
|
+
message: `uvx ${uvxVersion} found but warm-up timed out (network?)`,
|
|
256
|
+
detail: "Retry later: uvx --from markitdown-mcp markitdown-mcp --help"
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/cli/doctor/checks/optional.ts
|
|
263
|
+
var BINARY_CHECKS = [
|
|
264
|
+
{
|
|
265
|
+
name: "ast-grep (sg)",
|
|
266
|
+
check: () => {
|
|
267
|
+
try {
|
|
268
|
+
const r = Bun.spawnSync(["sg", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
269
|
+
return { available: r.exitCode === 0, version: r.stdout.toString().trim() };
|
|
270
|
+
} catch {
|
|
271
|
+
return { available: false };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "Gitleaks",
|
|
277
|
+
check: () => {
|
|
278
|
+
try {
|
|
279
|
+
const r = Bun.spawnSync(["gitleaks", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
280
|
+
return { available: r.exitCode === 0, version: r.stdout.toString().trim() };
|
|
281
|
+
} catch {
|
|
282
|
+
return { available: false };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
];
|
|
287
|
+
var PYTHON_CHECKS = [
|
|
288
|
+
{
|
|
289
|
+
name: "PyMuPDF (fitz)",
|
|
290
|
+
check: () => {
|
|
291
|
+
try {
|
|
292
|
+
const r = Bun.spawnSync(["python3", "-c", "import fitz; print(fitz.__version__)"], {
|
|
293
|
+
stdout: "pipe",
|
|
294
|
+
stderr: "pipe"
|
|
295
|
+
});
|
|
296
|
+
return { available: r.exitCode === 0, version: r.stdout.toString().trim() };
|
|
297
|
+
} catch {
|
|
298
|
+
return { available: false };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: "Playwright (CLI)",
|
|
304
|
+
check: () => {
|
|
305
|
+
try {
|
|
306
|
+
const r = Bun.spawnSync(["playwright", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
307
|
+
return { available: r.exitCode === 0, version: r.stdout.toString().trim() };
|
|
308
|
+
} catch {
|
|
309
|
+
return { available: false };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
];
|
|
314
|
+
var optionalToolsCheck = {
|
|
315
|
+
name: "optional-tools",
|
|
316
|
+
category: "tools",
|
|
317
|
+
check: () => {
|
|
318
|
+
const binaryResults = BINARY_CHECKS.map((t) => {
|
|
319
|
+
const result = t.check();
|
|
320
|
+
return { name: t.name, ...result };
|
|
321
|
+
});
|
|
322
|
+
let pythonResults = [];
|
|
323
|
+
try {
|
|
324
|
+
const pyCheck = Bun.spawnSync(["python3", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
325
|
+
if (pyCheck.exitCode === 0) {
|
|
326
|
+
pythonResults = PYTHON_CHECKS.map((t) => {
|
|
327
|
+
const result = t.check();
|
|
328
|
+
return { name: t.name, ...result };
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
pythonResults = PYTHON_CHECKS.map((t) => ({
|
|
332
|
+
name: t.name,
|
|
333
|
+
available: false,
|
|
334
|
+
version: undefined
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
} catch {
|
|
338
|
+
pythonResults = PYTHON_CHECKS.map((t) => ({
|
|
339
|
+
name: t.name,
|
|
340
|
+
available: false,
|
|
341
|
+
version: undefined
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
const allResults = [...binaryResults, ...pythonResults];
|
|
345
|
+
const available = allResults.filter((r) => r.available);
|
|
346
|
+
const missing = allResults.filter((r) => !r.available);
|
|
347
|
+
if (missing.length === 0) {
|
|
348
|
+
return {
|
|
349
|
+
name: "optional-tools",
|
|
350
|
+
status: "pass",
|
|
351
|
+
message: `All optional tools available: ${available.map((r) => r.name).join(", ")}`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const detailLines = allResults.map((r) => {
|
|
355
|
+
if (r.available) {
|
|
356
|
+
return ` \u2713 ${r.name}${r.version ? ` (${r.version})` : ""}`;
|
|
357
|
+
}
|
|
358
|
+
return ` \u2717 ${r.name} \u2014 not installed`;
|
|
359
|
+
});
|
|
360
|
+
const missingList = missing.map((r) => r.name).join(", ");
|
|
361
|
+
return {
|
|
362
|
+
name: "optional-tools",
|
|
363
|
+
status: "warn",
|
|
364
|
+
message: `Available: ${available.length}, Missing: ${missing.length} (${missingList})`,
|
|
365
|
+
detail: detailLines.join(`
|
|
366
|
+
`)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/cli/doctor/checks/plugin.ts
|
|
372
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
373
|
+
import { homedir as homedir3 } from "os";
|
|
374
|
+
import { join as join3 } from "path";
|
|
375
|
+
var OPECODE_CONFIG_PATHS = [
|
|
376
|
+
join3(homedir3(), ".config", "opencode", "opencode.json"),
|
|
377
|
+
join3(homedir3(), ".config", "opencode", "opencode.jsonc")
|
|
378
|
+
];
|
|
379
|
+
function findOpenCodeConfig() {
|
|
380
|
+
for (const p of OPECODE_CONFIG_PATHS) {
|
|
381
|
+
if (existsSync3(p))
|
|
382
|
+
return p;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
function checkOpenCodeVersion() {
|
|
387
|
+
try {
|
|
388
|
+
const result = Bun.spawnSync(["opencode", "--version"], {
|
|
389
|
+
stdout: "pipe",
|
|
390
|
+
stderr: "pipe"
|
|
391
|
+
});
|
|
392
|
+
if (result.exitCode === 0) {
|
|
393
|
+
return result.stdout.toString().trim();
|
|
394
|
+
}
|
|
395
|
+
} catch {}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
var pluginInstallationCheck = {
|
|
399
|
+
name: "plugin-installation",
|
|
400
|
+
category: "installation",
|
|
401
|
+
check: () => {
|
|
402
|
+
const version = checkOpenCodeVersion();
|
|
403
|
+
if (!version) {
|
|
404
|
+
return {
|
|
405
|
+
name: "plugin-installation",
|
|
406
|
+
status: "fail",
|
|
407
|
+
message: "OpenCode CLI not found in PATH",
|
|
408
|
+
detail: "Install OpenCode first: https://opencode.ai/docs"
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const semverMatch = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
412
|
+
if (!semverMatch) {
|
|
413
|
+
return {
|
|
414
|
+
name: "plugin-installation",
|
|
415
|
+
status: "warn",
|
|
416
|
+
message: `OpenCode version: ${version} (unable to parse)`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const major = Number(semverMatch[1]);
|
|
420
|
+
const minor = Number(semverMatch[2]);
|
|
421
|
+
const meetsMinVersion = major >= 1 && minor >= 0;
|
|
422
|
+
const configPath = findOpenCodeConfig();
|
|
423
|
+
if (!configPath) {
|
|
424
|
+
return {
|
|
425
|
+
name: "plugin-installation",
|
|
426
|
+
status: "warn",
|
|
427
|
+
message: `OpenCode ${version} installed, but opencode.json not found`,
|
|
428
|
+
detail: "Run `opencode` once to generate the config file"
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
let configContent;
|
|
432
|
+
try {
|
|
433
|
+
configContent = readFileSync3(configPath, "utf-8");
|
|
434
|
+
} catch {
|
|
435
|
+
return {
|
|
436
|
+
name: "plugin-installation",
|
|
437
|
+
status: "fail",
|
|
438
|
+
message: `Cannot read config: ${configPath}`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const hasPlugin = configContent.includes("opencode-matrixx") || configContent.includes("@klpanagi/opencode-matrixx");
|
|
442
|
+
if (!meetsMinVersion) {
|
|
443
|
+
return {
|
|
444
|
+
name: "plugin-installation",
|
|
445
|
+
status: "fail",
|
|
446
|
+
message: `OpenCode ${version} is below minimum 1.0.150`,
|
|
447
|
+
detail: "Update OpenCode to use Matrixx plugin"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (!hasPlugin) {
|
|
451
|
+
return {
|
|
452
|
+
name: "plugin-installation",
|
|
453
|
+
status: "warn",
|
|
454
|
+
message: `OpenCode ${version} installed, plugin not registered in ${configPath}`,
|
|
455
|
+
detail: "Run `bunx opencode-matrixx install` to register the plugin"
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
name: "plugin-installation",
|
|
460
|
+
status: "pass",
|
|
461
|
+
message: `OpenCode ${version}, plugin registered in ${configPath}`
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/cli/doctor/checks/runtime.ts
|
|
467
|
+
function checkCommand(cmd, arg = "--version") {
|
|
468
|
+
try {
|
|
469
|
+
const result = Bun.spawnSync([cmd, arg], {
|
|
470
|
+
stdout: "pipe",
|
|
471
|
+
stderr: "pipe"
|
|
472
|
+
});
|
|
473
|
+
if (result.exitCode === 0) {
|
|
474
|
+
return result.stdout.toString().trim().split(`
|
|
475
|
+
`)[0] || null;
|
|
476
|
+
}
|
|
477
|
+
} catch {}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
function getRuntimeVersions() {
|
|
481
|
+
const runtimes = [
|
|
482
|
+
{ name: "Bun", version: checkCommand("bun"), status: "fail" },
|
|
483
|
+
{ name: "Node.js", version: checkCommand("node"), status: "fail" },
|
|
484
|
+
{ name: "Git", version: checkCommand("git"), status: "fail" },
|
|
485
|
+
{ name: "Python3", version: checkCommand("python3", "--version"), status: "fail" }
|
|
486
|
+
];
|
|
487
|
+
for (const r of runtimes) {
|
|
488
|
+
if (r.version) {
|
|
489
|
+
r.status = "pass";
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return runtimes;
|
|
493
|
+
}
|
|
494
|
+
var runtimeDepsCheck = {
|
|
495
|
+
name: "runtime-dependencies",
|
|
496
|
+
category: "dependencies",
|
|
497
|
+
check: () => {
|
|
498
|
+
const runtimes = getRuntimeVersions();
|
|
499
|
+
const passed = runtimes.filter((r) => r.status === "pass");
|
|
500
|
+
const failed = runtimes.filter((r) => r.status === "fail");
|
|
501
|
+
if (failed.length === 0) {
|
|
502
|
+
return {
|
|
503
|
+
name: "runtime-dependencies",
|
|
504
|
+
status: "pass",
|
|
505
|
+
message: `All runtimes found: ${passed.map((r) => `${r.name} ${r.version}`).join(", ")}`
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const missingList = failed.map((r) => r.name).join(", ");
|
|
509
|
+
const detailLines = [];
|
|
510
|
+
for (const r of runtimes) {
|
|
511
|
+
const status = r.version ? `${r.version}` : "NOT FOUND";
|
|
512
|
+
detailLines.push(` ${r.name}: ${status}`);
|
|
513
|
+
}
|
|
514
|
+
if (failed.length <= passed.length) {
|
|
515
|
+
return {
|
|
516
|
+
name: "runtime-dependencies",
|
|
517
|
+
status: "warn",
|
|
518
|
+
message: `Found: ${passed.map((r) => r.name).join(", ")}. Missing: ${missingList}`,
|
|
519
|
+
detail: detailLines.join(`
|
|
520
|
+
`)
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
name: "runtime-dependencies",
|
|
525
|
+
status: "fail",
|
|
526
|
+
message: `Critical runtimes missing: ${missingList}`,
|
|
527
|
+
detail: detailLines.join(`
|
|
528
|
+
`)
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/cli/doctor/checks/index.ts
|
|
534
|
+
var ALL_CHECKS = [
|
|
535
|
+
pluginInstallationCheck,
|
|
536
|
+
configValidationCheck,
|
|
537
|
+
authCheck,
|
|
538
|
+
runtimeDepsCheck,
|
|
539
|
+
optionalToolsCheck,
|
|
540
|
+
mcpPrerequisitesCheck
|
|
541
|
+
];
|
|
542
|
+
function getChecksByCategory(category) {
|
|
543
|
+
return ALL_CHECKS.filter((c) => c.category === category);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/cli/doctor/format.ts
|
|
547
|
+
var STATUS_ICONS = {
|
|
548
|
+
pass: "\u2713",
|
|
549
|
+
warn: "\u26A0",
|
|
550
|
+
fail: "\u2717"
|
|
551
|
+
};
|
|
552
|
+
function padRight(s, len) {
|
|
553
|
+
return s.length >= len ? s : s + " ".repeat(len - s.length);
|
|
554
|
+
}
|
|
555
|
+
function formatDoctorReport(report) {
|
|
556
|
+
const lines = [];
|
|
557
|
+
const boxWidth = 60;
|
|
558
|
+
lines.push("");
|
|
559
|
+
lines.push(`\u250C${"\u2500".repeat(boxWidth - 2)}\u2510`);
|
|
560
|
+
lines.push(`\u2502${padRight(" Matrixx Doctor", boxWidth - 1)}\u2502`);
|
|
561
|
+
lines.push(`\u2514${"\u2500".repeat(boxWidth - 2)}\u2518`);
|
|
562
|
+
lines.push("");
|
|
563
|
+
const grouped = new Map;
|
|
564
|
+
for (const check of report.checks) {
|
|
565
|
+
const cat = check.name.split("-")[0] || "other";
|
|
566
|
+
if (!grouped.has(cat))
|
|
567
|
+
grouped.set(cat, []);
|
|
568
|
+
grouped.get(cat)?.push(check);
|
|
569
|
+
}
|
|
570
|
+
for (const [category, checks] of grouped) {
|
|
571
|
+
lines.push(category.charAt(0).toUpperCase() + category.slice(1));
|
|
572
|
+
for (const c of checks) {
|
|
573
|
+
const icon = STATUS_ICONS[c.status] || "?";
|
|
574
|
+
const label = padRight(` ${icon} ${c.message}`, boxWidth - 4);
|
|
575
|
+
lines.push(label);
|
|
576
|
+
if (c.detail) {
|
|
577
|
+
const detailLines = c.detail.split(`
|
|
578
|
+
`);
|
|
579
|
+
for (const dl of detailLines) {
|
|
580
|
+
lines.push(` ${dl}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
lines.push("");
|
|
585
|
+
}
|
|
586
|
+
const s = report.summary;
|
|
587
|
+
lines.push(`Summary: ${s.passed} passed, ${s.warnings} warning(s), ${s.failed} failed`);
|
|
588
|
+
lines.push("");
|
|
589
|
+
return lines.join(`
|
|
590
|
+
`);
|
|
591
|
+
}
|
|
592
|
+
function formatDoctorJson(report) {
|
|
593
|
+
return JSON.stringify(report, null, 2);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/cli/doctor/index.ts
|
|
597
|
+
async function runDoctor(options = {}) {
|
|
598
|
+
const checks = options.category ? getChecksByCategory(options.category) : ALL_CHECKS;
|
|
599
|
+
if (checks.length === 0) {
|
|
600
|
+
const _available = [...new Set(ALL_CHECKS.map((c) => c.category))];
|
|
601
|
+
return {
|
|
602
|
+
timestamp: new Date().toISOString(),
|
|
603
|
+
checks: [],
|
|
604
|
+
summary: { total: 0, passed: 0, warnings: 0, failed: 0 }
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const results = [];
|
|
608
|
+
for (const check of checks) {
|
|
609
|
+
try {
|
|
610
|
+
const result = await Promise.resolve(check.check());
|
|
611
|
+
result.category = check.category;
|
|
612
|
+
results.push(result);
|
|
613
|
+
} catch (err) {
|
|
614
|
+
results.push({
|
|
615
|
+
name: check.name,
|
|
616
|
+
category: check.category,
|
|
617
|
+
status: "fail",
|
|
618
|
+
message: `Check threw an exception: ${err instanceof Error ? err.message : String(err)}`
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const passed = results.filter((r) => r.status === "pass").length;
|
|
623
|
+
const warnings = results.filter((r) => r.status === "warn").length;
|
|
624
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
625
|
+
return {
|
|
626
|
+
timestamp: new Date().toISOString(),
|
|
627
|
+
checks: results,
|
|
628
|
+
summary: { total: results.length, passed, warnings, failed }
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function executeDoctor(options = {}) {
|
|
632
|
+
const report = await runDoctor(options);
|
|
633
|
+
if (options.json) {
|
|
634
|
+
return formatDoctorJson(report);
|
|
635
|
+
}
|
|
636
|
+
return formatDoctorReport(report);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/cli/install/index.ts
|
|
640
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
641
|
+
import { homedir as homedir4 } from "os";
|
|
642
|
+
import { join as join4 } from "path";
|
|
643
|
+
function getOpenCodeConfigPath() {
|
|
644
|
+
const configDir = join4(homedir4(), ".config", "opencode");
|
|
645
|
+
const jsonPath = join4(configDir, "opencode.json");
|
|
646
|
+
const jsoncPath = join4(configDir, "opencode.jsonc");
|
|
647
|
+
if (existsSync4(jsoncPath))
|
|
648
|
+
return jsoncPath;
|
|
649
|
+
return jsonPath;
|
|
650
|
+
}
|
|
651
|
+
function stripJsonComments2(text) {
|
|
652
|
+
return text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
653
|
+
}
|
|
654
|
+
function parseOpenCodeConfig(content) {
|
|
655
|
+
try {
|
|
656
|
+
const stripped = stripJsonComments2(content);
|
|
657
|
+
return JSON.parse(stripped);
|
|
658
|
+
} catch {
|
|
659
|
+
return {};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function hasExistingPluginRegistration(configPath) {
|
|
663
|
+
if (!existsSync4(configPath))
|
|
664
|
+
return false;
|
|
665
|
+
try {
|
|
666
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
667
|
+
return content.includes("opencode-matrixx");
|
|
668
|
+
} catch {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function isLocalRepoCheckout(dir) {
|
|
673
|
+
return existsSync4(join4(dir, "src", "index.ts")) && existsSync4(join4(dir, "package.json"));
|
|
674
|
+
}
|
|
675
|
+
function generatePluginEntry(absolutePath, forceLocal) {
|
|
676
|
+
const looksLocal = forceLocal || isLocalRepoCheckout(absolutePath);
|
|
677
|
+
if (looksLocal) {
|
|
678
|
+
const distEntry = join4(absolutePath, "dist", "index.js");
|
|
679
|
+
if (existsSync4(distEntry))
|
|
680
|
+
return `file://${distEntry}`;
|
|
681
|
+
if (existsSync4(absolutePath))
|
|
682
|
+
return `file://${absolutePath}`;
|
|
683
|
+
}
|
|
684
|
+
return "opencode-matrixx";
|
|
685
|
+
}
|
|
686
|
+
async function executeInstall(options) {
|
|
687
|
+
const configDir = join4(homedir4(), ".config", "opencode");
|
|
688
|
+
const configPath = getOpenCodeConfigPath();
|
|
689
|
+
if (!existsSync4(configDir)) {
|
|
690
|
+
mkdirSync(configDir, { recursive: true });
|
|
691
|
+
}
|
|
692
|
+
if (hasExistingPluginRegistration(configPath)) {
|
|
693
|
+
return `\u2713 Plugin already registered in ${configPath}`;
|
|
694
|
+
}
|
|
695
|
+
const cwd = process.cwd();
|
|
696
|
+
const pluginEntry = generatePluginEntry(cwd, options.local);
|
|
697
|
+
let config;
|
|
698
|
+
if (existsSync4(configPath)) {
|
|
699
|
+
try {
|
|
700
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
701
|
+
config = parseOpenCodeConfig(content);
|
|
702
|
+
} catch {
|
|
703
|
+
config = {};
|
|
704
|
+
}
|
|
705
|
+
} else {
|
|
706
|
+
config = {};
|
|
707
|
+
}
|
|
708
|
+
if (!config.plugin) {
|
|
709
|
+
config.plugin = [];
|
|
710
|
+
}
|
|
711
|
+
if (!config.plugin.includes(pluginEntry)) {
|
|
712
|
+
config.plugin.push(pluginEntry);
|
|
713
|
+
}
|
|
714
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
715
|
+
`);
|
|
716
|
+
const flags = [];
|
|
717
|
+
if (options.claude)
|
|
718
|
+
flags.push(`Claude: ${options.claude}`);
|
|
719
|
+
if (options.openai)
|
|
720
|
+
flags.push(`OpenAI: ${options.openai}`);
|
|
721
|
+
if (options.gemini)
|
|
722
|
+
flags.push(`Gemini: ${options.gemini}`);
|
|
723
|
+
if (options.copilot)
|
|
724
|
+
flags.push(`Copilot: ${options.copilot}`);
|
|
725
|
+
if (options.opencodeZen)
|
|
726
|
+
flags.push(`OpenCode Zen: ${options.opencodeZen}`);
|
|
727
|
+
if (options.zaiCodingPlan)
|
|
728
|
+
flags.push(`Z.ai: ${options.zaiCodingPlan}`);
|
|
729
|
+
const parts = [
|
|
730
|
+
"",
|
|
731
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
732
|
+
"\u2502 Matrixx Install Complete \u2502",
|
|
733
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
734
|
+
"",
|
|
735
|
+
`\u2713 Plugin registered in ${configPath}`,
|
|
736
|
+
` Entry: ${pluginEntry}`,
|
|
737
|
+
""
|
|
738
|
+
];
|
|
739
|
+
if (flags.length > 0) {
|
|
740
|
+
parts.push(" Subscriptions:");
|
|
741
|
+
for (const f of flags) {
|
|
742
|
+
parts.push(` ${f}`);
|
|
743
|
+
}
|
|
744
|
+
parts.push("");
|
|
745
|
+
}
|
|
746
|
+
parts.push(" Next steps:");
|
|
747
|
+
parts.push(" 1. Configure authentication via `opencode auth login`");
|
|
748
|
+
parts.push(" 2. Verify with `bunx opencode-matrixx doctor`");
|
|
749
|
+
if (pluginEntry === "opencode-matrixx") {
|
|
750
|
+
parts.push(" 3. Restart OpenCode to load the plugin");
|
|
751
|
+
} else {
|
|
752
|
+
parts.push(" 3. Run `bun run build` in the repo, then restart OpenCode");
|
|
753
|
+
}
|
|
754
|
+
parts.push("");
|
|
755
|
+
return parts.join(`
|
|
756
|
+
`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/cli/index.ts
|
|
760
|
+
function getVersion() {
|
|
761
|
+
try {
|
|
762
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
763
|
+
const candidates = [
|
|
764
|
+
join5(thisDir, "../../package.json"),
|
|
765
|
+
join5(thisDir, "../package.json"),
|
|
766
|
+
"package.json"
|
|
767
|
+
];
|
|
768
|
+
for (const p of candidates) {
|
|
769
|
+
try {
|
|
770
|
+
const raw = readFileSync5(p, "utf-8");
|
|
771
|
+
const pkg = JSON.parse(raw);
|
|
772
|
+
if (pkg.version && typeof pkg.version === "string")
|
|
773
|
+
return pkg.version;
|
|
774
|
+
} catch {}
|
|
775
|
+
}
|
|
776
|
+
} catch {}
|
|
777
|
+
return "2.6.0";
|
|
778
|
+
}
|
|
779
|
+
var VERSION = getVersion();
|
|
780
|
+
function printHelp() {
|
|
781
|
+
console.log(`
|
|
782
|
+
Matrixx CLI v${VERSION}
|
|
783
|
+
|
|
784
|
+
Usage: bunx opencode-matrixx <command> [options]
|
|
785
|
+
|
|
786
|
+
Commands:
|
|
787
|
+
doctor Environment diagnostics and health checks
|
|
788
|
+
install Interactive setup wizard
|
|
789
|
+
version Display version information
|
|
790
|
+
help Display this help message
|
|
791
|
+
|
|
792
|
+
Options:
|
|
793
|
+
--help, -h Show help for any command
|
|
794
|
+
--json (doctor) Output results as JSON
|
|
795
|
+
--category (doctor) Check specific category only
|
|
796
|
+
--no-tui (install) Run in non-interactive mode (CI/CD)
|
|
797
|
+
--local (install) Use local repo file:// path (dev only)
|
|
798
|
+
--verbose (install) Display detailed logs
|
|
799
|
+
|
|
800
|
+
Doctor categories:
|
|
801
|
+
installation Plugin registration and OpenCode version
|
|
802
|
+
configuration Config file validation
|
|
803
|
+
authentication Provider API key status
|
|
804
|
+
dependencies Runtime dependencies (Bun, Node, Git, Python)
|
|
805
|
+
tools Optional tools (ast-grep, Gitleaks, PyMuPDF, Playwright)
|
|
806
|
+
|
|
807
|
+
Subscription flags (install --no-tui):
|
|
808
|
+
--claude=<yes|no|max20>
|
|
809
|
+
--openai=<yes|no>
|
|
810
|
+
--gemini=<yes|no>
|
|
811
|
+
--copilot=<yes|no>
|
|
812
|
+
--opencode-zen=<yes|no>
|
|
813
|
+
--zai-coding-plan=<yes|no>
|
|
814
|
+
`);
|
|
815
|
+
}
|
|
816
|
+
function parseArgs(argv) {
|
|
817
|
+
const result = {
|
|
818
|
+
command: "",
|
|
819
|
+
help: false,
|
|
820
|
+
json: false,
|
|
821
|
+
noTui: false,
|
|
822
|
+
verbose: false,
|
|
823
|
+
local: false,
|
|
824
|
+
extra: []
|
|
825
|
+
};
|
|
826
|
+
let args = argv.slice(2);
|
|
827
|
+
if (args.length === 0) {
|
|
828
|
+
result.help = true;
|
|
829
|
+
return result;
|
|
830
|
+
}
|
|
831
|
+
result.command = args[0];
|
|
832
|
+
args = args.slice(1);
|
|
833
|
+
for (let i = 0;i < args.length; i++) {
|
|
834
|
+
const a = args[i];
|
|
835
|
+
if (a === "--help" || a === "-h") {
|
|
836
|
+
result.help = true;
|
|
837
|
+
} else if (a === "--json") {
|
|
838
|
+
result.json = true;
|
|
839
|
+
} else if (a === "--no-tui") {
|
|
840
|
+
result.noTui = true;
|
|
841
|
+
} else if (a === "--local") {
|
|
842
|
+
result.local = true;
|
|
843
|
+
} else if (a === "--verbose") {
|
|
844
|
+
result.verbose = true;
|
|
845
|
+
} else if (a.startsWith("--category=")) {
|
|
846
|
+
result.category = a.slice("--category=".length);
|
|
847
|
+
} else if (a === "--category" && i + 1 < args.length) {
|
|
848
|
+
result.category = args[++i];
|
|
849
|
+
} else if (a.startsWith("--claude=")) {
|
|
850
|
+
const v = a.slice("--claude=".length);
|
|
851
|
+
if (["yes", "no", "max20"].includes(v))
|
|
852
|
+
result.claude = v;
|
|
853
|
+
} else if (a.startsWith("--openai=")) {
|
|
854
|
+
const v = a.slice("--openai=".length);
|
|
855
|
+
if (["yes", "no"].includes(v))
|
|
856
|
+
result.openai = v;
|
|
857
|
+
} else if (a.startsWith("--gemini=")) {
|
|
858
|
+
const v = a.slice("--gemini=".length);
|
|
859
|
+
if (["yes", "no"].includes(v))
|
|
860
|
+
result.gemini = v;
|
|
861
|
+
} else if (a.startsWith("--copilot=")) {
|
|
862
|
+
const v = a.slice("--copilot=".length);
|
|
863
|
+
if (["yes", "no"].includes(v))
|
|
864
|
+
result.copilot = v;
|
|
865
|
+
} else if (a.startsWith("--opencode-zen=")) {
|
|
866
|
+
const v = a.slice("--opencode-zen=".length);
|
|
867
|
+
if (["yes", "no"].includes(v))
|
|
868
|
+
result.opencodeZen = v;
|
|
869
|
+
} else if (a.startsWith("--zai-coding-plan=")) {
|
|
870
|
+
const v = a.slice("--zai-coding-plan=".length);
|
|
871
|
+
if (["yes", "no"].includes(v))
|
|
872
|
+
result.zaiCodingPlan = v;
|
|
873
|
+
} else {
|
|
874
|
+
result.extra.push(a);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
async function main() {
|
|
880
|
+
const args = parseArgs(process.argv);
|
|
881
|
+
if (!args.command || args.command === "help" || args.help) {
|
|
882
|
+
printHelp();
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (args.command === "version") {
|
|
886
|
+
console.log(VERSION);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (args.command === "doctor") {
|
|
890
|
+
const output = await executeDoctor({
|
|
891
|
+
category: args.category,
|
|
892
|
+
json: args.json
|
|
893
|
+
});
|
|
894
|
+
console.log(output);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (args.command === "install") {
|
|
898
|
+
const output = await executeInstall({
|
|
899
|
+
noTui: args.noTui,
|
|
900
|
+
verbose: args.verbose,
|
|
901
|
+
local: args.local,
|
|
902
|
+
claude: args.claude,
|
|
903
|
+
openai: args.openai,
|
|
904
|
+
gemini: args.gemini,
|
|
905
|
+
copilot: args.copilot,
|
|
906
|
+
opencodeZen: args.opencodeZen,
|
|
907
|
+
zaiCodingPlan: args.zaiCodingPlan
|
|
908
|
+
});
|
|
909
|
+
console.log(output);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.error(`Unknown command: ${args.command}
|
|
913
|
+
`);
|
|
914
|
+
printHelp();
|
|
915
|
+
process.exit(1);
|
|
916
|
+
}
|
|
917
|
+
var cli_default = main;
|
|
918
|
+
if (import.meta.main) {
|
|
919
|
+
main().catch((err) => {
|
|
920
|
+
console.error("Fatal error:", err);
|
|
921
|
+
process.exit(1);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
export {
|
|
925
|
+
cli_default as default,
|
|
926
|
+
main
|
|
927
|
+
};
|