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.
- package/dist/{api-OAWRQIZM.js → api-JBVSHCKY.js} +2 -2
- package/dist/chunk-75YQWZ4Q.js +2587 -0
- package/dist/chunk-75YQWZ4Q.js.map +1 -0
- package/dist/chunk-GXKHWBGO.js +691 -0
- package/dist/chunk-GXKHWBGO.js.map +1 -0
- package/dist/{chunk-43E2A3O2.js → chunk-KNGRF4XU.js} +7 -2
- package/dist/chunk-KNGRF4XU.js.map +1 -0
- package/dist/{chunk-2Q7JYGCK.js → chunk-KY6OHQZW.js} +2 -1
- package/dist/doctor-27VDFNP7.js +29 -0
- package/dist/index.js +307 -2429
- package/dist/index.js.map +1 -1
- package/dist/repair-WSBWAW2B.js +124 -0
- package/dist/repair-WSBWAW2B.js.map +1 -0
- package/dist/{status-USHUUHK6.js → status-IZCIMES2.js} +2 -2
- package/dist/status-IZCIMES2.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-43E2A3O2.js.map +0 -1
- /package/dist/{api-OAWRQIZM.js.map → api-JBVSHCKY.js.map} +0 -0
- /package/dist/{chunk-2Q7JYGCK.js.map → chunk-KY6OHQZW.js.map} +0 -0
- /package/dist/{status-USHUUHK6.js.map → doctor-27VDFNP7.js.map} +0 -0
|
@@ -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
|