@eigenpal/cli 0.12.2 → 0.13.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @eigenpal/cli
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - aaf5a04: `eigenpal workflow versions` can now create, promote, and restore YAML workflow versions through the public API. Tag a candidate from a YAML file or an existing snapshot with an explicit semver, keep it off live traffic with `--no-activate`, then promote that exact version when it qualifies. Restoring still rolls a workflow back by copying a snapshot into a new current version — lists now show that untagged HEAD clearly instead of hiding it.
8
+
9
+ `workflow versions list --limit` and `--offset` page that full public list locally. `--json` prints the sliced `{ data, total, limit, offset }` envelope (`total` is the unsliced count), not the raw server payload.
10
+
3
11
  ## 0.11.0
4
12
 
5
13
  ### Minor Changes
package/dist/cli.js CHANGED
@@ -67452,10 +67452,16 @@ var init_workflow_tool_metadata = __esm(() => {
67452
67452
  description: "Download eval results in CSV or JSON."
67453
67453
  },
67454
67454
  workflow_list_versions: {
67455
- description: "List historical workflow versions, newest first."
67455
+ description: "List tagged workflow versions plus the current untagged snapshot when HEAD is untagged, newest first."
67456
+ },
67457
+ workflow_create_version: {
67458
+ description: "Create a tagged workflow version from YAML or by copying an existing snapshot."
67459
+ },
67460
+ workflow_promote_version: {
67461
+ description: "Make an existing tagged workflow version current without creating another snapshot."
67456
67462
  },
67457
67463
  workflow_restore_version: {
67458
- description: "Restore the workflow to a previous version."
67464
+ description: "Restore a previous snapshot as a new untagged current version. Does not retag the source."
67459
67465
  },
67460
67466
  workflow_list_step_types: {
67461
67467
  description: "List every step type the deployment supports."
@@ -191393,7 +191399,7 @@ import { fileURLToPath as fileURLToPath9 } from "url";
191393
191399
  // package.json
191394
191400
  var package_default = {
191395
191401
  name: "@eigenpal/cli",
191396
- version: "0.12.2",
191402
+ version: "0.13.0",
191397
191403
  description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
191398
191404
  keywords: [
191399
191405
  "eigenpal",
@@ -201016,6 +201022,45 @@ version: ${next}`);
201016
201022
  return `version: ${next}
201017
201023
  ${yaml}`;
201018
201024
  }
201025
+ function automationVersionsPath(workflowId, suffix = "") {
201026
+ return `/v1/automations/${encodeURIComponent(workflowId)}/versions${suffix}`;
201027
+ }
201028
+ function formatWorkflowVersionLabel(version2) {
201029
+ return typeof version2 === "string" && version2.length > 0 ? version2 : "untagged";
201030
+ }
201031
+ function paginatePublicVersionList(raw, opts) {
201032
+ const payload = raw;
201033
+ const all = Array.isArray(payload.data) ? payload.data : [];
201034
+ const offset = Number.isFinite(opts.offset) ? Math.max(0, Math.trunc(opts.offset)) : 0;
201035
+ const limit = Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : 0;
201036
+ return {
201037
+ data: all.slice(offset, offset + limit),
201038
+ total: all.length,
201039
+ limit,
201040
+ offset
201041
+ };
201042
+ }
201043
+ function resolveWorkflowVersionCreateRequest(opts) {
201044
+ const hasFile2 = Boolean(opts.file);
201045
+ const hasFrom = Boolean(opts.from);
201046
+ if (hasFile2 && hasFrom) {
201047
+ throw new Error("Pass either --file or --from, not both.");
201048
+ }
201049
+ if (!hasFile2 && !hasFrom) {
201050
+ throw new Error("Pass --file <yaml> or --from <version-id> to create a tagged version.");
201051
+ }
201052
+ if (opts.setVersion && opts.yamlVersion) {
201053
+ throw new Error(`--set-version conflicts with the top-level \`version: ${opts.yamlVersion}\` in the YAML. Remove the YAML field, or omit the flag.`);
201054
+ }
201055
+ const version2 = opts.setVersion ?? (hasFile2 ? opts.yamlVersion ?? undefined : undefined);
201056
+ if (!version2) {
201057
+ throw new Error(hasFrom ? "Pass --set-version <X.Y.Z> when copying an existing version." : "Pass --set-version <X.Y.Z>, or add a top-level `version: X.Y.Z` to the YAML.");
201058
+ }
201059
+ if (!SEMVER_RE.test(version2)) {
201060
+ throw new Error(`${opts.setVersion ? "--set-version" : "YAML version"} must be bare semver X.Y.Z (got "${version2}").`);
201061
+ }
201062
+ return { source: hasFile2 ? "yaml" : "history", version: version2 };
201063
+ }
201019
201064
  function registerClearLocalCommand(parent) {
201020
201065
  parent.command("clear-local [examples...]").description("Delete local execution artifacts under ./dataset/examples/. Keeps the latest run per example by default.").option("--dir <dir>", "Local eigenpal directory", undefined).option("--all", "Remove all artifacts, including the latest kept by default", false).action(async (examples, opts) => {
201021
201066
  const config2 = resolveConfig(opts);
@@ -201791,31 +201836,131 @@ async function pollExperimentUntilTerminal({
201791
201836
  }
201792
201837
  }
201793
201838
  function registerVersionsCommands(parent) {
201794
- const versions3 = parent.command("versions").description("Inspect and restore historical workflow versions.");
201839
+ const versions3 = parent.command("versions").description("Create, list, promote, and restore YAML workflow versions.").addHelpText("after", `
201840
+ Examples:
201841
+ $ eigenpal workflow versions list wf_abc123
201842
+ $ eigenpal workflow versions create wf_abc123 --file workflow.yaml --set-version 1.4.0
201843
+ $ eigenpal workflow versions create wf_abc123 --file workflow.yaml --set-version 1.4.1 --no-activate
201844
+ $ eigenpal workflow versions create wf_abc123 --from wh_abc --set-version 1.5.0 --no-activate
201845
+ $ eigenpal workflow versions promote wf_abc123 wh_abc
201846
+ $ eigenpal workflow versions restore wf_abc123 wh_old
201847
+
201848
+ Create publishes a tagged candidate. It becomes current unless you pass
201849
+ \`--no-activate\`. Promote makes an existing tagged candidate current without
201850
+ creating another snapshot. Restore copies a snapshot into a new untagged
201851
+ current HEAD; the source tag is left unchanged and the new row cannot be
201852
+ promoted until you create a tagged copy.
201853
+ `);
201795
201854
  const listCmd = versions3.command("list <workflow-id>").description(descriptionFor("workflow_list_versions"));
201796
- addJsonFlag(withBaseUrl(withPagination(listCmd))).action(action(async (workflow2, opts) => {
201855
+ withBaseUrl(withPagination(listCmd)).option("--json", "Print the sliced { data, total, limit, offset } envelope as JSON (not the raw server payload)").addHelpText("after", `
201856
+ Examples:
201857
+ $ eigenpal workflow versions list wf_abc123
201858
+ $ eigenpal workflow versions list wf_abc123 --json | jq '.data[] | {id, version, isCurrent}'
201859
+ $ eigenpal workflow versions list wf_abc123 --limit 1 --offset 0 --json
201860
+
201861
+ The public list includes tagged releases and, after restore, the untagged
201862
+ current snapshot (\`version: null\`, rendered as \`untagged\`). The public
201863
+ endpoint returns the full set; \`--limit\` / \`--offset\` slice it
201864
+ client-side so HEAD is never dropped unless it is off the requested page.
201865
+ \`--json\` prints \`{ data, total, limit, offset }\` after that slice —
201866
+ \`total\` is the unsliced count.
201867
+ `).action(action(async (workflow2, opts) => {
201797
201868
  const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
201798
- const raw = await client.get(`/api/workflows/${workflowId}/versions`, {
201799
- limit: String(opts.limit),
201800
- offset: String(opts.offset)
201869
+ const raw = await client.get(automationVersionsPath(workflowId));
201870
+ const paged = paginatePublicVersionList(raw, {
201871
+ limit: opts.limit,
201872
+ offset: opts.offset
201801
201873
  });
201802
- renderListResult(raw, [
201803
- { key: "version", header: "version" },
201874
+ renderListResult(paged, [
201875
+ {
201876
+ key: "version",
201877
+ header: "version",
201878
+ format: (value) => formatWorkflowVersionLabel(typeof value === "string" ? value : null)
201879
+ },
201804
201880
  { key: "id", header: "versionId" },
201805
201881
  { key: "isCurrent", header: "current", format: (v11) => v11 ? "yes" : "-" },
201806
201882
  { key: "createdAt", header: "createdAt", format: formatTimestamp }
201807
201883
  ], { ...opts, entityLabel: "version" });
201808
201884
  }));
201809
- const restoreCmd = versions3.command("restore <workflow-id> <versionId>").description(descriptionFor("workflow_restore_version"));
201810
- addJsonFlag(withBaseUrl(restoreCmd)).action(action(async (workflow2, versionId, opts) => {
201885
+ const createCmd = versions3.command("create <workflow-id>").description(descriptionFor("workflow_create_version")).option("--file <yaml>", "Path to workflow YAML. Mutually exclusive with --from.").option("--from <version-id>", "Existing version id to copy into a new tagged snapshot. Leaves the source tag unchanged. Mutually exclusive with --file.").option("--set-version <semver>", "Bare semver tag such as 1.4.0. Required when copying with --from. For --file, omit this flag if the YAML already has a top-level version: field. (Named --set-version to avoid the global -v, --version flag.)").option("--no-activate", "Keep the tagged version off live traffic until you promote it. Default is to make it current. Requires an existing current version.").addHelpText("after", `
201886
+ Examples:
201887
+ $ eigenpal workflow versions create wf_abc123 --file workflow.yaml --set-version 1.4.0
201888
+ $ eigenpal workflow versions create wf_abc123 --file workflow.yaml --set-version 1.4.1 --no-activate
201889
+ $ eigenpal workflow versions create wf_abc123 --from wh_abc --set-version 1.5.0 --no-activate
201890
+ $ eigenpal workflow versions create wf_abc123 --file workflow.yaml --set-version 1.4.0 --json | jq '.id'
201891
+
201892
+ Pass exactly one of \`--file\` or \`--from\`. \`--set-version\` is the tag on
201893
+ the new row; copying never retags the source. Default is to activate the new
201894
+ version (same as the public API). \`--no-activate\` keeps a detached candidate
201895
+ off live traffic until \`workflow versions promote\`.
201896
+ `);
201897
+ addJsonFlag(withBaseUrl(createCmd)).action(action(async (workflow2, opts) => {
201898
+ let yaml;
201899
+ if (opts.file) {
201900
+ yaml = await fs17.readFile(opts.file, "utf8");
201901
+ }
201902
+ const resolved = resolveWorkflowVersionCreateRequest({
201903
+ file: opts.file,
201904
+ from: opts.from,
201905
+ setVersion: opts.setVersion,
201906
+ yamlVersion: yaml ? extractWorkflowVersion(yaml) : null
201907
+ });
201908
+ const activate = opts.activate !== false;
201909
+ const body = resolved.source === "yaml" ? { yaml, version: resolved.version, activate } : { historyId: opts.from, version: resolved.version, activate };
201910
+ const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
201911
+ const result = await client.post(automationVersionsPath(workflowId), body);
201912
+ if (opts.json) {
201913
+ printJson2(result);
201914
+ return;
201915
+ }
201916
+ const id2 = typeof result.id === "string" ? result.id : "?";
201917
+ if (activate) {
201918
+ success2(`Created v${resolved.version} as current for ${ui.bold(workflowId)} ${ui.dim(`(${id2})`)}`);
201919
+ } else {
201920
+ success2(`Created v${resolved.version} as a detached candidate for ${ui.bold(workflowId)} ${ui.dim(`(${id2})`)}`);
201921
+ dim(`Promote it with: eigenpal workflow versions promote ${workflowId} ${id2}`);
201922
+ }
201923
+ }));
201924
+ const promoteCmd = versions3.command("promote <workflow-id> <versionId>").description(descriptionFor("workflow_promote_version"));
201925
+ addJsonFlag(withBaseUrl(promoteCmd)).addHelpText("after", `
201926
+ Examples:
201927
+ $ eigenpal workflow versions promote wf_abc123 wh_abc
201928
+ $ eigenpal workflow versions promote wf_abc123 wh_abc --json | jq '{id, version, isCurrent}'
201929
+
201930
+ Only tagged version rows can be promoted. Untagged snapshots (including
201931
+ restore HEAD) return 404 — create a tagged copy first.
201932
+ `).action(action(async (workflow2, versionId, opts) => {
201933
+ const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
201934
+ const result = await client.post(automationVersionsPath(workflowId, `/${encodeURIComponent(versionId)}/promote`));
201935
+ if (opts.json) {
201936
+ printJson2(result);
201937
+ return;
201938
+ }
201939
+ const label2 = formatWorkflowVersionLabel(result.version);
201940
+ const id2 = typeof result.id === "string" ? result.id : versionId;
201941
+ success2(`Promoted ${label2 === "untagged" ? "untagged version" : `v${label2}`} to current for ${ui.bold(workflowId)} ${ui.dim(`(${id2})`)}`);
201942
+ }));
201943
+ const restoreCmd = versions3.command("restore <workflow-id> <versionId>").description(descriptionFor("workflow_restore_version")).option("--message <text>", "Optional restore note stored on the new snapshot");
201944
+ addJsonFlag(withBaseUrl(restoreCmd)).addHelpText("after", `
201945
+ Examples:
201946
+ $ eigenpal workflow versions restore wf_abc123 wh_old
201947
+ $ eigenpal workflow versions restore wf_abc123 wh_old --message "roll back Friday change"
201948
+ $ eigenpal workflow versions restore wf_abc123 wh_old --json | jq '{id, version, isCurrent}'
201949
+
201950
+ Restore copies the selected snapshot into a new untagged current version. It
201951
+ does not retag or delete the source. The new HEAD appears in
201952
+ \`workflow versions list\` as \`untagged\` / \`version: null\` and cannot be
201953
+ promoted until you create a tagged copy.
201954
+ `).action(action(async (workflow2, versionId, opts) => {
201811
201955
  const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
201812
- const result = await client.post(`/api/workflows/${workflowId}/history/${versionId}/restore`, {});
201956
+ const body = opts.message ? { message: opts.message } : {};
201957
+ const result = await client.post(automationVersionsPath(workflowId, `/${encodeURIComponent(versionId)}/restore`), body);
201813
201958
  if (opts.json) {
201814
201959
  printJson2(result);
201815
201960
  return;
201816
201961
  }
201817
- const newVersion = result.version ?? "?";
201818
- success2(`Restored ${ui.bold(workflowId)} to v${newVersion} ${ui.dim(`(history ${versionId})`)}`);
201962
+ const id2 = typeof result.id === "string" ? result.id : "?";
201963
+ success2(`Restored ${ui.bold(workflowId)} to a new untagged current version ${ui.dim(`(${id2})`)} from ${versionId}`);
201819
201964
  }));
201820
201965
  }
201821
201966
  function registerStepTypeCommands(parent) {
@@ -80,6 +80,13 @@ eigenpal workflow evaluators push <workflow-id> --file ./evaluators.yaml
80
80
  eigenpal workflow experiment run <workflow-id>
81
81
  eigenpal workflow experiment watch <workflow-id> <batch-id>
82
82
  eigenpal workflow experiment results <workflow-id> <batch-id> --format csv --out results.csv
83
+
84
+ # Create a tagged candidate without moving live traffic, then promote it.
85
+ eigenpal workflow versions create <workflow-id> --file workflow.yaml --set-version 1.4.0 --no-activate
86
+ eigenpal workflow versions list <workflow-id>
87
+ eigenpal workflow versions promote <workflow-id> <version-id>
88
+ # Restore copies a snapshot into a new untagged current HEAD.
89
+ eigenpal workflow versions restore <workflow-id> <version-id>
83
90
  ```
84
91
 
85
92
  Folders are server metadata, not `workflow.yaml` content:
@@ -42,6 +42,8 @@ eigenpal
42
42
  │ │ └── watch <workflow-id> <batchId>
43
43
  │ ├── versions
44
44
  │ │ ├── list|ls <workflow-id>
45
+ │ │ ├── create <workflow-id>
46
+ │ │ ├── promote <workflow-id> <versionId>
45
47
  │ │ └── restore <workflow-id> <versionId>
46
48
  │ ├── step-type
47
49
  │ │ ├── list|ls
@@ -41,6 +41,8 @@ Manage workflows: push, pull, and evaluate.
41
41
  - [`eigenpal workflow experiment|exp compare|diff [options] <batchIdA> <batchIdB>`](#eigenpal-workflow-experimentexp-comparediff-options-batchida-batchidb)
42
42
  - [`eigenpal workflow experiment|exp watch [options] <workflow-id> <batchId>`](#eigenpal-workflow-experimentexp-watch-options-workflow-id-batchid)
43
43
  - [`eigenpal workflow versions list|ls [options] <workflow-id>`](#eigenpal-workflow-versions-listls-options-workflow-id)
44
+ - [`eigenpal workflow versions create [options] <workflow-id>`](#eigenpal-workflow-versions-create-options-workflow-id)
45
+ - [`eigenpal workflow versions promote [options] <workflow-id> <versionId>`](#eigenpal-workflow-versions-promote-options-workflow-id-versionid)
44
46
  - [`eigenpal workflow versions restore [options] <workflow-id> <versionId>`](#eigenpal-workflow-versions-restore-options-workflow-id-versionid)
45
47
  - [`eigenpal workflow step-type list|ls [options]`](#eigenpal-workflow-step-type-listls-options)
46
48
  - [`eigenpal workflow step-type get [options] <type>`](#eigenpal-workflow-step-type-get-options-type)
@@ -81,6 +83,8 @@ workflow
81
83
  │ └── watch <workflow-id> <batchId>
82
84
  ├── versions
83
85
  │ ├── list|ls <workflow-id>
86
+ │ ├── create <workflow-id>
87
+ │ ├── promote <workflow-id> <versionId>
84
88
  │ └── restore <workflow-id> <versionId>
85
89
  ├── step-type
86
90
  │ ├── list|ls
@@ -143,10 +147,12 @@ workflow
143
147
 
144
148
  ### Versions
145
149
 
146
- | Command | Description |
147
- | ------------------------------------------------------------------------ | ------------------------------------------------ |
148
- | `eigenpal workflow versions list\|ls [options] <workflow-id>` | List historical workflow versions, newest first. |
149
- | `eigenpal workflow versions restore [options] <workflow-id> <versionId>` | Restore the workflow to a previous version. |
150
+ | Command | Description |
151
+ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
152
+ | `eigenpal workflow versions list\|ls [options] <workflow-id>` | List tagged workflow versions plus the current untagged snapshot when HEAD is untagged, newest first. |
153
+ | `eigenpal workflow versions create [options] <workflow-id>` | Create a tagged workflow version from YAML or by copying an existing snapshot. |
154
+ | `eigenpal workflow versions promote [options] <workflow-id> <versionId>` | Make an existing tagged workflow version current without creating another snapshot. |
155
+ | `eigenpal workflow versions restore [options] <workflow-id> <versionId>` | Restore a previous snapshot as a new untagged current version. Does not retag the source. |
150
156
 
151
157
  ### Step-type
152
158
 
@@ -629,7 +635,7 @@ Poll until terminal, then auto-pull results — replaces `status --watch` + `res
629
635
 
630
636
  ### `eigenpal workflow versions list|ls [options] <workflow-id>`
631
637
 
632
- List historical workflow versions, newest first.
638
+ List tagged workflow versions plus the current untagged snapshot when HEAD is untagged, newest first.
633
639
 
634
640
  ### Arguments
635
641
 
@@ -639,16 +645,55 @@ List historical workflow versions, newest first.
639
645
 
640
646
  ### Options
641
647
 
648
+ | Flag | Required | Default | Description |
649
+ | ------------------ | -------- | ------- | --------------------------------------------------------------------------------------------- |
650
+ | `--limit <n>` | no | `50` | Page size |
651
+ | `--offset <n>` | no | `0` | Page offset |
652
+ | `--base-url <url>` | no | | Server base URL |
653
+ | `--json` | no | | Print the sliced { data, total, limit, offset } envelope as JSON (not the raw server payload) |
654
+
655
+ ### `eigenpal workflow versions create [options] <workflow-id>`
656
+
657
+ Create a tagged workflow version from YAML or by copying an existing snapshot.
658
+
659
+ ### Arguments
660
+
661
+ | Name | Required | Variadic | Description |
662
+ | ------------- | -------- | -------- | ----------- |
663
+ | `workflow-id` | yes | no | |
664
+
665
+ ### Options
666
+
667
+ | Flag | Required | Default | Description |
668
+ | ------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
669
+ | `--file <yaml>` | no | | Path to workflow YAML. Mutually exclusive with --from. |
670
+ | `--from <version-id>` | no | | Existing version id to copy into a new tagged snapshot. Leaves the source tag unchanged. Mutually exclusive with --file. |
671
+ | `--set-version <semver>` | no | | Bare semver tag such as 1.4.0. Required when copying with --from. For --file, omit this flag if the YAML already has a top-level version: field. (Named --set-version to avoid the global -v, --version flag.) |
672
+ | `--no-activate` | no | | Keep the tagged version off live traffic until you promote it. Default is to make it current. Requires an existing current version. |
673
+ | `--base-url <url>` | no | | Server base URL |
674
+ | `--json` | no | | Output the raw server response as JSON |
675
+
676
+ ### `eigenpal workflow versions promote [options] <workflow-id> <versionId>`
677
+
678
+ Make an existing tagged workflow version current without creating another snapshot.
679
+
680
+ ### Arguments
681
+
682
+ | Name | Required | Variadic | Description |
683
+ | ------------- | -------- | -------- | ----------- |
684
+ | `workflow-id` | yes | no | |
685
+ | `versionId` | yes | no | |
686
+
687
+ ### Options
688
+
642
689
  | Flag | Required | Default | Description |
643
690
  | ------------------ | -------- | ------- | -------------------------------------- |
644
- | `--limit <n>` | no | `50` | Page size |
645
- | `--offset <n>` | no | `0` | Page offset |
646
691
  | `--base-url <url>` | no | | Server base URL |
647
692
  | `--json` | no | | Output the raw server response as JSON |
648
693
 
649
694
  ### `eigenpal workflow versions restore [options] <workflow-id> <versionId>`
650
695
 
651
- Restore the workflow to a previous version.
696
+ Restore a previous snapshot as a new untagged current version. Does not retag the source.
652
697
 
653
698
  ### Arguments
654
699
 
@@ -659,10 +704,11 @@ Restore the workflow to a previous version.
659
704
 
660
705
  ### Options
661
706
 
662
- | Flag | Required | Default | Description |
663
- | ------------------ | -------- | ------- | -------------------------------------- |
664
- | `--base-url <url>` | no | | Server base URL |
665
- | `--json` | no | | Output the raw server response as JSON |
707
+ | Flag | Required | Default | Description |
708
+ | ------------------ | -------- | ------- | ------------------------------------------------ |
709
+ | `--message <text>` | no | | Optional restore note stored on the new snapshot |
710
+ | `--base-url <url>` | no | | Server base URL |
711
+ | `--json` | no | | Output the raw server response as JSON |
666
712
 
667
713
  ### `eigenpal workflow step-type list|ls [options]`
668
714
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eigenpal/cli",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
5
5
  "keywords": [
6
6
  "eigenpal",