olakai-cli 0.2.0 → 0.2.1
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/index.js +154 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2183,8 +2183,8 @@ function registerActivityCommand(program2) {
|
|
|
2183
2183
|
}
|
|
2184
2184
|
|
|
2185
2185
|
// src/commands/monitor.ts
|
|
2186
|
-
import * as
|
|
2187
|
-
import * as
|
|
2186
|
+
import * as fs3 from "fs";
|
|
2187
|
+
import * as path3 from "path";
|
|
2188
2188
|
import * as readline from "readline";
|
|
2189
2189
|
|
|
2190
2190
|
// src/commands/monitor-transcript.ts
|
|
@@ -2236,6 +2236,7 @@ function parseTranscript(raw) {
|
|
|
2236
2236
|
const lines = raw.split("\n");
|
|
2237
2237
|
let lastUserText = "";
|
|
2238
2238
|
let lastUserTimestamp = NaN;
|
|
2239
|
+
let lastUserTimestampRaw;
|
|
2239
2240
|
let lastAssistantText = "";
|
|
2240
2241
|
let lastAssistantTimestamp = NaN;
|
|
2241
2242
|
let lastAssistantModel = null;
|
|
@@ -2259,6 +2260,7 @@ function parseTranscript(raw) {
|
|
|
2259
2260
|
lastUserText = text;
|
|
2260
2261
|
lastUserTimestamp = parseTimestamp(parsed.timestamp);
|
|
2261
2262
|
currentTurnUserTimestamp = lastUserTimestamp;
|
|
2263
|
+
lastUserTimestampRaw = typeof parsed.timestamp === "string" && parsed.timestamp ? parsed.timestamp : void 0;
|
|
2262
2264
|
} else if (parsed.type === "assistant" && parsed.message) {
|
|
2263
2265
|
const text = extractTextContent(parsed.message.content);
|
|
2264
2266
|
numTurns += 1;
|
|
@@ -2297,9 +2299,80 @@ function parseTranscript(raw) {
|
|
|
2297
2299
|
if (skill) {
|
|
2298
2300
|
result.skill = skill;
|
|
2299
2301
|
}
|
|
2302
|
+
if (lastUserTimestampRaw) {
|
|
2303
|
+
result.userTurnTimestamp = lastUserTimestampRaw;
|
|
2304
|
+
}
|
|
2300
2305
|
return result;
|
|
2301
2306
|
}
|
|
2302
2307
|
|
|
2308
|
+
// src/commands/monitor-state.ts
|
|
2309
|
+
import * as fs2 from "fs";
|
|
2310
|
+
import * as os2 from "os";
|
|
2311
|
+
import * as path2 from "path";
|
|
2312
|
+
var STATE_DIR_SEGMENTS = [".olakai", "monitor-state"];
|
|
2313
|
+
function getStateDir(homeDir) {
|
|
2314
|
+
return path2.join(homeDir, ...STATE_DIR_SEGMENTS);
|
|
2315
|
+
}
|
|
2316
|
+
function getStateFile(sessionId, homeDir) {
|
|
2317
|
+
return path2.join(getStateDir(homeDir), `${sessionId}.json`);
|
|
2318
|
+
}
|
|
2319
|
+
var debugLogger = null;
|
|
2320
|
+
function setDebugLogger(logger) {
|
|
2321
|
+
debugLogger = logger;
|
|
2322
|
+
}
|
|
2323
|
+
function log(label, data) {
|
|
2324
|
+
if (debugLogger) {
|
|
2325
|
+
try {
|
|
2326
|
+
debugLogger(label, data);
|
|
2327
|
+
} catch {
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
function loadSessionState(sessionId, homeDir = os2.homedir()) {
|
|
2332
|
+
if (!sessionId) return null;
|
|
2333
|
+
const filePath = getStateFile(sessionId, homeDir);
|
|
2334
|
+
try {
|
|
2335
|
+
if (!fs2.existsSync(filePath)) return null;
|
|
2336
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
2337
|
+
const parsed = JSON.parse(raw);
|
|
2338
|
+
if (typeof parsed?.lastUserTimestamp !== "string" || typeof parsed?.lastReportedAt !== "string" || typeof parsed?.numTurnsAtLastReport !== "number") {
|
|
2339
|
+
return null;
|
|
2340
|
+
}
|
|
2341
|
+
return {
|
|
2342
|
+
lastUserTimestamp: parsed.lastUserTimestamp,
|
|
2343
|
+
lastReportedAt: parsed.lastReportedAt,
|
|
2344
|
+
numTurnsAtLastReport: parsed.numTurnsAtLastReport
|
|
2345
|
+
};
|
|
2346
|
+
} catch (err) {
|
|
2347
|
+
log("state-load-failed", {
|
|
2348
|
+
sessionId,
|
|
2349
|
+
error: err.message
|
|
2350
|
+
});
|
|
2351
|
+
return null;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
function saveSessionState(sessionId, state, homeDir = os2.homedir()) {
|
|
2355
|
+
if (!sessionId) return;
|
|
2356
|
+
const dir = getStateDir(homeDir);
|
|
2357
|
+
const filePath = getStateFile(sessionId, homeDir);
|
|
2358
|
+
try {
|
|
2359
|
+
if (!fs2.existsSync(dir)) {
|
|
2360
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2361
|
+
}
|
|
2362
|
+
fs2.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
log("state-save-failed", {
|
|
2365
|
+
sessionId,
|
|
2366
|
+
error: err.message
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
function shouldReportTurn(existing, currentUserTimestamp) {
|
|
2371
|
+
if (!currentUserTimestamp) return true;
|
|
2372
|
+
if (!existing) return true;
|
|
2373
|
+
return existing.lastUserTimestamp !== currentUserTimestamp;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2303
2376
|
// src/commands/monitor.ts
|
|
2304
2377
|
var CLAUDE_DIR = ".claude";
|
|
2305
2378
|
var SETTINGS_FILE = "settings.json";
|
|
@@ -2361,39 +2434,55 @@ function prompt(question) {
|
|
|
2361
2434
|
function findProjectRoot(startDir) {
|
|
2362
2435
|
let dir = startDir ?? process.cwd();
|
|
2363
2436
|
while (true) {
|
|
2364
|
-
if (
|
|
2437
|
+
if (fs3.existsSync(path3.join(dir, CLAUDE_DIR))) {
|
|
2365
2438
|
return dir;
|
|
2366
2439
|
}
|
|
2367
|
-
const parent =
|
|
2440
|
+
const parent = path3.dirname(dir);
|
|
2368
2441
|
if (parent === dir) break;
|
|
2369
2442
|
dir = parent;
|
|
2370
2443
|
}
|
|
2371
2444
|
return startDir ?? process.cwd();
|
|
2372
2445
|
}
|
|
2446
|
+
function findConfiguredProjectRoot(startDir) {
|
|
2447
|
+
let dir = startDir ?? process.cwd();
|
|
2448
|
+
while (true) {
|
|
2449
|
+
if (fs3.existsSync(path3.join(dir, CLAUDE_DIR, MONITOR_CONFIG_FILE))) {
|
|
2450
|
+
return dir;
|
|
2451
|
+
}
|
|
2452
|
+
const parent = path3.dirname(dir);
|
|
2453
|
+
if (parent === dir) break;
|
|
2454
|
+
dir = parent;
|
|
2455
|
+
}
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
function resolveProjectRootFromPayload(eventData, fallbackCwd) {
|
|
2459
|
+
const payloadCwd = typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : fallbackCwd;
|
|
2460
|
+
return findConfiguredProjectRoot(payloadCwd);
|
|
2461
|
+
}
|
|
2373
2462
|
function getClaudeDir(projectRoot) {
|
|
2374
|
-
return
|
|
2463
|
+
return path3.join(projectRoot, CLAUDE_DIR);
|
|
2375
2464
|
}
|
|
2376
2465
|
function getSettingsPath(projectRoot) {
|
|
2377
|
-
return
|
|
2466
|
+
return path3.join(getClaudeDir(projectRoot), SETTINGS_FILE);
|
|
2378
2467
|
}
|
|
2379
2468
|
function getMonitorConfigPath(projectRoot) {
|
|
2380
|
-
return
|
|
2469
|
+
return path3.join(getClaudeDir(projectRoot), MONITOR_CONFIG_FILE);
|
|
2381
2470
|
}
|
|
2382
2471
|
function readJsonFile(filePath) {
|
|
2383
2472
|
try {
|
|
2384
|
-
if (!
|
|
2385
|
-
const content =
|
|
2473
|
+
if (!fs3.existsSync(filePath)) return null;
|
|
2474
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
2386
2475
|
return JSON.parse(content);
|
|
2387
2476
|
} catch {
|
|
2388
2477
|
return null;
|
|
2389
2478
|
}
|
|
2390
2479
|
}
|
|
2391
2480
|
function writeJsonFile(filePath, data) {
|
|
2392
|
-
const dir =
|
|
2393
|
-
if (!
|
|
2394
|
-
|
|
2481
|
+
const dir = path3.dirname(filePath);
|
|
2482
|
+
if (!fs3.existsSync(dir)) {
|
|
2483
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
2395
2484
|
}
|
|
2396
|
-
|
|
2485
|
+
fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
2397
2486
|
}
|
|
2398
2487
|
function loadMonitorConfig(projectRoot) {
|
|
2399
2488
|
const root = projectRoot ?? findProjectRoot();
|
|
@@ -2433,7 +2522,7 @@ async function initCommand() {
|
|
|
2433
2522
|
}
|
|
2434
2523
|
console.log("Setting up Claude Code monitoring for this workspace...\n");
|
|
2435
2524
|
const projectRoot = process.cwd();
|
|
2436
|
-
const dirName =
|
|
2525
|
+
const dirName = path3.basename(projectRoot);
|
|
2437
2526
|
let agent;
|
|
2438
2527
|
const choice = await prompt(
|
|
2439
2528
|
"Create a new agent or use an existing one? (new/existing) [new]: "
|
|
@@ -2484,8 +2573,8 @@ Select agent (1-${agents.length}): `);
|
|
|
2484
2573
|
}
|
|
2485
2574
|
}
|
|
2486
2575
|
const claudeDir = getClaudeDir(projectRoot);
|
|
2487
|
-
if (!
|
|
2488
|
-
|
|
2576
|
+
if (!fs3.existsSync(claudeDir)) {
|
|
2577
|
+
fs3.mkdirSync(claudeDir, { recursive: true });
|
|
2489
2578
|
}
|
|
2490
2579
|
const settingsPath = getSettingsPath(projectRoot);
|
|
2491
2580
|
const existingSettings = readJsonFile(settingsPath) ?? {};
|
|
@@ -2506,7 +2595,7 @@ Select agent (1-${agents.length}): `);
|
|
|
2506
2595
|
};
|
|
2507
2596
|
const monitorConfigPath = getMonitorConfigPath(projectRoot);
|
|
2508
2597
|
writeJsonFile(monitorConfigPath, monitorConfig);
|
|
2509
|
-
|
|
2598
|
+
fs3.chmodSync(monitorConfigPath, 384);
|
|
2510
2599
|
console.log("");
|
|
2511
2600
|
console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
|
|
2512
2601
|
if (agent.apiKey?.key) {
|
|
@@ -2550,7 +2639,7 @@ function debugLog(label, data) {
|
|
|
2550
2639
|
const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;
|
|
2551
2640
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}
|
|
2552
2641
|
`;
|
|
2553
|
-
|
|
2642
|
+
fs3.appendFileSync(logPath, line, "utf-8");
|
|
2554
2643
|
} catch {
|
|
2555
2644
|
}
|
|
2556
2645
|
}
|
|
@@ -2567,7 +2656,7 @@ function extractFromTranscript(transcriptPath) {
|
|
|
2567
2656
|
if (!transcriptPath) return empty;
|
|
2568
2657
|
let raw;
|
|
2569
2658
|
try {
|
|
2570
|
-
raw =
|
|
2659
|
+
raw = fs3.readFileSync(transcriptPath, "utf-8");
|
|
2571
2660
|
} catch (err) {
|
|
2572
2661
|
debugLog("transcript-read-failed", {
|
|
2573
2662
|
transcriptPath,
|
|
@@ -2592,11 +2681,8 @@ function extractSubagentName(event) {
|
|
|
2592
2681
|
return void 0;
|
|
2593
2682
|
}
|
|
2594
2683
|
async function hookCommand(event) {
|
|
2684
|
+
setDebugLogger(debugLog);
|
|
2595
2685
|
try {
|
|
2596
|
-
const config = loadMonitorConfig();
|
|
2597
|
-
if (!config) {
|
|
2598
|
-
return;
|
|
2599
|
-
}
|
|
2600
2686
|
const stdinData = await readStdin(3e3);
|
|
2601
2687
|
debugLog("stdin-raw", stdinData);
|
|
2602
2688
|
let eventData = {};
|
|
@@ -2605,15 +2691,42 @@ async function hookCommand(event) {
|
|
|
2605
2691
|
eventData = JSON.parse(stdinData);
|
|
2606
2692
|
} catch {
|
|
2607
2693
|
debugLog("stdin-parse-failed", stdinData);
|
|
2608
|
-
return;
|
|
2609
2694
|
}
|
|
2610
2695
|
}
|
|
2611
2696
|
debugLog("event-parsed", { event, eventData });
|
|
2697
|
+
const projectRoot = resolveProjectRootFromPayload(
|
|
2698
|
+
eventData,
|
|
2699
|
+
process.cwd()
|
|
2700
|
+
);
|
|
2701
|
+
if (!projectRoot) {
|
|
2702
|
+
debugLog("config-not-found", {
|
|
2703
|
+
startDir: typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : process.cwd()
|
|
2704
|
+
});
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const config = loadMonitorConfig(projectRoot);
|
|
2708
|
+
if (!config) {
|
|
2709
|
+
debugLog("config-load-failed", { projectRoot });
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2612
2712
|
const payload = buildPayload(event, eventData, config);
|
|
2613
2713
|
if (!payload) {
|
|
2614
2714
|
return;
|
|
2615
2715
|
}
|
|
2616
2716
|
debugLog("payload-built", payload);
|
|
2717
|
+
const sessionId = typeof eventData.session_id === "string" && eventData.session_id || void 0;
|
|
2718
|
+
const extracted = extractFromTranscript(eventData.transcript_path);
|
|
2719
|
+
const userTurnTimestamp = extracted.userTurnTimestamp;
|
|
2720
|
+
if (sessionId) {
|
|
2721
|
+
const existingState = loadSessionState(sessionId);
|
|
2722
|
+
if (!shouldReportTurn(existingState, userTurnTimestamp)) {
|
|
2723
|
+
debugLog("turn-dedup-skip", {
|
|
2724
|
+
sessionId,
|
|
2725
|
+
userTurnTimestamp
|
|
2726
|
+
});
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2617
2730
|
const controller = new AbortController();
|
|
2618
2731
|
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
2619
2732
|
try {
|
|
@@ -2633,6 +2746,18 @@ async function hookCommand(event) {
|
|
|
2633
2746
|
} finally {
|
|
2634
2747
|
clearTimeout(timeoutId);
|
|
2635
2748
|
}
|
|
2749
|
+
if (sessionId && userTurnTimestamp) {
|
|
2750
|
+
saveSessionState(sessionId, {
|
|
2751
|
+
lastUserTimestamp: userTurnTimestamp,
|
|
2752
|
+
lastReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2753
|
+
numTurnsAtLastReport: extracted.numTurns
|
|
2754
|
+
});
|
|
2755
|
+
debugLog("state-saved", {
|
|
2756
|
+
sessionId,
|
|
2757
|
+
userTurnTimestamp,
|
|
2758
|
+
numTurns: extracted.numTurns
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2636
2761
|
} catch (err) {
|
|
2637
2762
|
debugLog("hook-exception", err.message);
|
|
2638
2763
|
}
|
|
@@ -2665,9 +2790,11 @@ function buildPayload(event, eventData, config) {
|
|
|
2665
2790
|
} else if (extracted.skill) {
|
|
2666
2791
|
customData.skill = extracted.skill;
|
|
2667
2792
|
}
|
|
2793
|
+
const payloadAssistant = eventData.last_assistant_message;
|
|
2794
|
+
const response = typeof payloadAssistant === "string" && payloadAssistant.trim() ? payloadAssistant : extracted.response;
|
|
2668
2795
|
return {
|
|
2669
2796
|
prompt: extracted.prompt,
|
|
2670
|
-
response
|
|
2797
|
+
response,
|
|
2671
2798
|
chatId: sessionId,
|
|
2672
2799
|
// Top-level source so the backend ExternalPromptRequestSchema
|
|
2673
2800
|
// picks it up (matches regex ^[a-z0-9_-]+$).
|
|
@@ -2774,8 +2901,8 @@ async function disableCommand(options) {
|
|
|
2774
2901
|
}
|
|
2775
2902
|
if (!options.keepConfig) {
|
|
2776
2903
|
const configPath = getMonitorConfigPath(projectRoot);
|
|
2777
|
-
if (
|
|
2778
|
-
|
|
2904
|
+
if (fs3.existsSync(configPath)) {
|
|
2905
|
+
fs3.unlinkSync(configPath);
|
|
2779
2906
|
console.log(`\u2713 Monitor config removed (${CLAUDE_DIR}/${MONITOR_CONFIG_FILE})`);
|
|
2780
2907
|
}
|
|
2781
2908
|
} else {
|