iteration-cli 0.18.0 → 0.19.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/cli.js CHANGED
@@ -7,9 +7,8 @@ var __export = (target, all) => {
7
7
 
8
8
  // src/cli.ts
9
9
  import { spawnSync } from "node:child_process";
10
- import { promises as fs25 } from "node:fs";
11
- import os12 from "node:os";
12
- import path31 from "node:path";
10
+ import { promises as fs29 } from "node:fs";
11
+ import path36 from "node:path";
13
12
  import { parseArgs } from "node:util";
14
13
 
15
14
  // ../trace-vault/packages/cli/dist/index.js
@@ -1580,8 +1579,8 @@ function finalizeIssue(iss, ctx, config22) {
1580
1579
  else
1581
1580
  iss.schema = iss.inst;
1582
1581
  }
1583
- const schemaError2 = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
1584
- const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError2?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config22.customError?.(iss)) ?? unwrapMessage(config22.localeError?.(iss)) ?? "Invalid input";
1582
+ const schemaError22 = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
1583
+ const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError22?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config22.customError?.(iss)) ?? unwrapMessage(config22.localeError?.(iss)) ?? "Invalid input";
1585
1584
  const full = {};
1586
1585
  for (const k of Object.keys(iss)) {
1587
1586
  if (k === "inst" || k === "schema" || k === "continue" || k === "input" || k === "__proto__")
@@ -6197,13 +6196,13 @@ var recordProcessor = (schema, ctx, _json, params) => {
6197
6196
  ...params,
6198
6197
  path: [...params.path, "propertyNames"]
6199
6198
  });
6200
- let pending = pendingRecords.get(ctx);
6201
- if (!pending) {
6202
- pending = [];
6203
- pendingRecords.set(ctx, pending);
6199
+ let pending2 = pendingRecords.get(ctx);
6200
+ if (!pending2) {
6201
+ pending2 = [];
6202
+ pendingRecords.set(ctx, pending2);
6204
6203
  ctx.deferred.push(() => rewriteKeyNames(ctx));
6205
6204
  }
6206
- pending.push(schema);
6205
+ pending2.push(schema);
6207
6206
  }
