tines 0.0.243 → 0.0.245

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 +183 -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
  }
@@ -9655,6 +9674,11 @@ async function discoverCodex(daemonVersion) {
9655
9674
  };
9656
9675
  const timer = setTimeout(() => fail("Codex model discovery timed out"), DEADLINE_MS);
9657
9676
  child.on("error", (error) => fail(error.message));
9677
+ child.stdin.on("error", (error) => fail(`Codex app-server stdin: ${error.message}`));
9678
+ child.on(
9679
+ "close",
9680
+ (code, signal) => fail(`Codex app-server exited (${signal ?? `code ${code}`}) before listing models`)
9681
+ );
9658
9682
  child.stdout.on("data", (chunk) => {
9659
9683
  bytes2 += chunk.length;
9660
9684
  if (bytes2 > MAX_STDOUT) return fail("Codex model discovery exceeded 1 MiB");
@@ -12937,7 +12961,7 @@ async function askToInstall(plan) {
12937
12961
  rl.close();
12938
12962
  }
12939
12963
  }
12940
- async function recoverOrInstall(api, raw, plan) {
12964
+ async function recoverOrInstall(api, rawOrLoader, plan) {
12941
12965
  const checked = (receipt) => {
12942
12966
  if (receipt.id !== plan.plan_id || receipt.document_digest !== plan.document_digest || receipt.plan_digest !== plan.plan_digest)
12943
12967
  throw new Error("recovered workflow package receipt does not match the saved plan");
@@ -12948,6 +12972,7 @@ async function recoverOrInstall(api, raw, plan) {
12948
12972
  } catch (error) {
12949
12973
  if (!(error instanceof ApiError) || error.status !== 404) throw error;
12950
12974
  }
12975
+ const raw = typeof rawOrLoader === "function" ? await rawOrLoader() : rawOrLoader;
12951
12976
  const request = {
12952
12977
  document_json: raw,
12953
12978
  plan_token: plan.plan_token,
@@ -12971,7 +12996,6 @@ import { lookup as dnsLookup } from "node:dns/promises";
12971
12996
  import { request as httpRequest } from "node:http";
12972
12997
  import { request as httpsRequest } from "node:https";
12973
12998
  import { isIP } from "node:net";
12974
- var SNAPSHOT_PATH = /^\/p\/[A-Za-z0-9_-]{20,100}(?:\/download)?$/;
12975
12999
  var MAX_REDIRECTS = 3;
12976
13000
  var DEFAULT_TIMEOUT_MS = 3e4;
12977
13001
  function mappedIpv4(address) {
@@ -13038,40 +13062,51 @@ function isPublicAddress(address) {
13038
13062
  }
13039
13063
  return false;
13040
13064
  }
13041
- function validateHop(url, initial) {
13065
+ function validateHop(url) {
13042
13066
  if (!["http:", "https:"].includes(url.protocol))
13043
13067
  throw new Error("publication URL must use HTTPS");
13044
13068
  if (url.username || url.password) throw new Error("publication URL must not contain credentials");
13045
13069
  if (url.hash) throw new Error("publication URL must not contain a fragment");
13046
13070
  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
13071
  }
13050
- async function pinnedAddress(url, lookup) {
13072
+ async function pinnedAddresses(url, lookup) {
13051
13073
  const hostname4 = url.hostname.replace(/^\[|\]$/g, "");
13052
13074
  const literalFamily = isIP(hostname4);
13053
13075
  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)));
13076
+ if (!addresses.length || addresses.some(({ address, family }) => isIP(address) !== family || ![4, 6].includes(family)))
13077
+ throw new Error(`publication host ${url.hostname} did not resolve to valid addresses`);
13078
+ const literalLoopback = Boolean(literalFamily && isLoopback(hostname4));
13079
+ const loopbackDevelopment = url.protocol === "http:" && (url.hostname === "localhost" || literalLoopback) && addresses.every(({ address }) => isLoopback(address));
13056
13080
  if (url.protocol === "http:" && !loopbackDevelopment)
13057
13081
  throw new Error(
13058
13082
  "publication URL must use HTTPS (HTTP is allowed only for loopback development)"
13059
13083
  );
13060
13084
  if (!loopbackDevelopment && addresses.some(({ address }) => !isPublicAddress(address)))
13061
13085
  throw new Error("publication URL resolves to a private or reserved address");
13062
- return addresses[0];
13086
+ return addresses;
13087
+ }
13088
+ function pinnedLookup(addresses) {
13089
+ return (_hostname, opts, callback) => {
13090
+ const family = typeof opts.family === "number" ? opts.family : 0;
13091
+ const matching = family === 4 || family === 6 ? addresses.filter((item) => item.family === family) : addresses;
13092
+ if (!matching.length)
13093
+ return callback(new Error(`publication host has no IPv${family} address`));
13094
+ if (opts.all) return callback(null, matching);
13095
+ return callback(null, matching[0].address, matching[0].family);
13096
+ };
13063
13097
  }
13064
13098
  async function readHop(url, options) {
13065
- const pinned = await pinnedAddress(url, options.lookup);
13099
+ const pinned = await pinnedAddresses(url, options.lookup);
13066
13100
  return new Promise((resolve2, reject) => {
13067
13101
  const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(
13068
13102
  url,
13069
13103
  {
13070
13104
  method: "GET",
13071
13105
  headers: { accept: "application/json" },
13072
- lookup: (_hostname, _opts, callback) => callback(null, pinned.address, pinned.family)
13106
+ lookup: pinnedLookup(pinned)
13073
13107
  },
13074
13108
  (response) => {
13109
+ let responseFailure;
13075
13110
  const status = response.statusCode ?? 0;
13076
13111
  if (status >= 300 && status < 400) {
13077
13112
  response.resume();
@@ -13101,10 +13136,16 @@ async function readHop(url, options) {
13101
13136
  const chunks = [];
13102
13137
  response.on("data", (chunk) => {
13103
13138
  size += chunk.length;
13104
- if (size > options.maxBytes)
13105
- response.destroy(new Error("publication download is too large"));
13106
- else chunks.push(chunk);
13139
+ if (size > options.maxBytes) {
13140
+ responseFailure = new Error("publication download is too large");
13141
+ response.destroy(responseFailure);
13142
+ } else chunks.push(chunk);
13107
13143
  });
13144
+ response.on(
13145
+ "aborted",
13146
+ () => reject(responseFailure ?? new Error("publication download was aborted"))
13147
+ );
13148
+ response.on("error", reject);
13108
13149
  response.on("end", () => {
13109
13150
  try {
13110
13151
  resolve2({
@@ -13128,17 +13169,11 @@ async function readHop(url, options) {
13128
13169
  });
13129
13170
  }
13130
13171
  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();
13172
+ const parsed = parsePublicSnapshotUrl(value);
13173
+ let url = new URL(parsed.downloadUrl);
13174
+ const sourceUrl = parsed.downloadUrl;
13140
13175
  for (let redirects = 0; ; redirects += 1) {
13141
- validateHop(url, false);
13176
+ validateHop(url);
13142
13177
  const result = await readHop(url, {
13143
13178
  lookup: options.lookup ?? dnsLookup,
13144
13179
  timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
@@ -13170,6 +13205,44 @@ function readPublicationProof(path2) {
13170
13205
  die("invalid workflow publication proof file");
13171
13206
  return saved;
13172
13207
  }
13208
+ function withWorkflowExportSelectors(cmd) {
13209
+ 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");
13210
+ }
13211
+ function hasWorkflowExportSelectors(opts) {
13212
+ return Boolean(
13213
+ opts.project || opts.schedule.length || opts.tier.length || opts.projectRouting || opts.inputs
13214
+ );
13215
+ }
13216
+ async function resolveWorkflowExportSelection(api, workflowRef, opts) {
13217
+ const all = await listAll((page) => api.listWorkflows(page));
13218
+ const workflow2 = pickWorkflow(all, workflowRef);
13219
+ const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
13220
+ if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
13221
+ die("--project is required with --schedule or --project-routing");
13222
+ const tiers = [];
13223
+ for (const selector of opts.tier) {
13224
+ const separator = selector.lastIndexOf("=");
13225
+ if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
13226
+ const tier = selector.slice(separator + 1);
13227
+ if (!["smartest", "balanced", "cheapest"].includes(tier))
13228
+ die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
13229
+ tiers.push({
13230
+ state_id: await resolveExportState(api, selector.slice(0, separator)),
13231
+ tier,
13232
+ project_scoped: Boolean(opts.projectRouting)
13233
+ });
13234
+ }
13235
+ const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
13236
+ return {
13237
+ workflow: workflow2,
13238
+ options: {
13239
+ source_project_id: sourceProject?.id,
13240
+ schedule_ids: opts.schedule,
13241
+ tiers,
13242
+ authoring
13243
+ }
13244
+ };
13245
+ }
13173
13246
  async function confirmPublicationAction(question) {
13174
13247
  const rl = createInterface5({ input: process.stdin, output: process.stderr });
13175
13248
  try {
@@ -13519,14 +13592,18 @@ function registerPackageCommands(workflows) {
13519
13592
  if (!result.valid) process.exitCode = 1;
13520
13593
  });
13521
13594
  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, [])
13595
+ withWorkflowExportSelectors(
13596
+ workflows.command("publish [workflow]").description("Prepare or commit one exact immutable public workflow snapshot").option(
13597
+ "--from <file>",
13598
+ "prepare from an existing workflow package instead of an owned workflow"
13599
+ ).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, [])
13600
+ )
13526
13601
  ).action(
13527
13602
  async (workflow2, opts) => {
13528
13603
  const apiBase = normalizeUrl(resolveUrl(opts));
13529
13604
  const api = client(opts);
13605
+ if ((opts.from || opts.proof) && hasWorkflowExportSelectors(opts))
13606
+ die("workflow selectors cannot be combined with --from or --proof");
13530
13607
  if (opts.proof) {
13531
13608
  if (workflow2 || opts.from || opts.proofOut || opts.displayName)
13532
13609
  die(
@@ -13540,11 +13617,13 @@ function registerPackageCommands(workflows) {
13540
13617
  die(`confirmation digest does not match reviewed proof ${proof2.review_digest}`);
13541
13618
  if (!process.stdin.isTTY && (opts.confirm !== proof2.review_digest || !opts.sharingRights))
13542
13619
  die(`non-interactive publish requires --confirm ${proof2.review_digest} --sharing-rights`);
13543
- if (process.stdin.isTTY && !opts.confirm) {
13620
+ if (process.stdin.isTTY && opts.confirm !== proof2.review_digest) {
13544
13621
  process.stderr.write(`${JSON.stringify(proof2, null, 2)}
13545
13622
  `);
13546
13623
  if (!await confirmPublicationAction("Publish these exact immutable bytes?"))
13547
13624
  die("publication declined");
13625
+ }
13626
+ if (process.stdin.isTTY && !opts.sharingRights) {
13548
13627
  if (!await confirmPublicationAction("Do you have sharing rights for every bundled item?"))
13549
13628
  die("publication declined");
13550
13629
  }
@@ -13573,11 +13652,16 @@ function registerPackageCommands(workflows) {
13573
13652
  if (opts.license !== "MIT") die("--license currently supports only MIT");
13574
13653
  const year = Number(opts.licenseYear);
13575
13654
  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
- };
13655
+ let source;
13656
+ if (opts.from) source = { kind: "file", document_json: readPackageSource(opts.from) };
13657
+ else {
13658
+ const selected = await resolveWorkflowExportSelection(api, workflow2, opts);
13659
+ source = {
13660
+ kind: "owned_workflow",
13661
+ workflow_id: selected.workflow.id,
13662
+ options: selected.options
13663
+ };
13664
+ }
13581
13665
  const proof = await api.preparePublication({
13582
13666
  prepare_request_id: crypto.randomUUID(),
13583
13667
  source,
@@ -13591,22 +13675,26 @@ function registerPackageCommands(workflows) {
13591
13675
  }
13592
13676
  }
13593
13677
  );
13594
- withCommon(
13678
+ withList(
13595
13679
  workflows.command("publications").description("List your public workflow snapshots").option("--workflow <id>", "filter by owned source workflow ID")
13596
13680
  ).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
13681
+ const result = await fetchList(
13682
+ opts,
13683
+ (page) => client(opts).listPublications(opts.workflow, page),
13684
+ (item) => item.candidate_id
13685
+ );
13686
+ printList(result, opts, (items) => {
13687
+ if (!items.length) return console.log("no public snapshots");
13601
13688
  table([
13602
13689
  ["NAME", "STATUS", "PUBLISHED", "SNAPSHOT"],
13603
- ...result.items.map((item) => [
13690
+ ...items.map((item) => [
13604
13691
  item.metadata.display_name,
13605
13692
  item.owner_state === "published" && item.host_state === "active" ? "hosted" : "unavailable",
13606
13693
  new Date(item.published_at).toISOString(),
13607
13694
  item.snapshot_id
13608
13695
  ])
13609
13696
  ]);
13697
+ });
13610
13698
  });
13611
13699
  withCommon(
13612
13700
  workflows.command("unpublish <public-url-or-id>").description("Withdraw a hosted public snapshot").option("-y, --yes", "skip the explicit withdrawal prompt")
@@ -13633,44 +13721,16 @@ function registerPackageCommands(workflows) {
13633
13721
  else console.log(`restored ${result.receipt.public_url}`);
13634
13722
  });
13635
13723
  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)}
13724
+ withWorkflowExportSelectors(
13725
+ workflows.command("export <workflow-id-or-unambiguous-name>").description("Export a canonical workflow package JSON document")
13726
+ )
13727
+ ).action(async (ref, opts) => {
13728
+ const api = client(opts);
13729
+ const selected = await resolveWorkflowExportSelection(api, ref, opts);
13730
+ const document = await api.exportWorkflowPackage(selected.workflow.id, selected.options);
13731
+ process.stdout.write(`${canonicalWorkflowPackage(document)}
13671
13732
  `);
13672
- }
13673
- );
13733
+ });
13674
13734
  withCommon(
13675
13735
  workflows.command("validate <file>").description("Validate a workflow package file (use - for stdin)").option("--public", "apply the stricter text-only public-hosting policy")
13676
13736
  ).action(async (path2, opts) => {
@@ -13739,39 +13799,45 @@ function registerPackageCommands(workflows) {
13739
13799
  );
13740
13800
  const apiBase = normalizeUrl(resolveUrl(opts));
13741
13801
  const api = client(opts);
13742
- const source = publicationSource(path2, apiBase);
13743
- let raw;
13802
+ let loadSource;
13744
13803
  let plan;
13745
13804
  let planPath = opts.plan;
13746
13805
  if (planPath) {
13747
13806
  const saved = readWorkflowPackagePlan(planPath);
13748
13807
  if (normalizeUrl(saved.api_base) !== apiBase)
13749
13808
  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
13809
  plan = saved.plan;
13810
+ loadSource = async () => {
13811
+ const source = publicationSource(path2, apiBase);
13812
+ let raw;
13813
+ if (saved.remote_source) {
13814
+ if (source.kind !== "remote")
13815
+ die("saved remote plan must be installed from its public source URL");
13816
+ const fetched = await fetchPublicWorkflowPackage(path2);
13817
+ if (fetched.sourceUrl !== saved.remote_source.url || workflowPackageBytesSha256(fetched.raw) !== saved.remote_source.bytes_sha256)
13818
+ die("public source changed since preview; create and review a fresh plan");
13819
+ raw = fetched.raw;
13820
+ } else if (saved.plan.source?.kind === "hosted_publication") {
13821
+ if (source.kind !== "hosted" || source.snapshotId !== saved.plan.source.snapshot_id)
13822
+ die("saved hosted plan belongs to a different public snapshot");
13823
+ await api.getPublicSnapshotStatus(source.snapshotId);
13824
+ raw = canonicalWorkflowPackage(saved.plan.document);
13825
+ } else {
13826
+ if (source.kind !== "file") die("saved file plan must be installed from its file");
13827
+ raw = readPackageSource(path2);
13828
+ }
13829
+ const { document } = await packageDocument(raw);
13830
+ if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
13831
+ die(
13832
+ `package digest ${document.digest} does not match saved plan ${saved.document_digest}`
13833
+ );
13834
+ return raw;
13835
+ };
13772
13836
  } else {
13837
+ const source = publicationSource(path2, apiBase);
13773
13838
  const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
13774
13839
  let remoteSource;
13840
+ let raw;
13775
13841
  if (source.kind === "hosted") {
13776
13842
  plan = await api.prepareHostedWorkflowPackage(source.snapshotId, choices);
13777
13843
  raw = canonicalWorkflowPackage(plan.document);
@@ -13791,6 +13857,7 @@ function registerPackageCommands(workflows) {
13791
13857
  planPath = source.kind === "file" ? `${path2}.plan.json` : "workflow-publication.plan.json";
13792
13858
  saveWorkflowPackagePlan(planPath, apiBase, plan, remoteSource);
13793
13859
  console.error(`saved retryable signed plan to ${planPath}`);
13860
+ loadSource = async () => raw;
13794
13861
  }
13795
13862
  if (opts.confirm !== void 0 && opts.confirm !== plan.plan_digest)
13796
13863
  die(`confirmation digest does not match reviewed plan ${plan.plan_digest}`);
@@ -13802,7 +13869,7 @@ function registerPackageCommands(workflows) {
13802
13869
  process.stderr.write(`${formatWorkflowPackageReview(plan)}
13803
13870
  `);
13804
13871
  }
13805
- const receipt = await recoverOrInstall(api, raw, plan);
13872
+ const receipt = await recoverOrInstall(api, loadSource, plan);
13806
13873
  if (opts.json) printJson(receipt);
13807
13874
  else {
13808
13875
  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.245",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",