iteration-cli 0.12.0 → 0.13.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/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # Iteration CLI
2
2
 
3
- Install one CLI for Iteration's supported coding-agent sources:
3
+ Connect a computer with the command shown on your Iteration workspace's overview:
4
+
5
+ ```sh
6
+ npx iteration-cli connect --workspace <workspace ID>
7
+ ```
8
+
9
+ `--workspace` connects to that workspace without a prompt. The collectors keep running from their own copy of the CLI, so the command does not need a global install. To keep the `iteration` command for status and later changes, install it globally:
4
10
 
5
11
  ```sh
6
12
  npm install --global iteration-cli
7
13
  iteration connect
8
14
  ```
9
15
 
10
- The collectors run on macOS or Linux with Node.js 22 or later. Cursor's local Agents window collector is tested on macOS. Linux needs systemd. The CLI automatically enables background collection after logout and installs a user service that starts at boot and restarts on failure. If server policy does not let the user enable this, an administrator must allow background services for the account. The setting applies to the whole Linux account and remains enabled after `iteration disconnect`; the CLI does not turn off other user services. `iteration connect` finds supported agents with local session directories, asks once before uploading their saved sessions and future activity, and connects them to one workspace. A connected collector checks for newly detected supported agents and connects them under that approval. Each source has its own background process, local checkpoint, and upload queue. Run `iteration` to see connected sources, `iteration status cursor` for Cursor details, and `iteration help` for other commands. Source-specific connect commands remain available for advanced setups.
16
+ The collectors run on macOS or Linux with Node.js 22 or later. Cursor's local Agents window collector is tested on macOS. Linux needs systemd. The CLI automatically enables background collection after logout and installs a user service that starts at boot and restarts on failure. If server policy does not let the user enable this, an administrator must allow background services for the account. The setting applies to the whole Linux account and remains enabled after `iteration disconnect`; the CLI does not turn off other user services. `iteration connect` finds supported agents with local session directories, shows how many saved sessions each one has, asks once before uploading them and future activity, and connects them to one workspace. Saved sessions import newest first, and each collector reports its import progress to Iteration until it completes. A connected collector checks for newly detected supported agents and connects them under that approval. Each source has its own background process, local checkpoint, and upload queue. Run `iteration` to see connected sources, `iteration status cursor` for Cursor details, and `iteration help` for other commands. Source-specific connect commands remain available for advanced setups.
11
17
 
12
18
  Cursor's **This Mac** Agents sessions are read from `~/.cursor/projects/*/agent-transcripts/<conversation>/<conversation>.jsonl`. Connecting Cursor also installs user-level hooks in `~/.cursor/hooks.json` automatically. They capture tool output, thought blocks, model and usage fields, subagent transcripts, and other lifecycle records that the main transcript omits; the collector queues those records locally before upload. Existing hooks are preserved, and `iteration disconnect cursor` removes only Iteration's entries. No Cursor approval or settings step is needed. A custom Cursor directory can be selected with `iteration connect cursor --cursor-home DIR`. Local history predating connection has transcript messages and tool calls, but tool results that Cursor never wrote to its transcript cannot be recovered. Cloud Agents are outside this local collector.
13
19
 
package/dist/cli.js CHANGED
@@ -7633,6 +7633,12 @@ var acknowledgementSchema = object({
7633
7633
  }).strict();
7634
7634
  var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
7635
7635
  var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