6208
6207
  json2.additionalProperties = processSchema(def.valueType, ctx, {
6209
6208
  ...params,
@@ -12927,7 +12926,7 @@ var connectionSchema = {
12927
12926
  dataDirectory: string2(),
12928
12927
  organizationId: uuid2().optional(),
12929
12928
  organizationName: string2().min(1).max(160).optional(),
12930
- destination: literal("vault").optional()
12929
+ destination: _enum(["vault", "host"]).optional()
12931
12930
  };
12932
12931
  var configSchema = union([
12933
12932
  object({ ...connectionSchema, codexHome: string2() }).strict(),
@@ -13347,6 +13346,7 @@ async function nextBatch(paths, maxBytes, first = /* @__PURE__ */ new Set()) {
13347
13346
  }
13348
13347
  var recentMessagesSchema = object({
13349
13348
  version: literal(1),
13349
+ captureCwds: record(string2(), string2()).optional(),
13350
13350
  cursors: record(
13351
13351
  string2(),
13352
13352
  number2().int().min(0).max(Number.MAX_SAFE_INTEGER)
@@ -13644,9 +13644,9 @@ async function lastCompleteRecordOffset(filename, size) {
13644
13644
  }
13645
13645
  async function rolloutFiles(root) {
13646
13646
  const files = [];
13647
- const pending = [root];
13648
- while (pending.length > 0) {
13649
- const directory = pending.pop();
13647
+ const pending2 = [root];
13648
+ while (pending2.length > 0) {
13649
+ const directory = pending2.pop();
13650
13650
  if (directory === void 0) continue;
13651
13651
  let entries2;
13652
13652
  try {
@@ -13660,7 +13660,7 @@ async function rolloutFiles(root) {
13660
13660
  }
13661
13661
  for (const entry of entries2) {
13662
13662
  const filename = path4.join(directory, entry.name);
13663
- if (entry.isDirectory()) pending.push(filename);
13663
+ if (entry.isDirectory()) pending2.push(filename);
13664
13664
  else if (entry.isFile() && /^rollout-.+\.jsonl$/.test(entry.name))
13665
13665
  files.push(filename);
13666
13666
  if (files.length > 1e4)
@@ -13705,9 +13705,6 @@ async function claudeFiles(root) {
13705
13705
  function isSessionFile(filename) {
13706
13706
  return !filename.includes(`${path4.sep}subagents${path4.sep}`);
13707
13707
  }
13708
- async function countSavedSessions(source, home) {
13709
- return (await sourceFiles(source, home)).filter(isSessionFile).length;
13710
- }
13711
13708
  async function sourceFilesNewestFirst(source, home) {
13712
13709
  const files = await sourceFiles(source, home);
13713
13710
  const listed = [];
@@ -13951,6 +13948,22 @@ function recordScope(source, record32, filename, cwds) {
13951
13948
  cwd: record32.input?.cwd ?? (record32.input?.workspace_roots?.length === 1 ? record32.input.workspace_roots[0] : void 0)
13952
13949
  };
13953
13950
  }
13951
+ function captureRecordLocation(source, line, filename, cwds) {
13952
+ let decoded;
13953
+ try {
13954
+ decoded = JSON.parse(line);
13955
+ } catch {
13956
+ delete cwds[filename];
13957
+ return null;
13958
+ }
13959
+ const parsed = scopeRecordSchema.safeParse(decoded);
13960
+ if (!parsed.success) {
13961
+ delete cwds[filename];
13962
+ return null;
13963
+ }
13964
+ const cwd = recordScope(source, parsed.data, filename, cwds).cwd;
13965
+ return cwd && path6.isAbsolute(cwd) ? resolveCaptureLocation(cwd) : null;
13966
+ }
13954
13967
  function authorizeVaultRecord(policy, source, line, filename, cwds, accounts = null) {
13955
13968
  let decoded;
13956
13969
  try {
@@ -15216,11 +15229,14 @@ async function vaultAccountIndex(config22) {
15216
15229
  await loadClaudeAccountLedger(config22.dataDirectory)
15217
15230
  ) : null;
15218
15231
  }
15219
- async function scanCaptureState(config22, state, policy, accounts) {
15232
+ async function scanCaptureState(config22, state, policy, accounts, host) {
15220
15233
  if (config22.destination === "vault" && policy === void 0)
15221
15234
  throw new Error(
15222
15235
  "Company vault scanning requires a current capture policy."
15223
15236
  );
15237
+ if (config22.destination === "host" && host === void 0)
15238
+ throw new Error("Host collection requires a current capture policy.");
15239
+ const revision = host?.revision ?? policy?.revision;
15224
15240
  const cwds = { ...state.captureCwds };
15225
15241
  const accountIndex = accounts === void 0 ? await vaultAccountIndex(config22) : accounts;
15226
15242
  return {
@@ -15229,15 +15245,21 @@ async function scanCaptureState(config22, state, policy, accounts) {
15229
15245
  persistEvidence: async () => {
15230
15246
  await accountIndex?.persistNativeTools(config22.dataDirectory);
15231
15247
  },
15232
- checkpoint: policy === void 0 ? {} : { captureCwds: cwds, captureRevision: policy.revision }
15248
+ checkpoint: revision === void 0 ? {} : { captureCwds: cwds, captureRevision: revision }
15233
15249
  };
15234
15250
  }
15235
- async function scanSourceOnce(config22, paths, signal, policy, accounts) {
15251
+ async function scanSourceOnce(config22, paths, signal, policy, accounts, host) {
15236
15252
  signal?.throwIfAborted();
15237
15253
  const source = collectorSource(config22);
15238
15254
  const sourceDirectory = path14.resolve(collectorSourceDirectory(config22));
15239
15255
  const state = await loadState(paths);
15240
- const captureState = await scanCaptureState(config22, state, policy, accounts);
15256
+ const captureState = await scanCaptureState(
15257
+ config22,
15258
+ state,
15259
+ policy,
15260
+ accounts,
15261
+ host
15262
+ );
15241
15263
  if (!state.initialized || state.sourceDirectory !== sourceDirectory) {
15242
15264
  throw new Error(
15243
15265
  `Collector state does not match the ${source} directory; reconnect this source.`
@@ -15363,7 +15385,7 @@ async function scanSourceOnce(config22, paths, signal, policy, accounts) {
15363
15385
  lineStart = index + 1;
15364
15386
  continue;
15365
15387
  }
15366
- const capture = policy === void 0 ? void 0 : authorizeVaultRecord(
15388
+ const capture = host !== void 0 ? host.authorizeRecord(source, line, filename, captureCwds) : policy === void 0 ? void 0 : authorizeVaultRecord(
15367
15389
  policy,
15368
15390
  source,
15369
15391
  line,
@@ -15453,7 +15475,7 @@ async function readRange(filename, start2, length) {
15453
15475
  await handle.close();
15454
15476
  }
15455
15477
  }
15456
- async function messagesInRecords(source, filename, buffer, start2, room, queuedIds) {
15478
+ async function messagesInRecords(source, filename, buffer, start2, room, queuedIds, host, cwds) {
15457
15479
  const events = [];
15458
15480
  let lineStart = 0;
15459
15481
  for (let index = 0; index < buffer.length; index += 1) {
@@ -15476,7 +15498,9 @@ async function messagesInRecords(source, filename, buffer, start2, room, queuedI
15476
15498
  filename,
15477
15499
  byteOffset
15478
15500
  );
15479
- for (const record32 of parsed) {
15501
+ const capture = host?.authorizeRecord(source, line, filename, cwds);
15502
+ if (capture === null) continue;
15503
+ for (const record32 of scopeVaultEvents(parsed, capture, line)) {
15480
15504
  if (record32.eventType !== "user_message" || queuedIds.has(record32.eventId))
15481
15505
  continue;
15482
15506
  const event3 = await redactTraceEvent(record32);
@@ -15486,17 +15510,19 @@ async function messagesInRecords(source, filename, buffer, start2, room, queuedI
15486
15510
  }
15487
15511
  return { events, consumed: lineStart };
15488
15512
  }
15489
- async function queueRecentMessages(config22, paths, limit2, signal) {
15513
+ async function queueRecentMessages(config22, paths, limit2, signal, host) {
15490
15514
  if (limit2 === void 0) return void 0;
15491
15515
  const recent = await loadRecentMessages(paths) ?? {
15492
15516
  version: 1,
15493
15517
  cursors: {},
15494
15518
  queued: [...await queuedUserMessageIds(paths, limit2)],
15495
15519
  sent: 0,
15520
+ captureCwds: {},
15496
15521
  scanned: false
15497
15522
  };
15498
15523
  if (recent.scanned) return new Set(recent.queued);
15499
15524
  const source = collectorSource(config22);
15525
+ const captureCwds = { ...recent.captureCwds };
15500
15526
  const mainCursors = (await loadState(paths)).cursors;
15501
15527
  const files = await sourceFilesNewestFirst(
15502
15528
  source,
@@ -15525,7 +15551,9 @@ async function queueRecentMessages(config22, paths, limit2, signal) {
15525
15551
  buffer,
15526
15552
  start2,
15527
15553
  room,
15528
- queuedIds
15554
+ queuedIds,
15555
+ host,
15556
+ captureCwds
15529
15557
  );
15530
15558
  for (const event3 of found.events) {
15531
15559
  events.push(event3);
@@ -15543,6 +15571,7 @@ async function queueRecentMessages(config22, paths, limit2, signal) {
15543
15571
  await saveRecentMessages(
15544
15572
  {
15545
15573
  version: 1,
15574
+ ...host === void 0 ? {} : { captureCwds },
15546
15575
  cursors,
15547
15576
  queued,
15548
15577
  sent: recent.sent,
@@ -15558,8 +15587,8 @@ async function settleRecentMessages(paths, limit2) {
15558
15587
  if (recent === null) return { sent: 0, complete: false };
15559
15588
  if (recent.queued.length === 0)
15560
15589
  return { sent: recent.sent, complete: recent.scanned };
15561
- const pending = await pendingEventIds(paths);
15562
- const queued = recent.queued.filter((id) => pending.has(id));
15590
+ const pending2 = await pendingEventIds(paths);
15591
+ const queued = recent.queued.filter((id) => pending2.has(id));
15563
15592
  const sent = recent.sent + recent.queued.length - queued.length;
15564
15593
  if (queued.length !== recent.queued.length)
15565
15594
  await saveRecentMessages(
@@ -15594,12 +15623,20 @@ function retryAfterMs(response) {
15594
15623
  const milliseconds = /^\d+$/.test(header) ? Number(header) * 1e3 : Date.parse(header) - Date.now();
15595
15624
  return Number.isFinite(milliseconds) ? Math.max(1e3, Math.min(milliseconds, 3e5)) : 5e3;
15596
15625
  }
15597
- async function uploadPendingOnce(config22, paths, fetcher = collectorFetch, signal, onAcknowledged, policy, first) {
15626
+ function captureBatch(pending2, host) {
15627
+ const first = pending2[0];
15628
+ const batchKey = host?.batchKey;
15629
+ if (first === void 0 || batchKey === void 0) return pending2;
15630
+ const key = batchKey(first);
15631
+ return pending2.filter((event3) => batchKey(event3) === key);
15632
+ }
15633
+ async function uploadPendingOnce(config22, paths, fetcher = collectorFetch, signal, onAcknowledged, policy, first, host) {
15598
15634
  let uploaded = 0;
15599
15635
  for (let attempt = 0; attempt < MAX_UPLOADS_PER_TICK; attempt += 1) {
15600
15636
  signal?.throwIfAborted();
15601
15637
  await recheckQueuedAccounts(config22, paths, policy);
15602
- const batch = await nextBatch(paths, MAX_UPLOAD_BYTES, first);
15638
+ const pending2 = await nextBatch(paths, MAX_UPLOAD_BYTES, first);
15639
+ const batch = captureBatch(pending2, host);
15603
15640
  if (batch.length === 0) return { uploaded, retryAfterMs: null };
15604
15641
  const body = JSON.stringify({ events: batch });
15605
15642
  if (Buffer.byteLength(body, "utf8") > MAX_REQUEST_BYTES) {
@@ -15668,8 +15705,33 @@ async function recheckQueuedAccounts(config22, paths, policy) {
15668
15705
  (event3) => reauthorizeVaultEvent(policy, event3, accounts)
15669
15706
  );
15670
15707
  }
15708
+ async function prepareHostCapture(config22, paths, host) {
15709
+ if (config22.destination === "host" && host === void 0)
15710
+ throw new Error("Host collection requires a current capture policy.");
15711
+ if (host !== void 0) {
15712
+ if (!Number.isSafeInteger(host.revision) || host.revision <= 0)
15713
+ throw new Error("Capture revisions must be positive safe integers.");
15714
+ await rewritePendingEvents(paths, host.authorizeEvent);
15715
+ const state = await loadState(paths);
15716
+ if (state.captureRevision !== host.revision) {
15717
+ await saveState(
15718
+ {
15719
+ ...state,
15720
+ cursors: {},
15721
+ skipping: [],
15722
+ captureCwds: {},
15723
+ captureRevision: host.revision
15724
+ },
15725
+ paths
15726
+ );
15727
+ await fs9.rm(paths.recentMessages, { force: true });
15728
+ }
15729
+ }
15730
+ }
15671
15731
  async function collectorTick(config22, paths, fetcher = collectorFetch, signal, onAcknowledged, options = {}) {
15672
15732
  const recentLimit = checkedRecentLimit(config22, options.recentMessages);
15733
+ const host = options.capture;
15734
+ await prepareHostCapture(config22, paths, host);
15673
15735
  const accounts = await vaultAccountIndex(config22);
15674
15736
  const policy = config22.destination === "vault" ? await fetchVaultPolicy(config22.endpoint, config22.token, fetcher, signal) : void 0;
15675
15737
  if (policy !== void 0) {
@@ -15694,7 +15756,13 @@ async function collectorTick(config22, paths, fetcher = collectorFetch, signal,
15694
15756
  }
15695
15757
  }
15696
15758
  await redactPendingOutbox(paths);
15697
- const first = await queueRecentMessages(config22, paths, recentLimit, signal);
15759
+ const first = await queueRecentMessages(
15760
+ config22,
15761
+ paths,
15762
+ recentLimit,
15763
+ signal,
15764
+ host
15765
+ );
15698
15766
  let upload = await uploadPendingOnce(
15699
15767
  config22,
15700
15768
  paths,
@@ -15702,14 +15770,15 @@ async function collectorTick(config22, paths, fetcher = collectorFetch, signal,
15702
15770
  signal,
15703
15771
  onAcknowledged,
15704
15772
  policy,
15705
- first
15773
+ first,
15774
+ host
15706
15775
  );
15707
15776
  const scan = await outboxSize(paths) >= SCAN_PAUSE_OUTBOX_BYTES ? {
15708
15777
  discovered: 0,
15709
15778
  excludedRecords: 0,
15710
15779
  caughtUp: false,
15711
15780
  history: await savedHistoryProgress(config22, paths)
15712
- } : await scanSourceOnce(config22, paths, signal, policy, accounts);
15781
+ } : await scanSourceOnce(config22, paths, signal, policy, accounts, host);
15713
15782
  if (policy !== void 0 && scan.caughtUp) {
15714
15783
  const state = await loadState(paths);
15715
15784
  if (state.captureRecheckAt === void 0)
@@ -15726,7 +15795,8 @@ async function collectorTick(config22, paths, fetcher = collectorFetch, signal,
15726
15795
  signal,
15727
15796
  onAcknowledged,
15728
15797
  policy,
15729
- first
15798
+ first,
15799
+ host
15730
15800
  );
15731
15801
  upload = { ...next, uploaded: upload.uploaded + next.uploaded };
15732
15802
  }
@@ -15756,6 +15826,14 @@ async function lockIdentity(filename) {
15756
15826
  }
15757
15827
  var CollectorLockBusyError = class extends Error {
15758
15828
  };
15829
+ async function tryAcquireCollectorLock(paths) {
15830
+ try {
15831
+ return await acquireCollectorLock(paths);
15832
+ } catch (error22) {
15833
+ if (error22 instanceof CollectorLockBusyError) return null;
15834
+ throw error22;
15835
+ }
15836
+ }
15759
15837
  async function acquireCollectorLock(paths) {
15760
15838
  await ensurePrivateDirectory(path15.dirname(paths.lock));
15761
15839
  const databasePath = `${paths.lock}.sqlite`;
@@ -16079,10 +16157,10 @@ function mergeDefs2(...defs) {
16079
16157
  function cloneDef2(schema) {
16080
16158
  return mergeDefs2(schema._zod.def);
16081
16159
  }
16082
- function getElementAtPath2(obj, path32) {
16083
- if (!path32)
16160
+ function getElementAtPath2(obj, path37) {
16161
+ if (!path37)
16084
16162
  return obj;
16085
- return path32.reduce((acc, key) => acc?.[key], obj);
16163
+ return path37.reduce((acc, key) => acc?.[key], obj);
16086
16164
  }
16087
16165
  function promiseAllObject2(promisesObj) {
16088
16166
  const keys = Object.keys(promisesObj);
@@ -16422,11 +16500,11 @@ function explicitlyAborted2(x, startIndex = 0) {
16422
16500
  }
16423
16501
  return false;
16424
16502
  }
16425
- function prefixIssues2(path32, issues) {
16503
+ function prefixIssues2(path37, issues) {
16426
16504
  return issues.map((iss) => {
16427
16505
  var _a5;
16428
16506
  (_a5 = iss).path ?? (_a5.path = []);
16429
- iss.path.unshift(path32);
16507
+ iss.path.unshift(path37);
16430
16508
  return iss;
16431
16509
  });
16432
16510
  }
@@ -16460,8 +16538,8 @@ function finalizeIssue2(iss, ctx, config3) {
16460
16538
  else
16461
16539
  iss.schema = iss.inst;
16462
16540
  }
16463
- const schemaError2 = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
16464
- const message = iss.message ? iss.message : unwrapMessage2(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage2(schemaError2?.(iss)) ?? unwrapMessage2(ctx?.error?.(iss)) ?? unwrapMessage2(config3.customError?.(iss)) ?? unwrapMessage2(config3.localeError?.(iss)) ?? "Invalid input";
16541
+ const schemaError3 = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
16542
+ const message = iss.message ? iss.message : unwrapMessage2(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage2(schemaError3?.(iss)) ?? unwrapMessage2(ctx?.error?.(iss)) ?? unwrapMessage2(config3.customError?.(iss)) ?? unwrapMessage2(config3.localeError?.(iss)) ?? "Invalid input";
16465
16543
  const full = {};
16466
16544
  for (const k of Object.keys(iss)) {
16467
16545
  if (k === "inst" || k === "schema" || k === "continue" || k === "input" || k === "__proto__")
@@ -16884,16 +16962,16 @@ function flattenError2(error3, mapper = (issue3) => issue3.message) {
16884
16962
  }
16885
16963
  function formatError2(error3, mapper = (issue3) => issue3.message) {
16886
16964
  const fieldErrors = { _errors: [] };
16887
- const processError = (error4, path32 = []) => {
16965
+ const processError = (error4, path37 = []) => {
16888
16966
  for (const issue3 of error4.issues) {
16889
16967
  if (issue3.code === "invalid_union" && issue3.errors.length) {
16890
- issue3.errors.map((issues) => processError({ issues }, [...path32, ...issue3.path]));
16968
+ issue3.errors.map((issues) => processError({ issues }, [...path37, ...issue3.path]));
16891
16969
  } else if (issue3.code === "invalid_key") {
16892
- processError({ issues: issue3.issues }, [...path32, ...issue3.path]);
16970
+ processError({ issues: issue3.issues }, [...path37, ...issue3.path]);
16893
16971
  } else if (issue3.code === "invalid_element") {
16894
- processError({ issues: issue3.issues }, [...path32, ...issue3.path]);
16972
+ processError({ issues: issue3.issues }, [...path37, ...issue3.path]);
16895
16973
  } else {
16896
- const fullpath = [...path32, ...issue3.path];
16974
+ const fullpath = [...path37, ...issue3.path];
16897
16975
  if (fullpath.length === 0) {
16898
16976
  fieldErrors._errors.push(mapper(issue3));
16899
16977
  } else {
@@ -21191,13 +21269,13 @@ var recordProcessor2 = (schema, ctx, _json, params) => {
21191
21269
  ...params,
21192
21270
  path: [...params.path, "propertyNames"]
21193
21271
  });
21194
- let pending = pendingRecords2.get(ctx);
21195
- if (!pending) {
21196
- pending = [];
21197
- pendingRecords2.set(ctx, pending);
21272
+ let pending2 = pendingRecords2.get(ctx);
21273
+ if (!pending2) {
21274
+ pending2 = [];
21275
+ pendingRecords2.set(ctx, pending2);
21198
21276
  ctx.deferred.push(() => rewriteKeyNames2(ctx));
21199
21277
  }
21200
- pending.push(schema);
21278
+ pending2.push(schema);
21201
21279
  }
21202
21280
  json2.additionalProperties = processSchema2(def.valueType, ctx, {
21203
21281
  ...params,
@@ -22539,6 +22617,65 @@ var acknowledgementSchema2 = object3({
22539
22617
  deviceId: string5().regex(UUID_PATTERN2),
22540
22618
  acceptedEventIds: array2(string5())
22541
22619
  }).strict().describe("Accepted event IDs must match the submitted batch exactly, without duplicates.");
22620
+ function schemaError2(error3, fallback) {
22621
+ return error3.issues[0]?.message ?? fallback;
22622
+ }
22623
+ function parseTraceEventBatch2(input) {
22624
+ const parsed = traceEventBatchSchema2.safeParse(input);
22625
+ if (!parsed.success) {
22626
+ return {
22627
+ success: false,
22628
+ error: schemaError2(parsed.error, "Trace event batch is invalid.")
22629
+ };
22630
+ }
22631
+ const eventIds = /* @__PURE__ */ new Set();
22632
+ for (const event3 of parsed.data.events) {
22633
+ if (eventIds.has(event3.eventId)) {
22634
+ return {
22635
+ success: false,
22636
+ error: "A batch cannot contain duplicate event ids."
22637
+ };
22638
+ }
22639
+ eventIds.add(event3.eventId);
22640
+ }
22641
+ return { success: true, value: parsed.data };
22642
+ }
22643
+ function parseTraceEventAcknowledgement2(input, expectedEventIds) {
22644
+ const parsed = acknowledgementSchema2.safeParse(input);
22645
+ if (!parsed.success) {
22646
+ return {
22647
+ success: false,
22648
+ error: schemaError2(parsed.error, "Acknowledgement has an invalid shape.")
22649
+ };
22650
+ }
22651
+ const { acceptedEventIds } = parsed.data;
22652
+ if (acceptedEventIds.length !== expectedEventIds.length) {
22653
+ return {
22654
+ success: false,
22655
+ error: "Acknowledgement event ids do not match the batch."
22656
+ };
22657
+ }
22658
+ const actual = /* @__PURE__ */ new Set();
22659
+ for (const eventId of acceptedEventIds) {
22660
+ if (!expectedEventIds.includes(eventId)) {
22661
+ return {
22662
+ success: false,
22663
+ error: "Acknowledgement includes an unknown event id."
22664
+ };
22665
+ }
22666
+ actual.add(eventId);
22667
+ }
22668
+ if (actual.size !== expectedEventIds.length) {
22669
+ return {
22670
+ success: false,
22671
+ error: "Acknowledgement contains duplicate event ids."
22672
+ };
22673
+ }
22674
+ return {
22675
+ success: true,
22676
+ value: { ...parsed.data, acceptedEventIds: [...actual] }
22677
+ };
22678
+ }
22542
22679
  function isLaunchDirectory(value) {
22543
22680
  if (!(value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value) || /^\\\\[^\\/]+[\\/][^\\/]+(?:[\\/]|$)/.test(value)) || value.length > 4096)
22544
22681
  return false;
@@ -22551,36 +22688,630 @@ function isLaunchDirectory(value) {
22551
22688
  }
22552
22689
 
22553
22690
  // src/agent-tools.ts
22691
+ import { promises as fs13 } from "node:fs";
22692
+ import os4 from "node:os";
22693
+ import path18 from "node:path";
22694
+ import readline from "node:readline";
22695
+
22696
+ // src/sharing.ts
22554
22697
  import { promises as fs11 } from "node:fs";
22555
- import os3 from "node:os";
22556
22698
  import path8 from "node:path";
22557
- import readline from "node:readline";
22558
22699
 
22559
- // src/runtime.ts
22560
- import { createHash as createHash4 } from "node:crypto";
22700
+ // ../collector-contracts/dist/sharing.js
22701
+ var collectorComputerIdSchema = uuid5();
22702
+ var collectorSharingApprovalIdentitySchema = object3({
22703
+ workspaceId: uuid5(),
22704
+ computerId: collectorComputerIdSchema,
22705
+ revision: coerce_exports.number().int().positive()
22706
+ }).strict();
22707
+ var captureLocationSchema = string5().min(1).max(4096).refine((value) => !value.split("").some((character) => character.charCodeAt(0) < 32) && (/^\//.test(value) || /^[A-Za-z]:[\\/]/.test(value)), "Choose an absolute folder path.").describe("An absolute Unix or Windows folder path without control characters.");
22708
+ var collectorProjectInventorySchema = object3({
22709
+ computerId: uuid5(),
22710
+ projects: array2(object3({
22711
+ path: captureLocationSchema,
22712
+ name: string5().min(1).max(160),
22713
+ sessions: number4().int().nonnegative(),
22714
+ sources: array2(_enum2(COLLECTOR_SOURCES2)).min(1).max(3)
22715
+ }).strict()).max(1e3),
22716
+ truncated: boolean4()
22717
+ }).strict();
22718
+ var collectorSharingSelectionSchema = object3({
22719
+ mode: _enum2(["selected", "all"]),
22720
+ locations: array2(captureLocationSchema).max(1e3),
22721
+ excludedLocations: array2(captureLocationSchema).max(1e3),
22722
+ paused: boolean4().default(false)
22723
+ }).strict().refine((value) => value.mode === "all" || value.locations.length > 0, "Choose at least one project or folder.").describe("Selected mode requires at least one folder. All mode shares unmatched projects except exclusions.");
22724
+ var collectorWorkspaceSharingSchema = object3({
22725
+ organizationId: uuid5(),
22726
+ organizationName: string5().min(1).max(160),
22727
+ selection: collectorSharingSelectionSchema,
22728
+ active: boolean4(),
22729
+ connectedSources: array2(_enum2(COLLECTOR_SOURCES2)).max(3)
22730
+ }).strict();
22731
+ var collectorSharingSnapshotSchema = object3({
22732
+ computerId: uuid5(),
22733
+ ownerId: uuid5(),
22734
+ revision: number4().int().positive(),
22735
+ approved: boolean4(),
22736
+ workspaces: array2(collectorWorkspaceSharingSchema).max(100)
22737
+ }).strict();
22738
+
22739
+ // ../collector-contracts/dist/sharing-policy.js
22740
+ function captureLocationContains(root, location) {
22741
+ const normalize = (value) => {
22742
+ const windows = /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
22743
+ const normalized = value.replaceAll("\\", "/").replace(/\/+$/, "");
22744
+ return windows ? normalized.toLowerCase() : normalized;
22745
+ };
22746
+ const parent = normalize(root);
22747
+ const child = normalize(location);
22748
+ return child === parent || child.startsWith(parent + "/");
22749
+ }
22750
+ function sharingWorkspaceForLocation(snapshot, location) {
22751
+ const candidates = snapshot.workspaces.flatMap((workspace) => {
22752
+ const specificity = sharingSelectionSpecificity(workspace.selection, location);
22753
+ if (specificity === null)
22754
+ return [];
22755
+ return [
22756
+ {
22757
+ workspace,
22758
+ specificity
22759
+ }
22760
+ ];
22761
+ }).sort((left, right) => right.specificity - left.specificity);
22762
+ const first = candidates[0];
22763
+ if (!first || candidates[1]?.specificity === first.specificity || !first.workspace.active)
22764
+ return null;
22765
+ return first.workspace.organizationId;
22766
+ }
22767
+ function sharingSelectionSpecificity(selection, location) {
22768
+ const included = selection.locations.filter((root) => captureLocationContains(root, location));
22769
+ if (!included.length && selection.mode !== "all")
22770
+ return null;
22771
+ const specificity = Math.max(0, ...included.map((root) => root.replace(/[/\\]+$/, "").length));
22772
+ const excluded = selection.excludedLocations.filter((root) => captureLocationContains(root, location));
22773
+ return excluded.some((root) => root.replace(/[/\\]+$/, "").length >= specificity) ? null : specificity;
22774
+ }
22775
+
22776
+ // ../collector-contracts/dist/folders.js
22777
+ var folderEntrySchema = object3({ name: string5().min(1).max(255), path: captureLocationSchema }).strict();
22778
+ var collectorFolderRequestSchema = object3({
22779
+ id: uuid5(),
22780
+ directory: captureLocationSchema.nullable(),
22781
+ query: string5().trim().max(80),
22782
+ showHidden: boolean4(),
22783
+ expiresAtMs: number4().int().positive()
22784
+ }).strict();
22785
+ var collectorFolderListingSchema = object3({
22786
+ directory: captureLocationSchema,
22787
+ name: string5().min(1).max(255),
22788
+ parent: captureLocationSchema.nullable(),
22789
+ breadcrumbs: array2(folderEntrySchema).min(1).max(8),
22790
+ entries: array2(folderEntrySchema).max(200),
22791
+ truncated: boolean4()
22792
+ }).strict();
22793
+ var pending = object3({
22794
+ status: literal2("pending"),
22795
+ request: collectorFolderRequestSchema
22796
+ }).strict();
22797
+ var ready = object3({
22798
+ status: literal2("ready"),
22799
+ request: collectorFolderRequestSchema,
22800
+ listing: collectorFolderListingSchema
22801
+ }).strict();
22802
+ var failed = object3({
22803
+ status: literal2("error"),
22804
+ request: collectorFolderRequestSchema,
22805
+ reason: _enum2(["permission", "missing", "unavailable"])
22806
+ }).strict();
22807
+ var collectorFolderStateSchema = discriminatedUnion2("status", [
22808
+ pending,
22809
+ ready,
22810
+ failed
22811
+ ]);
22812
+ var collectorFolderCommandSchema = object3({ organizationId: uuid5(), request: collectorFolderRequestSchema }).strict();
22813
+ var collectorFolderReplySchema = object3({
22814
+ organizationId: uuid5(),
22815
+ state: discriminatedUnion2("status", [ready, failed])
22816
+ }).strict();
22817
+ var collectorFolderActionSchema = object3({
22818
+ workspaceId: uuid5(),
22819
+ computerId: uuid5(),
22820
+ intent: discriminatedUnion2("type", [
22821
+ object3({
22822
+ type: literal2("browse"),
22823
+ id: uuid5(),
22824
+ directory: captureLocationSchema.nullable(),
22825
+ query: string5().trim().max(80),
22826
+ showHidden: boolean4()
22827
+ }).strict(),
22828
+ object3({ type: literal2("read"), id: uuid5() }).strict(),
22829
+ object3({ type: literal2("close"), id: uuid5() }).strict()
22830
+ ])
22831
+ }).strict();
22832
+
22833
+ // ../collector-contracts/dist/index.js
22834
+ var cursorBillingRequestSchema = object3({
22835
+ conversationId: uuid5(),
22836
+ reconcileFromMs: number4().int().nonnegative().nullable(),
22837
+ events: array2(object3({
22838
+ timestamp: number4().int().nonnegative(),
22839
+ kind: _enum2([
22840
+ "included",
22841
+ "paid",
22842
+ "byok",
22843
+ "not_charged",
22844
+ "unknown"
22845
+ ]),
22846
+ inputTokens: number4().int().nonnegative().nullable(),
22847
+ outputTokens: number4().int().nonnegative().nullable(),
22848
+ cacheReadTokens: number4().int().nonnegative().nullable(),
22849
+ cacheWriteTokens: number4().int().nonnegative().nullable(),
22850
+ paidUsd: number4().finite().nonnegative().nullable()
22851
+ }).strict()).max(5e3)
22852
+ }).strict();
22853
+ function codexCreditSign(value) {
22854
+ const mantissa = value.split(/[eE]/, 1)[0];
22855
+ if (!/[1-9]/.test(mantissa))
22856
+ return 0;
22857
+ return mantissa.startsWith("-") ? -1 : 1;
22858
+ }
22859
+ var codexBillingRequestSchema = object3({
22860
+ accountFingerprint: string5().regex(/^[0-9a-f]{64}$/),
22861
+ reconcile: boolean4(),
22862
+ tasks: array2(object3({
22863
+ threadId: uuid5(),
22864
+ createdAt: iso_exports2.datetime().nullable(),
22865
+ descendantThreadIds: array2(uuid5()).max(900),
22866
+ dataStatus: _enum2([
22867
+ "available",
22868
+ "partial",
22869
+ "unavailable",
22870
+ "not_queried"
22871
+ ]),
22872
+ usageSource: string5().max(100),
22873
+ balanceUsageCredits: string5().regex(/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d{1,3})?$/).max(128).nullable()
22874
+ }).strict()).max(100),
22875
+ authTurns: array2(object3({
22876
+ threadId: uuid5(),
22877
+ turnId: uuid5(),
22878
+ mode: _enum2(["chatgpt", "api_key", "conflicting"])
22879
+ }).strict()).max(5e3)
22880
+ }).strict();
22881
+ var UUID_PATTERN3 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
22882
+ var episodeCountSchema = number4().int().min(0).max(Number.MAX_SAFE_INTEGER);
22883
+ var collectorFrictionSummarySchema = object3({
22884
+ weekEpisodes: episodeCountSchema,
22885
+ dayEpisodes: episodeCountSchema
22886
+ }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.").describe("dayEpisodes must not exceed weekEpisodes.");
22887
+ var RECENT_MESSAGE_LIMIT = 400;
22888
+ var sessionCountSchema = number4().int().min(0).max(1e6);
22889
+ var collectorHistoryProgressSchema = object3({
22890
+ sessions: sessionCountSchema,
22891
+ readSessions: sessionCountSchema,
22892
+ complete: boolean4(),
22893
+ recentMessages: object3({
22894
+ sent: number4().int().min(0).max(RECENT_MESSAGE_LIMIT),
22895
+ complete: boolean4()
22896
+ }).strict().optional().describe("The newest messages sent ahead of the rest of the history; collectors before 0.17.0 omit it.")
22897
+ }).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.").describe("readSessions must not exceed sessions; complete requires equal counts.");
22898
+ var collectorSourceSchema = _enum2(COLLECTOR_SOURCES2);
22899
+ var collectorEnrollmentRequestSchema = object3({
22900
+ deviceName: string5().trim().min(1).max(160).describe("Trimmed before length validation."),
22901
+ organizationId: string5().regex(UUID_PATTERN3).optional(),
22902
+ computerId: uuid5().optional(),
22903
+ source: collectorSourceSchema.optional()
22904
+ }).strict();
22905
+ var collectorExpansionRequestSchema = object3({
22906
+ source: _enum2(COLLECTOR_SOURCES2),
22907
+ token: string5().regex(/^[A-Za-z0-9_-]{43}$/),
22908
+ organizationId: string5().regex(UUID_PATTERN3).optional()
22909
+ }).strict();
22910
+ var collectorEnrollmentResponseSchema = discriminatedUnion2("status", [
22911
+ object3({
22912
+ status: literal2("enrolled"),
22913
+ token: string5().regex(/^[A-Za-z0-9_-]{43}$/),
22914
+ deviceId: string5().regex(UUID_PATTERN3),
22915
+ organizationId: string5().regex(UUID_PATTERN3),
22916
+ organizationName: string5().min(1).max(160)
22917
+ }).strict(),
22918
+ object3({
22919
+ status: literal2("choose_organization"),
22920
+ organizations: array2(object3({
22921
+ id: string5().regex(UUID_PATTERN3),
22922
+ name: string5().min(1).max(160)
22923
+ }).strict()).max(20),
22924
+ hasMore: boolean4()
22925
+ }).strict()
22926
+ ]);
22927
+ function parseCollectorEnrollmentResponse(input) {
22928
+ const parsed = collectorEnrollmentResponseSchema.safeParse(input);
22929
+ return parsed.success ? { success: true, value: parsed.data } : {
22930
+ success: false,
22931
+ error: parsed.error.issues[0]?.message ?? "Collector enrollment response is invalid."
22932
+ };
22933
+ }
22934
+ function parseCollectorFrictionSummary(input) {
22935
+ const parsed = collectorFrictionSummarySchema.safeParse(input);
22936
+ return parsed.success ? { success: true, value: parsed.data } : {
22937
+ success: false,
22938
+ error: parsed.error.issues[0]?.message ?? "Friction summary is invalid."
22939
+ };
22940
+ }
22941
+
22942
+ // src/sharing-storage.ts
22943
+ import { randomUUID as randomUUID3 } from "node:crypto";
22561
22944
  import { promises as fs6 } from "node:fs";
22562
22945
  import os from "node:os";
22563
22946
  import path7 from "node:path";
22564
- import { setTimeout as delay2 } from "node:timers/promises";
22565
- import { fileURLToPath } from "node:url";
22566
- function runtimePaths(home = os.homedir()) {
22567
- const root = path7.join(home, ".config", "iteration", "runtime");
22947
+ var connectionSchema2 = object3({
22948
+ organizationId: uuid5(),
22949
+ organizationName: string5().min(1).max(160),
22950
+ source: _enum2(COLLECTOR_SOURCES2),
22951
+ token: string5().min(1),
22952
+ endpoint: url()
22953
+ }).strict();
22954
+ var registrySchema = object3({
22955
+ version: literal2(1),
22956
+ computerId: uuid5(),
22957
+ ownerId: uuid5().nullable(),
22958
+ connections: array2(connectionSchema2).max(300),
22959
+ sourceHomes: object3({ codex: string5(), claude: string5(), cursor: string5() }).strict().optional(),
22960
+ directories: partialRecord(_enum2(COLLECTOR_SOURCES2), string5()).optional()
22961
+ }).strict();
22962
+ function enrolledSharingConnection(enrollment, source, endpoint) {
22568
22963
  return {
22569
- home,
22570
- root,
22571
- versions: path7.join(root, "versions"),
22572
- staging: path7.join(root, "staging"),
22573
- state: path7.join(root, "state.json"),
22574
- settings: path7.join(root, "settings.json"),
22575
- lock: path7.join(root, "state.lock"),
22576
- updateLock: path7.join(root, "update.lock"),
22577
- launcher: path7.join(root, "launcher.js")
22964
+ token: enrollment.token,
22965
+ organizationId: enrollment.organizationId,
22966
+ organizationName: enrollment.organizationName,
22967
+ source,
22968
+ endpoint
22578
22969
  };
22579
22970
  }
22580
- var DEFAULT_UPDATE_REGISTRY = "https://registry.npmjs.org/";
22581
- var VERSION_PATTERN = /^(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})$/;
22582
- var versionSchema = string5().regex(VERSION_PATTERN);
22583
- var sourceSchema = _enum2(COLLECTOR_SOURCES2);
22971
+ function sharingDirectory() {
22972
+ return path7.join(os.homedir(), ".config", "iteration", "sharing");
22973
+ }
22974
+ async function readSharingRegistry() {
22975
+ let contents;
22976
+ try {
22977
+ contents = await fs6.readFile(
22978
+ path7.join(sharingDirectory(), "connections.json"),
22979
+ "utf8"
22980
+ );
22981
+ } catch (error3) {
22982
+ if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
22983
+ return null;
22984
+ throw error3;
22985
+ }
22986
+ return registrySchema.parse(JSON.parse(contents));
22987
+ }
22988
+ async function withSharingRegistry(work) {
22989
+ const directory = sharingDirectory();
22990
+ const release = await waitForCollectorLock(collectorPaths(directory));
22991
+ try {
22992
+ const registry3 = await readSharingRegistry() ?? {
22993
+ version: 1,
22994
+ computerId: randomUUID3(),
22995
+ ownerId: null,
22996
+ connections: []
22997
+ };
22998
+ const save = async (next) => {
22999
+ await ensurePrivateDirectory(directory);
23000
+ const filename = path7.join(directory, "connections.json");
23001
+ const temporary = `${filename}.${process.pid}.tmp`;
23002
+ await fs6.writeFile(
23003
+ temporary,
23004
+ JSON.stringify(registrySchema.parse(next)),
23005
+ { mode: 384 }
23006
+ );
23007
+ await fs6.rename(temporary, filename);
23008
+ };
23009
+ await save(registry3);
23010
+ return await work(registry3, save);
23011
+ } finally {
23012
+ await release();
23013
+ }
23014
+ }
23015
+ async function forgetSharingConnections() {
23016
+ if (await readSharingRegistry() === null) return;
23017
+ await withSharingRegistry(
23018
+ async (registry3, save) => save({ ...registry3, ownerId: null, connections: [] })
23019
+ );
23020
+ }
23021
+ function sharingConnection(registry3, organizationId, source) {
23022
+ return registry3.connections.find(
23023
+ (connection) => connection.organizationId === organizationId && connection.source === source
23024
+ ) ?? null;
23025
+ }
23026
+
23027
+ // src/sharing.ts
23028
+ async function fetchSharingSnapshot(config3, request2 = collectorFetch, signal) {
23029
+ const response = await request2(
23030
+ new URL("./collector-sharing", config3.endpoint),
23031
+ {
23032
+ headers: { authorization: `Bearer ${config3.token}` },
23033
+ signal: AbortSignal.any([
23034
+ AbortSignal.timeout(15e3),
23035
+ ...signal ? [signal] : []
23036
+ ])
23037
+ }
23038
+ );
23039
+ if (!response.ok)
23040
+ throw new Error(
23041
+ `Could not check project sharing (HTTP ${response.status}). Collection is waiting.`
23042
+ );
23043
+ return collectorSharingSnapshotSchema.parse(await response.json());
23044
+ }
23045
+ function workspaceForSource(snapshot, location, source) {
23046
+ const organizationId = sharingWorkspaceForLocation(
23047
+ snapshot,
23048
+ resolveCaptureLocation(location)
23049
+ );
23050
+ return snapshot.workspaces.some(
23051
+ (workspace) => workspace.organizationId === organizationId && workspace.connectedSources.includes(source)
23052
+ ) ? organizationId : null;
23053
+ }
23054
+ function hostCapturePolicy(snapshot, registry3, config3) {
23055
+ const source = collectorSource(config3);
23056
+ const permitted = (location) => {
23057
+ const organizationId = workspaceForSource(snapshot, location, source);
23058
+ return organizationId !== null && sharingConnection(registry3, organizationId, source) !== null;
23059
+ };
23060
+ return {
23061
+ revision: snapshot.revision,
23062
+ batchKey: (event3) => event3.capture ? sharingWorkspaceForLocation(snapshot, event3.capture.reference) ?? "unshared" : "unshared",
23063
+ authorizeRecord: (recordSource, line, filename, cwds) => {
23064
+ const location = captureRecordLocation(
23065
+ recordSource,
23066
+ line,
23067
+ filename,
23068
+ cwds
23069
+ );
23070
+ return location !== null && permitted(location) ? {
23071
+ revision: snapshot.revision,
23072
+ basis: "location",
23073
+ reference: location
23074
+ } : null;
23075
+ },
23076
+ authorizeEvent: (event3) => event3.capture?.basis === "location" && event3.capture.revision === snapshot.revision && permitted(event3.capture.reference) ? event3 : null
23077
+ };
23078
+ }
23079
+ var sessionIndexSchema = record3(
23080
+ string5(),
23081
+ object3({ location: string5().nullable(), at: number4() }).strict()
23082
+ );
23083
+ async function loadSessionIndex(config3) {
23084
+ try {
23085
+ return sessionIndexSchema.parse(
23086
+ JSON.parse(
23087
+ await fs11.readFile(
23088
+ path8.join(config3.dataDirectory, "shared-sessions.json"),
23089
+ "utf8"
23090
+ )
23091
+ )
23092
+ );
23093
+ } catch (error3) {
23094
+ if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
23095
+ return {};
23096
+ throw error3;
23097
+ }
23098
+ }
23099
+ async function sharingTransport(config3, request2 = collectorFetch, signal, savedRegistry) {
23100
+ const registry3 = savedRegistry ?? await readSharingRegistry();
23101
+ if (registry3 === null)
23102
+ throw new Error(
23103
+ "Choose projects in Iteration before sharing this computer."
23104
+ );
23105
+ const connections = registry3.connections.filter(
23106
+ (connection) => connection.source === collectorSource(config3) && connection.endpoint === config3.endpoint
23107
+ );
23108
+ let snapshot = null;
23109
+ for (const connection of connections) {
23110
+ try {
23111
+ snapshot = await fetchSharingSnapshot(connection, request2, signal);
23112
+ break;
23113
+ } catch (error3) {
23114
+ if (signal?.aborted) throw error3;
23115
+ }
23116
+ }
23117
+ if (snapshot === null || snapshot.computerId !== registry3.computerId || snapshot.ownerId !== registry3.ownerId)
23118
+ throw new Error("Could not verify project sharing. Collection is waiting.");
23119
+ const current = snapshot;
23120
+ const capture = hostCapturePolicy(current, registry3, config3);
23121
+ const index = await loadSessionIndex(config3);
23122
+ const destination = (location) => {
23123
+ const organizationId = workspaceForSource(
23124
+ current,
23125
+ location,
23126
+ collectorSource(config3)
23127
+ );
23128
+ return organizationId === null ? null : sharingConnection(registry3, organizationId, collectorSource(config3));
23129
+ };
23130
+ const forSession = (id) => {
23131
+ const location = index[id]?.location;
23132
+ return location ? destination(location) : null;
23133
+ };
23134
+ const upload = async (init, sendTo) => {
23135
+ const body = string5().parse(init?.body);
23136
+ const parsed = parseTraceEventBatch2(JSON.parse(body));
23137
+ if (!parsed.success) throw new Error(parsed.error);
23138
+ const batches = /* @__PURE__ */ new Map();
23139
+ for (const event3 of parsed.value.events) {
23140
+ if (capture.authorizeEvent(event3) === null)
23141
+ throw new Error("Project sharing changed. Waiting for the next scan.");
23142
+ const connection = destination(event3.capture.reference);
23143
+ if (!connection) throw new Error("The project is no longer shared.");
23144
+ const events = batches.get(connection.organizationId) ?? [];
23145
+ events.push(event3);
23146
+ batches.set(connection.organizationId, events);
23147
+ const previous = index[event3.sessionId];
23148
+ index[event3.sessionId] = {
23149
+ location: previous && previous.location !== event3.capture.reference ? null : event3.capture.reference,
23150
+ at: Date.now()
23151
+ };
23152
+ }
23153
+ const acceptedEventIds = [];
23154
+ let deviceId = "";
23155
+ let retryAfter = null;
23156
+ for (const [organizationId, events] of batches) {
23157
+ const response = await sendTo(organizationId, JSON.stringify({ events }));
23158
+ if (response.status !== 202) return response;
23159
+ retryAfter = response.headers.get("retry-after");
23160
+ const acknowledgement = parseTraceEventAcknowledgement2(
23161
+ await response.json(),
23162
+ events.map((event3) => event3.eventId)
23163
+ );
23164
+ if (!acknowledgement.success) throw new Error(acknowledgement.error);
23165
+ acceptedEventIds.push(...acknowledgement.value.acceptedEventIds);
23166
+ deviceId = acknowledgement.value.deviceId;
23167
+ }
23168
+ const retained = Object.fromEntries(
23169
+ Object.entries(index).sort((left, right) => right[1].at - left[1].at).slice(0, 1e5)
23170
+ );
23171
+ const filename = path8.join(config3.dataDirectory, "shared-sessions.json");
23172
+ const temporary = `${filename}.${process.pid}.tmp`;
23173
+ await fs11.writeFile(temporary, JSON.stringify(retained), { mode: 384 });
23174
+ await fs11.rename(temporary, filename);
23175
+ return Response.json(
23176
+ { deviceId, acceptedEventIds },
23177
+ {
23178
+ status: 202,
23179
+ headers: retryAfter === null ? {} : { "Retry-After": retryAfter }
23180
+ }
23181
+ );
23182
+ };
23183
+ const send = (input, init) => request2(input, init);
23184
+ const routed = async (input, init) => {
23185
+ const url2 = new URL(String(input));
23186
+ const sendTo = (organizationId, body) => {
23187
+ const connection = sharingConnection(
23188
+ registry3,
23189
+ organizationId,
23190
+ collectorSource(config3)
23191
+ );
23192
+ if (!connection)
23193
+ throw new Error(
23194
+ "The project workspace has no agent connection. Run iteration connect."
23195
+ );
23196
+ return send(input, {
23197
+ ...init,
23198
+ headers: {
23199
+ ...Object.fromEntries(new Headers(init?.headers)),
23200
+ authorization: `Bearer ${connection.token}`
23201
+ },
23202
+ ...body === void 0 ? {} : { body }
23203
+ });
23204
+ };
23205
+ if (url2.pathname === new URL(config3.endpoint).pathname)
23206
+ return upload(init, sendTo);
23207
+ if (url2.pathname.endsWith("/friction-summary")) {
23208
+ const directory = url2.searchParams.get("directory");
23209
+ const connection = directory === null ? null : destination(directory);
23210
+ return connection ? sendTo(connection.organizationId) : Response.json({ weekEpisodes: 0, dayEpisodes: 0 });
23211
+ }
23212
+ if (url2.pathname.endsWith("/codex-billing")) {
23213
+ const body = codexBillingRequestSchema.parse(
23214
+ JSON.parse(String(init?.body))
23215
+ );
23216
+ let matched = 0;
23217
+ for (const workspace of current.workspaces.filter(
23218
+ (workspace2) => workspace2.active && workspace2.connectedSources.includes(collectorSource(config3))
23219
+ )) {
23220
+ const tasks = body.tasks.filter(
23221
+ (task) => forSession(task.threadId)?.organizationId === workspace.organizationId
23222
+ ).map((task) => ({
23223
+ ...task,
23224
+ descendantThreadIds: task.descendantThreadIds.filter(
23225
+ (id) => forSession(id)?.organizationId === workspace.organizationId
23226
+ )
23227
+ }));
23228
+ if (tasks.length === 0) continue;
23229
+ const ids = new Set(
23230
+ tasks.flatMap((task) => [task.threadId, ...task.descendantThreadIds])
23231
+ );
23232
+ const response = await sendTo(
23233
+ workspace.organizationId,
23234
+ JSON.stringify({
23235
+ ...body,
23236
+ tasks,
23237
+ authTurns: body.authTurns.filter((turn) => ids.has(turn.threadId))
23238
+ })
23239
+ );
23240
+ if (!response.ok) return response;
23241
+ matched += object3({ matched: number4() }).parse(await response.json()).matched;
23242
+ }
23243
+ return Response.json({ matched });
23244
+ }
23245
+ if (url2.pathname.endsWith("/cursor-billing")) {
23246
+ const body = cursorBillingRequestSchema.parse(
23247
+ JSON.parse(String(init?.body))
23248
+ );
23249
+ const connection = forSession(`cursor:${body.conversationId}`);
23250
+ return connection ? sendTo(connection.organizationId) : Response.json({ matched: 0 });
23251
+ }
23252
+ if (url2.pathname.endsWith("/collector-progress")) {
23253
+ for (const workspace of current.workspaces.filter(
23254
+ (workspace2) => workspace2.active
23255
+ )) {
23256
+ if (!sharingConnection(
23257
+ registry3,
23258
+ workspace.organizationId,
23259
+ collectorSource(config3)
23260
+ ))
23261
+ continue;
23262
+ const response = await sendTo(workspace.organizationId);
23263
+ if (!response.ok) return response;
23264
+ await response.body?.cancel();
23265
+ }
23266
+ return Response.json({ ok: true });
23267
+ }
23268
+ throw new Error("This collector request does not support project sharing.");
23269
+ };
23270
+ return { capture, fetcher: routed, snapshot: current };
23271
+ }
23272
+ async function configForSharedDirectory(config3, directory) {
23273
+ if (config3.destination !== "host") return config3;
23274
+ const transport = await sharingTransport(config3);
23275
+ const organizationId = workspaceForSource(
23276
+ transport.snapshot,
23277
+ directory,
23278
+ collectorSource(config3)
23279
+ );
23280
+ const registry3 = await readSharingRegistry();
23281
+ const connection = organizationId && registry3 ? sharingConnection(registry3, organizationId, collectorSource(config3)) : null;
23282
+ return connection ? {
23283
+ ...config3,
23284
+ token: connection.token,
23285
+ organizationId: connection.organizationId,
23286
+ organizationName: connection.organizationName
23287
+ } : null;
23288
+ }
23289
+
23290
+ // src/runtime.ts
23291
+ import { createHash as createHash4 } from "node:crypto";
23292
+ import { promises as fs12 } from "node:fs";
23293
+ import os3 from "node:os";
23294
+ import path17 from "node:path";
23295
+ import { setTimeout as delay2 } from "node:timers/promises";
23296
+ import { fileURLToPath } from "node:url";
23297
+ function runtimePaths(home = os3.homedir()) {
23298
+ const root = path17.join(home, ".config", "iteration", "runtime");
23299
+ return {
23300
+ home,
23301
+ root,
23302
+ versions: path17.join(root, "versions"),
23303
+ staging: path17.join(root, "staging"),
23304
+ state: path17.join(root, "state.json"),
23305
+ settings: path17.join(root, "settings.json"),
23306
+ lock: path17.join(root, "state.lock"),
23307
+ updateLock: path17.join(root, "update.lock"),
23308
+ launcher: path17.join(root, "launcher.js")
23309
+ };
23310
+ }
23311
+ var DEFAULT_UPDATE_REGISTRY = "https://registry.npmjs.org/";
23312
+ var VERSION_PATTERN = /^(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})$/;
23313
+ var versionSchema = string5().regex(VERSION_PATTERN);
23314
+ var sourceSchema = _enum2(COLLECTOR_SOURCES2);
22584
23315
  var timeSchema = number4().int().min(0);
22585
23316
  var trialSchema = object3({
22586
23317
  version: versionSchema,
@@ -22665,19 +23396,19 @@ async function ensureRuntimeDirectory(paths) {
22665
23396
  }
22666
23397
  async function writeFileAtomic(filename, contents, mode) {
22667
23398
  const temporary = `${filename}.${process.pid}.${Date.now()}.tmp`;
22668
- const handle = await fs6.open(temporary, "w", mode);
23399
+ const handle = await fs12.open(temporary, "w", mode);
22669
23400
  try {
22670
23401
  await handle.writeFile(contents, "utf8");
22671
23402
  await handle.sync();
22672
23403
  } finally {
22673
23404
  await handle.close();
22674
23405
  }
22675
- await fs6.rename(temporary, filename);
23406
+ await fs12.rename(temporary, filename);
22676
23407
  }
22677
23408
  async function readRuntimeState(paths) {
22678
23409
  let contents;
22679
23410
  try {
22680
- contents = await fs6.readFile(paths.state, "utf8");
23411
+ contents = await fs12.readFile(paths.state, "utf8");
22681
23412
  } catch (error3) {
22682
23413
  if (errorCode2(error3) === "ENOENT") return null;
22683
23414
  throw error3;
@@ -22709,7 +23440,7 @@ async function writeRuntimeState(paths, state) {
22709
23440
  async function readRuntimeSettings(paths) {
22710
23441
  let contents;
22711
23442
  try {
22712
- contents = await fs6.readFile(paths.settings, "utf8");
23443
+ contents = await fs12.readFile(paths.settings, "utf8");
22713
23444
  } catch (error3) {
22714
23445
  if (errorCode2(error3) === "ENOENT")
22715
23446
  return { autoUpdate: true, registry: DEFAULT_UPDATE_REGISTRY };
@@ -22738,19 +23469,19 @@ var LockBusyError = class extends Error {
22738
23469
  async function tryLock(filename) {
22739
23470
  for (let attempt = 0; attempt < 2; attempt += 1) {
22740
23471
  try {
22741
- const handle = await fs6.open(filename, "wx", 384);
23472
+ const handle = await fs12.open(filename, "wx", 384);
22742
23473
  await handle.writeFile(`${process.pid}
22743
23474
  `, "utf8");
22744
23475
  await handle.close();
22745
23476
  return async () => {
22746
- const owner = await fs6.readFile(filename, "utf8").catch(() => "");
23477
+ const owner = await fs12.readFile(filename, "utf8").catch(() => "");
22747
23478
  if (owner.trim() === String(process.pid))
22748
- await fs6.rm(filename, { force: true });
23479
+ await fs12.rm(filename, { force: true });
22749
23480
  };
22750
23481
  } catch (error3) {
22751
23482
  if (errorCode2(error3) !== "EEXIST") throw error3;
22752
23483
  const owner = Number(
22753
- (await fs6.readFile(filename, "utf8").catch(() => "")).trim()
23484
+ (await fs12.readFile(filename, "utf8").catch(() => "")).trim()
22754
23485
  );
22755
23486
  if (Number.isSafeInteger(owner) && owner > 1) {
22756
23487
  try {
@@ -22760,7 +23491,7 @@ async function tryLock(filename) {
22760
23491
  if (errorCode2(processError) !== "ESRCH") throw processError;
22761
23492
  }
22762
23493
  }
22763
- await fs6.rm(filename, { force: true });
23494
+ await fs12.rm(filename, { force: true });
22764
23495
  }
22765
23496
  }
22766
23497
  throw new LockBusyError(`Could not obtain ${filename}.`);
@@ -22810,15 +23541,15 @@ function confirmTrialSource(state, version3, source, nowMs) {
22810
23541
  function versionDirectory(paths, version3) {
22811
23542
  if (!isVersion(version3))
22812
23543
  throw new Error(`Invalid Iteration version ${version3}.`);
22813
- return path7.join(paths.versions, version3);
23544
+ return path17.join(paths.versions, version3);
22814
23545
  }
22815
23546
  function versionEntry(paths, version3, entry) {
22816
- return path7.join(versionDirectory(paths, version3), "dist", entry);
23547
+ return path17.join(versionDirectory(paths, version3), "dist", entry);
22817
23548
  }
22818
23549
  var packageSchema = object3({ name: literal2("iteration-cli"), version: versionSchema }).loose();
22819
23550
  async function readPackageVersion(root) {
22820
23551
  const parsed = packageSchema.safeParse(
22821
- JSON.parse(await fs6.readFile(path7.join(root, "package.json"), "utf8"))
23552
+ JSON.parse(await fs12.readFile(path17.join(root, "package.json"), "utf8"))
22822
23553
  );
22823
23554
  if (!parsed.success) {
22824
23555
  throw new Error(`${root} does not contain an Iteration CLI package.`, {
@@ -22828,7 +23559,7 @@ async function readPackageVersion(root) {
22828
23559
  return parsed.data.version;
22829
23560
  }
22830
23561
  function ownPackageRoot() {
22831
- return path7.dirname(path7.dirname(fileURLToPath(import.meta.url)));
23562
+ return path17.dirname(path17.dirname(fileURLToPath(import.meta.url)));
22832
23563
  }
22833
23564
  async function assertInstallablePackage(root, version3) {
22834
23565
  const packageVersion = await readPackageVersion(root);
@@ -22837,12 +23568,12 @@ async function assertInstallablePackage(root, version3) {
22837
23568
  `The downloaded package is version ${packageVersion}, not ${version3}.`
22838
23569
  );
22839
23570
  }
22840
- const pending = [root];
22841
- while (pending.length > 0) {
22842
- const directory = pending.pop();
22843
- for (const entry of await fs6.readdir(directory, { withFileTypes: true })) {
22844
- const filename = path7.join(directory, entry.name);
22845
- if (entry.isDirectory()) pending.push(filename);
23571
+ const pending2 = [root];
23572
+ while (pending2.length > 0) {
23573
+ const directory = pending2.pop();
23574
+ for (const entry of await fs12.readdir(directory, { withFileTypes: true })) {
23575
+ const filename = path17.join(directory, entry.name);
23576
+ if (entry.isDirectory()) pending2.push(filename);
22846
23577
  else if (!entry.isFile())
22847
23578
  throw new Error(
22848
23579
  `The package contains an unexpected entry ${filename}.`
@@ -22850,7 +23581,7 @@ async function assertInstallablePackage(root, version3) {
22850
23581
  }
22851
23582
  }
22852
23583
  for (const entry of ["cli.js", "cursor-hook-entry.js", "launcher.js"]) {
22853
- const stats = await fs6.stat(path7.join(root, "dist", entry));
23584
+ const stats = await fs12.stat(path17.join(root, "dist", entry));
22854
23585
  if (!stats.isFile())
22855
23586
  throw new Error(`The package is missing dist/${entry}.`);
22856
23587
  }
@@ -22858,48 +23589,48 @@ async function assertInstallablePackage(root, version3) {
22858
23589
  async function installVersionDirectory(paths, staged, version3) {
22859
23590
  await assertInstallablePackage(staged, version3);
22860
23591
  const target = versionDirectory(paths, version3);
22861
- const displaced = path7.join(
23592
+ const displaced = path17.join(
22862
23593
  paths.staging,
22863
23594
  `displaced-${version3}-${process.pid}-${Date.now()}`
22864
23595
  );
22865
23596
  try {
22866
- await fs6.rename(target, displaced);
23597
+ await fs12.rename(target, displaced);
22867
23598
  } catch (error3) {
22868
23599
  if (errorCode2(error3) !== "ENOENT") throw error3;
22869
23600
  }
22870
- await fs6.rename(staged, target);
22871
- await fs6.rm(displaced, { recursive: true, force: true });
23601
+ await fs12.rename(staged, target);
23602
+ await fs12.rm(displaced, { recursive: true, force: true });
22872
23603
  }
22873
23604
  async function copyOwnPackage(paths, root, version3) {
22874
23605
  await ensureRuntimeDirectory(paths);
22875
- const staged = await fs6.mkdtemp(path7.join(paths.staging, `copy-${version3}-`));
23606
+ const staged = await fs12.mkdtemp(path17.join(paths.staging, `copy-${version3}-`));
22876
23607
  try {
22877
- const packageRoot = path7.join(staged, "package");
22878
- await fs6.mkdir(packageRoot);
22879
- await fs6.copyFile(
22880
- path7.join(root, "package.json"),
22881
- path7.join(packageRoot, "package.json")
23608
+ const packageRoot = path17.join(staged, "package");
23609
+ await fs12.mkdir(packageRoot);
23610
+ await fs12.copyFile(
23611
+ path17.join(root, "package.json"),
23612
+ path17.join(packageRoot, "package.json")
22882
23613
  );
22883
- await fs6.cp(path7.join(root, "dist"), path7.join(packageRoot, "dist"), {
23614
+ await fs12.cp(path17.join(root, "dist"), path17.join(packageRoot, "dist"), {
22884
23615
  recursive: true,
22885
23616
  dereference: true
22886
23617
  });
22887
23618
  await installVersionDirectory(paths, packageRoot, version3);
22888
23619
  } finally {
22889
- await fs6.rm(staged, { recursive: true, force: true });
23620
+ await fs12.rm(staged, { recursive: true, force: true });
22890
23621
  }
22891
23622
  }
22892
23623
  async function pruneVersions(paths, keep) {
22893
23624
  let entries2;
22894
23625
  try {
22895
- entries2 = await fs6.readdir(paths.versions);
23626
+ entries2 = await fs12.readdir(paths.versions);
22896
23627
  } catch (error3) {
22897
23628
  if (errorCode2(error3) === "ENOENT") return;
22898
23629
  throw error3;
22899
23630
  }
22900
23631
  for (const entry of entries2) {
22901
23632
  if (keep.includes(entry)) continue;
22902
- await fs6.rm(path7.join(paths.versions, entry), {
23633
+ await fs12.rm(path17.join(paths.versions, entry), {
22903
23634
  recursive: true,
22904
23635
  force: true
22905
23636
  });
@@ -22908,8 +23639,8 @@ async function pruneVersions(paths, keep) {
22908
23639
  async function launcherMatches(paths, version3) {
22909
23640
  try {
22910
23641
  const [installed, packaged] = await Promise.all([
22911
- fs6.readFile(paths.launcher),
22912
- fs6.readFile(versionEntry(paths, version3, "launcher.js"))
23642
+ fs12.readFile(paths.launcher),
23643
+ fs12.readFile(versionEntry(paths, version3, "launcher.js"))
22913
23644
  ]);
22914
23645
  return installed.equals(packaged);
22915
23646
  } catch (error3) {
@@ -22918,35 +23649,35 @@ async function launcherMatches(paths, version3) {
22918
23649
  }
22919
23650
  }
22920
23651
  async function stageLauncher(paths, version3) {
22921
- const staged = path7.join(
23652
+ const staged = path17.join(
22922
23653
  paths.staging,
22923
23654
  `launcher-${version3}-${process.pid}-${Date.now()}.js`
22924
23655
  );
22925
- await fs6.copyFile(versionEntry(paths, version3, "launcher.js"), staged);
22926
- await fs6.chmod(staged, 420);
23656
+ await fs12.copyFile(versionEntry(paths, version3, "launcher.js"), staged);
23657
+ await fs12.chmod(staged, 420);
22927
23658
  return staged;
22928
23659
  }
22929
23660
  async function commitLauncher(paths, staged) {
22930
- await fs6.rename(staged, paths.launcher);
23661
+ await fs12.rename(staged, paths.launcher);
22931
23662
  }
22932
23663
  function rolloutDelayMs(deviceKey) {
22933
23664
  const digest3 = createHash4("sha256").update(deviceKey).digest();
22934
23665
  return digest3.readUInt32BE(0) % 360 * 6e4;
22935
23666
  }
22936
23667
  function deviceRolloutKey() {
22937
- return `${os.hostname()}\0${os.homedir()}`;
23668
+ return `${os3.hostname()}\0${os3.homedir()}`;
22938
23669
  }
22939
23670
  async function stableNodePath() {
22940
- const actual = await fs6.realpath(process.execPath);
23671
+ const actual = await fs12.realpath(process.execPath);
22941
23672
  const invoked = process.argv0;
22942
23673
  const binary = process.platform === "win32" ? "node.exe" : "node";
22943
23674
  const candidates = [
22944
- ...invoked !== void 0 && path7.isAbsolute(invoked) ? [invoked] : [],
22945
- ...(process.env.PATH ?? "").split(path7.delimiter).filter((directory) => path7.isAbsolute(directory)).map((directory) => path7.join(directory, binary))
23675
+ ...invoked !== void 0 && path17.isAbsolute(invoked) ? [invoked] : [],
23676
+ ...(process.env.PATH ?? "").split(path17.delimiter).filter((directory) => path17.isAbsolute(directory)).map((directory) => path17.join(directory, binary))
22946
23677
  ];
22947
23678
  for (const candidate of candidates) {
22948
23679
  try {
22949
- const resolved = await fs6.realpath(candidate);
23680
+ const resolved = await fs12.realpath(candidate);
22950
23681
  const same = process.platform === "win32" ? resolved.toLowerCase() === actual.toLowerCase() : resolved === actual;
22951
23682
  if (same) return candidate;
22952
23683
  } catch (error3) {
@@ -22979,18 +23710,22 @@ function write(message) {
22979
23710
  }
22980
23711
  async function serveAgentTools(config3) {
22981
23712
  let access = null;
23713
+ let scopedConfig = config3;
22982
23714
  let version3;
22983
23715
  let renewing = null;
22984
23716
  const fetchAccess = async () => {
22985
- if (config3 === null)
23717
+ if (scopedConfig === null)
22986
23718
  throw new Error(
22987
23719
  "This computer is not connected to Iteration. Run iteration connect."
22988
23720
  );
22989
23721
  const response = await collectorFetch(
22990
- new URL("./tool-access", validateCollectorEndpoint(config3.endpoint)),
23722
+ new URL(
23723
+ "./tool-access",
23724
+ validateCollectorEndpoint(scopedConfig.endpoint)
23725
+ ),
22991
23726
  {
22992
23727
  method: "POST",
22993
- headers: { Authorization: `Bearer ${config3.token}` },
23728
+ headers: { Authorization: `Bearer ${scopedConfig.token}` },
22994
23729
  signal: AbortSignal.timeout(15e3)
22995
23730
  }
22996
23731
  );
@@ -23014,6 +23749,16 @@ async function serveAgentTools(config3) {
23014
23749
  return renewing;
23015
23750
  };
23016
23751
  const post = async (body, method, force) => {
23752
+ if (config3?.destination === "host") {
23753
+ const selected = await configForSharedDirectory(config3, process.cwd());
23754
+ if (selected === null)
23755
+ throw new Error(
23756
+ "This project is not shared with Iteration. Choose it in project sharing first."
23757
+ );
23758
+ if (selected.organizationId !== scopedConfig?.organizationId || selected.token !== scopedConfig?.token)
23759
+ access = null;
23760
+ scopedConfig = selected;
23761
+ }
23017
23762
  const current = await renew(force);
23018
23763
  return fetch(current.url, {
23019
23764
  method: "POST",
@@ -23068,27 +23813,27 @@ async function serveAgentTools(config3) {
23068
23813
  });
23069
23814
  }
23070
23815
  };
23071
- const pending = /* @__PURE__ */ new Set();
23816
+ const pending2 = /* @__PURE__ */ new Set();
23072
23817
  for await (const line of readline.createInterface({
23073
23818
  input: process.stdin
23074
23819
  })) {
23075
23820
  if (!line.trim()) continue;
23076
23821
  const task = forward(line).catch(() => void 0);
23077
- pending.add(task);
23078
- void task.finally(() => pending.delete(task));
23822
+ pending2.add(task);
23823
+ void task.finally(() => pending2.delete(task));
23079
23824
  }
23080
- await Promise.allSettled(pending);
23825
+ await Promise.allSettled(pending2);
23081
23826
  }
23082
23827
  async function writeAtomically(filename, content) {
23083
- await fs11.mkdir(path8.dirname(filename), { recursive: true });
23084
- const mode = await fs11.stat(filename).then((stat) => stat.mode & 511).catch(() => 384);
23828
+ await fs13.mkdir(path18.dirname(filename), { recursive: true });
23829
+ const mode = await fs13.stat(filename).then((stat) => stat.mode & 511).catch(() => 384);
23085
23830
  const temporary = `${filename}.${process.pid}.${Date.now()}.tmp`;
23086
- await fs11.writeFile(temporary, content, { mode });
23087
- await fs11.rename(temporary, filename);
23831
+ await fs13.writeFile(temporary, content, { mode });
23832
+ await fs13.rename(temporary, filename);
23088
23833
  }
23089
23834
  async function readText2(filename) {
23090
23835
  try {
23091
- return await fs11.readFile(filename, "utf8");
23836
+ return await fs13.readFile(filename, "utf8");
23092
23837
  } catch (error3) {
23093
23838
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
23094
23839
  return null;
@@ -23146,9 +23891,9 @@ args = ${JSON.stringify(args)}
23146
23891
  await writeAtomically(filename, next);
23147
23892
  }
23148
23893
  function claudeSettingsFile(claudeHome) {
23149
- return path8.resolve(claudeHome) === path8.join(os3.homedir(), ".claude") ? path8.join(os3.homedir(), ".claude.json") : path8.join(claudeHome, ".claude.json");
23894
+ return path18.resolve(claudeHome) === path18.join(os4.homedir(), ".claude") ? path18.join(os4.homedir(), ".claude.json") : path18.join(claudeHome, ".claude.json");
23150
23895
  }
23151
- var cursorSettingsFile = () => path8.join(os3.homedir(), ".cursor", "mcp.json");
23896
+ var cursorSettingsFile = () => path18.join(os4.homedir(), ".cursor", "mcp.json");
23152
23897
  async function installAgentTools(homes) {
23153
23898
  const command = [
23154
23899
  await stableNodePath(),
@@ -23163,7 +23908,7 @@ async function installAgentTools(homes) {
23163
23908
  args
23164
23909
  });
23165
23910
  if (homes.codex !== void 0)
23166
- await updateCodexServer(path8.join(homes.codex, "config.toml"), command);
23911
+ await updateCodexServer(path18.join(homes.codex, "config.toml"), command);
23167
23912
  if (homes.cursor)
23168
23913
  await updateJsonServers(cursorSettingsFile(), { command: program, args });
23169
23914
  }
@@ -23171,7 +23916,7 @@ async function uninstallAgentTools(homes) {
23171
23916
  if (homes.claude !== void 0)
23172
23917
  await updateJsonServers(claudeSettingsFile(homes.claude), null);
23173
23918
  if (homes.codex !== void 0)
23174
- await updateCodexServer(path8.join(homes.codex, "config.toml"), null);
23919
+ await updateCodexServer(path18.join(homes.codex, "config.toml"), null);
23175
23920
  if (homes.cursor) await updateJsonServers(cursorSettingsFile(), null);
23176
23921
  }
23177
23922
  function agentHomes(config3) {
@@ -23180,127 +23925,20 @@ function agentHomes(config3) {
23180
23925
 
23181
23926
  // src/auto-connect.ts
23182
23927
  import { createHmac } from "node:crypto";
23183
- import { promises as fs20 } from "node:fs";
23184
- import os9 from "node:os";
23185
- import path25 from "node:path";
23186
-
23187
- // ../collector-contracts/dist/index.js
23188
- var cursorBillingRequestSchema = object3({
23189
- conversationId: uuid5(),
23190
- reconcileFromMs: number4().int().nonnegative().nullable(),
23191
- events: array2(object3({
23192
- timestamp: number4().int().nonnegative(),
23193
- kind: _enum2([
23194
- "included",
23195
- "paid",
23196
- "byok",
23197
- "not_charged",
23198
- "unknown"
23199
- ]),
23200
- inputTokens: number4().int().nonnegative().nullable(),
23201
- outputTokens: number4().int().nonnegative().nullable(),
23202
- cacheReadTokens: number4().int().nonnegative().nullable(),
23203
- cacheWriteTokens: number4().int().nonnegative().nullable(),
23204
- paidUsd: number4().finite().nonnegative().nullable()
23205
- }).strict()).max(5e3)
23206
- }).strict();
23207
- function codexCreditSign(value) {
23208
- const mantissa = value.split(/[eE]/, 1)[0];
23209
- if (!/[1-9]/.test(mantissa))
23210
- return 0;
23211
- return mantissa.startsWith("-") ? -1 : 1;
23212
- }
23213
- var codexBillingRequestSchema = object3({
23214
- accountFingerprint: string5().regex(/^[0-9a-f]{64}$/),
23215
- reconcile: boolean4(),
23216
- tasks: array2(object3({
23217
- threadId: uuid5(),
23218
- createdAt: iso_exports2.datetime().nullable(),
23219
- descendantThreadIds: array2(uuid5()).max(900),
23220
- dataStatus: _enum2([
23221
- "available",
23222
- "partial",
23223
- "unavailable",
23224
- "not_queried"
23225
- ]),
23226
- usageSource: string5().max(100),
23227
- balanceUsageCredits: string5().regex(/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d{1,3})?$/).max(128).nullable()
23228
- }).strict()).max(100),
23229
- authTurns: array2(object3({
23230
- threadId: uuid5(),
23231
- turnId: uuid5(),
23232
- mode: _enum2(["chatgpt", "api_key", "conflicting"])
23233
- }).strict()).max(5e3)
23234
- }).strict();
23235
- var UUID_PATTERN3 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
23236
- var episodeCountSchema = number4().int().min(0).max(Number.MAX_SAFE_INTEGER);
23237
- var collectorFrictionSummarySchema = object3({
23238
- weekEpisodes: episodeCountSchema,
23239
- dayEpisodes: episodeCountSchema
23240
- }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.").describe("dayEpisodes must not exceed weekEpisodes.");
23241
- var RECENT_MESSAGE_LIMIT = 400;
23242
- var sessionCountSchema = number4().int().min(0).max(1e6);
23243
- var collectorHistoryProgressSchema = object3({
23244
- sessions: sessionCountSchema,
23245
- readSessions: sessionCountSchema,
23246
- complete: boolean4(),
23247
- recentMessages: object3({
23248
- sent: number4().int().min(0).max(RECENT_MESSAGE_LIMIT),
23249
- complete: boolean4()
23250
- }).strict().optional().describe("The newest messages sent ahead of the rest of the history; collectors before 0.17.0 omit it.")
23251
- }).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.").describe("readSessions must not exceed sessions; complete requires equal counts.");
23252
- var collectorEnrollmentRequestSchema = object3({
23253
- deviceName: string5().trim().min(1).max(160).describe("Trimmed before length validation."),
23254
- organizationId: string5().regex(UUID_PATTERN3).optional(),
23255
- source: _enum2(COLLECTOR_SOURCES2).optional()
23256
- }).strict();
23257
- var collectorExpansionRequestSchema = object3({
23258
- source: _enum2(COLLECTOR_SOURCES2),
23259
- token: string5().regex(/^[A-Za-z0-9_-]{43}$/),
23260
- organizationId: string5().regex(UUID_PATTERN3).optional()
23261
- }).strict();
23262
- var collectorEnrollmentResponseSchema = discriminatedUnion2("status", [
23263
- object3({
23264
- status: literal2("enrolled"),
23265
- token: string5().regex(/^[A-Za-z0-9_-]{43}$/),
23266
- deviceId: string5().regex(UUID_PATTERN3),
23267
- organizationId: string5().regex(UUID_PATTERN3),
23268
- organizationName: string5().min(1).max(160)
23269
- }).strict(),
23270
- object3({
23271
- status: literal2("choose_organization"),
23272
- organizations: array2(object3({
23273
- id: string5().regex(UUID_PATTERN3),
23274
- name: string5().min(1).max(160)
23275
- }).strict()).max(20),
23276
- hasMore: boolean4()
23277
- }).strict()
23278
- ]);
23279
- function parseCollectorEnrollmentResponse(input) {
23280
- const parsed = collectorEnrollmentResponseSchema.safeParse(input);
23281
- return parsed.success ? { success: true, value: parsed.data } : {
23282
- success: false,
23283
- error: parsed.error.issues[0]?.message ?? "Collector enrollment response is invalid."
23284
- };
23285
- }
23286
- function parseCollectorFrictionSummary(input) {
23287
- const parsed = collectorFrictionSummarySchema.safeParse(input);
23288
- return parsed.success ? { success: true, value: parsed.data } : {
23289
- success: false,
23290
- error: parsed.error.issues[0]?.message ?? "Friction summary is invalid."
23291
- };
23292
- }
23928
+ import { promises as fs22 } from "node:fs";
23929
+ import os10 from "node:os";
23930
+ import path27 from "node:path";
23293
23931
 
23294
23932
  // src/background-service.ts
23295
- import { promises as fs17 } from "node:fs";
23296
- import os7 from "node:os";
23297
- import path21 from "node:path";
23933
+ import { promises as fs19 } from "node:fs";
23934
+ import os8 from "node:os";
23935
+ import path23 from "node:path";
23298
23936
 
23299
23937
  // src/launch-agent.ts
23300
23938
  import { execFile as execFileCallback2 } from "node:child_process";
23301
- import { promises as fs12 } from "node:fs";
23302
- import os4 from "node:os";
23303
- import path17 from "node:path";
23939
+ import { promises as fs14 } from "node:fs";
23940
+ import os5 from "node:os";
23941
+ import path19 from "node:path";
23304
23942
  import { setTimeout as sleep } from "node:timers/promises";
23305
23943
  import { promisify as promisify2 } from "node:util";
23306
23944
  var execFile2 = promisify2(execFileCallback2);
@@ -23313,16 +23951,16 @@ var RESTART_POLL_MS = 200;
23313
23951
  function xml(value) {
23314
23952
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
23315
23953
  }
23316
- function plistPath(source, homeDirectory = os4.homedir()) {
23317
- return path17.join(
23954
+ function plistPath(source, homeDirectory = os5.homedir()) {
23955
+ return path19.join(
23318
23956
  homeDirectory,
23319
23957
  "Library",
23320
23958
  "LaunchAgents",
23321
23959
  `${label(source)}.plist`
23322
23960
  );
23323
23961
  }
23324
- function launchAgentPlist(launcherPath = runtimePaths().launcher, nodePath = process.execPath, logDirectory = path17.join(os4.homedir(), "Library", "Logs"), dataDirectory2 = path17.join(
23325
- os4.homedir(),
23962
+ function launchAgentPlist(launcherPath = runtimePaths().launcher, nodePath = process.execPath, logDirectory = path19.join(os5.homedir(), "Library", "Logs"), dataDirectory2 = path19.join(
23963
+ os5.homedir(),
23326
23964
  ".config",
23327
23965
  "iteration",
23328
23966
  "codex-collector"
@@ -23345,8 +23983,8 @@ function launchAgentPlist(launcherPath = runtimePaths().launcher, nodePath = pro
23345
23983
  "<key>RunAtLoad</key><true/>",
23346
23984
  "<key>KeepAlive</key><true/>",
23347
23985
  "<key>ThrottleInterval</key><integer>10</integer>",
23348
- `<key>StandardOutPath</key><string>${xml(path17.join(logDirectory, `iteration-${source}-collector.log`))}</string>`,
23349
- `<key>StandardErrorPath</key><string>${xml(path17.join(logDirectory, `iteration-${source}-collector.error.log`))}</string>`,
23986
+ `<key>StandardOutPath</key><string>${xml(path19.join(logDirectory, `iteration-${source}-collector.log`))}</string>`,
23987
+ `<key>StandardErrorPath</key><string>${xml(path19.join(logDirectory, `iteration-${source}-collector.error.log`))}</string>`,
23350
23988
  "</dict></plist>"
23351
23989
  ].join("");
23352
23990
  }
@@ -23426,17 +24064,17 @@ async function installLaunchAgent(dataDirectory2, source = "codex") {
23426
24064
  throw new Error("Automatic startup is currently supported on macOS only.");
23427
24065
  }
23428
24066
  const file = plistPath(source);
23429
- const launchAgents = path17.dirname(file);
23430
- const logs = path17.join(os4.homedir(), "Library", "Logs");
23431
- await fs12.mkdir(launchAgents, { recursive: true, mode: 493 });
23432
- await fs12.mkdir(logs, { recursive: true, mode: 493 });
23433
- await fs12.writeFile(
24067
+ const launchAgents = path19.dirname(file);
24068
+ const logs = path19.join(os5.homedir(), "Library", "Logs");
24069
+ await fs14.mkdir(launchAgents, { recursive: true, mode: 493 });
24070
+ await fs14.mkdir(logs, { recursive: true, mode: 493 });
24071
+ await fs14.writeFile(
23434
24072
  file,
23435
24073
  launchAgentPlist(
23436
24074
  void 0,
23437
24075
  await stableNodePath(),
23438
24076
  void 0,
23439
- dataDirectory2 ?? path17.join(os4.homedir(), ".config", "iteration", `${source}-collector`),
24077
+ dataDirectory2 ?? path19.join(os5.homedir(), ".config", "iteration", `${source}-collector`),
23440
24078
  source
23441
24079
  ),
23442
24080
  { encoding: "utf8", mode: 420 }
@@ -23468,7 +24106,7 @@ async function uninstallLaunchAgent(source = "codex") {
23468
24106
  }
23469
24107
  const file = plistPath(source);
23470
24108
  await bootoutIfLoaded(launchctlTarget(), source);
23471
- await fs12.rm(file, { force: true });
24109
+ await fs14.rm(file, { force: true });
23472
24110
  }
23473
24111
  async function launchAgentStatus(source = "codex") {
23474
24112
  if (process.platform !== "darwin") return false;
@@ -23489,21 +24127,21 @@ function launchAgentRunning(output) {
23489
24127
 
23490
24128
  // src/runtime-install.ts
23491
24129
  import { execFile as execFileCallback4 } from "node:child_process";
23492
- import { promises as fs14 } from "node:fs";
24130
+ import { promises as fs16 } from "node:fs";
23493
24131
  import { promisify as promisify4 } from "node:util";
23494
24132
 
23495
24133
  // src/updater.ts
23496
24134
  import { execFile as execFileCallback3 } from "node:child_process";
23497
24135
  import { createHash as createHash5 } from "node:crypto";
23498
- import { createWriteStream as createWriteStream2, promises as fs13 } from "node:fs";
24136
+ import { createWriteStream as createWriteStream2, promises as fs15 } from "node:fs";
23499
24137
  import http from "node:http";
23500
24138
  import https2 from "node:https";
23501
- import path18 from "node:path";
24139
+ import path20 from "node:path";
23502
24140
  import { Transform } from "node:stream";
23503
24141
  import { pipeline as pipeline2 } from "node:stream/promises";
23504
24142
  import { promisify as promisify3 } from "node:util";
23505
24143
  var execFile3 = promisify3(execFileCallback3);
23506
- var TAR = process.platform === "win32" ? path18.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
24144
+ var TAR = process.platform === "win32" ? path20.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
23507
24145
  var PACKAGE_NAME = "iteration-cli";
23508
24146
  var MAX_METADATA_BYTES = 16 * 1024 * 1024;
23509
24147
  var MAX_TARBALL_BYTES = 64 * 1024 * 1024;
@@ -23707,32 +24345,32 @@ async function selfCheck(paths, cliPath, version3) {
23707
24345
  }
23708
24346
  async function installCandidate(paths, candidate, signal) {
23709
24347
  await ensureRuntimeDirectory(paths);
23710
- const staged = await fs13.mkdtemp(
23711
- path18.join(paths.staging, `download-${candidate.version}-`)
24348
+ const staged = await fs15.mkdtemp(
24349
+ path20.join(paths.staging, `download-${candidate.version}-`)
23712
24350
  );
23713
24351
  try {
23714
- const archive = path18.join(staged, "package.tgz");
24352
+ const archive = path20.join(staged, "package.tgz");
23715
24353
  await downloadVerified(
23716
24354
  candidate.tarball,
23717
24355
  candidate.integrity,
23718
24356
  archive,
23719
24357
  signal
23720
24358
  );
23721
- const extracted = path18.join(staged, "extracted");
23722
- await fs13.mkdir(extracted, { mode: 448 });
24359
+ const extracted = path20.join(staged, "extracted");
24360
+ await fs15.mkdir(extracted, { mode: 448 });
23723
24361
  await execFile3(TAR, ["-xzf", archive, "-C", extracted], {
23724
24362
  timeout: REQUEST_TIMEOUT_MS,
23725
24363
  windowsHide: true
23726
24364
  });
23727
- const packageRoot = path18.join(extracted, "package");
24365
+ const packageRoot = path20.join(extracted, "package");
23728
24366
  await selfCheck(
23729
24367
  paths,
23730
- path18.join(packageRoot, "dist", "cli.js"),
24368
+ path20.join(packageRoot, "dist", "cli.js"),
23731
24369
  candidate.version
23732
24370
  );
23733
24371
  await installVersionDirectory(paths, packageRoot, candidate.version);
23734
24372
  } finally {
23735
- await fs13.rm(staged, { recursive: true, force: true });
24373
+ await fs15.rm(staged, { recursive: true, force: true });
23736
24374
  }
23737
24375
  }
23738
24376
  async function switchVersion(paths, from, version3, previous, expected, nowMs) {
@@ -23840,7 +24478,7 @@ async function ownVersion() {
23840
24478
  }
23841
24479
  async function exists(filename) {
23842
24480
  try {
23843
- await fs14.access(filename);
24481
+ await fs16.access(filename);
23844
24482
  return true;
23845
24483
  } catch (error3) {
23846
24484
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
@@ -23867,7 +24505,7 @@ async function installLauncher(paths, version3) {
23867
24505
  );
23868
24506
  await commitLauncher(paths, staged);
23869
24507
  } finally {
23870
- await fs14.rm(staged, { force: true });
24508
+ await fs16.rm(staged, { force: true });
23871
24509
  }
23872
24510
  }
23873
24511
  async function ensureRuntimeInstalled(expected, paths = runtimePaths()) {
@@ -23949,9 +24587,9 @@ async function expireRuntimeTrial(nowMs, paths = runtimePaths()) {
23949
24587
 
23950
24588
  // src/systemd-user.ts
23951
24589
  import { execFile as execFileCallback5 } from "node:child_process";
23952
- import { promises as fs15 } from "node:fs";
23953
- import os5 from "node:os";
23954
- import path19 from "node:path";
24590
+ import { promises as fs17 } from "node:fs";
24591
+ import os6 from "node:os";
24592
+ import path21 from "node:path";
23955
24593
  import { promisify as promisify5 } from "node:util";
23956
24594
  var execFile5 = promisify5(execFileCallback5);
23957
24595
  var SYSTEMCTL_TIMEOUT_MS = 1e4;
@@ -24021,8 +24659,8 @@ function unitName(source) {
24021
24659
  return `iteration-${source}-collector.service`;
24022
24660
  }
24023
24661
  function unitPath(source) {
24024
- return path19.join(
24025
- os5.homedir(),
24662
+ return path21.join(
24663
+ os6.homedir(),
24026
24664
  ".config",
24027
24665
  "systemd",
24028
24666
  "user",
@@ -24030,8 +24668,8 @@ function unitPath(source) {
24030
24668
  );
24031
24669
  }
24032
24670
  function systemdLogPath(source) {
24033
- return path19.join(
24034
- os5.homedir(),
24671
+ return path21.join(
24672
+ os6.homedir(),
24035
24673
  ".config",
24036
24674
  "iteration",
24037
24675
  "logs",
@@ -24043,13 +24681,13 @@ function unitValue(value) {
24043
24681
  throw new Error("Collector paths cannot contain line breaks.");
24044
24682
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
24045
24683
  }
24046
- function systemdUserUnit(launcherPath = runtimePaths().launcher, nodePath = process.execPath, dataDirectory2 = path19.join(
24047
- os5.homedir(),
24684
+ function systemdUserUnit(launcherPath = runtimePaths().launcher, nodePath = process.execPath, dataDirectory2 = path21.join(
24685
+ os6.homedir(),
24048
24686
  ".config",
24049
24687
  "iteration",
24050
24688
  "codex-collector"
24051
- ), source = "codex", outputPath = path19.join(
24052
- os5.homedir(),
24689
+ ), source = "codex", outputPath = path21.join(
24690
+ os6.homedir(),
24053
24691
  ".config",
24054
24692
  "iteration",
24055
24693
  "logs",
@@ -24079,10 +24717,10 @@ async function systemctl(...args) {
24079
24717
  async function installSystemdUserService(dataDirectory2, source) {
24080
24718
  await ensureSystemdUserLinger();
24081
24719
  const file = unitPath(source);
24082
- const logDirectory = path19.dirname(systemdLogPath(source));
24083
- await fs15.mkdir(path19.dirname(file), { recursive: true, mode: 448 });
24084
- await fs15.mkdir(logDirectory, { recursive: true, mode: 448 });
24085
- await fs15.writeFile(
24720
+ const logDirectory = path21.dirname(systemdLogPath(source));
24721
+ await fs17.mkdir(path21.dirname(file), { recursive: true, mode: 448 });
24722
+ await fs17.mkdir(logDirectory, { recursive: true, mode: 448 });
24723
+ await fs17.writeFile(
24086
24724
  file,
24087
24725
  systemdUserUnit(void 0, await stableNodePath(), dataDirectory2, source),
24088
24726
  {
@@ -24098,14 +24736,14 @@ async function installSystemdUserService(dataDirectory2, source) {
24098
24736
  async function uninstallSystemdUserService(source) {
24099
24737
  const file = unitPath(source);
24100
24738
  try {
24101
- await fs15.access(file);
24739
+ await fs17.access(file);
24102
24740
  } catch (error3) {
24103
24741
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
24104
24742
  return;
24105
24743
  throw error3;
24106
24744
  }
24107
24745
  await systemctl("disable", "--now", unitName(source));
24108
- await fs15.rm(file);
24746
+ await fs17.rm(file);
24109
24747
  await systemctl("daemon-reload");
24110
24748
  }
24111
24749
  async function systemdUserServiceStatus(source) {
@@ -24124,9 +24762,9 @@ async function systemdUserServiceStatus(source) {
24124
24762
 
24125
24763
  // src/task-scheduler.ts
24126
24764
  import { execFile as execFileCallback6 } from "node:child_process";
24127
- import { promises as fs16 } from "node:fs";
24128
- import os6 from "node:os";
24129
- import path20 from "node:path";
24765
+ import { promises as fs18 } from "node:fs";
24766
+ import os7 from "node:os";
24767
+ import path22 from "node:path";
24130
24768
  import { setTimeout as delay3 } from "node:timers/promises";
24131
24769
  import { promisify as promisify6 } from "node:util";
24132
24770
 
@@ -24228,14 +24866,14 @@ function scheduledTaskName(source, sid) {
24228
24866
  return `iteration-${sid}-${source}-collector`;
24229
24867
  }
24230
24868
  function servicesDirectory() {
24231
- return path20.join(os6.homedir(), ".config", "iteration", "services");
24869
+ return path22.join(os7.homedir(), ".config", "iteration", "services");
24232
24870
  }
24233
24871
  function taskDefinitionPath(source) {
24234
- return path20.join(servicesDirectory(), `iteration-${source}-collector.xml`);
24872
+ return path22.join(servicesDirectory(), `iteration-${source}-collector.xml`);
24235
24873
  }
24236
24874
  function windowsLogPath(source) {
24237
- return path20.join(
24238
- os6.homedir(),
24875
+ return path22.join(
24876
+ os7.homedir(),
24239
24877
  ".config",
24240
24878
  "iteration",
24241
24879
  "logs",
@@ -24243,8 +24881,8 @@ function windowsLogPath(source) {
24243
24881
  );
24244
24882
  }
24245
24883
  function windowsOutputPath(source) {
24246
- return path20.join(
24247
- os6.homedir(),
24884
+ return path22.join(
24885
+ os7.homedir(),
24248
24886
  ".config",
24249
24887
  "iteration",
24250
24888
  "logs",
@@ -24259,7 +24897,7 @@ function scheduledTaskScript(task) {
24259
24897
  "--data-directory",
24260
24898
  task.dataDirectory
24261
24899
  ].map(windowsArgument).join(" ");
24262
- const errorLog = path20.join(
24900
+ const errorLog = path22.join(
24263
24901
  task.home,
24264
24902
  ".config",
24265
24903
  "iteration",
@@ -24379,7 +25017,7 @@ function AccountSid([string]$account) {
24379
25017
  async function schtasks(source, operation, ...args) {
24380
25018
  try {
24381
25019
  await execFile6(
24382
- path20.join(
25020
+ path22.join(
24383
25021
  process.env.SystemRoot ?? "C:\\Windows",
24384
25022
  "System32",
24385
25023
  "schtasks.exe"
@@ -24423,7 +25061,7 @@ async function desiredTask(dataDirectory2, source) {
24423
25061
  userSid: await currentUserSid(),
24424
25062
  nodePath: await stableNodePath(),
24425
25063
  launcher: runtimePaths().launcher,
24426
- home: os6.homedir()
25064
+ home: os7.homedir()
24427
25065
  };
24428
25066
  }
24429
25067
  async function scheduledTaskUsesRuntime(dataDirectory2, source) {
@@ -24435,10 +25073,10 @@ async function scheduledTaskUsesRuntime(dataDirectory2, source) {
24435
25073
  async function installScheduledTask(dataDirectory2, source) {
24436
25074
  const file = taskDefinitionPath(source);
24437
25075
  await ensurePrivateDirectory(servicesDirectory());
24438
- await ensurePrivateDirectory(path20.dirname(windowsLogPath(source)));
25076
+ await ensurePrivateDirectory(path22.dirname(windowsLogPath(source)));
24439
25077
  await stopTask(source);
24440
25078
  const xml2 = scheduledTaskXml(await desiredTask(dataDirectory2, source));
24441
- await fs16.writeFile(
25079
+ await fs18.writeFile(
24442
25080
  file,
24443
25081
  Buffer.concat([Buffer.from([255, 254]), Buffer.from(xml2, "utf16le")])
24444
25082
  );
@@ -24448,7 +25086,7 @@ async function installScheduledTask(dataDirectory2, source) {
24448
25086
  }
24449
25087
  async function uninstallScheduledTask(source) {
24450
25088
  if (await stopTask(source)) await schtasks(source, "/Delete", "/F");
24451
- await fs16.rm(taskDefinitionPath(source), { force: true });
25089
+ await fs18.rm(taskDefinitionPath(source), { force: true });
24452
25090
  }
24453
25091
  async function scheduledTaskStatus(source) {
24454
25092
  return (await observedTask(source))?.state === 4;
@@ -24472,8 +25110,8 @@ async function prepareBackgroundPersistence() {
24472
25110
  function collectorLogPath(source) {
24473
25111
  if (process.platform === "linux") return systemdLogPath(source);
24474
25112
  if (process.platform === "win32") return windowsLogPath(source);
24475
- return path21.join(
24476
- os7.homedir(),
25113
+ return path23.join(
25114
+ os8.homedir(),
24477
25115
  "Library",
24478
25116
  "Logs",
24479
25117
  `iteration-${source}-collector.error.log`
@@ -24507,7 +25145,7 @@ async function backgroundServiceDefinition(source) {
24507
25145
  requireCollectorPlatform();
24508
25146
  const file = process.platform === "darwin" ? plistPath(source) : process.platform === "linux" ? unitPath(source) : taskDefinitionPath(source);
24509
25147
  try {
24510
- return await fs17.readFile(
25148
+ return await fs19.readFile(
24511
25149
  file,
24512
25150
  process.platform === "win32" ? "utf16le" : "utf8"
24513
25151
  );
@@ -24556,7 +25194,7 @@ function serviceDataDirectory(definition, platform) {
24556
25194
  (_, slashes) => slashes.slice(0, slashes.length / 2)
24557
25195
  );
24558
25196
  }
24559
- if (!directory || !(platform === "win32" ? path21.win32 : path21.posix).isAbsolute(directory))
25197
+ if (!directory || !(platform === "win32" ? path23.win32 : path23.posix).isAbsolute(directory))
24560
25198
  throw new Error("Could not read the collector service data directory.");
24561
25199
  return directory;
24562
25200
  }
@@ -24579,15 +25217,15 @@ async function backgroundServiceUsesRuntime(directory, source) {
24579
25217
  }
24580
25218
 
24581
25219
  // src/config-paths.ts
24582
- import os8 from "node:os";
24583
- import path22 from "node:path";
25220
+ import os9 from "node:os";
25221
+ import path24 from "node:path";
24584
25222
  function defaultCollectorDirectory(source) {
24585
- return path22.join(os8.homedir(), ".config", "iteration", `${source}-collector`);
25223
+ return path24.join(os9.homedir(), ".config", "iteration", `${source}-collector`);
24586
25224
  }
24587
25225
 
24588
25226
  // src/cursor-hooks.ts
24589
- import { promises as fs18 } from "node:fs";
24590
- import path23 from "node:path";
25227
+ import { promises as fs20 } from "node:fs";
25228
+ import path25 from "node:path";
24591
25229
  function shellQuote(value) {
24592
25230
  if (process.platform === "win32") return `"${value}"`;
24593
25231
  return `'${value.replaceAll("'", `'\\''`)}'`;
@@ -24622,8 +25260,8 @@ async function readHooks(filename) {
24622
25260
  let contents;
24623
25261
  let mode = 384;
24624
25262
  try {
24625
- contents = await fs18.readFile(filename, "utf8");
24626
- mode = (await fs18.stat(filename)).mode & 511;
25263
+ contents = await fs20.readFile(filename, "utf8");
25264
+ mode = (await fs20.stat(filename)).mode & 511;
24627
25265
  } catch (error3) {
24628
25266
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
24629
25267
  return { file: {}, mode };
@@ -24645,14 +25283,14 @@ async function readHooks(filename) {
24645
25283
  return { file: checked.data, mode };
24646
25284
  }
24647
25285
  async function writeHooks(filename, file, mode) {
24648
- await fs18.mkdir(path23.dirname(filename), { recursive: true, mode: 448 });
24649
- const target = await fs18.realpath(filename).catch((error3) => {
25286
+ await fs20.mkdir(path25.dirname(filename), { recursive: true, mode: 448 });
25287
+ const target = await fs20.realpath(filename).catch((error3) => {
24650
25288
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
24651
25289
  return filename;
24652
25290
  throw error3;
24653
25291
  });
24654
25292
  const temporary = `${target}.iteration-${process.pid}.tmp`;
24655
- const handle = await fs18.open(temporary, "w", mode);
25293
+ const handle = await fs20.open(temporary, "w", mode);
24656
25294
  try {
24657
25295
  await handle.writeFile(`${JSON.stringify(file, null, 2)}
24658
25296
  `, "utf8");
@@ -24660,14 +25298,14 @@ async function writeHooks(filename, file, mode) {
24660
25298
  } finally {
24661
25299
  await handle.close();
24662
25300
  }
24663
- await fs18.rename(temporary, target);
25301
+ await fs20.rename(temporary, target);
24664
25302
  }
24665
25303
  function entries(value) {
24666
25304
  return value ?? [];
24667
25305
  }
24668
25306
  async function installCursorHooks(cursorHome, dataDirectory2) {
24669
25307
  await ensurePrivateDirectory(cursorHookDirectory(dataDirectory2));
24670
- const filename = path23.join(cursorHome, "hooks.json");
25308
+ const filename = path25.join(cursorHome, "hooks.json");
24671
25309
  const { file, mode } = await readHooks(filename);
24672
25310
  const hooks = { ...file.hooks };
24673
25311
  const nodePath = await stableNodePath();
@@ -24681,7 +25319,7 @@ async function installCursorHooks(cursorHome, dataDirectory2) {
24681
25319
  await writeHooks(filename, { ...file, version: 1, hooks }, mode);
24682
25320
  }
24683
25321
  async function uninstallCursorHooks(cursorHome, dataDirectory2) {
24684
- const filename = path23.join(cursorHome, "hooks.json");
25322
+ const filename = path25.join(cursorHome, "hooks.json");
24685
25323
  const { file, mode } = await readHooks(filename);
24686
25324
  const hooks = { ...file.hooks };
24687
25325
  let changed = false;
@@ -24711,8 +25349,8 @@ function silenceCollectorLogs() {
24711
25349
  // src/turn-summary.ts
24712
25350
  import { execFile as execFileCallback7 } from "node:child_process";
24713
25351
  import { createHash as createHash6 } from "node:crypto";
24714
- import { promises as fs19 } from "node:fs";
24715
- import path24 from "node:path";
25352
+ import { promises as fs21 } from "node:fs";
25353
+ import path26 from "node:path";
24716
25354
  import { promisify as promisify7 } from "node:util";
24717
25355
  import { crc32 } from "node:zlib";
24718
25356
  var execFile7 = promisify7(execFileCallback7);
@@ -24723,13 +25361,13 @@ var REQUEST_TIMEOUT_MS2 = 1e4;
24723
25361
  var HOOK_TIMEOUT_SECONDS = 1;
24724
25362
  var REQUEST_KEY_PATTERN = /^\d{1,10}$/;
24725
25363
  function turnSummaryPaths(dataDirectory2) {
24726
- const directory = path24.join(dataDirectory2, "turn-summary");
25364
+ const directory = path26.join(dataDirectory2, "turn-summary");
24727
25365
  return {
24728
25366
  directory,
24729
- script: path24.join(directory, "stop-hook.sh"),
24730
- requests: path24.join(directory, "requests"),
24731
- lines: path24.join(directory, "lines"),
24732
- shown: path24.join(directory, "shown")
25367
+ script: path26.join(directory, "stop-hook.sh"),
25368
+ requests: path26.join(directory, "requests"),
25369
+ lines: path26.join(directory, "lines"),
25370
+ shown: path26.join(directory, "shown")
24733
25371
  };
24734
25372
  }
24735
25373
  function shellQuote2(value) {
@@ -24785,7 +25423,7 @@ async function sessionStartCommand(config3) {
24785
25423
  function agentHookFile(config3) {
24786
25424
  if ("cursorHome" in config3)
24787
25425
  throw new Error("Cursor uses its own trace hooks.");
24788
- return "claudeHome" in config3 ? path24.join(config3.claudeHome, "settings.json") : path24.join(config3.codexHome, "hooks.json");
25426
+ return "claudeHome" in config3 ? path26.join(config3.claudeHome, "settings.json") : path26.join(config3.codexHome, "hooks.json");
24789
25427
  }
24790
25428
  var hookEntrySchema2 = object3({
24791
25429
  command: string5().optional(),
@@ -24802,8 +25440,8 @@ async function readHookFile(filename) {
24802
25440
  let contents;
24803
25441
  let mode = 384;
24804
25442
  try {
24805
- contents = await fs19.readFile(filename, "utf8");
24806
- mode = (await fs19.stat(filename)).mode & 511;
25443
+ contents = await fs21.readFile(filename, "utf8");
25444
+ mode = (await fs21.stat(filename)).mode & 511;
24807
25445
  } catch (error3) {
24808
25446
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT") {
24809
25447
  return { file: {}, mode };
@@ -24829,19 +25467,19 @@ async function readHookFile(filename) {
24829
25467
  return { file: parsed.data, mode };
24830
25468
  }
24831
25469
  async function writeHookFile(filename, file, mode) {
24832
- await fs19.mkdir(path24.dirname(filename), { recursive: true });
24833
- const target = await fs19.realpath(filename).catch((error3) => {
25470
+ await fs21.mkdir(path26.dirname(filename), { recursive: true });
25471
+ const target = await fs21.realpath(filename).catch((error3) => {
24834
25472
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
24835
25473
  return filename;
24836
25474
  throw error3;
24837
25475
  });
24838
25476
  const temporary = `${target}.iteration-${process.pid}.tmp`;
24839
- await fs19.writeFile(temporary, `${JSON.stringify(file, null, 2)}
25477
+ await fs21.writeFile(temporary, `${JSON.stringify(file, null, 2)}
24840
25478
  `, {
24841
25479
  encoding: "utf8",
24842
25480
  mode
24843
25481
  });
24844
- await fs19.rename(temporary, target);
25482
+ await fs21.rename(temporary, target);
24845
25483
  }
24846
25484
  function withoutCommand(groups, command) {
24847
25485
  return groups.flatMap((group) => {
@@ -24866,24 +25504,24 @@ async function installTurnSummary(config3) {
24866
25504
  [paths.requests, paths.lines, paths.shown].map(ensurePrivateDirectory)
24867
25505
  );
24868
25506
  if (process.platform !== "win32") {
24869
- await fs19.writeFile(paths.script, hookScript(paths), {
25507
+ await fs21.writeFile(paths.script, hookScript(paths), {
24870
25508
  encoding: "utf8",
24871
25509
  mode: 448
24872
25510
  });
24873
- await fs19.chmod(paths.script, 448);
25511
+ await fs21.chmod(paths.script, 448);
24874
25512
  }
24875
25513
  if ("claudeHome" in config3) {
24876
25514
  const gitPaths = claudeGitHookPaths(config3.dataDirectory);
24877
25515
  await ensurePrivateDirectory(gitPaths.queue);
24878
25516
  if (process.platform !== "win32") {
24879
- await fs19.writeFile(
25517
+ await fs21.writeFile(
24880
25518
  gitPaths.script,
24881
25519
  `#!/bin/sh
24882
25520
  exec ${shellQuote2(await stableNodePath())} ${shellQuote2(runtimePaths().launcher)} claude-git-hook ${shellQuote2(config3.dataDirectory)}
24883
25521
  `,
24884
25522
  { encoding: "utf8", mode: 448 }
24885
25523
  );
24886
- await fs19.chmod(gitPaths.script, 448);
25524
+ await fs21.chmod(gitPaths.script, 448);
24887
25525
  }
24888
25526
  }
24889
25527
  const filename = agentHookFile(config3);
@@ -24973,19 +25611,19 @@ async function captureTurnSummary(dataDirectory2) {
24973
25611
  windowsHide: true
24974
25612
  }
24975
25613
  ));
24976
- directory = path24.normalize(directory.trim());
25614
+ directory = path26.normalize(directory.trim());
24977
25615
  } catch {
24978
25616
  quiet();
24979
25617
  return;
24980
25618
  }
24981
- if (!path24.isAbsolute(directory)) {
25619
+ if (!path26.isAbsolute(directory)) {
24982
25620
  quiet();
24983
25621
  return;
24984
25622
  }
24985
25623
  const key = String(crc32(directory));
24986
25624
  const paths = turnSummaryPaths(dataDirectory2);
24987
25625
  try {
24988
- await fs19.writeFile(path24.join(paths.requests, key), `${directory}
25626
+ await fs21.writeFile(path26.join(paths.requests, key), `${directory}
24989
25627
  `, {
24990
25628
  encoding: "utf8",
24991
25629
  mode: 384
@@ -24994,7 +25632,7 @@ async function captureTurnSummary(dataDirectory2) {
24994
25632
  }
24995
25633
  let lines;
24996
25634
  try {
24997
- lines = await fs19.readFile(path24.join(paths.lines, key), "utf8");
25635
+ lines = await fs21.readFile(path26.join(paths.lines, key), "utf8");
24998
25636
  } catch {
24999
25637
  quiet();
25000
25638
  return;
@@ -25006,7 +25644,7 @@ async function captureTurnSummary(dataDirectory2) {
25006
25644
  }
25007
25645
  let last;
25008
25646
  try {
25009
- last = (await fs19.readFile(path24.join(paths.shown, key), "utf8")).split("\n")[0] ?? null;
25647
+ last = (await fs21.readFile(path26.join(paths.shown, key), "utf8")).split("\n")[0] ?? null;
25010
25648
  } catch {
25011
25649
  last = null;
25012
25650
  }
@@ -25015,7 +25653,7 @@ async function captureTurnSummary(dataDirectory2) {
25015
25653
  return;
25016
25654
  }
25017
25655
  try {
25018
- await fs19.writeFile(path24.join(paths.shown, key), `${message}
25656
+ await fs21.writeFile(path26.join(paths.shown, key), `${message}
25019
25657
  `, {
25020
25658
  encoding: "utf8",
25021
25659
  mode: 384
@@ -25075,7 +25713,7 @@ async function fetchSummary(config3, directory, fetcher, signal) {
25075
25713
  }
25076
25714
  async function readRequest(filename) {
25077
25715
  try {
25078
- const [directory] = (await fs19.readFile(filename, "utf8")).split("\n");
25716
+ const [directory] = (await fs21.readFile(filename, "utf8")).split("\n");
25079
25717
  return directory ?? null;
25080
25718
  } catch (error3) {
25081
25719
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
@@ -25084,33 +25722,40 @@ async function readRequest(filename) {
25084
25722
  }
25085
25723
  }
25086
25724
  async function writeLine(paths, key, directory, message) {
25087
- const filename = path24.join(paths.lines, key);
25725
+ const filename = path26.join(paths.lines, key);
25088
25726
  if (message === null) {
25089
- await fs19.rm(filename, { force: true });
25727
+ await fs21.rm(filename, { force: true });
25090
25728
  return;
25091
25729
  }
25092
25730
  const temporary = `${filename}.${process.pid}.tmp`;
25093
- await fs19.writeFile(
25731
+ await fs21.writeFile(
25094
25732
  temporary,
25095
25733
  `${directory}
25096
25734
  ${JSON.stringify({ systemMessage: message })}
25097
25735
  `,
25098
25736
  { encoding: "utf8", mode: 384 }
25099
25737
  );
25100
- await fs19.rename(temporary, filename);
25738
+ await fs21.rename(temporary, filename);
25101
25739
  }
25102
25740
  async function forget(paths, key) {
25103
25741
  await Promise.all(
25104
25742
  [paths.requests, paths.lines, paths.shown].map(
25105
- (directory) => fs19.rm(path24.join(directory, key), { force: true })
25743
+ (directory) => fs21.rm(path26.join(directory, key), { force: true })
25106
25744
  )
25107
25745
  );
25108
25746
  }
25747
+ async function clearTurnSummaries(config3) {
25748
+ const paths = turnSummaryPaths(config3.dataDirectory);
25749
+ for (const directory of [paths.lines, paths.shown]) {
25750
+ await fs21.rm(directory, { recursive: true, force: true });
25751
+ await ensurePrivateDirectory(directory);
25752
+ }
25753
+ }
25109
25754
  async function refreshTurnSummaries(config3, refreshedAt, nowMs, fetcher = collectorFetch, signal) {
25110
25755
  const paths = turnSummaryPaths(config3.dataDirectory);
25111
25756
  let keys;
25112
25757
  try {
25113
- keys = await fs19.readdir(paths.requests);
25758
+ keys = await fs21.readdir(paths.requests);
25114
25759
  } catch (error3) {
25115
25760
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25116
25761
  return 0;
@@ -25120,8 +25765,8 @@ async function refreshTurnSummaries(config3, refreshedAt, nowMs, fetcher = colle
25120
25765
  for (const key of keys.sort()) {
25121
25766
  signal?.throwIfAborted();
25122
25767
  if (!REQUEST_KEY_PATTERN.test(key)) continue;
25123
- const filename = path24.join(paths.requests, key);
25124
- const stats = await fs19.stat(filename).catch((error3) => {
25768
+ const filename = path26.join(paths.requests, key);
25769
+ const stats = await fs21.stat(filename).catch((error3) => {
25125
25770
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25126
25771
  return null;
25127
25772
  throw error3;
@@ -25188,8 +25833,8 @@ async function codexTurnSummaryApproval(config3) {
25188
25833
  if (handlerIndex < 0) continue;
25189
25834
  let configToml;
25190
25835
  try {
25191
- configToml = await fs19.readFile(
25192
- path24.join(config3.codexHome, "config.toml"),
25836
+ configToml = await fs21.readFile(
25837
+ path26.join(config3.codexHome, "config.toml"),
25193
25838
  "utf8"
25194
25839
  );
25195
25840
  } catch (error3) {
@@ -25216,25 +25861,25 @@ var consentSchema = object3({
25216
25861
  sourceHomes: object3({
25217
25862
  codex: string5(),
25218
25863
  claude: string5(),
25219
- cursor: string5().default(path25.join(os9.homedir(), ".cursor"))
25864
+ cursor: string5().default(path27.join(os10.homedir(), ".cursor"))
25220
25865
  }).strict()
25221
25866
  }).strict();
25222
25867
  function consentPath() {
25223
- return path25.join(os9.homedir(), ".config", "iteration", "auto-connect.json");
25868
+ return path27.join(os10.homedir(), ".config", "iteration", "auto-connect.json");
25224
25869
  }
25225
25870
  async function removeMachineConsent() {
25226
- await fs20.rm(consentPath(), { force: true });
25871
+ await fs22.rm(consentPath(), { force: true });
25227
25872
  }
25228
25873
  async function detectedSources(sourceHomes) {
25229
25874
  const detected = [];
25230
25875
  for (const source of COLLECTOR_SOURCES2) {
25231
- const directory = path25.join(
25876
+ const directory = path27.join(
25232
25877
  sourceHomes[source],
25233
25878
  source === "codex" ? "sessions" : "projects"
25234
25879
  );
25235
25880
  let stats;
25236
25881
  try {
25237
- stats = await fs20.stat(directory);
25882
+ stats = await fs22.stat(directory);
25238
25883
  } catch (error3) {
25239
25884
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25240
25885
  continue;
@@ -25254,7 +25899,7 @@ async function detectedSources(sourceHomes) {
25254
25899
  async function loadMachineConsent() {
25255
25900
  let text;
25256
25901
  try {
25257
- text = await fs20.readFile(consentPath(), "utf8");
25902
+ text = await fs22.readFile(consentPath(), "utf8");
25258
25903
  } catch (error3) {
25259
25904
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25260
25905
  return null;
@@ -25276,43 +25921,6 @@ async function loadMachineConsent() {
25276
25921
  validateCollectorEndpoint(parsed.data.endpoint);
25277
25922
  return parsed.data;
25278
25923
  }
25279
- async function saveMachineConsent(consent) {
25280
- const parsed = consentSchema.parse(consent);
25281
- validateCollectorEndpoint(parsed.endpoint);
25282
- const filename = consentPath();
25283
- await ensurePrivateDirectory(path25.dirname(filename));
25284
- const temporary = `${filename}.${process.pid}.tmp`;
25285
- const handle = await fs20.open(temporary, "w", 384);
25286
- try {
25287
- await handle.writeFile(`${JSON.stringify(parsed)}
25288
- `, "utf8");
25289
- await handle.sync();
25290
- } finally {
25291
- await handle.close();
25292
- }
25293
- await fs20.rename(temporary, filename);
25294
- }
25295
- async function readCollectorScope(anchor3) {
25296
- const endpoint = validateCollectorEndpoint(anchor3.endpoint);
25297
- const response = await collectorFetch(
25298
- new URL("./collector-auth/expand", endpoint),
25299
- {
25300
- headers: { Authorization: `Bearer ${anchor3.token}` },
25301
- signal: AbortSignal.timeout(1e4)
25302
- }
25303
- );
25304
- const payload = await response.json();
25305
- if (!response.ok) {
25306
- const error3 = object3({ error: string5() }).safeParse(payload);
25307
- throw new Error(
25308
- `Could not identify the collector workspace: ${error3.success ? error3.data.error : `HTTP ${response.status}`}`
25309
- );
25310
- }
25311
- const parsed = object3({ organizationId: uuid5(), organizationName: string5().min(1) }).strict().safeParse(payload);
25312
- if (!parsed.success)
25313
- throw new Error("Iteration returned an invalid collector workspace.");
25314
- return parsed.data;
25315
- }
25316
25924
  async function expandSource(anchor3, source, organizationId, request2 = collectorFetch) {
25317
25925
  const endpoint = validateCollectorEndpoint(anchor3.endpoint);
25318
25926
  const token = createHmac("sha256", anchor3.token).update(`iteration-source:${source}`, "utf8").digest("base64url");
@@ -25386,7 +25994,7 @@ async function connectNewDetectedSources(anchor3, consent) {
25386
25994
  if (source === consent.anchorSource) continue;
25387
25995
  const paths = collectorPaths(defaultCollectorDirectory(source));
25388
25996
  try {
25389
- await fs20.access(paths.config);
25997
+ await fs22.access(paths.config);
25390
25998
  continue;
25391
25999
  } catch (error3) {
25392
26000
  if (!(error3 instanceof Error && "code" in error3 && error3.code === "ENOENT"))
@@ -25424,18 +26032,28 @@ async function connectNewDetectedSources(anchor3, consent) {
25424
26032
  }
25425
26033
  return connected;
25426
26034
  }
25427
- async function connectedConfig(source) {
25428
- const paths = collectorPaths(defaultCollectorDirectory(source));
26035
+ async function connectedConfig(source, directory = defaultCollectorDirectory(source)) {
26036
+ const paths = collectorPaths(directory);
25429
26037
  try {
25430
- await fs20.access(paths.config);
26038
+ await fs22.access(paths.config);
25431
26039
  } catch (error3) {
25432
26040
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25433
26041
  return null;
25434
26042
  throw error3;
25435
26043
  }
25436
- return loadConfig(paths);
26044
+ const config3 = await loadConfig(paths);
26045
+ if (collectorSource(config3) !== source)
26046
+ throw new Error(
26047
+ `The collector directory belongs to a different coding agent: ${directory}.`
26048
+ );
26049
+ return config3;
25437
26050
  }
25438
26051
 
26052
+ // src/sharing-connect.ts
26053
+ import os12 from "node:os";
26054
+ import path30 from "node:path";
26055
+ import { setTimeout as delay6 } from "node:timers/promises";
26056
+
25439
26057
  // src/clerk-device-auth.ts
25440
26058
  import { execFile as execFile8 } from "node:child_process";
25441
26059
  import { promisify as promisify8 } from "node:util";
@@ -25810,8 +26428,8 @@ async function clerkDeviceSignIn(configuration) {
25810
26428
  stopSpinner();
25811
26429
  }
25812
26430
  }
25813
- async function enrollDevice(endpoint, accessToken, deviceName, organizationId, source) {
25814
- const body = { deviceName };
26431
+ async function enrollDevice(endpoint, accessToken, deviceName, organizationId, source, computerId) {
26432
+ const body = { deviceName, ...computerId === void 0 ? {} : { computerId } };
25815
26433
  if (source !== "codex") body.source = source;
25816
26434
  if (organizationId !== null) body.organizationId = organizationId;
25817
26435
  const response = await collectorFetch(
@@ -25882,7 +26500,7 @@ async function chooseOrganization(organizations, hasMore) {
25882
26500
  }
25883
26501
  return organization.id;
25884
26502
  }
25885
- async function authenticateAndEnrollCollector(endpoint, deviceNameValue, organizationId, source = "codex") {
26503
+ async function authenticateAndEnrollCollector(endpoint, deviceNameValue, organizationId, source = "codex", computerId) {
25886
26504
  const deviceName = string5().trim().min(1).max(160).safeParse(deviceNameValue);
25887
26505
  if (!deviceName.success) {
25888
26506
  throw new Error("--device-name must contain 1 to 160 characters.");
@@ -25894,34 +26512,617 @@ async function authenticateAndEnrollCollector(endpoint, deviceNameValue, organiz
25894
26512
  clerkAccessToken,
25895
26513
  deviceName.data,
25896
26514
  organizationId,
25897
- source
26515
+ source,
26516
+ computerId
26517
+ );
26518
+ if (result.status === "choose_organization") {
26519
+ const selectedOrganizationId = await chooseOrganization(
26520
+ result.organizations,
26521
+ result.hasMore
26522
+ );
26523
+ result = await enrollDevice(
26524
+ endpoint,
26525
+ clerkAccessToken,
26526
+ deviceName.data,
26527
+ selectedOrganizationId,
26528
+ source,
26529
+ computerId
26530
+ );
26531
+ }
26532
+ if (result.status !== "enrolled") {
26533
+ throw new Error("The collector could not be assigned to that workspace.");
26534
+ }
26535
+ return {
26536
+ token: result.token,
26537
+ deviceId: result.deviceId,
26538
+ organizationId: result.organizationId,
26539
+ organizationName: result.organizationName
26540
+ };
26541
+ }
26542
+
26543
+ // src/sharing-inventory.ts
26544
+ import { promises as fs23 } from "node:fs";
26545
+ import path28 from "node:path";
26546
+ var MAX_FILES = 1e4;
26547
+ var MAX_HEADER_BYTES = 256 * 1024;
26548
+ async function projectInventory(computerId, homes) {
26549
+ const projects = /* @__PURE__ */ new Map();
26550
+ let inventoryBytes = 256;
26551
+ let truncated = false;
26552
+ for (const source of ["codex", "claude", "cursor"]) {
26553
+ const home = homes[source];
26554
+ if (home === void 0) continue;
26555
+ const files = await sourceFilesNewestFirst(source, home);
26556
+ truncated ||= files.length > MAX_FILES;
26557
+ for (const file of files.slice(0, MAX_FILES)) {
26558
+ const handle = await fs23.open(file.filename, "r");
26559
+ let contents;
26560
+ try {
26561
+ const buffer = Buffer.alloc(Math.min(file.size, MAX_HEADER_BYTES));
26562
+ const result = await handle.read(buffer, 0, buffer.length, 0);
26563
+ contents = buffer.subarray(0, result.bytesRead).toString("utf8");
26564
+ } finally {
26565
+ await handle.close();
26566
+ }
26567
+ const cwds = {};
26568
+ const locations = /* @__PURE__ */ new Set();
26569
+ for (const line of contents.split("\n")) {
26570
+ const location = captureRecordLocation(
26571
+ source,
26572
+ line,
26573
+ file.filename,
26574
+ cwds
26575
+ );
26576
+ if (location !== null) locations.add(location);
26577
+ }
26578
+ for (const location of locations) {
26579
+ const bytes = Buffer.byteLength(JSON.stringify(location), "utf8") + 1200;
26580
+ if (!projects.has(location) && (projects.size >= 1e3 || inventoryBytes + bytes > 480 * 1024)) {
26581
+ truncated = true;
26582
+ continue;
26583
+ }
26584
+ if (!projects.has(location)) inventoryBytes += bytes;
26585
+ const project = projects.get(location) ?? {
26586
+ name: (path28.basename(location) || location).slice(0, 160),
26587
+ sessions: 0,
26588
+ sources: /* @__PURE__ */ new Set()
26589
+ };
26590
+ project.sessions += file.session ? 1 : 0;
26591
+ project.sources.add(source);
26592
+ projects.set(location, project);
26593
+ }
26594
+ }
26595
+ }
26596
+ return {
26597
+ computerId,
26598
+ truncated,
26599
+ projects: [...projects].map(([folder, project]) => ({
26600
+ path: folder,
26601
+ name: project.name,
26602
+ sessions: project.sessions,
26603
+ sources: [...project.sources]
26604
+ })).sort(
26605
+ (left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path)
26606
+ )
26607
+ };
26608
+ }
26609
+
26610
+ // src/folder-browser.ts
26611
+ import { promises as fs24 } from "node:fs";
26612
+ import os11 from "node:os";
26613
+ import path29 from "node:path";
26614
+ import { setTimeout as delay5 } from "node:timers/promises";
26615
+ function breadcrumbs(directory, home) {
26616
+ const entries2 = [];
26617
+ let current = directory;
26618
+ for (let depth = 0; depth < 8; depth += 1) {
26619
+ entries2.unshift({
26620
+ path: current,
26621
+ name: current === home ? "Home" : path29.basename(current) || current
26622
+ });
26623
+ const parent = path29.dirname(current);
26624
+ if (parent === current) break;
26625
+ current = parent;
26626
+ }
26627
+ return entries2;
26628
+ }
26629
+ async function folderEntryIsDirectory(entry, filename) {
26630
+ if (!entry.isSymbolicLink()) return entry.isDirectory();
26631
+ try {
26632
+ return (await fs24.stat(filename)).isDirectory();
26633
+ } catch (error3) {
26634
+ if (error3 instanceof Error && "code" in error3 && ["ENOENT", "EACCES", "EPERM", "ELOOP"].includes(String(error3.code)))
26635
+ return false;
26636
+ throw error3;
26637
+ }
26638
+ }
26639
+ async function readFolderListing(request2, home = os11.homedir(), signal) {
26640
+ signal?.throwIfAborted();
26641
+ const directory = await fs24.realpath(request2.directory ?? home);
26642
+ const parent = path29.dirname(directory);
26643
+ const entries2 = [];
26644
+ let scanned = 0, bytes = 0, truncated = false;
26645
+ const deadline = Date.now() + 3e3;
26646
+ const handle = await fs24.opendir(directory);
26647
+ for await (const entry of handle) {
26648
+ signal?.throwIfAborted();
26649
+ if (++scanned > 1e4 || Date.now() > deadline) {
26650
+ truncated = true;
26651
+ break;
26652
+ }
26653
+ if (!request2.showHidden && entry.name.startsWith(".") || !entry.name.toLowerCase().includes(request2.query.toLowerCase()))
26654
+ continue;
26655
+ const filename = path29.join(directory, entry.name);
26656
+ if (!captureLocationSchema.safeParse(filename).success) continue;
26657
+ if (!await folderEntryIsDirectory(entry, filename)) continue;
26658
+ const folder = { name: entry.name, path: filename };
26659
+ bytes += Buffer.byteLength(JSON.stringify(folder), "utf8");
26660
+ if (entries2.length === 200 || bytes > 384 * 1024) {
26661
+ truncated = true;
26662
+ break;
26663
+ }
26664
+ entries2.push(folder);
26665
+ }
26666
+ entries2.sort((left, right) => left.name.localeCompare(right.name));
26667
+ return {
26668
+ directory,
26669
+ name: path29.basename(directory) || directory,
26670
+ parent: parent === directory ? null : parent,
26671
+ breadcrumbs: breadcrumbs(directory, home),
26672
+ entries: entries2,
26673
+ truncated
26674
+ };
26675
+ }
26676
+ async function listingState(request2, signal) {
26677
+ try {
26678
+ return {
26679
+ status: "ready",
26680
+ request: request2,
26681
+ listing: await readFolderListing(request2, void 0, signal)
26682
+ };
26683
+ } catch (error3) {
26684
+ if (signal.aborted) throw error3;
26685
+ const code = error3 instanceof Error && "code" in error3 ? String(error3.code) : "";
26686
+ return {
26687
+ status: "error",
26688
+ request: request2,
26689
+ reason: ["EACCES", "EPERM"].includes(code) ? "permission" : ["ENOENT", "ENOTDIR", "ELOOP"].includes(code) ? "missing" : "unavailable"
26690
+ };
26691
+ }
26692
+ }
26693
+ async function folderBrowserCycle(config3, signal, fetcher) {
26694
+ const registry3 = await readSharingRegistry();
26695
+ if (!registry3 || registry3.ownerId === null)
26696
+ throw new Error(
26697
+ "The computer connection is unavailable for folder selection."
26698
+ );
26699
+ const deadline = AbortSignal.any([signal, AbortSignal.timeout(1e4)]);
26700
+ for (const connection of registry3.connections.filter(
26701
+ (connection2) => connection2.endpoint === config3.endpoint
26702
+ )) {
26703
+ const url2 = new URL("./collector-folders", connection.endpoint);
26704
+ const headers = { authorization: `Bearer ${connection.token}` };
26705
+ const response = await fetcher(url2, { headers, signal: deadline });
26706
+ if (response.status === 401) {
26707
+ await response.body?.cancel();
26708
+ continue;
26709
+ }
26710
+ if (!response.ok)
26711
+ throw new Error(`Folder selection is waiting (HTTP ${response.status}).`);
26712
+ const command = collectorFolderCommandSchema.nullable().parse(await response.json());
26713
+ if (command === null) return false;
26714
+ if (command.request.expiresAtMs <= Date.now()) return true;
26715
+ const state = await listingState(command.request, deadline);
26716
+ const reply = await fetcher(url2, {
26717
+ method: "POST",
26718
+ headers: { ...headers, "content-type": "application/json" },
26719
+ body: JSON.stringify(
26720
+ collectorFolderReplySchema.parse({
26721
+ organizationId: command.organizationId,
26722
+ state
26723
+ })
26724
+ ),
26725
+ signal: deadline
26726
+ });
26727
+ if (!reply.ok)
26728
+ throw new Error(
26729
+ `Folder selection could not finish (HTTP ${reply.status}).`
26730
+ );
26731
+ object3({ accepted: boolean4() }).strict().parse(await reply.json());
26732
+ return true;
26733
+ }
26734
+ throw new Error("Reconnect this computer to use folder selection.");
26735
+ }
26736
+ async function runFolderBrowser(config3, signal, fetcher = collectorFetch) {
26737
+ const paths = collectorPaths(path29.join(sharingDirectory(), "folder-browser"));
26738
+ try {
26739
+ while (!signal.aborted) {
26740
+ const release = await tryAcquireCollectorLock(paths);
26741
+ if (release === null) {
26742
+ await delay5(1500, void 0, { signal });
26743
+ continue;
26744
+ }
26745
+ try {
26746
+ while (!signal.aborted) {
26747
+ let pause = 1500;
26748
+ try {
26749
+ if (await folderBrowserCycle(config3, signal, fetcher)) pause = 250;
26750
+ } catch (error3) {
26751
+ if (signal.aborted) throw error3;
26752
+ collectorLogger({
26753
+ level: "warning",
26754
+ operation: "folder_browser",
26755
+ message: "Folder selection is waiting for the computer connection. Retrying.",
26756
+ errorName: error3 instanceof Error ? error3.name : "UnknownError"
26757
+ });
26758
+ pause = 5e3;
26759
+ }
26760
+ await delay5(pause, void 0, { signal });
26761
+ }
26762
+ } finally {
26763
+ await release();
26764
+ }
26765
+ }
26766
+ } catch (error3) {
26767
+ if (!signal.aborted) throw error3;
26768
+ }
26769
+ }
26770
+
26771
+ // src/sharing-connect.ts
26772
+ function textOption(options, key) {
26773
+ const value = options[key];
26774
+ if (value === void 0) return null;
26775
+ const parsed = string5().trim().min(1).safeParse(value);
26776
+ if (!parsed.success) throw new Error(`--${key} requires a value.`);
26777
+ return parsed.data;
26778
+ }
26779
+ function sourceConfig(source, home, directory, endpoint, enrollment) {
26780
+ const common = {
26781
+ destination: "host",
26782
+ endpoint,
26783
+ dataDirectory: directory,
26784
+ token: enrollment.token,
26785
+ organizationId: enrollment.organizationId,
26786
+ organizationName: enrollment.organizationName
26787
+ };
26788
+ return source === "codex" ? { ...common, codexHome: home } : source === "claude" ? { ...common, source, claudeHome: home } : { ...common, source, cursorHome: home };
26789
+ }
26790
+ async function prepareSource(config3, previous) {
26791
+ const source = collectorSource(config3);
26792
+ const paths = collectorPaths(config3.dataDirectory);
26793
+ if (previous !== null) await uninstallBackgroundService(source);
26794
+ const release = await waitForCollectorLock(paths);
26795
+ try {
26796
+ if (previous === null) {
26797
+ const home = "codexHome" in config3 ? config3.codexHome : "claudeHome" in config3 ? config3.claudeHome : config3.cursorHome;
26798
+ await saveState(
26799
+ await prepareSourceInitialState(source, home, true),
26800
+ paths
26801
+ );
26802
+ } else await ensureStartableState(previous, paths);
26803
+ await saveConfig(config3, paths);
26804
+ } finally {
26805
+ await release();
26806
+ }
26807
+ }
26808
+ async function startSource(config3) {
26809
+ const source = collectorSource(config3);
26810
+ if ("cursorHome" in config3)
26811
+ await installCursorHooks(config3.cursorHome, config3.dataDirectory);
26812
+ await installBackgroundService(config3.dataDirectory, source);
26813
+ try {
26814
+ if (source !== "cursor") await installTurnSummary(config3);
26815
+ await installAgentTools(agentHomes(config3));
26816
+ } catch (error3) {
26817
+ process.stdout.write(
26818
+ `Connected, but the agent integration needs attention: ${error3 instanceof Error ? error3.message : "setup failed"}
26819
+ `
26820
+ );
26821
+ }
26822
+ }
26823
+ async function localConnectionOptions(options, selectedSource) {
26824
+ const existing = await Promise.all(
26825
+ COLLECTOR_SOURCES2.map(
26826
+ (source) => connectedConfig(
26827
+ source,
26828
+ source === selectedSource ? textOption(options, "data-directory") ?? void 0 : void 0
26829
+ )
26830
+ )
26831
+ );
26832
+ const homes = Object.fromEntries(
26833
+ COLLECTOR_SOURCES2.map((source, index) => {
26834
+ const previous = existing[index];
26835
+ const home = previous ? "codexHome" in previous ? previous.codexHome : "claudeHome" in previous ? previous.claudeHome : previous.cursorHome : sourceHome(source);
26836
+ return [
26837
+ source,
26838
+ path30.resolve(textOption(options, `${source}-home`) ?? home)
26839
+ ];
26840
+ })
26841
+ );
26842
+ const detected = await detectedSources(homes);
26843
+ const sources = selectedSource ? [selectedSource] : [
26844
+ .../* @__PURE__ */ new Set([
26845
+ ...detected,
26846
+ ...COLLECTOR_SOURCES2.filter((_, index) => existing[index] !== null)
26847
+ ])
26848
+ ];
26849
+ const endpoint = validateCollectorEndpoint(
26850
+ textOption(options, "endpoint") ?? existing.find((config3) => config3 !== null)?.endpoint ?? "https://useiteration.com/api/traces/ingest"
26851
+ ).toString();
26852
+ if (existing.some((config3) => config3 !== null && config3.endpoint !== endpoint))
26853
+ throw new Error(
26854
+ "This computer is connected to another Iteration server. Sign out before changing servers."
26855
+ );
26856
+ return { existing, homes, sources, endpoint };
26857
+ }
26858
+ function assertSharingOwner(ownerId, nextOwnerId) {
26859
+ if (ownerId !== null && ownerId !== nextOwnerId)
26860
+ throw new Error(
26861
+ "This computer is connected to another Iteration account. Run iteration logout before switching accounts."
26862
+ );
26863
+ }
26864
+ async function waitForSharing(anchor3, alreadyApproved, signal) {
26865
+ const deadline = Date.now() + 15 * 60 * 1e3;
26866
+ let approved = alreadyApproved;
26867
+ while (!approved && Date.now() < deadline) {
26868
+ await delay6(2e3, void 0, { signal });
26869
+ approved = (await fetchSharingSnapshot(anchor3, void 0, signal)).approved;
26870
+ }
26871
+ if (!approved)
26872
+ throw new Error(
26873
+ "Sharing is still waiting for your project choice. Finish it in Iteration, then run iteration connect again."
26874
+ );
26875
+ }
26876
+ async function connectWorkspace(options, selectedSource) {
26877
+ requireCollectorPlatform();
26878
+ if (selectedSource === void 0 && options["data-directory"] !== void 0)
26879
+ throw new Error(
26880
+ "--data-directory requires a source-specific connect command."
26881
+ );
26882
+ const local = await localConnectionOptions(options, selectedSource);
26883
+ if (local.sources.length === 0) {
26884
+ process.stdout.write(
26885
+ "No supported local coding agents found yet. Run iteration connect after using Claude Code, Codex or Cursor.\n"
26886
+ );
26887
+ return;
26888
+ }
26889
+ if ((!process.stdin.isTTY || !process.stdout.isTTY) && options.yes !== true)
26890
+ throw new Error(
26891
+ "Run iteration connect interactively or pass --yes to open browser sign-in. Project sharing still requires approval in Iteration."
26892
+ );
26893
+ await prepareBackgroundPersistence();
26894
+ const prepared = await withSharingRegistry(async (initial, save) => {
26895
+ let registry3 = initial;
26896
+ const { existing, homes, sources, endpoint } = local;
26897
+ const first = sources[0];
26898
+ const requested = textOption(options, "workspace");
26899
+ let anchor3 = requested === null ? null : sharingConnection(registry3, requested, first);
26900
+ if (anchor3 !== null) {
26901
+ try {
26902
+ await fetchSharingSnapshot(anchor3);
26903
+ } catch {
26904
+ anchor3 = null;
26905
+ }
26906
+ }
26907
+ if (anchor3 === null) {
26908
+ const enrollment = await authenticateAndEnrollCollector(
26909
+ new URL(endpoint),
26910
+ textOption(options, "device-name") ?? os12.hostname(),
26911
+ requested,
26912
+ first,
26913
+ registry3.computerId
26914
+ );
26915
+ anchor3 = enrolledSharingConnection(enrollment, first, endpoint);
26916
+ }
26917
+ const anchorConfig = sourceConfig(
26918
+ first,
26919
+ homes[first],
26920
+ defaultCollectorDirectory(first),
26921
+ endpoint,
26922
+ anchor3
26923
+ );
26924
+ const inventory = collectorProjectInventorySchema.parse(
26925
+ await projectInventory(registry3.computerId, homes)
26926
+ );
26927
+ const response = await collectorFetch(
26928
+ new URL("./collector-sharing", endpoint),
26929
+ {
26930
+ method: "POST",
26931
+ headers: {
26932
+ authorization: `Bearer ${anchor3.token}`,
26933
+ "content-type": "application/json"
26934
+ },
26935
+ body: JSON.stringify(inventory),
26936
+ signal: AbortSignal.timeout(2e4)
26937
+ }
26938
+ );
26939
+ if (!response.ok)
26940
+ throw new Error(
26941
+ `Could not prepare project sharing (HTTP ${response.status}). No new sharing has started.`
26942
+ );
26943
+ const snapshot = collectorSharingSnapshotSchema.parse(
26944
+ await response.json()
26945
+ );
26946
+ assertSharingOwner(registry3.ownerId, snapshot.ownerId);
26947
+ registry3 = {
26948
+ ...registry3,
26949
+ ownerId: snapshot.ownerId,
26950
+ sourceHomes: homes,
26951
+ connections: [
26952
+ ...registry3.connections.filter(
26953
+ (connection) => connection.organizationId !== anchor3.organizationId || connection.source !== first
26954
+ ),
26955
+ anchor3
26956
+ ]
26957
+ };
26958
+ await save(registry3);
26959
+ await prepareBackgroundPersistence();
26960
+ for (const previous of existing) {
26961
+ if (previous !== null && previous.destination !== "host")
26962
+ await prepareSource({ ...previous, destination: "host" }, previous);
26963
+ }
26964
+ const configs = [];
26965
+ for (const source of sources) {
26966
+ const enrollment = source === first ? anchor3 : await expandSource(anchorConfig, source, anchor3.organizationId);
26967
+ const connection = enrolledSharingConnection(
26968
+ enrollment,
26969
+ source,
26970
+ endpoint
26971
+ );
26972
+ registry3 = {
26973
+ ...registry3,
26974
+ connections: [
26975
+ ...registry3.connections.filter(
26976
+ (saved) => saved.organizationId !== connection.organizationId || saved.source !== source
26977
+ ),
26978
+ connection
26979
+ ]
26980
+ };
26981
+ await save(registry3);
26982
+ const previous = existing[COLLECTOR_SOURCES2.indexOf(source)] ?? null;
26983
+ const directory = selectedSource ? path30.resolve(
26984
+ textOption(options, "data-directory") ?? defaultCollectorDirectory(source)
26985
+ ) : defaultCollectorDirectory(source);
26986
+ const config3 = previous === null ? sourceConfig(source, homes[source], directory, endpoint, enrollment) : { ...previous, destination: "host" };
26987
+ if (previous === null || previous.destination !== "host")
26988
+ await prepareSource(config3, previous);
26989
+ registry3 = {
26990
+ ...registry3,
26991
+ directories: {
26992
+ ...registry3.directories,
26993
+ [source]: config3.dataDirectory
26994
+ }
26995
+ };
26996
+ await save(registry3);
26997
+ configs.push(config3);
26998
+ }
26999
+ return {
27000
+ configs,
27001
+ anchor: anchor3,
27002
+ approved: snapshot.approved,
27003
+ computerId: registry3.computerId
27004
+ };
27005
+ });
27006
+ const url2 = new URL("/app/collectors/share", prepared.anchor.endpoint);
27007
+ url2.searchParams.set("workspace", prepared.anchor.organizationId);
27008
+ url2.searchParams.set("computer", prepared.computerId);
27009
+ process.stdout.write(
27010
+ `
27011
+ Choose which projects ${prepared.anchor.organizationName} can see.
27012
+ ${url2}
27013
+
27014
+ Nothing new uploads until you approve sharing in Iteration.
27015
+ `
27016
+ );
27017
+ const folderController = new AbortController();
27018
+ const failures = [];
27019
+ const folderTask = runFolderBrowser(
27020
+ prepared.anchor,
27021
+ folderController.signal
27022
+ ).then(
27023
+ () => null,
27024
+ (error3) => {
27025
+ folderController.abort();
27026
+ return { error: error3 };
27027
+ }
25898
27028
  );
25899
- if (result.status === "choose_organization") {
25900
- const selectedOrganizationId = await chooseOrganization(
25901
- result.organizations,
25902
- result.hasMore
27029
+ try {
27030
+ await openBrowser(url2.toString());
27031
+ await waitForSharing(
27032
+ prepared.anchor,
27033
+ prepared.approved,
27034
+ folderController.signal
25903
27035
  );
25904
- result = await enrollDevice(
25905
- endpoint,
25906
- clerkAccessToken,
25907
- deviceName.data,
25908
- selectedOrganizationId,
25909
- source
27036
+ for (const config3 of prepared.configs) {
27037
+ await startSource(config3);
27038
+ process.stdout.write(
27039
+ `${collectorSourceLabel(collectorSource(config3))} connected \xB7 sharing approved projects
27040
+ `
27041
+ );
27042
+ }
27043
+ } catch (error3) {
27044
+ failures.push(error3);
27045
+ } finally {
27046
+ folderController.abort();
27047
+ const outcome = await folderTask;
27048
+ if (outcome !== null) failures.push(outcome.error);
27049
+ }
27050
+ if (failures.length === 1) throw failures[0];
27051
+ if (failures.length > 1)
27052
+ throw new AggregateError(
27053
+ failures,
27054
+ "The computer connection could not finish cleanly."
25910
27055
  );
25911
- }
25912
- if (result.status !== "enrolled") {
25913
- throw new Error("The collector could not be assigned to that workspace.");
25914
- }
25915
- return {
25916
- token: result.token,
25917
- deviceId: result.deviceId,
25918
- organizationId: result.organizationId,
25919
- organizationName: result.organizationName
25920
- };
27056
+ process.stdout.write(
27057
+ "Saved sessions from those projects import first. New activity follows the same choice, whichever coding account you use.\n"
27058
+ );
27059
+ }
27060
+ async function connectSharedDetectedSources(config3) {
27061
+ await withSharingRegistry(async (registry3, save) => {
27062
+ if (registry3.connections.length === 0) return;
27063
+ const { snapshot } = await sharingTransport(
27064
+ config3,
27065
+ void 0,
27066
+ void 0,
27067
+ registry3
27068
+ );
27069
+ const homes = registry3.sourceHomes ?? {
27070
+ codex: sourceHome("codex"),
27071
+ claude: sourceHome("claude"),
27072
+ cursor: sourceHome("cursor")
27073
+ };
27074
+ let next = registry3;
27075
+ for (const source of await detectedSources(homes)) {
27076
+ const previous = await connectedConfig(
27077
+ source,
27078
+ registry3.directories?.[source]
27079
+ );
27080
+ for (const workspace of snapshot.workspaces.filter(
27081
+ (workspace2) => workspace2.active
27082
+ )) {
27083
+ if (sharingConnection(next, workspace.organizationId, source)) continue;
27084
+ const anchor3 = next.connections.find(
27085
+ (connection2) => connection2.organizationId === workspace.organizationId
27086
+ );
27087
+ if (!anchor3) continue;
27088
+ const enrollment = await expandSource(
27089
+ {
27090
+ ...config3,
27091
+ token: anchor3.token,
27092
+ organizationId: anchor3.organizationId,
27093
+ organizationName: anchor3.organizationName
27094
+ },
27095
+ source,
27096
+ workspace.organizationId
27097
+ );
27098
+ next = {
27099
+ ...next,
27100
+ connections: [
27101
+ ...next.connections,
27102
+ enrolledSharingConnection(enrollment, source, config3.endpoint)
27103
+ ]
27104
+ };
27105
+ await save(next);
27106
+ }
27107
+ const connection = next.connections.find(
27108
+ (item) => item.source === source
27109
+ );
27110
+ if (previous !== null || !connection) continue;
27111
+ const added = sourceConfig(
27112
+ source,
27113
+ homes[source],
27114
+ defaultCollectorDirectory(source),
27115
+ config3.endpoint,
27116
+ connection
27117
+ );
27118
+ await prepareSource(added, null);
27119
+ await startSource(added);
27120
+ }
27121
+ });
25921
27122
  }
25922
27123
 
25923
27124
  // src/history-import.ts
25924
- import path26 from "node:path";
27125
+ import path31 from "node:path";
25925
27126
  function backgroundControl(source) {
25926
27127
  return {
25927
27128
  isRunning: () => backgroundServiceStatus(source),
@@ -25934,7 +27135,7 @@ async function importSourceHistory(config3, paths, confirmed, control = backgrou
25934
27135
  const source = collectorSource(config3);
25935
27136
  const directory = collectorSourceDirectory(config3);
25936
27137
  const state = await loadState(paths);
25937
- if (!state.initialized || state.sourceDirectory !== path26.resolve(directory)) {
27138
+ if (!state.initialized || state.sourceDirectory !== path31.resolve(directory)) {
25938
27139
  throw new Error(
25939
27140
  `Connect the ${source} source before importing its history.`
25940
27141
  );
@@ -25982,37 +27183,37 @@ async function importSourceHistory(config3, paths, confirmed, control = backgrou
25982
27183
  }
25983
27184
 
25984
27185
  // src/logout.ts
25985
- import { randomUUID as randomUUID3 } from "node:crypto";
25986
- import { promises as fs21 } from "node:fs";
25987
- import os10 from "node:os";
25988
- import path27 from "node:path";
27186
+ import { randomUUID as randomUUID4 } from "node:crypto";
27187
+ import { promises as fs25 } from "node:fs";
27188
+ import os13 from "node:os";
27189
+ import path32 from "node:path";
25989
27190
  async function pendingDirectories(filename) {
25990
27191
  let contents;
25991
27192
  try {
25992
- contents = await fs21.readFile(filename, "utf8");
27193
+ contents = await fs25.readFile(filename, "utf8");
25993
27194
  } catch (error3) {
25994
27195
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25995
27196
  return [];
25996
27197
  throw error3;
25997
27198
  }
25998
- const parsed = array2(string5().refine(path27.isAbsolute)).max(64).safeParse(JSON.parse(contents));
27199
+ const parsed = array2(string5().refine(path32.isAbsolute)).max(64).safeParse(JSON.parse(contents));
25999
27200
  if (!parsed.success)
26000
27201
  throw new Error("Pending logout directories are invalid.");
26001
27202
  return parsed.data;
26002
27203
  }
26003
27204
  async function savePendingDirectories(filename, directories) {
26004
- await ensurePrivateDirectory(path27.dirname(filename));
27205
+ await ensurePrivateDirectory(path32.dirname(filename));
26005
27206
  const temporary = `${filename}.${process.pid}.tmp`;
26006
- await fs21.writeFile(temporary, JSON.stringify([...directories]), {
27207
+ await fs25.writeFile(temporary, JSON.stringify([...directories]), {
26007
27208
  mode: 384
26008
27209
  });
26009
- await fs21.rename(temporary, filename);
27210
+ await fs25.rename(temporary, filename);
26010
27211
  }
26011
27212
  async function clearConnection(directory) {
26012
27213
  const paths = collectorPaths(directory);
26013
27214
  let names;
26014
27215
  try {
26015
- names = await fs21.readdir(directory);
27216
+ names = await fs25.readdir(directory);
26016
27217
  } catch (error3) {
26017
27218
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
26018
27219
  return;
@@ -26022,8 +27223,8 @@ async function clearConnection(directory) {
26022
27223
  try {
26023
27224
  for (const name of names)
26024
27225
  if (/^config\.json\.\d+\.tmp$/.test(name))
26025
- await fs21.rm(path27.join(directory, name), { force: true });
26026
- if (!names.includes(path27.basename(paths.config))) return;
27226
+ await fs25.rm(path32.join(directory, name), { force: true });
27227
+ if (!names.includes(path32.basename(paths.config))) return;
26027
27228
  const config3 = await loadConfig(paths);
26028
27229
  if (config3.destination === "vault")
26029
27230
  throw new Error("This directory belongs to Trace Vault, not Iteration.");
@@ -26044,41 +27245,43 @@ async function clearConnection(directory) {
26044
27245
  "claude-git",
26045
27246
  "turn-summary",
26046
27247
  "codex-billing-sync.json",
26047
- "cursor-billing-sync.json"
26048
- ].map((name) => path27.join(directory, name))
27248
+ "cursor-billing-sync.json",
27249
+ "shared-sessions.json"
27250
+ ].map((name) => path32.join(directory, name))
26049
27251
  ];
26050
- const archive = path27.join(directory, "logged-out", randomUUID3());
27252
+ const archive = path32.join(directory, "logged-out", randomUUID4());
26051
27253
  await ensurePrivateDirectory(archive);
26052
27254
  for (const filename of retained) {
26053
27255
  try {
26054
- await fs21.rename(filename, path27.join(archive, path27.basename(filename)));
27256
+ await fs25.rename(filename, path32.join(archive, path32.basename(filename)));
26055
27257
  } catch (error3) {
26056
27258
  if (!(error3 instanceof Error && "code" in error3 && error3.code === "ENOENT"))
26057
27259
  throw error3;
26058
27260
  }
26059
27261
  }
26060
- await fs21.rm(paths.config);
27262
+ await fs25.rm(paths.config);
26061
27263
  } finally {
26062
27264
  await release();
26063
27265
  }
26064
27266
  }
26065
27267
  async function logout(extraDirectory) {
26066
27268
  requireCollectorPlatform();
26067
- const pending = path27.join(
26068
- os10.homedir(),
27269
+ const pending2 = path32.join(
27270
+ os13.homedir(),
26069
27271
  ".config",
26070
27272
  "iteration",
26071
27273
  "logout-pending.json"
26072
27274
  );
26073
27275
  const directories = /* @__PURE__ */ new Set([
26074
27276
  ...COLLECTOR_SOURCES2.map(defaultCollectorDirectory),
26075
- ...await pendingDirectories(pending)
27277
+ ...await pendingDirectories(pending2)
26076
27278
  ]);
26077
27279
  if (extraDirectory !== void 0)
26078
- directories.add(path27.resolve(extraDirectory));
27280
+ directories.add(path32.resolve(extraDirectory));
26079
27281
  const failures = [];
26080
27282
  try {
26081
27283
  await removeMachineConsent();
27284
+ await forgetSharingConnections();
26082
27285
  } catch (error3) {
26083
27286
  failures.push(
26084
27287
  `Automatic connection approval: ${error3 instanceof Error ? error3.message : "Could not remove approval."}`
@@ -26096,7 +27299,7 @@ async function logout(extraDirectory) {
26096
27299
  );
26097
27300
  }
26098
27301
  }
26099
- await savePendingDirectories(pending, directories);
27302
+ await savePendingDirectories(pending2, directories);
26100
27303
  const stopped = await Promise.all(
26101
27304
  COLLECTOR_SOURCES2.filter((source) => !unreadableServices.has(source)).map(
26102
27305
  (source) => uninstallBackgroundService(source).then(
@@ -26120,7 +27323,7 @@ async function logout(extraDirectory) {
26120
27323
  `Logout did not finish. Resolve these errors and run iteration logout again:
26121
27324
  ${failures.join("\n")}`
26122
27325
  );
26123
- await fs21.rm(pending);
27326
+ await fs25.rm(pending2);
26124
27327
  }
26125
27328
 
26126
27329
  // src/runtime-services.ts
@@ -26147,17 +27350,17 @@ async function adoptRuntime() {
26147
27350
  }
26148
27351
 
26149
27352
  // src/service.ts
26150
- import { setTimeout as delay5 } from "node:timers/promises";
27353
+ import { setTimeout as delay7 } from "node:timers/promises";
26151
27354
 
26152
27355
  // src/codex-billing.ts
26153
27356
  import { createHash as createHash7 } from "node:crypto";
26154
- import { promises as fs22 } from "node:fs";
26155
- import path28 from "node:path";
27357
+ import { promises as fs26 } from "node:fs";
27358
+ import path33 from "node:path";
26156
27359
  var TASK_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage/thread_usage/query_v2";
26157
27360
  var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
26158
27361
  var DAY_MS = 24 * 60 * 60 * 1e3;
26159
27362
  var TASK_BATCH_SIZE = 50;
26160
- var MAX_HEADER_BYTES = 3 * 1024 * 1024;
27363
+ var MAX_HEADER_BYTES2 = 3 * 1024 * 1024;
26161
27364
  var stateSchema2 = object3({
26162
27365
  accountFingerprint: string5().regex(/^[0-9a-f]{64}$/),
26163
27366
  lastSyncedAtMs: number4().int().nonnegative(),
@@ -26204,7 +27407,7 @@ function fingerprint(value) {
26204
27407
  async function readAuth(codexHome) {
26205
27408
  let contents;
26206
27409
  try {
26207
- contents = await fs22.readFile(path28.join(codexHome, "auth.json"), "utf8");
27410
+ contents = await fs26.readFile(path33.join(codexHome, "auth.json"), "utf8");
26208
27411
  } catch (error3) {
26209
27412
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
26210
27413
  return {
@@ -26239,7 +27442,7 @@ async function readAuth(codexHome) {
26239
27442
  async function readState(filename) {
26240
27443
  let contents;
26241
27444
  try {
26242
- contents = await fs22.readFile(filename, "utf8");
27445
+ contents = await fs26.readFile(filename, "utf8");
26243
27446
  } catch (error3) {
26244
27447
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
26245
27448
  return null;
@@ -26261,7 +27464,7 @@ async function readState(filename) {
26261
27464
  }
26262
27465
  async function saveState2(filename, state) {
26263
27466
  const temporary = `${filename}.${process.pid}.tmp`;
26264
- const handle = await fs22.open(temporary, "w", 384);
27467
+ const handle = await fs26.open(temporary, "w", 384);
26265
27468
  try {
26266
27469
  await handle.writeFile(`${JSON.stringify(state)}
26267
27470
  `);
@@ -26269,21 +27472,21 @@ async function saveState2(filename, state) {
26269
27472
  } finally {
26270
27473
  await handle.close();
26271
27474
  }
26272
- await fs22.rename(temporary, filename);
27475
+ await fs26.rename(temporary, filename);
26273
27476
  }
26274
27477
  async function localThread(filename) {
26275
- const stat = await fs22.stat(filename);
26276
- const handle = await fs22.open(filename, "r");
27478
+ const stat = await fs26.stat(filename);
27479
+ const handle = await fs26.open(filename, "r");
26277
27480
  let header = null;
26278
27481
  try {
26279
27482
  const buffer = Buffer.alloc(64 * 1024);
26280
27483
  const chunks = [];
26281
27484
  let offset = 0;
26282
- while (offset < Math.min(stat.size, MAX_HEADER_BYTES)) {
27485
+ while (offset < Math.min(stat.size, MAX_HEADER_BYTES2)) {
26283
27486
  const read = await handle.read(
26284
27487
  buffer,
26285
27488
  0,
26286
- Math.min(buffer.length, MAX_HEADER_BYTES - offset),
27489
+ Math.min(buffer.length, MAX_HEADER_BYTES2 - offset),
26287
27490
  offset
26288
27491
  );
26289
27492
  if (read.bytesRead === 0) break;
@@ -26373,9 +27576,9 @@ function parseCodexAuthLog(threadId, body) {
26373
27576
  return { threadId, turnId: turn[1], mode };
26374
27577
  }
26375
27578
  async function localAuthTurns(codexHome, modelProviders) {
26376
- const filename = path28.join(codexHome, "logs_2.sqlite");
27579
+ const filename = path33.join(codexHome, "logs_2.sqlite");
26377
27580
  try {
26378
- await fs22.access(filename);
27581
+ await fs26.access(filename);
26379
27582
  } catch (error3) {
26380
27583
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
26381
27584
  return [];
@@ -26468,7 +27671,7 @@ async function queryTaskUsage(tasks, accessToken, accountId, signal) {
26468
27671
  }
26469
27672
  async function syncCodexBilling(config3, fetcher = collectorFetch, signal) {
26470
27673
  const auth = await readAuth(config3.codexHome);
26471
- const stateFile = path28.join(config3.dataDirectory, "codex-billing-sync.json");
27674
+ const stateFile = path33.join(config3.dataDirectory, "codex-billing-sync.json");
26472
27675
  const prior = await readState(stateFile);
26473
27676
  const nowMs = Date.now();
26474
27677
  const history = prior === null || prior.accountFingerprint !== auth.accountFingerprint;
@@ -26639,9 +27842,9 @@ async function syncCodexBilling(config3, fetcher = collectorFetch, signal) {
26639
27842
 
26640
27843
  // src/cursor-billing.ts
26641
27844
  import { createHash as createHash8 } from "node:crypto";
26642
- import { promises as fs23 } from "node:fs";
26643
- import os11 from "node:os";
26644
- import path29 from "node:path";
27845
+ import { promises as fs27 } from "node:fs";
27846
+ import os14 from "node:os";
27847
+ import path34 from "node:path";
26645
27848
  var CURSOR_USAGE_URL = "https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents";
26646
27849
  var PAGE_SIZE = 500;
26647
27850
  var MAX_PAGES = 1e3;
@@ -26672,18 +27875,18 @@ var usagePageSchema = object3({
26672
27875
  var emptyUsagePageSchema = object3({}).strict();
26673
27876
  function cursorAuthDatabase() {
26674
27877
  if (process.platform === "darwin")
26675
- return path29.join(
26676
- os11.homedir(),
27878
+ return path34.join(
27879
+ os14.homedir(),
26677
27880
  "Library/Application Support/Cursor/User/globalStorage/state.vscdb"
26678
27881
  );
26679
27882
  if (process.platform === "linux")
26680
- return path29.join(
26681
- os11.homedir(),
27883
+ return path34.join(
27884
+ os14.homedir(),
26682
27885
  ".config/Cursor/User/globalStorage/state.vscdb"
26683
27886
  );
26684
27887
  if (process.platform === "win32")
26685
- return path29.join(
26686
- process.env.APPDATA ?? path29.join(os11.homedir(), "AppData", "Roaming"),
27888
+ return path34.join(
27889
+ process.env.APPDATA ?? path34.join(os14.homedir(), "AppData", "Roaming"),
26687
27890
  "Cursor/User/globalStorage/state.vscdb"
26688
27891
  );
26689
27892
  return null;
@@ -26725,7 +27928,7 @@ async function readCursorAuth() {
26725
27928
  async function loadBillingState(filename) {
26726
27929
  let contents;
26727
27930
  try {
26728
- contents = await fs23.readFile(filename, "utf8");
27931
+ contents = await fs27.readFile(filename, "utf8");
26729
27932
  } catch (error3) {
26730
27933
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
26731
27934
  return null;
@@ -26741,7 +27944,7 @@ async function loadBillingState(filename) {
26741
27944
  }
26742
27945
  async function saveBillingState(filename, state) {
26743
27946
  const temporary = `${filename}.${process.pid}.tmp`;
26744
- const handle = await fs23.open(temporary, "w", 384);
27947
+ const handle = await fs27.open(temporary, "w", 384);
26745
27948
  try {
26746
27949
  await handle.writeFile(`${JSON.stringify(state)}
26747
27950
  `, "utf8");
@@ -26749,7 +27952,7 @@ async function saveBillingState(filename, state) {
26749
27952
  } finally {
26750
27953
  await handle.close();
26751
27954
  }
26752
- await fs23.rename(temporary, filename);
27955
+ await fs27.rename(temporary, filename);
26753
27956
  }
26754
27957
  function planCursorBillingSync(state, accountFingerprint, nowMs) {
26755
27958
  if (state === null || state.accountFingerprint !== accountFingerprint)
@@ -26771,7 +27974,7 @@ function planCursorBillingSync(state, accountFingerprint, nowMs) {
26771
27974
  }
26772
27975
  function cursorConversationIds(filenames) {
26773
27976
  return new Set(
26774
- filenames.map((filename) => path29.basename(filename, ".jsonl")).filter((id) => UUID3.test(id))
27977
+ filenames.map((filename) => path34.basename(filename, ".jsonl")).filter((id) => UUID3.test(id))
26775
27978
  );
26776
27979
  }
26777
27980
  function billingKind(kind) {
@@ -26829,11 +28032,11 @@ async function syncCursorBilling(config3, fetcher = collectorFetch, signal) {
26829
28032
  if (auth === null)
26830
28033
  return { sessions: 0, matched: 0, skipped: true, mode: null };
26831
28034
  const localConversations = cursorConversationIds(
26832
- await cursorFiles(path29.join(config3.cursorHome, "projects"))
28035
+ await cursorFiles(path34.join(config3.cursorHome, "projects"))
26833
28036
  );
26834
28037
  if (localConversations.size === 0)
26835
28038
  return { sessions: 0, matched: 0, skipped: true, mode: null };
26836
- const stateFile = path29.join(config3.dataDirectory, "cursor-billing-sync.json");
28039
+ const stateFile = path34.join(config3.dataDirectory, "cursor-billing-sync.json");
26837
28040
  const priorState = await loadBillingState(stateFile);
26838
28041
  const nowMs = Date.now();
26839
28042
  const plan = planCursorBillingSync(
@@ -27075,6 +28278,14 @@ async function onTrial(version3) {
27075
28278
  const state = await readRuntimeState(runtimePaths());
27076
28279
  return state?.trial?.version === version3;
27077
28280
  }
28281
+ function throwCollectorFailures(failures) {
28282
+ if (failures.length === 1) throw failures[0];
28283
+ if (failures.length > 1)
28284
+ throw new AggregateError(
28285
+ failures,
28286
+ "The collector could not finish cleanly."
28287
+ );
28288
+ }
27078
28289
  async function runCollector(directory = defaultCollectorDirectory("codex"), expectedSource) {
27079
28290
  const paths = collectorPaths(directory);
27080
28291
  const releaseLock = await acquireCollectorLock(paths);
@@ -27083,8 +28294,18 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27083
28294
  process.on("SIGTERM", stop);
27084
28295
  process.on("SIGINT", stop);
27085
28296
  let retryDelayMs = 5e3;
28297
+ let folderTask = null;
28298
+ const failures = [];
27086
28299
  try {
27087
28300
  const config3 = await loadConfig(paths);
28301
+ if (config3.destination === "host")
28302
+ folderTask = runFolderBrowser(config3, controller.signal).then(
28303
+ () => null,
28304
+ (error3) => {
28305
+ controller.abort();
28306
+ return { error: error3 };
28307
+ }
28308
+ );
27088
28309
  const source = collectorSource(config3);
27089
28310
  if (expectedSource !== void 0 && expectedSource !== source) {
27090
28311
  throw new Error(
@@ -27109,6 +28330,16 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27109
28330
  } else await ensureHistoryBackfill(config3.codexHome, paths);
27110
28331
  const summariesRefreshedAt = /* @__PURE__ */ new Map();
27111
28332
  let lastAutoCheckAt = 0;
28333
+ let scoped = null;
28334
+ let sharingRevision = null;
28335
+ const fetcher = (input, init) => {
28336
+ if (config3.destination !== "host") return collectorFetch(input, init);
28337
+ if (scoped === null)
28338
+ throw new Error(
28339
+ "Project sharing could not be verified. Collection is waiting."
28340
+ );
28341
+ return scoped.fetcher(input, init);
28342
+ };
27112
28343
  let lastBillingSyncAt = 0;
27113
28344
  const historyProgress2 = new HistoryProgressReporter();
27114
28345
  const runtimeVersion = process.env.ITERATION_RUNTIME_VERSION;
@@ -27145,6 +28376,10 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27145
28376
  if (Date.now() - lastAutoCheckAt >= 6e4) {
27146
28377
  lastAutoCheckAt = Date.now();
27147
28378
  try {
28379
+ if (config3.destination === "host") {
28380
+ await connectSharedDetectedSources(config3);
28381
+ return;
28382
+ }
27148
28383
  const consent = await loadMachineConsent();
27149
28384
  if (consent?.anchorSource === source) {
27150
28385
  const connected = await connectNewDetectedSources(config3, consent);
@@ -27197,15 +28432,31 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27197
28432
  let billingReady = false;
27198
28433
  let history = null;
27199
28434
  try {
28435
+ if (config3.destination === "host") {
28436
+ scoped = null;
28437
+ scoped = await sharingTransport(
28438
+ config3,
28439
+ collectorFetch,
28440
+ controller.signal
28441
+ );
28442
+ if (sharingRevision !== scoped.snapshot.revision) {
28443
+ await clearTurnSummaries(config3);
28444
+ summariesRefreshedAt.clear();
28445
+ sharingRevision = scoped.snapshot.revision;
28446
+ }
28447
+ }
27200
28448
  const result = await collectorTick(
27201
28449
  config3,
27202
28450
  paths,
27203
- collectorFetch,
28451
+ fetcher,
27204
28452
  controller.signal,
27205
28453
  () => {
27206
28454
  acknowledgedAtMs = Date.now();
27207
28455
  },
27208
- { recentMessages: RECENT_MESSAGE_LIMIT }
28456
+ {
28457
+ recentMessages: RECENT_MESSAGE_LIMIT,
28458
+ ...scoped === null ? {} : { capture: scoped.capture }
28459
+ }
27209
28460
  );
27210
28461
  billingReady = result.historyCaughtUp && result.outboxBytes === 0;
27211
28462
  history = {
@@ -27246,7 +28497,7 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27246
28497
  await historyProgress2.report(
27247
28498
  config3,
27248
28499
  history,
27249
- collectorFetch,
28500
+ fetcher,
27250
28501
  controller.signal,
27251
28502
  Date.now()
27252
28503
  );
@@ -27271,7 +28522,7 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27271
28522
  config3,
27272
28523
  summariesRefreshedAt,
27273
28524
  Date.now(),
27274
- collectorFetch,
28525
+ fetcher,
27275
28526
  controller.signal
27276
28527
  );
27277
28528
  } catch (error3) {
@@ -27292,7 +28543,7 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27292
28543
  try {
27293
28544
  const billing = await syncCursorBilling(
27294
28545
  config3,
27295
- collectorFetch,
28546
+ fetcher,
27296
28547
  controller.signal
27297
28548
  );
27298
28549
  lastBillingSyncAt = billing.skipped ? Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3 : Date.now();
@@ -27315,7 +28566,7 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27315
28566
  try {
27316
28567
  const billing = await syncCodexBilling(
27317
28568
  config3,
27318
- collectorFetch,
28569
+ fetcher,
27319
28570
  controller.signal
27320
28571
  );
27321
28572
  lastBillingSyncAt = billing.skipped ? Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3 : Date.now();
@@ -27345,17 +28596,23 @@ async function runCollector(directory = defaultCollectorDirectory("codex"), expe
27345
28596
  if (!await refreshSummaries()) break;
27346
28597
  if (!await syncBilling(result.billingReady)) break;
27347
28598
  try {
27348
- await delay5(retryDelayMs, void 0, { signal: controller.signal });
28599
+ await delay7(retryDelayMs, void 0, { signal: controller.signal });
27349
28600
  } catch (error3) {
27350
28601
  if (error3 instanceof Error && error3.name === "AbortError") break;
27351
28602
  throw error3;
27352
28603
  }
27353
28604
  }
28605
+ } catch (error3) {
28606
+ failures.push(error3);
27354
28607
  } finally {
28608
+ controller.abort();
27355
28609
  process.off("SIGTERM", stop);
27356
28610
  process.off("SIGINT", stop);
27357
- await releaseLock();
28611
+ const outcome = await folderTask;
28612
+ if (outcome) failures.push(outcome.error);
28613
+ await releaseLock().catch((error3) => failures.push(error3));
27358
28614
  }
28615
+ throwCollectorFailures(failures);
27359
28616
  }
27360
28617
 
27361
28618
  // src/status-view.ts
@@ -27480,14 +28737,14 @@ function failureSummary(terminal2, status2, source, nowMs) {
27480
28737
  }
27481
28738
  function connectionSummary(terminal2, status2, source, nowMs) {
27482
28739
  const name = sourceName(source);
27483
- const pending = status2.pendingEvents > 0 ? [`${pendingDescription(status2)}.`] : [];
28740
+ const pending2 = status2.pendingEvents > 0 ? [`${pendingDescription(status2)}.`] : [];
27484
28741
  const details = `Details: ${terminal2.bold(`iteration status ${source}`)}`;
27485
28742
  if (!status2.running)
27486
28743
  return {
27487
28744
  tone: "warning",
27488
28745
  headline: "Stopped",
27489
28746
  facts: [],
27490
- details: ["New sessions are not being collected.", ...pending],
28747
+ details: ["New sessions are not being collected.", ...pending2],
27491
28748
  next: `Resume with ${terminal2.bold(`iteration start ${source}`)}`
27492
28749
  };
27493
28750
  if (status2.hookFailureAtMs !== null && status2.hookFailureAtMs !== void 0)
@@ -27507,7 +28764,7 @@ function connectionSummary(terminal2, status2, source, nowMs) {
27507
28764
  facts: [],
27508
28765
  details: [
27509
28766
  "Upload history could not be read. Collection continues in the background.",
27510
- ...pending
28767
+ ...pending2
27511
28768
  ],
27512
28769
  next: details
27513
28770
  };
@@ -27517,7 +28774,7 @@ function connectionSummary(terminal2, status2, source, nowMs) {
27517
28774
  }
27518
28775
  function scanSummary(status2, source, nowMs) {
27519
28776
  const name = sourceName(source);
27520
- const pending = status2.pendingEvents > 0 ? [`${pendingDescription(status2)}.`] : [];
28777
+ const pending2 = status2.pendingEvents > 0 ? [`${pendingDescription(status2)}.`] : [];
27521
28778
  const lastUpload = status2.health.lastUploadAtMs === null ? [] : [`last upload ${relativeTime(status2.health.lastUploadAtMs, nowMs)}`];
27522
28779
  const summary = (headline, details, facts = []) => ({
27523
28780
  tone: "success",
@@ -27536,10 +28793,10 @@ function scanSummary(status2, source, nowMs) {
27536
28793
  if (scan.totalBytes - scan.scannedBytes >= 1e6)
27537
28794
  return summary("Catching up with new activity", [
27538
28795
  historyDescription(status2, source),
27539
- ...pending
28796
+ ...pending2
27540
28797
  ]);
27541
28798
  if (status2.pendingEvents > 0)
27542
- return summary("Uploading captured sessions", pending);
28799
+ return summary("Uploading captured sessions", pending2);
27543
28800
  if (scan.totalBytes === 0)
27544
28801
  return summary("Connected", [
27545
28802
  `Waiting for ${name} sessions on this computer.`
@@ -27759,26 +29016,26 @@ function renderRuntime(terminal2, summary) {
27759
29016
 
27760
29017
  // src/windows-supervisor.ts
27761
29018
  import { spawn } from "node:child_process";
27762
- import { promises as fs24 } from "node:fs";
27763
- import path30 from "node:path";
27764
- import { setTimeout as delay6 } from "node:timers/promises";
29019
+ import { promises as fs28 } from "node:fs";
29020
+ import path35 from "node:path";
29021
+ import { setTimeout as delay8 } from "node:timers/promises";
27765
29022
  var MAX_LOG_BYTES = 8 * 1024 * 1024;
27766
29023
  var RESTART_DELAY_MS = 5e3;
27767
29024
  async function appendLog(filename, bytes) {
27768
29025
  let offset = 0;
27769
29026
  while (offset < bytes.length) {
27770
- const size = await fs24.stat(filename).then((stat) => stat.size).catch((error3) => {
29027
+ const size = await fs28.stat(filename).then((stat) => stat.size).catch((error3) => {
27771
29028
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
27772
29029
  return 0;
27773
29030
  throw error3;
27774
29031
  });
27775
29032
  if (size >= MAX_LOG_BYTES) {
27776
- await fs24.rm(`${filename}.1`, { force: true });
27777
- await fs24.rename(filename, `${filename}.1`);
29033
+ await fs28.rm(`${filename}.1`, { force: true });
29034
+ await fs28.rename(filename, `${filename}.1`);
27778
29035
  continue;
27779
29036
  }
27780
29037
  const count = Math.min(bytes.length - offset, MAX_LOG_BYTES - size);
27781
- await fs24.appendFile(filename, bytes.subarray(offset, offset + count), {
29038
+ await fs28.appendFile(filename, bytes.subarray(offset, offset + count), {
27782
29039
  mode: 384
27783
29040
  });
27784
29041
  offset += count;
@@ -27794,7 +29051,7 @@ async function copyOutput(stream, filename) {
27794
29051
  async function superviseCollector(source, dataDirectory2) {
27795
29052
  const output = windowsOutputPath(source);
27796
29053
  const errors = windowsLogPath(source);
27797
- await ensurePrivateDirectory(path30.dirname(output));
29054
+ await ensurePrivateDirectory(path35.dirname(output));
27798
29055
  const controller = new AbortController();
27799
29056
  const stop = () => controller.abort();
27800
29057
  process.once("SIGTERM", stop);
@@ -27851,7 +29108,7 @@ async function superviseCollector(source, dataDirectory2) {
27851
29108
  `
27852
29109
  )
27853
29110
  );
27854
- await delay6(RESTART_DELAY_MS, void 0, {
29111
+ await delay8(RESTART_DELAY_MS, void 0, {
27855
29112
  signal: controller.signal
27856
29113
  }).catch((error3) => {
27857
29114
  if (!controller.signal.aborted) throw error3;
@@ -27864,7 +29121,6 @@ async function superviseCollector(source, dataDirectory2) {
27864
29121
  }
27865
29122
 
27866
29123
  // src/cli.ts
27867
- var DEFAULT_INGEST_ENDPOINT = "https://useiteration.com/api/traces/ingest";
27868
29124
  var terminal = terminalFor(process.stdout);
27869
29125
  function print(...lines) {
27870
29126
  process.stdout.write(`${lines.join("\n")}
@@ -27958,7 +29214,7 @@ function usage(command) {
27958
29214
  "",
27959
29215
  wrap(
27960
29216
  terminal,
27961
- "Existing local sessions and new activity are collected automatically."
29217
+ "Choose projects in Iteration before any sessions upload."
27962
29218
  ),
27963
29219
  "",
27964
29220
  ...section("Options for advanced setups", [
@@ -27967,7 +29223,7 @@ function usage(command) {
27967
29223
  ["--codex-home DIR", "Use a different Codex data directory"],
27968
29224
  ["--claude-home DIR", "Use a different Claude data directory"],
27969
29225
  ["--cursor-home DIR", "Use a different Cursor data directory"],
27970
- ["--yes", "Approve uploads without a terminal prompt"],
29226
+ ["--yes", "Project sharing still requires approval in Iteration"],
27971
29227
  [
27972
29228
  "--data-directory DIR",
27973
29229
  "Use a different state directory with a one-agent command"
@@ -28006,7 +29262,7 @@ function usage(command) {
28006
29262
  }
28007
29263
  async function fileExists(filename) {
28008
29264
  try {
28009
- await fs25.access(filename);
29265
+ await fs29.access(filename);
28010
29266
  return true;
28011
29267
  } catch (error3) {
28012
29268
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT") {
@@ -28047,7 +29303,7 @@ async function defaultCommand() {
28047
29303
  await connectAll({});
28048
29304
  }
28049
29305
  function dataDirectory(source, values) {
28050
- return path31.resolve(
29306
+ return path36.resolve(
28051
29307
  optionalString(values, "data-directory") ?? defaultCollectorDirectory(source)
28052
29308
  );
28053
29309
  }
@@ -28094,337 +29350,8 @@ function turnSummaryLines(results) {
28094
29350
  ...warnings
28095
29351
  ];
28096
29352
  }
28097
- var CONNECTED_WIDTH = Math.max(
28098
- ...COLLECTOR_SOURCES2.map(
28099
- (source) => `${collectorSourceLabel(source)} connected`.length
28100
- )
28101
- );
28102
- function connectedLine(source, sessions) {
28103
- return mark(
28104
- terminal,
28105
- "success",
28106
- `${padEnd(`${collectorSourceLabel(source)} connected`, CONNECTED_WIDTH)} ${terminal.dim(
28107
- sessions === 0 ? "no saved sessions yet" : `importing ${sessions.toLocaleString("en-US")} ${sessions === 1 ? "session" : "sessions"}`
28108
- )}`
28109
- );
28110
- }
28111
- function connectionSummary2(results, imported) {
28112
- const links = [
28113
- ...new Set(results.map(({ endpoint }) => tracesUrl(endpoint)))
28114
- ];
28115
- const notes = turnSummaryLines(results);
28116
- return [
28117
- ...notes.length === 0 ? [] : ["", ...notes],
28118
- "",
28119
- wrap(
28120
- terminal,
28121
- imported ? `Sessions import newest first. Run ${terminal.bold("iteration")} to follow progress.` : `Run ${terminal.bold("iteration")} to see their status.`
28122
- ),
28123
- ...links.map((url2) => ` Open Iteration ${terminal.link(url2)}`)
28124
- ];
28125
- }
28126
- async function initialize(source, values) {
28127
- requireCollectorPlatform();
28128
- const endpoint = optionalString(values, "endpoint") ?? DEFAULT_INGEST_ENDPOINT;
28129
- const endpointUrl = validateCollectorEndpoint(endpoint);
28130
- const deviceName = optionalString(values, "device-name") ?? os12.hostname();
28131
- const organizationId = optionalString(values, "workspace");
28132
- const directory = dataDirectory(source, values);
28133
- const paths = collectorPaths(directory);
28134
- const selectedHome = path31.resolve(
28135
- source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ?? path31.join(os12.homedir(), ".codex") : source === "claude" ? optionalString(values, "claude-home") ?? process.env.CLAUDE_CONFIG_DIR ?? path31.join(os12.homedir(), ".claude") : optionalString(values, "cursor-home") ?? path31.join(os12.homedir(), ".cursor")
28136
- );
28137
- await assertFreshCollectorDirectory(paths, source);
28138
- const preparedState = await prepareSourceInitialState(
28139
- source,
28140
- selectedHome,
28141
- true
28142
- );
28143
- await prepareBackgroundPersistence();
28144
- const enrollment = await authenticateAndEnrollCollector(
28145
- endpointUrl,
28146
- deviceName,
28147
- organizationId,
28148
- source
28149
- );
28150
- print(
28151
- mark(
28152
- terminal,
28153
- "success",
28154
- `Signed in to ${terminal.bold(safeWorkspaceName(enrollment.organizationName) ?? "your workspace")}`
28155
- )
28156
- );
28157
- const connection = {
28158
- endpoint: endpointUrl.toString(),
28159
- token: enrollment.token,
28160
- dataDirectory: directory,
28161
- organizationId: enrollment.organizationId,
28162
- organizationName: enrollment.organizationName
28163
- };
28164
- const config3 = source === "codex" ? { ...connection, codexHome: selectedHome } : source === "claude" ? { ...connection, source: "claude", claudeHome: selectedHome } : { ...connection, source: "cursor", cursorHome: selectedHome };
28165
- await saveConfig(config3, paths);
28166
- try {
28167
- await saveState(preparedState, paths);
28168
- if (source === "cursor") await installCursorHooks(selectedHome, directory);
28169
- } catch (error3) {
28170
- throw new Error(
28171
- `Collector access was approved, but local setup did not finish. Run iteration start ${source} to repair it.`,
28172
- { cause: error3 }
28173
- );
28174
- }
28175
- try {
28176
- await installBackgroundService(directory, source);
28177
- } catch (error3) {
28178
- throw new Error(
28179
- `Collector access was approved, but background startup failed. Run iteration start ${source} to retry it.`,
28180
- { cause: error3 }
28181
- );
28182
- }
28183
- const turnSummary = await setUpTurnSummary(config3);
28184
- print(connectedLine(source, await countSavedSessions(source, selectedHome)));
28185
- return {
28186
- config: config3,
28187
- result: { source, endpoint: config3.endpoint, turnSummary }
28188
- };
28189
- }
28190
- async function approveAllSources(sources, workspaceName, yes) {
28191
- const safeName = safeWorkspaceName(workspaceName);
28192
- print(
28193
- wrap(
28194
- terminal,
28195
- sources.length === 0 ? "No saved agent sessions found on this computer" : `Found ${sources.length} coding ${sources.length === 1 ? "agent" : "agents"} on this computer`,
28196
- { style: terminal.bold }
28197
- ),
28198
- ...sources.length === 0 ? [] : [
28199
- "",
28200
- rows(
28201
- terminal,
28202
- sources.map(({ source, sessions }) => [
28203
- collectorSourceLabel(source),
28204
- terminal.dim(
28205
- `${sessions.toLocaleString("en-US")} saved ${sessions === 1 ? "session" : "sessions"}`
28206
- )
28207
- ]),
28208
- " "
28209
- )
28210
- ],
28211
- "",
28212
- wrap(
28213
- terminal,
28214
- `Existing sessions and new activity, including prompts, code and tool output, upload to ${safeName ?? "your workspace"}, where its members can read them. Your agents also get the workspace's tools. Supported agents you install later connect automatically.${process.platform === "linux" ? " Collection continues after you log out." : ""}`
28215
- ),
28216
- ""
28217
- );
28218
- if (yes) return;
28219
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
28220
- throw new Error(
28221
- "Run iteration connect interactively or pass --yes to approve uploads."
28222
- );
28223
- }
28224
- const answer = await ask(
28225
- terminal,
28226
- `${terminal.bold(sources.length === 1 ? "Connect this agent?" : "Connect these agents?")} ${terminal.dim("(y/N)")}`
28227
- );
28228
- const approved = answer?.trim().toLowerCase();
28229
- if (approved !== "y" && approved !== "yes")
28230
- throw new Error(
28231
- "No agents were connected. Run iteration connect when you are ready."
28232
- );
28233
- }
28234
29353
  async function connectAll(values) {
28235
- requireCollectorPlatform();
28236
- if (values["data-directory"] !== void 0) {
28237
- throw new Error(
28238
- "--data-directory requires a source-specific connect command."
28239
- );
28240
- }
28241
- const configs = await Promise.all(COLLECTOR_SOURCES2.map(connectedConfig));
28242
- const resolveSourceHomes = () => ({
28243
- codex: path31.resolve(
28244
- optionalString(values, "codex-home") ?? (configs[0] && "codexHome" in configs[0] ? configs[0].codexHome : sourceHome("codex"))
28245
- ),
28246
- claude: path31.resolve(
28247
- optionalString(values, "claude-home") ?? (configs[1] && "claudeHome" in configs[1] ? configs[1].claudeHome : sourceHome("claude"))
28248
- ),
28249
- cursor: path31.resolve(
28250
- optionalString(values, "cursor-home") ?? (configs[2] && "cursorHome" in configs[2] ? configs[2].cursorHome : sourceHome("cursor"))
28251
- )
28252
- });
28253
- const sourceHomes = resolveSourceHomes();
28254
- const detected = await detectedSources(sourceHomes);
28255
- const existing = COLLECTOR_SOURCES2.flatMap(
28256
- (source, index) => configs[index] ? [{ source, config: configs[index] }] : []
28257
- );
28258
- print(brand(terminal), "");
28259
- if (detected.length === 0 && existing.length === 0) {
28260
- print(
28261
- mark(terminal, "warning", "No supported local coding agents found yet"),
28262
- wrap(
28263
- terminal,
28264
- `Run ${terminal.bold("iteration connect")} after using Codex, Claude Code or Cursor on this computer.`
28265
- )
28266
- );
28267
- return;
28268
- }
28269
- const sessions = await Promise.all(
28270
- detected.map(async (source) => ({
28271
- source,
28272
- sessions: await countSavedSessions(source, sourceHomes[source])
28273
- }))
28274
- );
28275
- const anchorSource = existing[0]?.source ?? detected[0];
28276
- const anchorExisting = existing[0]?.config ?? null;
28277
- const endpoint = validateCollectorEndpoint(
28278
- optionalString(values, "endpoint") ?? anchorExisting?.endpoint ?? DEFAULT_INGEST_ENDPOINT
28279
- ).toString();
28280
- const existingScopes = await Promise.all(
28281
- existing.map(async (item) => ({
28282
- ...item,
28283
- scope: await readCollectorScope(item.config)
28284
- }))
28285
- );
28286
- const validateWorkspace = () => {
28287
- for (const item of existingScopes) {
28288
- if (item.config.endpoint !== endpoint) {
28289
- throw new Error(
28290
- "Existing sources use different Iteration servers. Connect one source at a time."
28291
- );
28292
- }
28293
- if (existingScopes[0]?.scope.organizationId !== item.scope.organizationId) {
28294
- throw new Error(
28295
- "Existing sources use different workspaces. Connect one source at a time."
28296
- );
28297
- }
28298
- }
28299
- const requestedOrganizationId2 = optionalString(values, "workspace");
28300
- if (requestedOrganizationId2 && existingScopes[0] && requestedOrganizationId2 !== existingScopes[0].scope.organizationId) {
28301
- throw new Error(
28302
- "The existing collector belongs to a different workspace."
28303
- );
28304
- }
28305
- };
28306
- validateWorkspace();
28307
- const requestedOrganizationId = optionalString(values, "workspace");
28308
- const previousConsent = await loadMachineConsent();
28309
- const approveConnection = async () => {
28310
- const needsApproval = () => previousConsent === null || anchorExisting === null || previousConsent.endpoint !== endpoint || previousConsent.anchorSource !== anchorSource || previousConsent.anchorDataDirectory !== anchorExisting.dataDirectory || previousConsent.organizationId !== existingScopes[0]?.scope.organizationId || previousConsent.sourceHomes.codex !== sourceHomes.codex || previousConsent.sourceHomes.claude !== sourceHomes.claude || previousConsent.sourceHomes.cursor !== sourceHomes.cursor;
28311
- if (needsApproval()) {
28312
- await approveAllSources(
28313
- sessions,
28314
- existingScopes[0]?.scope.organizationName ?? null,
28315
- values.yes === true
28316
- );
28317
- }
28318
- };
28319
- await approveConnection();
28320
- await prepareBackgroundPersistence();
28321
- const results = [];
28322
- const initializeAnchor = async () => {
28323
- const initialized = await initialize(anchorSource, values);
28324
- results.push(initialized.result);
28325
- return initialized.config;
28326
- };
28327
- let anchor3 = anchorExisting ?? await initializeAnchor();
28328
- let organizationId = requestedOrganizationId ?? existingScopes[0]?.scope.organizationId ?? anchor3.organizationId ?? null;
28329
- const connectDetectedSources = async () => {
28330
- for (const source of detected) {
28331
- if (source === anchorSource || configs[COLLECTOR_SOURCES2.indexOf(source)] !== null)
28332
- continue;
28333
- const paths = collectorPaths(defaultCollectorDirectory(source));
28334
- await assertFreshCollectorDirectory(paths, source);
28335
- const state = await prepareSourceInitialState(
28336
- source,
28337
- sourceHomes[source],
28338
- true
28339
- );
28340
- const enrollment = await expandSource(anchor3, source, organizationId);
28341
- const config3 = await activateSource(
28342
- source,
28343
- sourceHomes[source],
28344
- endpoint,
28345
- enrollment,
28346
- state
28347
- );
28348
- organizationId = enrollment.organizationId;
28349
- results.push({
28350
- source,
28351
- endpoint: config3.endpoint,
28352
- turnSummary: await setUpTurnSummary(config3)
28353
- });
28354
- print(
28355
- connectedLine(
28356
- source,
28357
- sessions.find((item) => item.source === source)?.sessions ?? 0
28358
- )
28359
- );
28360
- }
28361
- };
28362
- await connectDetectedSources();
28363
- if (organizationId === null) {
28364
- organizationId = (await readCollectorScope(anchor3)).organizationId;
28365
- }
28366
- if (anchor3.organizationId === void 0) {
28367
- anchor3 = { ...anchor3, organizationId };
28368
- await saveConfig(anchor3, collectorPaths(anchor3.dataDirectory));
28369
- }
28370
- await saveMachineConsent({
28371
- version: 1,
28372
- endpoint,
28373
- organizationId,
28374
- anchorSource,
28375
- anchorDataDirectory: anchor3.dataDirectory,
28376
- sourceHomes
28377
- });
28378
- for (const item of existing) {
28379
- await ensureStartableState(
28380
- item.config,
28381
- collectorPaths(item.config.dataDirectory)
28382
- );
28383
- if ("cursorHome" in item.config)
28384
- await installCursorHooks(
28385
- item.config.cursorHome,
28386
- item.config.dataDirectory
28387
- );
28388
- await installBackgroundService(item.config.dataDirectory, item.source);
28389
- results.push({
28390
- source: item.source,
28391
- endpoint: item.config.endpoint,
28392
- turnSummary: await setUpTurnSummary(item.config)
28393
- });
28394
- print(
28395
- mark(
28396
- terminal,
28397
- "success",
28398
- `${collectorSourceLabel(item.source)} is still connected`
28399
- )
28400
- );
28401
- }
28402
- await addAgentTools(
28403
- results.map((result) => result.source),
28404
- sourceHomes
28405
- );
28406
- print(...connectionSummary2(results, results.length > existing.length));
28407
- }
28408
- async function addAgentTools(sources, sourceHomes) {
28409
- for (const source of new Set(sources)) {
28410
- try {
28411
- await installAgentTools(
28412
- source === "cursor" ? { cursor: true } : source === "claude" ? { claude: sourceHomes.claude } : { codex: sourceHomes.codex }
28413
- );
28414
- } catch (error3) {
28415
- print(
28416
- mark(
28417
- terminal,
28418
- "warning",
28419
- `Could not add Iteration's tools to ${collectorSourceLabel(source)}`
28420
- ),
28421
- wrap(
28422
- terminal,
28423
- error3 instanceof Error ? error3.message : "Its settings could not be updated."
28424
- )
28425
- );
28426
- }
28427
- }
29354
+ await connectWorkspace(values);
28428
29355
  }
28429
29356
  async function importHistory(source, values) {
28430
29357
  requireCollectorPlatform();
@@ -28757,7 +29684,9 @@ async function runInternalCommand() {
28757
29684
  }
28758
29685
  if (process.argv[2] === "mcp") {
28759
29686
  if (process.argv.length !== 3) throw new Error("Invalid mcp command.");
28760
- const configs = await Promise.all(COLLECTOR_SOURCES2.map(connectedConfig));
29687
+ const configs = await Promise.all(
29688
+ COLLECTOR_SOURCES2.map((source) => connectedConfig(source))
29689
+ );
28761
29690
  await serveAgentTools(configs.find((config3) => config3 !== null) ?? null);
28762
29691
  return true;
28763
29692
  }
@@ -28885,9 +29814,7 @@ async function dispatchSourceCommand(command, source, values, selectedSource) {
28885
29814
  };
28886
29815
  validateSourceOptions();
28887
29816
  if (command === "connect" && selectedSource !== null) {
28888
- print(brand(terminal), "");
28889
- const { result } = await initialize(selectedSource, values);
28890
- print(...connectionSummary2([result], true));
29817
+ await connectWorkspace(values, selectedSource);
28891
29818
  return;
28892
29819
  }
28893
29820
  if (command === "run") {