@aixle/insights 0.2.0 → 0.2.2-staging
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/README.md +38 -9
- package/dist/auth/credentials.d.ts +7 -1
- package/dist/auth/credentials.js +71 -14
- package/dist/auth/exchange.d.ts +1 -1
- package/dist/auth/exchange.js +1 -1
- package/dist/auth/flow.d.ts +8 -1
- package/dist/auth/flow.js +27 -5
- package/dist/auth/keycloak.d.ts +1 -1
- package/dist/auth/keycloak.js +20 -1
- package/dist/cli.d.ts +5 -3
- package/dist/cli.js +69 -21
- package/dist/collect-cursor-payloads.d.ts +4 -3
- package/dist/collect-cursor-payloads.js +8 -5
- package/dist/cursor-checkpoints.d.ts +2 -2
- package/dist/cursor-payload-contract.d.ts +5 -5
- package/dist/cursor-payload-contract.js +6 -0
- package/dist/cursor-settings.d.ts +9 -4
- package/dist/cursor-settings.js +80 -10
- package/dist/health.d.ts +3 -1
- package/dist/health.js +13 -1
- package/dist/hooks/cursor-hooks-mapper.d.ts +3 -3
- package/dist/hooks/cursor-hooks-mapper.js +1 -1
- package/dist/hooks/cursor-hooks-reader.d.ts +2 -0
- package/dist/hooks/cursor-hooks-reader.js +10 -3
- package/dist/install/cursor.d.ts +34 -0
- package/dist/install/cursor.js +193 -0
- package/dist/install/index.d.ts +6 -4
- package/dist/install/index.js +6 -1
- package/dist/lib/client.d.ts +7 -0
- package/dist/lib/client.js +17 -0
- package/dist/lib/config.js +7 -2
- package/dist/lib/project-resolver.d.ts +5 -4
- package/dist/lib/project-resolver.js +57 -11
- package/dist/lib/repo-path-safety.d.ts +35 -0
- package/dist/lib/repo-path-safety.js +102 -0
- package/dist/lib/spawn-arg-safety.d.ts +25 -0
- package/dist/lib/spawn-arg-safety.js +49 -0
- package/dist/lib/transport-security.d.ts +1 -0
- package/dist/lib/transport-security.js +1 -1
- package/dist/readers/claude.d.ts +54 -6
- package/dist/readers/claude.js +154 -2
- package/dist/readers/cursor.d.ts +10 -7
- package/dist/readers/cursor.js +113 -17
- package/dist/risk-scanner.js +7 -0
- package/dist/server.d.ts +20 -3
- package/dist/server.js +101 -67
- package/dist/state.js +7 -2
- package/dist/sync.d.ts +13 -2
- package/dist/sync.js +86 -58
- package/package.json +6 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guards for untrusted values that end up in the argv of a spawned process.
|
|
3
|
+
*
|
|
4
|
+
* `execFileSync` prevents *shell* injection but not *argv-option* injection: a
|
|
5
|
+
* value beginning with `-` is parsed by the child as a command-line option. Git
|
|
6
|
+
* remotes and workspace paths are untrusted text — they come from a repo the
|
|
7
|
+
* developer cloned, from Cursor's `workspace.json`, or from a Claude transcript
|
|
8
|
+
* — so every value derived from them must be checked before it reaches `git`
|
|
9
|
+
* or `ssh`. See DB90DV-546.
|
|
10
|
+
*/
|
|
11
|
+
/** Longest legal DNS name (253) with headroom for an ssh_config alias. */
|
|
12
|
+
const MAX_HOST_LENGTH = 255;
|
|
13
|
+
/**
|
|
14
|
+
* Dot-separated labels of alphanumerics, `-` and `_`. A label may not start or
|
|
15
|
+
* end with `-`, which is what blocks option injection (`-oProxyCommand=…`).
|
|
16
|
+
* Whitespace, `=`, quotes, backslashes, newlines and NUL are all excluded.
|
|
17
|
+
* Underscores are allowed because `~/.ssh/config` aliases commonly use them.
|
|
18
|
+
* IPv6 literals are not covered — the SCP/`ssh://` host capture in
|
|
19
|
+
* `project-resolver.ts` cannot produce one, since it excludes `:`.
|
|
20
|
+
*/
|
|
21
|
+
const HOST_LABEL = "[A-Za-z0-9_](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?";
|
|
22
|
+
const HOST_PATTERN = new RegExp(`^${HOST_LABEL}(?:\\.${HOST_LABEL})*$`);
|
|
23
|
+
/**
|
|
24
|
+
* True when `host` is safe to pass as an argv element to `ssh`. Accepts real
|
|
25
|
+
* hostnames, IPv4 literals, and `~/.ssh/config` host aliases.
|
|
26
|
+
*/
|
|
27
|
+
export function isSafeSshHost(host) {
|
|
28
|
+
if (host.length === 0 || host.length > MAX_HOST_LENGTH)
|
|
29
|
+
return false;
|
|
30
|
+
return HOST_PATTERN.test(host);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True when `value` is safe to pass as a filesystem-path argv element (e.g.
|
|
34
|
+
* after `git -C`). Deliberately permissive about path *content* — real
|
|
35
|
+
* workspace paths contain spaces, dashes and drive letters. It only rejects
|
|
36
|
+
* what makes the child misread the value as an option, plus embedded NUL.
|
|
37
|
+
*
|
|
38
|
+
* This is an argv guard, not a containment check: verifying the path points
|
|
39
|
+
* somewhere legitimate is DB90DV-547.
|
|
40
|
+
*/
|
|
41
|
+
export function isSafeSpawnPathArg(value) {
|
|
42
|
+
if (value.length === 0)
|
|
43
|
+
return false;
|
|
44
|
+
if (value.startsWith("-"))
|
|
45
|
+
return false;
|
|
46
|
+
if (value.includes("\0"))
|
|
47
|
+
return false;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
@@ -9,4 +9,5 @@ export interface TransportSecurityOptions {
|
|
|
9
9
|
allowInsecureHttp: boolean;
|
|
10
10
|
label: string;
|
|
11
11
|
}
|
|
12
|
+
export declare function isLoopbackHost(hostname: string): boolean;
|
|
12
13
|
export declare function evaluateTransportSecurity(rawUrl: string, options: TransportSecurityOptions): TransportSecurityResult;
|
|
@@ -8,7 +8,7 @@ function isIpv4Loopback(hostname) {
|
|
|
8
8
|
octet <= 255 &&
|
|
9
9
|
String(octet) === parts[index]) && octets[0] === 127;
|
|
10
10
|
}
|
|
11
|
-
function isLoopbackHost(hostname) {
|
|
11
|
+
export function isLoopbackHost(hostname) {
|
|
12
12
|
const normalized = hostname.toLowerCase();
|
|
13
13
|
return normalized === "localhost" ||
|
|
14
14
|
normalized === "::1" ||
|
package/dist/readers/claude.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { IngestPayload } from "../lib/index.js";
|
|
2
2
|
import { type PricingTable } from "../pricing.js";
|
|
3
3
|
import { type RiskLevel } from "../risk-scanner.js";
|
|
4
|
+
export type ClaudeDerivativeEventType = "edit" | "commit" | "test" | "tool_use";
|
|
5
|
+
export interface ClaudeToolUseBlock {
|
|
6
|
+
id?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
input?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface ClaudeCollectedToolUse {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
eventType: ClaudeDerivativeEventType;
|
|
14
|
+
summary: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function classifyToolUse(block: ClaudeToolUseBlock): ClaudeDerivativeEventType | null;
|
|
17
|
+
export declare function scrubBashCommand(cmd: string): string;
|
|
18
|
+
export declare function summarizeToolUse(block: ClaudeToolUseBlock): string;
|
|
4
19
|
/** True when prompt text alone matches known local-command injection markers. */
|
|
5
20
|
export declare function isClaudeLocalCommandNoisePrompt(promptText: string): boolean;
|
|
6
21
|
/**
|
|
@@ -41,9 +56,13 @@ export interface ClaudeTranscriptTurn {
|
|
|
41
56
|
riskLevel: RiskLevel;
|
|
42
57
|
riskScore: number;
|
|
43
58
|
riskCategories: string[];
|
|
59
|
+
toolUses: ClaudeCollectedToolUse[];
|
|
60
|
+
navToolCalls: number;
|
|
61
|
+
totalToolCalls: number;
|
|
62
|
+
messageIds: string[];
|
|
44
63
|
}
|
|
45
|
-
/** Payload shape
|
|
46
|
-
export interface
|
|
64
|
+
/** Payload shape for the parent chat turn (carries full token cost). */
|
|
65
|
+
export interface ClaudePayload extends IngestPayload {
|
|
47
66
|
tool_name: "claude_code";
|
|
48
67
|
event_type: "chat";
|
|
49
68
|
model?: string;
|
|
@@ -57,7 +76,7 @@ export interface Db90Payload extends IngestPayload {
|
|
|
57
76
|
session_id: string;
|
|
58
77
|
claude_session_id: string;
|
|
59
78
|
transcript_source: "claude_jsonl";
|
|
60
|
-
model
|
|
79
|
+
model?: string | null;
|
|
61
80
|
base_input_tokens: number;
|
|
62
81
|
output_tokens: number;
|
|
63
82
|
cache_write_tokens: number;
|
|
@@ -68,10 +87,39 @@ export interface Db90Payload extends IngestPayload {
|
|
|
68
87
|
prompt_text?: string;
|
|
69
88
|
assistant_text?: string;
|
|
70
89
|
scannable: true;
|
|
90
|
+
cost_model: "token_count";
|
|
91
|
+
nav_tool_calls: number;
|
|
92
|
+
total_tool_calls: number;
|
|
93
|
+
message_ids?: string[];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Payload shape for derivative tool-use children (cost_usd: 0, no tokens). */
|
|
97
|
+
export interface ClaudeDerivativePayload extends IngestPayload {
|
|
98
|
+
tool_name: "claude_code";
|
|
99
|
+
event_type: ClaudeDerivativeEventType;
|
|
100
|
+
cost_usd: 0;
|
|
101
|
+
occurred_at: string;
|
|
102
|
+
model?: string;
|
|
103
|
+
project_id?: string;
|
|
104
|
+
metadata: {
|
|
105
|
+
session_id: string;
|
|
106
|
+
claude_session_id: string;
|
|
107
|
+
transcript_source: "claude_jsonl";
|
|
108
|
+
cost_model: "derivative";
|
|
109
|
+
parent_session_id: string;
|
|
110
|
+
tool_name_inner: string;
|
|
111
|
+
tool_use_id: string;
|
|
112
|
+
summary: string;
|
|
113
|
+
scannable: false;
|
|
114
|
+
risk_level: "none";
|
|
115
|
+
risk_categories: string[];
|
|
116
|
+
risk_score: 0;
|
|
71
117
|
};
|
|
72
118
|
}
|
|
119
|
+
/** Union of all Claude transcript payloads expected by the ingest API. */
|
|
120
|
+
export type ClaudeMappedPayload = ClaudePayload | ClaudeDerivativePayload;
|
|
73
121
|
/** Options for mapTranscriptTurn. */
|
|
74
|
-
export interface
|
|
122
|
+
export interface ToClaudePayloadOptions {
|
|
75
123
|
projectId?: string | null;
|
|
76
124
|
pricing?: PricingTable;
|
|
77
125
|
}
|
|
@@ -79,5 +127,5 @@ export interface ToDb90PayloadOptions {
|
|
|
79
127
|
export declare function findTranscriptFiles(baseDirs?: string[]): string[];
|
|
80
128
|
/** Streams a JSONL file and splits Claude transcripts into individual turns. */
|
|
81
129
|
export declare function parseTranscriptFile(filePath: string, verbose?: boolean): Promise<ClaudeTranscriptTurn[]>;
|
|
82
|
-
/** Converts a Claude transcript turn to
|
|
83
|
-
export declare function mapTranscriptTurn(turn: ClaudeTranscriptTurn, options?:
|
|
130
|
+
/** Converts a Claude transcript turn to parent chat and derivative tool-use payloads. */
|
|
131
|
+
export declare function mapTranscriptTurn(turn: ClaudeTranscriptTurn, options?: ToClaudePayloadOptions): ClaudeMappedPayload[];
|
package/dist/readers/claude.js
CHANGED
|
@@ -6,6 +6,83 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import { glob } from "glob";
|
|
7
7
|
import { calculateCost } from "../pricing.js";
|
|
8
8
|
import { scanText } from "../risk-scanner.js";
|
|
9
|
+
const NAV_TOOLS = new Set(["Read", "Grep", "Glob", "LS"]);
|
|
10
|
+
const EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
11
|
+
function bashCommand(block) {
|
|
12
|
+
const c = block.input?.command;
|
|
13
|
+
return typeof c === "string" ? c : "";
|
|
14
|
+
}
|
|
15
|
+
export function classifyToolUse(block) {
|
|
16
|
+
if (NAV_TOOLS.has(block.name))
|
|
17
|
+
return null;
|
|
18
|
+
if (EDIT_TOOLS.has(block.name))
|
|
19
|
+
return "edit";
|
|
20
|
+
if (block.name === "Bash") {
|
|
21
|
+
const cmd = bashCommand(block);
|
|
22
|
+
// Require a space or end-of-string after "commit" so "git commit-tree" is not a commit.
|
|
23
|
+
if (/\bgit\s+commit(\s|$)/.test(cmd))
|
|
24
|
+
return "commit";
|
|
25
|
+
if (/\b(rspec|jest|vitest|pytest|phpunit)\b/i.test(cmd))
|
|
26
|
+
return "test";
|
|
27
|
+
}
|
|
28
|
+
return "tool_use";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Redacts credential-bearing substrings from a Bash command string before egress.
|
|
32
|
+
*
|
|
33
|
+
* Claude events carry `scannable: true`, which causes the server's ClassificationActivity
|
|
34
|
+
* to take Path 2 (classification_activity.rb:30-44) — trusting the CLI verbatim and
|
|
35
|
+
* skipping server-side sanitization. This function is the single point of trust for
|
|
36
|
+
* command strings derived from tool_input.command. Any new pattern that reads from
|
|
37
|
+
* tool_input.command MUST pass through this function before being emitted. See DATA-CURRENT.md §12.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* True when an env-var name looks credential-bearing.
|
|
41
|
+
* Long tokens (SECRET/TOKEN/PASSWORD/…) may appear as substrings; short ones
|
|
42
|
+
* (KEY/PASS/PWD) must be underscore-delimited segments so "monkey" / "keyboard"
|
|
43
|
+
* are not redacted.
|
|
44
|
+
*/
|
|
45
|
+
function isSecretEnvName(name) {
|
|
46
|
+
const n = name.toUpperCase();
|
|
47
|
+
if (/(?:SECRET|TOKEN|PASSWORD|APIKEY|API_KEY)/.test(n))
|
|
48
|
+
return true;
|
|
49
|
+
return /(?:^|_)(?:KEY|PASS|PWD)(?:_|$)/.test(n);
|
|
50
|
+
}
|
|
51
|
+
export function scrubBashCommand(cmd) {
|
|
52
|
+
return (cmd
|
|
53
|
+
// Authorization / Bearer / Basic headers (curl -H, fetch headers, etc.)
|
|
54
|
+
.replace(/\b(Authorization\s*:\s*)(Bearer|Basic|Token)\s+\S+/gi, "$1$2 [REDACTED]")
|
|
55
|
+
// Inline env-var assignments carrying secrets (AWS_SECRET_ACCESS_KEY=…, db_password=…)
|
|
56
|
+
.replace(/\b([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\S+/g, (match, name) => isSecretEnvName(name) ? `${name}=[REDACTED]` : match)
|
|
57
|
+
// --password=, --token=, --secret=, --api-key= flags
|
|
58
|
+
.replace(/(--(?:password|token|secret|api[-_]?key|access[-_]?key|auth[-_]?key)=)\S+/gi, "$1[REDACTED]")
|
|
59
|
+
// -p / --password <value> flag-value pairs
|
|
60
|
+
.replace(/((?:^|\s)-p\s+)\S+/g, "$1[REDACTED]")
|
|
61
|
+
// AWS CLI --profile (may embed customer identifiers)
|
|
62
|
+
.replace(/(--profile\s+)\S+/g, "$1[REDACTED]")
|
|
63
|
+
// Cloud CLI credential flags: --access-key-id, --secret-access-key, --service-account-key
|
|
64
|
+
.replace(/(--(?:access-key-id|secret-access-key|service-account-key|client-secret)\s+)\S+/gi, "$1[REDACTED]")
|
|
65
|
+
// curl | sh / curl | bash patterns (remote code execution)
|
|
66
|
+
.replace(/\|\s*(sh|bash|zsh|dash)\b/g, "| [SHELL REDACTED]"));
|
|
67
|
+
}
|
|
68
|
+
export function summarizeToolUse(block) {
|
|
69
|
+
const input = block.input ?? {};
|
|
70
|
+
let raw;
|
|
71
|
+
if (typeof input.file_path === "string" && input.file_path.trim()) {
|
|
72
|
+
raw = `${block.name}: ${input.file_path}`;
|
|
73
|
+
}
|
|
74
|
+
else if (typeof input.command === "string" && input.command.trim()) {
|
|
75
|
+
const scrubbed = scrubBashCommand(input.command.trim().replace(/\s+/g, " "));
|
|
76
|
+
raw = `${block.name}: ${scrubbed}`;
|
|
77
|
+
}
|
|
78
|
+
else if (typeof input.pattern === "string") {
|
|
79
|
+
raw = `${block.name}: ${scrubBashCommand(input.pattern)}`;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
raw = block.name;
|
|
83
|
+
}
|
|
84
|
+
return raw.length <= 256 ? raw : `${raw.slice(0, 253)}...`;
|
|
85
|
+
}
|
|
9
86
|
/** Prompt substrings emitted for local IDE commands — not real user prompts. */
|
|
10
87
|
const LOCAL_COMMAND_NOISE_PROMPT_PATTERNS = [
|
|
11
88
|
/<local-command-caveat\b/i,
|
|
@@ -125,6 +202,10 @@ function newTurn(sessionId, turnIndex, filePath, fileSize, occurredAt, promptId)
|
|
|
125
202
|
riskLevel: "low",
|
|
126
203
|
riskScore: 0,
|
|
127
204
|
riskCategories: [],
|
|
205
|
+
toolUses: [],
|
|
206
|
+
navToolCalls: 0,
|
|
207
|
+
totalToolCalls: 0,
|
|
208
|
+
messageIds: [],
|
|
128
209
|
persisted: false,
|
|
129
210
|
};
|
|
130
211
|
}
|
|
@@ -141,6 +222,41 @@ function enrichTurnRisk(turn) {
|
|
|
141
222
|
turn.riskScore = result.risk_score;
|
|
142
223
|
turn.riskCategories = result.risk_categories;
|
|
143
224
|
}
|
|
225
|
+
function collectToolUsesFromContent(content, turn) {
|
|
226
|
+
if (!Array.isArray(content))
|
|
227
|
+
return;
|
|
228
|
+
let nav = 0;
|
|
229
|
+
let total = 0;
|
|
230
|
+
for (const raw of content) {
|
|
231
|
+
if (typeof raw !== "object" || raw === null)
|
|
232
|
+
continue;
|
|
233
|
+
const block = raw;
|
|
234
|
+
if (block.type !== "tool_use" || typeof block.name !== "string")
|
|
235
|
+
continue;
|
|
236
|
+
total += 1;
|
|
237
|
+
const toolBlock = {
|
|
238
|
+
id: typeof block.id === "string" ? block.id : undefined,
|
|
239
|
+
name: block.name,
|
|
240
|
+
input: typeof block.input === "object" && block.input !== null
|
|
241
|
+
? block.input
|
|
242
|
+
: undefined,
|
|
243
|
+
};
|
|
244
|
+
const eventType = classifyToolUse(toolBlock);
|
|
245
|
+
if (eventType === null) {
|
|
246
|
+
nav += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const id = toolBlock.id ?? `idx-${turn.toolUses.length}`;
|
|
250
|
+
turn.toolUses.push({
|
|
251
|
+
id,
|
|
252
|
+
name: toolBlock.name,
|
|
253
|
+
eventType,
|
|
254
|
+
summary: summarizeToolUse(toolBlock),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
turn.navToolCalls += nav;
|
|
258
|
+
turn.totalToolCalls += total;
|
|
259
|
+
}
|
|
144
260
|
/** Streams a JSONL file and splits Claude transcripts into individual turns. */
|
|
145
261
|
export async function parseTranscriptFile(filePath, verbose = false) {
|
|
146
262
|
const turns = [];
|
|
@@ -254,9 +370,13 @@ export async function parseTranscriptFile(filePath, verbose = false) {
|
|
|
254
370
|
}
|
|
255
371
|
if (entry.message.model)
|
|
256
372
|
currentTurn.model = entry.message.model;
|
|
373
|
+
if (entry.message.id && !currentTurn.messageIds.includes(entry.message.id)) {
|
|
374
|
+
currentTurn.messageIds.push(entry.message.id);
|
|
375
|
+
}
|
|
257
376
|
currentTurn.occurredAt = timestamp > currentTurn.occurredAt ? timestamp : currentTurn.occurredAt;
|
|
258
377
|
const text = extractContentText(entry.message.content).join("\n\n").trim();
|
|
259
378
|
currentTurn.assistantText = appendText(currentTurn.assistantText, text);
|
|
379
|
+
collectToolUsesFromContent(entry.message.content, currentTurn);
|
|
260
380
|
}
|
|
261
381
|
}
|
|
262
382
|
}
|
|
@@ -273,7 +393,7 @@ export async function parseTranscriptFile(filePath, verbose = false) {
|
|
|
273
393
|
flushCurrentTurn();
|
|
274
394
|
return finalizedTurns.map(({ persisted: _persisted, ...turn }) => turn);
|
|
275
395
|
}
|
|
276
|
-
/** Converts a Claude transcript turn to
|
|
396
|
+
/** Converts a Claude transcript turn to parent chat and derivative tool-use payloads. */
|
|
277
397
|
export function mapTranscriptTurn(turn, options) {
|
|
278
398
|
const { projectId, pricing } = options ?? {};
|
|
279
399
|
const baseInputTokens = Math.max(0, turn.tokensIn - turn.cacheWriteTokens - turn.cacheReadTokens);
|
|
@@ -300,6 +420,11 @@ export function mapTranscriptTurn(turn, options) {
|
|
|
300
420
|
prompt_text: turn.promptText || undefined,
|
|
301
421
|
assistant_text: turn.assistantText || undefined,
|
|
302
422
|
scannable: true,
|
|
423
|
+
cost_model: "token_count",
|
|
424
|
+
// Zero is intentional: confirms no tool activity on this turn (not an omission).
|
|
425
|
+
nav_tool_calls: turn.navToolCalls,
|
|
426
|
+
total_tool_calls: turn.totalToolCalls,
|
|
427
|
+
message_ids: turn.messageIds.length > 0 ? turn.messageIds : undefined,
|
|
303
428
|
},
|
|
304
429
|
};
|
|
305
430
|
if (turn.model)
|
|
@@ -313,5 +438,32 @@ export function mapTranscriptTurn(turn, options) {
|
|
|
313
438
|
}
|
|
314
439
|
if (projectId)
|
|
315
440
|
payload.project_id = projectId;
|
|
316
|
-
|
|
441
|
+
const derivatives = turn.toolUses.map((toolUse) => {
|
|
442
|
+
const derivative = {
|
|
443
|
+
tool_name: "claude_code",
|
|
444
|
+
event_type: toolUse.eventType,
|
|
445
|
+
cost_usd: 0,
|
|
446
|
+
occurred_at: turn.occurredAt,
|
|
447
|
+
metadata: {
|
|
448
|
+
session_id: `${turn.turnId}:tool:${toolUse.id}`,
|
|
449
|
+
claude_session_id: turn.sessionId,
|
|
450
|
+
transcript_source: "claude_jsonl",
|
|
451
|
+
cost_model: "derivative",
|
|
452
|
+
parent_session_id: turn.turnId,
|
|
453
|
+
tool_name_inner: toolUse.name,
|
|
454
|
+
tool_use_id: toolUse.id,
|
|
455
|
+
summary: toolUse.summary,
|
|
456
|
+
scannable: false,
|
|
457
|
+
risk_level: "none",
|
|
458
|
+
risk_categories: [],
|
|
459
|
+
risk_score: 0,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
if (turn.model)
|
|
463
|
+
derivative.model = turn.model;
|
|
464
|
+
if (projectId)
|
|
465
|
+
derivative.project_id = projectId;
|
|
466
|
+
return derivative;
|
|
467
|
+
});
|
|
468
|
+
return [payload, ...derivatives];
|
|
317
469
|
}
|
package/dist/readers/cursor.d.ts
CHANGED
|
@@ -46,6 +46,8 @@ interface CursorComposerHeader {
|
|
|
46
46
|
composerId: string;
|
|
47
47
|
name: string | null;
|
|
48
48
|
workspacePath: string | null;
|
|
49
|
+
/** Session start (composer.createdAt). Used to spread turns when JSONL lacks per-message times. */
|
|
50
|
+
createdAt: string | null;
|
|
49
51
|
lastUpdatedAt: string | null;
|
|
50
52
|
}
|
|
51
53
|
export interface CursorTranscriptTurn {
|
|
@@ -83,7 +85,7 @@ export interface PricingConfig {
|
|
|
83
85
|
chat_output_per_mtok: number;
|
|
84
86
|
}
|
|
85
87
|
export declare const DEFAULT_CURSOR_PRICING: PricingConfig;
|
|
86
|
-
export type
|
|
88
|
+
export type CursorPayloadMetadata = {
|
|
87
89
|
session_id?: string;
|
|
88
90
|
cursor_session_id: string | null;
|
|
89
91
|
workspace: string;
|
|
@@ -109,8 +111,9 @@ export type Db90CursorPayloadMetadata = {
|
|
|
109
111
|
generation_id?: string;
|
|
110
112
|
hook_tool_name?: string;
|
|
111
113
|
duration_ms?: number;
|
|
114
|
+
model_resolution?: "settings_json" | "state_vscdb" | "unresolved";
|
|
112
115
|
};
|
|
113
|
-
export interface
|
|
116
|
+
export interface CursorPayload extends IngestPayload {
|
|
114
117
|
tool_name: "cursor";
|
|
115
118
|
event_type: "completion" | "chat" | "commit";
|
|
116
119
|
model: string;
|
|
@@ -119,16 +122,16 @@ export interface CursorDb90Payload extends IngestPayload {
|
|
|
119
122
|
cost_usd: number;
|
|
120
123
|
occurred_at: string;
|
|
121
124
|
project_id?: string;
|
|
122
|
-
metadata:
|
|
125
|
+
metadata: CursorPayloadMetadata;
|
|
123
126
|
}
|
|
124
127
|
export declare function toEpochMs(timestamp: number | string | null | undefined): number | null;
|
|
125
|
-
export declare function mapDailyStats(entry: DailyStatsEntry, projectId?: string, pricing?: PricingConfig, model?: string):
|
|
128
|
+
export declare function mapDailyStats(entry: DailyStatsEntry, projectId?: string, pricing?: PricingConfig, model?: string, modelResolution?: CursorPayloadMetadata["model_resolution"]): CursorPayload[];
|
|
126
129
|
/**
|
|
127
130
|
* Maps Cursor’s latest-commit snapshot (`aiCodeTracking.recentCommit`) to a single commit-classified event.
|
|
128
131
|
* Cursor only keeps one recent commit row (overwritten on each new commit).
|
|
129
132
|
* Line-cost math still follows the chat-style line proxy (`computeLineCost("chat", …)`); only `event_type` differs.
|
|
130
133
|
*/
|
|
131
|
-
export declare function mapRecentCommit(entry: RecentCommitSnapshot, projectId?: string, pricing?: PricingConfig, model?: string):
|
|
132
|
-
export declare function mapEvent(row: CursorRow, workspacePath: string, projectId?: string, pricing?: PricingConfig):
|
|
133
|
-
export declare function mapTranscriptTurn(turn: CursorTranscriptTurn, projectId?: string, pricing?: PricingConfig, model?: string):
|
|
134
|
+
export declare function mapRecentCommit(entry: RecentCommitSnapshot, projectId?: string, pricing?: PricingConfig, model?: string, modelResolution?: CursorPayloadMetadata["model_resolution"]): CursorPayload | null;
|
|
135
|
+
export declare function mapEvent(row: CursorRow, workspacePath: string, projectId?: string, pricing?: PricingConfig): CursorPayload | null;
|
|
136
|
+
export declare function mapTranscriptTurn(turn: CursorTranscriptTurn, projectId?: string, pricing?: PricingConfig, model?: string, modelResolution?: CursorPayloadMetadata["model_resolution"]): CursorPayload;
|
|
134
137
|
export {};
|