@eigenpal/cli 0.20.4 → 0.20.6

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.20.5
4
+
5
+ ### Patch Changes
6
+
7
+ - faf6d1e: `eigenpal auth status` (alias `whoami`) shows the active profile, server URL, and whether the credential validates, with `--json` support for scripting.
8
+ - faf6d1e: `--json` output is now safe to pipe: status chatter stays off stderr so `2>&1 | jq` parses, and re-pushing identical YAML with `workflow push --file X --json` reports `{ unchanged: true }` instead of an error.
9
+ - faf6d1e: `eigenpal reviews` dataset-review actions and decisions now use `remove` / `removed` instead of `reject` / `rejected`, matching the public API rename.
10
+
3
11
  ## 0.20.3
4
12
 
5
13
  ### Minor Changes
package/dist/cli.js CHANGED
@@ -206391,7 +206391,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
206391
206391
  // package.json
206392
206392
  var package_default = {
206393
206393
  name: "@eigenpal/cli",
206394
- version: "0.20.4",
206394
+ version: "0.20.6",
206395
206395
  description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
206396
206396
  keywords: [
206397
206397
  "eigenpal",
@@ -206954,8 +206954,12 @@ var quietMode = false;
206954
206954
  function setQuiet(value) {
206955
206955
  quietMode = value;
206956
206956
  }
206957
+ var jsonMode = false;
206958
+ function setJsonMode(value) {
206959
+ jsonMode = value;
206960
+ }
206957
206961
  function success(message) {
206958
- if (quietMode)
206962
+ if (quietMode || jsonMode)
206959
206963
  return;
206960
206964
  process.stderr.write(`${import_picocolors.default.green("✓")} ${message}
206961
206965
  `);
@@ -206965,17 +206969,19 @@ function error2(message) {
206965
206969
  `);
206966
206970
  }
206967
206971
  function info(message) {
206968
- if (quietMode)
206972
+ if (quietMode || jsonMode)
206969
206973
  return;
206970
206974
  process.stderr.write(`${import_picocolors.default.cyan("ℹ")} ${message}
206971
206975
  `);
206972
206976
  }
206973
206977
  function warn(message) {
206978
+ if (jsonMode)
206979
+ return;
206974
206980
  process.stderr.write(`${import_picocolors.default.yellow("!")} ${message}
206975
206981
  `);
206976
206982
  }
206977
206983
  function dim(message) {
206978
- if (quietMode)
206984
+ if (quietMode || jsonMode)
206979
206985
  return;
206980
206986
  process.stderr.write(`${import_picocolors.default.dim(message)}
206981
206987
  `);
@@ -207050,8 +207056,6 @@ function renderListResult(raw, columns, opts = {}) {
207050
207056
  const label = opts.entityLabel ?? "record";
207051
207057
  if (opts.json) {
207052
207058
  console.log(JSON.stringify(raw, null, 2));
207053
- if (rows.length > 0)
207054
- writeRecordCountHint(rows.length, total, label, false);
207055
207059
  return;
207056
207060
  }
207057
207061
  console.log(table(rows, columns));
@@ -210154,7 +210158,7 @@ function formatProgress(progress) {
210154
210158
  if (!progress)
210155
210159
  return "-";
210156
210160
  if (progress.complete) {
210157
- return progress.rejected ? `complete (${progress.rejected} rejected)` : "complete";
210161
+ return progress.removed ? `complete (${progress.removed} removed)` : "complete";
210158
210162
  }
210159
210163
  const remaining = progress.remaining ?? 0;
210160
210164
  const total = progress.total ?? remaining;
@@ -210174,10 +210178,10 @@ function parseApprovalDecision(opts, action) {
210174
210178
  if (normalized === "null" || normalized === "clear") {
210175
210179
  return null;
210176
210180
  }
210177
- if (normalized === "approved" || normalized === "rejected") {
210181
+ if (normalized === "approved" || normalized === "removed") {
210178
210182
  return normalized;
210179
210183
  }
210180
- throw new Error("--decision must be approved, rejected, null, or clear");
210184
+ throw new Error("--decision must be approved, removed, null, or clear");
210181
210185
  }
210182
210186
  function parseFieldDecision(opts) {
210183
210187
  return parseApprovalDecision(opts, "field-decision");
@@ -210227,7 +210231,7 @@ function buildReviewItemPatchBody(action, opts) {
210227
210231
  if (opts.decision !== undefined || opts.clear) {
210228
210232
  body.decision = parseFileDecision({ decision: opts.decision, clear: opts.clear });
210229
210233
  } else if (!opts.comment?.trim()) {
210230
- throw new Error("pass --decision approved|rejected (or --clear) or a --comment note when --action file-decision");
210234
+ throw new Error("pass --decision approved|removed (or --clear) or a --comment note when --action file-decision");
210231
210235
  }
210232
210236
  } else if (opts.filePath !== undefined || opts.newPath !== undefined || opts.file !== undefined) {
210233
210237
  if (action !== "edit-file") {
@@ -210280,20 +210284,20 @@ function formatReviewItemFiles(item) {
210280
210284
  return "-";
210281
210285
  const decisions = item.fileDecisions ?? {};
210282
210286
  let approved = 0;
210283
- let rejected = 0;
210287
+ let removed = 0;
210284
210288
  for (const file of files) {
210285
210289
  const key = file.path ?? file.name ?? "";
210286
210290
  const decision = decisions[key]?.decision;
210287
210291
  if (decision === "approved")
210288
210292
  approved += 1;
210289
- else if (decision === "rejected")
210290
- rejected += 1;
210293
+ else if (decision === "removed")
210294
+ removed += 1;
210291
210295
  }
210292
210296
  const total = files.length;
210293
- const decided = approved + rejected;
210297
+ const decided = approved + removed;
210294
210298
  if (decided === 0)
210295
210299
  return `${total} file${total === 1 ? "" : "s"}, undecided`;
210296
- return `${approved}/${total} approved${rejected ? `, ${rejected} rejected` : ""}`;
210300
+ return `${approved}/${total} approved${removed ? `, ${removed} removed` : ""}`;
210297
210301
  }
210298
210302
  function registerDatasetReviewRequestCommands(dataset, resolveAutomation) {
210299
210303
  const reviewRequest = dataset.command("review-request").description("Review dataset ground truth with a human. Snapshot examples for review and poll progress until reviewers finish.").action(() => {
@@ -210337,7 +210341,7 @@ Examples:
210337
210341
 
210338
210342
  Agent loop: create with focus/ignore and per-example notes → poll \`get --json\`
210339
210343
  until \`.progress.complete\` → record per-field decisions and per-example
210340
- approve/reject with comments → \`pull --out <dir>\` to fetch expected files
210344
+ approve/remove with comments → \`pull --out <dir>\` to fetch expected files
210341
210345
  (snapshot or reviewer-corrected) → correct bytes with
210342
210346
  \`item --action edit-file --file-path <path> --file <local>\` (or
210343
210347
  \`--new-path\` for brand-new files) → record per-file decisions with notes
@@ -210345,7 +210349,7 @@ via \`item --action file-decision\` → read \`events\` → \`dataset pull\` and
210345
210349
  MANUAL per-example reconcile into the live dataset (no auto-apply exists by
210346
210350
  design; reviewers can err) → \`update --status closed\`.
210347
210351
 
210348
- \`reject\` is a recommendation only — nothing is deleted by any review endpoint.
210352
+ \`remove\` is a recommendation only — nothing is deleted by any review endpoint.
210349
210353
  Use \`--json\` from agents; API errors exit non-zero.
210350
210354
  `).action(action(async (automationRef, opts) => {
210351
210355
  if (opts.exampleName.length === 0) {
@@ -210437,7 +210441,8 @@ Examples:
210437
210441
 
210438
210442
  At least one of --title, --instructions, --focus/--focus-json, --ignore, or
210439
210443
  --status is required. Closing a request does not write expected outputs back
210440
- to the dataset — reconcile manually after \`dataset pull\`.
210444
+ to the dataset — reconcile manually after \`dataset pull\`. A closed request
210445
+ accepts only \`--status open\` to reopen it.
210441
210446
  `).action(action(async (automationRef, reviewId, opts) => {
210442
210447
  const body = {};
210443
210448
  if (opts.title !== undefined)
@@ -210471,7 +210476,7 @@ to the dataset — reconcile manually after \`dataset pull\`.
210471
210476
  const updated = payload;
210472
210477
  success(`Updated review request ${ui.bold(updated.id ?? reviewId)}` + (updated.status ? ` ${ui.dim(`(${updated.status})`)}` : ""));
210473
210478
  }));
210474
- addJsonFlag(withBaseUrl(reviewRequest.command("items <automation-id> <review-id>"))).description("List snapshotted review items and their statuses.").option("--status <csv>", "Filter by item status (pending,approved,edited,rejected)").action(action(async (automationRef, reviewId, opts) => {
210479
+ addJsonFlag(withBaseUrl(reviewRequest.command("items <automation-id> <review-id>"))).description("List snapshotted review items and their statuses.").option("--status <csv>", "Filter by item status (pending,approved,edited,removed)").action(action(async (automationRef, reviewId, opts) => {
210475
210480
  const { client, automationId } = await resolveAutomation(automationRef, opts);
210476
210481
  const params = {};
210477
210482
  if (opts.status)
@@ -210512,7 +210517,7 @@ to the dataset — reconcile manually after \`dataset pull\`.
210512
210517
  { key: "createdAt", header: "createdAt" }
210513
210518
  ]));
210514
210519
  }));
210515
- addJsonFlag(withBaseUrl(reviewRequest.command("item <automation-id> <review-id> <item-id>"))).description("Approve, reject, reopen, comment, edit, or record a field- or file-decision on one review item. Upload corrected expected-file bytes with edit-file.").requiredOption("--action <approve|reject|reopen|comment|edit|field-decision|file-decision|edit-file>", "Item action").requiredOption("--expected-updated-at <iso>", "Item updatedAt the client last observed (optimistic concurrency)").option("--comment <text>", "Note stored on the item, field, or file").option("--field-path <path>", "Dotted expected-output path for comment or field-decision").option("--file-path <path>", "Expected-file path for file-decision or edit-file (correct)").option("--new-path <path>", "Expected-file path for edit-file uploads of brand-new reviewer files").option("--file <local path>", "Local file bytes to upload when --action edit-file").option("--decision <approved|rejected|null>", "Field or file decision for --action field-decision or file-decision (null/clear removes it)").option("--clear", "Clear a field or file decision (sends decision: null)", false).option("--expected-json <json>", "Replacement expected JSON when --action edit").addHelpText("after", `
210520
+ addJsonFlag(withBaseUrl(reviewRequest.command("item <automation-id> <review-id> <item-id>"))).description("Approve, remove, reopen, comment, edit, or record a field- or file-decision on one review item. Upload corrected expected-file bytes with edit-file.").requiredOption("--action <approve|remove|reopen|comment|edit|field-decision|file-decision|edit-file>", "Item action").requiredOption("--expected-updated-at <iso>", "Item updatedAt the client last observed (optimistic concurrency)").option("--comment <text>", "Note stored on the item, field, or file").option("--field-path <path>", "Dotted expected-output path for comment or field-decision").option("--file-path <path>", "Expected-file path for file-decision or edit-file (correct)").option("--new-path <path>", "Expected-file path for edit-file uploads of brand-new reviewer files").option("--file <local path>", "Local file bytes to upload when --action edit-file").option("--decision <approved|removed|null>", "Field or file decision for --action field-decision or file-decision (null/clear removes it)").option("--clear", "Clear a field or file decision (sends decision: null)", false).option("--expected-json <json>", "Replacement expected JSON when --action edit").addHelpText("after", `
210516
210521
  Examples:
210517
210522
  $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210518
210523
  --action field-decision --field-path vendor.iban --decision approved \\
@@ -210530,10 +210535,10 @@ Examples:
210530
210535
  --action edit-file --new-path expected/appendix.pdf --file ./appendix.pdf \\
210531
210536
  --expected-updated-at 2026-01-01T00:00:00.000Z --json
210532
210537
  $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210533
- --action reject --comment "wrong vendor" \\
210538
+ --action remove --comment "wrong vendor" \\
210534
210539
  --expected-updated-at 2026-01-01T00:00:00.000Z --json
210535
210540
 
210536
- \`reject\` marks the example as not recommended for the dataset; it does not
210541
+ \`remove\` marks the example as not recommended for the dataset; it does not
210537
210542
  delete anything. \`--clear\` / \`--decision null\` removes a prior field or
210538
210543
  file decision (API: decision: null). A file-decision \`--comment\` without a
210539
210544
  decision is a note and needs an existing decision server-side. \`edit-file\`
@@ -210608,7 +210613,7 @@ Layout (mirrors the dataset archive so reconcile is a copy):
210608
210613
 
210609
210614
  There is no write-back: copy approved/edited files and expected JSON into the
210610
210615
  live dataset example-by-example by hand (reviewers can err), then
210611
- \`dataset push\`. \`reject\` is a recommendation only — nothing is deleted.
210616
+ \`dataset push\`. \`remove\` is a recommendation only — nothing is deleted.
210612
210617
  `).action(action(async (automationRef, reviewId, opts) => {
210613
210618
  const { client, automationId } = await resolveAutomation(automationRef, opts);
210614
210619
  const detail = await client.get(datasetReviewRequestsPath(automationId, reviewId));
@@ -242212,6 +242217,69 @@ async function authUse(profileName) {
242212
242217
  ${ui.dim(`In scripts: \`eigenpal auth use ${target}\` or \`EIGENPAL_PROFILE=${target}\``)}`);
242213
242218
  }
242214
242219
  }
242220
+ async function authStatus(opts) {
242221
+ const config = resolveConfig(opts);
242222
+ const source = resolveSource();
242223
+ const profile = source.apiKey === "profile" ? source.profile ?? activeProfileName() : null;
242224
+ if (!config.apiKey) {
242225
+ if (opts.json) {
242226
+ console.log(JSON.stringify({ authenticated: false, profile, baseUrl: config.baseUrl, keySource: source.apiKey }, null, 2));
242227
+ } else {
242228
+ error2("Not authenticated. Run `eigenpal auth login` or set EIGENPAL_API_KEY.");
242229
+ }
242230
+ process.exit(1);
242231
+ }
242232
+ const client = new ApiClient(config);
242233
+ let auth = null;
242234
+ try {
242235
+ auth = await client.get("/v1/auth/check");
242236
+ } catch (err) {
242237
+ if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
242238
+ auth = null;
242239
+ } else {
242240
+ throw err;
242241
+ }
242242
+ }
242243
+ if (!auth || !auth.ok) {
242244
+ if (opts.json) {
242245
+ console.log(JSON.stringify({ authenticated: false, profile, baseUrl: config.baseUrl, keySource: source.apiKey }, null, 2));
242246
+ } else {
242247
+ error2("Credential invalid. Run `eigenpal auth login` for a fresh key.");
242248
+ }
242249
+ process.exit(1);
242250
+ }
242251
+ if (opts.json) {
242252
+ console.log(JSON.stringify({
242253
+ authenticated: true,
242254
+ profile,
242255
+ baseUrl: config.baseUrl,
242256
+ keySource: source.apiKey,
242257
+ tenantId: auth.tenantId ?? null,
242258
+ tenantName: auth.tenantName ?? null,
242259
+ user: { email: auth.email ?? null, name: auth.name ?? null },
242260
+ keyId: auth.keyId ?? null
242261
+ }, null, 2));
242262
+ return;
242263
+ }
242264
+ const lines = [];
242265
+ lines.push(`${ui.dim("profile:")} ${profile ? ui.bold(profile) : ui.dim("(env key, no profile)")}`);
242266
+ lines.push(`${ui.dim("server:")} ${config.baseUrl}`);
242267
+ lines.push(`${ui.dim("tenant:")} ${ui.bold(auth.tenantName ?? auth.tenantId ?? "?")}`);
242268
+ if (auth.tenantId) {
242269
+ lines.push(`${ui.dim("tenant id:")} ${auth.tenantId}`);
242270
+ }
242271
+ if (auth.email) {
242272
+ lines.push(`${ui.dim("user:")} ${auth.email}${auth.name ? ` (${auth.name})` : ""}`);
242273
+ }
242274
+ if (auth.keyId) {
242275
+ lines.push(`${ui.dim("key:")} ${auth.keyId}`);
242276
+ }
242277
+ if (source.apiKey === "env") {
242278
+ lines.push(`${ui.dim("key from:")} ${ui.warn("EIGENPAL_API_KEY env var (overrides credentials file)")}`);
242279
+ }
242280
+ console.log(lines.join(`
242281
+ `));
242282
+ }
242215
242283
 
242216
242284
  // src/commands/completion/index.ts
242217
242285
  function isShell(value) {
@@ -247463,6 +247531,10 @@ Update path requires the YAML to carry a top-level \`version: X.Y.Z\`, OR for
247463
247531
  into the YAML before sending so the file-on-disk doesn't have to be edited
247464
247532
  per push). Passing both an explicit \`version:\` in the YAML and
247465
247533
  \`--bump\` / \`--set-version\` on the command line is rejected.
247534
+ Re-pushing byte-identical YAML for an existing version with \`--json\` is a
247535
+ no-op reporting \`{ unchanged: true, workflowId, version }\` (exit 0);
247536
+ without \`--json\` — or with differing content — it stays a
247537
+ version-conflict error.
247466
247538
  \`--set-version\` is the long form because the global \`-v, --version\`
247467
247539
  flag would otherwise shadow it. Validation failures come back as
247468
247540
  \`{ issues: [{ field, message, code }], hint }\`; follow the hint and re-run
@@ -247545,7 +247617,13 @@ new uploads are deleted. The file on disk is not rewritten.
247545
247617
  error2(`Cannot update workflow ${ui.bold(workflowId)} — pass --bump <patch|minor|major> or --set-version <X.Y.Z>, or add a top-level \`version: X.Y.Z\` to ${filePath}.`);
247546
247618
  process.exit(1);
247547
247619
  }
247548
- await preflightVersionConflict(client, workflowId, nextVersion);
247620
+ const yamlToSendForCompare = loadedTemplates.length === 0 ? cliWantsRewrite ? spliceWorkflowVersion2(yamlOnDisk, nextVersion) : yamlOnDisk : undefined;
247621
+ if (await preflightVersionConflict(client, workflowId, nextVersion, {
247622
+ json: opts.json,
247623
+ yamlToSend: yamlToSendForCompare
247624
+ })) {
247625
+ return;
247626
+ }
247549
247627
  } else {
247550
247628
  if (opts.bump) {
247551
247629
  throw new Error("--bump requires --workflow-id (need a current version to bump from). For a new workflow, use --set-version <X.Y.Z> or omit both.");
@@ -247681,15 +247759,26 @@ async function preflightWorkflowValidate(client, yaml, workflowId) {
247681
247759
  ${detail}` : "Workflow validation failed.");
247682
247760
  }
247683
247761
  }
247684
- async function preflightVersionConflict(client, workflowId, nextVersion) {
247762
+ async function preflightVersionConflict(client, workflowId, nextVersion, opts) {
247685
247763
  const listed = await client.get(`/api/workflows/${workflowId}/versions`, {
247686
247764
  limit: "100",
247687
247765
  offset: "0"
247688
247766
  });
247689
247767
  const versions = Array.isArray(listed.data) ? listed.data : [];
247690
- if (versions.some((row) => row.version === nextVersion)) {
247691
- throw new Error(`Version ${nextVersion} already exists for ${workflowId}. Choose a new --set-version / --bump, or change the YAML version.`);
247768
+ if (!versions.some((row) => row.version === nextVersion))
247769
+ return false;
247770
+ if (opts?.json && opts.yamlToSend !== undefined) {
247771
+ const current = await client.get(`/api/workflows/${workflowId}`);
247772
+ if (current?.currentVersion?.version === nextVersion && normalizeYamlForCompare(current.currentVersion.yamlContent) === normalizeYamlForCompare(opts.yamlToSend)) {
247773
+ printJson6({ unchanged: true, workflowId, version: nextVersion });
247774
+ return true;
247775
+ }
247692
247776
  }
247777
+ throw new Error(`Version ${nextVersion} already exists for ${workflowId}. Choose a new --set-version / --bump, or change the YAML version.`);
247778
+ }
247779
+ function normalizeYamlForCompare(yaml) {
247780
+ return (yaml ?? "").replace(/\r\n/g, `
247781
+ `).trim();
247693
247782
  }
247694
247783
  async function rethrowAfterTemplateCleanup(client, created, err) {
247695
247784
  const reason = err instanceof Error ? err.message : String(err);
@@ -249212,9 +249301,12 @@ function commandLineWidth(cmd) {
249212
249301
  var __filename3 = path20.resolve(fileURLToPath8(import.meta.url));
249213
249302
  var program2 = new Command2;
249214
249303
  var cliVersion = package_default.version === "0.0.0-placeholder" ? "dev" : package_default.version;
249215
- program2.name("eigenpal").description("Eigenpal CLI — AI workflows, evals, and experiments").enablePositionalOptions().version(cliVersion, "-v, --version", "Print the CLI version and exit").option("-q, --quiet", "Suppress informational status output (success/info/dim). Errors and warnings still print; --json output is unaffected.").hook("preAction", () => {
249304
+ program2.name("eigenpal").description("Eigenpal CLI — AI workflows, evals, and experiments").enablePositionalOptions().version(cliVersion, "-v, --version", "Print the CLI version and exit").option("-q, --quiet", "Suppress informational status output (success/info/dim). Errors and warnings still print; --json output is unaffected.").hook("preAction", (_thisCommand, actionCommand) => {
249216
249305
  if (program2.opts().quiet)
249217
249306
  setQuiet(true);
249307
+ const actionOpts = actionCommand.opts();
249308
+ if (actionOpts.json ?? program2.opts().json)
249309
+ setJsonMode(true);
249218
249310
  });
249219
249311
  program2.command("status").description("One-shot dashboard: server, active tenant, user, key id, workflow count. Pair with `--json` for scripting.").option("--base-url <url>", "Server base URL").option("--json", "Emit machine-readable JSON instead of human-readable text").action(action(async (opts) => status(opts)));
249220
249312
  var initCmd = program2.command("init [name]").description("Scaffold a new workflow project. Without `[name]`, scaffolds into the current directory using the cwd basename as the workflow name. With `[name]`, creates `./<name>/` and uses that as the slug. The flat layout matches what `run workflows.<slug>` already discovers — no manual file moves.").option("--template <name>", "Skip the picker; use this template").option("--dir <dir>", "Target directory (default: cwd if `[name]` omitted, else ./<name>)").option("--yes", "Non-interactive: pick the default template (blank)").action(async (name, opts) => {
@@ -249262,6 +249354,7 @@ authCmd.command("list").description("List configured profiles. The active one is
249262
249354
  authCmd.command("use [profile]").description("Switch the active profile (persistent across shells). Omit `[profile]` to pick from a list. For one-shot per-shell switching, set `EIGENPAL_PROFILE=<name>` instead.").action(async (profile) => {
249263
249355
  await authUse(profile);
249264
249356
  });
249357
+ authCmd.command("status").alias("whoami").description("Show the active profile, server base URL, and whether the credential validates. Lighter than top-level `status` (no workflow count). Pair with `--json` for scripting.").option("--base-url <url>", "Server base URL").option("--json", "Emit machine-readable JSON instead of human-readable text").action(action(async (opts) => authStatus(opts)));
249265
249358
  registerWorkflowCommands(program2);
249266
249359
  registerAgentCommands(program2);
249267
249360
  registerModelsCommands(program2);