olakai-cli 0.1.15 → 0.2.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/index.js +182 -80
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -2186,6 +2186,121 @@ function registerActivityCommand(program2) {
|
|
|
2186
2186
|
import * as fs2 from "fs";
|
|
2187
2187
|
import * as path2 from "path";
|
|
2188
2188
|
import * as readline from "readline";
|
|
2189
|
+
|
|
2190
|
+
// src/commands/monitor-transcript.ts
|
|
2191
|
+
var SKILL_REGEX = /^\/([\w-]+)(?:\s|$)/;
|
|
2192
|
+
function detectSkill(userMessage) {
|
|
2193
|
+
if (typeof userMessage !== "string") return void 0;
|
|
2194
|
+
const trimmed = userMessage.trimStart();
|
|
2195
|
+
if (!trimmed) return void 0;
|
|
2196
|
+
const match = SKILL_REGEX.exec(trimmed);
|
|
2197
|
+
if (!match) return void 0;
|
|
2198
|
+
return match[1];
|
|
2199
|
+
}
|
|
2200
|
+
function extractTextContent(content) {
|
|
2201
|
+
if (typeof content === "string") return content;
|
|
2202
|
+
if (!Array.isArray(content)) return "";
|
|
2203
|
+
const parts = [];
|
|
2204
|
+
for (const block of content) {
|
|
2205
|
+
if (block?.type === "text" && typeof block.text === "string") {
|
|
2206
|
+
parts.push(block.text);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return parts.join("\n").trim();
|
|
2210
|
+
}
|
|
2211
|
+
function isMetaUserMessage(line, text) {
|
|
2212
|
+
if (line.isMeta === true) return true;
|
|
2213
|
+
if (!text) return true;
|
|
2214
|
+
return text.includes("<command-name>") || text.includes("<local-command-caveat>") || text.includes("<command-message>");
|
|
2215
|
+
}
|
|
2216
|
+
function parseTimestamp(ts) {
|
|
2217
|
+
if (typeof ts !== "string" || !ts) return NaN;
|
|
2218
|
+
return Date.parse(ts);
|
|
2219
|
+
}
|
|
2220
|
+
function isCompactionEntry(line) {
|
|
2221
|
+
if (line.type !== "system") return false;
|
|
2222
|
+
const subtype = line.subtype ?? "";
|
|
2223
|
+
return subtype === "compact_boundary" || subtype === "pre_compact" || subtype === "post_compact" || /compact/i.test(subtype);
|
|
2224
|
+
}
|
|
2225
|
+
function parseTranscript(raw) {
|
|
2226
|
+
const empty = {
|
|
2227
|
+
prompt: "",
|
|
2228
|
+
response: "",
|
|
2229
|
+
tokens: 0,
|
|
2230
|
+
inputTokens: 0,
|
|
2231
|
+
outputTokens: 0,
|
|
2232
|
+
modelName: null,
|
|
2233
|
+
numTurns: 0
|
|
2234
|
+
};
|
|
2235
|
+
if (!raw) return empty;
|
|
2236
|
+
const lines = raw.split("\n");
|
|
2237
|
+
let lastUserText = "";
|
|
2238
|
+
let lastUserTimestamp = NaN;
|
|
2239
|
+
let lastAssistantText = "";
|
|
2240
|
+
let lastAssistantTimestamp = NaN;
|
|
2241
|
+
let lastAssistantModel = null;
|
|
2242
|
+
let lastAssistantInputTokens = 0;
|
|
2243
|
+
let lastAssistantOutputTokens = 0;
|
|
2244
|
+
let numTurns = 0;
|
|
2245
|
+
let currentTurnUserTimestamp = NaN;
|
|
2246
|
+
for (const rawLine of lines) {
|
|
2247
|
+
if (!rawLine) continue;
|
|
2248
|
+
let parsed;
|
|
2249
|
+
try {
|
|
2250
|
+
parsed = JSON.parse(rawLine);
|
|
2251
|
+
} catch {
|
|
2252
|
+
continue;
|
|
2253
|
+
}
|
|
2254
|
+
if (isCompactionEntry(parsed)) continue;
|
|
2255
|
+
if (parsed.isSidechain === true) continue;
|
|
2256
|
+
if (parsed.type === "user" && parsed.message) {
|
|
2257
|
+
const text = extractTextContent(parsed.message.content);
|
|
2258
|
+
if (isMetaUserMessage(parsed, text)) continue;
|
|
2259
|
+
lastUserText = text;
|
|
2260
|
+
lastUserTimestamp = parseTimestamp(parsed.timestamp);
|
|
2261
|
+
currentTurnUserTimestamp = lastUserTimestamp;
|
|
2262
|
+
} else if (parsed.type === "assistant" && parsed.message) {
|
|
2263
|
+
const text = extractTextContent(parsed.message.content);
|
|
2264
|
+
numTurns += 1;
|
|
2265
|
+
if (text) lastAssistantText = text;
|
|
2266
|
+
if (typeof parsed.message.model === "string") {
|
|
2267
|
+
lastAssistantModel = parsed.message.model;
|
|
2268
|
+
}
|
|
2269
|
+
const usage = parsed.message.usage;
|
|
2270
|
+
if (usage) {
|
|
2271
|
+
const input = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
|
|
2272
|
+
const output = usage.output_tokens ?? 0;
|
|
2273
|
+
lastAssistantInputTokens = input;
|
|
2274
|
+
lastAssistantOutputTokens = output;
|
|
2275
|
+
}
|
|
2276
|
+
const ts = parseTimestamp(parsed.timestamp);
|
|
2277
|
+
if (!Number.isNaN(ts)) {
|
|
2278
|
+
lastAssistantTimestamp = ts;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
const result = {
|
|
2283
|
+
prompt: lastUserText,
|
|
2284
|
+
response: lastAssistantText,
|
|
2285
|
+
tokens: lastAssistantInputTokens + lastAssistantOutputTokens,
|
|
2286
|
+
inputTokens: lastAssistantInputTokens,
|
|
2287
|
+
outputTokens: lastAssistantOutputTokens,
|
|
2288
|
+
modelName: lastAssistantModel,
|
|
2289
|
+
numTurns
|
|
2290
|
+
};
|
|
2291
|
+
if (!Number.isNaN(currentTurnUserTimestamp) && !Number.isNaN(lastAssistantTimestamp) && lastAssistantTimestamp >= currentTurnUserTimestamp) {
|
|
2292
|
+
result.latencyMs = Math.round(
|
|
2293
|
+
lastAssistantTimestamp - currentTurnUserTimestamp
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
const skill = detectSkill(lastUserText);
|
|
2297
|
+
if (skill) {
|
|
2298
|
+
result.skill = skill;
|
|
2299
|
+
}
|
|
2300
|
+
return result;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
// src/commands/monitor.ts
|
|
2189
2304
|
var CLAUDE_DIR = ".claude";
|
|
2190
2305
|
var SETTINGS_FILE = "settings.json";
|
|
2191
2306
|
var MONITOR_CONFIG_FILE = "olakai-monitor.json";
|
|
@@ -2201,8 +2316,36 @@ var HOOK_DEFINITIONS = {
|
|
|
2201
2316
|
}
|
|
2202
2317
|
]
|
|
2203
2318
|
}
|
|
2319
|
+
],
|
|
2320
|
+
SubagentStop: [
|
|
2321
|
+
{
|
|
2322
|
+
matcher: "",
|
|
2323
|
+
hooks: [
|
|
2324
|
+
{
|
|
2325
|
+
type: "command",
|
|
2326
|
+
command: "olakai monitor hook subagent-stop"
|
|
2327
|
+
}
|
|
2328
|
+
]
|
|
2329
|
+
}
|
|
2204
2330
|
]
|
|
2205
2331
|
};
|
|
2332
|
+
function mergeHooksSettings(existing, definitions = HOOK_DEFINITIONS) {
|
|
2333
|
+
const merged = {
|
|
2334
|
+
...existing ?? {}
|
|
2335
|
+
};
|
|
2336
|
+
for (const [event, defaultEntries] of Object.entries(definitions)) {
|
|
2337
|
+
const existingEntries = merged[event] ?? [];
|
|
2338
|
+
const hasOlakaiHook = existingEntries.some(
|
|
2339
|
+
(e) => e.hooks.some((h) => h.command.includes(OLAKAI_HOOK_MARKER))
|
|
2340
|
+
);
|
|
2341
|
+
if (hasOlakaiHook) {
|
|
2342
|
+
merged[event] = existingEntries;
|
|
2343
|
+
} else {
|
|
2344
|
+
merged[event] = [...existingEntries, ...defaultEntries];
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return merged;
|
|
2348
|
+
}
|
|
2206
2349
|
function prompt(question) {
|
|
2207
2350
|
const rl = readline.createInterface({
|
|
2208
2351
|
input: process.stdin,
|
|
@@ -2346,16 +2489,7 @@ Select agent (1-${agents.length}): `);
|
|
|
2346
2489
|
}
|
|
2347
2490
|
const settingsPath = getSettingsPath(projectRoot);
|
|
2348
2491
|
const existingSettings = readJsonFile(settingsPath) ?? {};
|
|
2349
|
-
const mergedHooks =
|
|
2350
|
-
...existingSettings.hooks ?? {}
|
|
2351
|
-
};
|
|
2352
|
-
for (const [event, entries] of Object.entries(HOOK_DEFINITIONS)) {
|
|
2353
|
-
const existing = mergedHooks[event] ?? [];
|
|
2354
|
-
const filtered = existing.filter(
|
|
2355
|
-
(e) => !e.hooks.some((h) => h.command.includes(OLAKAI_HOOK_MARKER))
|
|
2356
|
-
);
|
|
2357
|
-
mergedHooks[event] = [...filtered, ...entries];
|
|
2358
|
-
}
|
|
2492
|
+
const mergedHooks = mergeHooksSettings(existingSettings.hooks);
|
|
2359
2493
|
const updatedSettings = {
|
|
2360
2494
|
...existingSettings,
|
|
2361
2495
|
hooks: mergedHooks
|
|
@@ -2420,22 +2554,6 @@ function debugLog(label, data) {
|
|
|
2420
2554
|
} catch {
|
|
2421
2555
|
}
|
|
2422
2556
|
}
|
|
2423
|
-
function extractTextContent(content) {
|
|
2424
|
-
if (typeof content === "string") return content;
|
|
2425
|
-
if (!Array.isArray(content)) return "";
|
|
2426
|
-
const parts = [];
|
|
2427
|
-
for (const block of content) {
|
|
2428
|
-
if (block?.type === "text" && typeof block.text === "string") {
|
|
2429
|
-
parts.push(block.text);
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
return parts.join("\n").trim();
|
|
2433
|
-
}
|
|
2434
|
-
function isMetaUserMessage(line, text) {
|
|
2435
|
-
if (line.isMeta === true) return true;
|
|
2436
|
-
if (!text) return true;
|
|
2437
|
-
return text.includes("<command-name>") || text.includes("<local-command-caveat>") || text.includes("<command-message>");
|
|
2438
|
-
}
|
|
2439
2557
|
function extractFromTranscript(transcriptPath) {
|
|
2440
2558
|
const empty = {
|
|
2441
2559
|
prompt: "",
|
|
@@ -2457,51 +2575,21 @@ function extractFromTranscript(transcriptPath) {
|
|
|
2457
2575
|
});
|
|
2458
2576
|
return empty;
|
|
2459
2577
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
} catch {
|
|
2473
|
-
continue;
|
|
2474
|
-
}
|
|
2475
|
-
if (parsed.isSidechain === true) continue;
|
|
2476
|
-
if (parsed.type === "user" && parsed.message) {
|
|
2477
|
-
const text = extractTextContent(parsed.message.content);
|
|
2478
|
-
if (isMetaUserMessage(parsed, text)) continue;
|
|
2479
|
-
lastUserText = text;
|
|
2480
|
-
} else if (parsed.type === "assistant" && parsed.message) {
|
|
2481
|
-
const text = extractTextContent(parsed.message.content);
|
|
2482
|
-
numTurns += 1;
|
|
2483
|
-
if (text) lastAssistantText = text;
|
|
2484
|
-
if (typeof parsed.message.model === "string") {
|
|
2485
|
-
lastAssistantModel = parsed.message.model;
|
|
2486
|
-
}
|
|
2487
|
-
const usage = parsed.message.usage;
|
|
2488
|
-
if (usage) {
|
|
2489
|
-
const input = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
|
|
2490
|
-
const output = usage.output_tokens ?? 0;
|
|
2491
|
-
lastAssistantInputTokens = input;
|
|
2492
|
-
lastAssistantOutputTokens = output;
|
|
2493
|
-
}
|
|
2578
|
+
return parseTranscript(raw);
|
|
2579
|
+
}
|
|
2580
|
+
function extractSubagentName(event) {
|
|
2581
|
+
const candidates = [
|
|
2582
|
+
event.agent_name,
|
|
2583
|
+
event.subagent_type,
|
|
2584
|
+
event.agent_type,
|
|
2585
|
+
event.tool_input?.subagent_type
|
|
2586
|
+
];
|
|
2587
|
+
for (const value of candidates) {
|
|
2588
|
+
if (typeof value === "string" && value.trim()) {
|
|
2589
|
+
return value.trim();
|
|
2494
2590
|
}
|
|
2495
2591
|
}
|
|
2496
|
-
return
|
|
2497
|
-
prompt: lastUserText,
|
|
2498
|
-
response: lastAssistantText,
|
|
2499
|
-
tokens: lastAssistantInputTokens + lastAssistantOutputTokens,
|
|
2500
|
-
inputTokens: lastAssistantInputTokens,
|
|
2501
|
-
outputTokens: lastAssistantOutputTokens,
|
|
2502
|
-
modelName: lastAssistantModel,
|
|
2503
|
-
numTurns
|
|
2504
|
-
};
|
|
2592
|
+
return void 0;
|
|
2505
2593
|
}
|
|
2506
2594
|
async function hookCommand(event) {
|
|
2507
2595
|
try {
|
|
@@ -2552,8 +2640,31 @@ async function hookCommand(event) {
|
|
|
2552
2640
|
function buildPayload(event, eventData, config) {
|
|
2553
2641
|
const sessionId = eventData.session_id ?? `claude-code-${Date.now()}`;
|
|
2554
2642
|
switch (event) {
|
|
2555
|
-
case "stop":
|
|
2643
|
+
case "stop":
|
|
2644
|
+
case "subagent-stop": {
|
|
2556
2645
|
const extracted = extractFromTranscript(eventData.transcript_path);
|
|
2646
|
+
const isSubagent = event === "subagent-stop";
|
|
2647
|
+
const customData = {
|
|
2648
|
+
hookEvent: eventData.hook_event_name ?? (isSubagent ? "SubagentStop" : "Stop"),
|
|
2649
|
+
sessionId,
|
|
2650
|
+
transcriptPath: eventData.transcript_path ?? "",
|
|
2651
|
+
cwd: eventData.cwd ?? "",
|
|
2652
|
+
stopHookActive: eventData.stop_hook_active ?? false,
|
|
2653
|
+
inputTokens: extracted.inputTokens,
|
|
2654
|
+
outputTokens: extracted.outputTokens,
|
|
2655
|
+
numTurns: extracted.numTurns
|
|
2656
|
+
};
|
|
2657
|
+
if (typeof extracted.latencyMs === "number") {
|
|
2658
|
+
customData.latencyMs = extracted.latencyMs;
|
|
2659
|
+
}
|
|
2660
|
+
if (isSubagent) {
|
|
2661
|
+
const subagent = extractSubagentName(eventData);
|
|
2662
|
+
if (subagent) {
|
|
2663
|
+
customData.subagent = subagent;
|
|
2664
|
+
}
|
|
2665
|
+
} else if (extracted.skill) {
|
|
2666
|
+
customData.skill = extracted.skill;
|
|
2667
|
+
}
|
|
2557
2668
|
return {
|
|
2558
2669
|
prompt: extracted.prompt,
|
|
2559
2670
|
response: extracted.response,
|
|
@@ -2563,16 +2674,7 @@ function buildPayload(event, eventData, config) {
|
|
|
2563
2674
|
source: config.source,
|
|
2564
2675
|
modelName: extracted.modelName ?? void 0,
|
|
2565
2676
|
tokens: extracted.tokens,
|
|
2566
|
-
customData
|
|
2567
|
-
hookEvent: eventData.hook_event_name ?? "Stop",
|
|
2568
|
-
sessionId,
|
|
2569
|
-
transcriptPath: eventData.transcript_path ?? "",
|
|
2570
|
-
cwd: eventData.cwd ?? "",
|
|
2571
|
-
stopHookActive: eventData.stop_hook_active ?? false,
|
|
2572
|
-
inputTokens: extracted.inputTokens,
|
|
2573
|
-
outputTokens: extracted.outputTokens,
|
|
2574
|
-
numTurns: extracted.numTurns
|
|
2575
|
-
}
|
|
2677
|
+
customData
|
|
2576
2678
|
};
|
|
2577
2679
|
}
|
|
2578
2680
|
default:
|