@gjermundgaraba/upup 0.3.0 → 0.5.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/main.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as reason, t as openFlags } from "./io-BGJ4Yp7u.mjs";
2
+ import { n as openFlags, r as reason, t as byName } from "./io-ns6xhFdF.mjs";
3
3
  import { findPackageJSON } from "node:module";
4
4
  import { NodeRuntime, NodeServices, NodeStdio } from "@effect/platform-node";
5
5
  import { Cause, Clock, Config, Context, Data, Deferred, Duration, Effect, Equal, Exit, Fiber, FiberHandle, FiberSet, FileSystem, Layer, Option, Predicate, Queue, Ref, Result, Schedule, Schema, Semaphore, Stream, SubscriptionRef, identity } from "effect";
@@ -17,10 +17,9 @@ import { homedir, tmpdir } from "node:os";
17
17
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
18
18
  import lockfile from "proper-lockfile";
19
19
  import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
20
- import { Type } from "@earendil-works/pi-ai";
21
- import { GenerationTask, Harness, ToolTask, createRegistry, defineDoc, defineExtension, defineTool, hook, section, watchEvents } from "@earendil-works/pi-durable";
20
+ import { GenerationTask, Harness, LiveDoc, ROOT_CONVERSATION_ID, ToolTask, createRegistry, defineDoc, defineExtension, defineTool, hook, section } from "@earendil-works/pi-durable";
22
21
  import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
23
- import { ChildProcess, ChildProcessSpawner } from "effect/process";
22
+ import { ChildProcess, ChildProcessSpawner, CommandLine } from "effect/process";
24
23
  import { Worker } from "node:worker_threads";
25
24
  import semver from "semver";
26
25
  import tarStream from "tar-stream";
@@ -41,8 +40,9 @@ import { XMLParser } from "fast-xml-parser";
41
40
  import { isSea } from "node:sea";
42
41
  import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
43
42
  import * as Action from "@gjermundgaraba/effect-actions/Action";
43
+ import { setup, types } from "xstate/fsm";
44
44
  // package.json
45
- var version$1 = "0.3.0";
45
+ var version$1 = "0.5.0";
46
46
  // src/ai/Setup.ts
