@aixle/insights 0.1.1 → 0.2.1-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 (53) hide show
  1. package/README.md +154 -3
  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 +10 -1
  7. package/dist/auth/flow.js +38 -5
  8. package/dist/auth/keycloak.d.ts +1 -1
  9. package/dist/auth/keycloak.js +20 -1
  10. package/dist/cli.d.ts +7 -3
  11. package/dist/cli.js +87 -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 +7 -0
  17. package/dist/cursor-settings.d.ts +9 -4
  18. package/dist/cursor-settings.js +80 -10
  19. package/dist/cursor-store-audit.d.ts +2 -2
  20. package/dist/cursor-store-audit.js +22 -11
  21. package/dist/daily-stats-versions.d.ts +3 -1
  22. package/dist/daily-stats-versions.js +6 -7
  23. package/dist/health.d.ts +3 -1
  24. package/dist/health.js +13 -1
  25. package/dist/hooks/cursor-hooks-mapper.d.ts +3 -3
  26. package/dist/hooks/cursor-hooks-mapper.js +1 -1
  27. package/dist/hooks/cursor-hooks-reader.d.ts +2 -0
  28. package/dist/hooks/cursor-hooks-reader.js +2 -2
  29. package/dist/install/cursor.d.ts +34 -0
  30. package/dist/install/cursor.js +193 -0
  31. package/dist/install/index.d.ts +6 -4
  32. package/dist/install/index.js +6 -1
  33. package/dist/lib/client.d.ts +7 -0
  34. package/dist/lib/client.js +17 -0
  35. package/dist/lib/config.js +7 -2
  36. package/dist/lib/project-resolver.d.ts +5 -4
  37. package/dist/lib/project-resolver.js +20 -8
  38. package/dist/lib/transport-security.d.ts +13 -0
  39. package/dist/lib/transport-security.js +47 -0
  40. package/dist/pricing.d.ts +9 -1
  41. package/dist/pricing.js +39 -8
  42. package/dist/readers/claude.d.ts +54 -6
  43. package/dist/readers/claude.js +158 -6
  44. package/dist/readers/cursor-sqlite.d.ts +23 -0
  45. package/dist/readers/cursor-sqlite.js +68 -0
  46. package/dist/readers/cursor.d.ts +11 -8
  47. package/dist/readers/cursor.js +149 -31
  48. package/dist/server.d.ts +20 -3
  49. package/dist/server.js +101 -67
  50. package/dist/state.js +7 -2
  51. package/dist/sync.d.ts +4 -2
  52. package/dist/sync.js +61 -46
  53. package/package.json +2 -2
