olakai-cli 0.6.6 → 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-63UDJX5M.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-GU4HEL24.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 +313 -2396
- 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-GU4HEL24.js.map +0 -1
- /package/dist/{api-63UDJX5M.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
package/dist/index.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TOOL_IDS,
|
|
4
|
+
formatRegistryTable,
|
|
5
|
+
getCodexConfigPath,
|
|
6
|
+
getCodexHomeDir,
|
|
7
|
+
getPlugin,
|
|
8
|
+
getToolSource,
|
|
9
|
+
isInteractive,
|
|
10
|
+
isToolId,
|
|
11
|
+
listPlugins,
|
|
12
|
+
promptUser,
|
|
13
|
+
readRegistry,
|
|
14
|
+
reconcileCurrentWorkspace,
|
|
15
|
+
removeEntry,
|
|
16
|
+
runMonitorInstall
|
|
17
|
+
} from "./chunk-75YQWZ4Q.js";
|
|
2
18
|
import {
|
|
3
19
|
createAgent,
|
|
4
20
|
createCustomDataConfig,
|
|
@@ -24,40 +40,24 @@ import {
|
|
|
24
40
|
listSessions,
|
|
25
41
|
listWorkflows,
|
|
26
42
|
pollForToken,
|
|
27
|
-
regenerateAgentApiKey,
|
|
28
43
|
requestDeviceCode,
|
|
29
44
|
updateAgent,
|
|
30
45
|
updateCustomDataConfig,
|
|
31
46
|
updateKpi,
|
|
32
47
|
updateWorkflow,
|
|
33
48
|
validateKpiFormula
|
|
34
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-KNGRF4XU.js";
|
|
35
50
|
import {
|
|
36
|
-
CLAUDE_DIR,
|
|
37
|
-
OLAKAI_DIR,
|
|
38
|
-
OLAKAI_HOOK_MARKER,
|
|
39
|
-
SETTINGS_FILE,
|
|
40
|
-
deleteClaudeCodeConfig,
|
|
41
51
|
findConfiguredWorkspace,
|
|
42
|
-
getClaudeCodeConfigPath,
|
|
43
|
-
getClaudeCodeStatus,
|
|
44
|
-
getClaudeDir,
|
|
45
52
|
getLegacyClaudeMonitorConfigPath,
|
|
46
|
-
getMonitorConfigPath
|
|
47
|
-
|
|
48
|
-
loadClaudeCodeConfig,
|
|
49
|
-
mergeHooksSettings,
|
|
50
|
-
readJsonFile,
|
|
51
|
-
writeClaudeCodeConfig,
|
|
52
|
-
writeJsonFile
|
|
53
|
-
} from "./chunk-2Q7JYGCK.js";
|
|
53
|
+
getMonitorConfigPath
|
|
54
|
+
} from "./chunk-KY6OHQZW.js";
|
|
54
55
|
import {
|
|
55
56
|
HOSTS,
|
|
56
57
|
clearToken,
|
|
57
58
|
getBaseUrl,
|
|
58
59
|
getEnvironment,
|
|
59
60
|
getValidEnvironments,
|
|
60
|
-
getValidToken,
|
|
61
61
|
isTokenValid,
|
|
62
62
|
isValidEnvironment,
|
|
63
63
|
loadToken,
|
|
@@ -292,2362 +292,117 @@ async function postHandshakeJson(url, body, options = {}) {
|
|
|
292
292
|
data = await response.json();
|
|
293
293
|
} catch (err) {
|
|
294
294
|
return {
|
|
295
|
-
kind: "error",
|
|
296
|
-
code: "unknown_error",
|
|
297
|
-
message: err instanceof Error ? err.message : "Failed to parse response",
|
|
298
|
-
status: response.status
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
return { kind: "ok", data };
|
|
302
|
-
}
|
|
303
|
-
let errBody = {};
|
|
304
|
-
try {
|
|
305
|
-
errBody = await response.json();
|
|
306
|
-
} catch {
|
|
307
|
-
}
|
|
308
|
-
const code = mapErrorCode(response.status, errBody);
|
|
309
|
-
let fallbackPath = "";
|
|
310
|
-
try {
|
|
311
|
-
fallbackPath = new URL(url).pathname;
|
|
312
|
-
} catch {
|
|
313
|
-
fallbackPath = url;
|
|
314
|
-
}
|
|
315
|
-
const message = errBody.message || errBody.error || `Request to ${fallbackPath} failed with status ${response.status}`;
|
|
316
|
-
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
317
|
-
const envelope = {
|
|
318
|
-
kind: "error",
|
|
319
|
-
code,
|
|
320
|
-
message,
|
|
321
|
-
status: response.status,
|
|
322
|
-
...retryAfter !== void 0 ? { retryAfter } : {}
|
|
323
|
-
};
|
|
324
|
-
if (options.captureDetail && typeof errBody.attemptsRemaining === "number") {
|
|
325
|
-
envelope.detail = { attemptsRemaining: errBody.attemptsRemaining };
|
|
326
|
-
}
|
|
327
|
-
return envelope;
|
|
328
|
-
}
|
|
329
|
-
function mapErrorCode(status, body) {
|
|
330
|
-
const known = [
|
|
331
|
-
"validation_error",
|
|
332
|
-
"invalid_code",
|
|
333
|
-
"invalid_consent",
|
|
334
|
-
"user_unavailable",
|
|
335
|
-
"blocked",
|
|
336
|
-
"no_code",
|
|
337
|
-
"expired",
|
|
338
|
-
"locked",
|
|
339
|
-
"rate_limited",
|
|
340
|
-
"service_unavailable"
|
|
341
|
-
];
|
|
342
|
-
if (typeof body.code === "string" && known.includes(body.code)) {
|
|
343
|
-
return body.code;
|
|
344
|
-
}
|
|
345
|
-
if (typeof body.error === "string" && known.includes(body.error)) {
|
|
346
|
-
return body.error;
|
|
347
|
-
}
|
|
348
|
-
switch (status) {
|
|
349
|
-
case 400:
|
|
350
|
-
return "validation_error";
|
|
351
|
-
case 401:
|
|
352
|
-
return "invalid_consent";
|
|
353
|
-
case 403:
|
|
354
|
-
return "blocked";
|
|
355
|
-
case 404:
|
|
356
|
-
return "no_code";
|
|
357
|
-
case 410:
|
|
358
|
-
return "expired";
|
|
359
|
-
case 429:
|
|
360
|
-
return "rate_limited";
|
|
361
|
-
case 503:
|
|
362
|
-
return "service_unavailable";
|
|
363
|
-
default:
|
|
364
|
-
return "unknown_error";
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
function buildNetworkError(err) {
|
|
368
|
-
const base = err instanceof Error ? err.message : "Network request failed";
|
|
369
|
-
let causeCode;
|
|
370
|
-
let causeMessage;
|
|
371
|
-
if (err instanceof Error && err.cause && typeof err.cause === "object") {
|
|
372
|
-
const c = err.cause;
|
|
373
|
-
if (typeof c.code === "string" && c.code.length > 0) {
|
|
374
|
-
causeCode = c.code;
|
|
375
|
-
}
|
|
376
|
-
if (typeof c.message === "string" && c.message.length > 0) {
|
|
377
|
-
causeMessage = c.message;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
const message = causeMessage && causeMessage !== base ? `${base}: ${causeMessage}` : base;
|
|
381
|
-
return {
|
|
382
|
-
kind: "error",
|
|
383
|
-
code: "network_error",
|
|
384
|
-
message,
|
|
385
|
-
status: 0,
|
|
386
|
-
...causeCode ? { causeCode } : {}
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function parseRetryAfter(header) {
|
|
390
|
-
if (!header) return void 0;
|
|
391
|
-
const asNumber2 = Number(header);
|
|
392
|
-
if (Number.isFinite(asNumber2) && asNumber2 > 0) {
|
|
393
|
-
return Math.floor(asNumber2);
|
|
394
|
-
}
|
|
395
|
-
const asDate = Date.parse(header);
|
|
396
|
-
if (Number.isFinite(asDate)) {
|
|
397
|
-
const seconds = Math.floor((asDate - Date.now()) / 1e3);
|
|
398
|
-
return seconds > 0 ? seconds : void 0;
|
|
399
|
-
}
|
|
400
|
-
return void 0;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/monitor/prompt.ts
|
|
404
|
-
import * as readline from "readline";
|
|
405
|
-
function promptUser(question) {
|
|
406
|
-
const rl = readline.createInterface({
|
|
407
|
-
input: process.stdin,
|
|
408
|
-
output: process.stdout
|
|
409
|
-
});
|
|
410
|
-
return new Promise((resolve) => {
|
|
411
|
-
rl.question(question, (answer) => {
|
|
412
|
-
rl.close();
|
|
413
|
-
resolve(answer.trim());
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
function isInteractive() {
|
|
418
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// src/monitor/detect-all.ts
|
|
422
|
-
import * as fs15 from "fs";
|
|
423
|
-
import * as path14 from "path";
|
|
424
|
-
|
|
425
|
-
// src/monitor/plugins/codex/paths.ts
|
|
426
|
-
import * as os from "os";
|
|
427
|
-
import * as path from "path";
|
|
428
|
-
var CODEX_HOME_DIRNAME = ".codex";
|
|
429
|
-
var CODEX_CONFIG_FILENAME = "config.toml";
|
|
430
|
-
var CODEX_SESSIONS_DIRNAME = "sessions";
|
|
431
|
-
function getCodexHomeDir() {
|
|
432
|
-
return path.join(os.homedir(), CODEX_HOME_DIRNAME);
|
|
433
|
-
}
|
|
434
|
-
function getCodexConfigPath() {
|
|
435
|
-
return path.join(getCodexHomeDir(), CODEX_CONFIG_FILENAME);
|
|
436
|
-
}
|
|
437
|
-
function getCodexSessionsDir() {
|
|
438
|
-
return path.join(getCodexHomeDir(), CODEX_SESSIONS_DIRNAME);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// src/monitor/plugins/claude-code/index.ts
|
|
442
|
-
import * as fs4 from "fs";
|
|
443
|
-
import { spawnSync } from "child_process";
|
|
444
|
-
|
|
445
|
-
// src/monitor/plugin.ts
|
|
446
|
-
var TOOL_IDS = [
|
|
447
|
-
"claude-code",
|
|
448
|
-
"codex",
|
|
449
|
-
"cursor"
|
|
450
|
-
];
|
|
451
|
-
var registry = /* @__PURE__ */ new Map();
|
|
452
|
-
function registerPlugin(plugin) {
|
|
453
|
-
registry.set(plugin.id, plugin);
|
|
454
|
-
}
|
|
455
|
-
function getPlugin(id) {
|
|
456
|
-
if (!isToolId(id)) {
|
|
457
|
-
throw new Error(
|
|
458
|
-
`Unknown tool: "${id}". Supported tools: ${TOOL_IDS.join(", ")}`
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
const plugin = registry.get(id);
|
|
462
|
-
if (!plugin) {
|
|
463
|
-
throw new Error(
|
|
464
|
-
`Tool "${id}" is not registered. This is a CLI bug \u2014 please report it.`
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
return plugin;
|
|
468
|
-
}
|
|
469
|
-
function listPlugins() {
|
|
470
|
-
return Array.from(registry.values());
|
|
471
|
-
}
|
|
472
|
-
function isToolId(value) {
|
|
473
|
-
return TOOL_IDS.includes(value);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// src/commands/monitor-state.ts
|
|
477
|
-
import * as fs from "fs";
|
|
478
|
-
import * as os2 from "os";
|
|
479
|
-
import * as path2 from "path";
|
|
480
|
-
var STATE_DIR_SEGMENTS = [".olakai", "monitor-state"];
|
|
481
|
-
function getStateDir(homeDir) {
|
|
482
|
-
return path2.join(homeDir, ...STATE_DIR_SEGMENTS);
|
|
483
|
-
}
|
|
484
|
-
function getStateFile(sessionId, homeDir) {
|
|
485
|
-
return path2.join(getStateDir(homeDir), `${sessionId}.json`);
|
|
486
|
-
}
|
|
487
|
-
var debugLogger = null;
|
|
488
|
-
function setDebugLogger(logger) {
|
|
489
|
-
debugLogger = logger;
|
|
490
|
-
}
|
|
491
|
-
function log(label, data) {
|
|
492
|
-
if (debugLogger) {
|
|
493
|
-
try {
|
|
494
|
-
debugLogger(label, data);
|
|
495
|
-
} catch {
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
function loadSessionState(sessionId, homeDir = os2.homedir()) {
|
|
500
|
-
if (!sessionId) return null;
|
|
501
|
-
const filePath = getStateFile(sessionId, homeDir);
|
|
502
|
-
try {
|
|
503
|
-
if (!fs.existsSync(filePath)) return null;
|
|
504
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
505
|
-
const parsed = JSON.parse(raw);
|
|
506
|
-
if (typeof parsed?.lastUserTimestamp !== "string" || typeof parsed?.lastReportedAt !== "string" || typeof parsed?.numTurnsAtLastReport !== "number") {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
return {
|
|
510
|
-
lastUserTimestamp: parsed.lastUserTimestamp,
|
|
511
|
-
lastReportedAt: parsed.lastReportedAt,
|
|
512
|
-
numTurnsAtLastReport: parsed.numTurnsAtLastReport
|
|
513
|
-
};
|
|
514
|
-
} catch (err) {
|
|
515
|
-
log("state-load-failed", {
|
|
516
|
-
sessionId,
|
|
517
|
-
error: err.message
|
|
518
|
-
});
|
|
519
|
-
return null;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function saveSessionState(sessionId, state, homeDir = os2.homedir()) {
|
|
523
|
-
if (!sessionId) return;
|
|
524
|
-
const dir = getStateDir(homeDir);
|
|
525
|
-
const filePath = getStateFile(sessionId, homeDir);
|
|
526
|
-
try {
|
|
527
|
-
if (!fs.existsSync(dir)) {
|
|
528
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
529
|
-
}
|
|
530
|
-
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
531
|
-
} catch (err) {
|
|
532
|
-
log("state-save-failed", {
|
|
533
|
-
sessionId,
|
|
534
|
-
error: err.message
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
function shouldReportTurn(existing, currentUserTimestamp) {
|
|
539
|
-
if (!currentUserTimestamp) return true;
|
|
540
|
-
if (!existing) return true;
|
|
541
|
-
return existing.lastUserTimestamp !== currentUserTimestamp;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// src/monitor/plugins/claude-code/install.ts
|
|
545
|
-
import * as fs2 from "fs";
|
|
546
|
-
|
|
547
|
-
// src/monitor/self-monitor-provision.ts
|
|
548
|
-
import path3 from "path";
|
|
549
|
-
async function provisionSelfMonitorAgent(opts) {
|
|
550
|
-
const me = await getCurrentUser();
|
|
551
|
-
const localPart = (me.email.split("@")[0] ?? "user").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
552
|
-
const workspaceName = path3.basename(opts.projectRoot).toLowerCase();
|
|
553
|
-
const defaultName = `${workspaceName}-${localPart}`;
|
|
554
|
-
let existing = [];
|
|
555
|
-
try {
|
|
556
|
-
existing = await listMyAgents({
|
|
557
|
-
source: opts.source,
|
|
558
|
-
name: defaultName
|
|
559
|
-
});
|
|
560
|
-
} catch (err) {
|
|
561
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
562
|
-
if (!message.includes("Failed to list your agents")) {
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
if (existing.length > 0) {
|
|
566
|
-
const found = existing[0];
|
|
567
|
-
console.log(
|
|
568
|
-
`
|
|
569
|
-
Found your existing ${opts.displayName} agent "${found.name}".`
|
|
570
|
-
);
|
|
571
|
-
console.log(
|
|
572
|
-
"Re-using it requires rotating its API key. Any other workspace currently using this agent will start failing on the next monitor request until it re-runs 'olakai monitor init'."
|
|
573
|
-
);
|
|
574
|
-
const ack = await promptUser("Rotate the API key and reuse? [y/N]: ");
|
|
575
|
-
if (ack.trim().toLowerCase() !== "y") {
|
|
576
|
-
console.error(
|
|
577
|
-
"Cancelled. To use this workspace without affecting the other workspace, run 'olakai monitor init' again and pick a different agent name when prompted."
|
|
578
|
-
);
|
|
579
|
-
process.exit(1);
|
|
580
|
-
}
|
|
581
|
-
const rotated = await regenerateAgentApiKey(found.id);
|
|
582
|
-
return {
|
|
583
|
-
id: found.id,
|
|
584
|
-
name: found.name,
|
|
585
|
-
description: found.description,
|
|
586
|
-
role: found.role,
|
|
587
|
-
// `source` on the listMyAgents response is the AgentSource enum
|
|
588
|
-
// string; the Agent type narrows it more loosely. Cast through
|
|
589
|
-
// the shared union.
|
|
590
|
-
source: found.source,
|
|
591
|
-
apiKey: {
|
|
592
|
-
id: rotated.id,
|
|
593
|
-
key: rotated.key,
|
|
594
|
-
keyMasked: rotated.keyMasked,
|
|
595
|
-
isActive: rotated.isActive
|
|
596
|
-
},
|
|
597
|
-
workflowId: null,
|
|
598
|
-
category: opts.category
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
const nameInput = await promptUser(`Agent name [${defaultName}]: `);
|
|
602
|
-
const agentName = nameInput.trim() || defaultName;
|
|
603
|
-
try {
|
|
604
|
-
return await createAgent({
|
|
605
|
-
name: agentName,
|
|
606
|
-
description: `${opts.displayName} local agent for ${agentName}`,
|
|
607
|
-
role: "WORKER",
|
|
608
|
-
createApiKey: true,
|
|
609
|
-
source: opts.source
|
|
610
|
-
});
|
|
611
|
-
} catch (err) {
|
|
612
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
613
|
-
if (message.toLowerCase().includes("already exists") || message.toLowerCase().includes("conflict")) {
|
|
614
|
-
console.log(
|
|
615
|
-
`
|
|
616
|
-
An agent named "${agentName}" already exists on this account (created by someone else or an admin).`
|
|
617
|
-
);
|
|
618
|
-
const retry = await promptUser("Try a different name: ");
|
|
619
|
-
if (!retry.trim()) {
|
|
620
|
-
console.error("No name provided. Aborting.");
|
|
621
|
-
process.exit(1);
|
|
622
|
-
}
|
|
623
|
-
return createAgent({
|
|
624
|
-
name: retry.trim(),
|
|
625
|
-
description: `${opts.displayName} local agent for ${retry.trim()}`,
|
|
626
|
-
role: "WORKER",
|
|
627
|
-
createApiKey: true,
|
|
628
|
-
source: opts.source
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
throw err;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// src/monitor/plugins/claude-code/install.ts
|
|
636
|
-
import path4 from "path";
|
|
637
|
-
var CLAUDE_CODE_SOURCE = "claude-code";
|
|
638
|
-
var CLAUDE_CODE_AGENT_SOURCE = "CLAUDE_CODE";
|
|
639
|
-
var CLAUDE_CODE_AGENT_CATEGORY = "CODING";
|
|
640
|
-
async function installClaudeCode(opts) {
|
|
641
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
642
|
-
const token = getValidToken();
|
|
643
|
-
if (!token) {
|
|
644
|
-
console.error("Not logged in. Run 'olakai login' first.");
|
|
645
|
-
process.exit(1);
|
|
646
|
-
}
|
|
647
|
-
console.log("Setting up Claude Code monitoring for this workspace...\n");
|
|
648
|
-
const agent = await provisionSelfMonitorAgent({
|
|
649
|
-
projectRoot,
|
|
650
|
-
source: CLAUDE_CODE_AGENT_SOURCE,
|
|
651
|
-
displayName: "Claude Code",
|
|
652
|
-
category: CLAUDE_CODE_AGENT_CATEGORY
|
|
653
|
-
});
|
|
654
|
-
const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
|
|
655
|
-
const apiKey = agent.apiKey?.key;
|
|
656
|
-
if (!apiKey) {
|
|
657
|
-
console.error(
|
|
658
|
-
"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'."
|
|
659
|
-
);
|
|
660
|
-
process.exit(1);
|
|
661
|
-
}
|
|
662
|
-
const claudeDir = getClaudeDir(projectRoot);
|
|
663
|
-
if (!fs2.existsSync(claudeDir)) {
|
|
664
|
-
fs2.mkdirSync(claudeDir, { recursive: true });
|
|
665
|
-
}
|
|
666
|
-
const settingsPath = getSettingsPath(projectRoot);
|
|
667
|
-
const existingSettings = readJsonFile(settingsPath) ?? {};
|
|
668
|
-
const mergedHooks = mergeHooksSettings(existingSettings.hooks);
|
|
669
|
-
const updatedSettings = {
|
|
670
|
-
...existingSettings,
|
|
671
|
-
hooks: mergedHooks
|
|
672
|
-
};
|
|
673
|
-
writeJsonFile(settingsPath, updatedSettings);
|
|
674
|
-
const monitorConfig = {
|
|
675
|
-
agentId: agent.id,
|
|
676
|
-
apiKey,
|
|
677
|
-
agentName: agent.name,
|
|
678
|
-
source: CLAUDE_CODE_SOURCE,
|
|
679
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
680
|
-
monitoringEndpoint
|
|
681
|
-
};
|
|
682
|
-
writeClaudeCodeConfig(projectRoot, monitorConfig);
|
|
683
|
-
const configPath = getClaudeCodeConfigPath(projectRoot);
|
|
684
|
-
const configRel = path4.relative(projectRoot, configPath);
|
|
685
|
-
console.log("");
|
|
686
|
-
console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
|
|
687
|
-
if (agent.apiKey?.key) {
|
|
688
|
-
console.log("\u2713 API key generated");
|
|
689
|
-
}
|
|
690
|
-
console.log(
|
|
691
|
-
`\u2713 Claude Code hooks configured in ${CLAUDE_DIR}/${SETTINGS_FILE}`
|
|
692
|
-
);
|
|
693
|
-
console.log(`\u2713 Monitor config saved to ${configRel}`);
|
|
694
|
-
console.log("");
|
|
695
|
-
console.log(
|
|
696
|
-
"Monitoring is now active. Claude Code will report activity to Olakai"
|
|
697
|
-
);
|
|
698
|
-
console.log(`on each turn. View activity at: ${getBaseUrl()}/dashboard`);
|
|
699
|
-
console.log("");
|
|
700
|
-
console.log(
|
|
701
|
-
`\u26A0 Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key)`
|
|
702
|
-
);
|
|
703
|
-
console.log("");
|
|
704
|
-
console.log("To check status: olakai monitor status --tool claude-code");
|
|
705
|
-
console.log("To disable: olakai monitor disable --tool claude-code");
|
|
706
|
-
return {
|
|
707
|
-
agentId: agent.id,
|
|
708
|
-
agentName: agent.name,
|
|
709
|
-
source: CLAUDE_CODE_SOURCE,
|
|
710
|
-
monitoringEndpoint
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
async function uninstallClaudeCode(opts) {
|
|
714
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
715
|
-
const settingsPath = getSettingsPath(projectRoot);
|
|
716
|
-
const settings = readJsonFile(settingsPath);
|
|
717
|
-
if (settings?.hooks) {
|
|
718
|
-
const cleanedHooks = {};
|
|
719
|
-
for (const [event, entries] of Object.entries(settings.hooks)) {
|
|
720
|
-
const filtered = entries.filter(
|
|
721
|
-
(e) => !e.hooks.some((h) => h.command.includes(OLAKAI_HOOK_MARKER))
|
|
722
|
-
);
|
|
723
|
-
if (filtered.length > 0) {
|
|
724
|
-
cleanedHooks[event] = filtered;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (Object.keys(cleanedHooks).length > 0) {
|
|
728
|
-
settings.hooks = cleanedHooks;
|
|
729
|
-
} else {
|
|
730
|
-
delete settings.hooks;
|
|
731
|
-
}
|
|
732
|
-
writeJsonFile(settingsPath, settings);
|
|
733
|
-
console.log(
|
|
734
|
-
`\u2713 Olakai hooks removed from ${CLAUDE_DIR}/${SETTINGS_FILE}`
|
|
735
|
-
);
|
|
736
|
-
} else {
|
|
737
|
-
console.log("No hooks found in settings.json.");
|
|
738
|
-
}
|
|
739
|
-
if (!opts.keepConfig) {
|
|
740
|
-
const configPath = getClaudeCodeConfigPath(projectRoot);
|
|
741
|
-
const configRel = path4.relative(projectRoot, configPath);
|
|
742
|
-
if (deleteClaudeCodeConfig(projectRoot)) {
|
|
743
|
-
console.log(`\u2713 Monitor config removed (${configRel})`);
|
|
744
|
-
}
|
|
745
|
-
} else {
|
|
746
|
-
const configPath = getClaudeCodeConfigPath(projectRoot);
|
|
747
|
-
const configRel = path4.relative(projectRoot, configPath);
|
|
748
|
-
console.log(`Monitor config retained at ${configRel}`);
|
|
749
|
-
}
|
|
750
|
-
console.log("");
|
|
751
|
-
console.log(
|
|
752
|
-
"Monitoring disabled. Run 'olakai monitor init --tool claude-code' to re-enable."
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// src/monitor/plugins/claude-code/hook.ts
|
|
757
|
-
import * as fs3 from "fs";
|
|
758
|
-
|
|
759
|
-
// src/commands/monitor-transcript.ts
|
|
760
|
-
var FILE_EDITING_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
761
|
-
"Edit",
|
|
762
|
-
"Write",
|
|
763
|
-
"MultiEdit"
|
|
764
|
-
]);
|
|
765
|
-
var BASH_TOOL_NAME = "Bash";
|
|
766
|
-
var SKILL_REGEX = /^\/([\w-]+)(?:\s|$)/;
|
|
767
|
-
function detectSkill(userMessage) {
|
|
768
|
-
if (typeof userMessage !== "string") return void 0;
|
|
769
|
-
const trimmed = userMessage.trimStart();
|
|
770
|
-
if (!trimmed) return void 0;
|
|
771
|
-
const match = SKILL_REGEX.exec(trimmed);
|
|
772
|
-
if (!match) return void 0;
|
|
773
|
-
return match[1];
|
|
774
|
-
}
|
|
775
|
-
function extractTextContent(content) {
|
|
776
|
-
if (typeof content === "string") return content;
|
|
777
|
-
if (!Array.isArray(content)) return "";
|
|
778
|
-
const parts = [];
|
|
779
|
-
for (const block of content) {
|
|
780
|
-
if (block?.type === "text" && typeof block.text === "string") {
|
|
781
|
-
parts.push(block.text);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return parts.join("\n").trim();
|
|
785
|
-
}
|
|
786
|
-
function isMetaUserMessage(line, text) {
|
|
787
|
-
if (line.isMeta === true) return true;
|
|
788
|
-
if (!text) return true;
|
|
789
|
-
return text.includes("<command-name>") || text.includes("<local-command-caveat>") || text.includes("<command-message>");
|
|
790
|
-
}
|
|
791
|
-
function parseTimestamp(ts) {
|
|
792
|
-
if (typeof ts !== "string" || !ts) return NaN;
|
|
793
|
-
return Date.parse(ts);
|
|
794
|
-
}
|
|
795
|
-
function isCompactionEntry(line) {
|
|
796
|
-
if (line.type !== "system") return false;
|
|
797
|
-
const subtype = line.subtype ?? "";
|
|
798
|
-
return subtype === "compact_boundary" || subtype === "pre_compact" || subtype === "post_compact" || /compact/i.test(subtype);
|
|
799
|
-
}
|
|
800
|
-
function parseTranscript(raw) {
|
|
801
|
-
const empty = {
|
|
802
|
-
prompt: "",
|
|
803
|
-
response: "",
|
|
804
|
-
tokens: 0,
|
|
805
|
-
inputTokens: 0,
|
|
806
|
-
outputTokens: 0,
|
|
807
|
-
modelName: null,
|
|
808
|
-
numTurns: 0,
|
|
809
|
-
toolCallCount: 0,
|
|
810
|
-
filesEditedCount: 0,
|
|
811
|
-
bashCommandCount: 0
|
|
812
|
-
};
|
|
813
|
-
if (!raw) return empty;
|
|
814
|
-
const lines = raw.split("\n");
|
|
815
|
-
let lastUserText = "";
|
|
816
|
-
let lastUserTimestamp = NaN;
|
|
817
|
-
let lastUserTimestampRaw;
|
|
818
|
-
let lastAssistantText = "";
|
|
819
|
-
let lastAssistantTimestamp = NaN;
|
|
820
|
-
let lastAssistantModel = null;
|
|
821
|
-
let lastAssistantInputTokens = 0;
|
|
822
|
-
let lastAssistantOutputTokens = 0;
|
|
823
|
-
let numTurns = 0;
|
|
824
|
-
let currentTurnUserTimestamp = NaN;
|
|
825
|
-
let toolCallCount = 0;
|
|
826
|
-
let bashCommandCount = 0;
|
|
827
|
-
let editedFilePaths = /* @__PURE__ */ new Set();
|
|
828
|
-
for (const rawLine of lines) {
|
|
829
|
-
if (!rawLine) continue;
|
|
830
|
-
let parsed;
|
|
831
|
-
try {
|
|
832
|
-
parsed = JSON.parse(rawLine);
|
|
833
|
-
} catch {
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
|
-
if (isCompactionEntry(parsed)) continue;
|
|
837
|
-
if (parsed.isSidechain === true) continue;
|
|
838
|
-
if (parsed.type === "user" && parsed.message) {
|
|
839
|
-
const text = extractTextContent(parsed.message.content);
|
|
840
|
-
if (isMetaUserMessage(parsed, text)) continue;
|
|
841
|
-
lastUserText = text;
|
|
842
|
-
lastUserTimestamp = parseTimestamp(parsed.timestamp);
|
|
843
|
-
currentTurnUserTimestamp = lastUserTimestamp;
|
|
844
|
-
toolCallCount = 0;
|
|
845
|
-
bashCommandCount = 0;
|
|
846
|
-
editedFilePaths = /* @__PURE__ */ new Set();
|
|
847
|
-
lastUserTimestampRaw = typeof parsed.timestamp === "string" && parsed.timestamp ? parsed.timestamp : void 0;
|
|
848
|
-
} else if (parsed.type === "assistant" && parsed.message) {
|
|
849
|
-
const text = extractTextContent(parsed.message.content);
|
|
850
|
-
numTurns += 1;
|
|
851
|
-
if (text) lastAssistantText = text;
|
|
852
|
-
if (typeof parsed.message.model === "string") {
|
|
853
|
-
lastAssistantModel = parsed.message.model;
|
|
854
|
-
}
|
|
855
|
-
const content = parsed.message.content;
|
|
856
|
-
if (Array.isArray(content)) {
|
|
857
|
-
for (const block of content) {
|
|
858
|
-
try {
|
|
859
|
-
if (!block || block.type !== "tool_use") continue;
|
|
860
|
-
toolCallCount += 1;
|
|
861
|
-
const name = typeof block.name === "string" ? block.name : "";
|
|
862
|
-
if (name === BASH_TOOL_NAME) {
|
|
863
|
-
bashCommandCount += 1;
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
if (FILE_EDITING_TOOL_NAMES.has(name)) {
|
|
867
|
-
const input = block.input;
|
|
868
|
-
if (input !== null && typeof input === "object" && "file_path" in input) {
|
|
869
|
-
const filePath = input.file_path;
|
|
870
|
-
if (typeof filePath === "string" && filePath) {
|
|
871
|
-
editedFilePaths.add(filePath);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
} catch {
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
const usage = parsed.message.usage;
|
|
880
|
-
if (usage) {
|
|
881
|
-
const input = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
|
|
882
|
-
const output = usage.output_tokens ?? 0;
|
|
883
|
-
lastAssistantInputTokens = input;
|
|
884
|
-
lastAssistantOutputTokens = output;
|
|
885
|
-
}
|
|
886
|
-
const ts = parseTimestamp(parsed.timestamp);
|
|
887
|
-
if (!Number.isNaN(ts)) {
|
|
888
|
-
lastAssistantTimestamp = ts;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
const result = {
|
|
893
|
-
prompt: lastUserText,
|
|
894
|
-
response: lastAssistantText,
|
|
895
|
-
tokens: lastAssistantInputTokens + lastAssistantOutputTokens,
|
|
896
|
-
inputTokens: lastAssistantInputTokens,
|
|
897
|
-
outputTokens: lastAssistantOutputTokens,
|
|
898
|
-
modelName: lastAssistantModel,
|
|
899
|
-
numTurns,
|
|
900
|
-
toolCallCount,
|
|
901
|
-
filesEditedCount: editedFilePaths.size,
|
|
902
|
-
bashCommandCount
|
|
903
|
-
};
|
|
904
|
-
if (!Number.isNaN(currentTurnUserTimestamp) && !Number.isNaN(lastAssistantTimestamp) && lastAssistantTimestamp >= currentTurnUserTimestamp) {
|
|
905
|
-
result.latencyMs = Math.round(
|
|
906
|
-
lastAssistantTimestamp - currentTurnUserTimestamp
|
|
907
|
-
);
|
|
908
|
-
}
|
|
909
|
-
const skill = detectSkill(lastUserText);
|
|
910
|
-
if (skill) {
|
|
911
|
-
result.skill = skill;
|
|
912
|
-
}
|
|
913
|
-
if (lastUserTimestampRaw) {
|
|
914
|
-
result.userTurnTimestamp = lastUserTimestampRaw;
|
|
915
|
-
}
|
|
916
|
-
return result;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// src/monitor/plugins/claude-code/hook.ts
|
|
920
|
-
var noopDebug = () => {
|
|
921
|
-
};
|
|
922
|
-
function extractFromTranscript(transcriptPath, debugLog4 = noopDebug) {
|
|
923
|
-
const empty = {
|
|
924
|
-
prompt: "",
|
|
925
|
-
response: "",
|
|
926
|
-
tokens: 0,
|
|
927
|
-
inputTokens: 0,
|
|
928
|
-
outputTokens: 0,
|
|
929
|
-
modelName: null,
|
|
930
|
-
numTurns: 0,
|
|
931
|
-
toolCallCount: 0,
|
|
932
|
-
filesEditedCount: 0,
|
|
933
|
-
bashCommandCount: 0
|
|
934
|
-
};
|
|
935
|
-
if (!transcriptPath) return empty;
|
|
936
|
-
let raw;
|
|
937
|
-
try {
|
|
938
|
-
raw = fs3.readFileSync(transcriptPath, "utf-8");
|
|
939
|
-
} catch (err) {
|
|
940
|
-
debugLog4("transcript-read-failed", {
|
|
941
|
-
transcriptPath,
|
|
942
|
-
error: err.message
|
|
943
|
-
});
|
|
944
|
-
return empty;
|
|
945
|
-
}
|
|
946
|
-
return parseTranscript(raw);
|
|
947
|
-
}
|
|
948
|
-
function extractSubagentName(event) {
|
|
949
|
-
const candidates = [
|
|
950
|
-
event.agent_name,
|
|
951
|
-
event.subagent_type,
|
|
952
|
-
event.agent_type,
|
|
953
|
-
event.tool_input?.subagent_type
|
|
954
|
-
];
|
|
955
|
-
for (const value of candidates) {
|
|
956
|
-
if (typeof value === "string" && value.trim()) {
|
|
957
|
-
return value.trim();
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
return void 0;
|
|
961
|
-
}
|
|
962
|
-
function buildClaudeCodePayload(event, eventData, config) {
|
|
963
|
-
const sessionId = eventData.session_id ?? `claude-code-${Date.now()}`;
|
|
964
|
-
switch (event) {
|
|
965
|
-
case "stop":
|
|
966
|
-
case "subagent-stop": {
|
|
967
|
-
const extracted = extractFromTranscript(eventData.transcript_path);
|
|
968
|
-
const isSubagent = event === "subagent-stop";
|
|
969
|
-
const customData = {
|
|
970
|
-
hookEvent: eventData.hook_event_name ?? (isSubagent ? "SubagentStop" : "Stop"),
|
|
971
|
-
sessionId,
|
|
972
|
-
transcriptPath: eventData.transcript_path ?? "",
|
|
973
|
-
cwd: eventData.cwd ?? "",
|
|
974
|
-
stopHookActive: eventData.stop_hook_active ?? false,
|
|
975
|
-
inputTokens: extracted.inputTokens,
|
|
976
|
-
outputTokens: extracted.outputTokens,
|
|
977
|
-
numTurns: extracted.numTurns,
|
|
978
|
-
// Per-turn work signals for the Claude Code classifier (D-027).
|
|
979
|
-
// Always emitted as JSON numbers — the backend classifier and
|
|
980
|
-
// KPI formulas must see numeric 0, not missing keys or strings.
|
|
981
|
-
toolCallCount: extracted.toolCallCount,
|
|
982
|
-
filesEditedCount: extracted.filesEditedCount,
|
|
983
|
-
bashCommandCount: extracted.bashCommandCount
|
|
984
|
-
};
|
|
985
|
-
if (typeof extracted.latencyMs === "number") {
|
|
986
|
-
customData.latencyMs = extracted.latencyMs;
|
|
987
|
-
}
|
|
988
|
-
if (isSubagent) {
|
|
989
|
-
const subagent = extractSubagentName(eventData);
|
|
990
|
-
if (subagent) {
|
|
991
|
-
customData.subagent = subagent;
|
|
992
|
-
}
|
|
993
|
-
} else if (extracted.skill) {
|
|
994
|
-
customData.skill = extracted.skill;
|
|
995
|
-
}
|
|
996
|
-
const payloadAssistant = eventData.last_assistant_message;
|
|
997
|
-
const response = typeof payloadAssistant === "string" && payloadAssistant.trim() ? payloadAssistant : extracted.response;
|
|
998
|
-
return {
|
|
999
|
-
prompt: extracted.prompt,
|
|
1000
|
-
response,
|
|
1001
|
-
chatId: sessionId,
|
|
1002
|
-
source: config.source,
|
|
1003
|
-
modelName: extracted.modelName ?? void 0,
|
|
1004
|
-
tokens: extracted.tokens,
|
|
1005
|
-
customData
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
default:
|
|
1009
|
-
return null;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// src/monitor/plugins/claude-code/index.ts
|
|
1014
|
-
var TOOL_ID = "claude-code";
|
|
1015
|
-
var SUPPORTED_HOOK_EVENTS = /* @__PURE__ */ new Set(["stop", "subagent-stop"]);
|
|
1016
|
-
function debugLog(label, data) {
|
|
1017
|
-
if (process.env.OLAKAI_MONITOR_DEBUG !== "1") return;
|
|
1018
|
-
try {
|
|
1019
|
-
const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;
|
|
1020
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}
|
|
1021
|
-
`;
|
|
1022
|
-
fs4.appendFileSync(logPath, line, "utf-8");
|
|
1023
|
-
} catch {
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
function resolveProjectRootFromPayload(eventData, fallbackCwd) {
|
|
1027
|
-
const payloadCwd = typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : fallbackCwd;
|
|
1028
|
-
return findConfiguredWorkspace(payloadCwd, [TOOL_ID]);
|
|
1029
|
-
}
|
|
1030
|
-
var claudeCodePlugin = {
|
|
1031
|
-
id: TOOL_ID,
|
|
1032
|
-
displayName: "Claude Code",
|
|
1033
|
-
install(opts) {
|
|
1034
|
-
return installClaudeCode(opts);
|
|
1035
|
-
},
|
|
1036
|
-
uninstall(opts) {
|
|
1037
|
-
return uninstallClaudeCode(opts);
|
|
1038
|
-
},
|
|
1039
|
-
status(opts) {
|
|
1040
|
-
return getClaudeCodeStatus(opts);
|
|
1041
|
-
},
|
|
1042
|
-
async handleHook(eventName, payloadJson) {
|
|
1043
|
-
setDebugLogger(debugLog);
|
|
1044
|
-
const event = eventName.trim();
|
|
1045
|
-
if (!SUPPORTED_HOOK_EVENTS.has(event)) {
|
|
1046
|
-
debugLog("hook-unknown-event", event);
|
|
1047
|
-
return null;
|
|
1048
|
-
}
|
|
1049
|
-
const eventData = payloadJson ?? {};
|
|
1050
|
-
debugLog("event-parsed", { event, eventData });
|
|
1051
|
-
const projectRoot = resolveProjectRootFromPayload(
|
|
1052
|
-
eventData,
|
|
1053
|
-
process.cwd()
|
|
1054
|
-
);
|
|
1055
|
-
if (!projectRoot) {
|
|
1056
|
-
debugLog("config-not-found", {
|
|
1057
|
-
startDir: typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : process.cwd()
|
|
1058
|
-
});
|
|
1059
|
-
return null;
|
|
1060
|
-
}
|
|
1061
|
-
const config = loadClaudeCodeConfig(projectRoot, () => {
|
|
1062
|
-
});
|
|
1063
|
-
if (!config) {
|
|
1064
|
-
debugLog("config-load-failed", { projectRoot });
|
|
1065
|
-
return null;
|
|
1066
|
-
}
|
|
1067
|
-
const payload = buildClaudeCodePayload(event, eventData, config);
|
|
1068
|
-
if (!payload) return null;
|
|
1069
|
-
debugLog("payload-built", payload);
|
|
1070
|
-
const sessionId = typeof eventData.session_id === "string" && eventData.session_id || void 0;
|
|
1071
|
-
const extracted = extractFromTranscript(
|
|
1072
|
-
eventData.transcript_path,
|
|
1073
|
-
debugLog
|
|
1074
|
-
);
|
|
1075
|
-
const userTurnTimestamp = extracted.userTurnTimestamp;
|
|
1076
|
-
if (extracted.prompt.trim() === "" && extracted.response.trim() === "" && extracted.numTurns === 0) {
|
|
1077
|
-
debugLog("empty-parse-skip", {
|
|
1078
|
-
event,
|
|
1079
|
-
sessionId,
|
|
1080
|
-
transcriptPath: eventData.transcript_path
|
|
1081
|
-
});
|
|
1082
|
-
return null;
|
|
1083
|
-
}
|
|
1084
|
-
if (sessionId) {
|
|
1085
|
-
const existingState = loadSessionState(sessionId);
|
|
1086
|
-
if (!shouldReportTurn(existingState, userTurnTimestamp)) {
|
|
1087
|
-
debugLog("turn-dedup-skip", { sessionId, userTurnTimestamp });
|
|
1088
|
-
return null;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
if (sessionId && userTurnTimestamp) {
|
|
1092
|
-
saveSessionState(sessionId, {
|
|
1093
|
-
lastUserTimestamp: userTurnTimestamp,
|
|
1094
|
-
lastReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1095
|
-
numTurnsAtLastReport: extracted.numTurns
|
|
1096
|
-
});
|
|
1097
|
-
debugLog("state-saved", {
|
|
1098
|
-
sessionId,
|
|
1099
|
-
userTurnTimestamp,
|
|
1100
|
-
numTurns: extracted.numTurns
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
return {
|
|
1104
|
-
payload,
|
|
1105
|
-
transport: {
|
|
1106
|
-
endpoint: config.monitoringEndpoint,
|
|
1107
|
-
apiKey: config.apiKey,
|
|
1108
|
-
projectRoot
|
|
1109
|
-
}
|
|
1110
|
-
};
|
|
1111
|
-
},
|
|
1112
|
-
async detectInstalled(opts) {
|
|
1113
|
-
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
1114
|
-
try {
|
|
1115
|
-
if (fs4.existsSync(getMonitorConfigPath(projectRoot, TOOL_ID))) {
|
|
1116
|
-
return true;
|
|
1117
|
-
}
|
|
1118
|
-
if (fs4.existsSync(getLegacyClaudeMonitorConfigPath(projectRoot))) {
|
|
1119
|
-
return true;
|
|
1120
|
-
}
|
|
1121
|
-
} catch {
|
|
1122
|
-
}
|
|
1123
|
-
return detectClaudeBinaryOnPath();
|
|
1124
|
-
}
|
|
1125
|
-
};
|
|
1126
|
-
function detectClaudeBinaryOnPath() {
|
|
1127
|
-
try {
|
|
1128
|
-
const probe = spawnSync(
|
|
1129
|
-
process.platform === "win32" ? "where" : "which",
|
|
1130
|
-
["claude"],
|
|
1131
|
-
{
|
|
1132
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1133
|
-
timeout: 1e3
|
|
1134
|
-
}
|
|
1135
|
-
);
|
|
1136
|
-
if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {
|
|
1137
|
-
return true;
|
|
1138
|
-
}
|
|
1139
|
-
} catch {
|
|
1140
|
-
}
|
|
1141
|
-
return false;
|
|
1142
|
-
}
|
|
1143
|
-
registerPlugin(claudeCodePlugin);
|
|
1144
|
-
|
|
1145
|
-
// src/monitor/plugins/codex/index.ts
|
|
1146
|
-
import * as fs9 from "fs";
|
|
1147
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
1148
|
-
|
|
1149
|
-
// src/monitor/plugins/codex/install.ts
|
|
1150
|
-
import * as fs6 from "fs";
|
|
1151
|
-
import * as path6 from "path";
|
|
1152
|
-
import * as TOML from "@iarna/toml";
|
|
1153
|
-
|
|
1154
|
-
// src/monitor/plugins/codex/hooks.ts
|
|
1155
|
-
var OLAKAI_HOOK_MARKER2 = "olakai monitor hook";
|
|
1156
|
-
var CODEX_HOOK_TIMEOUT_SECONDS = 5;
|
|
1157
|
-
var SUPPORTED_HOOK_EVENT_NAMES = ["Stop"];
|
|
1158
|
-
function buildOlakaiHookGroup(event) {
|
|
1159
|
-
return {
|
|
1160
|
-
hooks: [
|
|
1161
|
-
{
|
|
1162
|
-
type: "command",
|
|
1163
|
-
command: `olakai monitor hook --tool codex ${event}`,
|
|
1164
|
-
timeout: CODEX_HOOK_TIMEOUT_SECONDS
|
|
1165
|
-
}
|
|
1166
|
-
]
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
function isOlakaiHandler(handler) {
|
|
1170
|
-
return typeof handler.command === "string" && handler.command.includes(OLAKAI_HOOK_MARKER2);
|
|
1171
|
-
}
|
|
1172
|
-
function groupContainsOlakaiHandler(group) {
|
|
1173
|
-
if (!Array.isArray(group.hooks)) return false;
|
|
1174
|
-
return group.hooks.some(isOlakaiHandler);
|
|
1175
|
-
}
|
|
1176
|
-
function mergeCodexHooks(existing, events = SUPPORTED_HOOK_EVENT_NAMES) {
|
|
1177
|
-
const merged = { ...existing ?? {} };
|
|
1178
|
-
for (const event of events) {
|
|
1179
|
-
const existingGroups = merged[event] ?? [];
|
|
1180
|
-
const hasOlakaiHook = existingGroups.some(groupContainsOlakaiHandler);
|
|
1181
|
-
if (hasOlakaiHook) {
|
|
1182
|
-
merged[event] = existingGroups;
|
|
1183
|
-
} else {
|
|
1184
|
-
merged[event] = [...existingGroups, buildOlakaiHookGroup(event)];
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return merged;
|
|
1188
|
-
}
|
|
1189
|
-
function stripOlakaiHooks(existing) {
|
|
1190
|
-
if (!existing) return void 0;
|
|
1191
|
-
const cleaned = {};
|
|
1192
|
-
for (const [event, groups] of Object.entries(existing)) {
|
|
1193
|
-
if (!Array.isArray(groups)) continue;
|
|
1194
|
-
const filteredGroups = [];
|
|
1195
|
-
for (const group of groups) {
|
|
1196
|
-
const handlers = Array.isArray(group.hooks) ? group.hooks : [];
|
|
1197
|
-
const remaining = handlers.filter((h) => !isOlakaiHandler(h));
|
|
1198
|
-
if (remaining.length === 0 && handlers.length > 0) {
|
|
1199
|
-
continue;
|
|
1200
|
-
}
|
|
1201
|
-
if (remaining.length === handlers.length) {
|
|
1202
|
-
filteredGroups.push(group);
|
|
1203
|
-
} else {
|
|
1204
|
-
filteredGroups.push({ ...group, hooks: remaining });
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
if (filteredGroups.length > 0) {
|
|
1208
|
-
cleaned[event] = filteredGroups;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
return Object.keys(cleaned).length > 0 ? cleaned : void 0;
|
|
1212
|
-
}
|
|
1213
|
-
function hasOlakaiHooksInstalled(parsed) {
|
|
1214
|
-
const hooks = parsed.hooks;
|
|
1215
|
-
if (!hooks) return false;
|
|
1216
|
-
for (const groups of Object.values(hooks)) {
|
|
1217
|
-
if (!Array.isArray(groups)) continue;
|
|
1218
|
-
for (const group of groups) {
|
|
1219
|
-
if (groupContainsOlakaiHandler(group)) return true;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
return false;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// src/monitor/plugins/codex/config.ts
|
|
1226
|
-
import * as fs5 from "fs";
|
|
1227
|
-
import * as path5 from "path";
|
|
1228
|
-
function getCodexConfigPath2(projectRoot) {
|
|
1229
|
-
return getMonitorConfigPath(projectRoot, "codex");
|
|
1230
|
-
}
|
|
1231
|
-
function loadCodexConfig(projectRoot) {
|
|
1232
|
-
const filePath = getCodexConfigPath2(projectRoot);
|
|
1233
|
-
try {
|
|
1234
|
-
if (!fs5.existsSync(filePath)) return null;
|
|
1235
|
-
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
1236
|
-
return JSON.parse(raw);
|
|
1237
|
-
} catch {
|
|
1238
|
-
return null;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
function writeCodexConfig(projectRoot, config) {
|
|
1242
|
-
const filePath = getCodexConfigPath2(projectRoot);
|
|
1243
|
-
const dir = path5.dirname(filePath);
|
|
1244
|
-
if (!fs5.existsSync(dir)) {
|
|
1245
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
1246
|
-
}
|
|
1247
|
-
fs5.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1248
|
-
try {
|
|
1249
|
-
fs5.chmodSync(filePath, 384);
|
|
1250
|
-
} catch {
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
function deleteCodexConfig(projectRoot) {
|
|
1254
|
-
const filePath = getCodexConfigPath2(projectRoot);
|
|
1255
|
-
if (!fs5.existsSync(filePath)) return false;
|
|
1256
|
-
try {
|
|
1257
|
-
fs5.unlinkSync(filePath);
|
|
1258
|
-
return true;
|
|
1259
|
-
} catch {
|
|
1260
|
-
return false;
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
// src/monitor/plugins/codex/install.ts
|
|
1265
|
-
var CODEX_SOURCE = "codex";
|
|
1266
|
-
var CODEX_AGENT_SOURCE = "CODEX";
|
|
1267
|
-
var CODEX_AGENT_CATEGORY = "CODING";
|
|
1268
|
-
async function installCodex(opts) {
|
|
1269
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
1270
|
-
const token = getValidToken();
|
|
1271
|
-
if (!token) {
|
|
1272
|
-
console.error("Not logged in. Run 'olakai login' first.");
|
|
1273
|
-
process.exit(1);
|
|
1274
|
-
}
|
|
1275
|
-
console.log("Setting up Codex CLI monitoring for this workspace...\n");
|
|
1276
|
-
const agent = await provisionSelfMonitorAgent({
|
|
1277
|
-
projectRoot,
|
|
1278
|
-
source: CODEX_AGENT_SOURCE,
|
|
1279
|
-
displayName: "Codex CLI",
|
|
1280
|
-
category: CODEX_AGENT_CATEGORY
|
|
1281
|
-
});
|
|
1282
|
-
const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
|
|
1283
|
-
const apiKey = agent.apiKey?.key;
|
|
1284
|
-
if (!apiKey) {
|
|
1285
|
-
console.error(
|
|
1286
|
-
"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'."
|
|
1287
|
-
);
|
|
1288
|
-
process.exit(1);
|
|
1289
|
-
}
|
|
1290
|
-
const { configExisted: codexConfigExisted } = installCodexHooksConfig();
|
|
1291
|
-
const monitorConfig = {
|
|
1292
|
-
agentId: agent.id,
|
|
1293
|
-
apiKey,
|
|
1294
|
-
agentName: agent.name,
|
|
1295
|
-
source: CODEX_SOURCE,
|
|
1296
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1297
|
-
monitoringEndpoint
|
|
1298
|
-
};
|
|
1299
|
-
writeCodexConfig(projectRoot, monitorConfig);
|
|
1300
|
-
const configPath = getCodexConfigPath2(projectRoot);
|
|
1301
|
-
const configRel = path6.relative(projectRoot, configPath);
|
|
1302
|
-
console.log("");
|
|
1303
|
-
console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
|
|
1304
|
-
if (agent.apiKey?.key) {
|
|
1305
|
-
console.log("\u2713 API key generated");
|
|
1306
|
-
}
|
|
1307
|
-
console.log(
|
|
1308
|
-
`\u2713 Codex hooks configured in ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME}`
|
|
1309
|
-
);
|
|
1310
|
-
console.log(`\u2713 Monitor config saved to ${configRel}`);
|
|
1311
|
-
console.log("");
|
|
1312
|
-
console.log(
|
|
1313
|
-
"Monitoring is now active. Codex CLI will report activity to Olakai"
|
|
1314
|
-
);
|
|
1315
|
-
console.log(`on each turn. View activity at: ${getBaseUrl()}/dashboard`);
|
|
1316
|
-
console.log("");
|
|
1317
|
-
console.log(
|
|
1318
|
-
`\u26A0 Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key).`
|
|
1319
|
-
);
|
|
1320
|
-
console.log(
|
|
1321
|
-
`\u26A0 Codex hooks require codex >= 0.124.0. Earlier versions silently skip them.`
|
|
1322
|
-
);
|
|
1323
|
-
if (codexConfigExisted) {
|
|
1324
|
-
console.log(
|
|
1325
|
-
`\u26A0 Existing comments in ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME} were not preserved (TOML serializer limitation).`
|
|
1326
|
-
);
|
|
1327
|
-
}
|
|
1328
|
-
console.log("");
|
|
1329
|
-
console.log("To check status: olakai monitor status --tool codex");
|
|
1330
|
-
console.log("To disable: olakai monitor disable --tool codex");
|
|
1331
|
-
return {
|
|
1332
|
-
agentId: agent.id,
|
|
1333
|
-
agentName: agent.name,
|
|
1334
|
-
source: CODEX_SOURCE,
|
|
1335
|
-
monitoringEndpoint
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
function installCodexHooksConfig() {
|
|
1339
|
-
const homeDir = getCodexHomeDir();
|
|
1340
|
-
const configPath = getCodexConfigPath();
|
|
1341
|
-
if (!fs6.existsSync(homeDir)) {
|
|
1342
|
-
fs6.mkdirSync(homeDir, { recursive: true });
|
|
1343
|
-
}
|
|
1344
|
-
const configExisted = fs6.existsSync(configPath);
|
|
1345
|
-
const parsed = readCodexConfigToml(configPath);
|
|
1346
|
-
const merged = {
|
|
1347
|
-
...parsed,
|
|
1348
|
-
hooks: mergeCodexHooks(parsed.hooks, SUPPORTED_HOOK_EVENT_NAMES)
|
|
1349
|
-
};
|
|
1350
|
-
writeCodexConfigToml(configPath, merged);
|
|
1351
|
-
return { configExisted };
|
|
1352
|
-
}
|
|
1353
|
-
async function uninstallCodex(opts) {
|
|
1354
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
1355
|
-
const removedHooks = stripCodexHooksConfig();
|
|
1356
|
-
if (removedHooks) {
|
|
1357
|
-
console.log(
|
|
1358
|
-
`\u2713 Olakai hooks removed from ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME}`
|
|
1359
|
-
);
|
|
1360
|
-
} else {
|
|
1361
|
-
console.log("No Olakai hooks found in Codex config.");
|
|
1362
|
-
}
|
|
1363
|
-
if (!opts.keepConfig) {
|
|
1364
|
-
const configPath = getCodexConfigPath2(projectRoot);
|
|
1365
|
-
const configRel = path6.relative(projectRoot, configPath);
|
|
1366
|
-
if (deleteCodexConfig(projectRoot)) {
|
|
1367
|
-
console.log(`\u2713 Monitor config removed (${configRel})`);
|
|
1368
|
-
}
|
|
1369
|
-
} else {
|
|
1370
|
-
const configPath = getCodexConfigPath2(projectRoot);
|
|
1371
|
-
const configRel = path6.relative(projectRoot, configPath);
|
|
1372
|
-
console.log(`Monitor config retained at ${configRel}`);
|
|
1373
|
-
}
|
|
1374
|
-
console.log("");
|
|
1375
|
-
console.log(
|
|
1376
|
-
"Monitoring disabled. Run 'olakai monitor init --tool codex' to re-enable."
|
|
1377
|
-
);
|
|
1378
|
-
}
|
|
1379
|
-
function applyOlakaiStripToConfig(parsed) {
|
|
1380
|
-
const cleaned = stripOlakaiHooks(parsed.hooks);
|
|
1381
|
-
const next = { ...parsed };
|
|
1382
|
-
if (cleaned === void 0) {
|
|
1383
|
-
delete next.hooks;
|
|
1384
|
-
} else {
|
|
1385
|
-
next.hooks = cleaned;
|
|
1386
|
-
}
|
|
1387
|
-
return next;
|
|
1388
|
-
}
|
|
1389
|
-
function stripCodexHooksConfig() {
|
|
1390
|
-
const configPath = getCodexConfigPath();
|
|
1391
|
-
if (!fs6.existsSync(configPath)) return false;
|
|
1392
|
-
const parsed = readCodexConfigToml(configPath);
|
|
1393
|
-
if (!hasOlakaiHooksInstalled(parsed)) return false;
|
|
1394
|
-
const next = applyOlakaiStripToConfig(parsed);
|
|
1395
|
-
writeCodexConfigToml(configPath, next);
|
|
1396
|
-
return true;
|
|
1397
|
-
}
|
|
1398
|
-
function readCodexConfigToml(configPath) {
|
|
1399
|
-
try {
|
|
1400
|
-
if (!fs6.existsSync(configPath)) return {};
|
|
1401
|
-
const raw = fs6.readFileSync(configPath, "utf-8");
|
|
1402
|
-
if (!raw.trim()) return {};
|
|
1403
|
-
return TOML.parse(raw);
|
|
1404
|
-
} catch {
|
|
1405
|
-
return {};
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
function writeCodexConfigToml(configPath, data) {
|
|
1409
|
-
const dir = path6.dirname(configPath);
|
|
1410
|
-
if (!fs6.existsSync(dir)) {
|
|
1411
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
1412
|
-
}
|
|
1413
|
-
const serialized = TOML.stringify(data);
|
|
1414
|
-
fs6.writeFileSync(configPath, serialized, "utf-8");
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
// src/monitor/plugins/codex/status.ts
|
|
1418
|
-
import path7 from "path";
|
|
1419
|
-
import * as fs7 from "fs";
|
|
1420
|
-
async function getCodexStatus(opts) {
|
|
1421
|
-
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
1422
|
-
const configPath = getCodexConfigPath2(projectRoot);
|
|
1423
|
-
const config = loadCodexConfig(projectRoot);
|
|
1424
|
-
const hooksConfigured = isHooksBlockInstalled();
|
|
1425
|
-
if (!config) {
|
|
1426
|
-
return {
|
|
1427
|
-
toolId: "codex",
|
|
1428
|
-
configured: false,
|
|
1429
|
-
hooksConfigured,
|
|
1430
|
-
configPath,
|
|
1431
|
-
notes: hooksConfigured ? [
|
|
1432
|
-
`Codex hooks present in ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME} but no monitor config in this workspace \u2014 re-run init.`
|
|
1433
|
-
] : []
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
return {
|
|
1437
|
-
toolId: "codex",
|
|
1438
|
-
configured: true,
|
|
1439
|
-
hooksConfigured,
|
|
1440
|
-
agentId: config.agentId,
|
|
1441
|
-
agentName: config.agentName,
|
|
1442
|
-
source: config.source,
|
|
1443
|
-
apiKeyMasked: config.apiKey.slice(0, 12) + "...",
|
|
1444
|
-
monitoringEndpoint: config.monitoringEndpoint,
|
|
1445
|
-
configuredAt: config.createdAt,
|
|
1446
|
-
configPath
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
function isHooksBlockInstalled() {
|
|
1450
|
-
const homeConfig = getCodexConfigPath();
|
|
1451
|
-
if (!fs7.existsSync(homeConfig)) return false;
|
|
1452
|
-
const parsed = readCodexConfigToml(homeConfig);
|
|
1453
|
-
return hasOlakaiHooksInstalled(parsed);
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
// src/monitor/plugins/codex/transcript.ts
|
|
1457
|
-
import * as fs8 from "fs";
|
|
1458
|
-
import * as path8 from "path";
|
|
1459
|
-
function emptyRollout() {
|
|
1460
|
-
return {
|
|
1461
|
-
prompt: "",
|
|
1462
|
-
response: "",
|
|
1463
|
-
modelName: null,
|
|
1464
|
-
inputTokens: 0,
|
|
1465
|
-
outputTokens: 0,
|
|
1466
|
-
cachedInputTokens: 0,
|
|
1467
|
-
reasoningOutputTokens: 0,
|
|
1468
|
-
tokens: 0,
|
|
1469
|
-
numTurns: 0
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
var noopDebug2 = () => {
|
|
1473
|
-
};
|
|
1474
|
-
var DEFAULT_LIMITS = {
|
|
1475
|
-
maxDirs: 200,
|
|
1476
|
-
maxFiles: 5e3
|
|
1477
|
-
};
|
|
1478
|
-
function findRolloutPathForSession(sessionId, sessionsDir = getCodexSessionsDir(), limits = {}) {
|
|
1479
|
-
const merged = { ...DEFAULT_LIMITS, ...limits };
|
|
1480
|
-
if (!sessionId) return null;
|
|
1481
|
-
if (!fs8.existsSync(sessionsDir)) return null;
|
|
1482
|
-
const suffix = `-${sessionId}.jsonl`;
|
|
1483
|
-
const matches = [];
|
|
1484
|
-
let dirsScanned = 0;
|
|
1485
|
-
let filesScanned = 0;
|
|
1486
|
-
const queue = [sessionsDir];
|
|
1487
|
-
while (queue.length > 0) {
|
|
1488
|
-
const dir = queue.shift();
|
|
1489
|
-
if (dirsScanned++ > merged.maxDirs) break;
|
|
1490
|
-
let entries;
|
|
1491
|
-
try {
|
|
1492
|
-
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
1493
|
-
} catch {
|
|
1494
|
-
continue;
|
|
1495
|
-
}
|
|
1496
|
-
for (const entry of entries) {
|
|
1497
|
-
if (filesScanned++ > merged.maxFiles) break;
|
|
1498
|
-
const full = path8.join(dir, entry.name);
|
|
1499
|
-
if (entry.isDirectory()) {
|
|
1500
|
-
queue.push(full);
|
|
1501
|
-
continue;
|
|
1502
|
-
}
|
|
1503
|
-
if (!entry.isFile()) continue;
|
|
1504
|
-
if (!entry.name.endsWith(suffix)) continue;
|
|
1505
|
-
if (!entry.name.startsWith("rollout-")) continue;
|
|
1506
|
-
try {
|
|
1507
|
-
const stat = fs8.statSync(full);
|
|
1508
|
-
matches.push({ path: full, mtimeMs: stat.mtimeMs });
|
|
1509
|
-
} catch {
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
if (matches.length === 0) return null;
|
|
1514
|
-
matches.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1515
|
-
return matches[0].path;
|
|
1516
|
-
}
|
|
1517
|
-
function parseRolloutContent(raw, debugLog4 = noopDebug2) {
|
|
1518
|
-
const result = emptyRollout();
|
|
1519
|
-
if (!raw) return result;
|
|
1520
|
-
const lines = raw.split("\n");
|
|
1521
|
-
let lastUserMessage = "";
|
|
1522
|
-
let lastAssistantMessage = "";
|
|
1523
|
-
let lastTokenUsage = null;
|
|
1524
|
-
let totalTokenUsage = null;
|
|
1525
|
-
let modelName = null;
|
|
1526
|
-
let userTurnCount = 0;
|
|
1527
|
-
for (const line of lines) {
|
|
1528
|
-
const trimmed = line.trim();
|
|
1529
|
-
if (!trimmed) continue;
|
|
1530
|
-
let parsed;
|
|
1531
|
-
try {
|
|
1532
|
-
parsed = JSON.parse(trimmed);
|
|
1533
|
-
} catch (err) {
|
|
1534
|
-
debugLog4("rollout-line-parse-failed", {
|
|
1535
|
-
line: trimmed.slice(0, 120),
|
|
1536
|
-
error: err.message
|
|
1537
|
-
});
|
|
1538
|
-
continue;
|
|
1539
|
-
}
|
|
1540
|
-
const type = parsed.type;
|
|
1541
|
-
const payload = parsed.payload;
|
|
1542
|
-
if (!type || !payload) continue;
|
|
1543
|
-
if (type === "session_meta") {
|
|
1544
|
-
continue;
|
|
1545
|
-
}
|
|
1546
|
-
if (type === "event_msg") {
|
|
1547
|
-
const ev = payload;
|
|
1548
|
-
const evType = ev.type;
|
|
1549
|
-
if (evType === "user_message" && typeof ev.message === "string") {
|
|
1550
|
-
lastUserMessage = ev.message;
|
|
1551
|
-
userTurnCount += 1;
|
|
1552
|
-
} else if (evType === "agent_message" && typeof ev.message === "string") {
|
|
1553
|
-
lastAssistantMessage = ev.message;
|
|
1554
|
-
} else if (evType === "token_count" && ev.info) {
|
|
1555
|
-
if (ev.info.last_token_usage) {
|
|
1556
|
-
lastTokenUsage = ev.info.last_token_usage;
|
|
1557
|
-
}
|
|
1558
|
-
if (ev.info.total_token_usage) {
|
|
1559
|
-
totalTokenUsage = ev.info.total_token_usage;
|
|
1560
|
-
}
|
|
1561
|
-
} else if (evType === "session_configured" && typeof ev.model === "string" && ev.model) {
|
|
1562
|
-
modelName = ev.model;
|
|
1563
|
-
}
|
|
1564
|
-
continue;
|
|
1565
|
-
}
|
|
1566
|
-
if (type === "response_item") {
|
|
1567
|
-
const ri = payload;
|
|
1568
|
-
if (ri.type === "message" && Array.isArray(ri.content)) {
|
|
1569
|
-
const text = extractTextFromContent(ri.content);
|
|
1570
|
-
if (!text) continue;
|
|
1571
|
-
if (ri.role === "user") {
|
|
1572
|
-
lastUserMessage = text;
|
|
1573
|
-
userTurnCount += 1;
|
|
1574
|
-
} else if (ri.role === "assistant") {
|
|
1575
|
-
lastAssistantMessage = text;
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
continue;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
result.prompt = lastUserMessage;
|
|
1582
|
-
result.response = lastAssistantMessage;
|
|
1583
|
-
result.modelName = modelName;
|
|
1584
|
-
result.numTurns = userTurnCount;
|
|
1585
|
-
const tokenSource = lastTokenUsage ?? totalTokenUsage;
|
|
1586
|
-
if (tokenSource) {
|
|
1587
|
-
result.inputTokens = numberOrZero(tokenSource.input_tokens);
|
|
1588
|
-
result.outputTokens = numberOrZero(tokenSource.output_tokens);
|
|
1589
|
-
result.cachedInputTokens = numberOrZero(tokenSource.cached_input_tokens);
|
|
1590
|
-
result.reasoningOutputTokens = numberOrZero(
|
|
1591
|
-
tokenSource.reasoning_output_tokens
|
|
1592
|
-
);
|
|
1593
|
-
result.tokens = numberOrZero(tokenSource.total_tokens) || result.inputTokens + result.outputTokens;
|
|
1594
|
-
}
|
|
1595
|
-
return result;
|
|
1596
|
-
}
|
|
1597
|
-
function extractTextFromContent(content) {
|
|
1598
|
-
const parts = [];
|
|
1599
|
-
for (const block of content) {
|
|
1600
|
-
if (typeof block.text !== "string") continue;
|
|
1601
|
-
if (block.type === "output_text" || block.type === "input_text" || block.type === "text") {
|
|
1602
|
-
parts.push(block.text);
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
return parts.join("\n").trim();
|
|
1606
|
-
}
|
|
1607
|
-
function numberOrZero(value) {
|
|
1608
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1609
|
-
}
|
|
1610
|
-
function loadRolloutForSession(sessionId, options = {}) {
|
|
1611
|
-
const debugLog4 = options.debugLog ?? noopDebug2;
|
|
1612
|
-
const sessionsDir = options.sessionsDir ?? getCodexSessionsDir();
|
|
1613
|
-
const result = emptyRollout();
|
|
1614
|
-
const rolloutPath = findRolloutPathForSession(sessionId, sessionsDir);
|
|
1615
|
-
if (!rolloutPath) {
|
|
1616
|
-
debugLog4("rollout-not-found", {
|
|
1617
|
-
sessionId,
|
|
1618
|
-
sessionsDir
|
|
1619
|
-
});
|
|
1620
|
-
return result;
|
|
1621
|
-
}
|
|
1622
|
-
let raw;
|
|
1623
|
-
try {
|
|
1624
|
-
raw = fs8.readFileSync(rolloutPath, "utf-8");
|
|
1625
|
-
} catch (err) {
|
|
1626
|
-
debugLog4("rollout-read-failed", {
|
|
1627
|
-
rolloutPath,
|
|
1628
|
-
error: err.message
|
|
1629
|
-
});
|
|
1630
|
-
return result;
|
|
1631
|
-
}
|
|
1632
|
-
const parsed = parseRolloutContent(raw, debugLog4);
|
|
1633
|
-
parsed.rolloutPath = rolloutPath;
|
|
1634
|
-
return parsed;
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
// src/monitor/plugins/codex/hook.ts
|
|
1638
|
-
var noopDebug3 = () => {
|
|
1639
|
-
};
|
|
1640
|
-
var SUPPORTED_EVENTS = /* @__PURE__ */ new Set(["Stop", "UserPromptSubmit"]);
|
|
1641
|
-
function isSupportedCodexEvent(eventName) {
|
|
1642
|
-
return SUPPORTED_EVENTS.has(eventName);
|
|
1643
|
-
}
|
|
1644
|
-
function buildCodexPayload(eventName, eventData, config, rollout = emptyRollout()) {
|
|
1645
|
-
if (!isSupportedCodexEvent(eventName)) return null;
|
|
1646
|
-
if (eventName === "UserPromptSubmit") return null;
|
|
1647
|
-
const sessionId = typeof eventData.session_id === "string" && eventData.session_id ? eventData.session_id : `codex-${Date.now()}`;
|
|
1648
|
-
const cwd = typeof eventData.cwd === "string" ? eventData.cwd : "";
|
|
1649
|
-
const turnId = typeof eventData.turn_id === "string" ? eventData.turn_id : "";
|
|
1650
|
-
const permissionMode = typeof eventData.permission_mode === "string" ? eventData.permission_mode : "";
|
|
1651
|
-
const transcriptPath = typeof eventData.transcript_path === "string" ? eventData.transcript_path : "";
|
|
1652
|
-
const modelName = (typeof eventData.model === "string" && eventData.model.trim() ? eventData.model : null) ?? rollout.modelName;
|
|
1653
|
-
const customData = {
|
|
1654
|
-
hookEvent: eventData.hook_event_name ?? eventName,
|
|
1655
|
-
sessionId,
|
|
1656
|
-
cwd,
|
|
1657
|
-
turnId,
|
|
1658
|
-
permissionMode,
|
|
1659
|
-
transcriptPath,
|
|
1660
|
-
inputTokens: rollout.inputTokens,
|
|
1661
|
-
outputTokens: rollout.outputTokens,
|
|
1662
|
-
cachedInputTokens: rollout.cachedInputTokens,
|
|
1663
|
-
reasoningOutputTokens: rollout.reasoningOutputTokens,
|
|
1664
|
-
numTurns: rollout.numTurns
|
|
1665
|
-
};
|
|
1666
|
-
if (rollout.rolloutPath) {
|
|
1667
|
-
customData.rolloutPath = rollout.rolloutPath;
|
|
1668
|
-
}
|
|
1669
|
-
if (typeof eventData.stop_hook_active === "boolean") {
|
|
1670
|
-
customData.stopHookActive = eventData.stop_hook_active;
|
|
1671
|
-
}
|
|
1672
|
-
const inlineAssistant = typeof eventData.last_assistant_message === "string" && eventData.last_assistant_message.trim() ? eventData.last_assistant_message : "";
|
|
1673
|
-
const response = inlineAssistant || rollout.response;
|
|
1674
|
-
return {
|
|
1675
|
-
prompt: rollout.prompt,
|
|
1676
|
-
response,
|
|
1677
|
-
chatId: sessionId,
|
|
1678
|
-
source: config.source,
|
|
1679
|
-
modelName: modelName ?? void 0,
|
|
1680
|
-
tokens: rollout.tokens,
|
|
1681
|
-
customData
|
|
1682
|
-
};
|
|
1683
|
-
}
|
|
1684
|
-
function handleCodexHook(eventName, payloadJson, config, options = {}) {
|
|
1685
|
-
const debugLog4 = options.debugLog ?? noopDebug3;
|
|
1686
|
-
if (!isSupportedCodexEvent(eventName)) {
|
|
1687
|
-
debugLog4("hook-unknown-event", eventName);
|
|
1688
|
-
return null;
|
|
1689
|
-
}
|
|
1690
|
-
if (eventName === "UserPromptSubmit") {
|
|
1691
|
-
debugLog4("user-prompt-submit-dropped", { eventName });
|
|
1692
|
-
return null;
|
|
1693
|
-
}
|
|
1694
|
-
const eventData = payloadJson ?? {};
|
|
1695
|
-
debugLog4("event-parsed", { eventName, eventData });
|
|
1696
|
-
const sessionId = typeof eventData.session_id === "string" ? eventData.session_id : "";
|
|
1697
|
-
let rollout = emptyRollout();
|
|
1698
|
-
if (eventName === "Stop" && sessionId) {
|
|
1699
|
-
rollout = loadRolloutForSession(sessionId, {
|
|
1700
|
-
debugLog: debugLog4,
|
|
1701
|
-
sessionsDir: options.sessionsDir
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
const payload = buildCodexPayload(eventName, eventData, config, rollout);
|
|
1705
|
-
if (!payload) return null;
|
|
1706
|
-
if (!payload.prompt && !payload.response) {
|
|
1707
|
-
debugLog4("payload-empty", { eventName, sessionId });
|
|
1708
|
-
return null;
|
|
1709
|
-
}
|
|
1710
|
-
debugLog4("payload-built", payload);
|
|
1711
|
-
return payload;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
// src/monitor/plugins/codex/index.ts
|
|
1715
|
-
var TOOL_ID2 = "codex";
|
|
1716
|
-
function debugLog2(label, data) {
|
|
1717
|
-
if (process.env.OLAKAI_MONITOR_DEBUG !== "1") return;
|
|
1718
|
-
try {
|
|
1719
|
-
const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;
|
|
1720
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] codex/${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}
|
|
1721
|
-
`;
|
|
1722
|
-
fs9.appendFileSync(logPath, line, "utf-8");
|
|
1723
|
-
} catch {
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
function resolveCodexProjectRoot(eventData, fallbackCwd) {
|
|
1727
|
-
const payloadCwd = typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : fallbackCwd;
|
|
1728
|
-
return findConfiguredWorkspace(payloadCwd, [TOOL_ID2]);
|
|
1729
|
-
}
|
|
1730
|
-
var codexPlugin = {
|
|
1731
|
-
id: TOOL_ID2,
|
|
1732
|
-
displayName: "OpenAI Codex CLI",
|
|
1733
|
-
install(opts) {
|
|
1734
|
-
return installCodex(opts);
|
|
1735
|
-
},
|
|
1736
|
-
uninstall(opts) {
|
|
1737
|
-
return uninstallCodex(opts);
|
|
1738
|
-
},
|
|
1739
|
-
status(opts) {
|
|
1740
|
-
return getCodexStatus(opts);
|
|
1741
|
-
},
|
|
1742
|
-
async handleHook(eventName, payloadJson) {
|
|
1743
|
-
const eventData = payloadJson ?? {};
|
|
1744
|
-
debugLog2("hook-fired", { eventName, hasPayload: payloadJson != null });
|
|
1745
|
-
const projectRoot = resolveCodexProjectRoot(eventData, process.cwd());
|
|
1746
|
-
if (!projectRoot) {
|
|
1747
|
-
debugLog2("config-not-found", {
|
|
1748
|
-
startDir: typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : process.cwd()
|
|
1749
|
-
});
|
|
1750
|
-
return null;
|
|
1751
|
-
}
|
|
1752
|
-
const config = loadCodexConfig(projectRoot);
|
|
1753
|
-
if (!config) {
|
|
1754
|
-
debugLog2("monitor-config-missing", { projectRoot });
|
|
1755
|
-
return null;
|
|
1756
|
-
}
|
|
1757
|
-
const payload = handleCodexHook(eventName, eventData, config, {
|
|
1758
|
-
debugLog: debugLog2
|
|
1759
|
-
});
|
|
1760
|
-
if (!payload) return null;
|
|
1761
|
-
return {
|
|
1762
|
-
payload,
|
|
1763
|
-
transport: {
|
|
1764
|
-
endpoint: config.monitoringEndpoint,
|
|
1765
|
-
apiKey: config.apiKey,
|
|
1766
|
-
projectRoot
|
|
1767
|
-
}
|
|
1768
|
-
};
|
|
1769
|
-
},
|
|
1770
|
-
async detectInstalled(opts) {
|
|
1771
|
-
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
1772
|
-
try {
|
|
1773
|
-
if (fs9.existsSync(getCodexConfigPath2(projectRoot))) {
|
|
1774
|
-
return true;
|
|
1775
|
-
}
|
|
1776
|
-
} catch {
|
|
1777
|
-
}
|
|
1778
|
-
try {
|
|
1779
|
-
if (fs9.existsSync(getCodexConfigPath())) {
|
|
1780
|
-
return true;
|
|
1781
|
-
}
|
|
1782
|
-
if (fs9.existsSync(getCodexHomeDir())) {
|
|
1783
|
-
return true;
|
|
1784
|
-
}
|
|
1785
|
-
} catch {
|
|
1786
|
-
}
|
|
1787
|
-
return detectCodexBinaryOnPath();
|
|
1788
|
-
}
|
|
1789
|
-
};
|
|
1790
|
-
function detectCodexBinaryOnPath() {
|
|
1791
|
-
try {
|
|
1792
|
-
const probe = spawnSync2(
|
|
1793
|
-
process.platform === "win32" ? "where" : "which",
|
|
1794
|
-
["codex"],
|
|
1795
|
-
{
|
|
1796
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1797
|
-
timeout: 1e3
|
|
1798
|
-
}
|
|
1799
|
-
);
|
|
1800
|
-
if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {
|
|
1801
|
-
return true;
|
|
1802
|
-
}
|
|
1803
|
-
} catch {
|
|
1804
|
-
}
|
|
1805
|
-
return false;
|
|
1806
|
-
}
|
|
1807
|
-
registerPlugin(codexPlugin);
|
|
1808
|
-
|
|
1809
|
-
// src/monitor/plugins/cursor/index.ts
|
|
1810
|
-
import * as fs14 from "fs";
|
|
1811
|
-
import * as os7 from "os";
|
|
1812
|
-
|
|
1813
|
-
// src/monitor/plugins/cursor/install.ts
|
|
1814
|
-
import * as fs11 from "fs";
|
|
1815
|
-
import * as os4 from "os";
|
|
1816
|
-
import * as path11 from "path";
|
|
1817
|
-
|
|
1818
|
-
// src/monitor/plugins/cursor/config.ts
|
|
1819
|
-
import * as fs10 from "fs";
|
|
1820
|
-
import * as path9 from "path";
|
|
1821
|
-
function getCursorConfigPath(projectRoot) {
|
|
1822
|
-
return getMonitorConfigPath(projectRoot, "cursor");
|
|
1823
|
-
}
|
|
1824
|
-
function loadCursorConfig(projectRoot) {
|
|
1825
|
-
const filePath = getCursorConfigPath(projectRoot);
|
|
1826
|
-
try {
|
|
1827
|
-
if (!fs10.existsSync(filePath)) return null;
|
|
1828
|
-
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
1829
|
-
return JSON.parse(raw);
|
|
1830
|
-
} catch {
|
|
1831
|
-
return null;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
function writeCursorConfig(projectRoot, config) {
|
|
1835
|
-
const filePath = getCursorConfigPath(projectRoot);
|
|
1836
|
-
const dir = path9.dirname(filePath);
|
|
1837
|
-
if (!fs10.existsSync(dir)) {
|
|
1838
|
-
fs10.mkdirSync(dir, { recursive: true });
|
|
1839
|
-
}
|
|
1840
|
-
fs10.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1841
|
-
try {
|
|
1842
|
-
fs10.chmodSync(filePath, 384);
|
|
1843
|
-
} catch {
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
function deleteCursorConfig(projectRoot) {
|
|
1847
|
-
const filePath = getCursorConfigPath(projectRoot);
|
|
1848
|
-
if (!fs10.existsSync(filePath)) return false;
|
|
1849
|
-
try {
|
|
1850
|
-
fs10.unlinkSync(filePath);
|
|
1851
|
-
return true;
|
|
1852
|
-
} catch {
|
|
1853
|
-
return false;
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
// src/monitor/plugins/cursor/paths.ts
|
|
1858
|
-
import * as os3 from "os";
|
|
1859
|
-
import * as path10 from "path";
|
|
1860
|
-
var CURSOR_DIR_NAME = ".cursor";
|
|
1861
|
-
var CURSOR_HOOKS_FILE = "hooks.json";
|
|
1862
|
-
function getCursorUserDir(homeDir = os3.homedir()) {
|
|
1863
|
-
return path10.join(homeDir, CURSOR_DIR_NAME);
|
|
1864
|
-
}
|
|
1865
|
-
function getCursorHooksPath(homeDir = os3.homedir()) {
|
|
1866
|
-
return path10.join(getCursorUserDir(homeDir), CURSOR_HOOKS_FILE);
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
// src/monitor/plugins/cursor/hooks-config.ts
|
|
1870
|
-
var OLAKAI_HOOK_MARKER3 = "olakai monitor hook --tool cursor";
|
|
1871
|
-
var CURSOR_HOOK_DEFINITIONS = {
|
|
1872
|
-
beforeSubmitPrompt: [
|
|
1873
|
-
{
|
|
1874
|
-
command: "olakai monitor hook --tool cursor beforeSubmitPrompt",
|
|
1875
|
-
timeout: 5
|
|
1876
|
-
}
|
|
1877
|
-
],
|
|
1878
|
-
afterAgentResponse: [
|
|
1879
|
-
{
|
|
1880
|
-
command: "olakai monitor hook --tool cursor afterAgentResponse",
|
|
1881
|
-
timeout: 5
|
|
1882
|
-
}
|
|
1883
|
-
],
|
|
1884
|
-
sessionEnd: [
|
|
1885
|
-
{
|
|
1886
|
-
command: "olakai monitor hook --tool cursor sessionEnd",
|
|
1887
|
-
timeout: 5
|
|
1888
|
-
}
|
|
1889
|
-
],
|
|
1890
|
-
stop: [
|
|
1891
|
-
{
|
|
1892
|
-
command: "olakai monitor hook --tool cursor stop",
|
|
1893
|
-
timeout: 5
|
|
1894
|
-
}
|
|
1895
|
-
]
|
|
1896
|
-
};
|
|
1897
|
-
var CURSOR_HOOKS_VERSION = 1;
|
|
1898
|
-
function mergeCursorHooks(existing, definitions = CURSOR_HOOK_DEFINITIONS) {
|
|
1899
|
-
const base = existing ? { ...existing } : {};
|
|
1900
|
-
if (typeof base.version !== "number") {
|
|
1901
|
-
base.version = CURSOR_HOOKS_VERSION;
|
|
1902
|
-
}
|
|
1903
|
-
const mergedHooks = {
|
|
1904
|
-
...base.hooks ?? {}
|
|
1905
|
-
};
|
|
1906
|
-
for (const [event, defaultEntries] of Object.entries(definitions)) {
|
|
1907
|
-
const existingEntries = mergedHooks[event] ?? [];
|
|
1908
|
-
const hasOlakaiEntry = existingEntries.some(
|
|
1909
|
-
(e) => typeof e?.command === "string" && e.command.includes(OLAKAI_HOOK_MARKER3)
|
|
1910
|
-
);
|
|
1911
|
-
if (hasOlakaiEntry) {
|
|
1912
|
-
mergedHooks[event] = existingEntries;
|
|
1913
|
-
} else {
|
|
1914
|
-
mergedHooks[event] = [...existingEntries, ...defaultEntries];
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
return {
|
|
1918
|
-
...base,
|
|
1919
|
-
hooks: mergedHooks
|
|
1920
|
-
};
|
|
1921
|
-
}
|
|
1922
|
-
function removeOlakaiCursorHooks(existing) {
|
|
1923
|
-
const base = existing ? { ...existing } : {};
|
|
1924
|
-
const sourceHooks = base.hooks ?? {};
|
|
1925
|
-
const cleaned = {};
|
|
1926
|
-
for (const [event, entries] of Object.entries(sourceHooks)) {
|
|
1927
|
-
const filtered = entries.filter(
|
|
1928
|
-
(e) => typeof e?.command !== "string" || !e.command.includes(OLAKAI_HOOK_MARKER3)
|
|
1929
|
-
);
|
|
1930
|
-
if (filtered.length > 0) {
|
|
1931
|
-
cleaned[event] = filtered;
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
if (Object.keys(cleaned).length > 0) {
|
|
1935
|
-
base.hooks = cleaned;
|
|
1936
|
-
} else {
|
|
1937
|
-
delete base.hooks;
|
|
1938
|
-
}
|
|
1939
|
-
return base;
|
|
1940
|
-
}
|
|
1941
|
-
function hasOlakaiCursorHooks(config) {
|
|
1942
|
-
if (!config?.hooks) return false;
|
|
1943
|
-
return Object.values(config.hooks).some(
|
|
1944
|
-
(entries) => entries.some(
|
|
1945
|
-
(e) => typeof e?.command === "string" && e.command.includes(OLAKAI_HOOK_MARKER3)
|
|
1946
|
-
)
|
|
1947
|
-
);
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
// src/monitor/plugins/cursor/install.ts
|
|
1951
|
-
var CURSOR_SOURCE = "cursor";
|
|
1952
|
-
var CURSOR_AGENT_SOURCE = "CURSOR";
|
|
1953
|
-
var CURSOR_AGENT_CATEGORY = "CODING";
|
|
1954
|
-
function readJsonFileTolerant(filePath) {
|
|
1955
|
-
try {
|
|
1956
|
-
if (!fs11.existsSync(filePath)) return null;
|
|
1957
|
-
const raw = fs11.readFileSync(filePath, "utf-8");
|
|
1958
|
-
return JSON.parse(raw);
|
|
1959
|
-
} catch {
|
|
1960
|
-
return null;
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
function writeJsonFileWithDir(filePath, data) {
|
|
1964
|
-
const dir = path11.dirname(filePath);
|
|
1965
|
-
if (!fs11.existsSync(dir)) {
|
|
1966
|
-
fs11.mkdirSync(dir, { recursive: true });
|
|
1967
|
-
}
|
|
1968
|
-
fs11.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1969
|
-
}
|
|
1970
|
-
async function installCursor(opts) {
|
|
1971
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
1972
|
-
const homeDir = opts.homeDir ?? os4.homedir();
|
|
1973
|
-
const token = getValidToken();
|
|
1974
|
-
if (!token) {
|
|
1975
|
-
console.error("Not logged in. Run 'olakai login' first.");
|
|
1976
|
-
process.exit(1);
|
|
1977
|
-
}
|
|
1978
|
-
console.log("Setting up Cursor monitoring for this workspace...\n");
|
|
1979
|
-
console.log(
|
|
1980
|
-
"Note: Cursor hooks are installed globally per user (~/.cursor/hooks.json)."
|
|
1981
|
-
);
|
|
1982
|
-
console.log(
|
|
1983
|
-
`Activity from this workspace (${projectRoot}) will be associated with`
|
|
1984
|
-
);
|
|
1985
|
-
console.log("the agent you select below.\n");
|
|
1986
|
-
const agent = await provisionSelfMonitorAgent({
|
|
1987
|
-
projectRoot,
|
|
1988
|
-
source: CURSOR_AGENT_SOURCE,
|
|
1989
|
-
displayName: "Cursor",
|
|
1990
|
-
category: CURSOR_AGENT_CATEGORY
|
|
1991
|
-
});
|
|
1992
|
-
const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
|
|
1993
|
-
const apiKey = agent.apiKey?.key;
|
|
1994
|
-
if (!apiKey) {
|
|
1995
|
-
console.error(
|
|
1996
|
-
"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'."
|
|
1997
|
-
);
|
|
1998
|
-
process.exit(1);
|
|
1999
|
-
}
|
|
2000
|
-
const cursorDir = getCursorUserDir(homeDir);
|
|
2001
|
-
if (!fs11.existsSync(cursorDir)) {
|
|
2002
|
-
fs11.mkdirSync(cursorDir, { recursive: true });
|
|
2003
|
-
}
|
|
2004
|
-
const hooksPath = getCursorHooksPath(homeDir);
|
|
2005
|
-
const existingHooks = readJsonFileTolerant(hooksPath);
|
|
2006
|
-
const mergedHooks = mergeCursorHooks(existingHooks);
|
|
2007
|
-
writeJsonFileWithDir(hooksPath, mergedHooks);
|
|
2008
|
-
const monitorConfig = {
|
|
2009
|
-
agentId: agent.id,
|
|
2010
|
-
apiKey,
|
|
2011
|
-
agentName: agent.name,
|
|
2012
|
-
source: CURSOR_SOURCE,
|
|
2013
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2014
|
-
monitoringEndpoint
|
|
2015
|
-
};
|
|
2016
|
-
writeCursorConfig(projectRoot, monitorConfig);
|
|
2017
|
-
const configPath = getCursorConfigPath(projectRoot);
|
|
2018
|
-
const configRel = path11.relative(projectRoot, configPath);
|
|
2019
|
-
const hooksDisplay = `~/${path11.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`;
|
|
2020
|
-
console.log("");
|
|
2021
|
-
console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
|
|
2022
|
-
if (agent.apiKey?.key) {
|
|
2023
|
-
console.log("\u2713 API key generated");
|
|
2024
|
-
}
|
|
2025
|
-
console.log(`\u2713 Cursor hooks installed at ${hooksDisplay}`);
|
|
2026
|
-
console.log(`\u2713 Monitor config saved to ${configRel}`);
|
|
2027
|
-
console.log("");
|
|
2028
|
-
console.log(
|
|
2029
|
-
"Monitoring is now active. Restart Cursor for the new hooks to take effect."
|
|
2030
|
-
);
|
|
2031
|
-
console.log(`View activity at: ${getBaseUrl()}/dashboard`);
|
|
2032
|
-
console.log("");
|
|
2033
|
-
console.log(
|
|
2034
|
-
`\u26A0 Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key)`
|
|
2035
|
-
);
|
|
2036
|
-
console.log("");
|
|
2037
|
-
console.log("To check status: olakai monitor status --tool cursor");
|
|
2038
|
-
console.log("To disable: olakai monitor disable --tool cursor");
|
|
2039
|
-
return {
|
|
2040
|
-
agentId: agent.id,
|
|
2041
|
-
agentName: agent.name,
|
|
2042
|
-
source: CURSOR_SOURCE,
|
|
2043
|
-
monitoringEndpoint
|
|
2044
|
-
};
|
|
2045
|
-
}
|
|
2046
|
-
async function uninstallCursor(opts) {
|
|
2047
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
2048
|
-
const homeDir = opts.homeDir ?? os4.homedir();
|
|
2049
|
-
const hooksPath = getCursorHooksPath(homeDir);
|
|
2050
|
-
const existingHooks = readJsonFileTolerant(hooksPath);
|
|
2051
|
-
if (existingHooks) {
|
|
2052
|
-
const cleaned = removeOlakaiCursorHooks(existingHooks);
|
|
2053
|
-
if (!cleaned.hooks || Object.keys(cleaned.hooks).length === 0) {
|
|
2054
|
-
const otherKeys = Object.keys(cleaned).filter(
|
|
2055
|
-
(k) => k !== "hooks" && k !== "version"
|
|
2056
|
-
);
|
|
2057
|
-
const onlyDefaults = otherKeys.length === 0 && (cleaned.version === 1 || cleaned.version === void 0);
|
|
2058
|
-
if (onlyDefaults) {
|
|
2059
|
-
try {
|
|
2060
|
-
fs11.unlinkSync(hooksPath);
|
|
2061
|
-
} catch {
|
|
2062
|
-
}
|
|
2063
|
-
} else {
|
|
2064
|
-
writeJsonFileWithDir(hooksPath, cleaned);
|
|
2065
|
-
}
|
|
2066
|
-
} else {
|
|
2067
|
-
writeJsonFileWithDir(hooksPath, cleaned);
|
|
2068
|
-
}
|
|
2069
|
-
console.log(
|
|
2070
|
-
`\u2713 Olakai hooks removed from ~/${path11.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`
|
|
2071
|
-
);
|
|
2072
|
-
} else {
|
|
2073
|
-
console.log("No Cursor hooks file found.");
|
|
2074
|
-
}
|
|
2075
|
-
if (!opts.keepConfig) {
|
|
2076
|
-
const configPath = getCursorConfigPath(projectRoot);
|
|
2077
|
-
const configRel = path11.relative(projectRoot, configPath);
|
|
2078
|
-
if (deleteCursorConfig(projectRoot)) {
|
|
2079
|
-
console.log(`\u2713 Monitor config removed (${configRel})`);
|
|
2080
|
-
}
|
|
2081
|
-
} else {
|
|
2082
|
-
const configPath = getCursorConfigPath(projectRoot);
|
|
2083
|
-
const configRel = path11.relative(projectRoot, configPath);
|
|
2084
|
-
console.log(`Monitor config retained at ${configRel}`);
|
|
2085
|
-
}
|
|
2086
|
-
console.log("");
|
|
2087
|
-
console.log(
|
|
2088
|
-
"Monitoring disabled. Run 'olakai monitor init --tool cursor' to re-enable."
|
|
2089
|
-
);
|
|
2090
|
-
console.log("Restart Cursor for the change to take effect.");
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
// src/monitor/plugins/cursor/status.ts
|
|
2094
|
-
import * as fs12 from "fs";
|
|
2095
|
-
import * as os5 from "os";
|
|
2096
|
-
import * as path12 from "path";
|
|
2097
|
-
async function getCursorStatus(opts) {
|
|
2098
|
-
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
2099
|
-
const homeDir = opts?.homeDir ?? os5.homedir();
|
|
2100
|
-
const configPath = getCursorConfigPath(projectRoot);
|
|
2101
|
-
const config = loadCursorConfig(projectRoot);
|
|
2102
|
-
let hooksConfigured = false;
|
|
2103
|
-
const hooksPath = getCursorHooksPath(homeDir);
|
|
2104
|
-
if (fs12.existsSync(hooksPath)) {
|
|
2105
|
-
try {
|
|
2106
|
-
const raw = fs12.readFileSync(hooksPath, "utf-8");
|
|
2107
|
-
const parsed = JSON.parse(raw);
|
|
2108
|
-
hooksConfigured = hasOlakaiCursorHooks(parsed);
|
|
2109
|
-
} catch {
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
if (!config) {
|
|
2113
|
-
return {
|
|
2114
|
-
toolId: "cursor",
|
|
2115
|
-
configured: false,
|
|
2116
|
-
hooksConfigured,
|
|
2117
|
-
configPath,
|
|
2118
|
-
notes: hooksConfigured ? [
|
|
2119
|
-
"Cursor hooks installed but no monitor config in this workspace \u2014 run 'olakai monitor init --tool cursor' to associate it with an agent."
|
|
2120
|
-
] : []
|
|
2121
|
-
};
|
|
2122
|
-
}
|
|
2123
|
-
return {
|
|
2124
|
-
toolId: "cursor",
|
|
2125
|
-
configured: true,
|
|
2126
|
-
hooksConfigured,
|
|
2127
|
-
agentId: config.agentId,
|
|
2128
|
-
agentName: config.agentName,
|
|
2129
|
-
source: config.source,
|
|
2130
|
-
apiKeyMasked: config.apiKey.slice(0, 12) + "...",
|
|
2131
|
-
monitoringEndpoint: config.monitoringEndpoint,
|
|
2132
|
-
configuredAt: config.createdAt,
|
|
2133
|
-
configPath
|
|
2134
|
-
};
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
// src/monitor/plugins/cursor/hook.ts
|
|
2138
|
-
var SUPPORTED_CURSOR_EVENTS = /* @__PURE__ */ new Set([
|
|
2139
|
-
"beforeSubmitPrompt",
|
|
2140
|
-
"afterAgentResponse",
|
|
2141
|
-
"sessionEnd",
|
|
2142
|
-
"stop"
|
|
2143
|
-
]);
|
|
2144
|
-
function asString(value) {
|
|
2145
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2146
|
-
}
|
|
2147
|
-
function asNumber(value) {
|
|
2148
|
-
if (typeof value !== "number") return 0;
|
|
2149
|
-
if (!Number.isFinite(value)) return 0;
|
|
2150
|
-
return value;
|
|
2151
|
-
}
|
|
2152
|
-
function extractCursorTokens(payload) {
|
|
2153
|
-
return {
|
|
2154
|
-
inputTokens: asNumber(payload.input_tokens),
|
|
2155
|
-
outputTokens: asNumber(payload.output_tokens),
|
|
2156
|
-
cacheReadTokens: asNumber(payload.cache_read_tokens),
|
|
2157
|
-
cacheWriteTokens: asNumber(payload.cache_write_tokens)
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
function asStringArray(value) {
|
|
2161
|
-
if (!Array.isArray(value)) return void 0;
|
|
2162
|
-
const out = [];
|
|
2163
|
-
for (const item of value) {
|
|
2164
|
-
if (typeof item === "string" && item.trim()) out.push(item);
|
|
2165
|
-
}
|
|
2166
|
-
return out.length > 0 ? out : void 0;
|
|
2167
|
-
}
|
|
2168
|
-
function extractPromptText(payload) {
|
|
2169
|
-
if (typeof payload.prompt === "string") return payload.prompt;
|
|
2170
|
-
if (payload.prompt && typeof payload.prompt === "object" && typeof payload.prompt.text === "string") {
|
|
2171
|
-
return payload.prompt.text;
|
|
2172
|
-
}
|
|
2173
|
-
return "";
|
|
2174
|
-
}
|
|
2175
|
-
function extractResponseText(payload) {
|
|
2176
|
-
if (typeof payload.response === "string") return payload.response;
|
|
2177
|
-
if (payload.response && typeof payload.response === "object" && typeof payload.response.text === "string") {
|
|
2178
|
-
return payload.response.text;
|
|
2179
|
-
}
|
|
2180
|
-
if (typeof payload.text === "string") return payload.text;
|
|
2181
|
-
return "";
|
|
2182
|
-
}
|
|
2183
|
-
function extractAttachments(payload) {
|
|
2184
|
-
if (Array.isArray(payload.attachments)) return payload.attachments;
|
|
2185
|
-
if (payload.prompt && typeof payload.prompt === "object" && Array.isArray(payload.prompt.attachments)) {
|
|
2186
|
-
return payload.prompt.attachments;
|
|
2187
|
-
}
|
|
2188
|
-
return void 0;
|
|
2189
|
-
}
|
|
2190
|
-
function buildPairedPayload(inputs, config) {
|
|
2191
|
-
const customData = {
|
|
2192
|
-
hookEvent: "afterAgentResponse",
|
|
2193
|
-
conversationId: inputs.conversationId
|
|
2194
|
-
};
|
|
2195
|
-
if (inputs.generationId) customData.generationId = inputs.generationId;
|
|
2196
|
-
if (inputs.cursorVersion) customData.cursorVersion = inputs.cursorVersion;
|
|
2197
|
-
if (inputs.workspaceRoots) customData.workspaceRoots = inputs.workspaceRoots;
|
|
2198
|
-
if (inputs.transcriptPath) customData.transcriptPath = inputs.transcriptPath;
|
|
2199
|
-
if (inputs.attachments && inputs.attachments.length > 0) {
|
|
2200
|
-
customData.attachmentCount = inputs.attachments.length;
|
|
2201
|
-
}
|
|
2202
|
-
if (inputs.userEmail) customData.userEmail = inputs.userEmail;
|
|
2203
|
-
const inputTokens = asNumber(inputs.inputTokens);
|
|
2204
|
-
const outputTokens = asNumber(inputs.outputTokens);
|
|
2205
|
-
const cacheReadTokens = asNumber(inputs.cacheReadTokens);
|
|
2206
|
-
const cacheWriteTokens = asNumber(inputs.cacheWriteTokens);
|
|
2207
|
-
customData.inputTokens = inputTokens;
|
|
2208
|
-
customData.outputTokens = outputTokens;
|
|
2209
|
-
customData.cacheReadTokens = cacheReadTokens;
|
|
2210
|
-
customData.cacheWriteTokens = cacheWriteTokens;
|
|
2211
|
-
if (inputs.unknownFields && Object.keys(inputs.unknownFields).length > 0) {
|
|
2212
|
-
customData.unknownPayloadFields = inputs.unknownFields;
|
|
2213
|
-
}
|
|
2214
|
-
return {
|
|
2215
|
-
prompt: inputs.prompt,
|
|
2216
|
-
response: inputs.response,
|
|
2217
|
-
chatId: inputs.conversationId,
|
|
2218
|
-
source: config.source,
|
|
2219
|
-
modelName: inputs.model,
|
|
2220
|
-
tokens: inputTokens + outputTokens,
|
|
2221
|
-
customData
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
function buildOrphanPayload(inputs, config, reason = "orphan-prompt") {
|
|
2225
|
-
const base = buildPairedPayload({ ...inputs, response: "" }, config);
|
|
2226
|
-
return {
|
|
2227
|
-
...base,
|
|
2228
|
-
customData: {
|
|
2229
|
-
...base.customData,
|
|
2230
|
-
hookEvent: "sessionEnd",
|
|
2231
|
-
partial: true,
|
|
2232
|
-
partialReason: reason
|
|
2233
|
-
}
|
|
2234
|
-
};
|
|
2235
|
-
}
|
|
2236
|
-
var KNOWN_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
|
|
2237
|
-
"event",
|
|
2238
|
-
"conversation_id",
|
|
2239
|
-
"generation_id",
|
|
2240
|
-
"model",
|
|
2241
|
-
"cursor_version",
|
|
2242
|
-
"workspace_roots",
|
|
2243
|
-
"user_email",
|
|
2244
|
-
"transcript_path",
|
|
2245
|
-
"prompt",
|
|
2246
|
-
"response",
|
|
2247
|
-
"text",
|
|
2248
|
-
"attachments",
|
|
2249
|
-
"input_tokens",
|
|
2250
|
-
"output_tokens",
|
|
2251
|
-
"cache_read_tokens",
|
|
2252
|
-
"cache_write_tokens"
|
|
2253
|
-
]);
|
|
2254
|
-
function collectUnknownFields(payload) {
|
|
2255
|
-
const out = {};
|
|
2256
|
-
for (const key of Object.keys(payload)) {
|
|
2257
|
-
if (!KNOWN_PAYLOAD_KEYS.has(key)) {
|
|
2258
|
-
out[key] = payload[key];
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
2262
|
-
}
|
|
2263
|
-
function normalizeEventName(event) {
|
|
2264
|
-
const lower = event.trim();
|
|
2265
|
-
if (!lower) return null;
|
|
2266
|
-
const exact = lower;
|
|
2267
|
-
if (SUPPORTED_CURSOR_EVENTS.has(exact)) return exact;
|
|
2268
|
-
for (const known of SUPPORTED_CURSOR_EVENTS) {
|
|
2269
|
-
if (known.toLowerCase() === lower.toLowerCase()) return known;
|
|
2270
|
-
}
|
|
2271
|
-
return null;
|
|
2272
|
-
}
|
|
2273
|
-
function extractCursorMeta(payload) {
|
|
2274
|
-
return {
|
|
2275
|
-
conversationId: asString(payload.conversation_id),
|
|
2276
|
-
generationId: asString(payload.generation_id),
|
|
2277
|
-
model: asString(payload.model),
|
|
2278
|
-
cursorVersion: asString(payload.cursor_version),
|
|
2279
|
-
workspaceRoots: asStringArray(payload.workspace_roots),
|
|
2280
|
-
userEmail: asString(payload.user_email),
|
|
2281
|
-
transcriptPath: asString(payload.transcript_path)
|
|
2282
|
-
};
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
// src/monitor/plugins/cursor/pairing-state.ts
|
|
2286
|
-
import * as fs13 from "fs";
|
|
2287
|
-
import * as os6 from "os";
|
|
2288
|
-
import * as path13 from "path";
|
|
2289
|
-
var STATE_DIR_SEGMENTS2 = [".olakai", "cursor-pairings"];
|
|
2290
|
-
function getPairingsDir(homeDir) {
|
|
2291
|
-
return path13.join(homeDir, ...STATE_DIR_SEGMENTS2);
|
|
2292
|
-
}
|
|
2293
|
-
function sanitizeKeyFragment(value) {
|
|
2294
|
-
return value.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
2295
|
-
}
|
|
2296
|
-
function getPairingKey(conversationId, generationId) {
|
|
2297
|
-
const conv = sanitizeKeyFragment(conversationId);
|
|
2298
|
-
if (!generationId) return conv;
|
|
2299
|
-
return `${conv}__${sanitizeKeyFragment(generationId)}`;
|
|
2300
|
-
}
|
|
2301
|
-
function getPairingFile(conversationId, generationId, homeDir) {
|
|
2302
|
-
return path13.join(
|
|
2303
|
-
getPairingsDir(homeDir),
|
|
2304
|
-
`${getPairingKey(conversationId, generationId)}.json`
|
|
2305
|
-
);
|
|
2306
|
-
}
|
|
2307
|
-
function stashPendingPrompt(pending, homeDir = os6.homedir()) {
|
|
2308
|
-
if (!pending.conversationId) return;
|
|
2309
|
-
const dir = getPairingsDir(homeDir);
|
|
2310
|
-
try {
|
|
2311
|
-
if (!fs13.existsSync(dir)) {
|
|
2312
|
-
fs13.mkdirSync(dir, { recursive: true });
|
|
2313
|
-
}
|
|
2314
|
-
const filePath = getPairingFile(
|
|
2315
|
-
pending.conversationId,
|
|
2316
|
-
pending.generationId,
|
|
2317
|
-
homeDir
|
|
2318
|
-
);
|
|
2319
|
-
fs13.writeFileSync(
|
|
2320
|
-
filePath,
|
|
2321
|
-
JSON.stringify(pending, null, 2) + "\n",
|
|
2322
|
-
"utf-8"
|
|
2323
|
-
);
|
|
2324
|
-
} catch {
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
function takePendingPrompt(conversationId, generationId, homeDir = os6.homedir()) {
|
|
2328
|
-
if (!conversationId) return null;
|
|
2329
|
-
const candidates = [];
|
|
2330
|
-
if (generationId) {
|
|
2331
|
-
candidates.push(getPairingFile(conversationId, generationId, homeDir));
|
|
2332
|
-
}
|
|
2333
|
-
candidates.push(getPairingFile(conversationId, void 0, homeDir));
|
|
2334
|
-
for (const filePath of candidates) {
|
|
2335
|
-
let raw;
|
|
2336
|
-
try {
|
|
2337
|
-
if (!fs13.existsSync(filePath)) continue;
|
|
2338
|
-
raw = fs13.readFileSync(filePath, "utf-8");
|
|
2339
|
-
} catch {
|
|
2340
|
-
continue;
|
|
2341
|
-
}
|
|
2342
|
-
try {
|
|
2343
|
-
fs13.unlinkSync(filePath);
|
|
2344
|
-
} catch {
|
|
2345
|
-
}
|
|
2346
|
-
try {
|
|
2347
|
-
const parsed = JSON.parse(raw);
|
|
2348
|
-
if (parsed && typeof parsed.conversationId === "string") {
|
|
2349
|
-
return parsed;
|
|
2350
|
-
}
|
|
2351
|
-
} catch {
|
|
295
|
+
kind: "error",
|
|
296
|
+
code: "unknown_error",
|
|
297
|
+
message: err instanceof Error ? err.message : "Failed to parse response",
|
|
298
|
+
status: response.status
|
|
299
|
+
};
|
|
2352
300
|
}
|
|
301
|
+
return { kind: "ok", data };
|
|
2353
302
|
}
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
function listPendingPrompts(homeDir = os6.homedir()) {
|
|
2357
|
-
const dir = getPairingsDir(homeDir);
|
|
2358
|
-
if (!fs13.existsSync(dir)) return [];
|
|
2359
|
-
let files;
|
|
303
|
+
let errBody = {};
|
|
2360
304
|
try {
|
|
2361
|
-
|
|
305
|
+
errBody = await response.json();
|
|
2362
306
|
} catch {
|
|
2363
|
-
return [];
|
|
2364
|
-
}
|
|
2365
|
-
const result = [];
|
|
2366
|
-
for (const name of files) {
|
|
2367
|
-
if (!name.endsWith(".json")) continue;
|
|
2368
|
-
const filePath = path13.join(dir, name);
|
|
2369
|
-
try {
|
|
2370
|
-
const raw = fs13.readFileSync(filePath, "utf-8");
|
|
2371
|
-
const parsed = JSON.parse(raw);
|
|
2372
|
-
if (parsed && typeof parsed.conversationId === "string") {
|
|
2373
|
-
result.push(parsed);
|
|
2374
|
-
}
|
|
2375
|
-
} catch {
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
return result;
|
|
2379
|
-
}
|
|
2380
|
-
function clearPendingPrompt(conversationId, generationId, homeDir = os6.homedir()) {
|
|
2381
|
-
if (!conversationId) return;
|
|
2382
|
-
const candidates = [];
|
|
2383
|
-
if (generationId) {
|
|
2384
|
-
candidates.push(getPairingFile(conversationId, generationId, homeDir));
|
|
2385
|
-
}
|
|
2386
|
-
candidates.push(getPairingFile(conversationId, void 0, homeDir));
|
|
2387
|
-
for (const filePath of candidates) {
|
|
2388
|
-
try {
|
|
2389
|
-
if (fs13.existsSync(filePath)) {
|
|
2390
|
-
fs13.unlinkSync(filePath);
|
|
2391
|
-
}
|
|
2392
|
-
} catch {
|
|
2393
|
-
}
|
|
2394
307
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
// src/monitor/plugins/cursor/index.ts
|
|
2398
|
-
var TOOL_ID3 = "cursor";
|
|
2399
|
-
function debugLog3(label, data) {
|
|
2400
|
-
if (process.env.OLAKAI_MONITOR_DEBUG !== "1") return;
|
|
308
|
+
const code = mapErrorCode(response.status, errBody);
|
|
309
|
+
let fallbackPath = "";
|
|
2401
310
|
try {
|
|
2402
|
-
|
|
2403
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] cursor:${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}
|
|
2404
|
-
`;
|
|
2405
|
-
fs14.appendFileSync(logPath, line, "utf-8");
|
|
311
|
+
fallbackPath = new URL(url).pathname;
|
|
2406
312
|
} catch {
|
|
313
|
+
fallbackPath = url;
|
|
2407
314
|
}
|
|
315
|
+
const message = errBody.message || errBody.error || `Request to ${fallbackPath} failed with status ${response.status}`;
|
|
316
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
317
|
+
const envelope = {
|
|
318
|
+
kind: "error",
|
|
319
|
+
code,
|
|
320
|
+
message,
|
|
321
|
+
status: response.status,
|
|
322
|
+
...retryAfter !== void 0 ? { retryAfter } : {}
|
|
323
|
+
};
|
|
324
|
+
if (options.captureDetail && typeof errBody.attemptsRemaining === "number") {
|
|
325
|
+
envelope.detail = { attemptsRemaining: errBody.attemptsRemaining };
|
|
326
|
+
}
|
|
327
|
+
return envelope;
|
|
2408
328
|
}
|
|
2409
|
-
function
|
|
2410
|
-
const
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
if (
|
|
2423
|
-
|
|
2424
|
-
return null;
|
|
329
|
+
function mapErrorCode(status, body) {
|
|
330
|
+
const known = [
|
|
331
|
+
"validation_error",
|
|
332
|
+
"invalid_code",
|
|
333
|
+
"invalid_consent",
|
|
334
|
+
"user_unavailable",
|
|
335
|
+
"blocked",
|
|
336
|
+
"no_code",
|
|
337
|
+
"expired",
|
|
338
|
+
"locked",
|
|
339
|
+
"rate_limited",
|
|
340
|
+
"service_unavailable"
|
|
341
|
+
];
|
|
342
|
+
if (typeof body.code === "string" && known.includes(body.code)) {
|
|
343
|
+
return body.code;
|
|
2425
344
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
debugLog3("missing-conversation-id", { event });
|
|
2457
|
-
return null;
|
|
2458
|
-
}
|
|
2459
|
-
const projectRoot = resolveCursorProjectRoot(
|
|
2460
|
-
payload,
|
|
2461
|
-
opts.projectRoot ?? process.cwd()
|
|
2462
|
-
);
|
|
2463
|
-
if (!projectRoot) {
|
|
2464
|
-
debugLog3("config-not-found", {
|
|
2465
|
-
workspaceRoots: meta.workspaceRoots,
|
|
2466
|
-
fallbackCwd: opts.projectRoot ?? process.cwd()
|
|
2467
|
-
});
|
|
2468
|
-
takePendingPrompt(meta.conversationId, meta.generationId, homeDir);
|
|
2469
|
-
return null;
|
|
2470
|
-
}
|
|
2471
|
-
const config = loadCursorConfig(projectRoot);
|
|
2472
|
-
if (!config) {
|
|
2473
|
-
debugLog3("config-load-failed", { projectRoot });
|
|
2474
|
-
takePendingPrompt(meta.conversationId, meta.generationId, homeDir);
|
|
2475
|
-
return null;
|
|
2476
|
-
}
|
|
2477
|
-
const stashed = takePendingPrompt(
|
|
2478
|
-
meta.conversationId,
|
|
2479
|
-
meta.generationId,
|
|
2480
|
-
homeDir
|
|
2481
|
-
);
|
|
2482
|
-
const responseText = extractResponseText(payload);
|
|
2483
|
-
const promptText = stashed?.prompt ?? extractPromptText(payload);
|
|
2484
|
-
const userEmail = meta.userEmail ?? stashed?.userEmail;
|
|
2485
|
-
const model = meta.model ?? stashed?.model;
|
|
2486
|
-
const cursorVersion = meta.cursorVersion ?? stashed?.cursorVersion;
|
|
2487
|
-
const workspaceRoots = meta.workspaceRoots ?? stashed?.workspaceRoots;
|
|
2488
|
-
const transcriptPath = meta.transcriptPath ?? stashed?.transcriptPath;
|
|
2489
|
-
const attachments = extractAttachments(payload) ?? stashed?.attachments;
|
|
2490
|
-
const tokens = extractCursorTokens(payload);
|
|
2491
|
-
const unknownFields = mergeUnknownFields(
|
|
2492
|
-
collectUnknownFields(payload),
|
|
2493
|
-
stashed?.extra
|
|
2494
|
-
);
|
|
2495
|
-
const built = buildPairedPayload(
|
|
2496
|
-
{
|
|
2497
|
-
conversationId: meta.conversationId,
|
|
2498
|
-
generationId: meta.generationId ?? stashed?.generationId,
|
|
2499
|
-
prompt: promptText,
|
|
2500
|
-
response: responseText,
|
|
2501
|
-
userEmail,
|
|
2502
|
-
model,
|
|
2503
|
-
cursorVersion,
|
|
2504
|
-
workspaceRoots,
|
|
2505
|
-
transcriptPath,
|
|
2506
|
-
attachments,
|
|
2507
|
-
inputTokens: tokens.inputTokens,
|
|
2508
|
-
outputTokens: tokens.outputTokens,
|
|
2509
|
-
cacheReadTokens: tokens.cacheReadTokens,
|
|
2510
|
-
cacheWriteTokens: tokens.cacheWriteTokens,
|
|
2511
|
-
unknownFields
|
|
2512
|
-
},
|
|
2513
|
-
config
|
|
2514
|
-
);
|
|
2515
|
-
const finalPayload = userEmail ? { ...built, email: userEmail } : built;
|
|
2516
|
-
debugLog3("payload-built", finalPayload);
|
|
2517
|
-
return {
|
|
2518
|
-
payload: finalPayload,
|
|
2519
|
-
transport: {
|
|
2520
|
-
endpoint: config.monitoringEndpoint,
|
|
2521
|
-
apiKey: config.apiKey,
|
|
2522
|
-
projectRoot
|
|
2523
|
-
}
|
|
2524
|
-
};
|
|
345
|
+
if (typeof body.error === "string" && known.includes(body.error)) {
|
|
346
|
+
return body.error;
|
|
347
|
+
}
|
|
348
|
+
switch (status) {
|
|
349
|
+
case 400:
|
|
350
|
+
return "validation_error";
|
|
351
|
+
case 401:
|
|
352
|
+
return "invalid_consent";
|
|
353
|
+
case 403:
|
|
354
|
+
return "blocked";
|
|
355
|
+
case 404:
|
|
356
|
+
return "no_code";
|
|
357
|
+
case 410:
|
|
358
|
+
return "expired";
|
|
359
|
+
case 429:
|
|
360
|
+
return "rate_limited";
|
|
361
|
+
case 503:
|
|
362
|
+
return "service_unavailable";
|
|
363
|
+
default:
|
|
364
|
+
return "unknown_error";
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function buildNetworkError(err) {
|
|
368
|
+
const base = err instanceof Error ? err.message : "Network request failed";
|
|
369
|
+
let causeCode;
|
|
370
|
+
let causeMessage;
|
|
371
|
+
if (err instanceof Error && err.cause && typeof err.cause === "object") {
|
|
372
|
+
const c = err.cause;
|
|
373
|
+
if (typeof c.code === "string" && c.code.length > 0) {
|
|
374
|
+
causeCode = c.code;
|
|
2525
375
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
const orphan = pickOrphanForFlush(meta.conversationId, homeDir);
|
|
2529
|
-
if (!orphan) return null;
|
|
2530
|
-
const projectRoot = resolveCursorProjectRoot(
|
|
2531
|
-
// Synthesize a payload-like for resolution — orphan carries
|
|
2532
|
-
// the workspace_roots from the stashed beforeSubmitPrompt.
|
|
2533
|
-
{
|
|
2534
|
-
workspace_roots: orphan.workspaceRoots
|
|
2535
|
-
},
|
|
2536
|
-
opts.projectRoot ?? process.cwd()
|
|
2537
|
-
);
|
|
2538
|
-
if (!projectRoot) {
|
|
2539
|
-
debugLog3("orphan-config-not-found", {
|
|
2540
|
-
conversationId: orphan.conversationId
|
|
2541
|
-
});
|
|
2542
|
-
clearPendingPrompt(
|
|
2543
|
-
orphan.conversationId,
|
|
2544
|
-
orphan.generationId,
|
|
2545
|
-
homeDir
|
|
2546
|
-
);
|
|
2547
|
-
return null;
|
|
2548
|
-
}
|
|
2549
|
-
const config = loadCursorConfig(projectRoot);
|
|
2550
|
-
if (!config) {
|
|
2551
|
-
debugLog3("orphan-config-load-failed", { projectRoot });
|
|
2552
|
-
clearPendingPrompt(
|
|
2553
|
-
orphan.conversationId,
|
|
2554
|
-
orphan.generationId,
|
|
2555
|
-
homeDir
|
|
2556
|
-
);
|
|
2557
|
-
return null;
|
|
2558
|
-
}
|
|
2559
|
-
clearPendingPrompt(
|
|
2560
|
-
orphan.conversationId,
|
|
2561
|
-
orphan.generationId,
|
|
2562
|
-
homeDir
|
|
2563
|
-
);
|
|
2564
|
-
const built = buildOrphanPayload(
|
|
2565
|
-
{
|
|
2566
|
-
conversationId: orphan.conversationId,
|
|
2567
|
-
generationId: orphan.generationId,
|
|
2568
|
-
prompt: orphan.prompt,
|
|
2569
|
-
response: "",
|
|
2570
|
-
userEmail: orphan.userEmail,
|
|
2571
|
-
model: orphan.model,
|
|
2572
|
-
cursorVersion: orphan.cursorVersion,
|
|
2573
|
-
workspaceRoots: orphan.workspaceRoots,
|
|
2574
|
-
transcriptPath: orphan.transcriptPath,
|
|
2575
|
-
attachments: orphan.attachments,
|
|
2576
|
-
unknownFields: orphan.extra
|
|
2577
|
-
},
|
|
2578
|
-
config,
|
|
2579
|
-
event === "sessionEnd" ? "session-end" : "orphan-prompt"
|
|
2580
|
-
);
|
|
2581
|
-
const finalPayload = orphan.userEmail ? { ...built, email: orphan.userEmail } : built;
|
|
2582
|
-
return {
|
|
2583
|
-
payload: finalPayload,
|
|
2584
|
-
transport: {
|
|
2585
|
-
endpoint: config.monitoringEndpoint,
|
|
2586
|
-
apiKey: config.apiKey,
|
|
2587
|
-
projectRoot
|
|
2588
|
-
}
|
|
2589
|
-
};
|
|
376
|
+
if (typeof c.message === "string" && c.message.length > 0) {
|
|
377
|
+
causeMessage = c.message;
|
|
2590
378
|
}
|
|
2591
379
|
}
|
|
380
|
+
const message = causeMessage && causeMessage !== base ? `${base}: ${causeMessage}` : base;
|
|
381
|
+
return {
|
|
382
|
+
kind: "error",
|
|
383
|
+
code: "network_error",
|
|
384
|
+
message,
|
|
385
|
+
status: 0,
|
|
386
|
+
...causeCode ? { causeCode } : {}
|
|
387
|
+
};
|
|
2592
388
|
}
|
|
2593
|
-
function
|
|
2594
|
-
if (!
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
const pending = listPendingPrompts(homeDir);
|
|
2599
|
-
if (pending.length === 0) return null;
|
|
2600
|
-
if (conversationId) {
|
|
2601
|
-
const match = pending.find((p) => p.conversationId === conversationId);
|
|
2602
|
-
if (match) return match;
|
|
2603
|
-
}
|
|
2604
|
-
return pending.sort(
|
|
2605
|
-
(a, b) => (a.stashedAt || "").localeCompare(b.stashedAt || "")
|
|
2606
|
-
)[0];
|
|
2607
|
-
}
|
|
2608
|
-
var cursorPlugin = {
|
|
2609
|
-
id: TOOL_ID3,
|
|
2610
|
-
displayName: "Cursor",
|
|
2611
|
-
install(opts) {
|
|
2612
|
-
return installCursor(opts);
|
|
2613
|
-
},
|
|
2614
|
-
uninstall(opts) {
|
|
2615
|
-
return uninstallCursor(opts);
|
|
2616
|
-
},
|
|
2617
|
-
status(opts) {
|
|
2618
|
-
return getCursorStatus(opts);
|
|
2619
|
-
},
|
|
2620
|
-
handleHook(eventName, payloadJson, opts) {
|
|
2621
|
-
return handleCursorHook(eventName, payloadJson, opts);
|
|
2622
|
-
},
|
|
2623
|
-
async detectInstalled() {
|
|
2624
|
-
try {
|
|
2625
|
-
if (fs14.existsSync(getCursorUserDir())) return true;
|
|
2626
|
-
return whichCursorOnPath();
|
|
2627
|
-
} catch {
|
|
2628
|
-
return false;
|
|
2629
|
-
}
|
|
389
|
+
function parseRetryAfter(header) {
|
|
390
|
+
if (!header) return void 0;
|
|
391
|
+
const asNumber = Number(header);
|
|
392
|
+
if (Number.isFinite(asNumber) && asNumber > 0) {
|
|
393
|
+
return Math.floor(asNumber);
|
|
2630
394
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
const sep = process.platform === "win32" ? ";" : ":";
|
|
2636
|
-
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
2637
|
-
for (const dir of pathEnv.split(sep)) {
|
|
2638
|
-
for (const ext of exts) {
|
|
2639
|
-
const candidate = `${dir}/cursor${ext.toLowerCase()}`;
|
|
2640
|
-
try {
|
|
2641
|
-
if (fs14.existsSync(candidate)) return true;
|
|
2642
|
-
} catch {
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
395
|
+
const asDate = Date.parse(header);
|
|
396
|
+
if (Number.isFinite(asDate)) {
|
|
397
|
+
const seconds = Math.floor((asDate - Date.now()) / 1e3);
|
|
398
|
+
return seconds > 0 ? seconds : void 0;
|
|
2645
399
|
}
|
|
2646
|
-
return
|
|
400
|
+
return void 0;
|
|
2647
401
|
}
|
|
2648
|
-
registerPlugin(cursorPlugin);
|
|
2649
402
|
|
|
2650
403
|
// src/monitor/detect-all.ts
|
|
404
|
+
import * as fs from "fs";
|
|
405
|
+
import * as path from "path";
|
|
2651
406
|
async function detectInstalledTools(projectRoot = process.cwd()) {
|
|
2652
407
|
const detected = [];
|
|
2653
408
|
for (const plugin of listPlugins()) {
|
|
@@ -2667,7 +422,7 @@ async function detectInstalledTools(projectRoot = process.cwd()) {
|
|
|
2667
422
|
}
|
|
2668
423
|
function describeDetection(plugin, projectRoot) {
|
|
2669
424
|
try {
|
|
2670
|
-
if (
|
|
425
|
+
if (fs.existsSync(getMonitorConfigPath(projectRoot, plugin.id))) {
|
|
2671
426
|
return `existing config at .olakai/monitor-${plugin.id}.json`;
|
|
2672
427
|
}
|
|
2673
428
|
} catch {
|
|
@@ -2675,14 +430,14 @@ function describeDetection(plugin, projectRoot) {
|
|
|
2675
430
|
switch (plugin.id) {
|
|
2676
431
|
case "claude-code": {
|
|
2677
432
|
try {
|
|
2678
|
-
if (
|
|
433
|
+
if (fs.existsSync(getLegacyClaudeMonitorConfigPath(projectRoot))) {
|
|
2679
434
|
return "legacy config at .claude/olakai-monitor.json";
|
|
2680
435
|
}
|
|
2681
436
|
} catch {
|
|
2682
437
|
}
|
|
2683
438
|
try {
|
|
2684
|
-
const settings =
|
|
2685
|
-
if (
|
|
439
|
+
const settings = path.join(projectRoot, ".claude", "settings.json");
|
|
440
|
+
if (fs.existsSync(settings)) {
|
|
2686
441
|
return "found .claude/settings.json";
|
|
2687
442
|
}
|
|
2688
443
|
} catch {
|
|
@@ -2691,10 +446,10 @@ function describeDetection(plugin, projectRoot) {
|
|
|
2691
446
|
}
|
|
2692
447
|
case "codex": {
|
|
2693
448
|
try {
|
|
2694
|
-
if (
|
|
449
|
+
if (fs.existsSync(getCodexConfigPath())) {
|
|
2695
450
|
return "found ~/.codex/config.toml";
|
|
2696
451
|
}
|
|
2697
|
-
if (
|
|
452
|
+
if (fs.existsSync(getCodexHomeDir())) {
|
|
2698
453
|
return "found ~/.codex/";
|
|
2699
454
|
}
|
|
2700
455
|
} catch {
|
|
@@ -2712,15 +467,6 @@ function describeDetection(plugin, projectRoot) {
|
|
|
2712
467
|
}
|
|
2713
468
|
}
|
|
2714
469
|
|
|
2715
|
-
// src/monitor/install.ts
|
|
2716
|
-
async function runMonitorInstall(toolId, opts = {}) {
|
|
2717
|
-
const plugin = getPlugin(toolId);
|
|
2718
|
-
return plugin.install({
|
|
2719
|
-
projectRoot: opts.projectRoot ?? process.cwd(),
|
|
2720
|
-
interactive: opts.interactive ?? true
|
|
2721
|
-
});
|
|
2722
|
-
}
|
|
2723
|
-
|
|
2724
470
|
// src/lib/branding.ts
|
|
2725
471
|
var RESET = "\x1B[0m";
|
|
2726
472
|
var CYAN = "\x1B[36m";
|
|
@@ -2733,7 +479,7 @@ function colorize(text, color) {
|
|
|
2733
479
|
function printLogoHeader() {
|
|
2734
480
|
if (!process.stdout.isTTY) return;
|
|
2735
481
|
const mark = colorize("\u25C9", CYAN);
|
|
2736
|
-
const tagline = colorize("Enterprise AI
|
|
482
|
+
const tagline = colorize("Enterprise AI Adoption, ROI and Governance", DIM);
|
|
2737
483
|
console.log("");
|
|
2738
484
|
console.log(` ${mark} olakai`);
|
|
2739
485
|
console.log(` ${tagline}`);
|
|
@@ -2785,7 +531,9 @@ async function initCommand(options) {
|
|
|
2785
531
|
`Already authenticated as ${who} on ${where} (profile: ${profileName}). Re-authenticate? [y/N]: `
|
|
2786
532
|
);
|
|
2787
533
|
if (answer.trim().toLowerCase() !== "y") {
|
|
2788
|
-
console.log(
|
|
534
|
+
console.log(
|
|
535
|
+
"Keeping existing credentials. Run 'olakai logout' to sign out."
|
|
536
|
+
);
|
|
2789
537
|
return;
|
|
2790
538
|
}
|
|
2791
539
|
optedReAuth = true;
|
|
@@ -2911,7 +659,7 @@ async function resolveHost(profileName, options, interactive, forceHostPrompt =
|
|
|
2911
659
|
}
|
|
2912
660
|
const keep = await promptUser(
|
|
2913
661
|
`
|
|
2914
|
-
Connect to ${existing.host}? [Y/n] (or pick a different
|
|
662
|
+
Connect to ${existing.host}? [Y/n] (or pick a different domain such as on-prem): `
|
|
2915
663
|
);
|
|
2916
664
|
if (keep.trim().toLowerCase() !== "n") {
|
|
2917
665
|
return existing.host;
|
|
@@ -2937,12 +685,16 @@ Connect to ${existing.host}? [Y/n] (or pick a different workspace): `
|
|
|
2937
685
|
if (idx === PRESET_HOSTS.length + 1) {
|
|
2938
686
|
return promptForCustomHost();
|
|
2939
687
|
}
|
|
2940
|
-
console.error(
|
|
688
|
+
console.error(
|
|
689
|
+
"Invalid choice. Re-run 'olakai init' and pick a valid option."
|
|
690
|
+
);
|
|
2941
691
|
throw new InitAbortedError("", 1);
|
|
2942
692
|
}
|
|
2943
693
|
async function promptForCustomHost() {
|
|
2944
694
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2945
|
-
const raw = await promptUser(
|
|
695
|
+
const raw = await promptUser(
|
|
696
|
+
"On-prem URL or hostname (e.g. olakai.acme.com): "
|
|
697
|
+
);
|
|
2946
698
|
const normalized = normalizeHost(raw.trim());
|
|
2947
699
|
if (normalized) {
|
|
2948
700
|
return normalized;
|
|
@@ -2982,7 +734,7 @@ async function resolveEmail(options, interactive) {
|
|
|
2982
734
|
);
|
|
2983
735
|
}
|
|
2984
736
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2985
|
-
const raw = await promptUser("
|
|
737
|
+
const raw = await promptUser("Enter your primary work email address: ");
|
|
2986
738
|
const trimmed = raw.trim();
|
|
2987
739
|
if (EMAIL_REGEX.test(trimmed)) {
|
|
2988
740
|
return trimmed;
|
|
@@ -2995,10 +747,12 @@ async function runOtpFlow(email, profileName, ctx) {
|
|
|
2995
747
|
const expiryMin = Math.max(1, Math.round(ctx.expiresIn / 60));
|
|
2996
748
|
console.log(
|
|
2997
749
|
`
|
|
2998
|
-
We sent a 6-digit code to ${ctx.emailObfuscated} (expires in ${expiryMin} min).`
|
|
750
|
+
We sent a 6-digit code to your email ${ctx.emailObfuscated} (expires in ${expiryMin} min).`
|
|
2999
751
|
);
|
|
3000
752
|
for (; ; ) {
|
|
3001
|
-
const code = await promptUser(
|
|
753
|
+
const code = await promptUser(
|
|
754
|
+
"Please enter the 6-digit code you received: "
|
|
755
|
+
);
|
|
3002
756
|
const trimmed = code.trim();
|
|
3003
757
|
if (!OTP_REGEX.test(trimmed)) {
|
|
3004
758
|
console.log("Codes are 6 digits. Try again.");
|
|
@@ -3123,7 +877,9 @@ function handleHandshakeError(err) {
|
|
|
3123
877
|
break;
|
|
3124
878
|
}
|
|
3125
879
|
case "service_unavailable":
|
|
3126
|
-
console.error(
|
|
880
|
+
console.error(
|
|
881
|
+
"Olakai is temporarily unavailable. Try again in a minute."
|
|
882
|
+
);
|
|
3127
883
|
break;
|
|
3128
884
|
case "network_error":
|
|
3129
885
|
console.error(
|
|
@@ -3139,9 +895,7 @@ async function offerMonitorSetup(options, interactive) {
|
|
|
3139
895
|
if (options.skipMonitor) return;
|
|
3140
896
|
const detected = await detectInstalledTools(process.cwd());
|
|
3141
897
|
if (detected.length === 0) {
|
|
3142
|
-
console.log(
|
|
3143
|
-
"\nNo coding agents detected in this workspace."
|
|
3144
|
-
);
|
|
898
|
+
console.log("\nNo coding agents detected in this workspace.");
|
|
3145
899
|
console.log(
|
|
3146
900
|
"Install Claude Code, Codex, or Cursor and run 'olakai monitor init' to start tracking."
|
|
3147
901
|
);
|
|
@@ -3173,9 +927,7 @@ Set up monitoring for ${tool}? (${reason}) [Y/n]: `
|
|
|
3173
927
|
} catch (err) {
|
|
3174
928
|
const message = err instanceof Error ? err.message : String(err);
|
|
3175
929
|
console.error(`${tool} setup failed: ${message}`);
|
|
3176
|
-
console.error(
|
|
3177
|
-
`Run 'olakai monitor init --tool ${tool}' to retry.`
|
|
3178
|
-
);
|
|
930
|
+
console.error(`Run 'olakai monitor init --tool ${tool}' to retry.`);
|
|
3179
931
|
}
|
|
3180
932
|
}
|
|
3181
933
|
}
|
|
@@ -3243,6 +995,28 @@ function formatAgentDetail(agent) {
|
|
|
3243
995
|
}
|
|
3244
996
|
}
|
|
3245
997
|
}
|
|
998
|
+
function formatMineTable(agents) {
|
|
999
|
+
if (agents.length === 0) {
|
|
1000
|
+
console.log("You haven't created any agents.");
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const headers = ["NAME", "SOURCE", "AGENT ID", "CREATED", "API KEY"];
|
|
1004
|
+
const rows = agents.map((agent) => [
|
|
1005
|
+
agent.name.slice(0, 30),
|
|
1006
|
+
agent.source,
|
|
1007
|
+
agent.id.slice(0, 12) + "...",
|
|
1008
|
+
agent.createdAt ? agent.createdAt.slice(0, 10) : "-",
|
|
1009
|
+
agent.apiKey ? agent.apiKey.isActive ? "Active" : "Inactive" : "None"
|
|
1010
|
+
]);
|
|
1011
|
+
const widths = headers.map(
|
|
1012
|
+
(h, i) => Math.max(h.length, ...rows.map((r) => r[i].length))
|
|
1013
|
+
);
|
|
1014
|
+
console.log(headers.map((h, i) => h.padEnd(widths[i])).join(" "));
|
|
1015
|
+
console.log(widths.map((w) => "-".repeat(w)).join(" "));
|
|
1016
|
+
for (const row of rows) {
|
|
1017
|
+
console.log(row.map((cell, i) => cell.padEnd(widths[i])).join(" "));
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
3246
1020
|
async function listCommand(options) {
|
|
3247
1021
|
try {
|
|
3248
1022
|
const agents = await listAgents({ includeKpis: options.includeKpis });
|
|
@@ -3352,13 +1126,85 @@ async function deleteCommand(id, options) {
|
|
|
3352
1126
|
process.exit(1);
|
|
3353
1127
|
}
|
|
3354
1128
|
}
|
|
1129
|
+
async function mineCommand(options) {
|
|
1130
|
+
try {
|
|
1131
|
+
let source;
|
|
1132
|
+
if (options.source !== void 0) {
|
|
1133
|
+
if (!isToolId(options.source)) {
|
|
1134
|
+
console.error(
|
|
1135
|
+
`Unknown --source "${options.source}". Supported coding-agent sources: ${TOOL_IDS.join(", ")}`
|
|
1136
|
+
);
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1139
|
+
source = getToolSource(options.source);
|
|
1140
|
+
}
|
|
1141
|
+
const agents = await listMyAgents(source ? { source } : void 0);
|
|
1142
|
+
if (options.json) {
|
|
1143
|
+
console.log(JSON.stringify(agents, null, 2));
|
|
1144
|
+
} else {
|
|
1145
|
+
formatMineTable(agents);
|
|
1146
|
+
}
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
console.error(
|
|
1149
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1150
|
+
);
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
async function archiveCommand(id, options) {
|
|
1155
|
+
try {
|
|
1156
|
+
const archived = !options.unarchive;
|
|
1157
|
+
const agent = await updateAgent(id, { archived });
|
|
1158
|
+
if (options.json) {
|
|
1159
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
1160
|
+
} else {
|
|
1161
|
+
console.log(
|
|
1162
|
+
archived ? `Agent ${id} archived.` : `Agent ${id} unarchived.`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
console.error(
|
|
1167
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1168
|
+
);
|
|
1169
|
+
process.exit(1);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async function renameCommand(id, name, options) {
|
|
1173
|
+
try {
|
|
1174
|
+
if (!name || !name.trim()) {
|
|
1175
|
+
console.error("Error: a non-empty <name> is required");
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
const agent = await updateAgent(id, { name: name.trim() });
|
|
1179
|
+
if (options.json) {
|
|
1180
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
1181
|
+
} else {
|
|
1182
|
+
console.log(`Agent ${id} renamed to "${agent.name}".`);
|
|
1183
|
+
}
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
console.error(
|
|
1186
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1187
|
+
);
|
|
1188
|
+
process.exit(1);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
3355
1191
|
function registerAgentsCommand(program2) {
|
|
3356
1192
|
const agents = program2.command("agents").description("Manage agents");
|
|
3357
1193
|
agents.command("list").description("List all agents").option("--json", "Output as JSON").option("--include-kpis", "Include KPI definitions").action(listCommand);
|
|
3358
1194
|
agents.command("get <id>").description("Get agent details").option("--json", "Output as JSON").action(getCommand);
|
|
3359
1195
|
agents.command("create").description("Create a new agent").requiredOption("--name <name>", "Agent name").option("--description <description>", "Agent description").option("--role <role>", "Agent role (WORKER or COORDINATOR)", "WORKER").option("--workflow <id>", "Workflow ID to assign").option("--with-api-key", "Create an API key for this agent").option("--category <category>", "Agent category").option("--json", "Output as JSON").action(createCommand);
|
|
3360
1196
|
agents.command("update <id>").description("Update an agent").option("--name <name>", "Agent name").option("--description <description>", "Agent description").option("--role <role>", "Agent role (WORKER or COORDINATOR)").option("--workflow <id>", "Workflow ID to assign").option("--category <category>", "Agent category").option("--json", "Output as JSON").action(updateCommand);
|
|
3361
|
-
agents.command("delete <id>").description(
|
|
1197
|
+
agents.command("delete <id>").description(
|
|
1198
|
+
"Delete an agent (account-wide). Owner or ADMIN only \u2014 non-owners get a permission error."
|
|
1199
|
+
).option("--force", "Skip confirmation").action(deleteCommand);
|
|
1200
|
+
agents.command("mine").description(
|
|
1201
|
+
"List agents you created across your whole account (the account lens, cross-machine). For what's installed on THIS machine, use 'olakai monitor list'."
|
|
1202
|
+
).option(
|
|
1203
|
+
"--source <source>",
|
|
1204
|
+
`Filter to a coding-agent source (${TOOL_IDS.join("|")})`
|
|
1205
|
+
).option("--json", "Output as JSON").action(mineCommand);
|
|
1206
|
+
agents.command("archive <id>").description("Archive an agent account-wide (soft delete). Owner or ADMIN only.").option("--unarchive", "Restore an archived agent instead").option("--json", "Output as JSON").action(archiveCommand);
|
|
1207
|
+
agents.command("rename <id> <name>").description("Rename an agent account-wide. Owner or ADMIN only.").option("--json", "Output as JSON").action(renameCommand);
|
|
3362
1208
|
}
|
|
3363
1209
|
|
|
3364
1210
|
// src/commands/workflows.ts
|
|
@@ -4446,7 +2292,7 @@ function registerActivityCommand(program2) {
|
|
|
4446
2292
|
}
|
|
4447
2293
|
|
|
4448
2294
|
// src/commands/monitor.ts
|
|
4449
|
-
import * as
|
|
2295
|
+
import * as fs2 from "fs";
|
|
4450
2296
|
function readStdin(timeoutMs = 3e3) {
|
|
4451
2297
|
return new Promise((resolve) => {
|
|
4452
2298
|
if (process.stdin.isTTY) {
|
|
@@ -4534,7 +2380,7 @@ async function statusCommand(options) {
|
|
|
4534
2380
|
return;
|
|
4535
2381
|
}
|
|
4536
2382
|
if (plugin.id === "claude-code") {
|
|
4537
|
-
const { printClaudeCodeStatus } = await import("./status-
|
|
2383
|
+
const { printClaudeCodeStatus } = await import("./status-IZCIMES2.js");
|
|
4538
2384
|
await printClaudeCodeStatus({ projectRoot: process.cwd() });
|
|
4539
2385
|
return;
|
|
4540
2386
|
}
|
|
@@ -4568,6 +2414,7 @@ async function disableCommand(options) {
|
|
|
4568
2414
|
);
|
|
4569
2415
|
}
|
|
4570
2416
|
}
|
|
2417
|
+
const registryRoot = findConfiguredWorkspace(process.cwd(), TOOL_IDS);
|
|
4571
2418
|
await runPluginAction(
|
|
4572
2419
|
plugin,
|
|
4573
2420
|
() => plugin.uninstall({
|
|
@@ -4575,8 +2422,14 @@ async function disableCommand(options) {
|
|
|
4575
2422
|
keepConfig: options.keepConfig
|
|
4576
2423
|
})
|
|
4577
2424
|
);
|
|
2425
|
+
if (registryRoot && !options.keepConfig) {
|
|
2426
|
+
try {
|
|
2427
|
+
removeEntry(registryRoot, toolId);
|
|
2428
|
+
} catch {
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
4578
2431
|
if (agentIdToDelete) {
|
|
4579
|
-
const { deleteAgent: deleteAgent2 } = await import("./api-
|
|
2432
|
+
const { deleteAgent: deleteAgent2 } = await import("./api-JBVSHCKY.js");
|
|
4580
2433
|
try {
|
|
4581
2434
|
await deleteAgent2(agentIdToDelete);
|
|
4582
2435
|
console.log(`\u2713 Remote agent ${agentIdToDelete} deleted.`);
|
|
@@ -4592,6 +2445,49 @@ async function disableCommand(options) {
|
|
|
4592
2445
|
}
|
|
4593
2446
|
}
|
|
4594
2447
|
}
|
|
2448
|
+
async function listCommand6(options) {
|
|
2449
|
+
try {
|
|
2450
|
+
reconcileCurrentWorkspace(process.cwd());
|
|
2451
|
+
} catch {
|
|
2452
|
+
}
|
|
2453
|
+
const registry = readRegistry();
|
|
2454
|
+
if (options.json) {
|
|
2455
|
+
console.log(JSON.stringify(registry.workspaces, null, 2));
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
console.log(formatRegistryTable(registry));
|
|
2459
|
+
}
|
|
2460
|
+
async function doctorCommand(options) {
|
|
2461
|
+
const { runDoctor, printDoctorResult, exitCodeForStatus } = await import("./doctor-27VDFNP7.js");
|
|
2462
|
+
let tool;
|
|
2463
|
+
if (!options.all) {
|
|
2464
|
+
tool = await resolveToolFromOptions(options.tool, "status");
|
|
2465
|
+
}
|
|
2466
|
+
const result = await runDoctor({
|
|
2467
|
+
tool,
|
|
2468
|
+
all: options.all,
|
|
2469
|
+
fix: options.fix,
|
|
2470
|
+
json: options.json,
|
|
2471
|
+
recreateMissing: options.recreateMissing,
|
|
2472
|
+
interactive: isInteractive()
|
|
2473
|
+
});
|
|
2474
|
+
printDoctorResult(result, Boolean(options.json));
|
|
2475
|
+
process.exitCode = exitCodeForStatus(result.report.overall);
|
|
2476
|
+
}
|
|
2477
|
+
async function repairCommand(options) {
|
|
2478
|
+
const tool = await resolveToolFromOptions(options.tool, "init");
|
|
2479
|
+
const { runRepair, formatRepairResult, exitCodeForRepair } = await import("./repair-WSBWAW2B.js");
|
|
2480
|
+
const result = await runRepair({
|
|
2481
|
+
tool,
|
|
2482
|
+
interactive: isInteractive()
|
|
2483
|
+
});
|
|
2484
|
+
if (options.json) {
|
|
2485
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2486
|
+
} else {
|
|
2487
|
+
console.log(formatRepairResult(result));
|
|
2488
|
+
}
|
|
2489
|
+
process.exitCode = exitCodeForRepair(result);
|
|
2490
|
+
}
|
|
4595
2491
|
async function runPluginAction(plugin, fn) {
|
|
4596
2492
|
try {
|
|
4597
2493
|
return await fn();
|
|
@@ -4640,7 +2536,7 @@ function debugInvalidToolFlag(value, event) {
|
|
|
4640
2536
|
{ event, tool: value ?? null }
|
|
4641
2537
|
)}
|
|
4642
2538
|
`;
|
|
4643
|
-
|
|
2539
|
+
fs2.appendFileSync(logPath, line, "utf-8");
|
|
4644
2540
|
} catch {
|
|
4645
2541
|
}
|
|
4646
2542
|
}
|
|
@@ -4649,10 +2545,10 @@ async function postMonitoringPayload(result) {
|
|
|
4649
2545
|
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
4650
2546
|
const debug = process.env.OLAKAI_MONITOR_DEBUG === "1";
|
|
4651
2547
|
const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;
|
|
4652
|
-
const
|
|
2548
|
+
const log = (event, data) => {
|
|
4653
2549
|
if (!debug) return;
|
|
4654
2550
|
try {
|
|
4655
|
-
|
|
2551
|
+
fs2.appendFileSync(
|
|
4656
2552
|
logPath,
|
|
4657
2553
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] dispatcher/${event}: ${JSON.stringify(data)}
|
|
4658
2554
|
`,
|
|
@@ -4662,7 +2558,7 @@ async function postMonitoringPayload(result) {
|
|
|
4662
2558
|
}
|
|
4663
2559
|
};
|
|
4664
2560
|
try {
|
|
4665
|
-
|
|
2561
|
+
log("posting", {
|
|
4666
2562
|
endpoint: result.transport.endpoint,
|
|
4667
2563
|
apiKeyPresent: Boolean(result.transport.apiKey),
|
|
4668
2564
|
apiKeyPrefix: result.transport.apiKey ? result.transport.apiKey.slice(0, 8) + "..." : null,
|
|
@@ -4682,9 +2578,9 @@ async function postMonitoringPayload(result) {
|
|
|
4682
2578
|
bodyPreview = (await response.text()).slice(0, 500);
|
|
4683
2579
|
} catch {
|
|
4684
2580
|
}
|
|
4685
|
-
|
|
2581
|
+
log("posted", { status: response.status, bodyPreview });
|
|
4686
2582
|
} catch (err) {
|
|
4687
|
-
|
|
2583
|
+
log("post-error", {
|
|
4688
2584
|
name: err instanceof Error ? err.name : "unknown",
|
|
4689
2585
|
message: err instanceof Error ? err.message : String(err)
|
|
4690
2586
|
});
|
|
@@ -4709,6 +2605,27 @@ function registerMonitorCommand(program2) {
|
|
|
4709
2605
|
"--tool <tool>",
|
|
4710
2606
|
`Tool to inspect (${TOOL_IDS.join("|")}). Prompts when omitted in interactive mode.`
|
|
4711
2607
|
).option("--json", "Output as JSON").action(statusCommand);
|
|
2608
|
+
monitor.command("list").description(
|
|
2609
|
+
"List every workspace monitored on this machine (the machine lens)"
|
|
2610
|
+
).option("--json", "Output the raw registry entries as JSON").action(listCommand6);
|
|
2611
|
+
monitor.command("doctor").description(
|
|
2612
|
+
"Diagnose (and optionally repair) monitoring health for this workspace or the whole machine"
|
|
2613
|
+
).option(
|
|
2614
|
+
"--tool <tool>",
|
|
2615
|
+
`Tool to diagnose (${TOOL_IDS.join("|")}). Prompts when omitted in interactive mode. Ignored with --all.`
|
|
2616
|
+
).option(
|
|
2617
|
+
"--all",
|
|
2618
|
+
"Diagnose every workspace monitored on this machine (the machine lens), not just the current one"
|
|
2619
|
+
).option("--fix", "Attempt to repair fixable failures (idempotent, best-effort)").option(
|
|
2620
|
+
"--recreate-missing",
|
|
2621
|
+
"With --fix, also recreate a self-monitor agent that no longer exists on the backend (provisions a NEW agent). Off by default; 'olakai monitor repair' is the heavier alternative."
|
|
2622
|
+
).option("--json", "Output the structured diagnostic results as JSON").action(doctorCommand);
|
|
2623
|
+
monitor.command("repair").description(
|
|
2624
|
+
"Forcefully re-initialize monitoring for this workspace (re-merge hooks, re-link/recreate the agent if needed) while preserving agent linkage"
|
|
2625
|
+
).option(
|
|
2626
|
+
"--tool <tool>",
|
|
2627
|
+
`Tool to repair (${TOOL_IDS.join("|")}). Prompts when omitted in interactive mode.`
|
|
2628
|
+
).option("--json", "Output the structured repair result as JSON").action(repairCommand);
|
|
4712
2629
|
monitor.command("disable").description("Remove Olakai monitoring from this workspace").option(
|
|
4713
2630
|
"--tool <tool>",
|
|
4714
2631
|
`Tool to disable (${TOOL_IDS.join("|")}). Prompts when omitted in interactive mode.`
|