iteration-cli 0.13.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 {
@@ -6545,19 +6545,149 @@ var collectorEnrollmentResponseSchema = discriminatedUnion("status", [
6545
6545
  }).strict()
6546
6546
  ]);
6547
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
+
6548
6678
  // src/runtime.ts
6549
6679
  function runtimePaths(home = os.homedir()) {
6550
- const root = path.join(home, ".config", "iteration", "runtime");
6680
+ const root = path2.join(home, ".config", "iteration", "runtime");
6551
6681
  return {
6552
6682
  home,
6553
6683
  root,
6554
- versions: path.join(root, "versions"),
6555
- staging: path.join(root, "staging"),
6556
- state: path.join(root, "state.json"),
6557
- settings: path.join(root, "settings.json"),
6558
- lock: path.join(root, "state.lock"),
6559
- updateLock: path.join(root, "update.lock"),
6560
- 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")
6561
6691
  };
6562
6692
  }
6563
6693
  var MAX_TRIAL_STARTS = 3;
@@ -6601,25 +6731,24 @@ function isVersion(value) {
6601
6731
  }
6602
6732
  async function ensureRuntimeDirectory(paths) {
6603
6733
  for (const directory of [paths.root, paths.versions, paths.staging]) {
6604
- await fs.mkdir(directory, { recursive: true, mode: 448 });
6605
- await fs.chmod(directory, 448);
6734
+ await ensurePrivateDirectory(directory);
6606
6735
  }
6607
6736
  }
6608
6737
  async function writeFileAtomic(filename, contents, mode) {
6609
6738
  const temporary = `${filename}.${process.pid}.${Date.now()}.tmp`;
6610
- const handle = await fs.open(temporary, "w", mode);
6739
+ const handle = await fs2.open(temporary, "w", mode);
6611
6740
  try {
6612
6741
  await handle.writeFile(contents, "utf8");
6613
6742
  await handle.sync();
6614
6743
  } finally {
6615
6744
  await handle.close();
6616
6745
  }
6617
- await fs.rename(temporary, filename);
6746
+ await fs2.rename(temporary, filename);
6618
6747
  }
6619
6748
  async function readRuntimeState(paths) {
6620
6749
  let contents;
6621
6750
  try {
6622
- contents = await fs.readFile(paths.state, "utf8");
6751
+ contents = await fs2.readFile(paths.state, "utf8");
6623
6752
  } catch (error2) {
6624
6753
  if (errorCode(error2) === "ENOENT") return null;
6625
6754
  throw error2;
@@ -6653,19 +6782,19 @@ var LockBusyError = class extends Error {
6653
6782
  async function tryLock(filename) {
6654
6783
  for (let attempt = 0; attempt < 2; attempt += 1) {
6655
6784
  try {
6656
- const handle = await fs.open(filename, "wx", 384);
6785
+ const handle = await fs2.open(filename, "wx", 384);
6657
6786
  await handle.writeFile(`${process.pid}
6658
6787
  `, "utf8");
6659
6788
  await handle.close();
6660
6789
  return async () => {
6661
- const owner = await fs.readFile(filename, "utf8").catch(() => "");
6790
+ const owner = await fs2.readFile(filename, "utf8").catch(() => "");
6662
6791
  if (owner.trim() === String(process.pid))
6663
- await fs.rm(filename, { force: true });
6792
+ await fs2.rm(filename, { force: true });
6664
6793
  };
6665
6794
  } catch (error2) {
6666
6795
  if (errorCode(error2) !== "EEXIST") throw error2;
6667
6796
  const owner = Number(
6668
- (await fs.readFile(filename, "utf8").catch(() => "")).trim()
6797
+ (await fs2.readFile(filename, "utf8").catch(() => "")).trim()
6669
6798
  );
6670
6799
  if (Number.isSafeInteger(owner) && owner > 1) {
6671
6800
  try {
@@ -6675,7 +6804,7 @@ async function tryLock(filename) {
6675
6804
  if (errorCode(processError) !== "ESRCH") throw processError;
6676
6805
  }
6677
6806
  }
6678
- await fs.rm(filename, { force: true });
6807
+ await fs2.rm(filename, { force: true });
6679
6808
  }
6680
6809
  }
6681
6810
  throw new LockBusyError(`Could not obtain ${filename}.`);
@@ -6740,10 +6869,10 @@ function launchDecision(state, command, source, nowMs) {
6740
6869
  function versionDirectory(paths, version2) {
6741
6870
  if (!isVersion(version2))
6742
6871
  throw new Error(`Invalid Iteration version ${version2}.`);
6743
- return path.join(paths.versions, version2);
6872
+ return path2.join(paths.versions, version2);
6744
6873
  }
6745
6874
  function versionEntry(paths, version2, entry) {
6746
- return path.join(versionDirectory(paths, version2), "dist", entry);
6875
+ return path2.join(versionDirectory(paths, version2), "dist", entry);
6747
6876
  }
6748
6877
  var packageSchema = object({ name: literal("iteration-cli"), version: versionSchema }).loose();
6749
6878
 
@@ -6784,7 +6913,7 @@ async function launch() {
6784
6913
  command === "cursor-hook" ? "cursor-hook-entry.js" : "cli.js"
6785
6914
  );
6786
6915
  if (command === "launcher-check") {
6787
- await fs2.access(entry);
6916
+ await fs3.access(entry);
6788
6917
  process.stdout.write(`${version2}
6789
6918
  `);
6790
6919
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iteration-cli",
3
- "version": "0.13.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.10.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"