kraken-code 1.1.3 → 1.2.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/CHANGELOG.md +53 -0
- package/README.md +237 -24
- package/assets/kraken-code.schema.json +2107 -0
- package/dist/cli/index.js +36 -1499
- package/dist/google-auth.js +52 -4611
- package/dist/index.js +146 -23823
- package/dist/kraken-code.schema.json +804 -562
- package/docs/ARCHITECTURE.md +68 -0
- package/docs/BLITZKRIEG_MODE.md +85 -0
- package/docs/EXAMPLES.md +74 -0
- package/docs/FAQ.md +21 -0
- package/docs/SKILLS_GUIDE.md +45 -0
- package/docs/TROUBLESHOOTING.md +29 -0
- package/package.json +31 -11
- package/scripts/install-curl.sh +111 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,1504 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
skip: color.dim
|
|
24
|
-
};
|
|
25
|
-
var CHECK_IDS = {
|
|
26
|
-
OPENCODE_INSTALLATION: "opencode-installation",
|
|
27
|
-
PLUGIN_REGISTRATION: "plugin-registration",
|
|
28
|
-
CONFIG_VALIDATION: "config-validation",
|
|
29
|
-
AUTH_ANTHROPIC: "auth-anthropic",
|
|
30
|
-
AUTH_OPENAI: "auth-openai",
|
|
31
|
-
AUTH_GOOGLE: "auth-google",
|
|
32
|
-
DEP_AST_GREP_CLI: "dep-ast-grep-cli",
|
|
33
|
-
DEP_AST_GREP_NAPI: "dep-ast-grep-napi",
|
|
34
|
-
DEP_COMMENT_CHECKER: "dep-comment-checker",
|
|
35
|
-
GH_CLI: "gh-cli",
|
|
36
|
-
LSP_SERVERS: "lsp-servers",
|
|
37
|
-
MCP_BUILTIN: "mcp-builtin",
|
|
38
|
-
MCP_USER: "mcp-user",
|
|
39
|
-
VERSION_STATUS: "version-status"
|
|
40
|
-
};
|
|
41
|
-
var CHECK_NAMES = {
|
|
42
|
-
[CHECK_IDS.OPENCODE_INSTALLATION]: "OpenCode Installation",
|
|
43
|
-
[CHECK_IDS.PLUGIN_REGISTRATION]: "Plugin Registration",
|
|
44
|
-
[CHECK_IDS.CONFIG_VALIDATION]: "Configuration Validity",
|
|
45
|
-
[CHECK_IDS.AUTH_ANTHROPIC]: "Anthropic (Claude) Auth",
|
|
46
|
-
[CHECK_IDS.AUTH_OPENAI]: "OpenAI (ChatGPT) Auth",
|
|
47
|
-
[CHECK_IDS.AUTH_GOOGLE]: "Google (Gemini) Auth",
|
|
48
|
-
[CHECK_IDS.DEP_AST_GREP_CLI]: "AST-Grep CLI",
|
|
49
|
-
[CHECK_IDS.DEP_AST_GREP_NAPI]: "AST-Grep NAPI",
|
|
50
|
-
[CHECK_IDS.DEP_COMMENT_CHECKER]: "Comment Checker",
|
|
51
|
-
[CHECK_IDS.GH_CLI]: "GitHub CLI",
|
|
52
|
-
[CHECK_IDS.LSP_SERVERS]: "LSP Servers",
|
|
53
|
-
[CHECK_IDS.MCP_BUILTIN]: "Built-in MCP Servers",
|
|
54
|
-
[CHECK_IDS.MCP_USER]: "User MCP Configuration",
|
|
55
|
-
[CHECK_IDS.VERSION_STATUS]: "Version Status"
|
|
56
|
-
};
|
|
57
|
-
var CATEGORY_NAMES = {
|
|
58
|
-
installation: "Installation",
|
|
59
|
-
configuration: "Configuration",
|
|
60
|
-
authentication: "Authentication",
|
|
61
|
-
dependencies: "Dependencies",
|
|
62
|
-
tools: "Tools & Servers",
|
|
63
|
-
updates: "Updates"
|
|
64
|
-
};
|
|
65
|
-
var EXIT_CODES = {
|
|
66
|
-
SUCCESS: 0,
|
|
67
|
-
FAILURE: 1
|
|
68
|
-
};
|
|
69
|
-
var MIN_OPENCODE_VERSION = "1.0.150";
|
|
70
|
-
var PACKAGE_NAME = "kraken-code";
|
|
71
|
-
var OPENCODE_BINARIES = ["opencode", "opencode-desktop"];
|
|
72
|
-
|
|
73
|
-
// src/cli/doctor/checks/opencode.ts
|
|
74
|
-
async function findOpenCodeBinary() {
|
|
75
|
-
for (const binary of OPENCODE_BINARIES) {
|
|
76
|
-
try {
|
|
77
|
-
const proc = Bun.spawn(["which", binary], { stdout: "pipe", stderr: "pipe" });
|
|
78
|
-
const output = await new Response(proc.stdout).text();
|
|
79
|
-
await proc.exited;
|
|
80
|
-
if (proc.exitCode === 0) {
|
|
81
|
-
return { binary, path: output.trim() };
|
|
82
|
-
}
|
|
83
|
-
} catch {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
async function getOpenCodeVersion(binary) {
|
|
90
|
-
try {
|
|
91
|
-
const proc = Bun.spawn([binary, "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
92
|
-
const output = await new Response(proc.stdout).text();
|
|
93
|
-
await proc.exited;
|
|
94
|
-
if (proc.exitCode === 0) {
|
|
95
|
-
return output.trim();
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
function compareVersions(current, minimum) {
|
|
103
|
-
const parseVersion = (v) => {
|
|
104
|
-
const cleaned = v.replace(/^v/, "").split("-")[0];
|
|
105
|
-
return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
|
|
106
|
-
};
|
|
107
|
-
const curr = parseVersion(current);
|
|
108
|
-
const min = parseVersion(minimum);
|
|
109
|
-
for (let i = 0;i < Math.max(curr.length, min.length); i++) {
|
|
110
|
-
const c = curr[i] ?? 0;
|
|
111
|
-
const m = min[i] ?? 0;
|
|
112
|
-
if (c > m)
|
|
113
|
-
return true;
|
|
114
|
-
if (c < m)
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
async function getOpenCodeInfo() {
|
|
120
|
-
const binaryInfo = await findOpenCodeBinary();
|
|
121
|
-
if (!binaryInfo) {
|
|
122
|
-
return {
|
|
123
|
-
installed: false,
|
|
124
|
-
version: null,
|
|
125
|
-
path: null,
|
|
126
|
-
binary: null
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
const version = await getOpenCodeVersion(binaryInfo.binary);
|
|
130
|
-
return {
|
|
131
|
-
installed: true,
|
|
132
|
-
version,
|
|
133
|
-
path: binaryInfo.path,
|
|
134
|
-
binary: binaryInfo.binary
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
async function checkOpenCodeInstallation() {
|
|
138
|
-
const info = await getOpenCodeInfo();
|
|
139
|
-
if (!info.installed) {
|
|
140
|
-
return {
|
|
141
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
142
|
-
status: "fail",
|
|
143
|
-
message: "OpenCode is not installed",
|
|
144
|
-
details: [
|
|
145
|
-
"Visit: https://opencode.ai/docs for installation instructions",
|
|
146
|
-
"Run: npm install -g opencode"
|
|
147
|
-
]
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
if (info.version && !compareVersions(info.version, MIN_OPENCODE_VERSION)) {
|
|
151
|
-
return {
|
|
152
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
153
|
-
status: "warn",
|
|
154
|
-
message: `Version ${info.version} is below minimum ${MIN_OPENCODE_VERSION}`,
|
|
155
|
-
details: [
|
|
156
|
-
`Current: ${info.version}`,
|
|
157
|
-
`Required: >= ${MIN_OPENCODE_VERSION}`,
|
|
158
|
-
"Run: npm update -g opencode"
|
|
159
|
-
]
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
164
|
-
status: "pass",
|
|
165
|
-
message: info.version ?? "installed",
|
|
166
|
-
details: info.path ? [`Path: ${info.path}`] : undefined
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function getOpenCodeCheckDefinition() {
|
|
170
|
-
return {
|
|
171
|
-
id: CHECK_IDS.OPENCODE_INSTALLATION,
|
|
172
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
173
|
-
category: "installation",
|
|
174
|
-
check: checkOpenCodeInstallation,
|
|
175
|
-
critical: true
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// src/cli/doctor/checks/plugin.ts
|
|
180
|
-
import { existsSync, readFileSync } from "fs";
|
|
181
|
-
import * as jsoncParser from "jsonc-parser";
|
|
182
|
-
import path from "path";
|
|
183
|
-
import os from "os";
|
|
184
|
-
function getOpenCodeConfigPaths() {
|
|
185
|
-
const crossPlatformDir = path.join(os.homedir(), ".config", "opencode");
|
|
186
|
-
return {
|
|
187
|
-
configJson: path.join(crossPlatformDir, "opencode.json"),
|
|
188
|
-
configJsonc: path.join(crossPlatformDir, "opencode.jsonc")
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function parseJsonc(content) {
|
|
192
|
-
const errors = [];
|
|
193
|
-
const result = jsoncParser.parse(content, errors, { allowTrailingComma: true });
|
|
194
|
-
if (errors.length > 0) {
|
|
195
|
-
throw new Error(`JSONC parse error: ${errors[0].error}`);
|
|
196
|
-
}
|
|
197
|
-
return result;
|
|
198
|
-
}
|
|
199
|
-
function detectConfigPath() {
|
|
200
|
-
const paths = getOpenCodeConfigPaths();
|
|
201
|
-
if (existsSync(paths.configJsonc)) {
|
|
202
|
-
return { path: paths.configJsonc, format: "jsonc" };
|
|
203
|
-
}
|
|
204
|
-
if (existsSync(paths.configJson)) {
|
|
205
|
-
return { path: paths.configJson, format: "json" };
|
|
206
|
-
}
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
function findPluginEntry(plugins) {
|
|
210
|
-
for (const plugin of plugins) {
|
|
211
|
-
if (plugin === PACKAGE_NAME || plugin.startsWith(`${PACKAGE_NAME}@`)) {
|
|
212
|
-
const isPinned = plugin.includes("@");
|
|
213
|
-
const version = isPinned ? plugin.split("@")[1] : null;
|
|
214
|
-
return { entry: plugin, isPinned, version };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
function getPluginInfo() {
|
|
220
|
-
const configInfo = detectConfigPath();
|
|
221
|
-
if (!configInfo) {
|
|
222
|
-
return {
|
|
223
|
-
registered: false,
|
|
224
|
-
configPath: null,
|
|
225
|
-
entry: null,
|
|
226
|
-
isPinned: false,
|
|
227
|
-
pinnedVersion: null
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
const content = readFileSync(configInfo.path, "utf-8");
|
|
232
|
-
const config = parseJsonc(content);
|
|
233
|
-
const plugins = config.plugin ?? [];
|
|
234
|
-
const pluginEntry = findPluginEntry(plugins);
|
|
235
|
-
if (!pluginEntry) {
|
|
236
|
-
return {
|
|
237
|
-
registered: false,
|
|
238
|
-
configPath: configInfo.path,
|
|
239
|
-
entry: null,
|
|
240
|
-
isPinned: false,
|
|
241
|
-
pinnedVersion: null
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
registered: true,
|
|
246
|
-
configPath: configInfo.path,
|
|
247
|
-
entry: pluginEntry.entry,
|
|
248
|
-
isPinned: pluginEntry.isPinned,
|
|
249
|
-
pinnedVersion: pluginEntry.version
|
|
250
|
-
};
|
|
251
|
-
} catch {
|
|
252
|
-
return {
|
|
253
|
-
registered: false,
|
|
254
|
-
configPath: configInfo.path,
|
|
255
|
-
entry: null,
|
|
256
|
-
isPinned: false,
|
|
257
|
-
pinnedVersion: null
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
async function checkPluginRegistration() {
|
|
262
|
-
const info = getPluginInfo();
|
|
263
|
-
if (!info.configPath) {
|
|
264
|
-
const expectedPaths = getOpenCodeConfigPaths();
|
|
265
|
-
return {
|
|
266
|
-
name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
|
|
267
|
-
status: "fail",
|
|
268
|
-
message: "OpenCode config file not found",
|
|
269
|
-
details: [
|
|
270
|
-
"Run: kraken-code install",
|
|
271
|
-
`Expected: ${expectedPaths.configJson} or ${expectedPaths.configJsonc}`
|
|
272
|
-
]
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
if (!info.registered) {
|
|
276
|
-
return {
|
|
277
|
-
name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
|
|
278
|
-
status: "fail",
|
|
279
|
-
message: "Plugin not registered in config",
|
|
280
|
-
details: [
|
|
281
|
-
"Run: kraken-code install",
|
|
282
|
-
`Config: ${info.configPath}`
|
|
283
|
-
]
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
const message = info.isPinned ? `Registered (pinned: ${info.pinnedVersion})` : "Registered";
|
|
287
|
-
return {
|
|
288
|
-
name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
|
|
289
|
-
status: "pass",
|
|
290
|
-
message,
|
|
291
|
-
details: [`Config: ${info.configPath}`]
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
function getPluginCheckDefinition() {
|
|
295
|
-
return {
|
|
296
|
-
id: CHECK_IDS.PLUGIN_REGISTRATION,
|
|
297
|
-
name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
|
|
298
|
-
category: "installation",
|
|
299
|
-
check: checkPluginRegistration,
|
|
300
|
-
critical: true
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// src/cli/doctor/checks/config.ts
|
|
305
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
306
|
-
import * as jsoncParser2 from "jsonc-parser";
|
|
307
|
-
import path2 from "path";
|
|
308
|
-
import os2 from "os";
|
|
309
|
-
function getOpenCodeConfigPaths2() {
|
|
310
|
-
const crossPlatformDir = path2.join(os2.homedir(), ".config", "opencode");
|
|
311
|
-
return {
|
|
312
|
-
configJson: path2.join(crossPlatformDir, "opencode.json"),
|
|
313
|
-
configJsonc: path2.join(crossPlatformDir, "opencode.jsonc")
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
function parseJsonc2(content) {
|
|
317
|
-
const errors = [];
|
|
318
|
-
const result = jsoncParser2.parse(content, errors, { allowTrailingComma: true });
|
|
319
|
-
if (errors.length > 0) {
|
|
320
|
-
throw new Error(`JSONC parse error: ${errors[0].error}`);
|
|
321
|
-
}
|
|
322
|
-
return result;
|
|
323
|
-
}
|
|
324
|
-
async function checkConfigValidity() {
|
|
325
|
-
const paths = getOpenCodeConfigPaths2();
|
|
326
|
-
const configPath = paths.configJsonc || paths.configJson;
|
|
327
|
-
if (!existsSync2(configPath)) {
|
|
328
|
-
return {
|
|
329
|
-
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
330
|
-
status: "fail",
|
|
331
|
-
message: "OpenCode config file not found",
|
|
332
|
-
details: [`Expected: ${configPath}`]
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
const content = readFileSync2(configPath, "utf-8");
|
|
337
|
-
parseJsonc2(content);
|
|
338
|
-
return {
|
|
339
|
-
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
340
|
-
status: "pass",
|
|
341
|
-
message: "Config file is valid JSONC",
|
|
342
|
-
details: [`Config: ${configPath}`]
|
|
343
|
-
};
|
|
344
|
-
} catch (error) {
|
|
345
|
-
return {
|
|
346
|
-
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
347
|
-
status: "fail",
|
|
348
|
-
message: `Config parse error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
349
|
-
details: [`Config: ${configPath}`]
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
function getConfigCheckDefinition() {
|
|
354
|
-
return {
|
|
355
|
-
id: CHECK_IDS.CONFIG_VALIDATION,
|
|
356
|
-
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
357
|
-
category: "configuration",
|
|
358
|
-
check: checkConfigValidity,
|
|
359
|
-
critical: false
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// src/cli/doctor/checks/auth.ts
|
|
364
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
365
|
-
import { homedir } from "os";
|
|
366
|
-
import { join } from "path";
|
|
367
|
-
|
|
368
|
-
// src/shared/index.ts
|
|
369
|
-
import * as jsoncParser3 from "jsonc-parser";
|
|
370
|
-
function parseJsonc3(content) {
|
|
371
|
-
const errors = [];
|
|
372
|
-
const result = jsoncParser3.parse(content, errors, { allowTrailingComma: true });
|
|
373
|
-
if (errors.length > 0) {
|
|
374
|
-
throw new Error(`JSONC parse error: ${errors[0].error}`);
|
|
375
|
-
}
|
|
376
|
-
return result;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// src/cli/doctor/checks/auth.ts
|
|
380
|
-
var OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
381
|
-
var OPENCODE_JSON = join(OPENCODE_CONFIG_DIR, "opencode.json");
|
|
382
|
-
var OPENCODE_JSONC = join(OPENCODE_CONFIG_DIR, "opencode.jsonc");
|
|
383
|
-
var AUTH_PLUGINS = {
|
|
384
|
-
anthropic: { plugin: "builtin", name: "Anthropic (Claude)" },
|
|
385
|
-
openai: { plugin: "opencode-openai-codex-auth", name: "OpenAI (ChatGPT)" },
|
|
386
|
-
google: { plugin: "opencode-antigravity-auth", name: "Google (Gemini)" }
|
|
387
|
-
};
|
|
388
|
-
function getOpenCodeConfig() {
|
|
389
|
-
const configPath = existsSync3(OPENCODE_JSONC) ? OPENCODE_JSONC : OPENCODE_JSON;
|
|
390
|
-
if (!existsSync3(configPath))
|
|
391
|
-
return null;
|
|
392
|
-
try {
|
|
393
|
-
const content = readFileSync3(configPath, "utf-8");
|
|
394
|
-
return parseJsonc3(content);
|
|
395
|
-
} catch {
|
|
396
|
-
return null;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
function isPluginInstalled(plugins, pluginName) {
|
|
400
|
-
if (pluginName === "builtin")
|
|
401
|
-
return true;
|
|
402
|
-
return plugins.some((p) => p === pluginName || p.startsWith(`${pluginName}@`));
|
|
403
|
-
}
|
|
404
|
-
function getAuthProviderInfo(providerId) {
|
|
405
|
-
const config = getOpenCodeConfig();
|
|
406
|
-
const plugins = config?.plugin ?? [];
|
|
407
|
-
const authConfig = AUTH_PLUGINS[providerId];
|
|
408
|
-
const pluginInstalled = isPluginInstalled(plugins, authConfig.plugin);
|
|
409
|
-
return {
|
|
410
|
-
id: providerId,
|
|
411
|
-
name: authConfig.name,
|
|
412
|
-
pluginInstalled,
|
|
413
|
-
configured: pluginInstalled
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
async function checkAuthProvider(providerId) {
|
|
417
|
-
const info = getAuthProviderInfo(providerId);
|
|
418
|
-
const checkId = `auth-${providerId}`;
|
|
419
|
-
const checkName = CHECK_NAMES[checkId] || info.name;
|
|
420
|
-
if (!info.pluginInstalled) {
|
|
421
|
-
return {
|
|
422
|
-
name: checkName,
|
|
423
|
-
status: "skip",
|
|
424
|
-
message: "Auth plugin not installed",
|
|
425
|
-
details: [
|
|
426
|
-
`Plugin: ${AUTH_PLUGINS[providerId].plugin}`,
|
|
427
|
-
"Run: kraken-code install"
|
|
428
|
-
]
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
return {
|
|
432
|
-
name: checkName,
|
|
433
|
-
status: "pass",
|
|
434
|
-
message: "Auth plugin available",
|
|
435
|
-
details: [
|
|
436
|
-
providerId === "anthropic" ? "Run: opencode auth login (select Anthropic)" : `Plugin: ${AUTH_PLUGINS[providerId].plugin}`
|
|
437
|
-
]
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
async function checkAnthropicAuth() {
|
|
441
|
-
return checkAuthProvider("anthropic");
|
|
442
|
-
}
|
|
443
|
-
async function checkOpenAIAuth() {
|
|
444
|
-
return checkAuthProvider("openai");
|
|
445
|
-
}
|
|
446
|
-
async function checkGoogleAuth() {
|
|
447
|
-
return checkAuthProvider("google");
|
|
448
|
-
}
|
|
449
|
-
function getAuthCheckDefinitions() {
|
|
450
|
-
return [
|
|
451
|
-
{
|
|
452
|
-
id: CHECK_IDS.AUTH_ANTHROPIC,
|
|
453
|
-
name: CHECK_NAMES[CHECK_IDS.AUTH_ANTHROPIC],
|
|
454
|
-
category: "authentication",
|
|
455
|
-
check: checkAnthropicAuth,
|
|
456
|
-
critical: false
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
id: CHECK_IDS.AUTH_OPENAI,
|
|
460
|
-
name: CHECK_NAMES[CHECK_IDS.AUTH_OPENAI],
|
|
461
|
-
category: "authentication",
|
|
462
|
-
check: checkOpenAIAuth,
|
|
463
|
-
critical: false
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
id: CHECK_IDS.AUTH_GOOGLE,
|
|
467
|
-
name: CHECK_NAMES[CHECK_IDS.AUTH_GOOGLE],
|
|
468
|
-
category: "authentication",
|
|
469
|
-
check: checkGoogleAuth,
|
|
470
|
-
critical: false
|
|
471
|
-
}
|
|
472
|
-
];
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// src/cli/doctor/checks/dependencies.ts
|
|
476
|
-
async function checkBinaryExists(binary) {
|
|
477
|
-
try {
|
|
478
|
-
const proc = Bun.spawn(["which", binary], { stdout: "pipe", stderr: "pipe" });
|
|
479
|
-
const output = await new Response(proc.stdout).text();
|
|
480
|
-
await proc.exited;
|
|
481
|
-
if (proc.exitCode === 0) {
|
|
482
|
-
return { exists: true, path: output.trim() };
|
|
483
|
-
}
|
|
484
|
-
} catch {}
|
|
485
|
-
return { exists: false, path: null };
|
|
486
|
-
}
|
|
487
|
-
async function getBinaryVersion(binary) {
|
|
488
|
-
try {
|
|
489
|
-
const proc = Bun.spawn([binary, "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
490
|
-
const output = await new Response(proc.stdout).text();
|
|
491
|
-
await proc.exited;
|
|
492
|
-
if (proc.exitCode === 0) {
|
|
493
|
-
return output.trim().split(`
|
|
494
|
-
`)[0];
|
|
495
|
-
}
|
|
496
|
-
} catch {}
|
|
497
|
-
return null;
|
|
498
|
-
}
|
|
499
|
-
async function checkAstGrepCli() {
|
|
500
|
-
const binaryCheck = await checkBinaryExists("sg");
|
|
501
|
-
const altBinaryCheck = !binaryCheck.exists ? await checkBinaryExists("ast-grep") : null;
|
|
502
|
-
const binary = binaryCheck.exists ? binaryCheck : altBinaryCheck;
|
|
503
|
-
if (!binary || !binary.exists) {
|
|
504
|
-
return {
|
|
505
|
-
name: "AST-Grep CLI",
|
|
506
|
-
required: false,
|
|
507
|
-
installed: false,
|
|
508
|
-
version: null,
|
|
509
|
-
path: null,
|
|
510
|
-
installHint: "Install: npm install -g @ast-grep/cli"
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
const version = await getBinaryVersion(binary.path);
|
|
514
|
-
return {
|
|
515
|
-
name: "AST-Grep CLI",
|
|
516
|
-
required: false,
|
|
517
|
-
installed: true,
|
|
518
|
-
version,
|
|
519
|
-
path: binary.path
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
function checkAstGrepNapi() {
|
|
523
|
-
try {
|
|
524
|
-
__require.resolve("@ast-grep/napi");
|
|
525
|
-
return {
|
|
526
|
-
name: "AST-Grep NAPI",
|
|
527
|
-
required: false,
|
|
528
|
-
installed: true,
|
|
529
|
-
version: null,
|
|
530
|
-
path: null
|
|
531
|
-
};
|
|
532
|
-
} catch {
|
|
533
|
-
return {
|
|
534
|
-
name: "AST-Grep NAPI",
|
|
535
|
-
required: false,
|
|
536
|
-
installed: false,
|
|
537
|
-
version: null,
|
|
538
|
-
path: null,
|
|
539
|
-
installHint: "Will use CLI fallback if available"
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
function dependencyToCheckResult(dep, checkName) {
|
|
544
|
-
if (dep.installed) {
|
|
545
|
-
return {
|
|
546
|
-
name: checkName,
|
|
547
|
-
status: "pass",
|
|
548
|
-
message: dep.version ?? "installed",
|
|
549
|
-
details: dep.path ? [`Path: ${dep.path}`] : undefined
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
return {
|
|
553
|
-
name: checkName,
|
|
554
|
-
status: "warn",
|
|
555
|
-
message: "Not installed (optional)",
|
|
556
|
-
details: dep.installHint ? [dep.installHint] : undefined
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
async function checkDependencyAstGrepCli() {
|
|
560
|
-
const info = await checkAstGrepCli();
|
|
561
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_CLI]);
|
|
562
|
-
}
|
|
563
|
-
async function checkDependencyAstGrepNapi() {
|
|
564
|
-
const info = checkAstGrepNapi();
|
|
565
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_NAPI]);
|
|
566
|
-
}
|
|
567
|
-
async function checkDependencyCommentChecker() {
|
|
568
|
-
return {
|
|
569
|
-
name: "Comment Checker",
|
|
570
|
-
status: "pass",
|
|
571
|
-
message: "Built-in hook, always available",
|
|
572
|
-
details: ["Comment checking is handled by hooks/comment-checker"]
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
function getDependencyCheckDefinitions() {
|
|
576
|
-
return [
|
|
577
|
-
{
|
|
578
|
-
id: CHECK_IDS.DEP_AST_GREP_CLI,
|
|
579
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_CLI],
|
|
580
|
-
category: "dependencies",
|
|
581
|
-
check: checkDependencyAstGrepCli,
|
|
582
|
-
critical: false
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
id: CHECK_IDS.DEP_AST_GREP_NAPI,
|
|
586
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_NAPI],
|
|
587
|
-
category: "dependencies",
|
|
588
|
-
check: checkDependencyAstGrepNapi,
|
|
589
|
-
critical: false
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
id: CHECK_IDS.DEP_COMMENT_CHECKER,
|
|
593
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_COMMENT_CHECKER],
|
|
594
|
-
category: "dependencies",
|
|
595
|
-
check: checkDependencyCommentChecker,
|
|
596
|
-
critical: false
|
|
597
|
-
}
|
|
598
|
-
];
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// src/cli/doctor/checks/gh.ts
|
|
602
|
-
async function checkBinaryExists2(binary) {
|
|
603
|
-
try {
|
|
604
|
-
const proc = Bun.spawn(["which", binary], { stdout: "pipe", stderr: "pipe" });
|
|
605
|
-
const output = await new Response(proc.stdout).text();
|
|
606
|
-
await proc.exited;
|
|
607
|
-
if (proc.exitCode === 0) {
|
|
608
|
-
return { exists: true, path: output.trim() };
|
|
609
|
-
}
|
|
610
|
-
} catch {}
|
|
611
|
-
return { exists: false, path: null };
|
|
612
|
-
}
|
|
613
|
-
async function getGhVersion() {
|
|
614
|
-
try {
|
|
615
|
-
const proc = Bun.spawn(["gh", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
616
|
-
const output = await new Response(proc.stdout).text();
|
|
617
|
-
await proc.exited;
|
|
618
|
-
if (proc.exitCode === 0) {
|
|
619
|
-
const match = output.match(/gh version (\S+)/);
|
|
620
|
-
return match?.[1] ?? output.trim().split(`
|
|
621
|
-
`)[0];
|
|
622
|
-
}
|
|
623
|
-
} catch {}
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
|
-
async function getGhAuthStatus() {
|
|
627
|
-
try {
|
|
628
|
-
const proc = Bun.spawn(["gh", "auth", "status"], {
|
|
629
|
-
stdout: "pipe",
|
|
630
|
-
stderr: "pipe",
|
|
631
|
-
env: { ...process.env, GH_NO_UPDATE_NOTIFIER: "1" }
|
|
632
|
-
});
|
|
633
|
-
const stdout = await new Response(proc.stdout).text();
|
|
634
|
-
const stderr = await new Response(proc.stderr).text();
|
|
635
|
-
await proc.exited;
|
|
636
|
-
const output = stderr || stdout;
|
|
637
|
-
if (proc.exitCode === 0) {
|
|
638
|
-
const usernameMatch = output.match(/Logged in to github\.com account (\S+)/);
|
|
639
|
-
const username = usernameMatch?.[1]?.replace(/[()]/g, "") ?? null;
|
|
640
|
-
const scopesMatch = output.match(/Token scopes?:\s*(.+)/i);
|
|
641
|
-
const scopes = scopesMatch?.[1] ? scopesMatch[1].split(/,\s*/).map((s) => s.replace(/['"]/g, "").trim()).filter(Boolean) : [];
|
|
642
|
-
return { authenticated: true, username, scopes, error: null };
|
|
643
|
-
}
|
|
644
|
-
const errorMatch = output.match(/error[:\s]+(.+)/i);
|
|
645
|
-
return {
|
|
646
|
-
authenticated: false,
|
|
647
|
-
username: null,
|
|
648
|
-
scopes: [],
|
|
649
|
-
error: errorMatch?.[1]?.trim() ?? "Not authenticated"
|
|
650
|
-
};
|
|
651
|
-
} catch (err) {
|
|
652
|
-
return {
|
|
653
|
-
authenticated: false,
|
|
654
|
-
username: null,
|
|
655
|
-
scopes: [],
|
|
656
|
-
error: err instanceof Error ? err.message : "Failed to check auth status"
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
async function getGhCliInfo() {
|
|
661
|
-
const binaryCheck = await checkBinaryExists2("gh");
|
|
662
|
-
if (!binaryCheck.exists) {
|
|
663
|
-
return {
|
|
664
|
-
installed: false,
|
|
665
|
-
version: null,
|
|
666
|
-
path: null,
|
|
667
|
-
authenticated: false,
|
|
668
|
-
username: null,
|
|
669
|
-
scopes: [],
|
|
670
|
-
error: null
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
const [version, authStatus] = await Promise.all([getGhVersion(), getGhAuthStatus()]);
|
|
674
|
-
return {
|
|
675
|
-
installed: true,
|
|
676
|
-
version,
|
|
677
|
-
path: binaryCheck.path,
|
|
678
|
-
authenticated: authStatus.authenticated,
|
|
679
|
-
username: authStatus.username,
|
|
680
|
-
scopes: authStatus.scopes,
|
|
681
|
-
error: authStatus.error
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
async function checkGhCli() {
|
|
685
|
-
const info = await getGhCliInfo();
|
|
686
|
-
const name = CHECK_NAMES[CHECK_IDS.GH_CLI];
|
|
687
|
-
if (!info.installed) {
|
|
688
|
-
return {
|
|
689
|
-
name,
|
|
690
|
-
status: "warn",
|
|
691
|
-
message: "Not installed (optional)",
|
|
692
|
-
details: [
|
|
693
|
-
"GitHub CLI is used by librarian agent and scripts",
|
|
694
|
-
"Install: https://cli.github.com/"
|
|
695
|
-
]
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
if (!info.authenticated) {
|
|
699
|
-
return {
|
|
700
|
-
name,
|
|
701
|
-
status: "warn",
|
|
702
|
-
message: `${info.version ?? "installed"} - not authenticated`,
|
|
703
|
-
details: [
|
|
704
|
-
info.path ? `Path: ${info.path}` : null,
|
|
705
|
-
"Authenticate: gh auth login",
|
|
706
|
-
info.error ? `Error: ${info.error}` : null
|
|
707
|
-
].filter((d) => d !== null)
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
const details = [];
|
|
711
|
-
if (info.path)
|
|
712
|
-
details.push(`Path: ${info.path}`);
|
|
713
|
-
if (info.username)
|
|
714
|
-
details.push(`Account: ${info.username}`);
|
|
715
|
-
if (info.scopes.length > 0)
|
|
716
|
-
details.push(`Scopes: ${info.scopes.join(", ")}`);
|
|
717
|
-
return {
|
|
718
|
-
name,
|
|
719
|
-
status: "pass",
|
|
720
|
-
message: `${info.version ?? "installed"} - authenticated as ${info.username ?? "unknown"}`,
|
|
721
|
-
details: details.length > 0 ? details : undefined
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
function getGhCliCheckDefinition() {
|
|
725
|
-
return {
|
|
726
|
-
id: CHECK_IDS.GH_CLI,
|
|
727
|
-
name: CHECK_NAMES[CHECK_IDS.GH_CLI],
|
|
728
|
-
category: "tools",
|
|
729
|
-
check: checkGhCli,
|
|
730
|
-
critical: false
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// src/cli/doctor/checks/lsp.ts
|
|
735
|
-
var DEFAULT_LSP_SERVERS = [
|
|
736
|
-
{ id: "typescript-language-server", binary: "typescript-language-server", extensions: [".ts", ".tsx", ".js", ".jsx"] },
|
|
737
|
-
{ id: "pyright", binary: "pyright-langserver", extensions: [".py"] },
|
|
738
|
-
{ id: "rust-analyzer", binary: "rust-analyzer", extensions: [".rs"] },
|
|
739
|
-
{ id: "gopls", binary: "gopls", extensions: [".go"] }
|
|
740
|
-
];
|
|
741
|
-
async function checkBinaryExists3(binary) {
|
|
742
|
-
try {
|
|
743
|
-
const proc = Bun.spawn(["which", binary], { stdout: "pipe", stderr: "pipe" });
|
|
744
|
-
await proc.exited;
|
|
745
|
-
return proc.exitCode === 0;
|
|
746
|
-
} catch {
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
async function getLspServersInfo() {
|
|
751
|
-
const servers = [];
|
|
752
|
-
for (const server of DEFAULT_LSP_SERVERS) {
|
|
753
|
-
const installed = await checkBinaryExists3(server.binary);
|
|
754
|
-
servers.push({
|
|
755
|
-
id: server.id,
|
|
756
|
-
installed,
|
|
757
|
-
extensions: server.extensions,
|
|
758
|
-
source: "builtin"
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
return servers;
|
|
762
|
-
}
|
|
763
|
-
function getLspServerStats(servers) {
|
|
764
|
-
const installed = servers.filter((s) => s.installed).length;
|
|
765
|
-
return { installed, total: servers.length };
|
|
766
|
-
}
|
|
767
|
-
async function checkLspServers() {
|
|
768
|
-
const servers = await getLspServersInfo();
|
|
769
|
-
const stats = getLspServerStats(servers);
|
|
770
|
-
const installedServers = servers.filter((s) => s.installed);
|
|
771
|
-
const missingServers = servers.filter((s) => !s.installed);
|
|
772
|
-
if (stats.installed === 0) {
|
|
773
|
-
return {
|
|
774
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
775
|
-
status: "warn",
|
|
776
|
-
message: "No LSP servers detected",
|
|
777
|
-
details: [
|
|
778
|
-
"LSP tools will have limited functionality",
|
|
779
|
-
...missingServers.map((s) => `Missing: ${s.id}`)
|
|
780
|
-
]
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
const details = [
|
|
784
|
-
...installedServers.map((s) => `Installed: ${s.id}`),
|
|
785
|
-
...missingServers.map((s) => `Not found: ${s.id} (optional)`)
|
|
786
|
-
];
|
|
787
|
-
return {
|
|
788
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
789
|
-
status: "pass",
|
|
790
|
-
message: `${stats.installed}/${stats.total} servers available`,
|
|
791
|
-
details
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
function getLspCheckDefinition() {
|
|
795
|
-
return {
|
|
796
|
-
id: CHECK_IDS.LSP_SERVERS,
|
|
797
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
798
|
-
category: "tools",
|
|
799
|
-
check: checkLspServers,
|
|
800
|
-
critical: false
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// src/cli/doctor/checks/mcp.ts
|
|
805
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
806
|
-
import { homedir as homedir2 } from "os";
|
|
807
|
-
import { join as join2 } from "path";
|
|
808
|
-
var BUILTIN_MCP_SERVERS = ["websearch", "context7", "grep_app"];
|
|
809
|
-
var MCP_CONFIG_PATHS = [
|
|
810
|
-
join2(homedir2(), ".claude", ".mcp.json"),
|
|
811
|
-
join2(process.cwd(), ".mcp.json"),
|
|
812
|
-
join2(process.cwd(), ".claude", ".mcp.json")
|
|
813
|
-
];
|
|
814
|
-
function loadUserMcpConfig() {
|
|
815
|
-
const servers = {};
|
|
816
|
-
for (const configPath of MCP_CONFIG_PATHS) {
|
|
817
|
-
if (!existsSync4(configPath))
|
|
818
|
-
continue;
|
|
819
|
-
try {
|
|
820
|
-
const content = readFileSync4(configPath, "utf-8");
|
|
821
|
-
const config = parseJsonc3(content);
|
|
822
|
-
if (config.mcpServers) {
|
|
823
|
-
Object.assign(servers, config.mcpServers);
|
|
824
|
-
}
|
|
825
|
-
} catch {}
|
|
826
|
-
}
|
|
827
|
-
return servers;
|
|
828
|
-
}
|
|
829
|
-
function getBuiltinMcpInfo() {
|
|
830
|
-
return BUILTIN_MCP_SERVERS.map((id) => ({
|
|
831
|
-
id,
|
|
832
|
-
type: "builtin",
|
|
833
|
-
enabled: true,
|
|
834
|
-
valid: true
|
|
835
|
-
}));
|
|
836
|
-
}
|
|
837
|
-
function getUserMcpInfo() {
|
|
838
|
-
const userServers = loadUserMcpConfig();
|
|
839
|
-
const servers = [];
|
|
840
|
-
for (const [id, config] of Object.entries(userServers)) {
|
|
841
|
-
const isValid = typeof config === "object" && config !== null;
|
|
842
|
-
servers.push({
|
|
843
|
-
id,
|
|
844
|
-
type: "user",
|
|
845
|
-
enabled: true,
|
|
846
|
-
valid: isValid,
|
|
847
|
-
error: isValid ? undefined : "Invalid configuration format"
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
return servers;
|
|
851
|
-
}
|
|
852
|
-
async function checkBuiltinMcpServers() {
|
|
853
|
-
const servers = getBuiltinMcpInfo();
|
|
854
|
-
return {
|
|
855
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_BUILTIN],
|
|
856
|
-
status: "pass",
|
|
857
|
-
message: `${servers.length} built-in servers enabled`,
|
|
858
|
-
details: servers.map((s) => `Enabled: ${s.id}`)
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
async function checkUserMcpServers() {
|
|
862
|
-
const servers = getUserMcpInfo();
|
|
863
|
-
if (servers.length === 0) {
|
|
864
|
-
return {
|
|
865
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_USER],
|
|
866
|
-
status: "skip",
|
|
867
|
-
message: "No user MCP configuration found",
|
|
868
|
-
details: ["Optional: Add .mcp.json for custom MCP servers"]
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
const invalidServers = servers.filter((s) => !s.valid);
|
|
872
|
-
if (invalidServers.length > 0) {
|
|
873
|
-
return {
|
|
874
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_USER],
|
|
875
|
-
status: "warn",
|
|
876
|
-
message: `${invalidServers.length} server(s) have configuration issues`,
|
|
877
|
-
details: [
|
|
878
|
-
...servers.filter((s) => s.valid).map((s) => `Valid: ${s.id}`),
|
|
879
|
-
...invalidServers.map((s) => `Invalid: ${s.id} - ${s.error}`)
|
|
880
|
-
]
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
return {
|
|
884
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_USER],
|
|
885
|
-
status: "pass",
|
|
886
|
-
message: `${servers.length} user server(s) configured`,
|
|
887
|
-
details: servers.map((s) => `Configured: ${s.id}`)
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
function getMcpCheckDefinitions() {
|
|
891
|
-
return [
|
|
892
|
-
{
|
|
893
|
-
id: CHECK_IDS.MCP_BUILTIN,
|
|
894
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_BUILTIN],
|
|
895
|
-
category: "tools",
|
|
896
|
-
check: checkBuiltinMcpServers,
|
|
897
|
-
critical: false
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
id: CHECK_IDS.MCP_USER,
|
|
901
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_USER],
|
|
902
|
-
category: "tools",
|
|
903
|
-
check: checkUserMcpServers,
|
|
904
|
-
critical: false
|
|
905
|
-
}
|
|
906
|
-
];
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// src/cli/doctor/checks/version.ts
|
|
910
|
-
async function checkVersion() {
|
|
911
|
-
return {
|
|
912
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
913
|
-
status: "pass",
|
|
914
|
-
message: "Kraken Code plugin version check bypassed (version tracking not implemented)",
|
|
915
|
-
details: [
|
|
916
|
-
`Minimum required: ${MIN_OPENCODE_VERSION}`,
|
|
917
|
-
"Run 'bun run build' to build the plugin"
|
|
918
|
-
]
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
function getVersionCheckDefinition() {
|
|
922
|
-
return {
|
|
923
|
-
id: CHECK_IDS.VERSION_STATUS,
|
|
924
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
925
|
-
category: "updates",
|
|
926
|
-
check: checkVersion,
|
|
927
|
-
critical: false
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// src/cli/doctor/checks/index.ts
|
|
932
|
-
function getAllCheckDefinitions() {
|
|
933
|
-
return [
|
|
934
|
-
getOpenCodeCheckDefinition(),
|
|
935
|
-
getPluginCheckDefinition(),
|
|
936
|
-
getConfigCheckDefinition(),
|
|
937
|
-
...getAuthCheckDefinitions(),
|
|
938
|
-
...getDependencyCheckDefinitions(),
|
|
939
|
-
getGhCliCheckDefinition(),
|
|
940
|
-
getLspCheckDefinition(),
|
|
941
|
-
...getMcpCheckDefinitions(),
|
|
942
|
-
getVersionCheckDefinition()
|
|
943
|
-
];
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// src/cli/doctor/formatter.ts
|
|
947
|
-
import color2 from "picocolors";
|
|
948
|
-
function formatStatusSymbol(status) {
|
|
949
|
-
switch (status) {
|
|
950
|
-
case "pass":
|
|
951
|
-
return SYMBOLS.check;
|
|
952
|
-
case "fail":
|
|
953
|
-
return SYMBOLS.cross;
|
|
954
|
-
case "warn":
|
|
955
|
-
return SYMBOLS.warn;
|
|
956
|
-
case "skip":
|
|
957
|
-
return SYMBOLS.skip;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
function formatCheckResult(result, verbose) {
|
|
961
|
-
const symbol = formatStatusSymbol(result.status);
|
|
962
|
-
const colorFn = STATUS_COLORS[result.status];
|
|
963
|
-
const name = colorFn(result.name);
|
|
964
|
-
const message = color2.dim(result.message);
|
|
965
|
-
let line = ` ${symbol} ${name}`;
|
|
966
|
-
if (result.message) {
|
|
967
|
-
line += ` ${SYMBOLS.arrow} ${message}`;
|
|
968
|
-
}
|
|
969
|
-
if (verbose && result.details && result.details.length > 0) {
|
|
970
|
-
const detailLines = result.details.map((d) => ` ${SYMBOLS.bullet} ${color2.dim(d)}`).join(`
|
|
971
|
-
`);
|
|
972
|
-
line += `
|
|
973
|
-
` + detailLines;
|
|
974
|
-
}
|
|
975
|
-
return line;
|
|
976
|
-
}
|
|
977
|
-
function formatCategoryHeader(category) {
|
|
978
|
-
const name = CATEGORY_NAMES[category] || category;
|
|
979
|
-
return `
|
|
980
|
-
${color2.bold(color2.white(name))}
|
|
981
|
-
${color2.dim("\u2500".repeat(40))}`;
|
|
982
|
-
}
|
|
983
|
-
function formatSummary(summary) {
|
|
984
|
-
const lines = [];
|
|
985
|
-
lines.push(color2.bold(color2.white("Summary")));
|
|
986
|
-
lines.push(color2.dim("\u2500".repeat(40)));
|
|
987
|
-
lines.push("");
|
|
988
|
-
const passText = summary.passed > 0 ? color2.green(`${summary.passed} passed`) : color2.dim("0 passed");
|
|
989
|
-
const failText = summary.failed > 0 ? color2.red(`${summary.failed} failed`) : color2.dim("0 failed");
|
|
990
|
-
const warnText = summary.warnings > 0 ? color2.yellow(`${summary.warnings} warnings`) : color2.dim("0 warnings");
|
|
991
|
-
const skipText = summary.skipped > 0 ? color2.dim(`${summary.skipped} skipped`) : "";
|
|
992
|
-
const parts = [passText, failText, warnText];
|
|
993
|
-
if (skipText)
|
|
994
|
-
parts.push(skipText);
|
|
995
|
-
lines.push(` ${parts.join(", ")}`);
|
|
996
|
-
lines.push(` ${color2.dim(`Total: ${summary.total} checks in ${summary.duration}ms`)}`);
|
|
997
|
-
return lines.join(`
|
|
998
|
-
`);
|
|
999
|
-
}
|
|
1000
|
-
function formatHeader() {
|
|
1001
|
-
return `
|
|
1002
|
-
${color2.bgBlue(color2.white(" Kraken Code Doctor "))}
|
|
1003
|
-
`;
|
|
1004
|
-
}
|
|
1005
|
-
function formatFooter(summary) {
|
|
1006
|
-
if (summary.failed > 0) {
|
|
1007
|
-
return `
|
|
1008
|
-
${SYMBOLS.cross} ${color2.red("Issues detected. Please review the errors above.")}
|
|
1009
|
-
`;
|
|
1010
|
-
}
|
|
1011
|
-
if (summary.warnings > 0) {
|
|
1012
|
-
return `
|
|
1013
|
-
${SYMBOLS.warn} ${color2.yellow("All systems operational with warnings.")}
|
|
1014
|
-
`;
|
|
1015
|
-
}
|
|
1016
|
-
return `
|
|
1017
|
-
${SYMBOLS.check} ${color2.green("All systems operational!")}
|
|
1018
|
-
`;
|
|
1019
|
-
}
|
|
1020
|
-
function formatJsonOutput(result) {
|
|
1021
|
-
return JSON.stringify(result, null, 2);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// src/cli/doctor/runner.ts
|
|
1025
|
-
async function runCheck(check) {
|
|
1026
|
-
const start = performance.now();
|
|
1027
|
-
try {
|
|
1028
|
-
const result = await check.check();
|
|
1029
|
-
result.duration = Math.round(performance.now() - start);
|
|
1030
|
-
return result;
|
|
1031
|
-
} catch (err) {
|
|
1032
|
-
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
1033
|
-
const errorStack = err instanceof Error ? err.stack : undefined;
|
|
1034
|
-
console.error(`[doctor] Check "${check.name}" failed:`, errorMessage);
|
|
1035
|
-
if (errorStack) {
|
|
1036
|
-
console.error(`[doctor] Stack trace:
|
|
1037
|
-
${errorStack}`);
|
|
1038
|
-
}
|
|
1039
|
-
return {
|
|
1040
|
-
name: check.name,
|
|
1041
|
-
status: "fail",
|
|
1042
|
-
message: errorMessage,
|
|
1043
|
-
duration: Math.round(performance.now() - start)
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
function calculateSummary(results, duration) {
|
|
1048
|
-
return {
|
|
1049
|
-
total: results.length,
|
|
1050
|
-
passed: results.filter((r) => r.status === "pass").length,
|
|
1051
|
-
failed: results.filter((r) => r.status === "fail").length,
|
|
1052
|
-
warnings: results.filter((r) => r.status === "warn").length,
|
|
1053
|
-
skipped: results.filter((r) => r.status === "skip").length,
|
|
1054
|
-
duration: Math.round(duration)
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
function determineExitCode(results) {
|
|
1058
|
-
const hasFailures = results.some((r) => r.status === "fail");
|
|
1059
|
-
return hasFailures ? EXIT_CODES.FAILURE : EXIT_CODES.SUCCESS;
|
|
1060
|
-
}
|
|
1061
|
-
function filterChecksByCategory(checks, category) {
|
|
1062
|
-
if (!category)
|
|
1063
|
-
return checks;
|
|
1064
|
-
return checks.filter((c) => c.category === category);
|
|
1065
|
-
}
|
|
1066
|
-
function groupChecksByCategory(checks) {
|
|
1067
|
-
const groups = new Map;
|
|
1068
|
-
for (const check of checks) {
|
|
1069
|
-
const existing = groups.get(check.category) ?? [];
|
|
1070
|
-
existing.push(check);
|
|
1071
|
-
groups.set(check.category, existing);
|
|
1072
|
-
}
|
|
1073
|
-
return groups;
|
|
1074
|
-
}
|
|
1075
|
-
var CATEGORY_ORDER = [
|
|
1076
|
-
"installation",
|
|
1077
|
-
"configuration",
|
|
1078
|
-
"authentication",
|
|
1079
|
-
"dependencies",
|
|
1080
|
-
"tools",
|
|
1081
|
-
"updates"
|
|
1082
|
-
];
|
|
1083
|
-
async function runDoctor(options) {
|
|
1084
|
-
const start = performance.now();
|
|
1085
|
-
const allChecks = getAllCheckDefinitions();
|
|
1086
|
-
const filteredChecks = filterChecksByCategory(allChecks, options.category);
|
|
1087
|
-
const groupedChecks = groupChecksByCategory(filteredChecks);
|
|
1088
|
-
const results = [];
|
|
1089
|
-
if (!options.json) {
|
|
1090
|
-
console.log(formatHeader());
|
|
1091
|
-
}
|
|
1092
|
-
for (const category of CATEGORY_ORDER) {
|
|
1093
|
-
const checks = groupedChecks.get(category);
|
|
1094
|
-
if (!checks || checks.length === 0)
|
|
1095
|
-
continue;
|
|
1096
|
-
if (!options.json) {
|
|
1097
|
-
console.log(formatCategoryHeader(category));
|
|
1098
|
-
}
|
|
1099
|
-
for (const check of checks) {
|
|
1100
|
-
const result = await runCheck(check);
|
|
1101
|
-
results.push(result);
|
|
1102
|
-
if (!options.json) {
|
|
1103
|
-
console.log(formatCheckResult(result, options.verbose ?? false));
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
const duration = performance.now() - start;
|
|
1108
|
-
const summary = calculateSummary(results, duration);
|
|
1109
|
-
const exitCode = determineExitCode(results);
|
|
1110
|
-
const doctorResult = {
|
|
1111
|
-
results,
|
|
1112
|
-
summary,
|
|
1113
|
-
exitCode
|
|
1114
|
-
};
|
|
1115
|
-
if (options.json) {
|
|
1116
|
-
console.log(formatJsonOutput(doctorResult));
|
|
1117
|
-
} else {
|
|
1118
|
-
console.log("");
|
|
1119
|
-
console.log(formatSummary(summary));
|
|
1120
|
-
console.log(formatFooter(summary));
|
|
1121
|
-
}
|
|
1122
|
-
return doctorResult;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// src/cli/install.ts
|
|
1126
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync } from "fs";
|
|
1127
|
-
import * as jsoncParser4 from "jsonc-parser";
|
|
1128
|
-
import path3 from "path";
|
|
1129
|
-
import os3 from "os";
|
|
1130
|
-
import color3 from "picocolors";
|
|
1131
|
-
function getOpenCodeConfigPaths3() {
|
|
1132
|
-
const crossPlatformDir = path3.join(os3.homedir(), ".config", "opencode");
|
|
1133
|
-
return {
|
|
1134
|
-
configJson: path3.join(crossPlatformDir, "opencode.json"),
|
|
1135
|
-
configJsonc: path3.join(crossPlatformDir, "opencode.jsonc")
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
function detectConfigPath2() {
|
|
1139
|
-
const paths = getOpenCodeConfigPaths3();
|
|
1140
|
-
if (existsSync5(paths.configJsonc)) {
|
|
1141
|
-
return { path: paths.configJsonc, format: "jsonc" };
|
|
1142
|
-
}
|
|
1143
|
-
if (existsSync5(paths.configJson)) {
|
|
1144
|
-
return { path: paths.configJson, format: "json" };
|
|
1145
|
-
}
|
|
1146
|
-
return null;
|
|
1147
|
-
}
|
|
1148
|
-
function registerPluginInConfig(content, format) {
|
|
1149
|
-
const errors = [];
|
|
1150
|
-
const root = jsoncParser4.parse(content, errors, { allowTrailingComma: true });
|
|
1151
|
-
if (errors.length > 0) {
|
|
1152
|
-
throw new Error(`Failed to parse config: ${errors[0].error}`);
|
|
1153
|
-
}
|
|
1154
|
-
const plugins = Array.isArray(root.plugin) ? [...root.plugin] : [];
|
|
1155
|
-
const isRegistered = plugins.some((p) => p === PACKAGE_NAME || p.startsWith(`${PACKAGE_NAME}@`));
|
|
1156
|
-
if (isRegistered) {
|
|
1157
|
-
return content;
|
|
1158
|
-
}
|
|
1159
|
-
plugins.push(PACKAGE_NAME);
|
|
1160
|
-
const editOptions = {
|
|
1161
|
-
formattingOptions: {
|
|
1162
|
-
insertSpaces: true,
|
|
1163
|
-
tabSize: 2,
|
|
1164
|
-
insertFinalNewline: true
|
|
1165
|
-
}
|
|
1166
|
-
};
|
|
1167
|
-
if (root.plugin === undefined) {
|
|
1168
|
-
let insertPos = 0;
|
|
1169
|
-
const openBrace = content.indexOf("{");
|
|
1170
|
-
if (openBrace !== -1) {
|
|
1171
|
-
insertPos = openBrace + 1;
|
|
1172
|
-
}
|
|
1173
|
-
const indent = format === "jsonc" ? " " : " ";
|
|
1174
|
-
const newline = content.includes(`
|
|
1175
|
-
`) ? `
|
|
1176
|
-
` : "";
|
|
1177
|
-
const pluginText = `${newline}${indent}"plugin": ${JSON.stringify(plugins)},`;
|
|
1178
|
-
return content.slice(0, insertPos) + pluginText + content.slice(insertPos);
|
|
1179
|
-
}
|
|
1180
|
-
const edits = jsoncParser4.modify(content, ["plugin"], plugins, editOptions);
|
|
1181
|
-
return jsoncParser4.applyEdits(content, edits);
|
|
1182
|
-
}
|
|
1183
|
-
function createDefaultConfig() {
|
|
1184
|
-
return `{
|
|
3
|
+
var j=import.meta.require;import{Command as ht}from"commander";import h from"picocolors";var P={check:h.green("\u2713"),cross:h.red("\u2717"),warn:h.yellow("\u26A0"),info:h.blue("\u2139"),arrow:h.cyan("\u2192"),bullet:h.dim("\u2022"),skip:h.dim("\u25CB")},K={pass:h.green,fail:h.red,warn:h.yellow,skip:h.dim},s={OPENCODE_INSTALLATION:"opencode-installation",PLUGIN_REGISTRATION:"plugin-registration",CONFIG_VALIDATION:"config-validation",AUTH_ANTHROPIC:"auth-anthropic",AUTH_OPENAI:"auth-openai",AUTH_GOOGLE:"auth-google",DEP_AST_GREP_CLI:"dep-ast-grep-cli",DEP_AST_GREP_NAPI:"dep-ast-grep-napi",DEP_PYTHON:"dep-python",DEP_RIPGREP:"dep-ripgrep",DEP_COMMENT_CHECKER:"dep-comment-checker",GH_CLI:"gh-cli",LSP_SERVERS:"lsp-servers",MCP_BUILTIN:"mcp-builtin",MCP_USER:"mcp-user",VERSION_STATUS:"version-status"},a={[s.OPENCODE_INSTALLATION]:"OpenCode Installation",[s.PLUGIN_REGISTRATION]:"Plugin Registration",[s.CONFIG_VALIDATION]:"Configuration Validity",[s.AUTH_ANTHROPIC]:"Anthropic (Claude) Auth",[s.AUTH_OPENAI]:"OpenAI (ChatGPT) Auth",[s.AUTH_GOOGLE]:"Google (Gemini) Auth",[s.DEP_AST_GREP_CLI]:"AST-Grep CLI",[s.DEP_AST_GREP_NAPI]:"AST-Grep NAPI",[s.DEP_PYTHON]:"Python 3",[s.DEP_RIPGREP]:"ripgrep (rg)",[s.DEP_COMMENT_CHECKER]:"Comment Checker",[s.GH_CLI]:"GitHub CLI",[s.LSP_SERVERS]:"LSP Servers",[s.MCP_BUILTIN]:"Built-in MCP Servers",[s.MCP_USER]:"User MCP Configuration",[s.VERSION_STATUS]:"Version Status"},nn={installation:"Installation",configuration:"Configuration",authentication:"Authentication",dependencies:"Dependencies",tools:"Tools & Servers",updates:"Updates"},N={SUCCESS:0,FAILURE:1},y="1.0.150",d="kraken-code",en=["opencode","opencode-desktop"];async function zn(){for(let n of en)try{let e=Bun.spawn(["which",n],{stdout:"pipe",stderr:"pipe"}),t=await new Response(e.stdout).text();if(await e.exited,e.exitCode===0)return{binary:n,path:t.trim()}}catch{continue}return null}async function Fn(n){try{let e=Bun.spawn([n,"--version"],{stdout:"pipe",stderr:"pipe"}),t=await new Response(e.stdout).text();if(await e.exited,e.exitCode===0)return t.trim()}catch{return null}return null}function qn(n,e){let t=(r)=>{return r.replace(/^v/,"").split("-")[0].split(".").map((u)=>parseInt(u,10)||0)},i=t(n),o=t(e);for(let r=0;r<Math.max(i.length,o.length);r++){let l=i[r]??0,u=o[r]??0;if(l>u)return!0;if(l<u)return!1}return!0}async function Hn(){let n=await zn();if(!n)return{installed:!1,version:null,path:null,binary:null};return{installed:!0,version:await Fn(n.binary),path:n.path,binary:n.binary}}async function Xn(){let n=await Hn();if(!n.installed)return{name:a[s.OPENCODE_INSTALLATION],status:"fail",message:"OpenCode is not installed",details:["Visit: https://opencode.ai/docs for installation instructions","Run: npm install -g opencode"]};if(n.version&&!qn(n.version,y))return{name:a[s.OPENCODE_INSTALLATION],status:"warn",message:`Version ${n.version} is below minimum ${y}`,details:[`Current: ${n.version}`,`Required: >= ${y}`,"Run: npm update -g opencode"]};return{name:a[s.OPENCODE_INSTALLATION],status:"pass",message:n.version??"installed",details:n.path?[`Path: ${n.path}`]:void 0}}function tn(){return{id:s.OPENCODE_INSTALLATION,name:a[s.OPENCODE_INSTALLATION],category:"installation",check:Xn,critical:!0}}import{existsSync as sn,readFileSync as Wn}from"fs";import*as on from"jsonc-parser";import G from"path";import Qn from"os";function rn(){let n=G.join(Qn.homedir(),".config","opencode");return{configJson:G.join(n,"opencode.json"),configJsonc:G.join(n,"opencode.jsonc")}}function Zn(n){let e=[],t=on.parse(n,e,{allowTrailingComma:!0});if(e.length>0)throw Error(`JSONC parse error: ${e[0].error}`);return t}function Mn(){let n=rn();if(sn(n.configJsonc))return{path:n.configJsonc,format:"jsonc"};if(sn(n.configJson))return{path:n.configJson,format:"json"};return null}function Yn(n){for(let e of n)if(e===d||e.startsWith(`${d}@`)){let t=e.includes("@"),i=t?e.split("@")[1]:null;return{entry:e,isPinned:t,version:i}}return null}function Bn(){let n=Mn();if(!n)return{registered:!1,configPath:null,entry:null,isPinned:!1,pinnedVersion:null};try{let e=Wn(n.path,"utf-8"),i=Zn(e).plugin??[],o=Yn(i);if(!o)return{registered:!1,configPath:n.path,entry:null,isPinned:!1,pinnedVersion:null};return{registered:!0,configPath:n.path,entry:o.entry,isPinned:o.isPinned,pinnedVersion:o.version}}catch{return{registered:!1,configPath:n.path,entry:null,isPinned:!1,pinnedVersion:null}}}async function Kn(){let n=Bn();if(!n.configPath){let t=rn();return{name:a[s.PLUGIN_REGISTRATION],status:"fail",message:"OpenCode config file not found",details:["Run: kraken-code install",`Expected: ${t.configJson} or ${t.configJsonc}`]}}if(!n.registered)return{name:a[s.PLUGIN_REGISTRATION],status:"fail",message:"Plugin not registered in config",details:["Run: kraken-code install",`Config: ${n.configPath}`]};let e=n.isPinned?`Registered (pinned: ${n.pinnedVersion})`:"Registered";return{name:a[s.PLUGIN_REGISTRATION],status:"pass",message:e,details:[`Config: ${n.configPath}`]}}function an(){return{id:s.PLUGIN_REGISTRATION,name:a[s.PLUGIN_REGISTRATION],category:"installation",check:Kn,critical:!0}}import{existsSync as ne,readFileSync as ee}from"fs";import*as cn from"jsonc-parser";import J from"path";import te from"os";function se(){let n=J.join(te.homedir(),".config","opencode");return{configJson:J.join(n,"opencode.json"),configJsonc:J.join(n,"opencode.jsonc")}}function ie(n){let e=[],t=cn.parse(n,e,{allowTrailingComma:!0});if(e.length>0)throw Error(`JSONC parse error: ${e[0].error}`);return t}async function oe(){let n=se(),e=n.configJsonc||n.configJson;if(!ne(e))return{name:a[s.CONFIG_VALIDATION],status:"fail",message:"OpenCode config file not found",details:[`Expected: ${e}`]};try{let t=ee(e,"utf-8");return ie(t),{name:a[s.CONFIG_VALIDATION],status:"pass",message:"Config file is valid JSONC",details:[`Config: ${e}`]}}catch(t){return{name:a[s.CONFIG_VALIDATION],status:"fail",message:`Config parse error: ${t instanceof Error?t.message:"Unknown error"}`,details:[`Config: ${e}`]}}}function ln(){return{id:s.CONFIG_VALIDATION,name:a[s.CONFIG_VALIDATION],category:"configuration",check:oe,critical:!1}}import{existsSync as pn,readFileSync as re}from"fs";import{homedir as ae}from"os";import{join as V}from"path";import*as un from"jsonc-parser";function _(n){let e=[],t=un.parse(n,e,{allowTrailingComma:!0});if(e.length>0)throw Error(`JSONC parse error: ${e[0].error}`);return t}var mn=V(ae(),".config","opencode"),ce=V(mn,"opencode.json"),gn=V(mn,"opencode.jsonc"),U={anthropic:{plugin:"builtin",name:"Anthropic (Claude)"},openai:{plugin:"opencode-openai-codex-auth",name:"OpenAI (ChatGPT)"},google:{plugin:"opencode-antigravity-auth",name:"Google (Gemini)"}};function le(){let n=pn(gn)?gn:ce;if(!pn(n))return null;try{let e=re(n,"utf-8");return _(e)}catch{return null}}function ue(n,e){if(e==="builtin")return!0;return n.some((t)=>t===e||t.startsWith(`${e}@`))}function pe(n){let t=le()?.plugin??[],i=U[n],o=ue(t,i.plugin);return{id:n,name:i.name,pluginInstalled:o,configured:o}}async function z(n){let e=pe(n),t=`auth-${n}`,i=a[t]||e.name;if(!e.pluginInstalled)return{name:i,status:"skip",message:"Auth plugin not installed",details:[`Plugin: ${U[n].plugin}`,"Run: kraken-code install"]};return{name:i,status:"pass",message:"Auth plugin available",details:[n==="anthropic"?"Run: opencode auth login (select Anthropic)":`Plugin: ${U[n].plugin}`]}}async function ge(){return z("anthropic")}async function me(){return z("openai")}async function fe(){return z("google")}function fn(){return[{id:s.AUTH_ANTHROPIC,name:a[s.AUTH_ANTHROPIC],category:"authentication",check:ge,critical:!1},{id:s.AUTH_OPENAI,name:a[s.AUTH_OPENAI],category:"authentication",check:me,critical:!1},{id:s.AUTH_GOOGLE,name:a[s.AUTH_GOOGLE],category:"authentication",check:fe,critical:!1}]}function de(){return process.platform==="win32"?["where"]:["which"]}async function b(n){try{let e=Bun.spawn([...de(),n],{stdout:"pipe",stderr:"pipe"}),t=await new Response(e.stdout).text();if(await e.exited,e.exitCode===0)return{exists:!0,path:t.trim().split(`
|
|
4
|
+
`)[0]?.trim()??null}}catch{}return{exists:!1,path:null}}async function F(n){try{let e=Bun.spawn([n,"--version"],{stdout:"pipe",stderr:"pipe"}),t=await new Response(e.stdout).text(),i=await new Response(e.stderr).text();if(await e.exited,e.exitCode===0)return(t.trim()||i.trim()).split(`
|
|
5
|
+
`)[0]}catch{}return null}async function he(){let n=await b("sg"),e=!n.exists?await b("ast-grep"):null,t=n.exists?n:e;if(!t||!t.exists)return{name:"AST-Grep CLI",required:!1,installed:!1,version:null,path:null,installHint:"Install: npm install -g @ast-grep/cli"};return{name:"AST-Grep CLI",required:!1,installed:!0,version:await F(t.path),path:t.path}}function Pe(){try{return j.resolve("@ast-grep/napi"),{name:"AST-Grep NAPI",required:!1,installed:!0,version:null,path:null}}catch{return{name:"AST-Grep NAPI",required:!1,installed:!1,version:null,path:null,installHint:"Will use CLI fallback if available"}}}async function Ie(){let n=await b("python3"),e=!n.exists?await b("python"):null,t=n.exists?n:e;if(!t||!t.exists)return{name:"Python 3",required:!1,installed:!1,version:null,path:null,installHint:process.platform==="win32"?"Install: https://www.python.org/downloads/windows/":"Install: https://www.python.org/downloads/"};return{name:"Python 3",required:!1,installed:!0,version:await F(t.path),path:t.path}}async function Oe(){let n=await b("rg");if(!n.exists)return{name:"ripgrep (rg)",required:!1,installed:!1,version:null,path:null,installHint:"Install: apt install ripgrep or brew install ripgrep"};return{name:"ripgrep (rg)",required:!1,installed:!0,version:await F(n.path),path:n.path}}function $(n,e){if(n.installed)return{name:e,status:"pass",message:n.version??"installed",details:n.path?[`Path: ${n.path}`]:void 0};return{name:e,status:"warn",message:"Not installed (optional)",details:n.installHint?[n.installHint]:void 0}}async function xe(){let n=await he();return $(n,a[s.DEP_AST_GREP_CLI])}async function Re(){let n=Pe();return $(n,a[s.DEP_AST_GREP_NAPI])}async function ve(){let n=await Ie();return $(n,a[s.DEP_PYTHON])}async function Ce(){let n=await Oe();return $(n,a[s.DEP_RIPGREP])}async function ke(){return{name:"Comment Checker",status:"pass",message:"Built-in hook, always available",details:["Comment checking is handled by hooks/comment-checker"]}}function dn(){return[{id:s.DEP_AST_GREP_CLI,name:a[s.DEP_AST_GREP_CLI],category:"dependencies",check:xe,critical:!1},{id:s.DEP_AST_GREP_NAPI,name:a[s.DEP_AST_GREP_NAPI],category:"dependencies",check:Re,critical:!1},{id:s.DEP_PYTHON,name:a[s.DEP_PYTHON],category:"dependencies",check:ve,critical:!1},{id:s.DEP_RIPGREP,name:a[s.DEP_RIPGREP],category:"dependencies",check:Ce,critical:!1},{id:s.DEP_COMMENT_CHECKER,name:a[s.DEP_COMMENT_CHECKER],category:"dependencies",check:ke,critical:!1}]}async function Te(n){try{let e=Bun.spawn(["which",n],{stdout:"pipe",stderr:"pipe"}),t=await new Response(e.stdout).text();if(await e.exited,e.exitCode===0)return{exists:!0,path:t.trim()}}catch{}return{exists:!1,path:null}}async function je(){try{let n=Bun.spawn(["gh","--version"],{stdout:"pipe",stderr:"pipe"}),e=await new Response(n.stdout).text();if(await n.exited,n.exitCode===0)return e.match(/gh version (\S+)/)?.[1]??e.trim().split(`
|
|
6
|
+
`)[0]}catch{}return null}async function ye(){try{let n=Bun.spawn(["gh","auth","status"],{stdout:"pipe",stderr:"pipe",env:{...process.env,GH_NO_UPDATE_NOTIFIER:"1"}}),e=await new Response(n.stdout).text(),t=await new Response(n.stderr).text();await n.exited;let i=t||e;if(n.exitCode===0){let l=i.match(/Logged in to github\.com account (\S+)/)?.[1]?.replace(/[()]/g,"")??null,u=i.match(/Token scopes?:\s*(.+)/i),p=u?.[1]?u[1].split(/,\s*/).map((c)=>c.replace(/['"]/g,"").trim()).filter(Boolean):[];return{authenticated:!0,username:l,scopes:p,error:null}}let o=i.match(/error[:\s]+(.+)/i);return{authenticated:!1,username:null,scopes:[],error:o?.[1]?.trim()??"Not authenticated"}}catch(n){return{authenticated:!1,username:null,scopes:[],error:n instanceof Error?n.message:"Failed to check auth status"}}}async function Ee(){let n=await Te("gh");if(!n.exists)return{installed:!1,version:null,path:null,authenticated:!1,username:null,scopes:[],error:null};let[e,t]=await Promise.all([je(),ye()]);return{installed:!0,version:e,path:n.path,authenticated:t.authenticated,username:t.username,scopes:t.scopes,error:t.error}}async function we(){let n=await Ee(),e=a[s.GH_CLI];if(!n.installed)return{name:e,status:"warn",message:"Not installed (optional)",details:["GitHub CLI is used by librarian agent and scripts","Install: https://cli.github.com/"]};if(!n.authenticated)return{name:e,status:"warn",message:`${n.version??"installed"} - not authenticated`,details:[n.path?`Path: ${n.path}`:null,"Authenticate: gh auth login",n.error?`Error: ${n.error}`:null].filter((i)=>i!==null)};let t=[];if(n.path)t.push(`Path: ${n.path}`);if(n.username)t.push(`Account: ${n.username}`);if(n.scopes.length>0)t.push(`Scopes: ${n.scopes.join(", ")}`);return{name:e,status:"pass",message:`${n.version??"installed"} - authenticated as ${n.username??"unknown"}`,details:t.length>0?t:void 0}}function hn(){return{id:s.GH_CLI,name:a[s.GH_CLI],category:"tools",check:we,critical:!1}}function q(n){return{"typescript-language-server":"npm install -g typescript-language-server typescript",pyright:"npm install -g pyright","rust-analyzer":"rustup component add rust-analyzer",gopls:"go install golang.org/x/tools/gopls@latest",jdtls:"Install from https://download.eclipse.org/jdtls/snapshots/",clangd:"Install from https://clangd.llvm.org/installation",omnisharp:"Install from https://github.com/OmniSharp/omnisharp-roslyn",intelephense:"npm install -g intelephense","ruby-lsp":"gem install ruby-lsp","dart-analysis-server":"Install Dart SDK from https://dart.dev/get-dart","lua-language-server":"Install from https://github.com/LuaLS/lua-language-server","kotlin-language-server":"Install from https://github.com/fwcd/kotlin-language-server","svelte-language-server":"npm install -g svelte-language-server","vue-language-server":"npm install -g @vue/language-server","elixir-ls":"Install from https://github.com/elixir-lsp/elixir-ls","terraform-ls":"Install from https://github.com/hashicorp/terraform-ls"}[n]??`Install ${n}`}var Ae=[{id:"typescript-language-server",binary:"typescript-language-server",extensions:[".ts",".tsx",".js",".jsx"]},{id:"pyright",binary:"pyright-langserver",extensions:[".py"]},{id:"rust-analyzer",binary:"rust-analyzer",extensions:[".rs"]},{id:"gopls",binary:"gopls",extensions:[".go"]}];function Ne(){return process.platform==="win32"?["where"]:["which"]}async function be(n){try{let e=Bun.spawn([...Ne(),n],{stdout:"pipe",stderr:"pipe"});return await e.exited,e.exitCode===0}catch{return!1}}async function Le(){let n=[];for(let e of Ae){let t=await be(e.binary);n.push({id:e.id,installed:t,extensions:e.extensions,source:"builtin"})}return n}function De(n){return{installed:n.filter((t)=>t.installed).length,total:n.length}}async function _e(){let n=await Le(),e=De(n),t=n.filter((r)=>r.installed),i=n.filter((r)=>!r.installed);if(e.installed===0)return{name:a[s.LSP_SERVERS],status:"warn",message:"No LSP servers detected",details:["LSP tools will have limited functionality",...i.map((r)=>`Missing: ${r.id} (install: ${q(r.id)})`)]};let o=[...t.map((r)=>`Installed: ${r.id}`),...i.map((r)=>`Not found: ${r.id} (install: ${q(r.id)})`)];return{name:a[s.LSP_SERVERS],status:"pass",message:`${e.installed}/${e.total} servers available`,details:o}}function Pn(){return{id:s.LSP_SERVERS,name:a[s.LSP_SERVERS],category:"tools",check:_e,critical:!1}}import{existsSync as $e,readFileSync as Se}from"fs";import{homedir as Ge}from"os";import{join as H}from"path";var Je=["websearch","context7","grep_app"],Ue=[H(Ge(),".claude",".mcp.json"),H(process.cwd(),".mcp.json"),H(process.cwd(),".claude",".mcp.json")];function Ve(){let n={};for(let e of Ue){if(!$e(e))continue;try{let t=Se(e,"utf-8"),i=_(t);if(i.mcpServers)Object.assign(n,i.mcpServers)}catch{}}return n}function ze(){return Je.map((n)=>({id:n,type:"builtin",enabled:!0,valid:!0}))}function Fe(){let n=Ve(),e=[];for(let[t,i]of Object.entries(n)){let o=typeof i==="object"&&i!==null;e.push({id:t,type:"user",enabled:!0,valid:o,error:o?void 0:"Invalid configuration format"})}return e}async function qe(){let n=ze();return{name:a[s.MCP_BUILTIN],status:"pass",message:`${n.length} built-in servers enabled`,details:n.map((e)=>`Enabled: ${e.id}`)}}async function He(){let n=Fe();if(n.length===0)return{name:a[s.MCP_USER],status:"skip",message:"No user MCP configuration found",details:["Optional: Add .mcp.json for custom MCP servers"]};let e=n.filter((t)=>!t.valid);if(e.length>0)return{name:a[s.MCP_USER],status:"warn",message:`${e.length} server(s) have configuration issues`,details:[...n.filter((t)=>t.valid).map((t)=>`Valid: ${t.id}`),...e.map((t)=>`Invalid: ${t.id} - ${t.error}`)]};return{name:a[s.MCP_USER],status:"pass",message:`${n.length} user server(s) configured`,details:n.map((t)=>`Configured: ${t.id}`)}}function In(){return[{id:s.MCP_BUILTIN,name:a[s.MCP_BUILTIN],category:"tools",check:qe,critical:!1},{id:s.MCP_USER,name:a[s.MCP_USER],category:"tools",check:He,critical:!1}]}async function Xe(){return{name:a[s.VERSION_STATUS],status:"pass",message:"Kraken Code plugin version check bypassed (version tracking not implemented)",details:[`Minimum required: ${y}`,"Run 'bun run build' to build the plugin"]}}function On(){return{id:s.VERSION_STATUS,name:a[s.VERSION_STATUS],category:"updates",check:Xe,critical:!1}}function xn(){return[tn(),an(),ln(),...fn(),...dn(),hn(),Pn(),...In(),On()]}import g from"picocolors";function We(n){switch(n){case"pass":return P.check;case"fail":return P.cross;case"warn":return P.warn;case"skip":return P.skip}}function Rn(n,e){let t=We(n.status),i=K[n.status],o=i(n.name),r=g.dim(n.message),l=` ${t} ${o}`;if(n.message)l+=` ${P.arrow} ${r}`;if(e&&n.details&&n.details.length>0){let u=n.details.map((p)=>` ${P.bullet} ${g.dim(p)}`).join(`
|
|
7
|
+
`);l+=`
|
|
8
|
+
`+u}return l}function vn(n){let e=nn[n]||n;return`
|
|
9
|
+
${g.bold(g.white(e))}
|
|
10
|
+
${g.dim("\u2500".repeat(40))}`}function Cn(n){let e=[];e.push(g.bold(g.white("Summary"))),e.push(g.dim("\u2500".repeat(40))),e.push("");let t=n.passed>0?g.green(`${n.passed} passed`):g.dim("0 passed"),i=n.failed>0?g.red(`${n.failed} failed`):g.dim("0 failed"),o=n.warnings>0?g.yellow(`${n.warnings} warnings`):g.dim("0 warnings"),r=n.skipped>0?g.dim(`${n.skipped} skipped`):"",l=[t,i,o];if(r)l.push(r);return e.push(` ${l.join(", ")}`),e.push(` ${g.dim(`Total: ${n.total} checks in ${n.duration}ms`)}`),e.join(`
|
|
11
|
+
`)}function kn(){return`
|
|
12
|
+
${g.bgBlue(g.white(" Kraken Code Doctor "))}
|
|
13
|
+
`}function Tn(n){if(n.failed>0)return`
|
|
14
|
+
${P.cross} ${g.red("Issues detected. Please review the errors above.")}
|
|
15
|
+
`;if(n.warnings>0)return`
|
|
16
|
+
${P.warn} ${g.yellow("All systems operational with warnings.")}
|
|
17
|
+
`;return`
|
|
18
|
+
${P.check} ${g.green("All systems operational!")}
|
|
19
|
+
`}function jn(n){return JSON.stringify(n,null,2)}async function Qe(n){let e=performance.now();try{let t=await n.check();return t.duration=Math.round(performance.now()-e),t}catch(t){let i=t instanceof Error?t.message:"Unknown error",o=t instanceof Error?t.stack:void 0;if(console.error(`[doctor] Check "${n.name}" failed:`,i),o)console.error(`[doctor] Stack trace:
|
|
20
|
+
${o}`);return{name:n.name,status:"fail",message:i,duration:Math.round(performance.now()-e)}}}function Ze(n,e){return{total:n.length,passed:n.filter((t)=>t.status==="pass").length,failed:n.filter((t)=>t.status==="fail").length,warnings:n.filter((t)=>t.status==="warn").length,skipped:n.filter((t)=>t.status==="skip").length,duration:Math.round(e)}}function Me(n){return n.some((t)=>t.status==="fail")?N.FAILURE:N.SUCCESS}function Ye(n,e){if(!e)return n;return n.filter((t)=>t.category===e)}function Be(n){let e=new Map;for(let t of n){let i=e.get(t.category)??[];i.push(t),e.set(t.category,i)}return e}var Ke=["installation","configuration","authentication","dependencies","tools","updates"];async function yn(n){let e=performance.now(),t=xn(),i=Ye(t,n.category),o=Be(i),r=[];if(!n.json)console.log(kn());for(let f of Ke){let R=o.get(f);if(!R||R.length===0)continue;if(!n.json)console.log(vn(f));for(let A of R){let D=await Qe(A);if(r.push(D),!n.json)console.log(Rn(D,n.verbose??!1))}}let l=performance.now()-e,u=Ze(r,l),p=Me(r),c={results:r,summary:u,exitCode:p};if(n.json)console.log(jn(c));else console.log(""),console.log(Cn(u)),console.log(Tn(u));return c}import{existsSync as W,readFileSync as nt,writeFileSync as et}from"fs";import*as E from"jsonc-parser";import S from"path";import tt from"os";import X from"picocolors";function En(){let n=S.join(tt.homedir(),".config","opencode");return{configJson:S.join(n,"opencode.json"),configJsonc:S.join(n,"opencode.jsonc")}}function st(){let n=En();if(W(n.configJsonc))return{path:n.configJsonc,format:"jsonc"};if(W(n.configJson))return{path:n.configJson,format:"json"};return null}function it(n,e){let t=[],i=E.parse(n,t,{allowTrailingComma:!0});if(t.length>0)throw Error(`Failed to parse config: ${t[0].error}`);let o=Array.isArray(i.plugin)?[...i.plugin]:[];if(o.some((p)=>p===d||p.startsWith(`${d}@`)))return n;o.push(d);let l={formattingOptions:{insertSpaces:!0,tabSize:2,insertFinalNewline:!0}};if(i.plugin===void 0){let p=0,c=n.indexOf("{");if(c!==-1)p=c+1;let f=e==="jsonc"?" ":" ",A=`${n.includes(`
|
|
21
|
+
`)?`
|
|
22
|
+
`:""}${f}"plugin": ${JSON.stringify(o)},`;return n.slice(0,p)+A+n.slice(p)}let u=E.modify(n,["plugin"],o,l);return E.applyEdits(n,u)}function ot(){return`{
|
|
1185
23
|
"plugin": [
|
|
1186
|
-
"${
|
|
24
|
+
"${d}"
|
|
1187
25
|
]
|
|
1188
26
|
}
|
|
1189
|
-
|
|
1190
|
-
}
|
|
1191
|
-
async function
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
let
|
|
1195
|
-
let
|
|
1196
|
-
if (
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
const dir = path3.dirname(paths.configJson);
|
|
1208
|
-
if (!existsSync5(dir)) {
|
|
1209
|
-
try {
|
|
1210
|
-
const { mkdirSync } = await import("fs");
|
|
1211
|
-
mkdirSync(dir, { recursive: true });
|
|
1212
|
-
} catch (error) {
|
|
1213
|
-
return {
|
|
1214
|
-
success: false,
|
|
1215
|
-
message: `Failed to create config directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
content = createDefaultConfig();
|
|
1220
|
-
format = "json";
|
|
1221
|
-
}
|
|
1222
|
-
try {
|
|
1223
|
-
const updatedContent = registerPluginInConfig(content, format);
|
|
1224
|
-
writeFileSync(configInfo?.path ?? paths.configJson, updatedContent, "utf-8");
|
|
1225
|
-
const finalConfigPath = configInfo?.path ?? paths.configJson;
|
|
1226
|
-
if (updatedContent === content) {
|
|
1227
|
-
return {
|
|
1228
|
-
success: true,
|
|
1229
|
-
message: `Plugin "${PACKAGE_NAME}" is already registered`,
|
|
1230
|
-
configPath: finalConfigPath
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
return {
|
|
1234
|
-
success: true,
|
|
1235
|
-
message: `Plugin "${PACKAGE_NAME}" registered successfully`,
|
|
1236
|
-
configPath: finalConfigPath
|
|
1237
|
-
};
|
|
1238
|
-
} catch (error) {
|
|
1239
|
-
return {
|
|
1240
|
-
success: false,
|
|
1241
|
-
message: `Failed to register plugin: ${error instanceof Error ? error.message : String(error)}`
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
function printResult(result) {
|
|
1246
|
-
if (result.success) {
|
|
1247
|
-
console.log(color3.green(`
|
|
1248
|
-
${String.fromCodePoint(10003)} ${result.message}`));
|
|
1249
|
-
if (result.configPath) {
|
|
1250
|
-
console.log(color3.dim(` Config: ${result.configPath}`));
|
|
1251
|
-
}
|
|
1252
|
-
} else {
|
|
1253
|
-
console.log(color3.red(`
|
|
1254
|
-
${String.fromCodePoint(10007)} ${result.message}`));
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
async function runInstall() {
|
|
1258
|
-
const result = await install();
|
|
1259
|
-
printResult(result);
|
|
1260
|
-
process.exit(result.success ? 0 : 1);
|
|
1261
|
-
}
|
|
1262
|
-
if (false) {}
|
|
1263
|
-
|
|
1264
|
-
// src/cli/init.ts
|
|
1265
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync, readFileSync as readFileSync6 } from "fs";
|
|
1266
|
-
import * as path4 from "path";
|
|
1267
|
-
import * as os4 from "os";
|
|
1268
|
-
import color4 from "picocolors";
|
|
1269
|
-
var __dirname = "/tmp/kraken-code/src/cli";
|
|
1270
|
-
async function runInit(options) {
|
|
1271
|
-
console.log(color4.cyan("\uD83D\uDC19 Initializing Kraken Code..."));
|
|
1272
|
-
const configDir = path4.join(os4.homedir(), ".config", "opencode");
|
|
1273
|
-
const opencodeConfigPath = path4.join(configDir, "opencode.json");
|
|
1274
|
-
const krakenConfigPath = path4.join(configDir, "kraken-code.json");
|
|
1275
|
-
if (!existsSync6(configDir)) {
|
|
1276
|
-
mkdirSync(configDir, { recursive: true });
|
|
1277
|
-
}
|
|
1278
|
-
const isMinimal = options.minimal;
|
|
1279
|
-
const isFull = options.full;
|
|
1280
|
-
const krakenConfig = {
|
|
1281
|
-
default_agent: "Kraken",
|
|
1282
|
-
blitzkrieg: {
|
|
1283
|
-
enabled: true,
|
|
1284
|
-
testPlan: {
|
|
1285
|
-
requiredBeforeImplementation: true,
|
|
1286
|
-
minTestCases: 3,
|
|
1287
|
-
requireCoverageThreshold: true,
|
|
1288
|
-
coverageThresholdPercent: 80
|
|
1289
|
-
},
|
|
1290
|
-
tddWorkflow: {
|
|
1291
|
-
enforceWriteTestFirst: true,
|
|
1292
|
-
forbidCodeWithoutTest: true,
|
|
1293
|
-
allowRefactorWithoutTest: true
|
|
1294
|
-
},
|
|
1295
|
-
evidence: {
|
|
1296
|
-
requireTestExecutionEvidence: true,
|
|
1297
|
-
requireAssertionEvidence: true,
|
|
1298
|
-
requireEdgeCaseEvidence: true
|
|
1299
|
-
},
|
|
1300
|
-
plannerConstraints: {
|
|
1301
|
-
requireTestStep: true,
|
|
1302
|
-
requireVerificationStep: true,
|
|
1303
|
-
maxImplementationStepComplexity: 3
|
|
1304
|
-
}
|
|
1305
|
-
},
|
|
1306
|
-
kratos: {
|
|
1307
|
-
enabled: true,
|
|
1308
|
-
autoSave: true,
|
|
1309
|
-
storagePath: "~/.kratos"
|
|
1310
|
-
},
|
|
1311
|
-
modes: {
|
|
1312
|
-
blitzkrieg: {
|
|
1313
|
-
enabled: true
|
|
1314
|
-
},
|
|
1315
|
-
ultrathink: {
|
|
1316
|
-
enabled: true,
|
|
1317
|
-
thinkingBudget: 32000,
|
|
1318
|
-
autoVariantSwitch: true
|
|
1319
|
-
},
|
|
1320
|
-
ultrawork: {
|
|
1321
|
-
enabled: true,
|
|
1322
|
-
parallelAgents: 4
|
|
1323
|
-
},
|
|
1324
|
-
search: {
|
|
1325
|
-
enabled: true,
|
|
1326
|
-
maxResults: 50
|
|
1327
|
-
},
|
|
1328
|
-
analyze: {
|
|
1329
|
-
enabled: true,
|
|
1330
|
-
consultationPhases: 3
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
};
|
|
1334
|
-
let existingKrakenConfig = {};
|
|
1335
|
-
if (existsSync6(krakenConfigPath)) {
|
|
1336
|
-
try {
|
|
1337
|
-
existingKrakenConfig = JSON.parse(readFileSync6(krakenConfigPath, "utf-8"));
|
|
1338
|
-
} catch {
|
|
1339
|
-
existingKrakenConfig = {};
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
const mergedKrakenConfig = {
|
|
1343
|
-
...krakenConfig,
|
|
1344
|
-
...existingKrakenConfig
|
|
1345
|
-
};
|
|
1346
|
-
writeFileSync2(krakenConfigPath, JSON.stringify(mergedKrakenConfig, null, 2));
|
|
1347
|
-
console.log(color4.green(`\u2713 Kraken Code configuration written to ${krakenConfigPath}`));
|
|
1348
|
-
let existingOpencodeConfig = {};
|
|
1349
|
-
if (existsSync6(opencodeConfigPath)) {
|
|
1350
|
-
try {
|
|
1351
|
-
existingOpencodeConfig = JSON.parse(readFileSync6(opencodeConfigPath, "utf-8"));
|
|
1352
|
-
} catch {
|
|
1353
|
-
existingOpencodeConfig = {};
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
const mergedOpencodeConfig = {
|
|
1357
|
-
...existingOpencodeConfig,
|
|
1358
|
-
plugin: Array.from(new Set([
|
|
1359
|
-
...existingOpencodeConfig.plugin || [],
|
|
1360
|
-
"kraken-code"
|
|
1361
|
-
]))
|
|
1362
|
-
};
|
|
1363
|
-
writeFileSync2(opencodeConfigPath, JSON.stringify(mergedOpencodeConfig, null, 2));
|
|
1364
|
-
console.log(color4.green(`\u2713 OpenCode configuration updated at ${opencodeConfigPath}`));
|
|
1365
|
-
await installSkillTemplates();
|
|
1366
|
-
console.log(color4.green(`
|
|
1367
|
-
\uD83C\uDF89 Kraken Code initialized!`));
|
|
1368
|
-
console.log(color4.dim(`
|
|
1369
|
-
Next steps:`));
|
|
1370
|
-
console.log(color4.dim(" 1. Run: opencode"));
|
|
1371
|
-
console.log(color4.dim(" 2. Use 'blitz' or 'blz' to activate Blitzkrieg Mode"));
|
|
1372
|
-
}
|
|
1373
|
-
async function installSkillTemplates() {
|
|
1374
|
-
const skillDir = path4.join(os4.homedir(), ".config", "opencode", "skill");
|
|
1375
|
-
if (!existsSync6(skillDir)) {
|
|
1376
|
-
mkdirSync(skillDir, { recursive: true });
|
|
1377
|
-
}
|
|
1378
|
-
const sourceSkillsDir = path4.join(__dirname, "../../templates/skills");
|
|
1379
|
-
if (existsSync6(sourceSkillsDir)) {
|
|
1380
|
-
const { copyFile } = await import("fs/promises");
|
|
1381
|
-
const { readdir } = await import("fs/promises");
|
|
1382
|
-
try {
|
|
1383
|
-
const skillCategories = await readdir(sourceSkillsDir);
|
|
1384
|
-
for (const category of skillCategories) {
|
|
1385
|
-
const sourcePath = path4.join(sourceSkillsDir, category);
|
|
1386
|
-
const destPath = path4.join(skillDir, category);
|
|
1387
|
-
if (!existsSync6(destPath)) {
|
|
1388
|
-
mkdirSync(destPath, { recursive: true });
|
|
1389
|
-
}
|
|
1390
|
-
const skillFiles = await readdir(sourcePath);
|
|
1391
|
-
for (const skillFile of skillFiles) {
|
|
1392
|
-
const sourceFilePath = path4.join(sourcePath, skillFile);
|
|
1393
|
-
const destFilePath = path4.join(destPath, skillFile);
|
|
1394
|
-
await copyFile(sourceFilePath, destFilePath);
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
} catch (error) {
|
|
1398
|
-
console.log(color4.dim(` \u2713 Skill templates installed`));
|
|
1399
|
-
}
|
|
1400
|
-
} else {
|
|
1401
|
-
console.log(color4.dim(" \u2713 Skill templates ready (manual install)"));
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
// src/cli/status.ts
|
|
1406
|
-
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
1407
|
-
import * as path5 from "path";
|
|
1408
|
-
import * as os5 from "os";
|
|
1409
|
-
import color5 from "picocolors";
|
|
1410
|
-
async function runStatus() {
|
|
1411
|
-
console.log(color5.cyan(`\uD83D\uDC19 Kraken Code Status
|
|
1412
|
-
`));
|
|
1413
|
-
const opencodeConfigPath = path5.join(os5.homedir(), ".config", "opencode", "opencode.json");
|
|
1414
|
-
const krakenConfigPath = path5.join(os5.homedir(), ".config", "opencode", "kraken-code.json");
|
|
1415
|
-
if (!existsSync7(opencodeConfigPath)) {
|
|
1416
|
-
console.log(color5.red("\u2717 OpenCode configuration not found"));
|
|
1417
|
-
console.log(color5.dim(" Run: kraken-code init"));
|
|
1418
|
-
return;
|
|
1419
|
-
}
|
|
1420
|
-
const opencodeConfig = JSON.parse(readFileSync7(opencodeConfigPath, "utf-8"));
|
|
1421
|
-
const isPluginRegistered = opencodeConfig.plugin?.includes("kraken-code") || false;
|
|
1422
|
-
console.log(color5.bold("Plugin:"));
|
|
1423
|
-
if (isPluginRegistered) {
|
|
1424
|
-
console.log(color5.green(" \u2713 Registered: kraken-code"));
|
|
1425
|
-
} else {
|
|
1426
|
-
console.log(color5.red(" \u2717 Not registered"));
|
|
1427
|
-
console.log(color5.dim(" Run: kraken-code init"));
|
|
1428
|
-
}
|
|
1429
|
-
if (!existsSync7(krakenConfigPath)) {
|
|
1430
|
-
console.log(color5.yellow(`
|
|
1431
|
-
\u26A0 Kraken Code configuration not found`));
|
|
1432
|
-
console.log(color5.dim(" Run: kraken-code init"));
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
const config2 = JSON.parse(readFileSync7(krakenConfigPath, "utf-8"));
|
|
1436
|
-
console.log(color5.bold("Plugin:"));
|
|
1437
|
-
console.log(color5.green(" \u2713 Registered: kraken-code"));
|
|
1438
|
-
if (config2.default_agent) {
|
|
1439
|
-
console.log(color5.bold(`
|
|
1440
|
-
Default Agent:`));
|
|
1441
|
-
console.log(` ${config2.default_agent}`);
|
|
1442
|
-
}
|
|
1443
|
-
if (config2.agents) {
|
|
1444
|
-
const enabledAgents = Object.entries(config2.agents).filter(([_, agent]) => !agent.disable).map(([name]) => name);
|
|
1445
|
-
if (enabledAgents.length > 0) {
|
|
1446
|
-
console.log(color5.bold(`
|
|
1447
|
-
Enabled Agents:`));
|
|
1448
|
-
console.log(` ${enabledAgents.join(", ")}`);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
if (config2.blitzkrieg) {
|
|
1452
|
-
const status = config2.blitzkrieg.enabled ? color5.green("\u2713 Enabled") : color5.red("\u2717 Disabled");
|
|
1453
|
-
console.log(color5.bold(`
|
|
1454
|
-
Blitzkrieg Mode:`));
|
|
1455
|
-
console.log(` Status: ${status}`);
|
|
1456
|
-
console.log(color5.dim(" Activate with: 'blitz' or 'blz'"));
|
|
1457
|
-
}
|
|
1458
|
-
if (config2.kratos) {
|
|
1459
|
-
const status = config2.kratos.enabled ? color5.green("\u2713 Enabled") : color5.red("\u2717 Disabled");
|
|
1460
|
-
console.log(color5.bold(`
|
|
1461
|
-
Memory (Kratos):`));
|
|
1462
|
-
console.log(` Status: ${status}`);
|
|
1463
|
-
console.log(` Storage: ${config2.kratos.storagePath || config2.kratos.storagePath || "~/.kratos"}`);
|
|
1464
|
-
}
|
|
1465
|
-
if (config2.modes) {
|
|
1466
|
-
const modeNames = [];
|
|
1467
|
-
for (const [modeName, modeConfig] of Object.entries(config2.modes)) {
|
|
1468
|
-
if (typeof modeConfig === "object" && modeConfig && modeConfig.enabled) {
|
|
1469
|
-
modeNames.push(modeName);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
if (modeNames.length > 0) {
|
|
1473
|
-
console.log(color5.bold(`
|
|
1474
|
-
Active Modes:`));
|
|
1475
|
-
console.log(` ${modeNames.join(", ")}`);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
// package.json
|
|
1480
|
-
var version2 = "1.1.3";
|
|
1481
|
-
|
|
1482
|
-
// src/cli/index.ts
|
|
1483
|
-
var program = new Command;
|
|
1484
|
-
program.name("kraken-code").description("Kraken Code CLI - Unified OpenCode Plugin Manager").version(version2);
|
|
1485
|
-
program.command("install").description("Install and register Kraken Code plugin").action(async () => {
|
|
1486
|
-
await runInstall();
|
|
1487
|
-
});
|
|
1488
|
-
program.command("init").description("Initialize Kraken Code with recommended configuration").option("--minimal", "Minimal setup (agents only)").option("--full", "Full setup (all features)").action(async (options) => {
|
|
1489
|
-
await runInit(options);
|
|
1490
|
-
});
|
|
1491
|
-
program.command("status").description("Show Kraken Code installation status").action(async () => {
|
|
1492
|
-
await runStatus();
|
|
1493
|
-
});
|
|
1494
|
-
program.command("doctor").description("Run system checks and diagnostics").option("-c, --category <category>", "Run checks for a specific category").option("--json", "Output results as JSON").option("-v, --verbose", "Show detailed output").action(async (options) => {
|
|
1495
|
-
await runDoctor({
|
|
1496
|
-
category: options.category,
|
|
1497
|
-
json: options.json,
|
|
1498
|
-
verbose: options.verbose
|
|
1499
|
-
});
|
|
1500
|
-
});
|
|
1501
|
-
program.parse(process.argv);
|
|
1502
|
-
if (!process.argv.slice(2).length) {
|
|
1503
|
-
program.outputHelp();
|
|
1504
|
-
}
|
|
27
|
+
`}async function rt(){let n=En(),e=st(),t,i;if(e)try{t=nt(e.path,"utf-8"),i=e.format}catch(o){return{success:!1,message:`Failed to read config: ${o instanceof Error?o.message:String(o)}`}}else{let o=S.dirname(n.configJson);if(!W(o))try{let{mkdirSync:r}=await import("fs");r(o,{recursive:!0})}catch(r){return{success:!1,message:`Failed to create config directory: ${r instanceof Error?r.message:String(r)}`}}t=ot(),i="json"}try{let o=it(t,i);et(e?.path??n.configJson,o,"utf-8");let r=e?.path??n.configJson;if(o===t)return{success:!0,message:`Plugin "${d}" is already registered`,configPath:r};return{success:!0,message:`Plugin "${d}" registered successfully`,configPath:r}}catch(o){return{success:!1,message:`Failed to register plugin: ${o instanceof Error?o.message:String(o)}`}}}function at(n){if(n.success){if(console.log(X.green(`
|
|
28
|
+
${String.fromCodePoint(10003)} ${n.message}`)),n.configPath)console.log(X.dim(` Config: ${n.configPath}`))}else console.log(X.red(`
|
|
29
|
+
${String.fromCodePoint(10007)} ${n.message}`))}async function wn(){let n=await rt();at(n),process.exit(n.success?0:1)}import{writeFileSync as An,existsSync as w,mkdirSync as Q,readFileSync as Nn}from"fs";import*as I from"path";import*as Z from"os";import x from"picocolors";var __dirname="/home/runner/work/kraken-code/kraken-code/src/cli";async function bn(n){console.log(x.cyan("\uD83D\uDC19 Initializing Kraken Code..."));let e=I.join(Z.homedir(),".config","opencode"),t=I.join(e,"opencode.json"),i=I.join(e,"kraken-code.json");if(!w(e))Q(e,{recursive:!0});let{minimal:o,full:r}=n,l={default_agent:"Kraken",blitzkrieg:{enabled:!0,testPlan:{requiredBeforeImplementation:!0,minTestCases:3,requireCoverageThreshold:!0,coverageThresholdPercent:80},tddWorkflow:{enforceWriteTestFirst:!0,forbidCodeWithoutTest:!0,allowRefactorWithoutTest:!0},evidence:{requireTestExecutionEvidence:!0,requireAssertionEvidence:!0,requireEdgeCaseEvidence:!0},plannerConstraints:{requireTestStep:!0,requireVerificationStep:!0,maxImplementationStepComplexity:3}},learning:{enabled:!0,autoSave:!0,storagePath:"~/.kraken/learning",experienceStore:{enabled:!0,maxEntries:2000},knowledgeGraph:{enabled:!0,maxNodes:5000},patternDetection:{enabled:!0,minConfidence:0.6,maxPatterns:500},spacedRepetition:{enabled:!0,initialIntervalDays:1,easeFactor:2.5,maxIntervalDays:365},stateMachines:{enabled:!0}},modes:{blitzkrieg:{enabled:!0},ultrathink:{enabled:!0,thinkingBudget:32000,autoVariantSwitch:!0},ultrawork:{enabled:!0,parallelAgents:4},search:{enabled:!0,maxResults:50},analyze:{enabled:!0,consultationPhases:3}}},u={};if(w(i))try{u=JSON.parse(Nn(i,"utf-8"))}catch{u={}}let p={...l,...u};An(i,JSON.stringify(p,null,2)),console.log(x.green(`\u2713 Kraken Code configuration written to ${i}`));let c={};if(w(t))try{c=JSON.parse(Nn(t,"utf-8"))}catch{c={}}let f={...c,$schema:"https://opencode.ai/config.json",plugin:Array.from(new Set([...c.plugin||[],"kraken-code"]))};An(t,JSON.stringify(f,null,2)),console.log(x.green(`\u2713 OpenCode configuration updated at ${t}`)),await ct(),console.log(x.green(`
|
|
30
|
+
\uD83C\uDF89 Kraken Code initialized!`)),console.log(x.dim(`
|
|
31
|
+
Next steps:`)),console.log(x.dim(" 1. Run: opencode")),console.log(x.dim(" 2. Use 'blitz' or 'blz' to activate Blitzkrieg Mode"))}async function ct(){let n=I.join(Z.homedir(),".config","opencode","skill");if(!w(n))Q(n,{recursive:!0});let e=I.join(__dirname,"../../templates/skills");if(w(e)){let{copyFile:t}=await import("fs/promises"),{readdir:i}=await import("fs/promises");try{let o=await i(e);for(let r of o){let l=I.join(e,r),u=I.join(n,r);if(!w(u))Q(u,{recursive:!0});let p=await i(l);for(let c of p){let f=I.join(l,c),R=I.join(u,c);await t(f,R)}}}catch(o){console.log(x.dim(" \u2713 Skill templates installed"))}}else console.log(x.dim(" \u2713 Skill templates ready (manual install)"))}import{readFileSync as Ln,existsSync as Dn}from"fs";import*as M from"path";import*as Y from"os";import m from"picocolors";async function _n(){console.log(m.cyan(`\uD83D\uDC19 Kraken Code Status
|
|
32
|
+
`));let n=M.join(Y.homedir(),".config","opencode","opencode.json"),e=M.join(Y.homedir(),".config","opencode","kraken-code.json");if(!Dn(n)){console.log(m.red("\u2717 OpenCode configuration not found")),console.log(m.dim(" Run: kraken-code init"));return}let i=JSON.parse(Ln(n,"utf-8")).plugin?.includes("kraken-code")||!1;if(console.log(m.bold("Plugin:")),i)console.log(m.green(" \u2713 Registered: kraken-code"));else console.log(m.red(" \u2717 Not registered")),console.log(m.dim(" Run: kraken-code init"));if(!Dn(e)){console.log(m.yellow(`
|
|
33
|
+
\u26A0 Kraken Code configuration not found`)),console.log(m.dim(" Run: kraken-code init"));return}let o=JSON.parse(Ln(e,"utf-8"));if(console.log(m.bold("Plugin:")),console.log(m.green(" \u2713 Registered: kraken-code")),o.default_agent)console.log(m.bold(`
|
|
34
|
+
Default Agent:`)),console.log(` ${o.default_agent}`);if(o.agents){let l=Object.entries(o.agents).filter(([u,p])=>!p.disable).map(([u])=>u);if(l.length>0)console.log(m.bold(`
|
|
35
|
+
Enabled Agents:`)),console.log(` ${l.join(", ")}`)}if(o.blitzkrieg){let l=o.blitzkrieg.enabled?m.green("\u2713 Enabled"):m.red("\u2717 Disabled");console.log(m.bold(`
|
|
36
|
+
Blitzkrieg Mode:`)),console.log(` Status: ${l}`),console.log(m.dim(" Activate with: 'blitz' or 'blz'"))}let r=o.learning||o.memory;if(r){let l=r.enabled?m.green("\u2713 Enabled"):m.red("\u2717 Disabled");console.log(m.bold(`
|
|
37
|
+
Learning:`)),console.log(` Status: ${l}`),console.log(` Storage: ${r.storagePath||"~/.kraken/learning"}`)}if(o.modes){let l=[];for(let[u,p]of Object.entries(o.modes))if(typeof p==="object"&&p&&p.enabled)l.push(u);if(l.length>0)console.log(m.bold(`
|
|
38
|
+
Active Modes:`)),console.log(` ${l.join(", ")}`)}}import{existsSync as L,readFileSync as lt,writeFileSync as ut,unlinkSync as pt}from"fs";import*as k from"jsonc-parser";import C from"path";import B from"os";import O from"picocolors";function gt(){let n=C.join(B.homedir(),".config","opencode");return{configJson:C.join(n,"opencode.json"),configJsonc:C.join(n,"opencode.jsonc")}}function mt(){let n=gt();if(L(n.configJsonc))return{path:n.configJsonc,format:"jsonc"};if(L(n.configJson))return{path:n.configJson,format:"json"};return null}function ft(n,e){let t=[],i=k.parse(n,t,{allowTrailingComma:!0});if(t.length>0)throw Error(`Failed to parse config: ${t[0].error}`);let r=(Array.isArray(i.plugin)?[...i.plugin]:[]).filter((p)=>p!==d&&!p.startsWith(`${d}@`));if(i.plugin===void 0)return n;let u=k.modify(n,["plugin"],r,{formattingOptions:{insertSpaces:!0,tabSize:2,insertFinalNewline:!0}});return k.applyEdits(n,u)}async function $n(n){console.log(O.cyan("\uD83D\uDDD1\uFE0F Uninstalling Kraken Code plugin..."));let e=mt();if(!e)return{success:!1,message:"OpenCode config not found"};let t,i;try{t=lt(e.path,"utf-8"),i=e.format}catch(c){return{success:!1,message:`Failed to read config: ${c instanceof Error?c.message:String(c)}`}}let o=[],r=k.parse(t,o,{allowTrailingComma:!0});if(o.length>0)return{success:!1,message:`Failed to parse config: ${o[0].error}`};if(!(Array.isArray(r.plugin)?[...r.plugin]:[]).some((c)=>c===d||c.startsWith(`${d}@`))){if(n.verbose)console.log(O.dim(` Plugin "${d}" not found in config`));return{success:!0,message:`Plugin "${d}" not registered`,configPath:e.path}}try{let c=ft(t,i);ut(e.path,c,"utf-8"),console.log(O.green(`
|
|
39
|
+
\u2713 Removed "${d}" from OpenCode plugin list`))}catch(c){return{success:!1,message:`Failed to update config: ${c instanceof Error?c.message:String(c)}`}}let p=!1;if(n.config!==!1){let c=C.join(B.homedir(),".config","opencode","kraken-code.json");if(L(c))try{pt(c),p=!0,console.log(O.green(`\u2713 Removed Kraken Code config at ${c}`))}catch(f){console.log(O.yellow(` Warning: Failed to remove ${c}: ${f instanceof Error?f.message:String(f)}`))}else if(n.verbose)console.log(O.dim(` Kraken Code config not found at ${c}`))}if(n.config!==!1){let c=C.join(B.homedir(),".config","opencode","skill");if(L(c))try{let{readdir:f,unlink:R,rmdir:A}=await import("fs/promises"),D=["kraken-code"];for(let Gn of D){let T=C.join(c,Gn);if(L(T)){let Jn=await f(T);for(let Un of Jn){let Vn=C.join(T,Un);await R(Vn)}if((await f(T)).length===0)await A(T);if(n.verbose)console.log(O.dim(` Removed skill templates from ${T}`))}}}catch(f){console.log(O.yellow(` Warning: Failed to remove skill templates: ${f instanceof Error?f.message:String(f)}`))}}return console.log(O.green(`
|
|
40
|
+
\uD83C\uDF89 Kraken Code plugin uninstalled successfully!`)),console.log(O.dim(`
|
|
41
|
+
Note: OpenCode may still be running. If so, restart it to complete uninstallation.`)),{success:!0,message:"Kraken Code plugin uninstalled",configPath:e.path,krakenConfigRemoved:p}}var Sn="1.2.0";var v=new ht;v.name("kraken-code").description("Kraken Code CLI - Unified OpenCode Plugin Manager").version(Sn);v.command("install").description("Install and register Kraken Code plugin").action(async()=>{await wn()});v.command("init").description("Initialize Kraken Code with recommended configuration").option("--minimal","Minimal setup (agents only)").option("--full","Full setup (all features)").action(async(n)=>{await bn(n)});v.command("uninstall").description("Uninstall Kraken Code plugin from OpenCode").option("--config","Also remove Kraken Code config file").option("-v, --verbose","Show detailed output").action(async(n)=>{await $n(n)});v.command("status").description("Show Kraken Code installation status").action(async()=>{await _n()});v.command("doctor").description("Run system checks and diagnostics").option("-c, --category <category>","Run checks for a specific category").option("--json","Output results as JSON").option("-v, --verbose","Show detailed output").action(async(n)=>{await yn({category:n.category,json:n.json,verbose:n.verbose})});v.parse(process.argv);if(!process.argv.slice(2).length)v.outputHelp();
|