7636
+ var sessionCountSchema = number2().int().min(0).max(1e6);
7637
+ var collectorHistoryProgressSchema = object({
7638
+ sessions: sessionCountSchema,
7639
+ readSessions: sessionCountSchema,
7640
+ complete: boolean2()
7641
+ }).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.");
7636
7642
  var collectorEnrollmentRequestSchema = object({
7637
7643
  deviceName: string2().trim().min(1).max(160),
7638
7644
  organizationId: string2().regex(UUID_PATTERN).optional(),
@@ -7933,7 +7939,7 @@ async function clerkDeviceSignIn(configuration) {
7933
7939
  );
7934
7940
  const verificationUrl = challenge.verification_uri_complete ?? challenge.verification_uri;
7935
7941
  process.stdout.write(
7936
- "Sign in to Iteration to authorize this collector.\nCode: " + challenge.user_code + "\nLink: " + verificationUrl + "\n"
7942
+ "Sign in to Iteration in your browser and confirm this code.\nCode: " + challenge.user_code + "\nLink: " + verificationUrl + "\n"
7937
7943
  );
7938
7944
  await openBrowser(verificationUrl);
7939
7945
  return pollForAccessToken(
@@ -7981,7 +7987,7 @@ async function chooseOrganization(organizations, hasMore) {
7981
7987
  );
7982
7988
  }
7983
7989
  if (process.stdin.isTTY !== true) {
7984
- throw new Error("Run iteration interactively or pass --organization-id.");
7990
+ throw new Error("Run iteration interactively or pass --workspace.");
7985
7991
  }
7986
7992
  const prompt = createInterface({
7987
7993
  input: process.stdin,
@@ -15081,6 +15087,38 @@ async function claudeFiles(root) {
15081
15087
  }
15082
15088
  return files.sort();
15083
15089
  }
15090
+ function isSessionFile(filename) {
15091
+ return !filename.includes(`${path8.sep}subagents${path8.sep}`);
15092
+ }
15093
+ async function countSavedSessions(source, home) {
15094
+ return (await sourceFiles(source, home)).filter(isSessionFile).length;
15095
+ }
15096
+ async function sourceFilesNewestFirst(source, home) {
15097
+ const files = await sourceFiles(source, home);
15098
+ const listed = [];
15099
+ for (let start2 = 0; start2 < files.length; start2 += 64) {
15100
+ const batch = files.slice(start2, start2 + 64);
15101
+ const stats = await Promise.all(batch.map((filename) => fs8.stat(filename)));
15102
+ for (const [index, stat] of stats.entries()) {
15103
+ const filename = batch[index];
15104
+ if (filename === void 0) continue;
15105
+ listed.push({
15106
+ filename,
15107
+ size: stat.size,
15108
+ modifiedMs: stat.mtimeMs,
15109
+ session: isSessionFile(filename)
15110
+ });
15111
+ }
15112
+ }
15113
+ listed.sort(
15114
+ (left, right) => right.modifiedMs - left.modifiedMs || left.filename.localeCompare(right.filename)
15115
+ );
15116
+ return listed.map(({ filename, size, session }) => ({
15117
+ filename,
15118
+ size,
15119
+ session
15120
+ }));
15121
+ }
15084
15122
  function sourceFiles(source, home) {
15085
15123
  const directory = path8.resolve(home);
15086
15124
  if (source === "codex") return rolloutFiles(path8.join(directory, "sessions"));
@@ -17021,9 +17059,13 @@ async function scanSourceOnce(config2, paths, signal) {
17021
17059
  }
17022
17060
  const cursors = { ...state.cursors };
17023
17061
  const skipping = new Set(state.skipping);
17062
+ const sessionFiles = await sourceFilesNewestFirst(source, sourceDirectory);
17063
+ const listedSizes = new Map(
17064
+ sessionFiles.map((file) => [file.filename, file.size])
17065
+ );
17024
17066
  const files = [
17025
17067
  ...source === "claude" ? await claudeGitHookFiles(config2.dataDirectory) : [],
17026
- ...await sourceFiles(source, sourceDirectory),
17068
+ ...sessionFiles.map((file) => file.filename),
17027
17069
  ...source === "cursor" ? await cursorHookFiles(config2.dataDirectory) : []
17028
17070
  ];
17029
17071
  const completedHooks = [];
@@ -17039,15 +17081,15 @@ async function scanSourceOnce(config2, paths, signal) {
17039
17081
  caughtUp = false;
17040
17082
  break;
17041
17083
  }
17042
- const stats = await fs13.stat(filename);
17084
+ const size = listedSizes.get(filename) ?? (await fs13.stat(filename)).size;
17043
17085
  let offset = cursors[filename] ?? 0;
17044
- if (offset > stats.size) {
17086
+ if (offset > size) {
17045
17087
  logGap(filename, offset, "rollout_file_shrank_resetting_cursor");
17046
17088
  offset = 0;
17047
17089
  skipping.delete(filename);
17048
17090
  }
17049
17091
  const bytesToRead = Math.min(
17050
- stats.size - offset,
17092
+ size - offset,
17051
17093
  MAX_SCAN_BYTES_PER_FILE,
17052
17094
  remainingBudget
17053
17095
  );
@@ -17067,7 +17109,7 @@ async function scanSourceOnce(config2, paths, signal) {
17067
17109
  } finally {
17068
17110
  await handle.close();
17069
17111
  }
17070
- if (readEnd < stats.size) caughtUp = false;
17112
+ if (readEnd < size) caughtUp = false;
17071
17113
  if (skipping.has(filename)) {
17072
17114
  const newline = buffer.indexOf(10);
17073
17115
  if (newline < 0) {
@@ -17130,7 +17172,7 @@ async function scanSourceOnce(config2, paths, signal) {
17130
17172
  lineStart = buffer.length;
17131
17173
  }
17132
17174
  cursors[filename] = offset + lineStart;
17133
- if ((source === "cursor" && filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) || source === "claude" && filename.includes(`${path16.sep}git-queue${path16.sep}`)) && cursors[filename] === stats.size)
17175
+ if ((source === "cursor" && filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) || source === "claude" && filename.includes(`${path16.sep}git-queue${path16.sep}`)) && cursors[filename] === size)
17134
17176
  completedHooks.push(filename);
17135
17177
  }
17136
17178
  await appendEvents(events, paths);
@@ -17147,7 +17189,17 @@ async function scanSourceOnce(config2, paths, signal) {
17147
17189
  },
17148
17190
  paths
17149
17191
  );
17150
- return { discovered: events.length, caughtUp };
17192
+ const sessions = sessionFiles.filter((file) => file.session);
17193
+ return {
17194
+ discovered: events.length,
17195
+ caughtUp,
17196
+ history: {
17197
+ sessions: sessions.length,
17198
+ readSessions: sessions.filter(
17199
+ (file) => (cursors[file.filename] ?? 0) >= file.size
17200
+ ).length
17201
+ }
17202
+ };
17151
17203
  }
17152
17204
  async function uploadPendingOnce(config2, paths, fetcher = collectorFetch, signal, onAcknowledged) {
17153
17205
  let uploaded = 0;
@@ -17217,7 +17269,7 @@ async function collectorTick(config2, paths, fetcher = collectorFetch, signal, o
17217
17269
  signal,
17218
17270
  onAcknowledged
17219
17271
  );
17220
- const scan = await outboxSize(paths) >= SCAN_PAUSE_OUTBOX_BYTES ? { discovered: 0, caughtUp: false } : await scanSourceOnce(config2, paths, signal);
17272
+ const scan = await outboxSize(paths) >= SCAN_PAUSE_OUTBOX_BYTES ? { discovered: 0, caughtUp: false, history: null } : await scanSourceOnce(config2, paths, signal);
17221
17273
  uploaded += await uploadPendingOnce(
17222
17274
  config2,
17223
17275
  paths,
@@ -17229,10 +17281,66 @@ async function collectorTick(config2, paths, fetcher = collectorFetch, signal, o
17229
17281
  discovered: scan.discovered,
17230
17282
  uploaded,
17231
17283
  outboxBytes: await outboxSize(paths),
17232
- historyCaughtUp: scan.caughtUp
17284
+ historyCaughtUp: scan.caughtUp,
17285
+ history: scan.history
17233
17286
  };
17234
17287
  }
17235
17288
 
17289
+ // src/history-progress.ts
17290
+ var REQUEST_TIMEOUT_MS3 = 15e3;
17291
+ var REPORT_INTERVAL_MS = 1e4;
17292
+ var HistoryProgressError = class extends Error {
17293
+ constructor(message, options) {
17294
+ super(message, options);
17295
+ this.name = "HistoryProgressError";
17296
+ }
17297
+ };
17298
+ function unchanged(left, right) {
17299
+ return right !== null && left.sessions === right.sessions && left.readSessions === right.readSessions && left.complete === right.complete;
17300
+ }
17301
+ var HistoryProgressReporter = class {
17302
+ #sent = null;
17303
+ #sentAtMs = Number.NEGATIVE_INFINITY;
17304
+ #completed = false;
17305
+ async report(config2, progress, fetcher, signal, nowMs) {
17306
+ if (this.#completed || unchanged(progress, this.#sent)) return;
17307
+ if (!progress.complete && nowMs - this.#sentAtMs < REPORT_INTERVAL_MS)
17308
+ return;
17309
+ let response;
17310
+ try {
17311
+ response = await fetcher(
17312
+ new URL("/api/traces/collector-progress", config2.endpoint),
17313
+ {
17314
+ method: "POST",
17315
+ headers: {
17316
+ authorization: `Bearer ${config2.token}`,
17317
+ "content-type": "application/json"
17318
+ },
17319
+ body: JSON.stringify(progress),
17320
+ signal: AbortSignal.any([
17321
+ signal,
17322
+ AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
17323
+ ])
17324
+ }
17325
+ );
17326
+ } catch (error2) {
17327
+ if (signal.aborted) throw error2;
17328
+ throw new HistoryProgressError(
17329
+ "History progress could not reach Iteration.",
17330
+ { cause: error2 }
17331
+ );
17332
+ }
17333
+ if (response.status !== 204) {
17334
+ throw new HistoryProgressError(
17335
+ `History progress failed with HTTP ${response.status}.`
17336
+ );
17337
+ }
17338
+ this.#sent = progress;
17339
+ this.#sentAtMs = nowMs;
17340
+ this.#completed = progress.complete;
17341
+ }
17342
+ };
17343
+
17236
17344
  // src/cursor-billing.ts
17237
17345
  import { createHash as createHash7 } from "node:crypto";
17238
17346
  import { promises as fs14 } from "node:fs";
@@ -18172,6 +18280,7 @@ async function runCollector(directory, expectedSource) {
18172
18280
  const summariesRefreshedAt = /* @__PURE__ */ new Map();
18173
18281
  let lastAutoCheckAt = 0;
18174
18282
  let lastBillingSyncAt = 0;
18283
+ const historyProgress = new HistoryProgressReporter();
18175
18284
  const runtimeVersion = process.env.ITERATION_RUNTIME_VERSION;
18176
18285
  let lastRuntimeCheckAt = 0;
18177
18286
  let trialSettled = runtimeVersion === void 0;
@@ -18234,6 +18343,7 @@ async function runCollector(directory, expectedSource) {
18234
18343
  }
18235
18344
  let acknowledgedAtMs = null;
18236
18345
  let billingReady = false;
18346
+ let history = null;
18237
18347
  try {
18238
18348
  const result = await collectorTick(
18239
18349
  config2,
@@ -18245,6 +18355,12 @@ async function runCollector(directory, expectedSource) {
18245
18355
  }
18246
18356
  );
18247
18357
  billingReady = result.historyCaughtUp && result.outboxBytes === 0;
18358
+ if (result.history !== null) {
18359
+ history = {
18360
+ ...result.history,
18361
+ complete: billingReady && result.history.readSessions === result.history.sessions
18362
+ };
18363
+ }
18248
18364
  if (acknowledgedAtMs !== null || health.lastFailure !== null || result.historyCaughtUp && health.historyCaughtUpAtMs === null) {
18249
18365
  health = {
18250
18366
  lastUploadAtMs: acknowledgedAtMs ?? health.lastUploadAtMs,
@@ -18297,6 +18413,30 @@ async function runCollector(directory, expectedSource) {
18297
18413
  await persistHealth(health, paths);
18298
18414
  retryDelayMs = Math.min(retryDelayMs * 2, 6e4);
18299
18415
  }
18416
+ if (history !== null) {
18417
+ try {
18418
+ await historyProgress.report(
18419
+ config2,
18420
+ history,
18421
+ collectorFetch,
18422
+ controller.signal,
18423
+ Date.now()
18424
+ );
18425
+ } catch (error2) {
18426
+ if (controller.signal.aborted) break;
18427
+ console.error(
18428
+ JSON.stringify({
18429
+ level: "warning",
18430
+ at: (/* @__PURE__ */ new Date()).toISOString(),
18431
+ source,
18432
+ operation: "report_history_progress",
18433
+ errorName: error2 instanceof Error ? error2.name : "UnknownError",
18434
+ causes: errorCauses(error2),
18435
+ message: error2 instanceof Error ? error2.message : "Unknown history progress failure."
18436
+ })
18437
+ );
18438
+ }
18439
+ }
18300
18440
  try {
18301
18441
  if (source !== "cursor")
18302
18442
  await refreshTurnSummaries(
@@ -18753,7 +18893,7 @@ function argumentsForCli() {
18753
18893
  help: { type: "boolean" },
18754
18894
  endpoint: { type: "string" },
18755
18895
  "device-name": { type: "string" },
18756
- "organization-id": { type: "string" },
18896
+ workspace: { type: "string" },
18757
18897
  "codex-home": { type: "string" },
18758
18898
  "claude-home": { type: "string" },
18759
18899
  "cursor-home": { type: "string" },
@@ -18797,7 +18937,7 @@ function usage(command) {
18797
18937
  "",
18798
18938
  "Options for advanced setups:",
18799
18939
  " --device-name NAME Name this computer in Iteration",
18800
- " --organization-id UUID Select a workspace without a prompt",
18940
+ " --workspace ID Connect to this workspace without a prompt",
18801
18941
  " --codex-home DIR Use a different Codex data directory",
18802
18942
  " --claude-home DIR Use a different Claude data directory",
18803
18943
  " --cursor-home DIR Use a different Cursor data directory",
@@ -18902,7 +19042,7 @@ async function initialize(source, values) {
18902
19042
  const endpoint = optionalString(values, "endpoint") ?? DEFAULT_INGEST_ENDPOINT;
18903
19043
  const endpointUrl = validateCollectorEndpoint(endpoint);
18904
19044
  const deviceName = optionalString(values, "device-name") ?? os8.hostname();
18905
- const organizationId = optionalString(values, "organization-id");
19045
+ const organizationId = optionalString(values, "workspace");
18906
19046
  const directory = dataDirectory(source, values);
18907
19047
  const paths = collectorPaths(directory);
18908
19048
  const selectedHome = path21.resolve(
@@ -18949,13 +19089,15 @@ async function initialize(source, values) {
18949
19089
  }
18950
19090
  const notice = await turnSummaryNotice(config2);
18951
19091
  process.stdout.write(
18952
- `${collectorSourceLabel(source)} is connected to ${enrollment.organizationName}. Existing sessions are importing and new activity is collected automatically. Run iteration to check progress.${notice}
19092
+ `${collectorSourceLabel(source)} is connected to ${enrollment.organizationName}. Existing sessions are importing, newest first, and new activity is collected automatically. Iteration shows the import as it runs.${notice}
18953
19093
  `
18954
19094
  );
18955
19095
  return config2;
18956
19096
  }
18957
19097
  async function approveAllSources(sources, workspaceName, yes) {
18958
- const names = sources.map(collectorSourceLabel).join(" and ");
19098
+ const names = sources.map(
19099
+ ({ source, sessions }) => `${collectorSourceLabel(source)} (${sessions.toLocaleString("en-US")} saved ${sessions === 1 ? "session" : "sessions"})`
19100
+ ).join(" and ");
18959
19101
  const safeName = safeWorkspaceName(workspaceName);
18960
19102
  process.stdout.write(
18961
19103
  `Detected ${names || "no saved agent sessions"} on this computer.
@@ -19032,14 +19174,19 @@ async function connectAll(values) {
19032
19174
  );
19033
19175
  }
19034
19176
  }
19035
- const requestedOrganizationId = optionalString(values, "organization-id");
19177
+ const requestedOrganizationId = optionalString(values, "workspace");
19036
19178
  if (requestedOrganizationId && existingScopes[0] && requestedOrganizationId !== existingScopes[0].scope.organizationId) {
19037
19179
  throw new Error("The existing collector belongs to a different workspace.");
19038
19180
  }
19039
19181
  const previousConsent = await loadMachineConsent();
19040
19182
  if (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) {
19041
19183
  await approveAllSources(
19042
- detected,
19184
+ await Promise.all(
19185
+ detected.map(async (source) => ({
19186
+ source,
19187
+ sessions: await countSavedSessions(source, sourceHomes[source])
19188
+ }))
19189
+ ),
19043
19190
  existingScopes[0]?.scope.organizationName ?? null,
19044
19191
  values.yes === true
19045
19192
  );
@@ -7435,6 +7435,12 @@ var acknowledgementSchema = object({
7435
7435
  }).strict();
7436
7436
  var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
7437
7437
  var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
7438
+ var sessionCountSchema = number2().int().min(0).max(1e6);
7439
+ var collectorHistoryProgressSchema = object({
7440
+ sessions: sessionCountSchema,
7441
+ readSessions: sessionCountSchema,
7442
+ complete: boolean2()
7443
+ }).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.");
7438
7444
  var collectorEnrollmentRequestSchema = object({
7439
7445
  deviceName: string2().trim().min(1).max(160),
7440
7446
  organizationId: string2().regex(UUID_PATTERN).optional(),
package/dist/launcher.js CHANGED
@@ -6511,6 +6511,12 @@ var acknowledgementSchema = object({
6511
6511
  }).strict();
6512
6512
  var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
6513
6513
  var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
6514
+ var sessionCountSchema = number2().int().min(0).max(1e6);
6515
+ var collectorHistoryProgressSchema = object({
6516
+ sessions: sessionCountSchema,
6517
+ readSessions: sessionCountSchema,
6518
+ complete: boolean2()
6519
+ }).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.");
6514
6520
  var collectorEnrollmentRequestSchema = object({
6515
6521
  deviceName: string2().trim().min(1).max(160),
6516
6522
  organizationId: string2().regex(UUID_PATTERN).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iteration-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  "@secretlint/secretlint-rule-preset-recommend": "13.0.6",
23
23
  "esbuild": "0.28.2",
24
24
  "zod": "4.6.4",
25
- "iteration-trace-contracts": "0.9.0"
25
+ "iteration-trace-contracts": "0.10.0"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "pnpm --filter iteration-trace-contracts build && tsc -p tsconfig.json --noEmit && node build.mjs"