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