@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.
Files changed (50) hide show
  1. package/README.md +38 -9
  2. package/dist/auth/credentials.d.ts +7 -1
  3. package/dist/auth/credentials.js +71 -14
  4. package/dist/auth/exchange.d.ts +1 -1
  5. package/dist/auth/exchange.js +1 -1
  6. package/dist/auth/flow.d.ts +8 -1
  7. package/dist/auth/flow.js +27 -5
  8. package/dist/auth/keycloak.d.ts +1 -1
  9. package/dist/auth/keycloak.js +20 -1
  10. package/dist/cli.d.ts +5 -3
  11. package/dist/cli.js +69 -21
  12. package/dist/collect-cursor-payloads.d.ts +4 -3
  13. package/dist/collect-cursor-payloads.js +8 -5
  14. package/dist/cursor-checkpoints.d.ts +2 -2
  15. package/dist/cursor-payload-contract.d.ts +5 -5
  16. package/dist/cursor-payload-contract.js +6 -0
  17. package/dist/cursor-settings.d.ts +9 -4
  18. package/dist/cursor-settings.js +80 -10
  19. package/dist/health.d.ts +3 -1
  20. package/dist/health.js +13 -1
  21. package/dist/hooks/cursor-hooks-mapper.d.ts +3 -3
  22. package/dist/hooks/cursor-hooks-mapper.js +1 -1
  23. package/dist/hooks/cursor-hooks-reader.d.ts +2 -0
  24. package/dist/hooks/cursor-hooks-reader.js +10 -3
  25. package/dist/install/cursor.d.ts +34 -0
  26. package/dist/install/cursor.js +193 -0
  27. package/dist/install/index.d.ts +6 -4
  28. package/dist/install/index.js +6 -1
  29. package/dist/lib/client.d.ts +7 -0
  30. package/dist/lib/client.js +17 -0
  31. package/dist/lib/config.js +7 -2
  32. package/dist/lib/project-resolver.d.ts +5 -4
  33. package/dist/lib/project-resolver.js +57 -11
  34. package/dist/lib/repo-path-safety.d.ts +35 -0
  35. package/dist/lib/repo-path-safety.js +102 -0
  36. package/dist/lib/spawn-arg-safety.d.ts +25 -0
  37. package/dist/lib/spawn-arg-safety.js +49 -0
  38. package/dist/lib/transport-security.d.ts +1 -0
  39. package/dist/lib/transport-security.js +1 -1
  40. package/dist/readers/claude.d.ts +54 -6
  41. package/dist/readers/claude.js +154 -2
  42. package/dist/readers/cursor.d.ts +10 -7
  43. package/dist/readers/cursor.js +113 -17
  44. package/dist/risk-scanner.js +7 -0
  45. package/dist/server.d.ts +20 -3
  46. package/dist/server.js +101 -67
  47. package/dist/state.js +7 -2
  48. package/dist/sync.d.ts +13 -2
  49. package/dist/sync.js +86 -58
  50. 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" ||
@@ -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 expected by the db90 ingest API. */
46
- export interface Db90Payload extends IngestPayload {
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: string | null;
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 ToDb90PayloadOptions {
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 a db90 ingest payload. */
83
- export declare function mapTranscriptTurn(turn: ClaudeTranscriptTurn, options?: ToDb90PayloadOptions): Db90Payload;
130
+ /** Converts a Claude transcript turn to parent chat and derivative tool-use payloads. */
131
+ export declare function mapTranscriptTurn(turn: ClaudeTranscriptTurn, options?: ToClaudePayloadOptions): ClaudeMappedPayload[];
@@ -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 a db90 ingest payload. */
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
- return payload;
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
  }
@@ -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 Db90CursorPayloadMetadata = {
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 CursorDb90Payload extends IngestPayload {
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: Db90CursorPayloadMetadata;
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): CursorDb90Payload[];
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): CursorDb90Payload | null;
132
- export declare function mapEvent(row: CursorRow, workspacePath: string, projectId?: string, pricing?: PricingConfig): CursorDb90Payload | null;
133
- export declare function mapTranscriptTurn(turn: CursorTranscriptTurn, projectId?: string, pricing?: PricingConfig, model?: string): CursorDb90Payload;
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 {};