47
47
  const Thinking = Schema.Literals([
48
48
  "off",
@@ -283,7 +283,7 @@ const ordered = (left, right) => Number(right.signedIn) - Number(left.signedIn)
283
283
  var AiFailed = class extends Schema.TaggedError()("AiFailed", { issue: Schema.String }) {};
284
284
  var Ai = class extends Context.Service()("upup/Ai") {};
285
285
  const failed$2 = (cause) => new AiFailed({ issue: cause instanceof Error ? cause.message : String(cause) });
286
- const make$14 = (models, credentials = store()) => Effect.gen(function* () {
286
+ const make$15 = (models, credentials = store()) => Effect.gen(function* () {
287
287
  const settings = yield* Settings;
288
288
  const catalogue = Effect.promise(async () => {
289
289
  const providers = models.getProviders();
@@ -328,7 +328,7 @@ const make$14 = (models, credentials = store()) => Effect.gen(function* () {
328
328
  });
329
329
  const layer$7 = Layer.effect(Ai, Effect.suspend(() => {
330
330
  const credentials = store();
331
- return make$14(builtinModels({ credentials }), credentials);
331
+ return make$15(builtinModels({ credentials }), credentials);
332
332
  }));
333
333
  // src/process/lock.ts
334
334
  var LockFailed = class extends Schema.TaggedError()("LockFailed", {
@@ -426,12 +426,12 @@ const withTarget = (pkg, target) => ({
426
426
  target
427
427
  });
428
428
  const isUnaudited = Schema.is(UnauditedEcosystem);
429
- const isApp = Schema.is(AppEcosystem);
429
+ const isApp$1 = Schema.is(AppEcosystem);
430
430
  const isRuntime = Schema.is(RuntimeEcosystem);
431
431
  const isUnauditedUpdate = (pkg) => isUnaudited(pkg.ecosystem);
432
- const isAppUpdate = (pkg) => isApp(pkg.ecosystem);
432
+ const isAppUpdate = (pkg) => isApp$1(pkg.ecosystem);
433
433
  const unauditedKind = (pkg) => {
434
- if (isApp(pkg.ecosystem)) return "app";
434
+ if (isApp$1(pkg.ecosystem)) return "app";
435
435
  if (isRuntime(pkg.ecosystem)) return "runtime";
436
436
  if (isUnaudited(pkg.ecosystem)) return "os";
437
437
  };
@@ -519,7 +519,7 @@ var CommandFailed = class extends Schema.TaggedError()("CommandFailed", {
519
519
  output: Schema.String
520
520
  }) {};
521
521
  var Exec = class extends Context.Service()("upup/Exec") {};
522
- const commandLine = (invocation) => [invocation.command, ...invocation.args].join(" ");
522
+ const commandLine = (invocation) => CommandLine.display([invocation.command, ...invocation.args]);
523
523
  const requireSuccess = (run) => (invocation) => run(invocation).pipe(Effect.filterOrFail((output) => output.exitCode === 0, (output) => new CommandFailed({
524
524
  command: commandLine(invocation),
525
525
  cwd: invocation.cwd,
@@ -550,7 +550,7 @@ const layer$6 = Layer.effect(Exec, Effect.gen(function* () {
550
550
  }, Effect.scoped, (effect, invocation) => Effect.mapError(effect, (cause) => new CommandFailed({
551
551
  command: commandLine(invocation),
552
552
  cwd: invocation.cwd,
553
- output: cause.message
553
+ output: Predicate.isError(cause.cause) ? cause.cause.message : cause.reason.description ?? cause.reason._tag
554
554
  })));
555
555
  return Exec.of({
556
556
  run,
@@ -595,14 +595,14 @@ const Installed = Schema.Struct({
595
595
  formulae: Schema.Array(Formula$2),
596
596
  casks: Schema.Array(Cask$1)
597
597
  });
598
- const OutdatedEntry$1 = Schema.Struct({
598
+ const OutdatedEntry = Schema.Struct({
599
599
  name: Schema.String,
600
600
  current_version: Schema.String,
601
601
  pinned: Schema.Boolean
602
602
  });
603
- const Outdated$2 = Schema.Struct({
604
- formulae: Schema.Array(OutdatedEntry$1),
605
- casks: Schema.Array(OutdatedEntry$1)
603
+ const Outdated$1 = Schema.Struct({
604
+ formulae: Schema.Array(OutdatedEntry),
605
+ casks: Schema.Array(OutdatedEntry)
606
606
  });
607
607
  const brew$1 = (args, refreshMetadata = false) => ({
608
608
  command: "brew",
@@ -653,7 +653,7 @@ const packages = (installed, outdated) => {
653
653
  });
654
654
  return [...formulae, ...casks];
655
655
  };
656
- const make$13 = Effect.gen(function* () {
656
+ const make$14 = Effect.gen(function* () {
657
657
  const exec = yield* Exec;
658
658
  const json = (schema, invocation) => exec.stdout(invocation).pipe(Effect.flatMap(decodeJson(schema, commandLine(invocation))));
659
659
  const list = Effect.gen(function* () {
@@ -662,7 +662,7 @@ const make$13 = Effect.gen(function* () {
662
662
  "--json=v2",
663
663
  "--installed"
664
664
  ]));
665
- const outdated = yield* json(Outdated$2, brew$1(["outdated", "--json=v2"], true));
665
+ const outdated = yield* json(Outdated$1, brew$1(["outdated", "--json=v2"], true));
666
666
  return {
667
667
  packages: packages(installed, outdated),
668
668
  notices: []
@@ -695,7 +695,7 @@ const make$13 = Effect.gen(function* () {
695
695
  exitCode: output.exitCode,
696
696
  output: output.stderr
697
697
  });
698
- const outdated = yield* decodeJson(Outdated$2, commandLine(invocation))(output.stdout);
698
+ const outdated = yield* decodeJson(Outdated$1, commandLine(invocation))(output.stdout);
699
699
  return [...outdated.formulae, ...outdated.casks].find((entry) => entry.name === pkg.name)?.current_version;
700
700
  });
701
701
  return {
@@ -752,15 +752,15 @@ const Severity = Schema.Literals([
752
752
  const Finding = Schema.Struct({
753
753
  severity: Severity,
754
754
  title: Schema.String,
755
- detail: Schema.String,
756
- paths: Schema.Array(Schema.String)
755
+ detail: Schema.String.annotate({ description: "Markdown: the evidence and why it matters." }),
756
+ paths: Schema.Array(Schema.String).annotate({ description: "Workspace paths, with line ranges where useful." })
757
757
  });
758
758
  const Report = Schema.Struct({
759
759
  verdict: Verdict,
760
- rationale: Schema.String,
761
- summary: Schema.String,
760
+ rationale: Schema.String.annotate({ description: "One concrete sentence justifying the verdict." }),
761
+ summary: Schema.String.annotate({ description: "Markdown: what the update changes that bears on the mandate, what you flagged and why you cleared or kept it." }),
762
762
  findings: Schema.Array(Finding),
763
- unverified: Schema.Array(Schema.String)
763
+ unverified: Schema.Array(Schema.String).annotate({ description: "Each thing you could not verify." })
764
764
  });
765
765
  Schema.Struct({
766
766
  mode: Mode,
@@ -797,7 +797,7 @@ const landingKey = (landing) => sha256(JSON.stringify([
797
797
  ]));
798
798
  const unauditedReasons = {
799
799
  app: "an app update",
800
- runtime: "a runtime version the project pins",
800
+ runtime: "a Node.js runtime version",
801
801
  os: "an operating-system update"
802
802
  };
803
803
  const unauditable = (pkg) => {
@@ -843,21 +843,23 @@ const descriptions = {
843
843
  grep: "Find a JavaScript regular expression in UTF-8 regular files below a path. Each line is matched whole. Lines over 1 KiB show a bounded window around each match with its file byte offset; a file stops at a line over 16 MiB.",
844
844
  list: "List workspace entries below a directory to a bounded depth."
845
845
  };
846
- const parameters = {
847
- read: {
848
- path: "Workspace-relative file path.",
849
- offset: "Zero-based byte offset; must be >= 0.",
850
- limit: `Maximum bytes to return; must be between 1 and ${limits$1.readBytes} inclusive.`
851
- },
852
- grep: {
853
- path: "Workspace-relative file or directory path.",
854
- pattern: "JavaScript regular expression."
855
- },
856
- list: {
857
- path: "Workspace-relative directory path; use . for the root.",
858
- depth: `Maximum directory depth; must be between 1 and ${limits$1.listDepth} inclusive.`
859
- }
860
- };
846
+ const count$1 = (maximum) => Schema.Number.pipe(Schema.check(Schema.isInt(), Schema.isBetween({
847
+ minimum: 1,
848
+ maximum
849
+ })));
850
+ const ReadArgs = Schema.Struct({
851
+ path: Schema.String.annotate({ description: "Workspace-relative file path." }),
852
+ offset: Schema.Number.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))).annotate({ description: "Zero-based byte offset; must be >= 0." }),
853
+ limit: count$1(limits$1.readBytes).annotate({ description: `Maximum bytes to return; must be between 1 and ${limits$1.readBytes} inclusive.` })
854
+ });
855
+ const GrepArgs = Schema.Struct({
856
+ path: Schema.String.annotate({ description: "Workspace-relative file or directory path." }),
857
+ pattern: Schema.String.annotate({ description: "JavaScript regular expression." })
858
+ });
859
+ const ListArgs = Schema.Struct({
860
+ path: Schema.String.annotate({ description: "Workspace-relative directory path; use . for the root." }),
861
+ depth: count$1(limits$1.listDepth).annotate({ description: `Maximum directory depth; must be between 1 and ${limits$1.listDepth} inclusive.` })
862
+ });
861
863
  var ToolError = class extends Error {
862
864
  constructor(message) {
863
865
  super(message);
@@ -914,10 +916,6 @@ const locate$4 = async (workspace, name, context) => {
914
916
  return fail$1(`${context}: ${reason(error)}`);
915
917
  }
916
918
  };
917
- const byName = (left, right) => {
918
- if (left.name < right.name) return -1;
919
- return left.name > right.name ? 1 : 0;
920
- };
921
919
  const suffixOf = (entry) => {
922
920
  if (entry.isDirectory()) return "/";
923
921
  return entry.isSymbolicLink() ? "@" : "";
@@ -1343,17 +1341,11 @@ const runDiff = (cwd, args, from, to, input) => Effect.callback((resume) => {
1343
1341
  });
1344
1342
  return Effect.sync(() => child.kill());
1345
1343
  });
1346
- const regularFiles = async (root, relative = "") => {
1347
- const entries = await readdir(path.join(root, relative)).catch(() => []);
1348
- const found = [];
1349
- for (const name of entries.toSorted()) {
1350
- const file = relative === "" ? name : `${relative}/${name}`;
1351
- const info = await lstat(path.join(root, file));
1352
- if (info.isDirectory()) found.push(...await regularFiles(root, file));
1353
- else if (info.isFile()) found.push(file);
1354
- }
1355
- return found;
1356
- };
1344
+ const regularFiles = (root, relative = "") => Effect.promise(async () => await readdir(path.join(root, relative), { withFileTypes: true }).catch(() => [])).pipe(Effect.flatMap((entries) => Effect.forEach(entries.toSorted(byName), (entry) => {
1345
+ const file = relative === "" ? entry.name : `${relative}/${entry.name}`;
1346
+ if (entry.isDirectory()) return regularFiles(root, file);
1347
+ return Effect.succeed(entry.isFile() ? [file] : []);
1348
+ }, { concurrency: "unbounded" })), Effect.map((found) => found.flat()));
1357
1349
  const failure$1 = (from, to) => (cause) => new DiffFailed({
1358
1350
  from,
1359
1351
  to,
@@ -1371,15 +1363,25 @@ const plain$1 = (pair) => ({
1371
1363
  paired: false,
1372
1364
  rewritten: false
1373
1365
  });
1374
- const planTree = async (workspace, from, to) => {
1375
- const [before, after] = await Promise.all([regularFiles(path.join(workspace, from)), regularFiles(path.join(workspace, to))]);
1366
+ const planTree = (workspace, from, to) => Effect.gen(function* () {
1367
+ const attempt = (run) => Effect.tryPromise({
1368
+ try: run,
1369
+ catch: failure$1(from, to)
1370
+ });
1371
+ const [before, after] = yield* Effect.all([regularFiles(path.join(workspace, from)), regularFiles(path.join(workspace, to))], { concurrency: "unbounded" });
1376
1372
  const beforeSet = new Set(before);
1377
1373
  const afterSet = new Set(after);
1378
1374
  const fromOnly = before.filter((file) => !afterSet.has(file));
1379
1375
  const toOnly = after.filter((file) => !beforeSet.has(file));
1380
- const only = /* @__PURE__ */ new Map();
1381
- for (const file of fromOnly) only.set(`from\0${file}`, await readFile(path.join(workspace, from, file)));
1382
- for (const file of toOnly) only.set(`to\0${file}`, await readFile(path.join(workspace, to, file)));
1376
+ const only = new Map(yield* Effect.forEach([...fromOnly.map((file) => ({
1377
+ side: "from",
1378
+ root: from,
1379
+ file
1380
+ })), ...toOnly.map((file) => ({
1381
+ side: "to",
1382
+ root: to,
1383
+ file
1384
+ }))], ({ side, root, file }) => attempt(async () => [`${side}\0${file}`, await readFile(path.join(workspace, root, file))]), { concurrency: 8 }));
1383
1385
  const pairs = matchRenames(fromOnly, toOnly, (side, file) => only.get(`${side}\0${file}`) ?? Buffer.alloc(0));
1384
1386
  const renames = renamesOf(pairs);
1385
1387
  const pairedFrom = new Set(pairs.map((pair) => pair.from));
@@ -1390,32 +1392,36 @@ const planTree = async (workspace, from, to) => {
1390
1392
  from: file,
1391
1393
  to: file
1392
1394
  }))];
1393
- for (const pair of candidates) {
1395
+ const classify = async (pair) => {
1394
1396
  const oldPath = path.join(workspace, from, pair.from);
1395
1397
  const nextPath = path.join(workspace, to, pair.to);
1396
1398
  const paired = pair.from !== pair.to;
1397
- if (!paired && renames.size === 0) {
1398
- if (!await identical(oldPath, nextPath)) changes.push(plain$1(pair));
1399
- continue;
1400
- }
1401
- const old = only.get(`from\0${pair.from}`) ?? await readFile(oldPath);
1402
- const next = only.get(`to\0${pair.to}`) ?? await readFile(nextPath);
1403
- if (!paired && old.equals(next)) continue;
1399
+ if (!paired && renames.size === 0) return await identical(oldPath, nextPath) ? void 0 : {
1400
+ same: false,
1401
+ change: plain$1(pair)
1402
+ };
1403
+ const [old, next] = await Promise.all([only.get(`from\0${pair.from}`) ?? readFile(oldPath), only.get(`to\0${pair.to}`) ?? readFile(nextPath)]);
1404
+ if (!paired && old.equals(next)) return;
1404
1405
  const text = isText(old) ? old.toString("latin1") : void 0;
1405
1406
  const rewrittenText = text === void 0 ? void 0 : rewrite(text, renames);
1406
1407
  const rewritten = rewrittenText !== void 0 && rewrittenText !== text;
1407
- if (!paired && !rewritten) {
1408
- changes.push(plain$1(pair));
1409
- continue;
1410
- }
1408
+ if (!paired && !rewritten) return {
1409
+ same: false,
1410
+ change: plain$1(pair)
1411
+ };
1411
1412
  const expected = rewrittenText === void 0 ? old : Buffer.from(rewrittenText, "latin1");
1412
- (expected.equals(next) ? same : changes).push({
1413
- ...pair,
1414
- expected,
1415
- paired,
1416
- rewritten
1417
- });
1418
- }
1413
+ return {
1414
+ same: expected.equals(next),
1415
+ change: {
1416
+ ...pair,
1417
+ expected,
1418
+ paired,
1419
+ rewritten
1420
+ }
1421
+ };
1422
+ };
1423
+ const planned = yield* Effect.forEach(candidates, (pair) => attempt(async () => await classify(pair)), { concurrency: 8 });
1424
+ for (const entry of planned) if (entry !== void 0) (entry.same ? same : changes).push(entry.change);
1419
1425
  for (const file of fromOnly) if (!pairedFrom.has(file)) changes.push({
1420
1426
  ...plain$1({
1421
1427
  from: file,
@@ -1437,7 +1443,7 @@ const planTree = async (workspace, from, to) => {
1437
1443
  }),
1438
1444
  same
1439
1445
  };
1440
- };
1446
+ });
1441
1447
  const mebibytes = (bytes) => `${Math.round(bytes / 1024 / 1024)} MiB`;
1442
1448
  const shownOmissions = 2e3;
1443
1449
  const changeDiff = (workspace, from, to, change) => {
@@ -1469,10 +1475,7 @@ const partial = (what, omitted) => omitted === "" ? "" : `The ${what} diff is pa
1469
1475
  const DiffBudget = Context.Reference("upup/DiffBudget", { defaultValue: () => () => maxDiffBytes });
1470
1476
  const treeDiff = (workspace, from, to, limit) => Effect.gen(function* () {
1471
1477
  const budget = limit ?? (yield* DiffBudget)(from);
1472
- const { changes, same } = yield* Effect.tryPromise({
1473
- try: async () => await planTree(workspace, from, to),
1474
- catch: failure$1(from, to)
1475
- });
1478
+ const { changes, same } = yield* planTree(workspace, from, to);
1476
1479
  const parts = [];
1477
1480
  const left = [];
1478
1481
  let size = 0;
@@ -1806,12 +1809,16 @@ const majorsOf = (from, to) => {
1806
1809
  };
1807
1810
  const exists$1 = async (file) => await stat(file).then(() => true, () => false);
1808
1811
  const emptyTree = async (directory) => await readdir(directory).then((entries) => entries.length === 0, () => false);
1809
- const readMajors = (workspace) => Effect.promise(async () => majorsOf(await listed$2(workspace, files.dependencyListFrom), await listed$2(workspace, files.dependencyListTo)));
1812
+ const majorsIn = async (workspace) => {
1813
+ const [from, to] = await Promise.all([listed$2(workspace, files.dependencyListFrom), listed$2(workspace, files.dependencyListTo)]);
1814
+ return majorsOf(from, to);
1815
+ };
1816
+ const readMajors = (workspace) => Effect.promise(async () => await majorsIn(workspace));
1810
1817
  const read$1 = (workspace) => Effect.gen(function* () {
1811
1818
  const meta = yield* readMeta(workspace);
1812
1819
  return yield* Effect.tryPromise({
1813
1820
  try: async () => {
1814
- const diff = await readFile(path.join(workspace, files.diff));
1821
+ const [diff, majors] = await Promise.all([readFile(path.join(workspace, files.diff)), majorsIn(workspace)]);
1815
1822
  const trees = [];
1816
1823
  const titled = [];
1817
1824
  const targets = /* @__PURE__ */ new Map();
@@ -1850,7 +1857,7 @@ const read$1 = (workspace) => Effect.gen(function* () {
1850
1857
  trees,
1851
1858
  files: titled,
1852
1859
  sections,
1853
- majors: majorsOf(await listed$2(workspace, files.dependencyListFrom), await listed$2(workspace, files.dependencyListTo))
1860
+ majors
1854
1861
  };
1855
1862
  },
1856
1863
  catch: failed$1(workspace)
@@ -1920,28 +1927,18 @@ const BriefDoc = defineDoc({
1920
1927
  initial: () => ({ brief: null }),
1921
1928
  checkpointWhen: () => false
1922
1929
  });
1923
- const severity = Type.Union([
1924
- Type.Literal("info"),
1925
- Type.Literal("low"),
1926
- Type.Literal("medium"),
1927
- Type.Literal("high")
1928
- ]);
1929
- const ReportParameters = Type.Object({
1930
- verdict: Type.Union([
1931
- Type.Literal("ok"),
1932
- Type.Literal("caution"),
1933
- Type.Literal("danger"),
1934
- Type.Literal("no-verdict")
1935
- ]),
1936
- rationale: Type.String({ description: "One concrete sentence justifying the verdict." }),
1937
- summary: Type.String({ description: "Markdown: what the update changes that bears on the mandate, what you flagged and why you cleared or kept it." }),
1938
- findings: Type.Array(Type.Object({
1939
- severity,
1940
- title: Type.String(),
1941
- detail: Type.String({ description: "Markdown: the evidence and why it matters." }),
1942
- paths: Type.Array(Type.String(), { description: "Workspace paths, with line ranges where useful." })
1930
+ const parameters = (schema) => Schema.toJsonSchemaDocument(schema).schema;
1931
+ const decodeRead = Schema.decodeUnknownSync(ReadArgs);
1932
+ const decodeGrep = Schema.decodeUnknownSync(GrepArgs);
1933
+ const decodeList = Schema.decodeUnknownSync(ListArgs);
1934
+ const decodeSubmitted = Schema.decodeUnknownSync(Report);
1935
+ const encodeReport = (submitted) => ({
1936
+ ...submitted,
1937
+ findings: submitted.findings.map((finding) => ({
1938
+ ...finding,
1939
+ paths: [...finding.paths]
1943
1940
  })),
1944
- unverified: Type.Array(Type.String(), { description: "Each thing you could not verify." })
1941
+ unverified: [...submitted.unverified]
1945
1942
  });
1946
1943
  const ReportDoc = defineDoc({
1947
1944
  kind: "upup.report",
@@ -1967,42 +1964,33 @@ const auditExtension = (workspace) => {
1967
1964
  name: "read",
1968
1965
  description: descriptions.read,
1969
1966
  replay: "safe",
1970
- parameters: Type.Object({
1971
- path: Type.String({ description: parameters.read.path }),
1972
- offset: Type.Integer({ description: parameters.read.offset }),
1973
- limit: Type.Integer({ description: parameters.read.limit })
1974
- }),
1975
- execute: async (args, _api, invocation) => answer(await read$2(workspace, args, invocation.abortSignal))
1967
+ parameters: parameters(ReadArgs),
1968
+ execute: async (args, _api, invocation) => answer(await read$2(workspace, decodeRead(args), invocation.abortSignal))
1976
1969
  });
1977
1970
  const grep$1 = defineTool({
1978
1971
  name: "grep",
1979
1972
  description: descriptions.grep,
1980
1973
  replay: "safe",
1981
- parameters: Type.Object({
1982
- path: Type.String({ description: parameters.grep.path }),
1983
- pattern: Type.String({ description: parameters.grep.pattern })
1984
- }),
1985
- execute: async (args, _api, invocation) => answer(await grep(workspace, args, invocation.abortSignal === void 0 ? {} : { signal: invocation.abortSignal }))
1974
+ parameters: parameters(GrepArgs),
1975
+ execute: async (args, _api, invocation) => answer(await grep(workspace, decodeGrep(args), invocation.abortSignal === void 0 ? {} : { signal: invocation.abortSignal }))
1986
1976
  });
1987
1977
  const list$1 = defineTool({
1988
1978
  name: "list",
1989
1979
  description: descriptions.list,
1990
1980
  replay: "safe",
1991
- parameters: Type.Object({
1992
- path: Type.String({ description: parameters.list.path }),
1993
- depth: Type.Integer({ description: parameters.list.depth })
1994
- }),
1995
- execute: async (args, _api, invocation) => answer(await list(workspace, args, invocation.abortSignal))
1981
+ parameters: parameters(ListArgs),
1982
+ execute: async (args, _api, invocation) => answer(await list(workspace, decodeList(args), invocation.abortSignal))
1996
1983
  });
1997
1984
  const submit = defineTool({
1998
1985
  name: "submit_report",
1999
1986
  description: "Submit the audit report. Call exactly once, when the investigation is done; the audit ends.",
2000
1987
  replay: "safe",
2001
- parameters: ReportParameters,
1988
+ parameters: parameters(Report),
2002
1989
  execute: async (args, api, invocation) => {
1990
+ const encoded = encodeReport(decodeSubmitted(args));
2003
1991
  await api.commit(async (tx) => {
2004
1992
  const doc = await tx.doc(ReportDoc, api.conversationId);
2005
- doc.report = structuredClone(args);
1993
+ doc.report = encoded;
2006
1994
  }, invocation);
2007
1995
  return {
2008
1996
  ...text$2("Report submitted."),
@@ -2078,6 +2066,32 @@ const entriesOf$1 = (message) => {
2078
2066
  }
2079
2067
  return [];
2080
2068
  };
2069
+ const TextBlock = Schema.Struct({
2070
+ type: Schema.Literal("text"),
2071
+ text: Schema.String
2072
+ });
2073
+ const ThinkingBlock = Schema.Struct({
2074
+ type: Schema.Literal("thinking"),
2075
+ thinking: Schema.String
2076
+ });
2077
+ const isTextBlock = Schema.is(TextBlock);
2078
+ const isThinkingBlock = Schema.is(ThinkingBlock);
2079
+ const decodeLive = Schema.decodeUnknownOption(Schema.Struct({ generation: Schema.optional(Schema.Struct({
2080
+ attempt: Schema.Finite,
2081
+ message: Schema.optional(Schema.Struct({ content: Schema.Array(Schema.Unknown) })),
2082
+ retry: Schema.optional(Schema.Struct({ error: Schema.String }))
2083
+ })) }));
2084
+ const streamed = (block) => {
2085
+ if (isThinkingBlock(block)) return block.thinking.trim() === "" ? [] : [{
2086
+ kind: "thinking",
2087
+ text: block.thinking
2088
+ }];
2089
+ if (isTextBlock(block)) return block.text.trim() === "" ? [] : [{
2090
+ kind: "text",
2091
+ text: block.text
2092
+ }];
2093
+ return [];
2094
+ };
2081
2095
  var Transcript = class {
2082
2096
  committed = /* @__PURE__ */ new Map();
2083
2097
  live = [];
@@ -2085,41 +2099,20 @@ var Transcript = class {
2085
2099
  add(entry) {
2086
2100
  this.committed.set(entry.id, (entry.model ?? []).flatMap(entriesOf$1));
2087
2101
  }
2088
- apply(event) {
2089
- switch (event.type) {
2090
- case "snapshot":
2091
- this.committed.clear();
2092
- for (const entry of event.entries) this.add(entry);
2093
- this.live = [];
2094
- break;
2095
- case "entry_appended":
2096
- case "message_end":
2097
- this.add(event.entry);
2098
- this.live = [];
2099
- this.retry = void 0;
2100
- break;
2101
- case "message_update":
2102
- for (const change of event.changes) if (change.type === "text_delta" || change.type === "thinking_delta") {
2103
- const kind = change.type === "text_delta" ? "text" : "thinking";
2104
- const last = this.live.at(-1);
2105
- if (last?.kind === kind) this.live[this.live.length - 1] = {
2106
- kind,
2107
- text: last.text + change.delta
2108
- };
2109
- else this.live.push({
2110
- kind,
2111
- text: change.delta
2112
- });
2113
- }
2114
- break;
2115
- case "auto_retry_start":
2116
- this.retry = {
2117
- kind: "retry",
2118
- text: `retrying (attempt ${event.attempt}): ${event.errorMessage}`
2119
- };
2120
- break;
2121
- case "auto_retry_end": this.retry = void 0;
2122
- }
2102
+ view(view) {
2103
+ for (const entry of view.entries) this.add(entry);
2104
+ const generation = decodeLive(view.docs[LiveDoc.definition.kind]).pipe(Option.flatMapNullishOr((live) => live.generation));
2105
+ this.live = Option.match(generation, {
2106
+ onNone: () => [],
2107
+ onSome: ({ message }) => (message?.content ?? []).flatMap(streamed)
2108
+ });
2109
+ this.retry = Option.match(generation, {
2110
+ onNone: () => void 0,
2111
+ onSome: ({ attempt, retry }) => retry === void 0 ? void 0 : {
2112
+ kind: "retry",
2113
+ text: `retrying (attempt ${attempt}): ${retry.error}`
2114
+ }
2115
+ });
2123
2116
  }
2124
2117
  entries() {
2125
2118
  return [
@@ -2134,16 +2127,24 @@ const attempt$1 = (evaluate) => Effect.tryPromise({
2134
2127
  try: evaluate,
2135
2128
  catch: failed
2136
2129
  });
2130
+ const watched = (open, onValue) => Effect.acquireRelease(attempt$1(async () => {
2131
+ const watch = await open();
2132
+ watch.start(async (call) => {
2133
+ if (call.kind === "value") onValue(call.value);
2134
+ });
2135
+ return watch;
2136
+ }), (watch) => Effect.promise(async () => await watch.stop()).pipe(Effect.ignore));
2137
2137
  const lastAssistant = (entries) => entries.flatMap((entry) => entry.model ?? []).findLast((message) => message.role === "assistant");
2138
- const openHarness = (file, workspace, models) => Effect.acquireRelease(attempt$1(async () => {
2139
- if (workspace === "" && !existsSync(file)) throw new Error(`no audit at ${file}`);
2140
- const extension = auditExtension(workspace);
2138
+ const openHarness = (file, models, workspace) => Effect.acquireRelease(attempt$1(async () => {
2139
+ if (workspace === void 0 && !existsSync(file)) throw new Error(`no audit at ${file}`);
2140
+ const extension = auditExtension(workspace ?? "");
2141
2141
  const registry = createRegistry();
2142
2142
  registry.install(extension);
2143
2143
  return await Harness.open(await openNodeSqliteStorage(file), {
2144
2144
  models,
2145
2145
  registry,
2146
- settings: { extensions: [extension] }
2146
+ settings: { extensions: [extension] },
2147
+ readOnly: workspace === void 0
2147
2148
  }, context);
2148
2149
  }), (harness) => Effect.promise(async () => {
2149
2150
  await harness.close(context);
@@ -2156,7 +2157,7 @@ const encodeBrief = (brief) => ({
2156
2157
  artifacts: [...brief.artifacts]
2157
2158
  });
2158
2159
  const run$5 = (options) => Effect.scoped(Effect.gen(function* () {
2159
- const harness = yield* openHarness(options.file, options.brief.workspace, options.models);
2160
+ const harness = yield* openHarness(options.file, options.models, options.brief.workspace);
2160
2161
  const root = yield* attempt$1(async () => await harness.root(context, {
2161
2162
  agent: {
2162
2163
  model: {
@@ -2171,13 +2172,12 @@ const run$5 = (options) => Effect.scoped(Effect.gen(function* () {
2171
2172
  }
2172
2173
  }));
2173
2174
  const transcript = new Transcript();
2174
- const events = yield* Effect.acquireRelease(attempt$1(async () => await watchEvents(harness, root.id, context)), (stream) => Effect.promise(async () => await stream.stop()).pipe(Effect.ignore));
2175
- transcript.apply(events.snapshot);
2176
- options.onTranscript(transcript.entries());
2177
- events.start(async (batch) => {
2178
- for (const event of batch) transcript.apply(event);
2175
+ const views = yield* watched(async () => await root.watch(context), (view) => {
2176
+ transcript.view(view);
2179
2177
  options.onTranscript(transcript.entries());
2180
2178
  });
2179
+ transcript.view(views.value);
2180
+ options.onTranscript(transcript.entries());
2181
2181
  harness.resume();
2182
2182
  const settled = yield* Effect.tryPromise({
2183
2183
  try: async (signal) => {
@@ -2204,20 +2204,29 @@ const run$5 = (options) => Effect.scoped(Effect.gen(function* () {
2204
2204
  _tag: "Reported",
2205
2205
  report: decoded.value
2206
2206
  };
2207
- const final = yield* attempt$1(async () => await watchEvents(harness, root.id, context));
2208
- yield* attempt$1(async () => await final.stop());
2207
+ const final = yield* attempt$1(async () => await root.context(context));
2209
2208
  if (settled.value.status === "unanswered") return {
2210
2209
  _tag: "Failed",
2211
2210
  issue: Predicate.isString(settled.value.detail) ? settled.value.detail : settled.value.reason
2212
2211
  };
2213
2212
  return {
2214
2213
  _tag: "Failed",
2215
- issue: lastAssistant(final.snapshot.entries)?.errorMessage ?? "the model finished without submitting a report"
2214
+ issue: lastAssistant(final.entries)?.errorMessage ?? "the model finished without submitting a report"
2216
2215
  };
2217
2216
  }));
2217
+ const storedRoot = (file, models) => Effect.gen(function* () {
2218
+ const harness = yield* openHarness(file, models);
2219
+ return {
2220
+ harness,
2221
+ root: yield* attempt$1(async () => await harness.conversation(ROOT_CONVERSATION_ID, context))
2222
+ };
2223
+ });
2218
2224
  const stored = (file, models) => Effect.scoped(Effect.gen(function* () {
2219
- const harness = yield* openHarness(file, "", models);
2220
- const root = yield* attempt$1(async () => await harness.root(context));
2225
+ const { harness, root } = yield* storedRoot(file, models);
2226
+ if (root === void 0) return {
2227
+ brief: Option.none(),
2228
+ report: Option.none()
2229
+ };
2221
2230
  const brief = yield* attempt$1(async () => await harness.snapshot(BriefDoc, root.id, context));
2222
2231
  const submitted = yield* attempt$1(async () => await harness.snapshot(ReportDoc, root.id, context));
2223
2232
  return {
@@ -2226,12 +2235,12 @@ const stored = (file, models) => Effect.scoped(Effect.gen(function* () {
2226
2235
  };
2227
2236
  }));
2228
2237
  const transcript = (file, models) => Effect.scoped(Effect.gen(function* () {
2229
- const harness = yield* openHarness(file, "", models);
2230
- const root = yield* attempt$1(async () => await harness.root(context));
2231
- const events = yield* attempt$1(async () => await watchEvents(harness, root.id, context));
2232
- yield* attempt$1(async () => await events.stop());
2238
+ const { root } = yield* storedRoot(file, models);
2233
2239
  const built = new Transcript();
2234
- built.apply(events.snapshot);
2240
+ if (root !== void 0) {
2241
+ const active = yield* attempt$1(async () => await root.context(context));
2242
+ for (const entry of active.entries) built.add(entry);
2243
+ }
2235
2244
  return built.entries();
2236
2245
  }));
2237
2246
  // src/inventory/versions.ts
@@ -2268,7 +2277,7 @@ var LookupFailed = class extends Schema.TaggedError()("LookupFailed", {
2268
2277
  url: Schema.String,
2269
2278
  issue: Schema.String
2270
2279
  }) {};
2271
- const Release$2 = Schema.fromJsonString(Schema.Struct({
2280
+ const Release$3 = Schema.fromJsonString(Schema.Struct({
2272
2281
  vers: Schema.String,
2273
2282
  yanked: Schema.Boolean
2274
2283
  }));
@@ -2285,7 +2294,7 @@ const newestRelease = Effect.fn("CratesIndex.newestRelease")(function* (client,
2285
2294
  issue: error.message
2286
2295
  });
2287
2296
  const text = yield* HttpClient.filterStatusOk(client).get(url).pipe(Effect.flatMap((response) => response.text), Effect.mapError(failed));
2288
- const releases = yield* Effect.forEach(text.split("\n").filter((line) => line.trim().length > 0), (line) => Schema.decodeEffect(Release$2)(line).pipe(Effect.mapError(failed)));
2297
+ const releases = yield* Effect.forEach(text.split("\n").filter((line) => line.trim().length > 0), (line) => Schema.decodeEffect(Release$3)(line).pipe(Effect.mapError(failed)));
2289
2298
  return newestOffer(current, releases.filter((release) => !release.yanked).map((release) => release.vers));
2290
2299
  });
2291
2300
  // src/registry/Metadata.ts
@@ -2338,7 +2347,7 @@ const escape = (path) => path.replaceAll(/[A-Z]/gu, (letter) => `!${letter.toLow
2338
2347
  const deprecation = (mod) => Option.fromNullishOr(/^\/\/\s*Deprecated:\s*(?<reason>.+)$/mu.exec(mod)?.groups?.["reason"]?.trim());
2339
2348
  const lookup$5 = Effect.fn("go.lookup")(function* (client, wanted) {
2340
2349
  const base = `https://proxy.golang.org/${escape(wanted.name)}/@v`;
2341
- const [latest, infos] = yield* Effect.all([getJson(client, `https://proxy.golang.org/${escape(wanted.name)}/@latest`, Info$2), Effect.forEach(wanted.versions, (version) => getJson(client, `${base}/${escape(version)}.info`, Info$2).pipe(Effect.option), { concurrency: 2 })], { concurrency: 2 });
2350
+ const [latest, infos] = yield* Effect.all([getJson(client, `https://proxy.golang.org/${escape(wanted.name)}/@latest`, Info$2), Effect.forEach(wanted.versions, (version) => getJson(client, `${base}/${escape(version)}.info`, Info$2).pipe(Effect.option), { concurrency: 2 })], { concurrency: "unbounded" });
2342
2351
  const mod = yield* getText(client, `${base}/${escape(latest.Version)}.mod`);
2343
2352
  const deprecated = deprecation(mod);
2344
2353
  const found = infos.flatMap((info) => Option.isSome(info) ? [info.value] : []);
@@ -4849,30 +4858,36 @@ const resolveFormula = Effect.fn("brew.resolveFormula")(function* (client, sourc
4849
4858
  digest: createHash("sha256").update(body).digest("hex")
4850
4859
  };
4851
4860
  });
4852
- let to;
4853
- let from;
4854
- if (source.tapGitHead !== "") {
4855
- const current = yield* Effect.option(snapshot(source.tapGitHead, "tap revision reported by brew info"));
4856
- if (current._tag === "Some" && matchesVersion(current.value.snapshot.text, toVersion) && (source.rubySourceChecksum === "" || source.rubySourceChecksum.toLowerCase() === current.value.digest)) to = current.value.snapshot;
4857
- }
4858
- let endpoint = `${githubApi}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits?${new URLSearchParams({
4859
- path: source.rubySourcePath,
4860
- per_page: `${historyPageSize}`
4861
- }).toString()}`;
4862
- let scanned = 0;
4863
- const complete = () => from !== void 0 && to !== void 0;
4864
- for (let page = 0; endpoint !== "" && page < maxHistoryPages && !complete(); page += 1) {
4865
- const { body, link } = yield* githubGet(client, endpoint, token).pipe(Effect.mapError(prefixed("query brew formula history")));
4866
- const commits = yield* commitsOf(body, endpoint);
4867
- for (const commit of commits) {
4868
- scanned += 1;
4869
- const { snapshot: candidate } = yield* snapshot(commit.sha, commit.message);
4870
- if (to === void 0 && matchesVersion(candidate.text, toVersion)) to = candidate;
4871
- if (from === void 0 && matchesVersion(candidate.text, fromVersion)) from = candidate;
4872
- if (complete()) break;
4861
+ const scanHistory = Effect.gen(function* () {
4862
+ let to;
4863
+ let from;
4864
+ let endpoint = `${githubApi}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits?${new URLSearchParams({
4865
+ path: source.rubySourcePath,
4866
+ per_page: `${historyPageSize}`
4867
+ }).toString()}`;
4868
+ let scanned = 0;
4869
+ const complete = () => from !== void 0 && to !== void 0;
4870
+ for (let page = 0; endpoint !== "" && page < maxHistoryPages && !complete(); page += 1) {
4871
+ const { body, link } = yield* githubGet(client, endpoint, token).pipe(Effect.mapError(prefixed("query brew formula history")));
4872
+ const commits = yield* commitsOf(body, endpoint);
4873
+ for (const commit of commits) {
4874
+ scanned += 1;
4875
+ const { snapshot: candidate } = yield* snapshot(commit.sha, commit.message);
4876
+ if (to === void 0 && matchesVersion(candidate.text, toVersion)) to = candidate;
4877
+ if (from === void 0 && matchesVersion(candidate.text, fromVersion)) from = candidate;
4878
+ if (complete()) break;
4879
+ }
4880
+ endpoint = complete() ? "" : nextPage(endpoint, link);
4873
4881
  }
4874
- endpoint = complete() ? "" : nextPage(endpoint, link);
4875
- }
4882
+ return {
4883
+ from,
4884
+ to,
4885
+ scanned
4886
+ };
4887
+ });
4888
+ const [current, history] = yield* Effect.all([source.tapGitHead === "" ? Effect.succeedNone : Effect.option(snapshot(source.tapGitHead, "tap revision reported by brew info")), scanHistory], { concurrency: "unbounded" });
4889
+ const { from, scanned } = history;
4890
+ const to = current._tag === "Some" && matchesVersion(current.value.snapshot.text, toVersion) && (source.rubySourceChecksum === "" || source.rubySourceChecksum.toLowerCase() === current.value.digest) ? current.value.snapshot : history.to;
4876
4891
  if (to === void 0) return yield* new EvidenceUnavailable({
4877
4892
  source: "brew",
4878
4893
  issue: `brew formula history has no snapshot for target version ${toVersion} within the newest ${scanned} commits`
@@ -5277,13 +5292,14 @@ const payloadDiff = Effect.fn("brew.payloadDiff")(function* (client, workspace,
5277
5292
  const { name } = shortName(artifact);
5278
5293
  const oldStrip = yield* strip(name, plan.old);
5279
5294
  const newStrip = yield* strip(name, plan.new);
5280
- const oldManifest = yield* payloadManifest(client, "installed", plan.old, oldStrip, path.join(workspace, files.payloadTreeFrom));
5295
+ const [oldManifest, newManifest] = yield* Effect.all([payloadManifest(client, "installed", plan.old, oldStrip, path.join(workspace, files.payloadTreeFrom)), payloadManifest(client, "target", plan.new, newStrip, path.join(workspace, files.payloadTreeTo))], { concurrency: "unbounded" });
5281
5296
  if (oldManifest === void 0) return yield* unavailablePayload(workspace, "installed Homebrew payload archive format is unsupported");
5282
- const newManifest = yield* payloadManifest(client, "target", plan.new, newStrip, path.join(workspace, files.payloadTreeTo));
5283
5297
  if (newManifest === void 0) return yield* unavailablePayload(workspace, "target Homebrew payload archive format is unsupported");
5284
- const manifests = yield* writeFileDiff(workspace, files.payloadFrom, files.payloadTo, manifestJson(oldManifest), manifestJson(newManifest));
5285
- const binaries = yield* writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(oldManifest)), json(binarySummaries(newManifest)));
5286
- const trees = yield* treeDiff(workspace, files.payloadTreeFrom, files.payloadTreeTo);
5298
+ const [manifests, binaries, trees] = yield* Effect.all([
5299
+ writeFileDiff(workspace, files.payloadFrom, files.payloadTo, manifestJson(oldManifest), manifestJson(newManifest)),
5300
+ writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(oldManifest)), json(binarySummaries(newManifest))),
5301
+ treeDiff(workspace, files.payloadTreeFrom, files.payloadTreeTo)
5302
+ ], { concurrency: "unbounded" });
5287
5303
  const contents = trees.diff;
5288
5304
  const contentDetail = [" Extracted payload file contents are diffed.", partial("payload", trees.omitted)].filter((sentence) => sentence !== "").join(" ");
5289
5305
  const origin = plan.old.origin === "" ? "" : ` (pinned by ${plan.old.origin})`;
@@ -5762,9 +5778,9 @@ const writeChanges = Effect.fn("git.writeChanges")(function* (from, to, destinat
5762
5778
  modes
5763
5779
  };
5764
5780
  const contents = readBatch((yield* git(["cat-file", "--batch"], objects.map((object) => `${object}\n`).join(""))).stdout, objects);
5765
- yield* Effect.tryPromise({
5781
+ yield* Effect.forEach(changes, (change) => Effect.tryPromise({
5766
5782
  try: async () => {
5767
- for (const change of changes) for (const [object, root] of [[change.fromObject, destination.from], [change.toObject, destination.to]]) {
5783
+ for (const [object, root] of [[change.fromObject, destination.from], [change.toObject, destination.to]]) {
5768
5784
  if (object === void 0) continue;
5769
5785
  const file = path.join(root, change.path);
5770
5786
  await mkdir(path.dirname(file), {
@@ -5778,6 +5794,9 @@ const writeChanges = Effect.fn("git.writeChanges")(function* (from, to, destinat
5778
5794
  repository,
5779
5795
  issue: `write source files: ${String(error)}`
5780
5796
  })
5797
+ }), {
5798
+ concurrency: 8,
5799
+ discard: true
5781
5800
  });
5782
5801
  return {
5783
5802
  files: changes.length,
@@ -5838,9 +5857,9 @@ const moduleChanges = (from, to, released) => {
5838
5857
  };
5839
5858
  };
5840
5859
  const readSummaries = (workspace, file) => Effect.tryPromise(async () => await readFile(path.join(workspace, file), "utf-8")).pipe(Effect.flatMap((text) => Schema.decodeEffect(Summaries)(text)), Effect.option);
5860
+ const readBinarySummaries = (workspace) => Effect.all([readSummaries(workspace, files.binaryFrom), readSummaries(workspace, files.binaryTo)], { concurrency: "unbounded" });
5841
5861
  const goSourceEvidence = Effect.fn("goSource.evidence")(function* (client, workspace, released) {
5842
- const from = yield* readSummaries(workspace, files.binaryFrom);
5843
- const to = yield* readSummaries(workspace, files.binaryTo);
5862
+ const [from, to] = yield* readBinarySummaries(workspace);
5844
5863
  if (from._tag === "None" || to._tag === "None") return;
5845
5864
  const { changes, reason } = moduleChanges(from.value, to.value, released);
5846
5865
  if (changes.length === 0) return {
@@ -5852,7 +5871,7 @@ const goSourceEvidence = Effect.fn("goSource.evidence")(function* (client, works
5852
5871
  const plan = yield* resolve$3(client, "go", modulePath, version);
5853
5872
  return yield* materialize$2(client, plan, path.join(workspace, tree, modulePath));
5854
5873
  });
5855
- const fetched = yield* Effect.forEach(changes, (change) => Effect.result(Effect.all([fetch(change.path, change.from, files.goSourceFrom), fetch(change.path, change.to, files.goSourceTo)], { concurrency: 2 })), { concurrency: 2 });
5874
+ const fetched = yield* Effect.forEach(changes, (change) => Effect.result(Effect.all([fetch(change.path, change.from, files.goSourceFrom), fetch(change.path, change.to, files.goSourceTo)], { concurrency: "unbounded" })), { concurrency: 2 });
5856
5875
  const failures = changes.flatMap((change, index) => {
5857
5876
  const result = fetched[index];
5858
5877
  return result?._tag === "Failure" ? [`${change.path}: ${result.failure.issue}`] : [];
@@ -5911,8 +5930,7 @@ const buildInfoSources = (from, to) => {
5911
5930
  return [...sources.values()].slice(0, 4);
5912
5931
  };
5913
5932
  const readBuildInfoSources = Effect.fn("goSource.buildInfoSources")(function* (workspace) {
5914
- const from = yield* readSummaries(workspace, files.binaryFrom);
5915
- const to = yield* readSummaries(workspace, files.binaryTo);
5933
+ const [from, to] = yield* readBinarySummaries(workspace);
5916
5934
  return from._tag === "None" || to._tag === "None" ? [] : buildInfoSources(from.value, to.value);
5917
5935
  });
5918
5936
  // src/audit/source-evidence.ts
@@ -6224,7 +6242,7 @@ const releaseAssetSources = Effect.fn("sourceLink.releaseAssets")(function* (cli
6224
6242
  } })), Effect.catch((error) => Effect.succeed(gap(error.issue))));
6225
6243
  }
6226
6244
  return Effect.succeed(gap("no release of the repository publishes this exact binary"));
6227
- });
6245
+ }, { concurrency: 4 });
6228
6246
  });
6229
6247
  // src/audit/brew.ts
6230
6248
  const caskArchiveUnavailable = "compiled cask payloads are not source archives";
@@ -6290,7 +6308,7 @@ const sourceDiff = Effect.fn("brew.sourceDiff")(function* (client, workspace, pa
6290
6308
  const [gap] = yield* Effect.all([sourceTree(client, pair.old, path.join(workspace, files.sourceFrom)).pipe(Effect.as(""), Effect.catch((error) => {
6291
6309
  const reason = historicalGap(pair.old.url, error);
6292
6310
  return reason === void 0 ? Effect.fail(labelled("download old brew source archive")(error)) : Effect.succeed(reason);
6293
- })), sourceTree(client, pair.new, path.join(workspace, files.source)).pipe(Effect.mapError(labelled("download new brew source archive")))], { concurrency: 2 });
6311
+ })), sourceTree(client, pair.new, path.join(workspace, files.source)).pipe(Effect.mapError(labelled("download new brew source archive")))], { concurrency: "unbounded" });
6294
6312
  if (gap !== "") return {
6295
6313
  diff: Buffer.alloc(0),
6296
6314
  reason: gap,
@@ -6327,9 +6345,11 @@ const sourceEvidence = Effect.fn("brew.sourceEvidence")(function* (client, works
6327
6345
  };
6328
6346
  });
6329
6347
  const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace, identity, plan, key) {
6330
- const formula = yield* materializeFormula(workspace, plan.formula);
6331
- const source = yield* sourceEvidence(client, workspace, archives(plan.formula), plan.sourceGap);
6332
- const payload = yield* materializePayload(client, workspace, identity.delta.artifact, plan.payload);
6348
+ const [formula, source, payload] = yield* Effect.all([
6349
+ materializeFormula(workspace, plan.formula),
6350
+ sourceEvidence(client, workspace, archives(plan.formula), plan.sourceGap),
6351
+ materializePayload(client, workspace, identity.delta.artifact, plan.payload)
6352
+ ], { concurrency: "unbounded" });
6333
6353
  const goSource = source.included ? void 0 : yield* goSourceEvidence(client, workspace, identity.delta);
6334
6354
  const sources = goSource?.included === true || source.included ? [] : [...plan.repository === void 0 ? [] : [plan.repository], ...yield* readBuildInfoSources(workspace)];
6335
6355
  const repository = sources.length === 0 ? void 0 : yield* materializeSources(workspace, sources);
@@ -6356,7 +6376,7 @@ const resolve$2 = Effect.fn("brew.resolve")(function* (client, identity) {
6356
6376
  const formulaGap = formula.fromGap === "" ? archiveUnavailableReason(archives(formula)) : "the installed version's formula snapshot is unavailable";
6357
6377
  const sourceGap = kind === "cask" ? caskArchiveUnavailable : formulaGap;
6358
6378
  const payload = yield* resolvePayload(info, artifact, from, to, formula.from?.text);
6359
- const released = sourceGap !== "" && payload.old.url !== "" && payload.new.url !== "" ? yield* Effect.all([releaseTagSource(payload.old.url), releaseTagSource(payload.new.url)], { concurrency: 2 }) : void 0;
6379
+ const released = sourceGap !== "" && payload.old.url !== "" && payload.new.url !== "" ? yield* Effect.all([releaseTagSource(payload.old.url), releaseTagSource(payload.new.url)], { concurrency: "unbounded" }) : void 0;
6360
6380
  const repository = released === void 0 || released.every((side) => "gap" in side) ? void 0 : {
6361
6381
  label: artifact,
6362
6382
  from: released[0],
@@ -6421,7 +6441,7 @@ const workspaceOf$1 = Effect.fn("githubActions.workspace")(function* (workspace,
6421
6441
  const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubActions.prepare")(function* (_client, identity) {
6422
6442
  const { artifact, from, to } = identity.delta;
6423
6443
  const repository = repositoryOf(artifact);
6424
- const [fromCommit, toCommit] = yield* Effect.all([tagCommit(repository, from, options), tagCommit(repository, to, options)], { concurrency: 2 }).pipe(Effect.mapError(unavailable$1));
6444
+ const [fromCommit, toCommit] = yield* Effect.all([tagCommit(repository, from, options), tagCommit(repository, to, options)], { concurrency: "unbounded" }).pipe(Effect.mapError(unavailable$1));
6425
6445
  const listed = identity.context["revision"];
6426
6446
  if (listed !== toCommit) return yield* new EvidenceUnavailable({
6427
6447
  source: repository,
@@ -6447,7 +6467,7 @@ const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubAc
6447
6467
  const prepareGitHubAction = githubActionPreparer(githubRepository);
6448
6468
  // src/audit/pypi.ts
6449
6469
  const optional = (schema) => Schema.optionalKey(Schema.NullOr(schema));
6450
- const Release$1 = Schema.Struct({ urls: optional(Schema.Array(Schema.Struct({
6470
+ const Release$2 = Schema.Struct({ urls: optional(Schema.Array(Schema.Struct({
6451
6471
  filename: optional(Schema.String),
6452
6472
  url: optional(Schema.String),
6453
6473
  packagetype: optional(Schema.String),
@@ -6538,7 +6558,7 @@ const artifactPlan = ([file, reason]) => {
6538
6558
  };
6539
6559
  const resolve$1 = Effect.fn("pypi.resolve")(function* (client, name, version, pythonVersion) {
6540
6560
  const source = releaseUrl(name, version);
6541
- const files = ((yield* getJson(client, source, Release$1).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6561
+ const files = ((yield* getJson(client, source, Release$2).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6542
6562
  source,
6543
6563
  issue: error.issue
6544
6564
  })))).urls ?? []).map((file) => ({
@@ -6618,12 +6638,14 @@ const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace,
6618
6638
  issue: error instanceof Error ? error.message : String(error)
6619
6639
  })
6620
6640
  });
6621
- const [from, to] = yield* Effect.all([materialize(client, plans.from, path.join(workspace, files.from), path.join(workspace, files.wheelFrom)), materialize(client, plans.to, path.join(workspace, files.to), path.join(workspace, files.wheelTo))], { concurrency: 2 });
6622
- const sources = yield* treeDiff(workspace, files.from, files.to);
6623
- const records = yield* writeFileDiff(workspace, files.pypiFrom, files.pypiTo, manifestJson(from), manifestJson(to));
6624
- const sourceBinaries = yield* writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(from.publisher_sdist?.archive_manifest)), json(binarySummaries(to.publisher_sdist?.archive_manifest)));
6625
- const wheels = yield* treeDiff(workspace, files.wheelFrom, files.wheelTo);
6626
- const wheelBinaries = yield* writeFileDiff(workspace, files.binaryWheelFrom, files.binaryWheelTo, json(binarySummaries(from.compatible_universal_wheel?.archive_manifest)), json(binarySummaries(to.compatible_universal_wheel?.archive_manifest)));
6641
+ const [from, to] = yield* Effect.all([materialize(client, plans.from, path.join(workspace, files.from), path.join(workspace, files.wheelFrom)), materialize(client, plans.to, path.join(workspace, files.to), path.join(workspace, files.wheelTo))], { concurrency: "unbounded" });
6642
+ const [sources, records, sourceBinaries, wheels, wheelBinaries] = yield* Effect.all([
6643
+ treeDiff(workspace, files.from, files.to),
6644
+ writeFileDiff(workspace, files.pypiFrom, files.pypiTo, manifestJson(from), manifestJson(to)),
6645
+ writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(from.publisher_sdist?.archive_manifest)), json(binarySummaries(to.publisher_sdist?.archive_manifest))),
6646
+ treeDiff(workspace, files.wheelFrom, files.wheelTo),
6647
+ writeFileDiff(workspace, files.binaryWheelFrom, files.binaryWheelTo, json(binarySummaries(from.compatible_universal_wheel?.archive_manifest)), json(binarySummaries(to.compatible_universal_wheel?.archive_manifest)))
6648
+ ], { concurrency: "unbounded" });
6627
6649
  const detail = `Checksum-verified PyPI publisher artifacts; ${pairedDetail("source distributions", from.publisher_sdist, to.publisher_sdist)}; ${pairedDetail("compatible universal wheel candidates", from.compatible_universal_wheel, to.compatible_universal_wheel)}; the installer’s exact resolver selection is not known.`;
6628
6650
  return yield* write(workspace, identity, Buffer.concat([
6629
6651
  sources.diff,
@@ -6640,7 +6662,7 @@ const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace,
6640
6662
  const preparePyPI = Effect.fn("pypi.prepare")(function* (client, identity) {
6641
6663
  const { artifact, from, to } = identity.delta;
6642
6664
  const python = identity.context["python"] ?? "";
6643
- const [fromPlan, toPlan] = yield* Effect.all([resolve$1(client, artifact, from, python), resolve$1(client, artifact, to, python)], { concurrency: 2 });
6665
+ const [fromPlan, toPlan] = yield* Effect.all([resolve$1(client, artifact, from, python), resolve$1(client, artifact, to, python)], { concurrency: "unbounded" });
6644
6666
  const plans = {
6645
6667
  from: fromPlan,
6646
6668
  to: toPlan
@@ -6733,7 +6755,7 @@ const resolveDependencies = Effect.fn("npmDependencies.resolve")(function* (clie
6733
6755
  const outcomes = yield* Effect.forEach(changedNames, (dependency) => Effect.gen(function* () {
6734
6756
  if (!packageName.test(dependency)) return Result.fail("its name is not a valid registry package name");
6735
6757
  const side = (declared) => declared === void 0 ? Effect.undefined : resolveSide(client, host, dependency, declared);
6736
- const resolved = yield* Effect.result(Effect.all([side(before.get(dependency)), side(after.get(dependency))], { concurrency: 2 }));
6758
+ const resolved = yield* Effect.result(Effect.all([side(before.get(dependency)), side(after.get(dependency))], { concurrency: "unbounded" }));
6737
6759
  return Result.isFailure(resolved) ? Result.fail(resolved.failure.issue) : Result.succeed(resolved.success);
6738
6760
  }), { concurrency: 4 });
6739
6761
  for (const [index, dependency] of changedNames.entries()) {
@@ -6840,8 +6862,8 @@ const materializeDependencies = Effect.fn("npmDependencies.materialize")(functio
6840
6862
  issue: error instanceof Error ? error.message : String(error)
6841
6863
  })
6842
6864
  });
6843
- const sides = yield* Effect.forEach(dependencies.changed, (dependency) => Effect.all([fetchSide(client, dependency.from, path.join(workspace, files.dependenciesFrom, dependency.name)), fetchSide(client, dependency.to, path.join(workspace, files.dependenciesTo, dependency.name))], { concurrency: 2 }), { concurrency: 4 });
6844
- const summaries = yield* writeFileDiff(workspace, files.dependencyListFrom, files.dependencyListTo, record(dependencies, dependencies.changed.map((dependency, index) => ({
6865
+ const sides = yield* Effect.forEach(dependencies.changed, (dependency) => Effect.all([fetchSide(client, dependency.from, path.join(workspace, files.dependenciesFrom, dependency.name)), fetchSide(client, dependency.to, path.join(workspace, files.dependenciesTo, dependency.name))], { concurrency: "unbounded" }), { concurrency: 4 });
6866
+ const [summaries, trees] = yield* Effect.all([writeFileDiff(workspace, files.dependencyListFrom, files.dependencyListTo, record(dependencies, dependencies.changed.map((dependency, index) => ({
6845
6867
  name: dependency.name,
6846
6868
  ...dependency.from === void 0 ? {} : { resolved: dependency.from },
6847
6869
  ...sides[index]?.[0] === void 0 ? {} : { fetched: sides[index][0] }
@@ -6849,8 +6871,7 @@ const materializeDependencies = Effect.fn("npmDependencies.materialize")(functio
6849
6871
  name: dependency.name,
6850
6872
  ...dependency.to === void 0 ? {} : { resolved: dependency.to },
6851
6873
  ...sides[index]?.[1] === void 0 ? {} : { fetched: sides[index][1] }
6852
- }))));
6853
- const trees = yield* treeDiff(workspace, files.dependenciesFrom, files.dependenciesTo);
6874
+ })))), treeDiff(workspace, files.dependenciesFrom, files.dependenciesTo)], { concurrency: "unbounded" });
6854
6875
  const failed = dependencies.changed.filter((_, index) => (sides[index] ?? []).some((side) => side?.error !== void 0));
6855
6876
  const included = dependencies.changed.map((dependency) => `${dependency.name} ${dependency.from?.plan.version ?? "(new)"}${dependency.from?.lowest === true ? " (lowest of its range)" : ""} -> ${dependency.to?.plan.version ?? "(removed)"}${dependency.to?.bundled === true ? " (now a devDependency, so probably bundled into the package)" : ""}`);
6856
6877
  const noneIncluded = dependencies.omitted.length === 0 ? "No direct dependency's declared version changed." : "No changed direct dependency is compared.";
@@ -6883,10 +6904,12 @@ const materializeDependencies = Effect.fn("npmDependencies.materialize")(functio
6883
6904
  // src/audit/registry-evidence.ts
6884
6905
  const registryDetail = "Both published packages were downloaded and their checksums verified. File types and modes, and any native binaries, are summarised without running them.";
6885
6906
  const registryWorkspace = Effect.fn("evidence.registryWorkspace")(function* (client, workspace, identity, plans, npm, key) {
6886
- const [from, to] = yield* Effect.all([materialize$2(client, plans.from, path.join(workspace, files.from)), materialize$2(client, plans.to, path.join(workspace, files.to))], { concurrency: 2 });
6887
- const artifacts = yield* writeFileDiff(workspace, files.artifactFrom, files.artifactTo, manifestJson(from), manifestJson(to));
6888
- const trees = yield* treeDiff(workspace, files.from, files.to);
6889
- const binaries = yield* writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(from)), json(binarySummaries(to)));
6907
+ const [from, to] = yield* Effect.all([materialize$2(client, plans.from, path.join(workspace, files.from)), materialize$2(client, plans.to, path.join(workspace, files.to))], { concurrency: "unbounded" });
6908
+ const [artifacts, trees, binaries] = yield* Effect.all([
6909
+ writeFileDiff(workspace, files.artifactFrom, files.artifactTo, manifestJson(from), manifestJson(to)),
6910
+ treeDiff(workspace, files.from, files.to),
6911
+ writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(from)), json(binarySummaries(to)))
6912
+ ], { concurrency: "unbounded" });
6890
6913
  const treeDetail = [registryDetail, partial("artifact", trees.omitted)].filter((sentence) => sentence !== "").join(" ");
6891
6914
  if (npm === void 0) return yield* write(workspace, identity, Buffer.concat([
6892
6915
  artifacts,
@@ -6920,7 +6943,7 @@ const npmExtras = Effect.fn("evidence.npmExtras")(function* (client, name, from,
6920
6943
  cpu: process.arch
6921
6944
  });
6922
6945
  const verifier = yield* Effect.cached(sigstoreVerifier);
6923
- const [fromSource, toSource] = yield* Effect.all([npmSource(client, name, from, fromManifest, verifier), npmSource(client, name, to, toManifest, verifier)], { concurrency: 2 });
6946
+ const [fromSource, toSource] = yield* Effect.all([npmSource(client, name, from, fromManifest, verifier), npmSource(client, name, to, toManifest, verifier)], { concurrency: "unbounded" });
6924
6947
  const { directory } = npmRepository(toManifest);
6925
6948
  return {
6926
6949
  dependencies,
@@ -6937,8 +6960,8 @@ const prepareRegistry = Effect.fn("evidence.prepareRegistry")(function* (client,
6937
6960
  const manifests = ecosystem === "npm" ? yield* Effect.all({
6938
6961
  from: npmVersion(client, artifact, from),
6939
6962
  to: npmVersion(client, artifact, to)
6940
- }, { concurrency: 2 }) : void 0;
6941
- const [fromPlan, toPlan] = yield* Effect.all(manifests === void 0 ? [resolve$3(client, ecosystem, artifact, from), resolve$3(client, ecosystem, artifact, to)] : [npmPlan(artifact, from, manifests.from), npmPlan(artifact, to, manifests.to)], { concurrency: 2 });
6963
+ }, { concurrency: "unbounded" }) : void 0;
6964
+ const [fromPlan, toPlan] = yield* Effect.all(manifests === void 0 ? [resolve$3(client, ecosystem, artifact, from), resolve$3(client, ecosystem, artifact, to)] : [npmPlan(artifact, from, manifests.from), npmPlan(artifact, to, manifests.to)], { concurrency: "unbounded" });
6942
6965
  const plans = {
6943
6966
  from: fromPlan,
6944
6967
  to: toPlan
@@ -7313,10 +7336,9 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7313
7336
  }));
7314
7337
  const started = yield* Clock.currentTimeMillis;
7315
7338
  const base = path.join(root, "workspaces");
7316
- for (const name of yield* children(base)) {
7317
- if (name.endsWith(".lock")) continue;
7339
+ yield* Effect.forEach((yield* children(base)).filter((name) => !name.endsWith(".lock")), (name) => {
7318
7340
  const workspace = path.join(base, name);
7319
- yield* Effect.scoped(Effect.gen(function* () {
7341
+ return Effect.scoped(Effect.gen(function* () {
7320
7342
  if (!(yield* tryHold(workspace))) return;
7321
7343
  const prepared = yield* Effect.promise(async () => await stat(path.join(workspace, files.meta)).then((info) => info.mtimeMs, () => 0));
7322
7344
  if (started - prepared > Duration.toMillis(keptWorkspaces)) {
@@ -7324,9 +7346,15 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7324
7346
  return;
7325
7347
  }
7326
7348
  const entries = yield* Effect.promise(async () => await readdir(workspace, { withFileTypes: true }).catch(() => []));
7327
- for (const entry of entries) if (entry.isDirectory()) yield* discard(path.join(workspace, entry.name), name);
7349
+ yield* Effect.forEach(entries.filter((entry) => entry.isDirectory()), (entry) => discard(path.join(workspace, entry.name), name), {
7350
+ concurrency: "unbounded",
7351
+ discard: true
7352
+ });
7328
7353
  })).pipe(Effect.ignore);
7329
- }
7354
+ }, {
7355
+ concurrency: 8,
7356
+ discard: true
7357
+ });
7330
7358
  yield* Effect.forkScoped(Effect.promise(async () => {
7331
7359
  for (const name of await readdir(trash).catch(() => [])) await rm(path.join(trash, name), {
7332
7360
  recursive: true,
@@ -7543,12 +7571,15 @@ const workflowFiles = Effect.fn("workflowFiles")(function* (root) {
7543
7571
  for (const name of ["action.yml", "action.yaml"]) if (yield* present(path.join(dir, name))) return Option.some(path.join(dir, name));
7544
7572
  return Option.none();
7545
7573
  });
7546
- const workflows = yield* listed(path.join(".github", "workflows"), false);
7547
- const actions = yield* listed(path.join(".github", "actions"), true);
7574
+ const [workflows, actions, own] = yield* Effect.all([
7575
+ listed(path.join(".github", "workflows"), false),
7576
+ listed(path.join(".github", "actions"), true),
7577
+ actionFile("")
7578
+ ], { concurrency: "unbounded" });
7548
7579
  const pending = [
7549
7580
  ...workflows.filter((file) => /\.ya?ml$/u.test(file)),
7550
7581
  ...actions.filter((file) => /^action\.ya?ml$/u.test(path.basename(file))),
7551
- ...Option.toArray(yield* actionFile(""))
7582
+ ...Option.toArray(own)
7552
7583
  ];
7553
7584
  const seen = /* @__PURE__ */ new Set();
7554
7585
  const files = [];
@@ -7748,7 +7779,7 @@ const toPackage$1 = ({ uses, resolved, source }) => {
7748
7779
  };
7749
7780
  };
7750
7781
  const publicTags = (url) => tags(url, { timeout: lsRemoteTimeout }).pipe(Effect.mapError((error) => error.issue), Effect.result);
7751
- const make$12 = (root, origin, readPublic = publicTags) => Effect.gen(function* () {
7782
+ const make$13 = (root, origin, readPublic = publicTags) => Effect.gen(function* () {
7752
7783
  const exec = yield* Exec;
7753
7784
  const fs = yield* FileSystem.FileSystem;
7754
7785
  const read = (file) => readText(file).pipe(Effect.provideService(FileSystem.FileSystem, fs));
@@ -7873,7 +7904,7 @@ const discover$4 = Effect.fn("GitHubActions.discover")(function* (dir) {
7873
7904
  ".github",
7874
7905
  "action.yml",
7875
7906
  "action.yaml"
7876
- ], exists)).length === 0) return [];
7907
+ ], exists, { concurrency: "unbounded" })).length === 0) return [];
7877
7908
  const origin = yield* exec.stdout({
7878
7909
  command: "git",
7879
7910
  args: [
@@ -7884,11 +7915,11 @@ const discover$4 = Effect.fn("GitHubActions.discover")(function* (dir) {
7884
7915
  cwd: root
7885
7916
  }).pipe(Effect.option);
7886
7917
  const host = Option.isSome(origin) ? hostOf(origin.value.trim()) : void 0;
7887
- return [yield* make$12(root, host === "github.com" ? void 0 : host)];
7918
+ return [yield* make$13(root, host === "github.com" ? void 0 : host)];
7888
7919
  });
7889
7920
  // src/process/on-path.ts
7890
7921
  const executable = (file) => Effect.promise(async () => await access(file, constants.X_OK).then(() => true, () => false));
7891
- const onPath = (command) => Effect.forEach((process.env["PATH"] ?? "").split(path.delimiter).filter((dir) => dir !== ""), (dir) => executable(path.join(dir, command))).pipe(Effect.map((found) => found.includes(true)));
7922
+ const onPath = (command) => Effect.forEach((process.env["PATH"] ?? "").split(path.delimiter).filter((dir) => dir !== ""), (dir) => executable(path.join(dir, command)), { concurrency: "unbounded" }).pipe(Effect.map((found) => found.includes(true)));
7892
7923
  // src/process/Sudo.ts
7893
7924
  var Sudo = class extends Context.Service()("upup/Sudo") {};
7894
7925
  const asRoot = (invocation) => [
@@ -7898,7 +7929,7 @@ const asRoot = (invocation) => [
7898
7929
  ];
7899
7930
  const enablesTouchId = (pam) => pam.split("\n").some((line) => /^\s*auth\s+\S+\s+pam_tid\.so\b/u.test(line));
7900
7931
  const pamFiles = ["/etc/pam.d/sudo_local", "/etc/pam.d/sudo"];
7901
- const make$11 = (interactive) => Effect.gen(function* () {
7932
+ const make$12 = (interactive) => Effect.gen(function* () {
7902
7933
  const exec = yield* Exec;
7903
7934
  const fs = yield* FileSystem.FileSystem;
7904
7935
  const asking = yield* SubscriptionRef.make(void 0);
@@ -7957,8 +7988,8 @@ const make$11 = (interactive) => Effect.gen(function* () {
7957
7988
  answer
7958
7989
  });
7959
7990
  });
7960
- const layer$4 = Layer.effect(Sudo, make$11(true));
7961
- const layerNoPrompt = Layer.effect(Sudo, make$11(false));
7991
+ const layer$4 = Layer.effect(Sudo, make$12(true));
7992
+ const layerNoPrompt = Layer.effect(Sudo, make$12(false));
7962
7993
  // src/apps/app-store.ts
7963
7994
  const countryOf = (locale) => (/[@;]rg=(?<region>[a-z]{2})/iu.exec(locale)?.groups?.["region"] ?? /^[a-z]{2,3}[_-](?<region>[a-z]{2})\b/iu.exec(locale)?.groups?.["region"] ?? "us").toLowerCase();
7964
7995
  const Lookup = Schema.Struct({ results: Schema.Array(Schema.Struct({
@@ -8482,8 +8513,7 @@ const read = Effect.fn("bundles.read")(function* (bundle, arch) {
8482
8513
  const short = nonEmpty(info.value.CFBundleShortVersionString);
8483
8514
  const build = nonEmpty(info.value.CFBundleVersion);
8484
8515
  const executable = nonEmpty(info.value.CFBundleExecutable);
8485
- const sparkle = yield* sparkleKeysOf(bundle, info.value);
8486
- const source = yield* sourceOf(bundle, bundleId, nonEmpty(info.value.SUFeedURL), arch);
8516
+ const [sparkle, source] = yield* Effect.all([sparkleKeysOf(bundle, info.value), sourceOf(bundle, bundleId, nonEmpty(info.value.SUFeedURL), arch)], { concurrency: "unbounded" });
8487
8517
  return Option.some({
8488
8518
  path: bundle,
8489
8519
  name: path.basename(bundle, ".app"),
@@ -8496,23 +8526,25 @@ const read = Effect.fn("bundles.read")(function* (bundle, arch) {
8496
8526
  ...plist === void 0 ? {} : { plist }
8497
8527
  });
8498
8528
  });
8499
- const walk = Effect.fn("bundles.walk")(function* (roots, wanted) {
8529
+ const isApp = (entry) => entry.endsWith(".app");
8530
+ const isLeftover = (entry) => entry.endsWith(".app.upup-old");
8531
+ const wanted$1 = (entry) => isApp(entry) || isLeftover(entry);
8532
+ const walk = Effect.fn("bundles.walk")(function* (roots) {
8500
8533
  const fs = yield* FileSystem.FileSystem;
8501
8534
  const entries = (dir) => fs.readDirectory(dir).pipe(Effect.orElseSucceed(() => []), Effect.map((names) => names.toSorted((left, right) => left.localeCompare(right)).map((name) => path.join(dir, name))));
8502
8535
  const isLink = (entry) => fs.readLink(entry).pipe(Effect.isSuccess);
8503
8536
  const isDirectory = (entry) => fs.stat(entry).pipe(Effect.map((info) => info.type === "Directory"), Effect.orElseSucceed(() => false));
8504
- const found = [];
8505
- for (const root of roots) for (const entry of yield* entries(root)) {
8506
- if (yield* isLink(entry)) continue;
8507
- if (wanted(entry)) found.push(entry);
8508
- else if (!/\.(?:app|localized|upup-old|upup-new)$/u.test(entry) && (yield* isDirectory(entry))) {
8509
- for (const nested of yield* entries(entry)) if (wanted(nested) && !(yield* isLink(nested))) found.push(nested);
8510
- }
8511
- }
8512
- return found;
8537
+ const nestedIn = (folder) => entries(folder).pipe(Effect.flatMap((listed) => Effect.filter(listed.filter(wanted$1), (nested) => Effect.map(isLink(nested), (link) => !link), { concurrency: "unbounded" })));
8538
+ const walked = (yield* Effect.forEach(roots, (root) => entries(root).pipe(Effect.flatMap((listed) => Effect.forEach(listed, (entry) => Effect.gen(function* () {
8539
+ if (yield* isLink(entry)) return [];
8540
+ if (wanted$1(entry)) return [entry];
8541
+ return !/\.(?:app|localized|upup-old|upup-new)$/u.test(entry) && (yield* isDirectory(entry)) ? yield* nestedIn(entry) : [];
8542
+ }), { concurrency: "unbounded" })), Effect.map((listed) => listed.flat())), { concurrency: "unbounded" })).flat();
8543
+ return {
8544
+ apps: walked.filter(isApp),
8545
+ leftovers: walked.filter(isLeftover)
8546
+ };
8513
8547
  });
8514
- const paths = (roots) => walk(roots, (entry) => entry.endsWith(".app"));
8515
- const leftovers = (roots) => walk(roots, (entry) => entry.endsWith(".app.upup-old"));
8516
8548
  // src/apps/running.ts
8517
8549
  const command$4 = (name, args) => ({
8518
8550
  command: name,
@@ -8575,7 +8607,7 @@ const command$3 = (name, args) => ({
8575
8607
  });
8576
8608
  const host = Effect.gen(function* () {
8577
8609
  const exec = yield* Exec;
8578
- const [arm64, os] = yield* Effect.all([exec.run(command$3("sysctl", ["-n", "hw.optional.arm64"])).pipe(Effect.map((output) => output.exitCode === 0 && output.stdout.trim() === "1"), Effect.orElseSucceed(() => false)), exec.stdout(command$3("sw_vers", ["-productVersion"])).pipe(Effect.map((text) => text.trim()))], { concurrency: 2 });
8610
+ const [arm64, os] = yield* Effect.all([exec.run(command$3("sysctl", ["-n", "hw.optional.arm64"])).pipe(Effect.map((output) => output.exitCode === 0 && output.stdout.trim() === "1"), Effect.orElseSucceed(() => false)), exec.stdout(command$3("sw_vers", ["-productVersion"])).pipe(Effect.map((text) => text.trim()))], { concurrency: "unbounded" });
8579
8611
  return {
8580
8612
  arch: arm64 ? "arm64" : "x86_64",
8581
8613
  os
@@ -8653,7 +8685,7 @@ const stage = Effect.fn("install.stage")(function* (client, wanted) {
8653
8685
  const found = (yield* Effect.forEach(candidates, (candidate) => readInfo(candidate).pipe(Effect.map((info) => ({
8654
8686
  path: candidate,
8655
8687
  info
8656
- }))))).find(({ info }) => Option.isSome(info) && info.value.CFBundleIdentifier === wanted.bundleId);
8688
+ }))), { concurrency: 4 })).find(({ info }) => Option.isSome(info) && info.value.CFBundleIdentifier === wanted.bundleId);
8657
8689
  if (found === void 0 || Option.isNone(found.info)) return yield* refused(`its download holds no app with the bundle ID ${wanted.bundleId}`);
8658
8690
  const app = found.path;
8659
8691
  const info = found.info.value;
@@ -8672,7 +8704,7 @@ const stage = Effect.fn("install.stage")(function* (client, wanted) {
8672
8704
  ])).pipe(Effect.mapError((error) => refused(`codesign failed: ${error.output.trim()}`)), Effect.flatMap((output) => {
8673
8705
  const hash = /^CDHash=(?<hash>[0-9a-f]+)$/mu.exec(output.stderr)?.groups?.["hash"];
8674
8706
  return hash === void 0 ? Effect.fail(unsigned(output.stderr.trim())) : Effect.succeed(hash);
8675
- }))).pipe(Effect.map((hashes) => `${requirement(wanted.bundleId, wanted.team)} and info [CFBundleShortVersionString] = "${wanted.version.replaceAll(/["\\]/gu, "\\$&")}" and (${hashes.map((hash) => `cdhash H"${hash}"`).join(" or ")})`));
8707
+ })), { concurrency: 2 }).pipe(Effect.map((hashes) => `${requirement(wanted.bundleId, wanted.team)} and info [CFBundleShortVersionString] = "${wanted.version.replaceAll(/["\\]/gu, "\\$&")}" and (${hashes.map((hash) => `cdhash H"${hash}"`).join(" or ")})`));
8676
8708
  const signed = yield* exec.run(command$2("codesign", [
8677
8709
  "--verify",
8678
8710
  "--deep",
@@ -8750,7 +8782,7 @@ const locate$3 = Effect.gen(function* () {
8750
8782
  return Option.isNone(manifest) || !(yield* fs.exists(zip).pipe(Effect.orElseSucceed(() => false))) ? Option.none() : yield* decode(manifest.value, zip);
8751
8783
  });
8752
8784
  // src/apps/swap.ts
8753
- const tools = {
8785
+ const tools$2 = {
8754
8786
  ps: "/bin/ps",
8755
8787
  ditto: "/usr/bin/ditto",
8756
8788
  mv: "/bin/mv",
@@ -9155,16 +9187,27 @@ const makeWith = (options) => Effect.gen(function* () {
9155
9187
  }
9156
9188
  };
9157
9189
  });
9190
+ const leftoverNotice = (old) => {
9191
+ const app = old.slice(0, -9);
9192
+ return fs.exists(app).pipe(Effect.orElseSucceed(() => true), Effect.map((present) => ({ message: present ? `${old} is the old copy of ${path.basename(app)} that an update left behind; move it to the Trash` : `${old} is ${path.basename(app)} from before an update that did not finish; rename it back to ${path.basename(app)}` })));
9193
+ };
9194
+ const scan = Effect.gen(function* () {
9195
+ const [here, walked] = yield* Effect.all([host, walk(options.roots)], { concurrency: "unbounded" });
9196
+ const [bundles, left] = yield* Effect.all([Effect.forEach(walked.apps, (app) => read(app, here.arch), { concurrency: inFlight }), Effect.forEach(walked.leftovers, leftoverNotice, { concurrency: "unbounded" })], { concurrency: "unbounded" });
9197
+ return {
9198
+ here,
9199
+ found: bundles.flatMap((bundle) => Option.toArray(bundle)),
9200
+ left
9201
+ };
9202
+ });
9158
9203
  const list = Effect.gen(function* () {
9159
- const owners = yield* Effect.result(casks);
9204
+ const [owners, { here, found, left }] = yield* Effect.all([Effect.result(casks), scan], { concurrency: "unbounded" });
9160
9205
  if (Result.isFailure(owners)) return {
9161
9206
  packages: [],
9162
9207
  notices: [{ message: `upup lists no apps while it cannot read which apps Homebrew installed: ${describeError(owners.failure)}` }]
9163
9208
  };
9164
- const here = yield* host;
9165
9209
  const locale = yield* Effect.cached(country);
9166
9210
  const caskOf = new Map(owners.success.flatMap((cask) => caskApps(cask).map((app) => [app, cask])));
9167
- const found = (yield* Effect.forEach(yield* paths(options.roots), (app) => read(app, here.arch), { concurrency: inFlight })).flatMap((bundle) => Option.toArray(bundle));
9168
9211
  const ids = /* @__PURE__ */ new Set();
9169
9212
  const kept = found.filter((bundle) => {
9170
9213
  const cask = caskOf.get(path.basename(bundle.path));
@@ -9192,13 +9235,9 @@ const makeWith = (options) => Effect.gen(function* () {
9192
9235
  here,
9193
9236
  rows: new Map(all.map((row) => [row.pkg.id, row.listed]))
9194
9237
  });
9195
- const leftovers$1 = yield* Effect.forEach(yield* leftovers(options.roots), (old) => {
9196
- const app = old.slice(0, -9);
9197
- return fs.exists(app).pipe(Effect.orElseSucceed(() => true), Effect.map((present) => ({ message: present ? `${old} is the old copy of ${path.basename(app)} that an update left behind; move it to the Trash` : `${old} is ${path.basename(app)} from before an update that did not finish; rename it back to ${path.basename(app)}` })));
9198
- });
9199
9238
  return {
9200
9239
  packages: all.map((row) => row.pkg),
9201
- notices: [...all.flatMap((row) => row.notice), ...leftovers$1]
9240
+ notices: [...all.flatMap((row) => row.notice), ...left]
9202
9241
  };
9203
9242
  }).pipe(Effect.withSpan("Apps.list"), provide);
9204
9243
  const observe = Effect.fn("Apps.observe")(function* (pkg) {
@@ -9233,7 +9272,7 @@ const makeWith = (options) => Effect.gen(function* () {
9233
9272
  const ids = yield* Effect.forEach(paths, (app) => readInfo(app).pipe(Effect.flatMap((info) => Option.match(info, {
9234
9273
  onNone: () => Effect.fail(new Unsupported({ reason: `upup could not read ${app}` })),
9235
9274
  onSome: (found) => Effect.succeed(found.CFBundleIdentifier)
9236
- }))));
9275
+ }))), { concurrency: inFlight });
9237
9276
  const found = {
9238
9277
  paths,
9239
9278
  ids: new Set(ids)
@@ -9242,7 +9281,7 @@ const makeWith = (options) => Effect.gen(function* () {
9242
9281
  return found;
9243
9282
  });
9244
9283
  const queue = yield* Ref.make([]);
9245
- const using = options.tools ?? tools;
9284
+ const using = options.tools ?? tools$2;
9246
9285
  const helper = options.sparkle === void 0 ? locate$3.pipe(Effect.provideService(FileSystem.FileSystem, fs)) : Effect.succeed(options.sparkle);
9247
9286
  const sparkleOf = (pkg, install, build) => {
9248
9287
  const refused = (reason) => new InstallRefused({
@@ -9442,7 +9481,7 @@ const makeWith = (options) => Effect.gen(function* () {
9442
9481
  })
9443
9482
  };
9444
9483
  });
9445
- const make$10 = onPath("brew").pipe(Effect.flatMap((brewInstalled) => makeWith({
9484
+ const make$11 = onPath("brew").pipe(Effect.flatMap((brewInstalled) => makeWith({
9446
9485
  roots: ["/Applications", path.join(homedir(), "Applications")],
9447
9486
  brew: brewInstalled
9448
9487
  })));
@@ -9468,7 +9507,7 @@ const parseList = (text) => text.split("\n").flatMap((line) => {
9468
9507
  fromCratesIo: source === void 0
9469
9508
  }];
9470
9509
  });
9471
- const make$9 = Effect.gen(function* () {
9510
+ const make$10 = Effect.gen(function* () {
9472
9511
  const exec = yield* Exec;
9473
9512
  const client = yield* HttpClient.HttpClient;
9474
9513
  const installed = exec.stdout(cargo$1(["install", "--list"])).pipe(Effect.map(parseList));
@@ -9514,7 +9553,7 @@ const nestedDirs = Effect.fn("nestedDirs")(function* (dir, names) {
9514
9553
  args,
9515
9554
  cwd: dir
9516
9555
  }).pipe(Effect.orElseSucceed(() => ""));
9517
- const [prefix, listed] = yield* Effect.all([git("rev-parse", "--show-prefix"), git("ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...names.map((name) => `:(glob)**/${name}`))], { concurrency: 2 });
9556
+ const [prefix, listed] = yield* Effect.all([git("rev-parse", "--show-prefix"), git("ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...names.map((name) => `:(glob)**/${name}`))], { concurrency: "unbounded" });
9518
9557
  const files = listed.split("\0").filter((file) => {
9519
9558
  const folders = file.split("/").slice(0, -1);
9520
9559
  return folders.length > 0 && !folders.includes("node_modules") && !(path.basename(file) === "go.mod" && ignoredByGo(folders));
@@ -9541,21 +9580,32 @@ const failing = (name, error) => ({
9541
9580
  observe: () => Effect.succeedNone
9542
9581
  });
9543
9582
  const locateAll = Effect.fn("locateAll")(function* (dir, nested, manifest, locate) {
9544
- const own = yield* locate(dir);
9545
- const below = yield* Effect.forEach(nested, (from) => Effect.map(Effect.result(locate(from)), (result) => [from, result]), { concurrency: 8 });
9583
+ const [own, below] = yield* Effect.all([locate(dir), Effect.forEach(nested, (from) => Effect.map(Effect.result(locate(from)), (result) => [from, result]), { concurrency: 8 })], { concurrency: "unbounded" });
9546
9584
  const found = /* @__PURE__ */ new Map();
9585
+ const claimed = /* @__PURE__ */ new Map();
9547
9586
  const failed = [];
9548
- const add = (located) => {
9549
- if (Option.isSome(located) && !found.has(located.value.root)) found.set(located.value.root, located.value);
9587
+ const add = (from, located) => {
9588
+ if (Option.isNone(located)) return;
9589
+ const { root } = located.value;
9590
+ if (!found.has(root)) found.set(root, located.value);
9591
+ claimed.set(root, [...claimed.get(root) ?? [], from]);
9550
9592
  };
9551
- add(own);
9593
+ add(dir, own);
9552
9594
  const ownRoot = Option.map(own, (located) => located.root);
9553
9595
  const outside = (located) => Option.isSome(located) && placeOf$1(dir, located.value.root) === "" && !Option.contains(ownRoot, located.value.root);
9596
+ const failures = below.flatMap(([from, result]) => Result.isFailure(result) ? [{
9597
+ from,
9598
+ reason: describeError(result.failure)
9599
+ }] : []);
9600
+ const repeated = (from, reason) => failures.some((other) => other.reason === reason && from.startsWith(`${other.from}${path.sep}`));
9554
9601
  for (const [from, result] of below) if (Result.isSuccess(result)) {
9555
- if (!outside(result.success)) add(result.success);
9556
- } else failed.push(failing(path.join(path.relative(dir, from), manifest), result.failure));
9602
+ if (!outside(result.success)) add(from, result.success);
9603
+ } else if (!repeated(from, describeError(result.failure))) failed.push(failing(path.join(path.relative(dir, from), manifest), result.failure));
9557
9604
  return {
9558
- found: [...found.values()],
9605
+ found: [...found.values()].map((located) => ({
9606
+ ...located,
9607
+ claimed: claimed.get(located.root) ?? []
9608
+ })),
9559
9609
  failed
9560
9610
  };
9561
9611
  });
@@ -9642,10 +9692,11 @@ const entriesOf = (manifest, parsed) => {
9642
9692
  for (const [name, spec] of Object.entries(parsed.workspace?.dependencies ?? {})) add(["workspace", "dependencies"], name, spec);
9643
9693
  return entries;
9644
9694
  };
9645
- const cargo = (root, args) => ({
9695
+ const cargo = (cwd, args) => ({
9646
9696
  command: "cargo",
9647
9697
  args,
9648
- cwd: root
9698
+ cwd,
9699
+ env: { RUSTUP_AUTO_INSTALL: "0" }
9649
9700
  });
9650
9701
  const parseManifest = (file, text) => Effect.try({
9651
9702
  try: () => getStaticTOMLValue(parseTOML(text)),
@@ -9670,7 +9721,7 @@ const resolvedByMember = (root, meta) => {
9670
9721
  return [path.relative(root, pkg.manifest_path), deps];
9671
9722
  }));
9672
9723
  };
9673
- const make$8 = (root, place = "") => Effect.gen(function* () {
9724
+ const make$9 = (root, place = "") => Effect.gen(function* () {
9674
9725
  const providerName = placed("cargo", place);
9675
9726
  const exec = yield* Exec;
9676
9727
  const fs = yield* FileSystem.FileSystem;
@@ -9683,6 +9734,7 @@ const make$8 = (root, place = "") => Effect.gen(function* () {
9683
9734
  "metadata",
9684
9735
  "--format-version",
9685
9736
  "1",
9737
+ "--all-features",
9686
9738
  "--locked"
9687
9739
  ]);
9688
9740
  return yield* exec.stdout(invocation).pipe(Effect.flatMap(decodeJson(Metadata, commandLine(invocation))));
@@ -9691,7 +9743,7 @@ const make$8 = (root, place = "") => Effect.gen(function* () {
9691
9743
  const entries = (manifest) => manifestOf(manifest).pipe(Effect.map((parsed) => entriesOf(manifest, parsed)));
9692
9744
  const workspace = Effect.gen(function* () {
9693
9745
  const resolved = resolvedByMember(root, yield* metadata);
9694
- const manifests = yield* Effect.forEach(/* @__PURE__ */ new Set(["Cargo.toml", ...resolved.keys()]), (manifest) => manifestOf(manifest).pipe(Effect.map((parsed) => [manifest, parsed])));
9746
+ const manifests = yield* Effect.forEach(/* @__PURE__ */ new Set(["Cargo.toml", ...resolved.keys()]), (manifest) => manifestOf(manifest).pipe(Effect.map((parsed) => [manifest, parsed])), { concurrency: 8 });
9695
9747
  const inheritors = (key) => manifests.flatMap(([manifest, parsed]) => resolved.has(manifest) && inheritedKeys(parsed).includes(key) ? [manifest] : []);
9696
9748
  return {
9697
9749
  resolved,
@@ -9731,6 +9783,11 @@ const make$8 = (root, place = "") => Effect.gen(function* () {
9731
9783
  };
9732
9784
  });
9733
9785
  const list = Effect.gen(function* () {
9786
+ const lockfile = path.join(root, "Cargo.lock");
9787
+ if (!(yield* fs.exists(lockfile).pipe(Effect.mapError(fileFailed("stat", lockfile))))) return {
9788
+ packages: [],
9789
+ notices: [{ message: "not listed: no Cargo.lock yet" }]
9790
+ };
9734
9791
  const { declared, notices } = yield* declarations;
9735
9792
  return {
9736
9793
  packages: yield* Effect.forEach(declared, ({ pkg }) => newestRelease(client, pkg.artifact, pkg.current).pipe(Effect.map((target) => {
@@ -9812,21 +9869,17 @@ const locate$2 = Effect.fn("CargoWorkspace.locate")(function* (dir) {
9812
9869
  const exec = yield* Exec;
9813
9870
  const manifest = yield* findUp(dir, "Cargo.toml");
9814
9871
  if (Option.isNone(manifest)) return Option.none();
9815
- const root = yield* exec.stdout({
9816
- command: "cargo",
9817
- args: [
9818
- "locate-project",
9819
- "--workspace",
9820
- "--message-format",
9821
- "plain"
9822
- ],
9823
- cwd: manifest.value
9824
- });
9872
+ const root = yield* exec.stdout(cargo(manifest.value, [
9873
+ "locate-project",
9874
+ "--workspace",
9875
+ "--message-format",
9876
+ "plain"
9877
+ ]));
9825
9878
  return Option.some({ root: path.dirname(root.trim()) });
9826
9879
  });
9827
9880
  const discover$3 = Effect.fn("CargoWorkspace.discover")(function* (dir, nested = []) {
9828
9881
  const { found, failed } = yield* locateAll(dir, nested, "Cargo.toml", locate$2);
9829
- return [...failed, ...yield* Effect.forEach(found, ({ root }) => make$8(root, placeOf$1(dir, root)))];
9882
+ return [...failed, ...yield* Effect.forEach(found, ({ root }) => make$9(root, placeOf$1(dir, root)))];
9830
9883
  });
9831
9884
  // src/registry/origin.ts
9832
9885
  const trailing = (url) => url.endsWith("/") ? url : `${url}/`;
@@ -9971,14 +10024,15 @@ const fromBuildInfo = (info, file) => {
9971
10024
  current: main.Version
9972
10025
  }) : Option.none();
9973
10026
  };
9974
- const make$7 = Effect.gen(function* () {
10027
+ const make$8 = Effect.gen(function* () {
9975
10028
  const exec = yield* Exec;
9976
10029
  const fs = yield* FileSystem.FileSystem;
9977
- const goEnv = yield* Effect.cached(exec.stdout(go([
10030
+ const envInvocation = go([
9978
10031
  "env",
9979
10032
  "-json",
9980
10033
  ...envKeys
9981
- ])).pipe(Effect.flatMap(decodeGoJson(GoEnv, `go env -json ${envKeys.join(" ")}`))));
10034
+ ]);
10035
+ const goEnv = yield* Effect.cached(exec.stdout(envInvocation).pipe(Effect.flatMap(decodeGoJson(GoEnv, commandLine(envInvocation)))));
9982
10036
  const binDir = goEnv.pipe(Effect.map((env) => env.GOBIN || path.join(env.GOPATH.split(":")[0] ?? env.GOPATH, "bin")));
9983
10037
  const inspect = Effect.fn("GoInstall.inspect")(function* (dir, file) {
9984
10038
  const invocation = go([
@@ -9994,9 +10048,11 @@ const make$7 = Effect.gen(function* () {
9994
10048
  });
9995
10049
  const isPlainExecutable = Effect.fn("GoInstall.isPlainExecutable")(function* (dir, file) {
9996
10050
  const binary = path.join(dir, file);
9997
- const info = yield* fs.stat(binary).pipe(Effect.mapError(fileFailed("stat", binary)));
9998
- const real = yield* fs.realPath(binary).pipe(Effect.mapError(fileFailed("resolve", binary)));
9999
- const realDir = yield* fs.realPath(dir).pipe(Effect.mapError(fileFailed("resolve", dir)));
10051
+ const [info, real, realDir] = yield* Effect.all([
10052
+ fs.stat(binary).pipe(Effect.mapError(fileFailed("stat", binary))),
10053
+ fs.realPath(binary).pipe(Effect.mapError(fileFailed("resolve", binary))),
10054
+ fs.realPath(dir).pipe(Effect.mapError(fileFailed("resolve", dir)))
10055
+ ], { concurrency: "unbounded" });
10000
10056
  return info.type === "File" && (info.mode & 73) !== 0 && real === path.join(realDir, file);
10001
10057
  });
10002
10058
  const latest = Effect.fn("GoInstall.latest")(function* (modules) {
@@ -10066,6 +10122,7 @@ const make$7 = Effect.gen(function* () {
10066
10122
  });
10067
10123
  // src/go/GoModule.ts
10068
10124
  const ModFile = Schema.Struct({
10125
+ Module: Schema.optionalKey(Schema.Struct({ Path: Schema.String })),
10069
10126
  Require: Schema.optionalKey(Schema.Array(Schema.Struct({
10070
10127
  Path: Schema.String,
10071
10128
  Version: Schema.String,
@@ -10079,7 +10136,10 @@ const Selected = Schema.Struct({
10079
10136
  Update: Schema.optionalKey(Schema.Struct({ Version: Schema.String })),
10080
10137
  Error: Schema.optionalKey(Schema.Struct({ Err: Schema.String }))
10081
10138
  });
10082
- const WorkFile = Schema.Struct({ Use: Schema.optionalKey(Schema.Array(Schema.Struct({ DiskPath: Schema.String }))) });
10139
+ const WorkFile = Schema.Struct({
10140
+ Use: Schema.optionalKey(Schema.Array(Schema.Struct({ DiskPath: Schema.String }))),
10141
+ Replace: Schema.optionalKey(Schema.Array(Schema.Struct({ Old: Schema.Struct({ Path: Schema.String }) })))
10142
+ });
10083
10143
  const goEnv = {
10084
10144
  GOWORK: "off",
10085
10145
  GIT_TERMINAL_PROMPT: "0"
@@ -10089,26 +10149,30 @@ const GoProxyEnv = Schema.Struct({
10089
10149
  GOPRIVATE: Schema.optionalKey(Schema.String),
10090
10150
  GONOPROXY: Schema.optionalKey(Schema.String)
10091
10151
  });
10092
- const directRequirements = (mod) => {
10152
+ const directRequirements = (mod, local) => {
10093
10153
  const replaced = new Set((mod.Replace ?? []).map((replace) => replace.Old.Path));
10094
- return (mod.Require ?? []).filter((requirement) => requirement.Indirect !== true && !replaced.has(requirement.Path));
10154
+ return (mod.Require ?? []).filter((requirement) => requirement.Indirect !== true && !replaced.has(requirement.Path) && !local.has(requirement.Path));
10155
+ };
10156
+ const goIn = (cwd, args) => ({
10157
+ command: "go",
10158
+ args,
10159
+ cwd,
10160
+ env: goEnv
10161
+ });
10162
+ const readModFile = (exec, cwd) => {
10163
+ const edit = goIn(cwd, [
10164
+ "mod",
10165
+ "edit",
10166
+ "-json"
10167
+ ]);
10168
+ return exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(ModFile, commandLine(edit))));
10095
10169
  };
10096
- const make$6 = (root, moduleDir, place = "") => Effect.gen(function* () {
10170
+ const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set()) => Effect.gen(function* () {
10097
10171
  const exec = yield* Exec;
10098
10172
  const manifest = path.join(place, path.relative(root, path.join(moduleDir, "go.mod")));
10099
- const go = (args) => ({
10100
- command: "go",
10101
- args,
10102
- cwd: moduleDir,
10103
- env: goEnv
10104
- });
10173
+ const go = (args) => goIn(moduleDir, args);
10105
10174
  const requirements = Effect.gen(function* () {
10106
- const edit = go([
10107
- "mod",
10108
- "edit",
10109
- "-json"
10110
- ]);
10111
- return directRequirements(yield* exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(ModFile, commandLine(edit)))));
10175
+ return directRequirements(yield* readModFile(exec, moduleDir), local);
10112
10176
  });
10113
10177
  const selected = Effect.fn("GoModule.selected")(function* (paths, withUpdates) {
10114
10178
  if (paths.length === 0) return /* @__PURE__ */ new Map();
@@ -10147,8 +10211,7 @@ const make$6 = (root, moduleDir, place = "") => Effect.gen(function* () {
10147
10211
  ]);
10148
10212
  const origin = exec.stdout(env).pipe(Effect.flatMap(decodeGoJson(GoProxyEnv, commandLine(env))), Effect.map(withGoOrigin));
10149
10213
  const list = Effect.gen(function* () {
10150
- const direct = yield* requirements;
10151
- const located = yield* origin;
10214
+ const [direct, located] = yield* Effect.all([requirements, origin], { concurrency: "unbounded" });
10152
10215
  const entries = yield* selected(direct.map((requirement) => requirement.Path), true);
10153
10216
  const notices = [];
10154
10217
  return {
@@ -10210,65 +10273,91 @@ const locate$1 = Effect.fn("GoModule.locate")(function* (dir) {
10210
10273
  const discover$2 = Effect.fn("GoModule.discover")(function* (dir, nested = []) {
10211
10274
  const exec = yield* Exec;
10212
10275
  const { found, failed } = yield* locateAll(dir, nested, "go.mod", locate$1);
10213
- const failedWorks = [];
10214
- const modules = yield* Effect.forEach(found, ({ root, work }) => Effect.gen(function* () {
10215
- if (!work) return [{
10216
- root,
10217
- moduleDir: root
10218
- }];
10219
- const edit = {
10220
- command: "go",
10221
- args: [
10222
- "work",
10223
- "edit",
10224
- "-json"
10225
- ],
10226
- cwd: root
10276
+ const works = yield* Effect.forEach(found, ({ root, work, claimed }) => Effect.gen(function* () {
10277
+ if (!work) return {
10278
+ modules: [{
10279
+ root,
10280
+ moduleDir: root,
10281
+ local: /* @__PURE__ */ new Set()
10282
+ }],
10283
+ failed: [],
10284
+ covered: [root]
10227
10285
  };
10228
- const file = yield* exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(WorkFile, commandLine(edit))), Effect.result);
10286
+ const covered = [];
10229
10287
  const place = placeOf$1(dir, root);
10230
- if (Result.isSuccess(file)) return (file.success.Use ?? []).map((use) => ({
10231
- root,
10232
- moduleDir: path.resolve(root, use.DiskPath)
10233
- })).filter(({ moduleDir }) => place === "" || placeOf$1(dir, moduleDir) !== "");
10234
- if (place === "") return yield* file.failure;
10235
- failedWorks.push(failing(path.join(place, "go.work"), file.failure));
10236
- return [];
10237
- }));
10238
- const listed = new Set(modules.flat().map(({ moduleDir }) => moduleDir));
10288
+ const scanned = yield* Effect.gen(function* () {
10289
+ const edit = {
10290
+ command: "go",
10291
+ args: [
10292
+ "work",
10293
+ "edit",
10294
+ "-json"
10295
+ ],
10296
+ cwd: root
10297
+ };
10298
+ const file = yield* exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(WorkFile, commandLine(edit))));
10299
+ const used = (file.Use ?? []).map((use) => path.resolve(root, use.DiskPath));
10300
+ covered.push(...used);
10301
+ const paths = yield* Effect.forEach(used, (moduleDir) => Effect.gen(function* () {
10302
+ const mod = yield* readModFile(exec, moduleDir);
10303
+ if (mod.Module === void 0) return yield* new OutputMalformed({
10304
+ command: "go mod edit -json",
10305
+ issue: `no module path in ${path.join(moduleDir, "go.mod")}`
10306
+ });
10307
+ return mod.Module.Path;
10308
+ }), { concurrency: 8 });
10309
+ const local = /* @__PURE__ */ new Set([...paths, ...(file.Replace ?? []).map((replace) => replace.Old.Path)]);
10310
+ return used.map((moduleDir) => ({
10311
+ root,
10312
+ moduleDir,
10313
+ local
10314
+ })).filter(({ moduleDir }) => place === "" || placeOf$1(dir, moduleDir) !== "");
10315
+ }).pipe(Effect.result);
10316
+ if (Result.isSuccess(scanned)) return {
10317
+ modules: scanned.success,
10318
+ failed: [],
10319
+ covered
10320
+ };
10321
+ return {
10322
+ modules: [],
10323
+ failed: [failing(path.join(place, "go.work"), scanned.failure)],
10324
+ covered: [...covered, ...claimed]
10325
+ };
10326
+ }), { concurrency: 4 });
10327
+ const listed = new Set(works.flatMap((work) => work.covered));
10239
10328
  const outside = nested.flatMap((moduleDir) => listed.has(moduleDir) ? [] : [{
10240
10329
  root: moduleDir,
10241
- moduleDir
10330
+ moduleDir,
10331
+ local: /* @__PURE__ */ new Set()
10242
10332
  }]);
10243
- const providers = yield* Effect.forEach([...modules.flat(), ...outside], ({ root, moduleDir }) => make$6(root, moduleDir, placeOf$1(dir, root)));
10333
+ const providers = yield* Effect.forEach([...works.flatMap(({ modules }) => modules), ...outside], ({ root, moduleDir, local }) => make$7(root, moduleDir, placeOf$1(dir, root), local));
10244
10334
  return [
10245
10335
  ...failed,
10246
- ...failedWorks,
10336
+ ...works.flatMap((work) => work.failed),
10247
10337
  ...providers
10248
10338
  ];
10249
10339
  });
10250
10340
  // src/inventory/ProviderSource.ts
10251
10341
  var ProviderSource = class extends Context.Service()("upup/ProviderSource") {};
10252
- // src/node/prerelease-line.ts
10342
+ // src/node/release-history.ts
10253
10343
  const minutesPerDay = 1440;
10254
- const firstPnpmMajorWithDefaultReleaseAge = 11;
10255
10344
  const gateSettings = {
10256
10345
  pnpm: {
10257
10346
  age: "minimumReleaseAge",
10258
10347
  exclude: "minimumReleaseAgeExclude",
10259
10348
  unit: 1,
10260
- unset: (version) => (semver.coerce(version)?.major ?? 0) >= firstPnpmMajorWithDefaultReleaseAge ? minutesPerDay : 0
10349
+ unset: minutesPerDay
10261
10350
  },
10262
10351
  npm: {
10263
10352
  age: "min-release-age",
10264
10353
  exclude: "min-release-age-exclude",
10265
10354
  unit: minutesPerDay,
10266
- unset: () => 0
10355
+ unset: 0
10267
10356
  }
10268
10357
  };
10269
- const releaseGate = (manager, version, config) => {
10358
+ const releaseGate = (manager, config) => {
10270
10359
  const settings = gateSettings[manager];
10271
- const minutes = Number(config[settings.age] ?? settings.unset(version)) * settings.unit;
10360
+ const minutes = Number(config[settings.age] ?? settings.unset) * settings.unit;
10272
10361
  return {
10273
10362
  minutes: Number.isFinite(minutes) && minutes > 0 ? minutes : 0,
10274
10363
  exclude: [config[settings.exclude] ?? []].flat().filter((pattern) => typeof pattern === "string")
@@ -10287,16 +10376,18 @@ const exempt = (rules, name, version) => {
10287
10376
  };
10288
10377
  const isPrerelease = (version) => semver.prerelease(version) !== null;
10289
10378
  const lineOf = (version) => (semver.prerelease(version) ?? []).map((part) => Predicate.isNumber(part) ? "#" : part).join(".");
10290
- const prereleaseSuccessor = (name, current, history, gate, now) => {
10379
+ const releaseSuccessor = (name, current, history, gate, now) => {
10291
10380
  const line = lineOf(current);
10292
10381
  const following = isPrerelease(current);
10293
10382
  const pinned = Date.parse(history.time[current] ?? "");
10294
10383
  if (following && Number.isNaN(pinned)) return Result.fail(`${name} ${current} has no publish date, so upup offers it nothing`);
10295
10384
  const releasedBeforePrereleasePin = (published) => following && !(published > pinned);
10385
+ const latest = semver.valid(history.latest ?? "");
10386
+ const beyondLatest = (candidate) => latest !== null && semver.gt(candidate, latest);
10296
10387
  let newest;
10297
10388
  for (const candidate of history.versions) {
10298
10389
  if (semver.valid(candidate) === null || !semver.gt(candidate, current)) continue;
10299
- if (isPrerelease(candidate) && lineOf(candidate) !== line) continue;
10390
+ if (isPrerelease(candidate) ? lineOf(candidate) !== line : beyondLatest(candidate)) continue;
10300
10391
  const published = Date.parse(history.time[candidate] ?? "");
10301
10392
  if (Number.isNaN(published) || releasedBeforePrereleasePin(published)) continue;
10302
10393
  if (gate.minutes > 0 && now - published < gate.minutes * 6e4 && !exempt(gate.exclude, name, candidate)) continue;
@@ -10304,35 +10395,63 @@ const prereleaseSuccessor = (name, current, history, gate, now) => {
10304
10395
  }
10305
10396
  return Result.succeed(newest);
10306
10397
  };
10307
- const Viewed = Schema.Struct({
10398
+ const ViewedRelease = Schema.Struct({
10308
10399
  versions: Schema.Union([Schema.String, Schema.Array(Schema.String)]),
10309
- time: Schema.Record(Schema.String, Schema.String)
10310
- });
10311
- const prereleaseOffers = Effect.fn("prereleaseOffers")(function* (pins, manager, run, config) {
10400
+ time: Schema.Record(Schema.String, Schema.String),
10401
+ "dist-tags": Schema.optionalKey(Schema.Struct({ latest: Schema.optionalKey(Schema.String) }))
10402
+ });
10403
+ const Viewed = Schema.Union([ViewedRelease, Schema.NonEmptyArray(ViewedRelease)]);
10404
+ const ViewFailed = Schema.Struct({ error: Schema.Struct({
10405
+ code: Schema.optionalKey(Schema.String),
10406
+ summary: Schema.optionalKey(Schema.String),
10407
+ message: Schema.optionalKey(Schema.String)
10408
+ }) });
10409
+ const releaseOffers = Effect.fn("releaseOffers")(function* (pins, manager, run, config) {
10312
10410
  const exec = yield* Exec;
10313
10411
  const now = yield* Clock.currentTimeMillis;
10314
- const read = (artifact) => {
10412
+ const read = Effect.fn("releaseOffers.read")(function* (artifact) {
10315
10413
  const view = run([
10316
10414
  "view",
10317
10415
  artifact,
10318
10416
  "versions",
10319
10417
  "time",
10418
+ "dist-tags",
10320
10419
  "--json"
10321
10420
  ]);
10322
- return exec.stdout(view).pipe(Effect.flatMap(decodeJson(Viewed, commandLine(view))), Effect.map((viewed) => ({
10421
+ const output = yield* exec.run(view);
10422
+ if (output.exitCode !== 0) {
10423
+ const reported = Option.getOrUndefined(Schema.decodeOption(Schema.fromJsonString(ViewFailed))(output.stdout))?.error;
10424
+ if (reported?.code?.includes("404") === true) return [artifact, Result.fail(`${artifact} isn't on the registry, or your credentials can't see it, so upup offers it nothing`)];
10425
+ return yield* new CommandFailed({
10426
+ command: commandLine(view),
10427
+ cwd: view.cwd,
10428
+ exitCode: output.exitCode,
10429
+ output: reported?.summary ?? reported?.message ?? output.stderr
10430
+ });
10431
+ }
10432
+ const decoded = yield* decodeJson(Viewed, commandLine(view))(output.stdout);
10433
+ const viewed = "versions" in decoded ? decoded : decoded[0];
10434
+ const latest = viewed["dist-tags"]?.latest;
10435
+ const history = {
10323
10436
  versions: [viewed.versions].flat(),
10324
- time: viewed.time
10325
- })), Effect.result, Effect.map((history) => [artifact, history]));
10437
+ time: viewed.time,
10438
+ ...latest === void 0 ? {} : { latest }
10439
+ };
10440
+ return [artifact, Result.succeed(history)];
10441
+ });
10442
+ const artifacts = new Set(pins.map((pin) => pin.artifact));
10443
+ const reads = yield* Effect.result(Effect.forEach(artifacts, read, { concurrency: 8 }));
10444
+ const histories = new Map(Result.isSuccess(reads) ? reads.success : []);
10445
+ const notices = Result.isFailure(reads) ? [{ message: `release histories could not be read, so upup offers nothing: ${describeError(reads.failure)}` }] : [];
10446
+ const gate = releaseGate(manager, config);
10447
+ const offer = (pin) => {
10448
+ const history = histories.get(pin.artifact);
10449
+ if (history === void 0) return Result.succeed(void 0);
10450
+ return Result.flatMap(history, (found) => releaseSuccessor(pin.artifact, pin.current, found, gate, now));
10326
10451
  };
10327
- const followed = (pin) => isPrerelease(pin.current) || pin.history === true;
10328
- const artifacts = new Set(pins.flatMap((pin) => followed(pin) ? [pin.artifact] : []));
10329
- const histories = new Map(yield* Effect.forEach(artifacts, read, { concurrency: 4 }));
10330
- const version = manager === "pnpm" && artifacts.size > 0 ? yield* exec.stdout(run(["--version"])) : "";
10331
- const gate = releaseGate(manager, version.trim(), config);
10332
- return (pin) => {
10333
- const history = followed(pin) ? histories.get(pin.artifact) : void 0;
10334
- if (history === void 0) return;
10335
- return Result.isFailure(history) ? Result.fail(`${pin.artifact}'s release history could not be read, so upup offers it nothing: ${describeError(history.failure)}`) : prereleaseSuccessor(pin.artifact, pin.current, history.success, gate, now);
10452
+ return {
10453
+ offer,
10454
+ notices
10336
10455
  };
10337
10456
  });
10338
10457
  // src/node/script-policy.ts
@@ -10388,7 +10507,7 @@ const Root = Schema.Struct({
10388
10507
  dependencies: Schema.optionalKey(Schema.Record(Schema.String, Dependency))
10389
10508
  });
10390
10509
  const Listed$1 = Schema.Union([Root, Schema.Array(Root)]);
10391
- const Outdated$1 = Schema.Record(Schema.String, Schema.Struct({ latest: Schema.optionalKey(Schema.String) }));
10510
+ const Outdated = Schema.Record(Schema.String, Schema.Struct({ latest: Schema.optionalKey(Schema.String) }));
10392
10511
  const parseListed = (provider, listed) => {
10393
10512
  const roots = Array.isArray(listed) ? listed : [listed];
10394
10513
  const pkg = (name, version, runtime) => ({
@@ -10422,7 +10541,7 @@ const makeGlobal = (spec) => Effect.gen(function* () {
10422
10541
  const latest = Effect.gen(function* () {
10423
10542
  const outdated = invocation(spec.outdated);
10424
10543
  const output = yield* exec.run(outdated);
10425
- const parsed = yield* decodeJson(Outdated$1, commandLine(outdated))(output.stdout).pipe(Effect.catchTag("OutputMalformed", (error) => output.exitCode === 0 ? Effect.fail(error) : Effect.fail(new CommandFailed({
10544
+ const parsed = yield* decodeJson(Outdated, commandLine(outdated))(output.stdout).pipe(Effect.catchTag("OutputMalformed", (error) => output.exitCode === 0 ? Effect.fail(error) : Effect.fail(new CommandFailed({
10426
10545
  command: commandLine(outdated),
10427
10546
  cwd: outdated.cwd,
10428
10547
  exitCode: output.exitCode,
@@ -10435,21 +10554,21 @@ const makeGlobal = (spec) => Effect.gen(function* () {
10435
10554
  installed,
10436
10555
  latest,
10437
10556
  readNpmConfig(spec.config, "/").pipe(Effect.provideService(Exec, exec))
10438
- ], { concurrency: 3 });
10439
- const offers = yield* prereleaseOffers(packages, spec.config, (args) => ({
10557
+ ], { concurrency: "unbounded" });
10558
+ const { offer, notices: unread } = yield* releaseOffers(packages.filter((pkg) => isPrerelease(pkg.current)), spec.config, (args) => ({
10440
10559
  command: spec.config,
10441
10560
  args,
10442
10561
  cwd: "/"
10443
10562
  }), config).pipe(Effect.provideService(Exec, exec));
10444
- const notices = [];
10563
+ const notices = [...unread];
10445
10564
  return {
10446
10565
  packages: packages.map(withNpmOrigin(npmRegistries(config))).map((pkg) => {
10447
- const line = offers(pkg);
10448
- if (line !== void 0 && Result.isFailure(line)) notices.push({
10566
+ const line = offer(pkg);
10567
+ if (Result.isFailure(line)) notices.push({
10449
10568
  rowKey: rowKey(pkg),
10450
10569
  message: line.failure
10451
10570
  });
10452
- const target = line === void 0 ? targets.get(pkg.name) : Result.getOrUndefined(line);
10571
+ const target = isPrerelease(pkg.current) ? Result.getOrUndefined(line) : targets.get(pkg.name);
10453
10572
  return target !== void 0 && isNewerStable(pkg.current, target) ? withTarget(pkg, target) : pkg;
10454
10573
  }),
10455
10574
  notices
@@ -10666,12 +10785,13 @@ const scan = Effect.fn("NodePins.scan")(function* (root, dir, nested) {
10666
10785
  const fs = yield* FileSystem.FileSystem;
10667
10786
  const read = (file) => readText(path.join(root, file));
10668
10787
  const exists = (file) => fs.exists(path.join(root, file)).pipe(Effect.mapError(fileFailed("stat", path.join(root, file))));
10669
- const steps = (yield* workflowFiles(root)).flatMap(({ file, text }) => stepPins(file, text) ?? []);
10670
- const versioned = yield* Effect.filter(yield* versionFilesOf(root, dir, nested), exists);
10671
- const files = yield* Effect.forEach(versioned.toSorted(), (file) => Effect.map(read(file), (text) => filePin(file, text)));
10788
+ const [workflows, listed] = yield* Effect.all([workflowFiles(root), versionFilesOf(root, dir, nested)], { concurrency: "unbounded" });
10789
+ const steps = workflows.flatMap(({ file, text }) => stepPins(file, text) ?? []);
10790
+ const versioned = yield* Effect.filter(listed, exists, { concurrency: "unbounded" });
10791
+ const files = yield* Effect.forEach(versioned.toSorted(), (file) => Effect.map(read(file), (text) => filePin(file, text)), { concurrency: 8 });
10672
10792
  return [...steps, ...files.flat()];
10673
10793
  });
10674
- const make$5 = (root, dir = root, nested = []) => Effect.gen(function* () {
10794
+ const make$6 = (root, dir = root, nested = []) => Effect.gen(function* () {
10675
10795
  const fs = yield* FileSystem.FileSystem;
10676
10796
  const client = yield* HttpClient.HttpClient;
10677
10797
  const context = yield* Effect.context();
@@ -10742,7 +10862,7 @@ const discover$1 = Effect.fn("NodePins.discover")(function* (dir, nested = []) {
10742
10862
  if (Option.isNone(repository)) return [];
10743
10863
  const pins = yield* scan(repository.value, dir, nested).pipe(Effect.result);
10744
10864
  if (Result.isSuccess(pins) && pins.success.length === 0) return [];
10745
- return [yield* make$5(repository.value, dir, nested)];
10865
+ return [yield* make$6(repository.value, dir, nested)];
10746
10866
  });
10747
10867
  // src/node/selector.ts
10748
10868
  const split = (name, spec) => {
@@ -10794,20 +10914,21 @@ const Manifest = Schema.Struct({
10794
10914
  optionalDependencies: Schema.optionalKey(Specs)
10795
10915
  });
10796
10916
  const Resolved = Schema.Record(Schema.String, Schema.Struct({ version: Schema.String }));
10797
- const PnpmLock = Schema.Struct({ importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({
10798
- dependencies: Schema.optionalKey(Resolved),
10799
- devDependencies: Schema.optionalKey(Resolved),
10800
- optionalDependencies: Schema.optionalKey(Resolved)
10801
- }))) });
10917
+ const PnpmLock = Schema.Struct({
10918
+ overrides: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
10919
+ importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({
10920
+ dependencies: Schema.optionalKey(Resolved),
10921
+ devDependencies: Schema.optionalKey(Resolved),
10922
+ optionalDependencies: Schema.optionalKey(Resolved)
10923
+ })))
10924
+ });
10802
10925
  const PnpmManagerLock = Schema.Struct({ importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ packageManagerDependencies: Schema.optionalKey(Resolved) }))) });
10803
10926
  const NpmLock = Schema.Struct({ packages: Schema.Record(Schema.String, Schema.Struct({ version: Schema.optionalKey(Schema.String) })) });
10804
10927
  const Workspace = Schema.Struct({
10805
10928
  catalog: Schema.optionalKey(Specs),
10806
- catalogs: Schema.optionalKey(Schema.Record(Schema.String, Specs))
10929
+ catalogs: Schema.optionalKey(Schema.Record(Schema.String, Specs)),
10930
+ overrides: Schema.optionalKey(Specs)
10807
10931
  });
10808
- const OutdatedEntry = Schema.Struct({ latest: Schema.optionalKey(Schema.String) });
10809
- const Outdated = Schema.Record(Schema.String, Schema.Union([OutdatedEntry, Schema.Array(OutdatedEntry)]));
10810
- const NpmError = Schema.Struct({ error: Schema.Struct({ summary: Schema.String }) });
10811
10932
  const PackageJsonFields = Schema.Struct({
10812
10933
  workspaces: Schema.optionalKey(Schema.Unknown),
10813
10934
  packageManager: Schema.optionalKey(Schema.String),
@@ -10830,25 +10951,22 @@ const managerEntry = "the package manager's declaration is removed by hand, in p
10830
10951
  const resolvedVersion = (version) => version?.split("(")[0];
10831
10952
  const readJson = (schema, file) => readText(file).pipe(Effect.flatMap((text) => decodeFile(schema, file)(parse$1(text))));
10832
10953
  const exact = (version) => semver.valid(version) === version ? version : void 0;
10954
+ const overriddenName = (key) => {
10955
+ const target = key.split(">").at(-1) ?? key;
10956
+ const at = target.indexOf("@", target.startsWith("@") ? 1 : 0);
10957
+ return at === -1 ? target : target.slice(0, at);
10958
+ };
10833
10959
  const corepackHashed = (name, algorithm) => {
10834
10960
  if (algorithm === void 0) return {};
10835
10961
  if (algorithm === "sha512") return { hashed: true };
10836
10962
  return { held: `${name}'s packageManager entry carries a ${algorithm} hash, which upup can work out only for sha512; move it by hand` };
10837
10963
  };
10838
- const pinOf = ({ pkg, manager: own }) => ({
10839
- artifact: pkg.artifact,
10840
- current: pkg.current,
10841
- ...own === true ? { history: true } : {}
10842
- });
10843
10964
  const editDeclaration = (text, manifest, entry, next) => {
10844
10965
  if (next === void 0 && entry.pkg.unremovable !== void 0) return Result.fail(entry.pkg.unremovable);
10845
10966
  if (manifest.endsWith(".yaml") && next !== void 0) return editYamlScalar(text, entry.path, next);
10846
10967
  return editJson(text, entry.path, next);
10847
10968
  };
10848
- const withoutDependencyKindMarker = (key) => key.replace(/ \([a-z]+\)$/u, "");
10849
- const withoutLockedVersionSuffix = (key) => key.replace(/(?<=.)@[^@/ ]+$/u, "");
10850
- const outdatedName = (key) => withoutLockedVersionSuffix(withoutDependencyKindMarker(key));
10851
- const make$4 = (root, manager, place = "") => Effect.gen(function* () {
10969
+ const make$5 = (root, manager, place = "", claimed = []) => Effect.gen(function* () {
10852
10970
  const providerName = placed(manager, place);
10853
10971
  const exec = yield* Exec;
10854
10972
  const fs = yield* FileSystem.FileSystem;
@@ -10929,27 +11047,35 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
10929
11047
  onNone: () => []
10930
11048
  })
10931
11049
  });
11050
+ const lockedDirs = new Set(members.map((member) => path.normalize(member)));
11051
+ const unlocked = claimed.filter((dir) => !lockedDirs.has(path.normalize(dir))).toSorted((a, b) => a.localeCompare(b));
11052
+ if (unlocked.length > 0) notices.push({ message: `not listed: ${unlocked.join(", ")} (not in ${lockfile})` });
10932
11053
  const catalogLocked = /* @__PURE__ */ new Map();
10933
- for (const member of members.toSorted((a, b) => a.localeCompare(b))) {
11054
+ const memberManifests = yield* Effect.forEach(members.toSorted((a, b) => a.localeCompare(b)), (member) => {
10934
11055
  const memberDir = member === "." ? "" : member;
10935
11056
  const manifest = path.join(memberDir, "package.json");
10936
- const fields = yield* readJson(Manifest, path.join(root, manifest));
10937
- for (const section of sections) for (const [name, spec] of Object.entries(fields[section] ?? {})) {
10938
- const resolved = Option.match(pnpm, {
10939
- onSome: (lock) => resolvedVersion(lock.importers?.[member]?.[section]?.[name]?.version),
10940
- onNone: () => Option.match(npm, {
10941
- onSome: (lock) => lock.packages[path.join(memberDir, "node_modules", name)]?.version ?? lock.packages[path.join("node_modules", name)]?.version,
10942
- onNone: () => void 0
10943
- })
10944
- });
10945
- if (spec.startsWith("catalog:")) {
10946
- const catalog = spec.slice(8) || "default";
10947
- const locked = catalogLocked.get(catalog) ?? /* @__PURE__ */ new Map();
10948
- if (resolved !== void 0 && !locked.has(name)) catalogLocked.set(catalog, locked.set(name, resolved));
10949
- continue;
10950
- }
10951
- add(manifest, section, [section, name], name, spec, resolved);
11057
+ return Effect.map(readJson(Manifest, path.join(root, manifest)), (fields) => ({
11058
+ member,
11059
+ memberDir,
11060
+ manifest,
11061
+ fields
11062
+ }));
11063
+ }, { concurrency: 8 });
11064
+ for (const { member, memberDir, manifest, fields } of memberManifests) for (const section of sections) for (const [name, spec] of Object.entries(fields[section] ?? {})) {
11065
+ const resolved = Option.match(pnpm, {
11066
+ onSome: (lock) => resolvedVersion(lock.importers?.[member]?.[section]?.[name]?.version),
11067
+ onNone: () => Option.match(npm, {
11068
+ onSome: (lock) => lock.packages[path.join(memberDir, "node_modules", name)]?.version ?? lock.packages[path.join("node_modules", name)]?.version,
11069
+ onNone: () => void 0
11070
+ })
11071
+ });
11072
+ if (spec.startsWith("catalog:")) {
11073
+ const catalog = spec.slice(8) || "default";
11074
+ const locked = catalogLocked.get(catalog) ?? /* @__PURE__ */ new Map();
11075
+ if (resolved !== void 0 && !locked.has(name)) catalogLocked.set(catalog, locked.set(name, resolved));
11076
+ continue;
10952
11077
  }
11078
+ add(manifest, section, [section, name], name, spec, resolved);
10953
11079
  }
10954
11080
  if (Option.isSome(pnpm) && (yield* exists(path.join(root, "pnpm-workspace.yaml")))) {
10955
11081
  const workspacePath = path.join(root, "pnpm-workspace.yaml");
@@ -10963,8 +11089,25 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
10963
11089
  ["catalogs", catalog],
10964
11090
  entries
10965
11091
  ])];
11092
+ const overridden = /* @__PURE__ */ new Map();
11093
+ for (const [key, spec] of Object.entries(workspace.overrides ?? {})) {
11094
+ if (!spec.startsWith("catalog:")) continue;
11095
+ const name = overriddenName(key);
11096
+ const locked = pnpm.value.overrides?.[key];
11097
+ const selector = locked === void 0 ? void 0 : parseSelector(name, locked);
11098
+ if (selector === void 0 || exact(selector.range) === void 0) continue;
11099
+ const id = `${spec.slice(8) || "default"}:${name}`;
11100
+ const versions = overridden.get(id) ?? /* @__PURE__ */ new Set();
11101
+ versions.add(selector.artifact === name ? selector.range : `${selector.artifact}@${selector.range}`);
11102
+ overridden.set(id, versions);
11103
+ }
10966
11104
  for (const [catalog, catalogPath, entries] of catalogs) for (const [name, spec] of Object.entries(entries)) {
10967
- const resolved = catalogLocked.get(catalog)?.get(name);
11105
+ const overrides = overridden.get(`${catalog}:${name}`);
11106
+ if (overrides !== void 0 && overrides.size > 1) {
11107
+ notices.push({ message: `${catalog} catalog entry ${name} has conflicting locked overrides; run pnpm install` });
11108
+ continue;
11109
+ }
11110
+ const resolved = catalogLocked.get(catalog)?.get(name) ?? overrides?.values().next().value;
10968
11111
  if (resolved === void 0) continue;
10969
11112
  add("pnpm-workspace.yaml", catalogPath.join("."), [...catalogPath, name], name, spec, resolved);
10970
11113
  }
@@ -11001,53 +11144,20 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
11001
11144
  notices
11002
11145
  };
11003
11146
  }).pipe(Effect.provideService(FileSystem.FileSystem, fs));
11004
- const latest = Effect.gen(function* () {
11005
- const workspaces = manager === "npm" && (yield* readJson(PackageJsonFields, path.join(root, "package.json"))).workspaces !== void 0;
11006
- const outdated = run(manager === "pnpm" ? [
11007
- "outdated",
11008
- "-r",
11009
- "--format",
11010
- "json"
11011
- ] : [
11012
- "outdated",
11013
- "--json",
11014
- ...workspaces ? ["--workspaces", "--include-workspace-root"] : []
11015
- ]);
11016
- const output = yield* exec.run(outdated);
11017
- const reported = Schema.decodeOption(Schema.fromJsonString(NpmError))(output.stdout);
11018
- if (output.exitCode !== 0 && (output.stdout.trim() === "" || Option.isSome(reported))) return yield* new CommandFailed({
11019
- command: commandLine(outdated),
11020
- cwd: root,
11021
- exitCode: output.exitCode,
11022
- output: Option.isSome(reported) ? reported.value.error.summary : output.stderr
11023
- });
11024
- const parsed = yield* decodeJson(Outdated, commandLine(outdated))(output.stdout || "{}");
11025
- const targets = /* @__PURE__ */ new Map();
11026
- for (const [key, entries] of Object.entries(parsed)) {
11027
- const name = outdatedName(key);
11028
- const newest = [entries].flat().find((entry) => entry.latest !== void 0)?.latest;
11029
- if (newest !== void 0 && !targets.has(name)) targets.set(name, newest);
11030
- }
11031
- return targets;
11032
- }).pipe(Effect.provideService(FileSystem.FileSystem, fs));
11033
11147
  const list = Effect.gen(function* () {
11034
- const [{ declared, notices }, targets, config] = yield* Effect.all([
11035
- declarations,
11036
- latest,
11037
- readNpmConfig(manager, root).pipe(Effect.provideService(Exec, exec))
11038
- ], { concurrency: 3 });
11148
+ const [{ declared, notices }, config] = yield* Effect.all([declarations, readNpmConfig(manager, root).pipe(Effect.provideService(Exec, exec))], { concurrency: "unbounded" });
11039
11149
  const origin = withNpmOrigin(npmRegistries(config));
11040
- const offers = yield* prereleaseOffers(declared.flatMap((entry) => entry.held === true ? [] : [pinOf(entry)]), manager, run, config).pipe(Effect.provideService(Exec, exec));
11150
+ const { offer, notices: unread } = yield* releaseOffers(declared.flatMap((entry) => entry.held === true ? [] : [entry.pkg]), manager, run, config).pipe(Effect.provideService(Exec, exec));
11041
11151
  return {
11042
11152
  packages: declared.map((entry) => {
11043
11153
  const pkg = origin(entry.pkg);
11044
11154
  if (entry.held === true) return pkg;
11045
- const line = offers(pinOf(entry));
11046
- if (line !== void 0 && Result.isFailure(line)) notices.push({
11155
+ const line = offer(entry.pkg);
11156
+ if (Result.isFailure(line)) notices.push({
11047
11157
  rowKey: rowKey(pkg),
11048
11158
  message: line.failure
11049
11159
  });
11050
- const target = line === void 0 ? targets.get(pkg.name) ?? targets.get(pkg.artifact) : Result.getOrUndefined(line);
11160
+ const target = Result.getOrUndefined(line);
11051
11161
  if (target === void 0 || !isNewerStable(pkg.current, target)) return pkg;
11052
11162
  const selector = parseSelector(pkg.name, pkg.declaration?.selector ?? "");
11053
11163
  if (selector === void 0 || moveTo(selector, target) === void 0) {
@@ -11059,7 +11169,7 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
11059
11169
  }
11060
11170
  return withTarget(pkg, target);
11061
11171
  }),
11062
- notices
11172
+ notices: [...unread, ...notices]
11063
11173
  };
11064
11174
  }).pipe(Effect.withSpan(`NodeProject.list`));
11065
11175
  const find = (pkg) => declarations.pipe(Effect.map(({ declared }) => declared.find((entry) => entry.pkg.id === pkg.id)));
@@ -11230,7 +11340,7 @@ const discover = Effect.fn("NodeProject.discover")(function* (dir, nested = [])
11230
11340
  const { found, failed } = yield* locateAll(dir, nested, "package.json", locate);
11231
11341
  const providers = [...failed];
11232
11342
  const unlisted = [];
11233
- for (const { root, manager } of found) {
11343
+ for (const { root, manager, claimed } of found) {
11234
11344
  const place = placeOf$1(dir, root);
11235
11345
  if (!isManager(manager)) {
11236
11346
  if (place === "") providers.push(failing(manager, new Unsupported({ reason: `${path.relative(dir, root) || "This"} project uses ${manager}, which upup does not support` })));
@@ -11240,11 +11350,147 @@ const discover = Effect.fn("NodeProject.discover")(function* (dir, nested = [])
11240
11350
  const lockfile = path.join(root, lockfiles[manager]);
11241
11351
  const installed = yield* fs.exists(lockfile).pipe(Effect.mapError(fileFailed("stat", lockfile)));
11242
11352
  if (place !== "" && !installed) unlisted.push(`${place} (no ${lockfiles[manager]} yet)`);
11243
- else providers.push(yield* make$4(root, manager, place));
11353
+ else {
11354
+ const manifests = yield* Effect.filter(claimed, (from) => fs.exists(path.join(from, "package.json")).pipe(Effect.mapError(fileFailed("stat", from))), { concurrency: "unbounded" });
11355
+ providers.push(yield* make$5(root, manager, place, manifests.map((from) => path.relative(root, from))));
11356
+ }
11244
11357
  }
11245
11358
  if (unlisted.length > 0) providers.push(notListed(unlisted));
11246
11359
  return providers;
11247
11360
  });
11361
+ // src/node/VpEnv.ts
11362
+ const vp$1 = (args) => ({
11363
+ command: "vp",
11364
+ args: ["env", ...args],
11365
+ cwd: "/"
11366
+ });
11367
+ const defaults = vp$1(["default"]);
11368
+ const tools$1 = /* @__PURE__ */ new Set([
11369
+ "node",
11370
+ "npm",
11371
+ "pnpm",
11372
+ "yarn",
11373
+ "bun"
11374
+ ]);
11375
+ const Release$1 = Schema.Struct({
11376
+ version: Schema.String,
11377
+ latest: Schema.Boolean
11378
+ });
11379
+ const NodeReleases = Schema.Struct({ node: Schema.Array(Release$1) });
11380
+ const ManagerReleases = Schema.Struct({ package_managers: Schema.Record(Schema.String, Schema.Array(Release$1)) });
11381
+ const malformed$1 = (command, issue) => new OutputMalformed({
11382
+ command: commandLine(command),
11383
+ issue
11384
+ });
11385
+ const readDefaults = Effect.fn("VpEnv.readDefaults")(function* (output) {
11386
+ const entries = [];
11387
+ const notices = [];
11388
+ let recognized = false;
11389
+ for (const line of output.split("\n").map((text) => text.trim())) {
11390
+ if (line.startsWith("No default Node.js version configured.") || line === "Package manager defaults: not configured") recognized = true;
11391
+ if (!line.startsWith("Default ")) continue;
11392
+ const fields = /^Default (?<name>.+) version: (?<selector>\S+)$/u.exec(line)?.groups;
11393
+ const label = fields?.["name"];
11394
+ const selector = fields?.["selector"];
11395
+ if (label === void 0 || selector === void 0) return yield* malformed$1(defaults, `unrecognized default: ${line}`);
11396
+ recognized = true;
11397
+ const name = label === "Node.js" ? "node" : label;
11398
+ if (tools$1.has(name)) entries.push({
11399
+ name,
11400
+ selector
11401
+ });
11402
+ else notices.push({ message: `Vite+ default ${name} is not supported` });
11403
+ }
11404
+ if (!recognized) return yield* malformed$1(defaults, "no global defaults found");
11405
+ if (entries.length === 0) return {
11406
+ packages: [],
11407
+ notices
11408
+ };
11409
+ const manifest = /^\s+Set via: (?<path>.+)$/mu.exec(output)?.groups?.["path"];
11410
+ if (manifest === void 0) return yield* malformed$1(defaults, "no configuration path found");
11411
+ return {
11412
+ packages: entries.map(({ name, selector }) => {
11413
+ const version = semver.parse(selector);
11414
+ return {
11415
+ provider: "vp-env",
11416
+ id: name,
11417
+ name,
11418
+ ecosystem: name === "node" ? "node" : "npm",
11419
+ artifact: name === "yarn" && version !== null && version.major >= 2 ? "@yarnpkg/cli-dist" : name,
11420
+ current: version?.version ?? selector,
11421
+ declaration: {
11422
+ manifest,
11423
+ section: `${name} default`,
11424
+ selector
11425
+ },
11426
+ description: version === null ? `Managed automatically by Vite+ (${selector})` : "Vite+ global default",
11427
+ unremovable: "Vite+ defaults are cleared with vp env default --unset"
11428
+ };
11429
+ }),
11430
+ notices
11431
+ };
11432
+ });
11433
+ const make$4 = Effect.gen(function* () {
11434
+ const exec = yield* Exec;
11435
+ const configured = exec.stdout(defaults).pipe(Effect.flatMap(readDefaults));
11436
+ const offer = Effect.fn("VpEnv.offer")(function* (pkg) {
11437
+ const current = semver.parse(pkg.current);
11438
+ if (current === null) return;
11439
+ const withinMajor = pkg.id === "node" || pkg.id === "yarn" && current.major === 1;
11440
+ const command = vp$1([
11441
+ "list-remote",
11442
+ pkg.id,
11443
+ ...withinMajor ? [String(current.major)] : [],
11444
+ "--json"
11445
+ ]);
11446
+ const output = yield* exec.stdout(command);
11447
+ const releases = pkg.id === "node" ? (yield* decodeJson(NodeReleases, commandLine(command))(output)).node : (yield* decodeJson(ManagerReleases, commandLine(command))(output)).package_managers[pkg.id];
11448
+ if (releases === void 0 || releases.length === 0) return yield* malformed$1(command, `no releases found for ${pkg.name}`);
11449
+ return newestOffer(pkg.current, releases.filter(({ version, latest }) => (withinMajor || latest) && semver.prerelease(version) === null).map(({ version }) => version));
11450
+ });
11451
+ const list = Effect.gen(function* () {
11452
+ const found = yield* configured;
11453
+ const offered = yield* Effect.forEach(found.packages, (pkg) => Effect.map(Effect.result(offer(pkg)), (target) => ({
11454
+ pkg,
11455
+ target
11456
+ })), { concurrency: 4 });
11457
+ const notices = [...found.notices];
11458
+ return {
11459
+ packages: offered.map(({ pkg, target }) => {
11460
+ if (Result.isFailure(target)) {
11461
+ notices.push({
11462
+ rowKey: rowKey(pkg),
11463
+ message: describeError(target.failure)
11464
+ });
11465
+ return pkg;
11466
+ }
11467
+ return target.success === void 0 ? pkg : withTarget(pkg, target.success);
11468
+ }),
11469
+ notices
11470
+ };
11471
+ }).pipe(Effect.withSpan("VpEnv.list"));
11472
+ const observe = Effect.fn("VpEnv.observe")(function* (pkg) {
11473
+ return Option.fromUndefinedOr((yield* configured).packages.find((candidate) => candidate.id === pkg.id));
11474
+ });
11475
+ const unchanged = Effect.fn("VpEnv.unchanged")(function* (pkg) {
11476
+ const current = yield* observe(pkg);
11477
+ if (Option.isNone(current) || current.value.current !== pkg.current || current.value.declaration?.selector !== pkg.declaration?.selector) return yield* new InstallRefused({
11478
+ package: pkg.name,
11479
+ reason: "the Vite+ default changed; refresh before updating"
11480
+ });
11481
+ });
11482
+ return {
11483
+ name: "vp-env",
11484
+ mode: "system",
11485
+ list,
11486
+ observe,
11487
+ update: Effect.fn("VpEnv.update")(function* (pkg, target) {
11488
+ yield* exec.stdout(vp$1(["install", `${pkg.id}@${target}`]));
11489
+ yield* unchanged(pkg);
11490
+ yield* exec.stdout(vp$1(["default", `${pkg.id}@${target}`]));
11491
+ })
11492
+ };
11493
+ });
11248
11494
  // src/node/VpSelf.ts
11249
11495
  const vp = (args) => ({
11250
11496
  command: "vp",
@@ -11272,6 +11518,10 @@ const make$3 = Effect.gen(function* () {
11272
11518
  mode: "system",
11273
11519
  list: Effect.gen(function* () {
11274
11520
  const output = yield* exec.stdout(check$1);
11521
+ if (output.includes("Homebrew manages this installation.")) return {
11522
+ packages: [],
11523
+ notices: []
11524
+ };
11275
11525
  const { latest, current } = /found vite-plus@(?<latest>\S+) \(current: (?<current>\S+)\)/u.exec(output)?.groups ?? {};
11276
11526
  if (latest === void 0 || current === void 0) return yield* malformed(check$1);
11277
11527
  const pkg = vitePlus(current);
@@ -11347,7 +11597,7 @@ const make$2 = Effect.gen(function* () {
11347
11597
  exec.stdout(command$1("apt", ["list", "--upgradable"])).pipe(Effect.map(parseUpgradable)),
11348
11598
  exec.stdout(command$1("apt-mark", ["showhold"])).pipe(Effect.map((text) => new Set(text.split("\n").filter((line) => line !== "")))),
11349
11599
  restart
11350
- ], { concurrency: 4 });
11600
+ ], { concurrency: "unbounded" });
11351
11601
  const packages = upgradable.map((entry) => {
11352
11602
  const pkg = packageOf(entry, arch);
11353
11603
  return holds.has(entry.name) || holds.has(pkg.id) ? pkg : withTarget(pkg, entry.version);
@@ -11492,7 +11742,7 @@ const make$1 = Effect.gen(function* () {
11492
11742
  running,
11493
11743
  scan(args),
11494
11744
  Ref.get(installed)
11495
- ], { concurrency: 2 });
11745
+ ], { concurrency: "unbounded" });
11496
11746
  return {
11497
11747
  packages: packagesOf$1(version, offers.filter((offer) => !done.has(offer.label))),
11498
11748
  done
@@ -11577,7 +11827,7 @@ const make = Effect.gen(function* () {
11577
11827
  "list",
11578
11828
  "--outdated",
11579
11829
  "--show-python"
11580
- ])).pipe(Effect.map(parseTools))], { concurrency: 2 });
11830
+ ])).pipe(Effect.map(parseTools))], { concurrency: "unbounded" });
11581
11831
  const targets = new Map(outdated.flatMap((tool) => tool.fromPyPI && tool.latest !== void 0 ? [[tool.pkg.id, tool.latest]] : []));
11582
11832
  const index = privatePypiIndex();
11583
11833
  return {
@@ -11615,18 +11865,19 @@ const make = Effect.gen(function* () {
11615
11865
  const system$1 = [
11616
11866
  ["softwareupdate", make$1],
11617
11867
  ["apt-get", make$2],
11618
- ["brew", make$13],
11619
- ["codesign", make$10],
11868
+ ["brew", make$14],
11869
+ ["codesign", make$11],
11620
11870
  ["npm", makeNpm],
11621
11871
  ["pnpm", makePnpm],
11622
11872
  ["vp", makeVp],
11623
11873
  ["vp", make$3],
11624
- ["cargo", make$9],
11625
- ["go", make$7],
11874
+ ["vp", make$4],
11875
+ ["cargo", make$10],
11876
+ ["go", make$8],
11626
11877
  ["uv", make]
11627
11878
  ];
11628
- const available = (entries) => Effect.filter([...new Set(entries.map(([command]) => command))], onPath).pipe(Effect.map((found) => entries.filter(([command]) => found.includes(command))));
11629
- const systemProviders = available(system$1).pipe(Effect.flatMap((found) => Effect.forEach(found, ([, make]) => make)));
11879
+ const available = (entries) => Effect.filter([...new Set(entries.map(([command]) => command))], onPath, { concurrency: "unbounded" }).pipe(Effect.map((found) => entries.filter(([command]) => found.includes(command))));
11880
+ const systemProviders = available(system$1).pipe(Effect.flatMap((found) => Effect.forEach(found, ([, make]) => make, { concurrency: "unbounded" })));
11630
11881
  const project$1 = [
11631
11882
  ["go", {
11632
11883
  manifests: ["go.mod"],
@@ -11652,9 +11903,8 @@ const project$1 = [
11652
11903
  const manifests = project$1.flatMap(([, kind]) => kind.manifests);
11653
11904
  const projectProviders = (given) => Effect.gen(function* () {
11654
11905
  const dir = yield* (yield* FileSystem.FileSystem).realPath(given).pipe(Effect.orElseSucceed(() => given));
11655
- const found = yield* available(project$1);
11656
- const nested = yield* nestedDirs(dir, manifests);
11657
- return (yield* Effect.forEach(found, ([, kind]) => kind.discover(dir, [...new Set(kind.manifests.flatMap(nested))]))).flat();
11906
+ const [found, nested] = yield* Effect.all([available(project$1), nestedDirs(dir, manifests)], { concurrency: "unbounded" });
11907
+ return (yield* Effect.forEach(found, ([, kind]) => kind.discover(dir, [...new Set(kind.manifests.flatMap(nested))]), { concurrency: "unbounded" })).flat();
11658
11908
  });
11659
11909
  const layer$3 = Layer.effect(ProviderSource, Effect.gen(function* () {
11660
11910
  const context = yield* Effect.context();
@@ -11764,7 +12014,7 @@ const message = (version) => {
11764
12014
  };
11765
12015
  const lookup$1 = Effect.fn("npm.lookup")(function* (client, wanted) {
11766
12016
  const path = wanted.name.replace("/", "%2F");
11767
- const [packument, downloads] = yield* Effect.all([getJson(client, `https://registry.npmjs.org/${path}`, Packument), getJson(client, `https://api.npmjs.org/downloads/point/last-week/${wanted.name}`, Downloads).pipe(Effect.option)], { concurrency: 2 });
12017
+ const [packument, downloads] = yield* Effect.all([getJson(client, `https://registry.npmjs.org/${path}`, Packument), getJson(client, `https://api.npmjs.org/downloads/point/last-week/${wanted.name}`, Downloads).pipe(Effect.option)], { concurrency: "unbounded" });
11768
12018
  const versions = packument.versions ?? {};
11769
12019
  const latest = packument["dist-tags"]?.latest;
11770
12020
  const repository = Predicate.isString(packument.repository) ? packument.repository : packument.repository?.url;
@@ -11804,7 +12054,7 @@ const repositoryKeys = [
11804
12054
  "GitHub"
11805
12055
  ];
11806
12056
  const lookup = Effect.fn("pypi.lookup")(function* (client, wanted) {
11807
- const [project, recent] = yield* Effect.all([getJson(client, `https://pypi.org/pypi/${wanted.name}/json`, Project), getJson(client, `https://pypistats.org/api/packages/${wanted.name.toLowerCase()}/recent`, Recent).pipe(Effect.option)], { concurrency: 2 });
12057
+ const [project, recent] = yield* Effect.all([getJson(client, `https://pypi.org/pypi/${wanted.name}/json`, Project), getJson(client, `https://pypistats.org/api/packages/${wanted.name.toLowerCase()}/recent`, Recent).pipe(Effect.option)], { concurrency: "unbounded" });
11808
12058
  const urls = project.info.project_urls ?? {};
11809
12059
  const repository = repositoryKeys.map((key) => urls[key]).find((url) => url !== void 0);
11810
12060
  const homepage = urls["Homepage"] ?? project.info.home_page ?? void 0;
@@ -12398,12 +12648,10 @@ const layer$1 = (cacheDir) => Layer.succeed(UpdateLog, UpdateLog.of({
12398
12648
  const directory = path.join(cacheDir, "updates");
12399
12649
  const names = (await readdir(directory).catch(() => [])).filter((name) => name.endsWith(".jsonl")).toSorted();
12400
12650
  const found = [];
12401
- for (const name of names) {
12402
- const text = await readFile(path.join(directory, name), "utf-8").catch(() => "");
12403
- for (const line of text.split("\n")) {
12404
- const entry = Option.getOrUndefined(decodeLine(line));
12405
- if (entry?.scope === scope && Date.parse(entry.at) >= since) found.push(entry);
12406
- }
12651
+ const texts = await Promise.all(names.map(async (name) => await readFile(path.join(directory, name), "utf-8").catch(() => "")));
12652
+ for (const text of texts) for (const line of text.split("\n")) {
12653
+ const entry = Option.getOrUndefined(decodeLine(line));
12654
+ if (entry?.scope === scope && Date.parse(entry.at) >= since) found.push(entry);
12407
12655
  }
12408
12656
  return found;
12409
12657
  })
@@ -12491,18 +12739,17 @@ const runProvider = Effect.fn("Engine.runProvider")(function* (provider, targets
12491
12739
  key: rowKey(target.pkg),
12492
12740
  outcome
12493
12741
  }))));
12494
- const checked = [];
12742
+ const applied = [];
12495
12743
  for (const target of targets) {
12496
12744
  if (yield* stopped) {
12497
12745
  yield* finish(target, Outcome.NotStarted());
12498
12746
  continue;
12499
12747
  }
12500
- const result = yield* check(target, emit);
12501
- if (Result.isFailure(result)) yield* finish(target, result.failure);
12502
- else checked.push([target, result.success]);
12503
- }
12504
- const applied = [];
12505
- for (const [target, attempt] of checked) {
12748
+ const checked = yield* check(target, emit);
12749
+ if (Result.isFailure(checked)) {
12750
+ yield* finish(target, checked.failure);
12751
+ continue;
12752
+ }
12506
12753
  if (yield* stopped) {
12507
12754
  yield* finish(target, Outcome.NotStarted());
12508
12755
  continue;
@@ -12511,7 +12758,7 @@ const runProvider = Effect.fn("Engine.runProvider")(function* (provider, targets
12511
12758
  key: rowKey(target.pkg),
12512
12759
  phase: "applying"
12513
12760
  }));
12514
- const result = yield* Effect.result(withDecisions(attempt));
12761
+ const result = yield* Effect.result(withDecisions(checked.success));
12515
12762
  if (Result.isFailure(result)) yield* finish(target, failedAs(result.failure, false));
12516
12763
  else if (provider.reconcile === void 0) yield* finish(target, yield* verify(target, emit));
12517
12764
  else applied.push(target);
@@ -12656,15 +12903,236 @@ const updateTools = (home) => Action.implement(UpdateActions, Effect.gen(functio
12656
12903
  }));
12657
12904
  // src/mcp.ts
12658
12905
  const instructions = "upup reads the tools installed on this machine and the registry dependencies of a project, audits offered updates with the user's chosen model, and applies or removes them. Start with list_packages; every other tool takes its package keys. Before updating, look at package_changes or run audit_packages and poll audit_reports until no Audit is running; operating-system updates (ecosystem macos or apt) can't be audited and have no changes to read; app updates (ecosystem sparkle, electron, appstore or app) can't be audited either, and package_changes gives their release notes when the app's feed has any; ask the user to quit an open app before updating it; the Node.js versions a project pins (ecosystem node) can't be audited either and are edited in place. update_packages and remove_packages change the user's machine or project and cannot be undone by upup: confirm them with the user, and ask before passing allowScripts.";
12659
- const serve = (home) => ActionMcp.runStdio([
12906
+ const tools = (home) => [
12660
12907
  inventoryTools(home),
12661
12908
  auditTools(home),
12662
12909
  updateTools(home)
12663
- ], {
12910
+ ];
12911
+ const serverInfo = {
12664
12912
  name: "upup",
12665
12913
  version: version$1,
12666
12914
  instructions
12915
+ };
12916
+ const serve = (home) => ActionMcp.runStdio(tools(home), serverInfo);
12917
+ // src/session/operation.ts
12918
+ const machine$1 = setup({
12919
+ schemas: { events: {
12920
+ Run: types(),
12921
+ Stop: types(),
12922
+ Ask: types(),
12923
+ Answer: types(),
12924
+ Finish: types()
12925
+ } },
12926
+ states: {
12927
+ Idle: { schemas: { context: types() } },
12928
+ Running: { schemas: { context: types() } },
12929
+ Asking: { schemas: { context: types() } }
12930
+ }
12931
+ }).createFSM({
12932
+ initial: "Idle",
12933
+ context: {
12934
+ stopping: false,
12935
+ decision: void 0
12936
+ },
12937
+ states: {
12938
+ Idle: { on: { Run: { target: "Running" } } },
12939
+ Running: { on: {
12940
+ Stop: { context: { stopping: true } },
12941
+ Ask: ({ event }) => ({
12942
+ target: "Asking",
12943
+ context: { decision: event.decision }
12944
+ }),
12945
+ Finish: {
12946
+ target: "Idle",
12947
+ context: { stopping: false }
12948
+ }
12949
+ } },
12950
+ Asking: { on: {
12951
+ Stop: { context: { stopping: true } },
12952
+ Answer: {
12953
+ target: "Running",
12954
+ context: { decision: void 0 }
12955
+ },
12956
+ Finish: {
12957
+ target: "Idle",
12958
+ context: {
12959
+ stopping: false,
12960
+ decision: void 0
12961
+ }
12962
+ }
12963
+ } }
12964
+ }
12965
+ });
12966
+ const step$1 = (snapshot, event) => {
12967
+ const [next] = machine$1.transition(snapshot, event);
12968
+ return [next, next !== snapshot];
12969
+ };
12970
+ // src/session/sign-in.ts
12971
+ const machine = setup({
12972
+ schemas: { events: {
12973
+ SaveKey: types(),
12974
+ Start: types(),
12975
+ Noticed: types(),
12976
+ Prompt: types(),
12977
+ Answer: types(),
12978
+ Succeed: types(),
12979
+ Fail: types(),
12980
+ Close: types()
12981
+ } },
12982
+ states: {
12983
+ Idle: { schemas: { context: types() } },
12984
+ SavingKey: { schemas: { context: types() } },
12985
+ Working: { schemas: { context: types() } },
12986
+ Prompted: { schemas: { context: types() } },
12987
+ Failed: { schemas: { context: types() } },
12988
+ SignedIn: { schemas: { context: types() } }
12989
+ }
12990
+ }).createFSM({
12991
+ initial: "Idle",
12992
+ context: {
12993
+ attempt: 0,
12994
+ provider: void 0,
12995
+ notices: void 0,
12996
+ prompt: void 0,
12997
+ issue: void 0
12998
+ },
12999
+ states: {
13000
+ Idle: { on: {
13001
+ SaveKey: ({ context, event }) => ({
13002
+ target: "SavingKey",
13003
+ context: {
13004
+ attempt: context.attempt + 1,
13005
+ provider: event.provider
13006
+ }
13007
+ }),
13008
+ Start: ({ context, event }) => ({
13009
+ target: "Working",
13010
+ context: {
13011
+ attempt: context.attempt + 1,
13012
+ provider: event.provider,
13013
+ notices: []
13014
+ }
13015
+ })
13016
+ } },
13017
+ SavingKey: { on: {
13018
+ Succeed: ({ context, event }) => event.attempt === context.attempt ? { target: "SignedIn" } : void 0,
13019
+ Fail: ({ context, event }) => event.attempt === context.attempt ? {
13020
+ target: "Failed",
13021
+ context: { issue: event.issue }
13022
+ } : void 0,
13023
+ Close: {
13024
+ target: "Idle",
13025
+ context: { provider: void 0 }
13026
+ }
13027
+ } },
13028
+ Working: { on: {
13029
+ Noticed: ({ context, event }) => event.attempt === context.attempt ? { context: { notices: [...context.notices, event.notice] } } : void 0,
13030
+ Prompt: ({ context, event }) => event.attempt === context.attempt ? {
13031
+ target: "Prompted",
13032
+ context: { prompt: event.prompt }
13033
+ } : void 0,
13034
+ Succeed: ({ context, event }) => event.attempt === context.attempt ? {
13035
+ target: "SignedIn",
13036
+ context: { notices: void 0 }
13037
+ } : void 0,
13038
+ Fail: ({ context, event }) => event.attempt === context.attempt ? {
13039
+ target: "Failed",
13040
+ context: {
13041
+ notices: void 0,
13042
+ issue: event.issue
13043
+ }
13044
+ } : void 0,
13045
+ Close: {
13046
+ target: "Idle",
13047
+ context: {
13048
+ provider: void 0,
13049
+ notices: void 0
13050
+ }
13051
+ }
13052
+ } },
13053
+ Prompted: { on: {
13054
+ Noticed: ({ context, event }) => event.attempt === context.attempt ? { context: { notices: [...context.notices, event.notice] } } : void 0,
13055
+ Answer: ({ context, event }) => event.attempt === context.attempt ? {
13056
+ target: "Working",
13057
+ context: { prompt: void 0 }
13058
+ } : void 0,
13059
+ Succeed: ({ context, event }) => event.attempt === context.attempt ? {
13060
+ target: "SignedIn",
13061
+ context: {
13062
+ notices: void 0,
13063
+ prompt: void 0
13064
+ }
13065
+ } : void 0,
13066
+ Fail: ({ context, event }) => event.attempt === context.attempt ? {
13067
+ target: "Failed",
13068
+ context: {
13069
+ notices: void 0,
13070
+ prompt: void 0,
13071
+ issue: event.issue
13072
+ }
13073
+ } : void 0,
13074
+ Close: {
13075
+ target: "Idle",
13076
+ context: {
13077
+ provider: void 0,
13078
+ notices: void 0,
13079
+ prompt: void 0
13080
+ }
13081
+ }
13082
+ } },
13083
+ Failed: { on: {
13084
+ SaveKey: ({ context, event }) => ({
13085
+ target: "SavingKey",
13086
+ context: {
13087
+ attempt: context.attempt + 1,
13088
+ provider: event.provider,
13089
+ issue: void 0
13090
+ }
13091
+ }),
13092
+ Start: ({ context, event }) => ({
13093
+ target: "Working",
13094
+ context: {
13095
+ attempt: context.attempt + 1,
13096
+ provider: event.provider,
13097
+ notices: [],
13098
+ issue: void 0
13099
+ }
13100
+ }),
13101
+ Close: {
13102
+ target: "Idle",
13103
+ context: {
13104
+ provider: void 0,
13105
+ issue: void 0
13106
+ }
13107
+ }
13108
+ } },
13109
+ SignedIn: { on: {
13110
+ SaveKey: ({ context, event }) => ({
13111
+ target: "SavingKey",
13112
+ context: {
13113
+ attempt: context.attempt + 1,
13114
+ provider: event.provider
13115
+ }
13116
+ }),
13117
+ Start: ({ context, event }) => ({
13118
+ target: "Working",
13119
+ context: {
13120
+ attempt: context.attempt + 1,
13121
+ provider: event.provider,
13122
+ notices: []
13123
+ }
13124
+ }),
13125
+ Close: {
13126
+ target: "Idle",
13127
+ context: { provider: void 0 }
13128
+ }
13129
+ } }
13130
+ }
12667
13131
  });
13132
+ const step = (snapshot, event) => {
13133
+ const [next] = machine.transition(snapshot, event);
13134
+ return [next, next !== snapshot];
13135
+ };
12668
13136
  // src/session/Session.ts
12669
13137
  const aboutKey = (pkg) => `${rowKey(pkg)}@${pkg.current}..${pkg.target ?? ""}`;
12670
13138
  const auditKey = (pkg, mode) => {
@@ -12677,13 +13145,14 @@ const questionOf = (state) => {
12677
13145
  _tag: "Password",
12678
13146
  request: state.password
12679
13147
  };
12680
- if (state.decision?._tag === "ScriptsBlocked") return {
13148
+ if (state.operation.value !== "Asking") return;
13149
+ const { decision } = state.operation.context;
13150
+ return decision._tag === "ScriptsBlocked" ? {
12681
13151
  _tag: "Scripts",
12682
- blocked: state.decision
12683
- };
12684
- if (state.decision?._tag === "AppRunning") return {
13152
+ blocked: decision
13153
+ } : {
12685
13154
  _tag: "Quit",
12686
- running: state.decision
13155
+ running: decision
12687
13156
  };
12688
13157
  };
12689
13158
  const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
@@ -12694,14 +13163,12 @@ const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
12694
13163
  discoveryError: void 0,
12695
13164
  providers: [],
12696
13165
  selected: [],
12697
- running: false,
12698
- stopping: false,
13166
+ operation: machine$1.initialState,
12699
13167
  activity: {},
12700
13168
  outcomes: {},
12701
13169
  actions: {},
12702
13170
  installed: {},
12703
13171
  removed: {},
12704
- decision: void 0,
12705
13172
  password: void 0,
12706
13173
  about: {},
12707
13174
  audits,
@@ -12711,7 +13178,7 @@ const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
12711
13178
  preferences,
12712
13179
  settingsIssue,
12713
13180
  catalogue: void 0,
12714
- signIn: void 0
13181
+ signIn: machine.initialState
12715
13182
  });
12716
13183
  const issueOf = (error) => `${error.file} cannot be read (${error.issue}); upup keeps audit first on and changes no settings until it is fixed or deleted`;
12717
13184
  const without = (record, key) => Object.fromEntries(Object.entries(record).filter(([entry]) => entry !== key));
@@ -12822,9 +13289,15 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
12822
13289
  const providers = yield* Ref.make([]);
12823
13290
  const loading = yield* FiberHandle.make();
12824
13291
  const operation = yield* FiberHandle.make();
12825
- const stopRequested = yield* Ref.make(false);
12826
13292
  const pendingDecision = yield* Ref.make(Option.none());
12827
13293
  const update = (f) => SubscriptionRef.update(state, f);
13294
+ const send = (event) => SubscriptionRef.modify(state, (current) => {
13295
+ const [next, handled] = step$1(current.operation, event);
13296
+ return [handled, handled ? {
13297
+ ...current,
13298
+ operation: next
13299
+ } : current];
13300
+ });
12828
13301
  const setProvider = (name, f) => update((current) => ({
12829
13302
  ...current,
12830
13303
  providers: current.providers.map((entry) => entry.name === name ? f(entry) : entry)
@@ -12922,8 +13395,8 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
12922
13395
  };
12923
13396
  });
12924
13397
  const recallAll = Effect.gen(function* () {
12925
- const found = yield* earlier(yield* SubscriptionRef.get(state));
12926
- const logged = yield* fromLog(yield* SubscriptionRef.get(state));
13398
+ const before = yield* SubscriptionRef.get(state);
13399
+ const [found, logged] = yield* Effect.all([earlier(before), fromLog(before)], { concurrency: "unbounded" });
12927
13400
  yield* update((latest) => ({
12928
13401
  ...latest,
12929
13402
  installed: {
@@ -12942,15 +13415,14 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
12942
13415
  const identity = identityOf(pkg, current.mode);
12943
13416
  if (identity !== void 0 && current.audits[jobKey(identity)]?._tag !== "Running") wanted.set(jobKey(identity), identity);
12944
13417
  }
12945
- yield* Effect.forEach(wanted.values(), audits.recall, {
12946
- concurrency: 4,
12947
- discard: true
12948
- });
12949
13418
  const offered = packagesOf(current).flatMap((pkg) => {
12950
13419
  const identity = pkg.target === void 0 ? void 0 : identityOf(pkg, current.mode);
12951
13420
  return identity === void 0 ? [] : [identity];
12952
13421
  });
12953
- const majors = yield* Effect.forEach(offered, (identity) => audits.majors(identity).pipe(Effect.map((identityMajors) => [jobKey(identity), identityMajors])), { concurrency: 4 });
13422
+ const [, majors] = yield* Effect.all([Effect.forEach(wanted.values(), audits.recall, {
13423
+ concurrency: 4,
13424
+ discard: true
13425
+ }), Effect.forEach(offered, (identity) => audits.majors(identity).pipe(Effect.map((identityMajors) => [jobKey(identity), identityMajors])), { concurrency: 4 })], { concurrency: "unbounded" });
12954
13426
  yield* update((latest) => ({
12955
13427
  ...latest,
12956
13428
  majors: {
@@ -12995,11 +13467,12 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
12995
13467
  return {
12996
13468
  ...initial(mode, scope, current.preferences, current.audits, current.settingsIssue),
12997
13469
  ...places.get(`${mode}:${scope}`),
12998
- catalogue: current.catalogue
13470
+ catalogue: current.catalogue,
13471
+ signIn: current.signIn
12999
13472
  };
13000
13473
  });
13001
13474
  const open = Effect.fn("Session.open")(function* (mode, scope) {
13002
- if ((yield* SubscriptionRef.get(state)).running) return;
13475
+ if ((yield* SubscriptionRef.get(state)).operation.value !== "Idle") return;
13003
13476
  yield* FiberHandle.clear(loading);
13004
13477
  yield* reset(mode, scope);
13005
13478
  yield* FiberHandle.run(loading, discover(mode, scope));
@@ -13016,7 +13489,7 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13016
13489
  });
13017
13490
  const refresh = Effect.gen(function* () {
13018
13491
  const current = yield* SubscriptionRef.get(state);
13019
- if (current.running || current.discovering) return;
13492
+ if (current.operation.value !== "Idle" || current.discovering) return;
13020
13493
  yield* FiberHandle.run(loading, discover(current.mode, current.scope));
13021
13494
  });
13022
13495
  const toggle = Effect.fn("Session.toggle")(function* (key) {
@@ -13038,16 +13511,11 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13038
13511
  const ask = (decision) => Effect.gen(function* () {
13039
13512
  const answer = yield* Deferred.make();
13040
13513
  yield* Ref.set(pendingDecision, Option.some(answer));
13041
- yield* update((current) => ({
13042
- ...current,
13514
+ yield* send({
13515
+ type: "Ask",
13043
13516
  decision
13044
- }));
13045
- const allowed = yield* Deferred.await(answer);
13046
- yield* update((current) => ({
13047
- ...current,
13048
- decision: void 0
13049
- }));
13050
- return allowed;
13517
+ });
13518
+ return yield* Deferred.await(answer);
13051
13519
  });
13052
13520
  const decisions = Decisions.of({
13053
13521
  asks: true,
@@ -13056,16 +13524,9 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13056
13524
  });
13057
13525
  const run = Effect.fn("Session.run")(function* (action, keys, accepted = []) {
13058
13526
  const current = yield* SubscriptionRef.get(state);
13059
- if (current.running) return;
13060
13527
  const targets = targetsFor(current, yield* Ref.get(providers), action, keys);
13061
- if (targets.length === 0) return;
13062
- yield* Ref.set(stopRequested, false);
13528
+ if (targets.length === 0 || !(yield* send({ type: "Run" }))) return;
13063
13529
  const vetting = action === "update" && current.preferences.auditFirst;
13064
- yield* update((latest) => ({
13065
- ...latest,
13066
- running: true,
13067
- stopping: false
13068
- }));
13069
13530
  const touched = [...new Set(targets.map((target) => target.provider))];
13070
13531
  const confirmed = targets.filter((target) => accepted.includes(rowKey(target.pkg)));
13071
13532
  const chosen = vetting ? vet(targets.filter((target) => !accepted.includes(rowKey(target.pkg))), current.mode).pipe(Effect.flatMap(({ approved, refused }) => update((latest) => ({
@@ -13085,7 +13546,7 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13085
13546
  ...latest.activity,
13086
13547
  ...Object.fromEntries(approved.map((target) => [rowKey(target.pkg), "waiting"]))
13087
13548
  }
13088
- }))), Effect.flatMap((approved) => run$1(approved, Ref.get(stopRequested)).pipe(Stream.runForEach((event) => update((latest) => event._tag === "Progress" ? {
13549
+ }))), Effect.flatMap((approved) => run$1(approved, SubscriptionRef.get(state).pipe(Effect.map((latest) => latest.operation.context.stopping))).pipe(Stream.runForEach((event) => update((latest) => event._tag === "Progress" ? {
13089
13550
  ...latest,
13090
13551
  activity: {
13091
13552
  ...latest.activity,
@@ -13107,21 +13568,14 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13107
13568
  selected: event.outcome._tag === "Completed" ? latest.selected.filter((key) => key !== event.key) : latest.selected
13108
13569
  })), Effect.provideService(Decisions, decisions), Effect.provideService(UpdateLog, scoped(updateLog, placeOf(current))), Effect.andThen(collect$2(touched)), Effect.andThen(recallAll))), Effect.ensuring(Ref.set(pendingDecision, Option.none()).pipe(Effect.andThen(update((latest) => ({
13109
13570
  ...latest,
13110
- running: false,
13111
- stopping: false,
13112
- activity: {},
13113
- decision: void 0
13571
+ operation: step$1(latest.operation, { type: "Finish" })[0],
13572
+ activity: {}
13114
13573
  })))))));
13115
13574
  });
13116
13575
  const answerPassword = (password) => sudo.answer(password);
13117
- const stop = Effect.gen(function* () {
13118
- yield* Ref.set(stopRequested, true);
13119
- yield* update((current) => current.running ? {
13120
- ...current,
13121
- stopping: true
13122
- } : current);
13123
- });
13576
+ const stop = Effect.asVoid(send({ type: "Stop" }));
13124
13577
  const answer = Effect.fn("Session.answer")(function* (allowed) {
13578
+ if (!(yield* send({ type: "Answer" }))) return;
13125
13579
  const pending = yield* Ref.getAndSet(pendingDecision, Option.none());
13126
13580
  if (Option.isSome(pending)) yield* Deferred.succeed(pending.value, allowed);
13127
13581
  });
@@ -13178,73 +13632,86 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13178
13632
  ...stored,
13179
13633
  auditFirst: on
13180
13634
  }));
13181
- const setSignIn = (signIn) => update((current) => ({
13182
- ...current,
13183
- signIn
13184
- }));
13185
- const saveKey = (provider, key) => setSignIn({
13186
- _tag: "Working",
13187
- provider,
13188
- notices: []
13189
- }).pipe(Effect.andThen(ai.setApiKey(provider, key)), Effect.matchEffect({
13190
- onSuccess: () => setSignIn(void 0).pipe(Effect.andThen(loadCatalogue)),
13191
- onFailure: (error) => setSignIn({
13192
- _tag: "Failed",
13193
- provider,
13635
+ const sendSignIn = (event) => SubscriptionRef.modify(state, (current) => {
13636
+ const stepped = step(current.signIn, event);
13637
+ const [next, handled] = stepped;
13638
+ return [stepped, handled ? {
13639
+ ...current,
13640
+ signIn: next
13641
+ } : current];
13642
+ });
13643
+ const takeAnswer = (taken) => Ref.modify(pendingAnswer, (pending) => Option.isSome(pending) && taken(pending.value.attempt) ? [pending, Option.none()] : [Option.none(), pending]);
13644
+ const finish = (attempt) => Effect.matchEffect({
13645
+ onSuccess: () => sendSignIn({
13646
+ type: "Succeed",
13647
+ attempt
13648
+ }).pipe(Effect.andThen(loadCatalogue)),
13649
+ onFailure: (error) => sendSignIn({
13650
+ type: "Fail",
13651
+ attempt,
13194
13652
  issue: error.issue
13195
13653
  })
13196
- }));
13654
+ });
13655
+ const saveKey = (provider, key) => Effect.gen(function* () {
13656
+ const [saving, handled] = yield* sendSignIn({
13657
+ type: "SaveKey",
13658
+ provider
13659
+ });
13660
+ if (handled) yield* ai.setApiKey(provider, key).pipe(finish(saving.context.attempt));
13661
+ });
13197
13662
  const signIn = (provider) => Effect.gen(function* () {
13198
- yield* setSignIn({
13199
- _tag: "Working",
13200
- provider,
13201
- notices: []
13663
+ const [working, handled] = yield* sendSignIn({
13664
+ type: "Start",
13665
+ provider
13202
13666
  });
13667
+ if (!handled) return;
13668
+ const { attempt } = working.context;
13203
13669
  const context = yield* Effect.context();
13204
- const working = (change) => update((current) => current.signIn?._tag === "Working" && current.signIn.provider === provider ? {
13205
- ...current,
13206
- signIn: change(current.signIn)
13207
- } : current);
13208
13670
  yield* FiberHandle.run(signing, ai.login(provider, {
13209
13671
  notify: (event) => {
13210
- const notice = noticeOf(event);
13211
- Effect.runForkWith(context)(working((current) => ({
13212
- ...current,
13213
- notices: [...current.notices, notice]
13214
- })));
13672
+ Effect.runForkWith(context)(sendSignIn({
13673
+ type: "Noticed",
13674
+ attempt,
13675
+ notice: noticeOf(event)
13676
+ }));
13215
13677
  },
13216
13678
  prompt: async (prompt) => await Effect.runPromiseWith(context)(Effect.gen(function* () {
13217
13679
  const reply = yield* Deferred.make();
13218
- yield* Ref.set(pendingAnswer, Option.some(reply));
13219
- yield* working((current) => ({
13220
- ...current,
13680
+ const replaced = yield* Ref.modify(pendingAnswer, (pending) => Option.isSome(pending) && pending.value.attempt >= attempt ? [void 0, pending] : [pending, Option.some({
13681
+ attempt,
13682
+ reply
13683
+ })]);
13684
+ if (replaced === void 0) return yield* Effect.interrupt;
13685
+ if (Option.isSome(replaced)) yield* Deferred.interrupt(replaced.value.reply);
13686
+ const [, asked] = yield* sendSignIn({
13687
+ type: "Prompt",
13688
+ attempt,
13221
13689
  prompt
13222
- }));
13690
+ });
13691
+ if (!asked) {
13692
+ yield* takeAnswer((stored) => stored === attempt);
13693
+ return yield* Effect.interrupt;
13694
+ }
13223
13695
  return yield* Deferred.await(reply);
13224
13696
  }))
13225
- }).pipe(Effect.matchEffect({
13226
- onSuccess: () => setSignIn(void 0).pipe(Effect.andThen(loadCatalogue)),
13227
- onFailure: (error) => setSignIn({
13228
- _tag: "Failed",
13229
- provider,
13230
- issue: error.issue
13231
- })
13232
- })));
13697
+ }).pipe(finish(attempt)));
13233
13698
  });
13234
13699
  const answerSignIn = (reply) => Effect.gen(function* () {
13235
- const pending = yield* Ref.getAndSet(pendingAnswer, Option.none());
13236
- if (Option.isNone(pending)) return;
13237
- yield* update((current) => current.signIn?._tag === "Working" ? {
13238
- ...current,
13239
- signIn: {
13240
- _tag: "Working",
13241
- provider: current.signIn.provider,
13242
- notices: current.signIn.notices
13243
- }
13244
- } : current);
13245
- yield* Deferred.succeed(pending.value, reply);
13700
+ const { attempt } = (yield* SubscriptionRef.get(state)).signIn.context;
13701
+ const [, handled] = yield* sendSignIn({
13702
+ type: "Answer",
13703
+ attempt
13704
+ });
13705
+ if (!handled) return;
13706
+ const pending = yield* takeAnswer((stored) => stored === attempt);
13707
+ if (Option.isSome(pending)) yield* Deferred.succeed(pending.value.reply, reply);
13708
+ });
13709
+ const closeSignIn = Effect.gen(function* () {
13710
+ const [closed] = yield* sendSignIn({ type: "Close" });
13711
+ yield* FiberHandle.clear(signing);
13712
+ const pending = yield* takeAnswer((stored) => stored <= closed.context.attempt);
13713
+ if (Option.isSome(pending)) yield* Deferred.interrupt(pending.value.reply);
13246
13714
  });
13247
- const closeSignIn = FiberHandle.clear(signing).pipe(Effect.andThen(Ref.set(pendingAnswer, Option.none())), Effect.andThen(setSignIn(void 0)));
13248
13715
  const awaitIdle = FiberHandle.awaitEmpty(operation);
13249
13716
  return Session.of({
13250
13717
  changes: SubscriptionRef.changes(state),
@@ -13284,7 +13751,7 @@ const silenceNodeWarningsWhileTuiDraws = () => {
13284
13751
  };
13285
13752
  const loadTui = Effect.suspend(() => {
13286
13753
  silenceNodeWarningsWhileTuiDraws();
13287
- return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.12 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-BiTtZ1EF.mjs"));
13754
+ return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.13 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-DV_6wbrQ.mjs"));
13288
13755
  });
13289
13756
  const open$1 = (begin, projectDir) => Effect.gen(function* () {
13290
13757
  const { launch } = yield* loadTui;