tines 0.0.243 → 0.0.244

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +178 -116
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4243,8 +4243,8 @@ function createApiClient(options) {
4243
4243
  getPublicationResult: (candidateId) => get(
4244
4244
  `/api/v1/publications/${encodeURIComponent(candidateId)}/result`
4245
4245
  ),
4246
- listPublications: (workflow2) => get(
4247
- `/api/v1/publications${query({ workflow: workflow2 })}`
4246
+ listPublications: (workflow2, page) => get(
4247
+ `/api/v1/publications${query({ workflow: workflow2, ...page })}`
4248
4248
  ),
4249
4249
  withdrawPublication: (snapshotId) => request(
4250
4250
  "POST",
@@ -4453,11 +4453,12 @@ async function* listPages(fetchPage, opts = {}) {
4453
4453
  const maxItems = opts.maxItems ?? MAX_ALL_PAGES_ITEMS;
4454
4454
  const seen = /* @__PURE__ */ new Set();
4455
4455
  const seenCursors = /* @__PURE__ */ new Set();
4456
+ const identify = opts.identify ?? ((item) => item.id);
4456
4457
  let cursor;
4457
4458
  for (; ; ) {
4458
4459
  const res = await fetchPage({ limit: pageSize, cursor });
4459
- const fresh = res.items.filter((item) => !seen.has(item.id));
4460
- for (const item of fresh) seen.add(item.id);
4460
+ const fresh = res.items.filter((item) => !seen.has(identify(item)));
4461
+ for (const item of fresh) seen.add(identify(item));
4461
4462
  if (seen.size > maxItems) {
4462
4463
  throw new Error(
4463
4464
  `list has more than ${maxItems} items \u2014 narrow it with filters, or page manually with --cursor`
@@ -5549,21 +5550,35 @@ var PACKAGE_CHOICES_MAX_BYTES = 512 * 1024;
5549
5550
  var PUBLIC_WORKFLOW_MAX_BYTES = 1048576;
5550
5551
  var PUBLICATION_CANDIDATE_TTL_MS = 15 * 60 * 1e3;
5551
5552
  var SNAPSHOT_ID = /^[A-Za-z0-9_-]{20,100}$/;
5552
- function parsePublicSnapshotReference(value, configuredOrigin) {
5553
- if (SNAPSHOT_ID.test(value)) return value;
5553
+ function parsePublicSnapshotUrl(value) {
5554
5554
  let url;
5555
5555
  try {
5556
5556
  url = new URL(value);
5557
5557
  } catch {
5558
- throw new Error("Expected a publication ID or canonical public URL");
5558
+ throw new Error("Expected a canonical public URL");
5559
5559
  }
5560
- if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
5561
- throw new Error("Invalid public publication URL");
5562
- if (configuredOrigin && url.origin !== new URL(configuredOrigin).origin)
5560
+ if (!["http:", "https:"].includes(url.protocol))
5561
+ throw new Error("Invalid public publication URL: must use HTTP or HTTPS");
5562
+ if (url.username || url.password)
5563
+ throw new Error("Invalid public publication URL: must not contain credentials");
5564
+ if (url.hash) throw new Error("Invalid public publication URL: must not contain a fragment");
5565
+ if (url.search)
5566
+ throw new Error("Invalid public publication URL: must not contain query parameters");
5567
+ const match = /^\/p\/([A-Za-z0-9_-]{20,100})(?:\/download)?$/.exec(url.pathname) ?? /^\/api\/v1\/publications\/public\/([A-Za-z0-9_-]{20,100})\/download$/.exec(url.pathname);
5568
+ if (!match) throw new Error("Invalid public publication URL: expected a canonical path");
5569
+ const origin = url.origin;
5570
+ return {
5571
+ snapshotId: match[1],
5572
+ publicUrl: `${origin}/p/${match[1]}`,
5573
+ downloadUrl: `${origin}/api/v1/publications/public/${match[1]}/download`
5574
+ };
5575
+ }
5576
+ function parsePublicSnapshotReference(value, configuredOrigin) {
5577
+ if (SNAPSHOT_ID.test(value)) return value;
5578
+ const parsed = parsePublicSnapshotUrl(value);
5579
+ if (configuredOrigin && new URL(parsed.publicUrl).origin !== new URL(configuredOrigin).origin)
5563
5580
  throw new Error("external_source_requires_download");
5564
- const match = /^\/p\/([A-Za-z0-9_-]{20,100})(?:\/download)?$/.exec(url.pathname);
5565
- if (!match || url.search) throw new Error("Invalid public publication URL");
5566
- return match[1];
5581
+ return parsed.snapshotId;
5567
5582
  }
5568
5583
 
5569
5584
  // ../shared/src/usage.ts
@@ -6125,10 +6140,14 @@ more results: rerun with --all-pages, or resume with --cursor ${res.next_cursor}
6125
6140
  );
6126
6141
  }
6127
6142
  }
6128
- async function fetchList(opts, fetchPage) {
6143
+ async function fetchList(opts, fetchPage, identify) {
6129
6144
  if (opts.allPages) {
6130
6145
  return {
6131
- items: await listAll(fetchPage, { pageSize: opts.limit, maxItems: opts.maxItems }),
6146
+ items: await listAll(fetchPage, {
6147
+ pageSize: opts.limit,
6148
+ maxItems: opts.maxItems,
6149
+ identify: identify ?? ((item) => item.id)
6150
+ }),
6132
6151
  next_cursor: null
6133
6152
  };
6134
6153
  }
@@ -12937,7 +12956,7 @@ async function askToInstall(plan) {
12937
12956
  rl.close();
12938
12957
  }
12939
12958
  }
12940
- async function recoverOrInstall(api, raw, plan) {
12959
+ async function recoverOrInstall(api, rawOrLoader, plan) {
12941
12960
  const checked = (receipt) => {
12942
12961
  if (receipt.id !== plan.plan_id || receipt.document_digest !== plan.document_digest || receipt.plan_digest !== plan.plan_digest)
12943
12962
  throw new Error("recovered workflow package receipt does not match the saved plan");
@@ -12948,6 +12967,7 @@ async function recoverOrInstall(api, raw, plan) {
12948
12967
  } catch (error) {
12949
12968
  if (!(error instanceof ApiError) || error.status !== 404) throw error;
12950
12969
  }
12970
+ const raw = typeof rawOrLoader === "function" ? await rawOrLoader() : rawOrLoader;
12951
12971
  const request = {
12952
12972
  document_json: raw,
12953
12973
  plan_token: plan.plan_token,
@@ -12971,7 +12991,6 @@ import { lookup as dnsLookup } from "node:dns/promises";
12971
12991
  import { request as httpRequest } from "node:http";
12972
12992
  import { request as httpsRequest } from "node:https";
12973
12993
  import { isIP } from "node:net";
12974
- var SNAPSHOT_PATH = /^\/p\/[A-Za-z0-9_-]{20,100}(?:\/download)?$/;
12975
12994
  var MAX_REDIRECTS = 3;
12976
12995
  var DEFAULT_TIMEOUT_MS = 3e4;
12977
12996
  function mappedIpv4(address) {
@@ -13038,40 +13057,51 @@ function isPublicAddress(address) {
13038
13057
  }
13039
13058
  return false;
13040
13059
  }
13041
- function validateHop(url, initial) {
13060
+ function validateHop(url) {
13042
13061
  if (!["http:", "https:"].includes(url.protocol))
13043
13062
  throw new Error("publication URL must use HTTPS");
13044
13063
  if (url.username || url.password) throw new Error("publication URL must not contain credentials");
13045
13064
  if (url.hash) throw new Error("publication URL must not contain a fragment");
13046
13065
  if (url.search) throw new Error("publication URL must not contain query parameters");
13047
- if (initial && !SNAPSHOT_PATH.test(url.pathname))
13048
- throw new Error("expected a canonical public snapshot or download URL");
13049
13066
  }
13050
- async function pinnedAddress(url, lookup) {
13067
+ async function pinnedAddresses(url, lookup) {
13051
13068
  const hostname4 = url.hostname.replace(/^\[|\]$/g, "");
13052
13069
  const literalFamily = isIP(hostname4);
13053
13070
  const addresses = literalFamily ? [{ address: hostname4, family: literalFamily }] : await lookup(hostname4, { all: true, verbatim: true });
13054
- if (!addresses.length) throw new Error(`publication host ${url.hostname} did not resolve`);
13055
- const loopbackDevelopment = url.protocol === "http:" && (url.hostname === "localhost" || addresses.every(({ address }) => isLoopback(address)));
13071
+ if (!addresses.length || addresses.some(({ address, family }) => isIP(address) !== family || ![4, 6].includes(family)))
13072
+ throw new Error(`publication host ${url.hostname} did not resolve to valid addresses`);
13073
+ const literalLoopback = Boolean(literalFamily && isLoopback(hostname4));
13074
+ const loopbackDevelopment = url.protocol === "http:" && (url.hostname === "localhost" || literalLoopback) && addresses.every(({ address }) => isLoopback(address));
13056
13075
  if (url.protocol === "http:" && !loopbackDevelopment)
13057
13076
  throw new Error(
13058
13077
  "publication URL must use HTTPS (HTTP is allowed only for loopback development)"
13059
13078
  );
13060
13079
  if (!loopbackDevelopment && addresses.some(({ address }) => !isPublicAddress(address)))
13061
13080
  throw new Error("publication URL resolves to a private or reserved address");
13062
- return addresses[0];
13081
+ return addresses;
13082
+ }
13083
+ function pinnedLookup(addresses) {
13084
+ return (_hostname, opts, callback) => {
13085
+ const family = typeof opts.family === "number" ? opts.family : 0;
13086
+ const matching = family === 4 || family === 6 ? addresses.filter((item) => item.family === family) : addresses;
13087
+ if (!matching.length)
13088
+ return callback(new Error(`publication host has no IPv${family} address`));
13089
+ if (opts.all) return callback(null, matching);
13090
+ return callback(null, matching[0].address, matching[0].family);
13091
+ };
13063
13092
  }
13064
13093
  async function readHop(url, options) {
13065
- const pinned = await pinnedAddress(url, options.lookup);
13094
+ const pinned = await pinnedAddresses(url, options.lookup);
13066
13095
  return new Promise((resolve2, reject) => {
13067
13096
  const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(
13068
13097
  url,
13069
13098
  {
13070
13099
  method: "GET",
13071
13100
  headers: { accept: "application/json" },
13072
- lookup: (_hostname, _opts, callback) => callback(null, pinned.address, pinned.family)
13101
+ lookup: pinnedLookup(pinned)
13073
13102
  },
13074
13103
  (response) => {
13104
+ let responseFailure;
13075
13105
  const status = response.statusCode ?? 0;
13076
13106
  if (status >= 300 && status < 400) {
13077
13107
  response.resume();
@@ -13101,10 +13131,16 @@ async function readHop(url, options) {
13101
13131
  const chunks = [];
13102
13132
  response.on("data", (chunk) => {
13103
13133
  size += chunk.length;
13104
- if (size > options.maxBytes)
13105
- response.destroy(new Error("publication download is too large"));
13106
- else chunks.push(chunk);
13134
+ if (size > options.maxBytes) {
13135
+ responseFailure = new Error("publication download is too large");
13136
+ response.destroy(responseFailure);
13137
+ } else chunks.push(chunk);
13107
13138
  });
13139
+ response.on(
13140
+ "aborted",
13141
+ () => reject(responseFailure ?? new Error("publication download was aborted"))
13142
+ );
13143
+ response.on("error", reject);
13108
13144
  response.on("end", () => {
13109
13145
  try {
13110
13146
  resolve2({
@@ -13128,17 +13164,11 @@ async function readHop(url, options) {
13128
13164
  });
13129
13165
  }
13130
13166
  async function fetchPublicWorkflowPackage(value, options = {}) {
13131
- let url;
13132
- try {
13133
- url = new URL(value);
13134
- } catch {
13135
- throw new Error("expected a canonical public snapshot or download URL");
13136
- }
13137
- validateHop(url, true);
13138
- if (!url.pathname.endsWith("/download")) url.pathname += "/download";
13139
- const sourceUrl = url.toString();
13167
+ const parsed = parsePublicSnapshotUrl(value);
13168
+ let url = new URL(parsed.downloadUrl);
13169
+ const sourceUrl = parsed.downloadUrl;
13140
13170
  for (let redirects = 0; ; redirects += 1) {
13141
- validateHop(url, false);
13171
+ validateHop(url);
13142
13172
  const result = await readHop(url, {
13143
13173
  lookup: options.lookup ?? dnsLookup,
13144
13174
  timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
@@ -13170,6 +13200,44 @@ function readPublicationProof(path2) {
13170
13200
  die("invalid workflow publication proof file");
13171
13201
  return saved;
13172
13202
  }
13203
+ function withWorkflowExportSelectors(cmd) {
13204
+ return cmd.option("--project <id-or-name>", "source project for selected project-bound configuration").option("--schedule <id>", "include this source schedule ID (repeatable)", collect2, []).option("--tier <state-ref=tier>", "include a state tier preference (repeatable)", collect2, []).option("--project-routing", "make every --tier selector project scoped").option("--inputs <file>", "JSON object containing input declarations and text uses");
13205
+ }
13206
+ function hasWorkflowExportSelectors(opts) {
13207
+ return Boolean(
13208
+ opts.project || opts.schedule.length || opts.tier.length || opts.projectRouting || opts.inputs
13209
+ );
13210
+ }
13211
+ async function resolveWorkflowExportSelection(api, workflowRef, opts) {
13212
+ const all = await listAll((page) => api.listWorkflows(page));
13213
+ const workflow2 = pickWorkflow(all, workflowRef);
13214
+ const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
13215
+ if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
13216
+ die("--project is required with --schedule or --project-routing");
13217
+ const tiers = [];
13218
+ for (const selector of opts.tier) {
13219
+ const separator = selector.lastIndexOf("=");
13220
+ if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
13221
+ const tier = selector.slice(separator + 1);
13222
+ if (!["smartest", "balanced", "cheapest"].includes(tier))
13223
+ die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
13224
+ tiers.push({
13225
+ state_id: await resolveExportState(api, selector.slice(0, separator)),
13226
+ tier,
13227
+ project_scoped: Boolean(opts.projectRouting)
13228
+ });
13229
+ }
13230
+ const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
13231
+ return {
13232
+ workflow: workflow2,
13233
+ options: {
13234
+ source_project_id: sourceProject?.id,
13235
+ schedule_ids: opts.schedule,
13236
+ tiers,
13237
+ authoring
13238
+ }
13239
+ };
13240
+ }
13173
13241
  async function confirmPublicationAction(question) {
13174
13242
  const rl = createInterface5({ input: process.stdin, output: process.stderr });
13175
13243
  try {
@@ -13519,14 +13587,18 @@ function registerPackageCommands(workflows) {
13519
13587
  if (!result.valid) process.exitCode = 1;
13520
13588
  });
13521
13589
  withCommon(
13522
- workflows.command("publish [workflow]").description("Prepare or commit one exact immutable public workflow snapshot").option(
13523
- "--from <file>",
13524
- "prepare from an existing workflow package instead of an owned workflow"
13525
- ).option("--proof-out <file>", "save the complete prepared proof with mode 0600").option("--proof <file>", "commit a previously saved exact proof").option("--display-name <name>", "public attribution name (never an email)").option("--license <license>", "reuse license (currently MIT)", "MIT").option("--license-year <year>", "MIT copyright year", String((/* @__PURE__ */ new Date()).getFullYear())).option("--confirm <review-digest>", "exact digest of the reviewed proof").option("--sharing-rights", "confirm rights to every bundled declaration and file").option("--recover", "reconcile this same candidate after a lost response").option("--repo <local-id>", "confirm one bundled repository ID (repeatable)", collect2, [])
13590
+ withWorkflowExportSelectors(
13591
+ workflows.command("publish [workflow]").description("Prepare or commit one exact immutable public workflow snapshot").option(
13592
+ "--from <file>",
13593
+ "prepare from an existing workflow package instead of an owned workflow"
13594
+ ).option("--proof-out <file>", "save the complete prepared proof with mode 0600").option("--proof <file>", "commit a previously saved exact proof").option("--display-name <name>", "public attribution name (never an email)").option("--license <license>", "reuse license (currently MIT)", "MIT").option("--license-year <year>", "MIT copyright year", String((/* @__PURE__ */ new Date()).getFullYear())).option("--confirm <review-digest>", "exact digest of the reviewed proof").option("--sharing-rights", "confirm rights to every bundled declaration and file").option("--recover", "reconcile this same candidate after a lost response").option("--repo <local-id>", "confirm one bundled repository ID (repeatable)", collect2, [])
13595
+ )
13526
13596
  ).action(
13527
13597
  async (workflow2, opts) => {
13528
13598
  const apiBase = normalizeUrl(resolveUrl(opts));
13529
13599
  const api = client(opts);
13600
+ if ((opts.from || opts.proof) && hasWorkflowExportSelectors(opts))
13601
+ die("workflow selectors cannot be combined with --from or --proof");
13530
13602
  if (opts.proof) {
13531
13603
  if (workflow2 || opts.from || opts.proofOut || opts.displayName)
13532
13604
  die(
@@ -13540,11 +13612,13 @@ function registerPackageCommands(workflows) {
13540
13612
  die(`confirmation digest does not match reviewed proof ${proof2.review_digest}`);
13541
13613
  if (!process.stdin.isTTY && (opts.confirm !== proof2.review_digest || !opts.sharingRights))
13542
13614
  die(`non-interactive publish requires --confirm ${proof2.review_digest} --sharing-rights`);
13543
- if (process.stdin.isTTY && !opts.confirm) {
13615
+ if (process.stdin.isTTY && opts.confirm !== proof2.review_digest) {
13544
13616
  process.stderr.write(`${JSON.stringify(proof2, null, 2)}
13545
13617
  `);
13546
13618
  if (!await confirmPublicationAction("Publish these exact immutable bytes?"))
13547
13619
  die("publication declined");
13620
+ }
13621
+ if (process.stdin.isTTY && !opts.sharingRights) {
13548
13622
  if (!await confirmPublicationAction("Do you have sharing rights for every bundled item?"))
13549
13623
  die("publication declined");
13550
13624
  }
@@ -13573,11 +13647,16 @@ function registerPackageCommands(workflows) {
13573
13647
  if (opts.license !== "MIT") die("--license currently supports only MIT");
13574
13648
  const year = Number(opts.licenseYear);
13575
13649
  if (!Number.isSafeInteger(year)) die("--license-year must be an integer");
13576
- const source = opts.from ? { kind: "file", document_json: readPackageSource(opts.from) } : {
13577
- kind: "owned_workflow",
13578
- workflow_id: (await resolveWorkflow(api, workflow2)).id,
13579
- options: { schedule_ids: [], tiers: [] }
13580
- };
13650
+ let source;
13651
+ if (opts.from) source = { kind: "file", document_json: readPackageSource(opts.from) };
13652
+ else {
13653
+ const selected = await resolveWorkflowExportSelection(api, workflow2, opts);
13654
+ source = {
13655
+ kind: "owned_workflow",
13656
+ workflow_id: selected.workflow.id,
13657
+ options: selected.options
13658
+ };
13659
+ }
13581
13660
  const proof = await api.preparePublication({
13582
13661
  prepare_request_id: crypto.randomUUID(),
13583
13662
  source,
@@ -13591,22 +13670,26 @@ function registerPackageCommands(workflows) {
13591
13670
  }
13592
13671
  }
13593
13672
  );
13594
- withCommon(
13673
+ withList(
13595
13674
  workflows.command("publications").description("List your public workflow snapshots").option("--workflow <id>", "filter by owned source workflow ID")
13596
13675
  ).action(async (opts) => {
13597
- const result = await client(opts).listPublications(opts.workflow);
13598
- if (opts.json) printJson(result);
13599
- else if (!result.items.length) console.log("no public snapshots");
13600
- else
13676
+ const result = await fetchList(
13677
+ opts,
13678
+ (page) => client(opts).listPublications(opts.workflow, page),
13679
+ (item) => item.candidate_id
13680
+ );
13681
+ printList(result, opts, (items) => {
13682
+ if (!items.length) return console.log("no public snapshots");
13601
13683
  table([
13602
13684
  ["NAME", "STATUS", "PUBLISHED", "SNAPSHOT"],
13603
- ...result.items.map((item) => [
13685
+ ...items.map((item) => [
13604
13686
  item.metadata.display_name,
13605
13687
  item.owner_state === "published" && item.host_state === "active" ? "hosted" : "unavailable",
13606
13688
  new Date(item.published_at).toISOString(),
13607
13689
  item.snapshot_id
13608
13690
  ])
13609
13691
  ]);
13692
+ });
13610
13693
  });
13611
13694
  withCommon(
13612
13695
  workflows.command("unpublish <public-url-or-id>").description("Withdraw a hosted public snapshot").option("-y, --yes", "skip the explicit withdrawal prompt")
@@ -13633,44 +13716,16 @@ function registerPackageCommands(workflows) {
13633
13716
  else console.log(`restored ${result.receipt.public_url}`);
13634
13717
  });
13635
13718
  withCommon(
13636
- workflows.command("export <workflow-id-or-unambiguous-name>").description("Export a canonical workflow package JSON document").option("--project <id-or-name>", "source project for selected project-bound configuration").option("--schedule <id>", "include this source schedule ID (repeatable)", collect2, []).option(
13637
- "--tier <state-ref=tier>",
13638
- "include a state tier preference (repeatable)",
13639
- collect2,
13640
- []
13641
- ).option("--project-routing", "make every --tier selector project scoped").option("--inputs <file>", "JSON object containing input declarations and text uses")
13642
- ).action(
13643
- async (ref, opts) => {
13644
- const api = client(opts);
13645
- const all = await listAll((page) => api.listWorkflows(page));
13646
- const workflow2 = pickWorkflow(all, ref);
13647
- const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
13648
- if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
13649
- die("--project is required with --schedule or --project-routing");
13650
- const tiers = [];
13651
- for (const selector of opts.tier) {
13652
- const separator = selector.lastIndexOf("=");
13653
- if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
13654
- const tier = selector.slice(separator + 1);
13655
- if (!["smartest", "balanced", "cheapest"].includes(tier))
13656
- die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
13657
- tiers.push({
13658
- state_id: await resolveExportState(api, selector.slice(0, separator)),
13659
- tier,
13660
- project_scoped: Boolean(opts.projectRouting)
13661
- });
13662
- }
13663
- const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
13664
- const document = await api.exportWorkflowPackage(workflow2.id, {
13665
- source_project_id: sourceProject?.id,
13666
- schedule_ids: opts.schedule,
13667
- tiers,
13668
- authoring
13669
- });
13670
- process.stdout.write(`${canonicalWorkflowPackage(document)}
13719
+ withWorkflowExportSelectors(
13720
+ workflows.command("export <workflow-id-or-unambiguous-name>").description("Export a canonical workflow package JSON document")
13721
+ )
13722
+ ).action(async (ref, opts) => {
13723
+ const api = client(opts);
13724
+ const selected = await resolveWorkflowExportSelection(api, ref, opts);
13725
+ const document = await api.exportWorkflowPackage(selected.workflow.id, selected.options);
13726
+ process.stdout.write(`${canonicalWorkflowPackage(document)}
13671
13727
  `);
13672
- }
13673
- );
13728
+ });
13674
13729
  withCommon(
13675
13730
  workflows.command("validate <file>").description("Validate a workflow package file (use - for stdin)").option("--public", "apply the stricter text-only public-hosting policy")
13676
13731
  ).action(async (path2, opts) => {
@@ -13739,39 +13794,45 @@ function registerPackageCommands(workflows) {
13739
13794
  );
13740
13795
  const apiBase = normalizeUrl(resolveUrl(opts));
13741
13796
  const api = client(opts);
13742
- const source = publicationSource(path2, apiBase);
13743
- let raw;
13797
+ let loadSource;
13744
13798
  let plan;
13745
13799
  let planPath = opts.plan;
13746
13800
  if (planPath) {
13747
13801
  const saved = readWorkflowPackagePlan(planPath);
13748
13802
  if (normalizeUrl(saved.api_base) !== apiBase)
13749
13803
  die(`plan belongs to ${saved.api_base}, not ${apiBase}`);
13750
- if (saved.remote_source) {
13751
- if (source.kind !== "remote")
13752
- die("saved remote plan must be installed from its public source URL");
13753
- const fetched = await fetchPublicWorkflowPackage(path2);
13754
- if (fetched.sourceUrl !== saved.remote_source.url || workflowPackageBytesSha256(fetched.raw) !== saved.remote_source.bytes_sha256)
13755
- die("public source changed since preview; create and review a fresh plan");
13756
- raw = fetched.raw;
13757
- } else if (saved.plan.source?.kind === "hosted_publication") {
13758
- if (source.kind !== "hosted" || source.snapshotId !== saved.plan.source.snapshot_id)
13759
- die("saved hosted plan belongs to a different public snapshot");
13760
- await api.getPublicSnapshotStatus(source.snapshotId);
13761
- raw = canonicalWorkflowPackage(saved.plan.document);
13762
- } else {
13763
- if (source.kind !== "file") die("saved file plan must be installed from its file");
13764
- raw = readPackageSource(path2);
13765
- }
13766
- const { document } = await packageDocument(raw);
13767
- if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
13768
- die(
13769
- `package digest ${document.digest} does not match saved plan ${saved.document_digest}`
13770
- );
13771
13804
  plan = saved.plan;
13805
+ loadSource = async () => {
13806
+ const source = publicationSource(path2, apiBase);
13807
+ let raw;
13808
+ if (saved.remote_source) {
13809
+ if (source.kind !== "remote")
13810
+ die("saved remote plan must be installed from its public source URL");
13811
+ const fetched = await fetchPublicWorkflowPackage(path2);
13812
+ if (fetched.sourceUrl !== saved.remote_source.url || workflowPackageBytesSha256(fetched.raw) !== saved.remote_source.bytes_sha256)
13813
+ die("public source changed since preview; create and review a fresh plan");
13814
+ raw = fetched.raw;
13815
+ } else if (saved.plan.source?.kind === "hosted_publication") {
13816
+ if (source.kind !== "hosted" || source.snapshotId !== saved.plan.source.snapshot_id)
13817
+ die("saved hosted plan belongs to a different public snapshot");
13818
+ await api.getPublicSnapshotStatus(source.snapshotId);
13819
+ raw = canonicalWorkflowPackage(saved.plan.document);
13820
+ } else {
13821
+ if (source.kind !== "file") die("saved file plan must be installed from its file");
13822
+ raw = readPackageSource(path2);
13823
+ }
13824
+ const { document } = await packageDocument(raw);
13825
+ if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
13826
+ die(
13827
+ `package digest ${document.digest} does not match saved plan ${saved.document_digest}`
13828
+ );
13829
+ return raw;
13830
+ };
13772
13831
  } else {
13832
+ const source = publicationSource(path2, apiBase);
13773
13833
  const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
13774
13834
  let remoteSource;
13835
+ let raw;
13775
13836
  if (source.kind === "hosted") {
13776
13837
  plan = await api.prepareHostedWorkflowPackage(source.snapshotId, choices);
13777
13838
  raw = canonicalWorkflowPackage(plan.document);
@@ -13791,6 +13852,7 @@ function registerPackageCommands(workflows) {
13791
13852
  planPath = source.kind === "file" ? `${path2}.plan.json` : "workflow-publication.plan.json";
13792
13853
  saveWorkflowPackagePlan(planPath, apiBase, plan, remoteSource);
13793
13854
  console.error(`saved retryable signed plan to ${planPath}`);
13855
+ loadSource = async () => raw;
13794
13856
  }
13795
13857
  if (opts.confirm !== void 0 && opts.confirm !== plan.plan_digest)
13796
13858
  die(`confirmation digest does not match reviewed plan ${plan.plan_digest}`);
@@ -13802,7 +13864,7 @@ function registerPackageCommands(workflows) {
13802
13864
  process.stderr.write(`${formatWorkflowPackageReview(plan)}
13803
13865
  `);
13804
13866
  }
13805
- const receipt = await recoverOrInstall(api, raw, plan);
13867
+ const receipt = await recoverOrInstall(api, loadSource, plan);
13806
13868
  if (opts.json) printJson(receipt);
13807
13869
  else {
13808
13870
  console.log(`installed workflow package (${receipt.id})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.243",
3
+ "version": "0.0.244",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",