codiedev 0.9.7-beta.0 → 0.9.7-beta.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/hook.js CHANGED
@@ -256,6 +256,11 @@ async function main() {
256
256
  // server-side against a merged MR/PR's source_branch / commits for AI attribution.
257
257
  const branch = (0, utils_1.getGitBranch)(workspaceRoot);
258
258
  const headSha = (0, utils_1.getGitHeadSha)(workspaceRoot);
259
+ // Cost/velocity/steering signals parsed straight from the transcript we're
260
+ // already holding — no extra hooks, no extra git/process calls.
261
+ const usage = (0, utils_1.parseUsage)(transcriptContent, mode);
262
+ const sessionStartedAt = (0, utils_1.parseSessionStart)(transcriptContent);
263
+ const steering = (0, utils_1.parseSteering)(transcriptContent, mode);
259
264
  const payload = (0, utils_1.buildCapturePayload)({
260
265
  sessionId: session_id,
261
266
  transcriptBase64,
@@ -268,6 +273,13 @@ async function main() {
268
273
  source: mode,
269
274
  branch,
270
275
  headSha,
276
+ inputTokens: usage.inputTokens,
277
+ outputTokens: usage.outputTokens,
278
+ cacheTokens: usage.cacheTokens,
279
+ model: usage.model,
280
+ sessionStartedAt,
281
+ interruptCount: steering.interrupts,
282
+ rejectionCount: steering.rejections,
271
283
  });
272
284
  // Fire the upload from a fully detached child and exit immediately. Awaiting
273
285
  // it inline let Claude Code's SessionEnd shutdown window elapse and kill the
package/dist/utils.d.ts CHANGED
@@ -41,6 +41,13 @@ export interface CapturePayloadInput {
41
41
  source: string;
42
42
  branch?: string | null;
43
43
  headSha?: string | null;
44
+ inputTokens?: number;
45
+ outputTokens?: number;
46
+ cacheTokens?: number;
47
+ model?: string | null;
48
+ sessionStartedAt?: number | null;
49
+ interruptCount?: number;
50
+ rejectionCount?: number;
44
51
  }
45
52
  export declare function buildCapturePayload(input: CapturePayloadInput): Record<string, unknown>;
46
53
  export declare function hashToken(token: string): string;
@@ -119,6 +126,21 @@ export interface ParsedStats {
119
126
  messageCount: number;
120
127
  toolCallCount: number;
121
128
  }
129
+ export interface SteeringStats {
130
+ interrupts: number;
131
+ rejections: number;
132
+ }
133
+ export declare function parseClaudeSteering(content: string): SteeringStats;
134
+ export declare function parseCodexSteering(content: string): SteeringStats;
135
+ export declare function parseSteering(content: string, mode: "claude-code" | "codex" | "cursor"): SteeringStats;
136
+ export interface UsageStats {
137
+ inputTokens: number;
138
+ outputTokens: number;
139
+ cacheTokens: number;
140
+ model: string | null;
141
+ }
142
+ export declare function parseUsage(content: string, mode: "claude-code" | "codex" | "cursor"): UsageStats;
143
+ export declare function parseSessionStart(content: string): number | null;
122
144
  export declare function parseClaudeCodeStats(content: string): ParsedStats;
123
145
  export declare function parseCodexStats(content: string): ParsedStats;
124
146
  export declare function extractCodexRepoUrl(content: string): string | null;
package/dist/utils.js CHANGED
@@ -65,6 +65,11 @@ exports.cleanupCodexMcp = cleanupCodexMcp;
65
65
  exports.installCodexMcp = installCodexMcp;
66
66
  exports.installCodexHook = installCodexHook;
67
67
  exports.installCursorHook = installCursorHook;
68
+ exports.parseClaudeSteering = parseClaudeSteering;
69
+ exports.parseCodexSteering = parseCodexSteering;
70
+ exports.parseSteering = parseSteering;
71
+ exports.parseUsage = parseUsage;
72
+ exports.parseSessionStart = parseSessionStart;
68
73
  exports.parseClaudeCodeStats = parseClaudeCodeStats;
69
74
  exports.parseCodexStats = parseCodexStats;
70
75
  exports.extractCodexRepoUrl = extractCodexRepoUrl;
@@ -349,6 +354,15 @@ function buildCapturePayload(input) {
349
354
  toolCallCount: input.toolCallCount,
350
355
  capturedAt: input.capturedAt,
351
356
  source: input.source,
357
+ // New capture signals (cost / velocity / steering). Optional — omitted
358
+ // (undefined) keys drop out of the JSON body, so older servers ignore them.
359
+ inputTokens: input.inputTokens,
360
+ outputTokens: input.outputTokens,
361
+ cacheTokens: input.cacheTokens,
362
+ model: input.model ?? undefined,
363
+ sessionStartedAt: input.sessionStartedAt ?? undefined,
364
+ interruptCount: input.interruptCount,
365
+ rejectionCount: input.rejectionCount,
352
366
  };
353
367
  }
354
368
  function hashToken(token) {
@@ -1032,6 +1046,137 @@ function installCursorHook() {
1032
1046
  hooks.sessionEnd = filtered;
1033
1047
  fs.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
1034
1048
  }
1049
+ function parseClaudeSteering(content) {
1050
+ let interrupts = 0;
1051
+ let rejections = 0;
1052
+ for (const line of content.split("\n")) {
1053
+ if (!line.trim())
1054
+ continue;
1055
+ // "[Request interrupted by user]" / "...for tool use" — Claude Code markers
1056
+ if (line.includes("[Request interrupted by user"))
1057
+ interrupts++;
1058
+ // Standard Claude Code tool-rejection sentinels in tool_result content
1059
+ if (line.includes("doesn't want to take this action") ||
1060
+ line.includes("user doesn't want to proceed"))
1061
+ rejections++;
1062
+ }
1063
+ return { interrupts, rejections };
1064
+ }
1065
+ function parseCodexSteering(content) {
1066
+ let interrupts = 0;
1067
+ let rejections = 0;
1068
+ for (const line of content.split("\n")) {
1069
+ if (!line.trim())
1070
+ continue;
1071
+ try {
1072
+ const e = JSON.parse(line);
1073
+ const pt = e?.payload?.type;
1074
+ if (e?.type === "event_msg" && pt === "turn_aborted")
1075
+ interrupts++;
1076
+ if (e?.type === "response_item" && pt === "function_call_output") {
1077
+ const out = String(e.payload?.output ?? "");
1078
+ if (/\b(rejected|denied|not approved)\b/i.test(out))
1079
+ rejections++;
1080
+ }
1081
+ }
1082
+ catch {
1083
+ // skip malformed
1084
+ }
1085
+ }
1086
+ return { interrupts, rejections };
1087
+ }
1088
+ function parseSteering(content, mode) {
1089
+ return mode === "codex"
1090
+ ? parseCodexSteering(content)
1091
+ : parseClaudeSteering(content);
1092
+ }
1093
+ function parseClaudeUsage(content) {
1094
+ // Claude Code logs per-turn usage; sum across the session. `input_tokens` is
1095
+ // already fresh (cache read/creation are reported separately).
1096
+ let inputTokens = 0;
1097
+ let outputTokens = 0;
1098
+ let cacheTokens = 0;
1099
+ let model = null;
1100
+ for (const line of content.split("\n")) {
1101
+ if (!line.trim())
1102
+ continue;
1103
+ try {
1104
+ const e = JSON.parse(line);
1105
+ const m = e?.message;
1106
+ const u = m?.usage;
1107
+ if (u) {
1108
+ inputTokens += u.input_tokens ?? 0;
1109
+ outputTokens += u.output_tokens ?? 0;
1110
+ cacheTokens += (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0);
1111
+ }
1112
+ if (typeof m?.model === "string" && m.model)
1113
+ model = m.model;
1114
+ }
1115
+ catch {
1116
+ // skip malformed
1117
+ }
1118
+ }
1119
+ return { inputTokens, outputTokens, cacheTokens, model };
1120
+ }
1121
+ function parseCodexUsage(content) {
1122
+ // Codex reports a CUMULATIVE token_count per turn — the last one is the
1123
+ // session total. `input_tokens` includes the cached portion, so subtract it.
1124
+ let model = null;
1125
+ let last = null;
1126
+ for (const line of content.split("\n")) {
1127
+ if (!line.trim())
1128
+ continue;
1129
+ try {
1130
+ const e = JSON.parse(line);
1131
+ if (e?.type === "turn_context" && typeof e.payload?.model === "string" && e.payload.model) {
1132
+ model = e.payload.model;
1133
+ }
1134
+ if (e?.type === "event_msg" && e.payload?.type === "token_count") {
1135
+ const t = e.payload?.info?.total_token_usage;
1136
+ if (t)
1137
+ last = t;
1138
+ }
1139
+ }
1140
+ catch {
1141
+ // skip malformed
1142
+ }
1143
+ }
1144
+ if (!last)
1145
+ return { inputTokens: 0, outputTokens: 0, cacheTokens: 0, model };
1146
+ const cached = last.cached_input_tokens ?? 0;
1147
+ const input = last.input_tokens ?? 0;
1148
+ return {
1149
+ inputTokens: Math.max(0, input - cached),
1150
+ outputTokens: (last.output_tokens ?? 0) + (last.reasoning_output_tokens ?? 0),
1151
+ cacheTokens: cached,
1152
+ model,
1153
+ };
1154
+ }
1155
+ function parseUsage(content, mode) {
1156
+ return mode === "codex" ? parseCodexUsage(content) : parseClaudeUsage(content);
1157
+ }
1158
+ // Lead-time start proxy: the earliest top-level `timestamp` in the transcript.
1159
+ // Both Claude Code and Codex stamp every line, so this is mode-agnostic.
1160
+ function parseSessionStart(content) {
1161
+ let min = null;
1162
+ for (const line of content.split("\n")) {
1163
+ if (!line.trim())
1164
+ continue;
1165
+ try {
1166
+ const e = JSON.parse(line);
1167
+ const ts = e?.timestamp;
1168
+ if (typeof ts === "string") {
1169
+ const ms = Date.parse(ts);
1170
+ if (!Number.isNaN(ms) && (min === null || ms < min))
1171
+ min = ms;
1172
+ }
1173
+ }
1174
+ catch {
1175
+ // skip malformed
1176
+ }
1177
+ }
1178
+ return min;
1179
+ }
1035
1180
  function parseClaudeCodeStats(content) {
1036
1181
  let messageCount = 0;
1037
1182
  let toolCallCount = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.7-beta.0",
3
+ "version": "0.9.7-beta.1",
4
4
  "description": "Connect Claude Code, Codex, Cursor, or VS Code Copilot to Codiedev for org-wide session capture and artifact collaboration",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",