olakai-cli 0.6.7 → 0.7.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,691 @@
1
+ import {
2
+ getCursorHooksPath,
3
+ getPlugin,
4
+ getToolScope,
5
+ installCodexHooksConfig,
6
+ loadCodexConfig,
7
+ loadCursorConfig,
8
+ mergeCursorHooks,
9
+ readRegistry,
10
+ reconcileCurrentWorkspace,
11
+ runMonitorInstall,
12
+ upsertEntry,
13
+ writeCodexConfig,
14
+ writeCursorConfig
15
+ } from "./chunk-75YQWZ4Q.js";
16
+ import {
17
+ getAgent,
18
+ listActivity,
19
+ listMyAgents,
20
+ regenerateAgentApiKey,
21
+ validateMonitoringApiKey
22
+ } from "./chunk-KNGRF4XU.js";
23
+ import {
24
+ findConfiguredWorkspace,
25
+ getSettingsPath,
26
+ loadClaudeCodeConfig,
27
+ mergeHooksSettings,
28
+ readJsonFile,
29
+ writeClaudeCodeConfig,
30
+ writeJsonFile
31
+ } from "./chunk-KY6OHQZW.js";
32
+ import {
33
+ getValidToken
34
+ } from "./chunk-AVB4N2UN.js";
35
+
36
+ // src/monitor/doctor.ts
37
+ import * as os2 from "os";
38
+
39
+ // src/monitor/doctor-adapters.ts
40
+ import * as fs from "fs";
41
+ import * as os from "os";
42
+ import * as path from "path";
43
+ var claudeCodeAdapter = {
44
+ tool: "claude-code",
45
+ displayName: "Claude Code",
46
+ configRelLabel: ".olakai/monitor-claude-code.json",
47
+ hooksLabel: ".claude/settings.json",
48
+ loadConfig: (root) => {
49
+ const c = loadClaudeCodeConfig(root, () => {
50
+ });
51
+ return c ?? null;
52
+ },
53
+ writeConfig: (root, config) => {
54
+ writeClaudeCodeConfig(root, {
55
+ agentId: config.agentId,
56
+ apiKey: config.apiKey,
57
+ agentName: config.agentName,
58
+ source: config.source,
59
+ createdAt: config.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
60
+ monitoringEndpoint: config.monitoringEndpoint
61
+ });
62
+ },
63
+ repairHooks: (root) => {
64
+ const settingsPath = getSettingsPath(root);
65
+ const existing = readJsonFile(settingsPath) ?? {};
66
+ const merged = {
67
+ ...existing,
68
+ hooks: mergeHooksSettings(existing.hooks)
69
+ };
70
+ writeJsonFile(settingsPath, merged);
71
+ }
72
+ };
73
+ var codexAdapter = {
74
+ tool: "codex",
75
+ displayName: "OpenAI Codex CLI",
76
+ configRelLabel: ".olakai/monitor-codex.json",
77
+ hooksLabel: "~/.codex/config.toml",
78
+ loadConfig: (root) => loadCodexConfig(root) ?? null,
79
+ writeConfig: (root, config) => {
80
+ writeCodexConfig(root, {
81
+ agentId: config.agentId,
82
+ apiKey: config.apiKey,
83
+ agentName: config.agentName,
84
+ source: config.source,
85
+ createdAt: config.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
86
+ monitoringEndpoint: config.monitoringEndpoint
87
+ });
88
+ },
89
+ repairHooks: () => {
90
+ installCodexHooksConfig();
91
+ }
92
+ };
93
+ var cursorAdapter = {
94
+ tool: "cursor",
95
+ displayName: "Cursor",
96
+ configRelLabel: ".olakai/monitor-cursor.json",
97
+ hooksLabel: "~/.cursor/hooks.json",
98
+ loadConfig: (root) => loadCursorConfig(root) ?? null,
99
+ writeConfig: (root, config) => {
100
+ writeCursorConfig(root, {
101
+ agentId: config.agentId,
102
+ apiKey: config.apiKey,
103
+ agentName: config.agentName,
104
+ source: config.source,
105
+ createdAt: config.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
106
+ monitoringEndpoint: config.monitoringEndpoint
107
+ });
108
+ },
109
+ repairHooks: (_root, homeDir = os.homedir()) => {
110
+ const hooksPath = getCursorHooksPath(homeDir);
111
+ let existing = null;
112
+ try {
113
+ if (fs.existsSync(hooksPath)) {
114
+ existing = JSON.parse(
115
+ fs.readFileSync(hooksPath, "utf-8")
116
+ );
117
+ }
118
+ } catch {
119
+ existing = null;
120
+ }
121
+ const merged = mergeCursorHooks(existing);
122
+ const dir = path.dirname(hooksPath);
123
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
124
+ fs.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
125
+ }
126
+ };
127
+ var ADAPTERS = {
128
+ "claude-code": claudeCodeAdapter,
129
+ codex: codexAdapter,
130
+ cursor: cursorAdapter
131
+ };
132
+ function getDoctorToolAdapter(tool) {
133
+ return ADAPTERS[tool];
134
+ }
135
+
136
+ // src/monitor/doctor.ts
137
+ var SEVERITY = { ok: 0, warn: 1, fail: 2 };
138
+ function worst(a, b) {
139
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
140
+ }
141
+ function sevenDaysAgoIso(now = /* @__PURE__ */ new Date()) {
142
+ return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3).toISOString();
143
+ }
144
+ async function runCheckChain(target, opts = {}) {
145
+ const homeDir = opts.homeDir ?? os2.homedir();
146
+ const now = opts.now ?? /* @__PURE__ */ new Date();
147
+ const tool = target.tool;
148
+ const scope = getToolScope(tool);
149
+ const adapter = getDoctorToolAdapter(tool);
150
+ const checks = [];
151
+ let registry = readRegistry(homeDir);
152
+ let entry = registry.workspaces.find(
153
+ (e) => e.path === target.path && e.tool === tool
154
+ );
155
+ if (!entry) {
156
+ try {
157
+ reconcileCurrentWorkspace(target.path, homeDir);
158
+ } catch {
159
+ }
160
+ registry = readRegistry(homeDir);
161
+ entry = registry.workspaces.find(
162
+ (e) => e.path === target.path && e.tool === tool
163
+ );
164
+ }
165
+ if (entry) {
166
+ checks.push({
167
+ id: "registry-entry",
168
+ label: "Registry entry",
169
+ status: "ok",
170
+ detail: `Recorded in the machine registry as "${entry.agentName}".`,
171
+ fixable: false
172
+ });
173
+ } else {
174
+ checks.push({
175
+ id: "registry-entry",
176
+ label: "Registry entry",
177
+ status: "warn",
178
+ detail: "No machine-registry entry for this workspace+tool (and none could be adopted from disk).",
179
+ fixable: true
180
+ });
181
+ }
182
+ const config = adapter.loadConfig(target.path);
183
+ if (config && config.agentId && config.apiKey && config.monitoringEndpoint) {
184
+ checks.push({
185
+ id: "config-valid",
186
+ label: "Config file",
187
+ status: "ok",
188
+ detail: `${adapter.configRelLabel} is present and valid (agent ${config.agentId}).`,
189
+ fixable: false
190
+ });
191
+ } else if (config) {
192
+ checks.push({
193
+ id: "config-valid",
194
+ label: "Config file",
195
+ status: "fail",
196
+ detail: `${adapter.configRelLabel} is missing required fields (agentId/apiKey/endpoint).`,
197
+ fixable: true
198
+ });
199
+ } else {
200
+ const detail = scope === "global" ? `No ${adapter.configRelLabel} in this workspace. ${adapter.displayName} hooks are global, so any activity here is NOT attributed to an agent until you run 'olakai monitor init --tool ${tool}'.` : `No ${adapter.configRelLabel} \u2014 this workspace is not configured for monitoring.`;
201
+ checks.push({
202
+ id: "config-valid",
203
+ label: "Config file",
204
+ status: "fail",
205
+ detail,
206
+ fixable: false
207
+ });
208
+ }
209
+ let hooksConfigured = false;
210
+ let hooksStatusError = null;
211
+ try {
212
+ const status = await getPlugin(tool).status({ projectRoot: target.path });
213
+ hooksConfigured = status.hooksConfigured;
214
+ } catch (err) {
215
+ hooksStatusError = err instanceof Error ? err.message : String(err);
216
+ }
217
+ if (hooksStatusError) {
218
+ checks.push({
219
+ id: "hooks-installed",
220
+ label: "Hooks installed",
221
+ status: "warn",
222
+ detail: `Couldn't determine hook status (${hooksStatusError}).`,
223
+ fixable: true
224
+ });
225
+ } else if (hooksConfigured) {
226
+ if (scope === "global" && !config) {
227
+ checks.push({
228
+ id: "hooks-installed",
229
+ label: "Hooks installed",
230
+ status: "warn",
231
+ detail: `${adapter.displayName} hooks are installed globally (${adapter.hooksLabel}), but this workspace has no monitor config \u2014 events fired here silent-exit unattributed.`,
232
+ fixable: false
233
+ });
234
+ } else {
235
+ checks.push({
236
+ id: "hooks-installed",
237
+ label: "Hooks installed",
238
+ status: "ok",
239
+ detail: scope === "global" ? `Installed globally at ${adapter.hooksLabel}.` : `Installed at ${adapter.hooksLabel}.`,
240
+ fixable: true
241
+ });
242
+ }
243
+ } else {
244
+ checks.push({
245
+ id: "hooks-installed",
246
+ label: "Hooks installed",
247
+ status: "fail",
248
+ detail: `${adapter.displayName} hooks are not installed (${adapter.hooksLabel}). The tool will not report any activity.`,
249
+ fixable: true
250
+ });
251
+ }
252
+ if (!config || !config.agentId || !config.apiKey) {
253
+ checks.push({
254
+ id: "api-key-valid",
255
+ label: "API key valid",
256
+ status: "warn",
257
+ detail: "Skipped \u2014 no usable config to read the API key from.",
258
+ fixable: true
259
+ });
260
+ checks.push({
261
+ id: "agent-exists",
262
+ label: "Agent exists",
263
+ status: "warn",
264
+ detail: "Skipped \u2014 no agentId in config to resolve.",
265
+ fixable: false
266
+ });
267
+ checks.push({
268
+ id: "events-flowing",
269
+ label: "Events flowing",
270
+ status: "warn",
271
+ detail: "Skipped \u2014 no agentId in config to probe.",
272
+ fixable: false
273
+ });
274
+ return { checks, config, allGreen: false };
275
+ }
276
+ const validation = await validateMonitoringApiKey(
277
+ config.monitoringEndpoint,
278
+ config.apiKey
279
+ );
280
+ if (validation === null) {
281
+ checks.push({
282
+ id: "api-key-valid",
283
+ label: "API key valid",
284
+ status: "fail",
285
+ detail: "The configured API key did not authenticate (rejected, or the monitoring endpoint was unreachable).",
286
+ fixable: true
287
+ });
288
+ } else {
289
+ checks.push({
290
+ id: "api-key-valid",
291
+ label: "API key valid",
292
+ status: "ok",
293
+ detail: `Authenticates against ${config.monitoringEndpoint}.`,
294
+ fixable: false
295
+ });
296
+ }
297
+ let agentResolves = false;
298
+ try {
299
+ await getAgent(config.agentId);
300
+ agentResolves = true;
301
+ checks.push({
302
+ id: "agent-exists",
303
+ label: "Agent exists",
304
+ status: "ok",
305
+ detail: `Agent ${config.agentId} resolves on the backend.`,
306
+ fixable: false
307
+ });
308
+ } catch (err) {
309
+ const message = err instanceof Error ? err.message : String(err);
310
+ if (/not found/i.test(message)) {
311
+ checks.push({
312
+ id: "agent-exists",
313
+ label: "Agent exists",
314
+ status: "fail",
315
+ detail: `Agent ${config.agentId} no longer exists on the backend (404).`,
316
+ fixable: true
317
+ });
318
+ } else if (/permission|forbidden|analyst|403/i.test(message)) {
319
+ try {
320
+ const mine = await listMyAgents();
321
+ if (mine.some((a) => a.id === config.agentId)) {
322
+ agentResolves = true;
323
+ checks.push({
324
+ id: "agent-exists",
325
+ label: "Agent exists",
326
+ status: "ok",
327
+ detail: `Agent ${config.agentId} exists on the backend (verified via account lens \u2014 you're the creator).`,
328
+ fixable: false
329
+ });
330
+ } else {
331
+ checks.push({
332
+ id: "agent-exists",
333
+ label: "Agent exists",
334
+ status: "warn",
335
+ detail: `Agent ${config.agentId} isn't in your account-lens listing (run 'olakai agents mine'). The config may be stale or belong to another user.`,
336
+ fixable: false
337
+ });
338
+ }
339
+ } catch (mineErr) {
340
+ const mineMsg = mineErr instanceof Error ? mineErr.message : String(mineErr);
341
+ checks.push({
342
+ id: "agent-exists",
343
+ label: "Agent exists",
344
+ status: "warn",
345
+ detail: `Couldn't verify agent ${config.agentId} (permission denied; account-lens lookup also failed: ${mineMsg}).`,
346
+ fixable: false
347
+ });
348
+ }
349
+ } else {
350
+ checks.push({
351
+ id: "agent-exists",
352
+ label: "Agent exists",
353
+ status: "warn",
354
+ detail: `Couldn't verify agent ${config.agentId} (${message}).`,
355
+ fixable: false
356
+ });
357
+ }
358
+ }
359
+ let lastEventAt;
360
+ if (!agentResolves) {
361
+ checks.push({
362
+ id: "events-flowing",
363
+ label: "Events flowing",
364
+ status: "warn",
365
+ detail: "Skipped \u2014 agent could not be resolved.",
366
+ fixable: false
367
+ });
368
+ } else if (!getValidToken()) {
369
+ checks.push({
370
+ id: "events-flowing",
371
+ label: "Events flowing",
372
+ status: "warn",
373
+ detail: "Skipped \u2014 not logged in (run 'olakai login' to enable this check).",
374
+ fixable: false
375
+ });
376
+ } else {
377
+ try {
378
+ const since = sevenDaysAgoIso(now);
379
+ const activity = await listActivity({
380
+ agentId: config.agentId,
381
+ since,
382
+ limit: 1
383
+ });
384
+ if (activity.prompts.length > 0) {
385
+ lastEventAt = activity.prompts[0].createdAt;
386
+ checks.push({
387
+ id: "events-flowing",
388
+ label: "Events flowing",
389
+ status: "ok",
390
+ detail: `Most recent event ${lastEventAt} (within the last 7 days).`,
391
+ fixable: false
392
+ });
393
+ } else {
394
+ checks.push({
395
+ id: "events-flowing",
396
+ label: "Events flowing",
397
+ status: "warn",
398
+ detail: "No events in the last 7 days. This is expected if the workspace has been idle; if you've been active, check hooks + API key.",
399
+ fixable: false
400
+ });
401
+ }
402
+ } catch (err) {
403
+ const message = err instanceof Error ? err.message : String(err);
404
+ checks.push({
405
+ id: "events-flowing",
406
+ label: "Events flowing",
407
+ status: "warn",
408
+ detail: /403|forbidden|permission/i.test(message) ? "Couldn't verify \u2014 your token lacks activity read access." : `Couldn't probe activity (${message}).`,
409
+ fixable: false
410
+ });
411
+ }
412
+ }
413
+ const allGreen = checks.every((c) => c.status === "ok");
414
+ if (entry && (lastEventAt || allGreen)) {
415
+ try {
416
+ upsertEntry(
417
+ {
418
+ ...entry,
419
+ ...lastEventAt ? { lastEventAt } : {},
420
+ ...allGreen ? { lastVerifiedAt: now.toISOString() } : {}
421
+ },
422
+ homeDir
423
+ );
424
+ } catch {
425
+ }
426
+ }
427
+ return { checks, config, allGreen, lastEventAt };
428
+ }
429
+ async function runFixes(target, outcome, opts = {}) {
430
+ const homeDir = opts.homeDir ?? os2.homedir();
431
+ const tool = target.tool;
432
+ const adapter = getDoctorToolAdapter(tool);
433
+ const results = [];
434
+ const byId = /* @__PURE__ */ new Map();
435
+ for (const c of outcome.checks) byId.set(c.id, c);
436
+ const needsFix = (id) => {
437
+ const c = byId.get(id);
438
+ return Boolean(c && c.fixable && c.status !== "ok");
439
+ };
440
+ if (needsFix("registry-entry")) {
441
+ try {
442
+ const adopted = reconcileCurrentWorkspace(target.path, homeDir);
443
+ const has = adopted.some((e) => e.tool === tool);
444
+ results.push({
445
+ checkId: "registry-entry",
446
+ fixed: has,
447
+ detail: has ? "Adopted the on-disk config into the machine registry." : "Nothing to adopt \u2014 there's no on-disk config to register. Run 'olakai monitor init'."
448
+ });
449
+ } catch (err) {
450
+ results.push({
451
+ checkId: "registry-entry",
452
+ fixed: false,
453
+ detail: `Reconcile failed: ${err instanceof Error ? err.message : String(err)}`
454
+ });
455
+ }
456
+ }
457
+ if (needsFix("agent-exists")) {
458
+ if (!opts.recreateMissing) {
459
+ results.push({
460
+ checkId: "agent-exists",
461
+ fixed: false,
462
+ detail: `The configured agent no longer exists on the backend. Recreating it provisions a NEW agent \u2014 re-run with '--recreate-missing', or use 'olakai monitor repair --tool ${tool}'.`
463
+ });
464
+ } else {
465
+ try {
466
+ const result = await runMonitorInstall(tool, {
467
+ projectRoot: target.path,
468
+ interactive: opts.interactive ?? false
469
+ });
470
+ results.push({
471
+ checkId: "agent-exists",
472
+ fixed: true,
473
+ detail: `Recreated the self-monitor agent "${result.agentName}" (${result.agentId}) and rewrote the config + registry.`
474
+ });
475
+ byId.delete("config-valid");
476
+ byId.delete("api-key-valid");
477
+ } catch (err) {
478
+ results.push({
479
+ checkId: "agent-exists",
480
+ fixed: false,
481
+ detail: `Could not recreate the agent: ${err instanceof Error ? err.message : String(err)}. This needs a human (check 'olakai login' and your role).`
482
+ });
483
+ }
484
+ }
485
+ }
486
+ const relinkNeeded = needsFix("config-valid") || needsFix("api-key-valid");
487
+ if (relinkNeeded) {
488
+ const config = outcome.config;
489
+ if (!config || !config.agentId) {
490
+ results.push({
491
+ checkId: needsFix("config-valid") ? "config-valid" : "api-key-valid",
492
+ fixed: false,
493
+ detail: "Can't re-link \u2014 no agentId in config. Run 'olakai monitor init' to set up this workspace."
494
+ });
495
+ } else {
496
+ try {
497
+ const rotated = await regenerateAgentApiKey(config.agentId);
498
+ adapter.writeConfig(target.path, {
499
+ agentId: config.agentId,
500
+ apiKey: rotated.key,
501
+ agentName: config.agentName,
502
+ source: config.source,
503
+ createdAt: config.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
504
+ monitoringEndpoint: config.monitoringEndpoint
505
+ });
506
+ const detail = "Rotated the agent API key and rewrote the workspace config. Other workspaces using the old key must re-init.";
507
+ if (needsFix("config-valid")) {
508
+ results.push({ checkId: "config-valid", fixed: true, detail });
509
+ }
510
+ if (needsFix("api-key-valid")) {
511
+ results.push({ checkId: "api-key-valid", fixed: true, detail });
512
+ }
513
+ } catch (err) {
514
+ const detail = `Key rotation failed: ${err instanceof Error ? err.message : String(err)}.`;
515
+ if (needsFix("config-valid")) {
516
+ results.push({ checkId: "config-valid", fixed: false, detail });
517
+ }
518
+ if (needsFix("api-key-valid")) {
519
+ results.push({ checkId: "api-key-valid", fixed: false, detail });
520
+ }
521
+ }
522
+ }
523
+ }
524
+ if (needsFix("hooks-installed")) {
525
+ try {
526
+ adapter.repairHooks(target.path);
527
+ results.push({
528
+ checkId: "hooks-installed",
529
+ fixed: true,
530
+ detail: `Re-merged ${adapter.displayName} hooks (${adapter.hooksLabel}).`
531
+ });
532
+ } catch (err) {
533
+ results.push({
534
+ checkId: "hooks-installed",
535
+ fixed: false,
536
+ detail: `Could not re-merge hooks: ${err instanceof Error ? err.message : String(err)}.`
537
+ });
538
+ }
539
+ }
540
+ return results;
541
+ }
542
+ function resolveTargets(opts) {
543
+ const cwd = opts.cwd ?? process.cwd();
544
+ const homeDir = opts.homeDir ?? os2.homedir();
545
+ if (opts.all) {
546
+ try {
547
+ reconcileCurrentWorkspace(cwd, homeDir);
548
+ } catch {
549
+ }
550
+ const registry = readRegistry(homeDir);
551
+ return registry.workspaces.map((e) => ({ path: e.path, tool: e.tool }));
552
+ }
553
+ const tool = opts.tool;
554
+ if (!tool) return [];
555
+ const root = findConfiguredWorkspace(cwd, [tool]) ?? cwd;
556
+ return [{ path: root, tool }];
557
+ }
558
+ async function buildReport(targets, opts = {}) {
559
+ const targetReports = [];
560
+ const outcomes = /* @__PURE__ */ new Map();
561
+ let overall = "ok";
562
+ for (const target of targets) {
563
+ const outcome = await runCheckChain(target, opts);
564
+ outcomes.set(targetKey(target), outcome);
565
+ const targetOverall = outcome.checks.reduce(
566
+ (acc, c) => worst(acc, c.status),
567
+ "ok"
568
+ );
569
+ overall = worst(overall, targetOverall);
570
+ const entry = findEntry(target, opts.homeDir);
571
+ targetReports.push({
572
+ path: target.path,
573
+ tool: target.tool,
574
+ scope: getToolScope(target.tool),
575
+ agentId: outcome.config?.agentId ?? entry?.agentId,
576
+ checks: outcome.checks,
577
+ overall: targetOverall
578
+ });
579
+ }
580
+ return { report: { targets: targetReports, overall }, outcomes };
581
+ }
582
+ function targetKey(target) {
583
+ return `${target.path}::${target.tool}`;
584
+ }
585
+ function findEntry(target, homeDir) {
586
+ const registry = readRegistry(homeDir ?? os2.homedir());
587
+ return registry.workspaces.find(
588
+ (e) => e.path === target.path && e.tool === target.tool
589
+ );
590
+ }
591
+ var ICON = {
592
+ ok: "\u2713",
593
+ // ✓
594
+ warn: "\u26A0",
595
+ // ⚠
596
+ fail: "\u2717"
597
+ // ✗
598
+ };
599
+ function formatReport(report) {
600
+ if (report.targets.length === 0) {
601
+ return "No monitored workspaces to check. Run 'olakai monitor init --tool <tool>' first.";
602
+ }
603
+ const lines = [];
604
+ for (const t of report.targets) {
605
+ lines.push(`${t.tool} \u2014 ${t.path} [${t.scope}]`);
606
+ for (const c of t.checks) {
607
+ lines.push(` ${ICON[c.status]} ${c.label}: ${c.detail}`);
608
+ }
609
+ lines.push("");
610
+ }
611
+ const summary = report.overall === "ok" ? "All checks passed." : report.overall === "warn" ? "Some checks need attention (warnings). Run with --fix to repair fixable issues." : "Some checks failed. Run with --fix to repair fixable issues.";
612
+ lines.push(summary);
613
+ return lines.join("\n").trimEnd();
614
+ }
615
+ function formatFixResults(target, fixes) {
616
+ if (fixes.length === 0) {
617
+ return `${target.tool} \u2014 ${target.path}: nothing to fix.`;
618
+ }
619
+ const lines = [`${target.tool} \u2014 ${target.path}:`];
620
+ for (const f of fixes) {
621
+ lines.push(` ${f.fixed ? ICON.ok : ICON.fail} ${f.checkId}: ${f.detail}`);
622
+ }
623
+ return lines.join("\n");
624
+ }
625
+ async function runDoctor(options) {
626
+ const targets = resolveTargets({
627
+ all: options.all,
628
+ tool: options.tool,
629
+ cwd: options.cwd,
630
+ homeDir: options.homeDir
631
+ });
632
+ const { report, outcomes } = await buildReport(targets, {
633
+ homeDir: options.homeDir,
634
+ now: options.now
635
+ });
636
+ if (!options.fix) {
637
+ return { report };
638
+ }
639
+ const fixes = [];
640
+ for (const target of targets) {
641
+ const outcome = outcomes.get(targetKey(target));
642
+ if (!outcome) continue;
643
+ const results = await runFixes(target, outcome, {
644
+ homeDir: options.homeDir,
645
+ interactive: options.interactive,
646
+ recreateMissing: options.recreateMissing
647
+ });
648
+ if (results.length > 0) {
649
+ fixes.push({ path: target.path, tool: target.tool, results });
650
+ }
651
+ }
652
+ const { report: afterReport } = await buildReport(targets, {
653
+ homeDir: options.homeDir,
654
+ now: options.now
655
+ });
656
+ return { report: afterReport, fixes };
657
+ }
658
+ function exitCodeForStatus(status) {
659
+ return status === "fail" ? 1 : 0;
660
+ }
661
+ function printDoctorResult(result, json) {
662
+ if (json) {
663
+ const payload = { report: result.report };
664
+ if (result.fixes) payload.fixes = result.fixes;
665
+ console.log(JSON.stringify(payload, null, 2));
666
+ return;
667
+ }
668
+ console.log(formatReport(result.report));
669
+ if (result.fixes && result.fixes.length > 0) {
670
+ console.log("");
671
+ console.log("Repairs:");
672
+ for (const f of result.fixes) {
673
+ console.log(formatFixResults({ path: f.path, tool: f.tool }, f.results));
674
+ }
675
+ }
676
+ }
677
+
678
+ export {
679
+ getDoctorToolAdapter,
680
+ runCheckChain,
681
+ runFixes,
682
+ resolveTargets,
683
+ buildReport,
684
+ targetKey,
685
+ formatReport,
686
+ formatFixResults,
687
+ runDoctor,
688
+ exitCodeForStatus,
689
+ printDoctorResult
690
+ };
691
+ //# sourceMappingURL=chunk-GXKHWBGO.js.map