kraken-code 1.0.0

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