runtape 0.6.0 → 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/{chunk-LGAXYSLN.js → chunk-6QY5NL5S.js} +7 -2
- package/dist/chunk-6QY5NL5S.js.map +1 -0
- package/dist/index.js +65 -10
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +75 -64
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LGAXYSLN.js.map +0 -1
|
@@ -14,7 +14,12 @@ var CliAugment = z.object({
|
|
|
14
14
|
});
|
|
15
15
|
var SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
16
16
|
type: z.literal("session_start"),
|
|
17
|
-
source: z.string()
|
|
17
|
+
source: z.string(),
|
|
18
|
+
// Best-effort project label derived from the session's cwd. The CLI fills
|
|
19
|
+
// this in on SessionStart (outermost git repo basename, falling back to
|
|
20
|
+
// nearest package.json name, finally to basename(cwd)). Optional so older
|
|
21
|
+
// CLIs that never emit it remain compatible.
|
|
22
|
+
project_name: z.string().min(1).optional()
|
|
18
23
|
});
|
|
19
24
|
var UserPromptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
20
25
|
type: z.literal("user_prompt"),
|
|
@@ -102,4 +107,4 @@ export {
|
|
|
102
107
|
IngestionRequest,
|
|
103
108
|
IngestionResponse
|
|
104
109
|
};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
110
|
+
//# sourceMappingURL=chunk-6QY5NL5S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { z } from 'zod';\n\n// Every Claude Code hook event carries these envelope fields.\nconst ClaudeHookBase = z.object({\n session_id: z.string().min(1),\n transcript_path: z.string().min(1),\n cwd: z.string().min(1),\n hook_event_name: z.string().min(1),\n permission_mode: z.string().optional(),\n});\n\n// CLI-augmented envelope adds monotonic ordering + wall-clock timestamp.\n// agent_tool_use_id is set when the CLI is synthesizing events from a\n// subagent transcript — it carries the parent Agent step's tool_use_id so\n// the server can resolve parent_step_id deterministically instead of\n// relying on the temporal open-stack heuristic. Optional so the field is\n// absent for normal top-level events.\nconst CliAugment = z.object({\n wall_ts: z.string().datetime(),\n sequence: z.number().int().nonnegative(),\n agent_tool_use_id: z.string().min(1).optional(),\n});\n\nexport const SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_start'),\n source: z.string(),\n // Best-effort project label derived from the session's cwd. The CLI fills\n // this in on SessionStart (outermost git repo basename, falling back to\n // nearest package.json name, finally to basename(cwd)). Optional so older\n // CLIs that never emit it remain compatible.\n project_name: z.string().min(1).optional(),\n});\n\nexport const UserPromptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('user_prompt'),\n prompt: z.string(),\n});\n\nexport const ToolAttemptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('tool_attempt'),\n tool_name: z.string().min(1),\n tool_input: z.unknown(),\n tool_use_id: z.string().min(1),\n});\n\nexport const ToolCallEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('tool_call'),\n tool_name: z.string().min(1),\n tool_input: z.unknown(),\n tool_response: z.unknown(),\n tool_use_id: z.string().min(1),\n duration_ms: z.number().nonnegative(),\n // Set by the CLI when the tool_response signals an error (Bash non-zero\n // exit, Edit/Write rejection, tool_response.is_error, etc.). Optional so\n // older CLI versions stay forward-compatible.\n is_error: z.boolean().optional(),\n error_message: z.string().optional(),\n});\n\n// Emitted by the CLI on Stop / PostToolUse after scanning the Claude Code\n// transcript JSONL. One event per assistant message we haven't seen yet,\n// keyed by message_uuid so re-deliveries dedupe at the server. Carries the\n// model identifier and token usage for that turn — the only place either\n// datum is available in Claude Code's emit chain.\nexport const AssistantTurnEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('assistant_turn'),\n message_uuid: z.string().min(1),\n model: z.string().min(1),\n input_tokens: z.number().int().nonnegative(),\n output_tokens: z.number().int().nonnegative(),\n cache_read_tokens: z.number().int().nonnegative().default(0),\n cache_creation_tokens: z.number().int().nonnegative().default(0),\n text: z.string().optional(),\n});\n\nexport const SubagentEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('subagent_end'),\n agent_id: z.string().min(1),\n agent_type: z.string().min(1),\n agent_transcript_path: z.string().min(1),\n last_assistant_message: z.string(),\n stop_hook_active: z.boolean().optional(),\n});\n\n// Stop hook (fires every turn). Despite the literal name 'session_end', this\n// is \"turn end\" semantically. Kept for backward compatibility with CLI <= 0.3.x.\n// New CLIs still emit this; the server interprets it as \"run is idle\".\nexport const SessionEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_end'),\n last_assistant_message: z.string().optional(),\n stop_hook_active: z.boolean().optional(),\n});\n\n// SessionEnd hook (fires once when Claude Code actually closes the session).\n// Server uses this to promote the run from 'idle' to 'ended'.\nexport const SessionCloseEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_close'),\n reason: z.string().optional(),\n});\n\nexport const RuntapeEvent = z.discriminatedUnion('type', [\n SessionStartEvent,\n UserPromptEvent,\n ToolAttemptEvent,\n ToolCallEvent,\n AssistantTurnEvent,\n SubagentEndEvent,\n SessionEndEvent,\n SessionCloseEvent,\n]);\n\nexport type RuntapeEvent = z.infer<typeof RuntapeEvent>;\n\n// POST /v1/events body — a batch of up to 100 events.\nexport const IngestionRequest = z.object({\n events: z.array(RuntapeEvent).min(1).max(100),\n});\n\nexport type IngestionRequest = z.infer<typeof IngestionRequest>;\n\n// Response shape (server returns 200 on accepted, 400 on Zod failure, 401 on bad auth).\nexport const IngestionResponse = z.object({\n accepted: z.number().int().nonnegative(),\n errors: z\n .array(\n z.object({\n index: z.number().int().nonnegative(),\n reason: z.string(),\n }),\n )\n .default([]),\n});\n\nexport type IngestionResponse = z.infer<typeof IngestionResponse>;\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RuntapeEvent
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6QY5NL5S.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8
|
-
import { dirname as
|
|
8
|
+
import { dirname as dirname9, join as join3 } from "path";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
|
|
11
11
|
// src/commands/login.ts
|
|
@@ -773,6 +773,55 @@ function normalizePath(input4) {
|
|
|
773
773
|
return abs.replace(/\/+$/, "") || "/";
|
|
774
774
|
}
|
|
775
775
|
|
|
776
|
+
// src/lib/project-name.ts
|
|
777
|
+
import { stat as stat2, readFile as readFile7 } from "fs/promises";
|
|
778
|
+
import { basename, dirname as dirname7, join as join2 } from "path";
|
|
779
|
+
async function detectProjectName(cwd) {
|
|
780
|
+
const repoRoot = await findOutermostGitRoot(cwd);
|
|
781
|
+
if (repoRoot) return basename(repoRoot);
|
|
782
|
+
const pkgName = await findNearestPackageName(cwd);
|
|
783
|
+
if (pkgName) return pkgName;
|
|
784
|
+
return basename(cwd) || cwd;
|
|
785
|
+
}
|
|
786
|
+
async function pathExists(p) {
|
|
787
|
+
try {
|
|
788
|
+
await stat2(p);
|
|
789
|
+
return true;
|
|
790
|
+
} catch {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async function findOutermostGitRoot(cwd) {
|
|
795
|
+
let dir = cwd;
|
|
796
|
+
let lastFound = null;
|
|
797
|
+
for (let i = 0; i < 100; i++) {
|
|
798
|
+
if (await pathExists(join2(dir, ".git"))) {
|
|
799
|
+
lastFound = dir;
|
|
800
|
+
}
|
|
801
|
+
const parent = dirname7(dir);
|
|
802
|
+
if (parent === dir) break;
|
|
803
|
+
dir = parent;
|
|
804
|
+
}
|
|
805
|
+
return lastFound;
|
|
806
|
+
}
|
|
807
|
+
async function findNearestPackageName(cwd) {
|
|
808
|
+
let dir = cwd;
|
|
809
|
+
for (let i = 0; i < 100; i++) {
|
|
810
|
+
try {
|
|
811
|
+
const raw = await readFile7(join2(dir, "package.json"), "utf8");
|
|
812
|
+
const parsed = JSON.parse(raw);
|
|
813
|
+
if (typeof parsed.name === "string" && parsed.name.trim() !== "") {
|
|
814
|
+
return parsed.name.trim();
|
|
815
|
+
}
|
|
816
|
+
} catch {
|
|
817
|
+
}
|
|
818
|
+
const parent = dirname7(dir);
|
|
819
|
+
if (parent === dir) break;
|
|
820
|
+
dir = parent;
|
|
821
|
+
}
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
|
|
776
825
|
// src/commands/push.ts
|
|
777
826
|
function isDisabledByEnv() {
|
|
778
827
|
const v = (process.env.RUNTAPE_DISABLE ?? "").trim().toLowerCase();
|
|
@@ -833,6 +882,12 @@ async function pushCommand(opts) {
|
|
|
833
882
|
`);
|
|
834
883
|
return 0;
|
|
835
884
|
}
|
|
885
|
+
if (result.event.type === "session_start" && cwd) {
|
|
886
|
+
const projectName = await detectProjectName(cwd);
|
|
887
|
+
if (projectName) {
|
|
888
|
+
result.event.project_name = projectName;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
836
891
|
await appendEvent(sessionId, result.event);
|
|
837
892
|
if (opts.event === "PostToolUse" || opts.event === "Stop" || opts.event === "SubagentStop") {
|
|
838
893
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
@@ -951,10 +1006,10 @@ async function pushCommand(opts) {
|
|
|
951
1006
|
}
|
|
952
1007
|
|
|
953
1008
|
// src/commands/status.ts
|
|
954
|
-
import { readFile as
|
|
1009
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
955
1010
|
async function readFlusherPid() {
|
|
956
1011
|
try {
|
|
957
|
-
const raw = await
|
|
1012
|
+
const raw = await readFile8(paths.flusherPid, "utf8");
|
|
958
1013
|
const n = Number.parseInt(raw.trim(), 10);
|
|
959
1014
|
return Number.isFinite(n) ? n : null;
|
|
960
1015
|
} catch (err) {
|
|
@@ -1245,15 +1300,15 @@ async function watchResetCommand() {
|
|
|
1245
1300
|
}
|
|
1246
1301
|
|
|
1247
1302
|
// src/lib/flusher.ts
|
|
1248
|
-
import { appendFile as appendFile2, mkdir as mkdir7, readFile as
|
|
1249
|
-
import { dirname as
|
|
1303
|
+
import { appendFile as appendFile2, mkdir as mkdir7, readFile as readFile9, unlink as unlink3, writeFile as writeFile7 } from "fs/promises";
|
|
1304
|
+
import { dirname as dirname8 } from "path";
|
|
1250
1305
|
var POLL_INTERVAL_MS = 5e3;
|
|
1251
1306
|
var IDLE_EXIT_MS = 3e4;
|
|
1252
1307
|
var BATCH_MAX = 500;
|
|
1253
1308
|
var BACKOFF_STEPS_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 6e4];
|
|
1254
1309
|
async function log(line) {
|
|
1255
1310
|
try {
|
|
1256
|
-
await mkdir7(
|
|
1311
|
+
await mkdir7(dirname8(paths.flusherLog), { recursive: true });
|
|
1257
1312
|
await appendFile2(paths.flusherLog, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
1258
1313
|
`);
|
|
1259
1314
|
} catch {
|
|
@@ -1270,9 +1325,9 @@ async function isProcessAlive(pid) {
|
|
|
1270
1325
|
}
|
|
1271
1326
|
}
|
|
1272
1327
|
async function acquirePidLock() {
|
|
1273
|
-
await mkdir7(
|
|
1328
|
+
await mkdir7(dirname8(paths.flusherPid), { recursive: true });
|
|
1274
1329
|
try {
|
|
1275
|
-
const existing = await
|
|
1330
|
+
const existing = await readFile9(paths.flusherPid, "utf8");
|
|
1276
1331
|
const pid = Number.parseInt(existing.trim(), 10);
|
|
1277
1332
|
if (Number.isFinite(pid) && await isProcessAlive(pid)) {
|
|
1278
1333
|
return false;
|
|
@@ -1365,7 +1420,7 @@ async function runFlusher() {
|
|
|
1365
1420
|
}
|
|
1366
1421
|
|
|
1367
1422
|
// src/index.ts
|
|
1368
|
-
var pkgPath =
|
|
1423
|
+
var pkgPath = join3(dirname9(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
1369
1424
|
var PKG_VERSION = JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
1370
1425
|
if (process.argv.includes("--internal-flusher")) {
|
|
1371
1426
|
void runFlusher().then(
|