iteration-cli 0.14.4 → 0.15.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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +265 -54
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Iteration CLI (private source)
2
2
 
3
- This package owns the `iteration-cli@0.14.4` product executable. Clerk sign-in,
3
+ This package owns the `iteration-cli@0.15.0` product executable. Clerk sign-in,
4
4
  workspace enrollment and automatic expansion, the default Iteration endpoint,
5
5
  commands/status UI, history progress, friction summaries and Codex/Cursor billing
6
6
  clients live here, not in the public Trace Vault repository.
@@ -28,9 +28,9 @@ package installs only `iteration`.
28
28
  From the private repository root after workspace initialization:
29
29
 
30
30
  ```sh
31
- pnpm --filter iteration-cli build
31
+ pnpm --filter "iteration-cli..." --recursive --workspace-concurrency=4 run build
32
32
  pnpm --filter iteration-cli pack --pack-destination /tmp/iteration-cli-pack
33
- node packages/cli/smoke.mjs /tmp/iteration-cli-pack/iteration-cli-0.14.4.tgz
33
+ node packages/cli/smoke.mjs /tmp/iteration-cli-pack/iteration-cli-0.15.0.tgz
34
34
  ```
35
35
 
36
36
  The package smoke installs the tarball offline under a disposable home and checks
package/dist/cli.js CHANGED
@@ -7,9 +7,9 @@ var __export = (target, all) => {
7
7
 
8
8
  // src/cli.ts
9
9
  import { spawnSync } from "node:child_process";
10
- import { promises as fs23 } from "node:fs";
11
- import os10 from "node:os";
12
- import path29 from "node:path";
10
+ import { promises as fs24 } from "node:fs";
11
+ import os11 from "node:os";
12
+ import path30 from "node:path";
13
13
  import { createInterface as createInterface2 } from "node:readline/promises";
14
14
  import { parseArgs } from "node:util";
15
15
 
@@ -15850,10 +15850,10 @@ function mergeDefs2(...defs) {
15850
15850
  function cloneDef2(schema) {
15851
15851
  return mergeDefs2(schema._zod.def);
15852
15852
  }
15853
- function getElementAtPath2(obj, path30) {
15854
- if (!path30)
15853
+ function getElementAtPath2(obj, path31) {
15854
+ if (!path31)
15855
15855
  return obj;
15856
- return path30.reduce((acc, key) => acc?.[key], obj);
15856
+ return path31.reduce((acc, key) => acc?.[key], obj);
15857
15857
  }
15858
15858
  function promiseAllObject2(promisesObj) {
15859
15859
  const keys = Object.keys(promisesObj);
@@ -16193,11 +16193,11 @@ function explicitlyAborted2(x, startIndex = 0) {
16193
16193
  }
16194
16194
  return false;
16195
16195
  }
16196
- function prefixIssues2(path30, issues) {
16196
+ function prefixIssues2(path31, issues) {
16197
16197
  return issues.map((iss) => {
16198
16198
  var _a5;
16199
16199
  (_a5 = iss).path ?? (_a5.path = []);
16200
- iss.path.unshift(path30);
16200
+ iss.path.unshift(path31);
16201
16201
  return iss;
16202
16202
  });
16203
16203
  }
@@ -16655,16 +16655,16 @@ function flattenError2(error3, mapper = (issue3) => issue3.message) {
16655
16655
  }
16656
16656
  function formatError2(error3, mapper = (issue3) => issue3.message) {
16657
16657
  const fieldErrors = { _errors: [] };
16658
- const processError = (error4, path30 = []) => {
16658
+ const processError = (error4, path31 = []) => {
16659
16659
  for (const issue3 of error4.issues) {
16660
16660
  if (issue3.code === "invalid_union" && issue3.errors.length) {
16661
- issue3.errors.map((issues) => processError({ issues }, [...path30, ...issue3.path]));
16661
+ issue3.errors.map((issues) => processError({ issues }, [...path31, ...issue3.path]));
16662
16662
  } else if (issue3.code === "invalid_key") {
16663
- processError({ issues: issue3.issues }, [...path30, ...issue3.path]);
16663
+ processError({ issues: issue3.issues }, [...path31, ...issue3.path]);
16664
16664
  } else if (issue3.code === "invalid_element") {
16665
- processError({ issues: issue3.issues }, [...path30, ...issue3.path]);
16665
+ processError({ issues: issue3.issues }, [...path31, ...issue3.path]);
16666
16666
  } else {
16667
- const fullpath = [...path30, ...issue3.path];
16667
+ const fullpath = [...path31, ...issue3.path];
16668
16668
  if (fullpath.length === 0) {
16669
16669
  fieldErrors._errors.push(mapper(issue3));
16670
16670
  } else {
@@ -24059,6 +24059,53 @@ async function backgroundServiceDefinition(source) {
24059
24059
  throw error3;
24060
24060
  }
24061
24061
  }
24062
+ function decodeXml(value) {
24063
+ return value.replace(/&(amp|lt|gt|quot|apos);/g, (entity) => {
24064
+ const entities = {
24065
+ "&": "&",
24066
+ "&lt;": "<",
24067
+ "&gt;": ">",
24068
+ "&quot;": '"',
24069
+ "&apos;": "'"
24070
+ };
24071
+ return entities[entity];
24072
+ });
24073
+ }
24074
+ function serviceDataDirectory(definition, platform) {
24075
+ let directory;
24076
+ if (platform === "darwin") {
24077
+ const value = /<string>--data-directory<\/string>\s*<string>([^<]*)<\/string>/.exec(
24078
+ definition
24079
+ )?.[1];
24080
+ if (value !== void 0) directory = decodeXml(value);
24081
+ } else if (platform === "linux") {
24082
+ const value = /^ExecStart=.*?"--data-directory" "((?:\\.|[^"\\])*)"\s*$/m.exec(
24083
+ definition
24084
+ )?.[1];
24085
+ if (value !== void 0)
24086
+ directory = value.replace(/\\([\\"])/g, "$1").replaceAll("%%", "%");
24087
+ } else if (platform === "win32") {
24088
+ const encoded = /-EncodedCommand ([A-Za-z0-9+/=]+)/.exec(definition)?.[1];
24089
+ const script = encoded === void 0 ? "" : Buffer.from(encoded, "base64").toString("utf16le");
24090
+ const args = /\$start\.Arguments = '((?:''|[^'])*)'/.exec(script)?.[1]?.replaceAll("''", "'");
24091
+ const value = /"--data-directory" "(.*)"$/.exec(args ?? "")?.[1];
24092
+ if (value !== void 0)
24093
+ directory = value.replace(
24094
+ /(\\+)"/g,
24095
+ (_, slashes) => `${slashes.slice(0, (slashes.length - 1) / 2)}"`
24096
+ ).replace(
24097
+ /(\\+)$/,
24098
+ (_, slashes) => slashes.slice(0, slashes.length / 2)
24099
+ );
24100
+ }
24101
+ if (!directory || !(platform === "win32" ? path20.win32 : path20.posix).isAbsolute(directory))
24102
+ throw new Error("Could not read the collector service data directory.");
24103
+ return directory;
24104
+ }
24105
+ async function backgroundServiceDataDirectory(source) {
24106
+ const definition = await backgroundServiceDefinition(source);
24107
+ return definition === null ? null : serviceDataDirectory(definition, process.platform);
24108
+ }
24062
24109
  async function installedServiceSources() {
24063
24110
  const definitions = await Promise.all(
24064
24111
  COLLECTOR_SOURCES2.map(backgroundServiceDefinition)
@@ -24713,6 +24760,9 @@ var consentSchema = object3({
24713
24760
  function consentPath() {
24714
24761
  return path24.join(os8.homedir(), ".config", "iteration", "auto-connect.json");
24715
24762
  }
24763
+ async function removeMachineConsent() {
24764
+ await fs19.rm(consentPath(), { force: true });
24765
+ }
24716
24766
  async function detectedSources(sourceHomes) {
24717
24767
  const detected = [];
24718
24768
  for (const source of COLLECTOR_SOURCES2) {
@@ -25289,6 +25339,147 @@ async function importSourceHistory(config3, paths, confirmed, control = backgrou
25289
25339
  return { imported: true, size, backgroundRunning: wasRunning };
25290
25340
  }
25291
25341
 
25342
+ // src/logout.ts
25343
+ import { randomUUID as randomUUID3 } from "node:crypto";
25344
+ import { promises as fs20 } from "node:fs";
25345
+ import os9 from "node:os";
25346
+ import path26 from "node:path";
25347
+ async function pendingDirectories(filename) {
25348
+ let contents;
25349
+ try {
25350
+ contents = await fs20.readFile(filename, "utf8");
25351
+ } catch (error3) {
25352
+ if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25353
+ return [];
25354
+ throw error3;
25355
+ }
25356
+ const parsed = array2(string5().refine(path26.isAbsolute)).max(64).safeParse(JSON.parse(contents));
25357
+ if (!parsed.success)
25358
+ throw new Error("Pending logout directories are invalid.");
25359
+ return parsed.data;
25360
+ }
25361
+ async function savePendingDirectories(filename, directories) {
25362
+ await ensurePrivateDirectory(path26.dirname(filename));
25363
+ const temporary = `${filename}.${process.pid}.tmp`;
25364
+ await fs20.writeFile(temporary, JSON.stringify([...directories]), {
25365
+ mode: 384
25366
+ });
25367
+ await fs20.rename(temporary, filename);
25368
+ }
25369
+ async function clearConnection(directory) {
25370
+ const paths = collectorPaths(directory);
25371
+ let names;
25372
+ try {
25373
+ names = await fs20.readdir(directory);
25374
+ } catch (error3) {
25375
+ if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25376
+ return;
25377
+ throw error3;
25378
+ }
25379
+ const release = await waitForCollectorLock(paths);
25380
+ try {
25381
+ for (const name of names)
25382
+ if (/^config\.json\.\d+\.tmp$/.test(name))
25383
+ await fs20.rm(path26.join(directory, name), { force: true });
25384
+ if (!names.includes(path26.basename(paths.config))) return;
25385
+ const config3 = await loadConfig(paths);
25386
+ if (config3.destination === "vault")
25387
+ throw new Error("This directory belongs to Trace Vault, not Iteration.");
25388
+ if ("cursorHome" in config3)
25389
+ await uninstallCursorHooks(config3.cursorHome, config3.dataDirectory);
25390
+ else await uninstallTurnSummary(config3);
25391
+ const retained = [
25392
+ paths.state,
25393
+ paths.outbox,
25394
+ paths.redactionMarker,
25395
+ paths.historyBackfill,
25396
+ paths.health,
25397
+ ...[
25398
+ "cursor-hooks",
25399
+ "cursor-hook-failure.json",
25400
+ "claude-git",
25401
+ "turn-summary",
25402
+ "codex-billing-sync.json",
25403
+ "cursor-billing-sync.json"
25404
+ ].map((name) => path26.join(directory, name))
25405
+ ];
25406
+ const archive = path26.join(directory, "logged-out", randomUUID3());
25407
+ await ensurePrivateDirectory(archive);
25408
+ for (const filename of retained) {
25409
+ try {
25410
+ await fs20.rename(filename, path26.join(archive, path26.basename(filename)));
25411
+ } catch (error3) {
25412
+ if (!(error3 instanceof Error && "code" in error3 && error3.code === "ENOENT"))
25413
+ throw error3;
25414
+ }
25415
+ }
25416
+ await fs20.rm(paths.config);
25417
+ } finally {
25418
+ await release();
25419
+ }
25420
+ }
25421
+ async function logout(extraDirectory) {
25422
+ requireCollectorPlatform();
25423
+ const pending = path26.join(
25424
+ os9.homedir(),
25425
+ ".config",
25426
+ "iteration",
25427
+ "logout-pending.json"
25428
+ );
25429
+ const directories = /* @__PURE__ */ new Set([
25430
+ ...COLLECTOR_SOURCES2.map(defaultCollectorDirectory),
25431
+ ...await pendingDirectories(pending)
25432
+ ]);
25433
+ if (extraDirectory !== void 0)
25434
+ directories.add(path26.resolve(extraDirectory));
25435
+ const failures = [];
25436
+ try {
25437
+ await removeMachineConsent();
25438
+ } catch (error3) {
25439
+ failures.push(
25440
+ `Automatic connection approval: ${error3 instanceof Error ? error3.message : "Could not remove approval."}`
25441
+ );
25442
+ }
25443
+ const unreadableServices = /* @__PURE__ */ new Set();
25444
+ for (const source of COLLECTOR_SOURCES2) {
25445
+ try {
25446
+ const directory = await backgroundServiceDataDirectory(source);
25447
+ if (directory !== null) directories.add(directory);
25448
+ } catch (error3) {
25449
+ unreadableServices.add(source);
25450
+ failures.push(
25451
+ `${source}: ${error3 instanceof Error ? error3.message : "Could not inspect collector startup."}`
25452
+ );
25453
+ }
25454
+ }
25455
+ await savePendingDirectories(pending, directories);
25456
+ for (const source of COLLECTOR_SOURCES2) {
25457
+ if (unreadableServices.has(source)) continue;
25458
+ try {
25459
+ await uninstallBackgroundService(source);
25460
+ } catch (error3) {
25461
+ failures.push(
25462
+ `${source}: ${error3 instanceof Error ? error3.message : "Could not stop collector startup."}`
25463
+ );
25464
+ }
25465
+ }
25466
+ for (const directory of directories) {
25467
+ try {
25468
+ await clearConnection(directory);
25469
+ } catch (error3) {
25470
+ failures.push(
25471
+ `${directory}: ${error3 instanceof Error ? error3.message : "Could not clear the collector connection."}`
25472
+ );
25473
+ }
25474
+ }
25475
+ if (failures.length > 0)
25476
+ throw new Error(
25477
+ `Logout did not finish. Resolve these errors and run iteration logout again:
25478
+ ${failures.join("\n")}`
25479
+ );
25480
+ await fs20.rm(pending);
25481
+ }
25482
+
25292
25483
  // src/runtime-services.ts
25293
25484
  async function refreshBackgroundServices(restart) {
25294
25485
  const refreshed = [];
@@ -25317,8 +25508,8 @@ import { setTimeout as delay5 } from "node:timers/promises";
25317
25508
 
25318
25509
  // src/codex-billing.ts
25319
25510
  import { createHash as createHash7 } from "node:crypto";
25320
- import { promises as fs20 } from "node:fs";
25321
- import path26 from "node:path";
25511
+ import { promises as fs21 } from "node:fs";
25512
+ import path27 from "node:path";
25322
25513
  var TASK_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage/thread_usage/query_v2";
25323
25514
  var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
25324
25515
  var DAY_MS = 24 * 60 * 60 * 1e3;
@@ -25370,7 +25561,7 @@ function fingerprint(value) {
25370
25561
  async function readAuth(codexHome) {
25371
25562
  let contents;
25372
25563
  try {
25373
- contents = await fs20.readFile(path26.join(codexHome, "auth.json"), "utf8");
25564
+ contents = await fs21.readFile(path27.join(codexHome, "auth.json"), "utf8");
25374
25565
  } catch (error3) {
25375
25566
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25376
25567
  return {
@@ -25405,7 +25596,7 @@ async function readAuth(codexHome) {
25405
25596
  async function readState(filename) {
25406
25597
  let contents;
25407
25598
  try {
25408
- contents = await fs20.readFile(filename, "utf8");
25599
+ contents = await fs21.readFile(filename, "utf8");
25409
25600
  } catch (error3) {
25410
25601
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25411
25602
  return null;
@@ -25427,7 +25618,7 @@ async function readState(filename) {
25427
25618
  }
25428
25619
  async function saveState2(filename, state) {
25429
25620
  const temporary = `${filename}.${process.pid}.tmp`;
25430
- const handle = await fs20.open(temporary, "w", 384);
25621
+ const handle = await fs21.open(temporary, "w", 384);
25431
25622
  try {
25432
25623
  await handle.writeFile(`${JSON.stringify(state)}
25433
25624
  `);
@@ -25435,11 +25626,11 @@ async function saveState2(filename, state) {
25435
25626
  } finally {
25436
25627
  await handle.close();
25437
25628
  }
25438
- await fs20.rename(temporary, filename);
25629
+ await fs21.rename(temporary, filename);
25439
25630
  }
25440
25631
  async function localThread(filename) {
25441
- const stat = await fs20.stat(filename);
25442
- const handle = await fs20.open(filename, "r");
25632
+ const stat = await fs21.stat(filename);
25633
+ const handle = await fs21.open(filename, "r");
25443
25634
  let header = null;
25444
25635
  try {
25445
25636
  const buffer = Buffer.alloc(64 * 1024);
@@ -25539,9 +25730,9 @@ function parseCodexAuthLog(threadId, body) {
25539
25730
  return { threadId, turnId: turn[1], mode };
25540
25731
  }
25541
25732
  async function localAuthTurns(codexHome, modelProviders) {
25542
- const filename = path26.join(codexHome, "logs_2.sqlite");
25733
+ const filename = path27.join(codexHome, "logs_2.sqlite");
25543
25734
  try {
25544
- await fs20.access(filename);
25735
+ await fs21.access(filename);
25545
25736
  } catch (error3) {
25546
25737
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25547
25738
  return [];
@@ -25634,7 +25825,7 @@ async function queryTaskUsage(tasks, accessToken, accountId, signal) {
25634
25825
  }
25635
25826
  async function syncCodexBilling(config3, fetcher = collectorFetch, signal) {
25636
25827
  const auth = await readAuth(config3.codexHome);
25637
- const stateFile = path26.join(config3.dataDirectory, "codex-billing-sync.json");
25828
+ const stateFile = path27.join(config3.dataDirectory, "codex-billing-sync.json");
25638
25829
  const prior = await readState(stateFile);
25639
25830
  const nowMs = Date.now();
25640
25831
  const history = prior === null || prior.accountFingerprint !== auth.accountFingerprint;
@@ -25805,9 +25996,9 @@ async function syncCodexBilling(config3, fetcher = collectorFetch, signal) {
25805
25996
 
25806
25997
  // src/cursor-billing.ts
25807
25998
  import { createHash as createHash8 } from "node:crypto";
25808
- import { promises as fs21 } from "node:fs";
25809
- import os9 from "node:os";
25810
- import path27 from "node:path";
25999
+ import { promises as fs22 } from "node:fs";
26000
+ import os10 from "node:os";
26001
+ import path28 from "node:path";
25811
26002
  var CURSOR_USAGE_URL = "https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents";
25812
26003
  var PAGE_SIZE = 500;
25813
26004
  var MAX_PAGES = 1e3;
@@ -25838,18 +26029,18 @@ var usagePageSchema = object3({
25838
26029
  var emptyUsagePageSchema = object3({}).strict();
25839
26030
  function cursorAuthDatabase() {
25840
26031
  if (process.platform === "darwin")
25841
- return path27.join(
25842
- os9.homedir(),
26032
+ return path28.join(
26033
+ os10.homedir(),
25843
26034
  "Library/Application Support/Cursor/User/globalStorage/state.vscdb"
25844
26035
  );
25845
26036
  if (process.platform === "linux")
25846
- return path27.join(
25847
- os9.homedir(),
26037
+ return path28.join(
26038
+ os10.homedir(),
25848
26039
  ".config/Cursor/User/globalStorage/state.vscdb"
25849
26040
  );
25850
26041
  if (process.platform === "win32")
25851
- return path27.join(
25852
- process.env.APPDATA ?? path27.join(os9.homedir(), "AppData", "Roaming"),
26042
+ return path28.join(
26043
+ process.env.APPDATA ?? path28.join(os10.homedir(), "AppData", "Roaming"),
25853
26044
  "Cursor/User/globalStorage/state.vscdb"
25854
26045
  );
25855
26046
  return null;
@@ -25891,7 +26082,7 @@ async function readCursorAuth() {
25891
26082
  async function loadBillingState(filename) {
25892
26083
  let contents;
25893
26084
  try {
25894
- contents = await fs21.readFile(filename, "utf8");
26085
+ contents = await fs22.readFile(filename, "utf8");
25895
26086
  } catch (error3) {
25896
26087
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
25897
26088
  return null;
@@ -25907,7 +26098,7 @@ async function loadBillingState(filename) {
25907
26098
  }
25908
26099
  async function saveBillingState(filename, state) {
25909
26100
  const temporary = `${filename}.${process.pid}.tmp`;
25910
- const handle = await fs21.open(temporary, "w", 384);
26101
+ const handle = await fs22.open(temporary, "w", 384);
25911
26102
  try {
25912
26103
  await handle.writeFile(`${JSON.stringify(state)}
25913
26104
  `, "utf8");
@@ -25915,7 +26106,7 @@ async function saveBillingState(filename, state) {
25915
26106
  } finally {
25916
26107
  await handle.close();
25917
26108
  }
25918
- await fs21.rename(temporary, filename);
26109
+ await fs22.rename(temporary, filename);
25919
26110
  }
25920
26111
  function planCursorBillingSync(state, accountFingerprint, nowMs) {
25921
26112
  if (state === null || state.accountFingerprint !== accountFingerprint)
@@ -25937,7 +26128,7 @@ function planCursorBillingSync(state, accountFingerprint, nowMs) {
25937
26128
  }
25938
26129
  function cursorConversationIds(filenames) {
25939
26130
  return new Set(
25940
- filenames.map((filename) => path27.basename(filename, ".jsonl")).filter((id) => UUID3.test(id))
26131
+ filenames.map((filename) => path28.basename(filename, ".jsonl")).filter((id) => UUID3.test(id))
25941
26132
  );
25942
26133
  }
25943
26134
  function billingKind(kind) {
@@ -25995,11 +26186,11 @@ async function syncCursorBilling(config3, fetcher = collectorFetch, signal) {
25995
26186
  if (auth === null)
25996
26187
  return { sessions: 0, matched: 0, skipped: true, mode: null };
25997
26188
  const localConversations = cursorConversationIds(
25998
- await cursorFiles(path27.join(config3.cursorHome, "projects"))
26189
+ await cursorFiles(path28.join(config3.cursorHome, "projects"))
25999
26190
  );
26000
26191
  if (localConversations.size === 0)
26001
26192
  return { sessions: 0, matched: 0, skipped: true, mode: null };
26002
- const stateFile = path27.join(config3.dataDirectory, "cursor-billing-sync.json");
26193
+ const stateFile = path28.join(config3.dataDirectory, "cursor-billing-sync.json");
26003
26194
  const priorState = await loadBillingState(stateFile);
26004
26195
  const nowMs = Date.now();
26005
26196
  const plan = planCursorBillingSync(
@@ -26804,26 +26995,26 @@ function renderRuntime(summary) {
26804
26995
 
26805
26996
  // src/windows-supervisor.ts
26806
26997
  import { spawn } from "node:child_process";
26807
- import { promises as fs22 } from "node:fs";
26808
- import path28 from "node:path";
26998
+ import { promises as fs23 } from "node:fs";
26999
+ import path29 from "node:path";
26809
27000
  import { setTimeout as delay6 } from "node:timers/promises";
26810
27001
  var MAX_LOG_BYTES = 8 * 1024 * 1024;
26811
27002
  var RESTART_DELAY_MS = 5e3;
26812
27003
  async function appendLog(filename, bytes) {
26813
27004
  let offset = 0;
26814
27005
  while (offset < bytes.length) {
26815
- const size = await fs22.stat(filename).then((stat) => stat.size).catch((error3) => {
27006
+ const size = await fs23.stat(filename).then((stat) => stat.size).catch((error3) => {
26816
27007
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT")
26817
27008
  return 0;
26818
27009
  throw error3;
26819
27010
  });
26820
27011
  if (size >= MAX_LOG_BYTES) {
26821
- await fs22.rm(`${filename}.1`, { force: true });
26822
- await fs22.rename(filename, `${filename}.1`);
27012
+ await fs23.rm(`${filename}.1`, { force: true });
27013
+ await fs23.rename(filename, `${filename}.1`);
26823
27014
  continue;
26824
27015
  }
26825
27016
  const count = Math.min(bytes.length - offset, MAX_LOG_BYTES - size);
26826
- await fs22.appendFile(filename, bytes.subarray(offset, offset + count), {
27017
+ await fs23.appendFile(filename, bytes.subarray(offset, offset + count), {
26827
27018
  mode: 384
26828
27019
  });
26829
27020
  offset += count;
@@ -26839,7 +27030,7 @@ async function copyOutput(stream, filename) {
26839
27030
  async function superviseCollector(source, dataDirectory2) {
26840
27031
  const output = windowsOutputPath(source);
26841
27032
  const errors = windowsLogPath(source);
26842
- await ensurePrivateDirectory(path28.dirname(output));
27033
+ await ensurePrivateDirectory(path29.dirname(output));
26843
27034
  const controller = new AbortController();
26844
27035
  const stop = () => controller.abort();
26845
27036
  process.once("SIGTERM", stop);
@@ -26950,6 +27141,12 @@ function optionalString(values, name) {
26950
27141
  return parsed.data;
26951
27142
  }
26952
27143
  function usage(command) {
27144
+ if (command === "logout") {
27145
+ process.stdout.write(
27146
+ "Sign out of Iteration on this computer\n iteration logout\n\nStops all collectors and removes saved credentials and automatic connection approval.\nLocal pending data is archived and will not be uploaded after reconnecting.\nAgent session files and previously uploaded data are kept.\n\n --data-directory DIR Also clear a custom collector directory with no installed service\n\nRun iteration connect to sign in again.\n"
27147
+ );
27148
+ return;
27149
+ }
26953
27150
  if (command === "connect") {
26954
27151
  process.stdout.write(
26955
27152
  [
@@ -26986,6 +27183,7 @@ function usage(command) {
26986
27183
  " iteration status See scan, upload, and error details",
26987
27184
  " iteration connect codex|claude|cursor Connect one source (advanced)",
26988
27185
  " iteration disconnect codex|claude|cursor Stop a source",
27186
+ " iteration logout Sign out and stop all sources on this computer",
26989
27187
  " iteration update Update now (updates also install automatically)",
26990
27188
  " iteration update off|on Turn automatic updates off or on",
26991
27189
  " iteration version Show the installed version",
@@ -26996,7 +27194,7 @@ function usage(command) {
26996
27194
  }
26997
27195
  async function fileExists(filename) {
26998
27196
  try {
26999
- await fs23.access(filename);
27197
+ await fs24.access(filename);
27000
27198
  return true;
27001
27199
  } catch (error3) {
27002
27200
  if (error3 instanceof Error && "code" in error3 && error3.code === "ENOENT") {
@@ -27046,7 +27244,7 @@ ${missing2.map(collectorSourceLabel).join(" and ")} found on this computer but n
27046
27244
  await connectAll({});
27047
27245
  }
27048
27246
  function dataDirectory(source, values) {
27049
- return path29.resolve(
27247
+ return path30.resolve(
27050
27248
  optionalString(values, "data-directory") ?? defaultCollectorDirectory(source)
27051
27249
  );
27052
27250
  }
@@ -27067,12 +27265,12 @@ async function initialize(source, values) {
27067
27265
  requireCollectorPlatform();
27068
27266
  const endpoint = optionalString(values, "endpoint") ?? DEFAULT_INGEST_ENDPOINT;
27069
27267
  const endpointUrl = validateCollectorEndpoint(endpoint);
27070
- const deviceName = optionalString(values, "device-name") ?? os10.hostname();
27268
+ const deviceName = optionalString(values, "device-name") ?? os11.hostname();
27071
27269
  const organizationId = optionalString(values, "workspace");
27072
27270
  const directory = dataDirectory(source, values);
27073
27271
  const paths = collectorPaths(directory);
27074
- const selectedHome = path29.resolve(
27075
- source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ?? path29.join(os10.homedir(), ".codex") : source === "claude" ? optionalString(values, "claude-home") ?? process.env.CLAUDE_CONFIG_DIR ?? path29.join(os10.homedir(), ".claude") : optionalString(values, "cursor-home") ?? path29.join(os10.homedir(), ".cursor")
27272
+ const selectedHome = path30.resolve(
27273
+ source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ?? path30.join(os11.homedir(), ".codex") : source === "claude" ? optionalString(values, "claude-home") ?? process.env.CLAUDE_CONFIG_DIR ?? path30.join(os11.homedir(), ".claude") : optionalString(values, "cursor-home") ?? path30.join(os11.homedir(), ".cursor")
27076
27274
  );
27077
27275
  await assertFreshCollectorDirectory(paths, source);
27078
27276
  const preparedState = await prepareSourceInitialState(
@@ -27157,13 +27355,13 @@ async function connectAll(values) {
27157
27355
  }
27158
27356
  const configs = await Promise.all(COLLECTOR_SOURCES2.map(connectedConfig));
27159
27357
  const resolveSourceHomes = () => ({
27160
- codex: path29.resolve(
27358
+ codex: path30.resolve(
27161
27359
  optionalString(values, "codex-home") ?? (configs[0] && "codexHome" in configs[0] ? configs[0].codexHome : sourceHome("codex"))
27162
27360
  ),
27163
- claude: path29.resolve(
27361
+ claude: path30.resolve(
27164
27362
  optionalString(values, "claude-home") ?? (configs[1] && "claudeHome" in configs[1] ? configs[1].claudeHome : sourceHome("claude"))
27165
27363
  ),
27166
- cursor: path29.resolve(
27364
+ cursor: path30.resolve(
27167
27365
  optionalString(values, "cursor-home") ?? (configs[2] && "cursorHome" in configs[2] ? configs[2].cursorHome : sourceHome("cursor"))
27168
27366
  )
27169
27367
  });
@@ -27585,6 +27783,9 @@ async function dispatchCommand({
27585
27783
  if (command === "connect" && source === void 0) {
27586
27784
  return connectAll(values);
27587
27785
  }
27786
+ if (command === "logout") {
27787
+ return signOut(source, values);
27788
+ }
27588
27789
  if (command === "") {
27589
27790
  return showHome(values);
27590
27791
  }
@@ -27598,6 +27799,16 @@ async function dispatchCommand({
27598
27799
  if (command === "update") return update(source, values);
27599
27800
  return dispatchSourceCommand(command, source, values, selectedSource);
27600
27801
  }
27802
+ async function signOut(source, values) {
27803
+ if (source !== void 0 || Object.keys(values).some((name) => name !== "data-directory"))
27804
+ throw new Error(
27805
+ "Run iteration logout without a source. Only --data-directory is supported."
27806
+ );
27807
+ await logout(optionalString(values, "data-directory") ?? void 0);
27808
+ process.stdout.write(
27809
+ "Signed out of Iteration on this computer. Collection stopped and saved credentials removed.\nLocal pending data was archived. Agent sessions and uploaded data were kept.\nRun iteration connect to sign in again.\n"
27810
+ );
27811
+ }
27601
27812
  async function showHome(values) {
27602
27813
  if (Object.keys(values).length > 0) {
27603
27814
  throw new Error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iteration-cli",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,11 +24,11 @@
24
24
  "devDependencies": {
25
25
  "esbuild": "0.28.2",
26
26
  "zod": "4.6.6",
27
- "iteration-collector-contracts": "0.1.0",
28
27
  "iteration-trace-collector": "0.1.0",
29
- "iteration-trace-contracts": "0.12.0"
28
+ "iteration-trace-contracts": "0.12.0",
29
+ "iteration-collector-contracts": "0.1.0"
30
30
  },
31
31
  "scripts": {
32
- "build": "pnpm --filter iteration-trace-collector build && pnpm --filter iteration-collector-contracts build && tsc -p tsconfig.json --noEmit && node build.mjs"
32
+ "build": "tsc -p tsconfig.json --noEmit && node build.mjs"
33
33
  }
34
34
  }