@@ -10,8 +10,8 @@ import { basename, dirname, join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { homedir } from "node:os";
12
12
  import { glob } from "glob";
13
- import Database from "better-sqlite3";
14
13
  import { scanText } from "../risk-scanner.js";
14
+ import { openCursorSqliteReadonly } from "./cursor-sqlite.js";
15
15
  // ─── Reader: paths & SQLite ──────────────────────────────────────────────────
16
16
  export function cursorUserDir() {
17
17
  switch (process.platform) {
@@ -43,14 +43,19 @@ function logDbTables(db, dbPath, label) {
43
43
  console.log(` tables: ${tables.join(", ") || "(none)"}`);
44
44
  }
45
45
  /** Smoke-test better-sqlite3 against the global Cursor state DB (CUR-V02 / verify scripts). */
46
- export function probeCursorGlobalStateDb(verbose = false) {
47
- const dbPath = join(cursorUserDir(), "globalStorage", "state.vscdb");
46
+ export function probeCursorGlobalStateDb(verbose = false, baseDir) {
47
+ const userDir = baseDir ?? cursorUserDir();
48
+ const dbPath = join(userDir, "globalStorage", "state.vscdb");
49
+ const opened = openCursorSqliteReadonly(dbPath, { rootDir: userDir });
50
+ if (!opened.ok) {
51
+ console.error(` [probe] failed to read ${dbPath}: ${opened.message}`);
52
+ return false;
53
+ }
54
+ const db = opened.db;
48
55
  try {
49
- const db = new Database(dbPath, { readonly: true });
50
56
  const row = db
51
57
  .prepare(`SELECT count(*) AS c FROM ${STATE_TABLE} WHERE key LIKE 'aiCodeTracking.dailyStats%'`)
52
58
  .get();
53
- db.close();
54
59
  if (verbose) {
55
60
  console.log(` [probe] global state.vscdb OK — ${row.c} dailyStats key(s)`);
56
61
  }
@@ -61,6 +66,9 @@ export function probeCursorGlobalStateDb(verbose = false) {
61
66
  console.error(` [probe] failed to read ${dbPath}: ${msg}`);
62
67
  return false;
63
68
  }
69
+ finally {
70
+ db.close();
71
+ }
64
72
  }
65
73
  // ─── Legacy: cursor.db / CursorRequestFeedback ─────────────────────────────────
66
74
  export function findCursorDbs(baseDir) {
@@ -72,10 +80,13 @@ export function findCursorDbs(baseDir) {
72
80
  return [];
73
81
  }
74
82
  }
75
- function readLegacyFromDb(dbPath, since, workspacePath, verbose) {
83
+ function readLegacyFromDb(dbPath, since, workspacePath, rootDir, verbose) {
76
84
  let db = null;
77
85
  try {
78
- db = new Database(dbPath, { readonly: true });
86
+ const opened = openCursorSqliteReadonly(dbPath, { rootDir });
87
+ if (!opened.ok)
88
+ return [];
89
+ db = opened.db;
79
90
  if (verbose)
80
91
  logDbTables(db, dbPath, "cursor.db");
81
92
  if (!tableExists(db, LEGACY_TABLE))
@@ -105,13 +116,14 @@ function readLegacyFromDb(dbPath, since, workspacePath, verbose) {
105
116
  }
106
117
  }
107
118
  export function readLegacyEvents(since, baseDir, verbose = false) {
119
+ const rootDir = baseDir ?? cursorUserDir();
108
120
  const dbPaths = findCursorDbs(baseDir);
109
121
  if (verbose)
110
122
  console.log(`Found ${dbPaths.length} legacy cursor.db file(s)`);
111
123
  const results = [];
112
124
  for (const dbPath of dbPaths) {
113
125
  const workspacePath = dbPath.replace(/[\\/]cursor\.db$/, "");
114
- for (const row of readLegacyFromDb(dbPath, since, workspacePath, verbose)) {
126
+ for (const row of readLegacyFromDb(dbPath, since, workspacePath, rootDir, verbose)) {
115
127
  results.push({ row, workspacePath });
116
128
  }
117
129
  }
@@ -239,10 +251,13 @@ export function findStateVscDbs(baseDir) {
239
251
  }
240
252
  return results;
241
253
  }
242
- function readDailyStatsFromDb(dbPath, since, verbose) {
254
+ function readDailyStatsFromDb(dbPath, since, rootDir, verbose) {
243
255
  let db = null;
244
256
  try {
245
- db = new Database(dbPath, { readonly: true });
257
+ const opened = openCursorSqliteReadonly(dbPath, { rootDir });
258
+ if (!opened.ok)
259
+ return [];
260
+ db = opened.db;
246
261
  if (verbose)
247
262
  logDbTables(db, dbPath, "state.vscdb");
248
263
  if (!tableExists(db, STATE_TABLE))
@@ -264,7 +279,7 @@ function readDailyStatsFromDb(dbPath, since, verbose) {
264
279
  if (!dateMatch)
265
280
  continue;
266
281
  const date = dateMatch[1];
267
- if (since && date <= since.toISOString().slice(0, 10))
282
+ if (since && date < since.toISOString().slice(0, 10))
268
283
  continue;
269
284
  let parsed;
270
285
  try {
@@ -285,6 +300,7 @@ function readDailyStatsFromDb(dbPath, since, verbose) {
285
300
  }
286
301
  }
287
302
  function readDailyStatsRaw(since, baseDir, verbose) {
303
+ const rootDir = baseDir ?? cursorUserDir();
288
304
  const dbPaths = findStateVscDbs(baseDir);
289
305
  if (verbose) {
290
306
  console.log(`Searching: ${baseDir ?? cursorUserDir()}`);
@@ -292,7 +308,7 @@ function readDailyStatsRaw(since, baseDir, verbose) {
292
308
  }
293
309
  const raw = [];
294
310
  for (const dbPath of dbPaths) {
295
- raw.push(...readDailyStatsFromDb(dbPath, since, verbose));
311
+ raw.push(...readDailyStatsFromDb(dbPath, since, rootDir, verbose));
296
312
  }
297
313
  return raw;
298
314
  }
@@ -339,10 +355,13 @@ function dedupeRecentCommitSnapshots(entries) {
339
355
  }
340
356
  return [...byKey.values()];
341
357
  }
342
- function readRecentCommitFromDb(dbPath, since, verbose) {
358
+ function readRecentCommitFromDb(dbPath, since, rootDir, verbose) {
343
359
  let db = null;
344
360
  try {
345
- db = new Database(dbPath, { readonly: true });
361
+ const opened = openCursorSqliteReadonly(dbPath, { rootDir });
362
+ if (!opened.ok)
363
+ return [];
364
+ db = opened.db;
346
365
  if (!tableExists(db, STATE_TABLE))
347
366
  return [];
348
367
  const row = db
@@ -384,13 +403,14 @@ function readRecentCommitFromDb(dbPath, since, verbose) {
384
403
  }
385
404
  }
386
405
  export function readRecentCommitSnapshots(since, baseDir, verbose = false) {
406
+ const rootDir = baseDir ?? cursorUserDir();
387
407
  const dbPaths = findStateVscDbs(baseDir);
388
408
  if (verbose) {
389
409
  console.log(`Searching recentCommit: ${baseDir ?? cursorUserDir()}`);
390
410
  }
391
411
  const found = [];
392
412
  for (const dbPath of dbPaths) {
393
- found.push(...readRecentCommitFromDb(dbPath, since, verbose));
413
+ found.push(...readRecentCommitFromDb(dbPath, since, rootDir, verbose));
394
414
  }
395
415
  return dedupeRecentCommitSnapshots(found);
396
416
  }
@@ -427,12 +447,71 @@ function toIsoFromMs(value) {
427
447
  const date = new Date(value);
428
448
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
429
449
  }
450
+ /** Coerce epoch ms/seconds, numeric strings, or ISO-8601 into an ISO timestamp. */
451
+ function coerceTranscriptTimestamp(value) {
452
+ if (value == null)
453
+ return null;
454
+ if (typeof value === "number")
455
+ return toIsoFromMs(value);
456
+ if (typeof value !== "string")
457
+ return null;
458
+ const trimmed = value.trim();
459
+ if (!trimmed)
460
+ return null;
461
+ const fromEpoch = toIsoString(trimmed);
462
+ if (fromEpoch)
463
+ return fromEpoch;
464
+ const ms = Date.parse(trimmed);
465
+ if (Number.isNaN(ms))
466
+ return null;
467
+ return new Date(ms).toISOString();
468
+ }
469
+ /** Prefer explicit per-line / per-message times when Cursor includes them. */
470
+ function extractLineOccurredAt(entry) {
471
+ const candidates = [
472
+ entry.timestamp,
473
+ entry.createdAt,
474
+ entry.unixMs,
475
+ entry.message?.timestamp,
476
+ entry.message?.createdAt,
477
+ ];
478
+ for (const candidate of candidates) {
479
+ const iso = coerceTranscriptTimestamp(candidate);
480
+ if (iso)
481
+ return iso;
482
+ }
483
+ return null;
484
+ }
485
+ /**
486
+ * Spread turns across [start, end] when the JSONL has no per-message times.
487
+ * Prevents first-sync backfill from collapsing every turn onto lastUpdatedAt/mtime
488
+ * (DB90DV-605 weekly chart spike).
489
+ */
490
+ function interpolateTurnOccurredAt(startIso, endIso, index, total, fallbackIso) {
491
+ if (total <= 0)
492
+ return fallbackIso;
493
+ // Single turn: prefer session end (lastUpdatedAt) — matches prior composer-header behavior.
494
+ if (total === 1)
495
+ return endIso ?? startIso ?? fallbackIso;
496
+ const startMs = startIso ? Date.parse(startIso) : NaN;
497
+ const endMs = endIso ? Date.parse(endIso) : NaN;
498
+ if (Number.isNaN(startMs) || Number.isNaN(endMs)) {
499
+ return startIso ?? endIso ?? fallbackIso;
500
+ }
501
+ if (endMs <= startMs)
502
+ return startIso ?? fallbackIso;
503
+ const ms = startMs + ((endMs - startMs) * index) / (total - 1);
504
+ return new Date(ms).toISOString();
505
+ }
430
506
  function readComposerHeaders(baseDir) {
431
507
  const userDir = baseDir ?? cursorUserDir();
432
508
  const dbPath = join(userDir, "globalStorage", "state.vscdb");
433
509
  let db = null;
434
510
  try {
435
- db = new Database(dbPath, { readonly: true });
511
+ const opened = openCursorSqliteReadonly(dbPath, { rootDir: userDir });
512
+ if (!opened.ok)
513
+ return new Map();
514
+ db = opened.db;
436
515
  if (!tableExists(db, STATE_TABLE))
437
516
  return new Map();
438
517
  const row = db
@@ -468,6 +547,7 @@ function readComposerHeaders(baseDir) {
468
547
  composerId,
469
548
  name: typeof composer.name === "string" ? composer.name : null,
470
549
  workspacePath: typeof uri?.fsPath === "string" ? uri.fsPath : null,
550
+ createdAt: toIsoFromMs(composer.createdAt),
471
551
  lastUpdatedAt: toIsoFromMs(composer.lastUpdatedAt),
472
552
  });
473
553
  }
@@ -540,11 +620,15 @@ function workspaceFromTranscriptFile(filePath) {
540
620
  const MAX_TRANSCRIPT_BYTES = 50 * 1024 * 1024; // 50 MB
541
621
  export async function parseCursorTranscriptFile(filePath, composerHeaders, verbose = false) {
542
622
  let fileSize = 0;
543
- let occurredAt = new Date().toISOString();
623
+ let fileMtimeIso = new Date().toISOString();
624
+ let fileBirthIso = null;
544
625
  try {
545
626
  const stat = statSync(filePath);
546
627
  fileSize = stat.size;
547
- occurredAt = stat.mtime.toISOString();
628
+ fileMtimeIso = stat.mtime.toISOString();
629
+ if (stat.birthtime && !Number.isNaN(stat.birthtime.getTime()) && stat.birthtime.getTime() > 0) {
630
+ fileBirthIso = stat.birthtime.toISOString();
631
+ }
548
632
  }
549
633
  catch {
550
634
  return [];
@@ -557,15 +641,25 @@ export async function parseCursorTranscriptFile(filePath, composerHeaders, verbo
557
641
  }
558
642
  const sessionId = basename(filePath, ".jsonl");
559
643
  const header = composerHeaders.get(sessionId);
560
- if (header?.lastUpdatedAt)
561
- occurredAt = header.lastUpdatedAt;
562
- const turns = [];
644
+ const sessionFallback = header?.lastUpdatedAt ?? fileMtimeIso;
645
+ const sessionStart = header?.createdAt ?? fileBirthIso ?? sessionFallback;
646
+ const sessionEnd = header?.lastUpdatedAt ?? fileMtimeIso;
647
+ const drafts = [];
563
648
  let currentPromptParts = [];
564
649
  let currentAssistantParts = [];
650
+ let currentTurnOccurredAt = null;
565
651
  let turnIndex = 0;
566
652
  const hasher = createHash("sha256");
567
653
  const stream = createReadStream(filePath, { encoding: "utf-8" });
568
654
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
655
+ const noteLineTime = (entry) => {
656
+ const lineAt = extractLineOccurredAt(entry);
657
+ if (!lineAt)
658
+ return;
659
+ // Prefer the first timestamp in the turn (usually the user message).
660
+ if (!currentTurnOccurredAt)
661
+ currentTurnOccurredAt = lineAt;
662
+ };
569
663
  const finalizeTurn = () => {
570
664
  const promptText = currentPromptParts.join("\n\n").trim();
571
665
  const assistantText = currentAssistantParts.join("\n\n").trim();
@@ -573,14 +667,14 @@ export async function parseCursorTranscriptFile(filePath, composerHeaders, verbo
573
667
  return;
574
668
  const risk = scanText(promptText);
575
669
  turnIndex += 1;
576
- turns.push({
670
+ drafts.push({
577
671
  turnId: `${sessionId}:${turnIndex}`,
578
672
  sessionId,
579
673
  filePath,
580
674
  fileSize,
581
675
  workspacePath: header?.workspacePath ?? workspaceFromTranscriptFile(filePath),
582
676
  composerName: header?.name ?? null,
583
- occurredAt,
677
+ turnOccurredAt: currentTurnOccurredAt,
584
678
  promptText,
585
679
  assistantText,
586
680
  tokensIn: estimateTokens(promptText),
@@ -589,6 +683,7 @@ export async function parseCursorTranscriptFile(filePath, composerHeaders, verbo
589
683
  riskScore: risk.risk_score,
590
684
  riskCategories: risk.risk_categories,
591
685
  });
686
+ currentTurnOccurredAt = null;
592
687
  };
593
688
  let lineNumber = 0;
594
689
  try {
@@ -617,9 +712,11 @@ export async function parseCursorTranscriptFile(filePath, composerHeaders, verbo
617
712
  currentPromptParts = [];
618
713
  currentAssistantParts = [];
619
714
  }
715
+ noteLineTime(entry);
620
716
  currentPromptParts.push(...texts.map(stripUserQueryWrapper).filter((text) => text.length > 0));
621
717
  }
622
718
  else if (entry.role === "assistant") {
719
+ noteLineTime(entry);
623
720
  currentAssistantParts.push(...texts.map((text) => text.trim()).filter((text) => text.length > 0));
624
721
  }
625
722
  }
@@ -636,9 +733,16 @@ export async function parseCursorTranscriptFile(filePath, composerHeaders, verbo
636
733
  }
637
734
  finalizeTurn();
638
735
  const contentHash = hasher.digest("hex").slice(0, 32);
639
- for (const t of turns)
640
- t.contentHash = contentHash;
641
- return turns;
736
+ const total = drafts.length;
737
+ return drafts.map((draft, index) => {
738
+ const { turnOccurredAt, ...rest } = draft;
739
+ return {
740
+ ...rest,
741
+ contentHash,
742
+ occurredAt: turnOccurredAt ??
743
+ interpolateTurnOccurredAt(sessionStart, sessionEnd, index, total, sessionFallback),
744
+ };
745
+ });
642
746
  }
643
747
  export async function readCursorTranscriptSessions(cursorUserBaseDir, transcriptProjectDirs, verbose = false) {
644
748
  const composerHeaders = readComposerHeaders(cursorUserBaseDir);
@@ -702,8 +806,12 @@ function pick(obj, ...keys) {
702
806
  }
703
807
  return typeof cur === "number" ? cur : null;
704
808
  }
809
+ function dailyStatsSessionId(date, eventType, modelKey) {
810
+ const suffix = modelKey ? `${eventType}:${modelKey}` : eventType;
811
+ return `cursor:daily_stats:${date}:${suffix}`;
812
+ }
705
813
  function buildPayload(opts) {
706
- const { eventType, tokensIn, tokensOut, costUsd, occurredAt, dbPath, model = "unknown", projectId, costModel = LINE_COST_MODEL, } = opts;
814
+ const { eventType, tokensIn, tokensOut, costUsd, occurredAt, dbPath, date, model = "unknown", modelResolution, modelKey, projectId, costModel = LINE_COST_MODEL, } = opts;
707
815
  const payload = {
708
816
  tool_name: "cursor",
709
817
  event_type: eventType,
@@ -713,18 +821,20 @@ function buildPayload(opts) {
713
821
  cost_usd: costUsd,
714
822
  occurred_at: occurredAt,
715
823
  metadata: {
824
+ session_id: dailyStatsSessionId(date, eventType, modelKey),
716
825
  cursor_session_id: null,
717
826
  ...cursorWorkspaceMetadata(dbPath),
718
827
  cost_model: costModel,
719
828
  scannable: false,
720
829
  risk_level: "none",
830
+ ...(modelResolution !== undefined ? { model_resolution: modelResolution } : {}),
721
831
  },
722
832
  };
723
833
  if (projectId)
724
834
  payload.project_id = projectId;
725
835
  return payload;
726
836
  }
727
- export function mapDailyStats(entry, projectId, pricing = DEFAULT_CURSOR_PRICING, model) {
837
+ export function mapDailyStats(entry, projectId, pricing = DEFAULT_CURSOR_PRICING, model, modelResolution) {
728
838
  const { date, value, dbPath } = entry;
729
839
  const occurredAt = `${date}T00:00:00.000Z`;
730
840
  const results = [];
@@ -740,10 +850,12 @@ export function mapDailyStats(entry, projectId, pricing = DEFAULT_CURSOR_PRICING
740
850
  eventType: "completion",
741
851
  tokensIn: tabSuggested,
742
852
  tokensOut: tabAccepted,
743
- costUsd: computeLineCost("completion", tabSuggested, pricing),
853
+ costUsd: computeLineCost("completion", tabAccepted, pricing),
744
854
  occurredAt,
745
855
  dbPath,
856
+ date,
746
857
  model,
858
+ modelResolution,
747
859
  projectId,
748
860
  }));
749
861
  }
@@ -755,7 +867,9 @@ export function mapDailyStats(entry, projectId, pricing = DEFAULT_CURSOR_PRICING
755
867
  costUsd: computeLineCost("chat", composerSuggested, pricing),
756
868
  occurredAt,
757
869
  dbPath,
870
+ date,
758
871
  model,
872
+ modelResolution,
759
873
  projectId,
760
874
  }));
761
875
  }
@@ -776,7 +890,9 @@ export function mapDailyStats(entry, projectId, pricing = DEFAULT_CURSOR_PRICING
776
890
  costUsd: computeTokenCost("chat", tokensIn, tokensOut, pricing),
777
891
  occurredAt,
778
892
  dbPath,
893
+ date,
779
894
  model,
895
+ modelKey: model,
780
896
  projectId,
781
897
  costModel: TOKEN_COST_MODEL,
782
898
  }));
@@ -788,7 +904,7 @@ export function mapDailyStats(entry, projectId, pricing = DEFAULT_CURSOR_PRICING
788
904
  * Cursor only keeps one recent commit row (overwritten on each new commit).
789
905
  * Line-cost math still follows the chat-style line proxy (`computeLineCost("chat", …)`); only `event_type` differs.
790
906
  */
791
- export function mapRecentCommit(entry, projectId, pricing = DEFAULT_CURSOR_PRICING, model) {
907
+ export function mapRecentCommit(entry, projectId, pricing = DEFAULT_CURSOR_PRICING, model, modelResolution) {
792
908
  const { value: obj, dbPath } = entry;
793
909
  const occurredAt = toIsoString(obj.timestamp);
794
910
  if (!occurredAt)
@@ -834,6 +950,7 @@ export function mapRecentCommit(entry, projectId, pricing = DEFAULT_CURSOR_PRICI
834
950
  : undefined,
835
951
  scannable: false,
836
952
  risk_level: "none",
953
+ ...(modelResolution !== undefined ? { model_resolution: modelResolution } : {}),
837
954
  },
838
955
  };
839
956
  if (projectId)
@@ -870,7 +987,7 @@ export function mapEvent(row, workspacePath, projectId, pricing = DEFAULT_CURSOR
870
987
  payload.project_id = projectId;
871
988
  return payload;
872
989
  }
873
- export function mapTranscriptTurn(turn, projectId, pricing = DEFAULT_CURSOR_PRICING, model) {
990
+ export function mapTranscriptTurn(turn, projectId, pricing = DEFAULT_CURSOR_PRICING, model, modelResolution) {
874
991
  const payload = {
875
992
  tool_name: "cursor",
876
993
  event_type: "chat",
@@ -892,6 +1009,7 @@ export function mapTranscriptTurn(turn, projectId, pricing = DEFAULT_CURSOR_PRIC
892
1009
  composer_name: turn.composerName ?? undefined,
893
1010
  prompt_text: turn.promptText || undefined,
894
1011
  assistant_text: turn.assistantText || undefined,
1012
+ ...(modelResolution !== undefined ? { model_resolution: modelResolution } : {}),
895
1013
  },
896
1014
  };
897
1015
  if (projectId)
package/dist/server.d.ts CHANGED
@@ -7,8 +7,25 @@ export declare const SYNC_NOW_INPUT_SCHEMA: z.ZodObject<{
7
7
  cursor: "cursor";
8
8
  }>>>;
9
9
  }, z.core.$strict>;
10
- /** Structured status for `db90_status` — tolerates missing/malformed credentials and state. */
11
- export declare function buildDb90StatusPayload(): Promise<Record<string, unknown>>;
10
+ /**
11
+ * Whether a `credential_validation_failed` warning should mirror to stderr.
12
+ * Mirrors for one-shot/startup sources so an installed-but-uninitialized MCP is
13
+ * visible, but NOT for the recurring background `"interval"` tick — that fires
14
+ * every SYNC_INTERVAL_MS for the whole process lifetime and would spam the logs.
15
+ * The event is always written to mcp.log regardless of this return value.
16
+ */
17
+ export declare function shouldMirrorMissingCredentials(source: string): boolean;
18
+ /** Structured status for `aixle_insights_status` — tolerates missing/malformed credentials and state. */
19
+ export declare function buildAixleInsightsStatusPayload(): Promise<Record<string, unknown>>;
12
20
  /** In-process MCP server instance (stdio not attached). */
13
- export declare function createDb90McpServer(): McpServer;
21
+ export declare function createAixleInsightsMcpServer(): McpServer;
22
+ /**
23
+ * Subscribes `shutdown` to the given stream's "end" and "close" events.
24
+ * Closing stdin is how the OS signals a stdio-transport MCP server that its
25
+ * parent process is gone — this fires even when the parent can't deliver a
26
+ * signal (e.g. it was itself SIGKILL'd, or the OS reparented this process
27
+ * without ever sending one). Exported standalone so it's testable with a
28
+ * plain EventEmitter instead of the real process.stdin.
29
+ */
30
+ export declare function wireParentExitShutdown(stdin: Pick<NodeJS.ReadStream, "on">, shutdown: () => void): void;
14
31
  export declare function startServer(): Promise<void>;