iteration-cli 0.12.0 → 0.14.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/launcher.js CHANGED
@@ -6,13 +6,13 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/launcher.ts
9
- import { promises as fs2 } from "node:fs";
9
+ import { promises as fs3 } from "node:fs";
10
10
  import { pathToFileURL } from "node:url";
11
11
 
12
12
  // src/runtime.ts
13
- import { promises as fs } from "node:fs";
13
+ import { promises as fs2 } from "node:fs";
14
14
  import os from "node:os";
15
- import path from "node:path";
15
+ import path2 from "node:path";
16
16
  import { setTimeout as delay } from "node:timers/promises";
17
17
 
18
18
  // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/util.js
@@ -251,10 +251,10 @@ function mergeDefs(...defs) {
251
251
  function cloneDef(schema) {
252
252
  return mergeDefs(schema._zod.def);
253
253
  }
254
- function getElementAtPath(obj, path2) {
255
- if (!path2)
254
+ function getElementAtPath(obj, path3) {
255
+ if (!path3)
256
256
  return obj;
257
- return path2.reduce((acc, key) => acc?.[key], obj);
257
+ return path3.reduce((acc, key) => acc?.[key], obj);
258
258
  }
259
259
  function promiseAllObject(promisesObj) {
260
260
  const keys = Object.keys(promisesObj);
@@ -594,11 +594,11 @@ function explicitlyAborted(x, startIndex = 0) {
594
594
  }
595
595
  return false;
596
596
  }
597
- function prefixIssues(path2, issues) {
597
+ function prefixIssues(path3, issues) {
598
598
  return issues.map((iss) => {
599
599
  var _a3;
600
600
  (_a3 = iss).path ?? (_a3.path = []);
601
- iss.path.unshift(path2);
601
+ iss.path.unshift(path3);
602
602
  return iss;
603
603
  });
604
604
  }
@@ -1044,16 +1044,16 @@ function flattenError(error2, mapper = (issue2) => issue2.message) {
1044
1044
  }
1045
1045
  function formatError(error2, mapper = (issue2) => issue2.message) {
1046
1046
  const fieldErrors = { _errors: [] };
1047
- const processError = (error3, path2 = []) => {
1047
+ const processError = (error3, path3 = []) => {
1048
1048
  for (const issue2 of error3.issues) {
1049
1049
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1050
- issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
1050
+ issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
1051
1051
  } else if (issue2.code === "invalid_key") {
1052
- processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
1052
+ processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
1053
1053
  } else if (issue2.code === "invalid_element") {
1054
- processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
1054
+ processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
1055
1055
  } else {
1056
- const fullpath = [...path2, ...issue2.path];
1056
+ const fullpath = [...path3, ...issue2.path];
1057
1057
  if (fullpath.length === 0) {
1058
1058
  fieldErrors._errors.push(mapper(issue2));
1059
1059
  } else {
@@ -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(),
@@ -6539,19 +6545,149 @@ var collectorEnrollmentResponseSchema = discriminatedUnion("status", [
6539
6545
  }).strict()
6540
6546
  ]);
6541
6547
 
6548
+ // src/private-storage.ts
6549
+ import { promises as fs } from "node:fs";
6550
+
6551
+ // src/windows-powershell.ts
6552
+ import { execFile as execFileCallback } from "node:child_process";
6553
+ import path from "node:path";
6554
+ import { promisify } from "node:util";
6555
+ var execFile = promisify(execFileCallback);
6556
+ function windowsPowerShellPath() {
6557
+ return path.join(
6558
+ process.env.SystemRoot ?? "C:\\Windows",
6559
+ "System32",
6560
+ "WindowsPowerShell",
6561
+ "v1.0",
6562
+ "powershell.exe"
6563
+ );
6564
+ }
6565
+ function powershellLiteral(value) {
6566
+ return `'${value.replaceAll("'", "''")}'`;
6567
+ }
6568
+ function encodedPowerShell(script) {
6569
+ return Buffer.from(script, "utf16le").toString("base64");
6570
+ }
6571
+ async function runWindowsPowerShell(operation, script) {
6572
+ try {
6573
+ const { stdout } = await execFile(
6574
+ windowsPowerShellPath(),
6575
+ [
6576
+ "-NoLogo",
6577
+ "-NoProfile",
6578
+ "-NonInteractive",
6579
+ "-EncodedCommand",
6580
+ encodedPowerShell(
6581
+ `$ErrorActionPreference = 'Stop'
6582
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
6583
+ ${script}`
6584
+ )
6585
+ ],
6586
+ {
6587
+ timeout: 3e4,
6588
+ maxBuffer: 1024 * 1024,
6589
+ windowsHide: true
6590
+ }
6591
+ );
6592
+ return stdout.trim();
6593
+ } catch (error2) {
6594
+ const failure2 = object({ stderr: string2() }).safeParse(error2);
6595
+ const detail = failure2.success ? failure2.data.stderr.trim().slice(0, 4096) : "";
6596
+ throw new Error(
6597
+ `Windows could not ${operation}.${detail ? `
6598
+ ${detail}` : ""}`,
6599
+ { cause: error2 }
6600
+ );
6601
+ }
6602
+ }
6603
+
6604
+ // src/private-storage.ts
6605
+ var securedDirectories = /* @__PURE__ */ new Map();
6606
+ var MAX_SECURED_DIRECTORIES = 128;
6607
+ async function secureWindowsDirectory(directory) {
6608
+ await runWindowsPowerShell(
6609
+ "secure private collector storage",
6610
+ `
6611
+ $root = ${powershellLiteral(directory)}
6612
+ $owner = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
6613
+ $principals = @($owner, [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18'), [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544'))
6614
+ $pending = [System.Collections.Generic.Stack[string]]::new()
6615
+ $pending.Push($root)
6616
+ $count = 0
6617
+ while ($pending.Count -gt 0) {
6618
+ $entry = $pending.Pop()
6619
+ try {
6620
+ $item = Get-Item -LiteralPath $entry -Force
6621
+ if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'Private storage cannot contain filesystem links.' }
6622
+ if (++$count -gt 65536) { throw 'Private storage has too many entries to secure.' }
6623
+ if ($item.PSIsContainer) {
6624
+ $acl = [System.Security.AccessControl.DirectorySecurity]::new()
6625
+ $inheritance = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
6626
+ } else {
6627
+ $acl = [System.Security.AccessControl.FileSecurity]::new()
6628
+ $inheritance = [System.Security.AccessControl.InheritanceFlags]::None
6629
+ }
6630
+ $acl.SetOwner($owner)
6631
+ $acl.SetAccessRuleProtection($true, $false)
6632
+ foreach ($principal in $principals) {
6633
+ $acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($principal, 'FullControl', $inheritance, 'None', 'Allow'))
6634
+ }
6635
+ # Set-Acl may request audit privileges on a protected DACL. Persist only
6636
+ # the changed owner and access rules so repair needs no elevation.
6637
+ $item.SetAccessControl($acl)
6638
+ $actual = Get-Acl -LiteralPath $item.FullName
6639
+ if (!$actual.AreAccessRulesProtected -or $actual.GetOwner([System.Security.Principal.SecurityIdentifier]).Value -ne $owner.Value) { throw 'Private storage ACL verification failed.' }
6640
+ foreach ($rule in $actual.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) {
6641
+ if ($rule.AccessControlType -ne 'Allow' -or $principals.Value -notcontains $rule.IdentityReference.Value) { throw 'Private storage contains an unexpected access rule.' }
6642
+ }
6643
+ if ($item.PSIsContainer) {
6644
+ foreach ($child in [System.IO.Directory]::EnumerateFileSystemEntries($item.FullName)) {
6645
+ if ($count + $pending.Count -ge 65536) { throw 'Private storage has too many entries to secure.' }
6646
+ $pending.Push($child)
6647
+ }
6648
+ }
6649
+ } catch [System.Management.Automation.ItemNotFoundException] {
6650
+ if ($entry -eq $root) { throw }
6651
+ }
6652
+ }
6653
+ `
6654
+ );
6655
+ }
6656
+ async function ensurePrivateDirectory(directory) {
6657
+ await fs.mkdir(directory, { recursive: true, mode: 448 });
6658
+ if (process.platform !== "win32") {
6659
+ await fs.chmod(directory, 448);
6660
+ return;
6661
+ }
6662
+ const resolved = await fs.realpath(directory);
6663
+ let secured = securedDirectories.get(resolved);
6664
+ if (secured === void 0) {
6665
+ if (securedDirectories.size >= MAX_SECURED_DIRECTORIES) {
6666
+ const oldest = securedDirectories.keys().next().value;
6667
+ if (oldest !== void 0) securedDirectories.delete(oldest);
6668
+ }
6669
+ secured = secureWindowsDirectory(resolved).catch((error2) => {
6670
+ securedDirectories.delete(resolved);
6671
+ throw error2;
6672
+ });
6673
+ securedDirectories.set(resolved, secured);
6674
+ }
6675
+ await secured;
6676
+ }
6677
+
6542
6678
  // src/runtime.ts
6543
6679
  function runtimePaths(home = os.homedir()) {
6544
- const root = path.join(home, ".config", "iteration", "runtime");
6680
+ const root = path2.join(home, ".config", "iteration", "runtime");
6545
6681
  return {
6546
6682
  home,
6547
6683
  root,
6548
- versions: path.join(root, "versions"),
6549
- staging: path.join(root, "staging"),
6550
- state: path.join(root, "state.json"),
6551
- settings: path.join(root, "settings.json"),
6552
- lock: path.join(root, "state.lock"),
6553
- updateLock: path.join(root, "update.lock"),
6554
- launcher: path.join(root, "launcher.js")
6684
+ versions: path2.join(root, "versions"),
6685
+ staging: path2.join(root, "staging"),
6686
+ state: path2.join(root, "state.json"),
6687
+ settings: path2.join(root, "settings.json"),
6688
+ lock: path2.join(root, "state.lock"),
6689
+ updateLock: path2.join(root, "update.lock"),
6690
+ launcher: path2.join(root, "launcher.js")
6555
6691
  };
6556
6692
  }
6557
6693
  var MAX_TRIAL_STARTS = 3;
@@ -6595,25 +6731,24 @@ function isVersion(value) {
6595
6731
  }
6596
6732
  async function ensureRuntimeDirectory(paths) {
6597
6733
  for (const directory of [paths.root, paths.versions, paths.staging]) {
6598
- await fs.mkdir(directory, { recursive: true, mode: 448 });
6599
- await fs.chmod(directory, 448);
6734
+ await ensurePrivateDirectory(directory);
6600
6735
  }
6601
6736
  }
6602
6737
  async function writeFileAtomic(filename, contents, mode) {
6603
6738
  const temporary = `${filename}.${process.pid}.${Date.now()}.tmp`;
6604
- const handle = await fs.open(temporary, "w", mode);
6739
+ const handle = await fs2.open(temporary, "w", mode);
6605
6740
  try {
6606
6741
  await handle.writeFile(contents, "utf8");
6607
6742
  await handle.sync();
6608
6743
  } finally {
6609
6744
  await handle.close();
6610
6745
  }
6611
- await fs.rename(temporary, filename);
6746
+ await fs2.rename(temporary, filename);
6612
6747
  }
6613
6748
  async function readRuntimeState(paths) {
6614
6749
  let contents;
6615
6750
  try {
6616
- contents = await fs.readFile(paths.state, "utf8");
6751
+ contents = await fs2.readFile(paths.state, "utf8");
6617
6752
  } catch (error2) {
6618
6753
  if (errorCode(error2) === "ENOENT") return null;
6619
6754
  throw error2;
@@ -6647,19 +6782,19 @@ var LockBusyError = class extends Error {
6647
6782
  async function tryLock(filename) {
6648
6783
  for (let attempt = 0; attempt < 2; attempt += 1) {
6649
6784
  try {
6650
- const handle = await fs.open(filename, "wx", 384);
6785
+ const handle = await fs2.open(filename, "wx", 384);
6651
6786
  await handle.writeFile(`${process.pid}
6652
6787
  `, "utf8");
6653
6788
  await handle.close();
6654
6789
  return async () => {
6655
- const owner = await fs.readFile(filename, "utf8").catch(() => "");
6790
+ const owner = await fs2.readFile(filename, "utf8").catch(() => "");
6656
6791
  if (owner.trim() === String(process.pid))
6657
- await fs.rm(filename, { force: true });
6792
+ await fs2.rm(filename, { force: true });
6658
6793
  };
6659
6794
  } catch (error2) {
6660
6795
  if (errorCode(error2) !== "EEXIST") throw error2;
6661
6796
  const owner = Number(
6662
- (await fs.readFile(filename, "utf8").catch(() => "")).trim()
6797
+ (await fs2.readFile(filename, "utf8").catch(() => "")).trim()
6663
6798
  );
6664
6799
  if (Number.isSafeInteger(owner) && owner > 1) {
6665
6800
  try {
@@ -6669,7 +6804,7 @@ async function tryLock(filename) {
6669
6804
  if (errorCode(processError) !== "ESRCH") throw processError;
6670
6805
  }
6671
6806
  }
6672
- await fs.rm(filename, { force: true });
6807
+ await fs2.rm(filename, { force: true });
6673
6808
  }
6674
6809
  }
6675
6810
  throw new LockBusyError(`Could not obtain ${filename}.`);
@@ -6734,10 +6869,10 @@ function launchDecision(state, command, source, nowMs) {
6734
6869
  function versionDirectory(paths, version2) {
6735
6870
  if (!isVersion(version2))
6736
6871
  throw new Error(`Invalid Iteration version ${version2}.`);
6737
- return path.join(paths.versions, version2);
6872
+ return path2.join(paths.versions, version2);
6738
6873
  }
6739
6874
  function versionEntry(paths, version2, entry) {
6740
- return path.join(versionDirectory(paths, version2), "dist", entry);
6875
+ return path2.join(versionDirectory(paths, version2), "dist", entry);
6741
6876
  }
6742
6877
  var packageSchema = object({ name: literal("iteration-cli"), version: versionSchema }).loose();
6743
6878
 
@@ -6778,7 +6913,7 @@ async function launch() {
6778
6913
  command === "cursor-hook" ? "cursor-hook-entry.js" : "cli.js"
6779
6914
  );
6780
6915
  if (command === "launcher-check") {
6781
- await fs2.access(entry);
6916
+ await fs3.access(entry);
6782
6917
  process.stdout.write(`${version2}
6783
6918
  `);
6784
6919
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iteration-cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.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.11.0"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "pnpm --filter iteration-trace-contracts build && tsc -p tsconfig.json --noEmit && node build.mjs"