CLI
Major Changes
-
fadb59b: CLI command tree reorganized around the data model. Top-level surface is now
workflow,agent(reserved), andskill; everything that operates on a workflow lives underworkflow, grouped by sub-entity. Renames (clean break — no aliases):-
eigenpal exec <slug>→eigenpal workflow execution run <slug> -
eigenpal clear→eigenpal workflow clear -
eigenpal install-skill→eigenpal skill(same interactive multiselect UX) -
eigenpal execution \{get,list,watch,compare}→eigenpal workflow execution \{…} -
eigenpal workflow set-definition→eigenpal workflow push -
eigenpal workflow get-definition→eigenpal workflow pull -
eigenpal workflow set-evaluators/get-evaluators→workflow evaluators \{push,pull} -
eigenpal workflow set-dataset/get-dataset/list-examples→workflow dataset \{push,pull,list} -
eigenpal workflow run-experiment/get-experiment-status/get-experiment-results/get-eval-results/list-executions→workflow experiment \{run,status,results,eval-results,list} -
eigenpal workflow list-versions/restore-version→workflow version \{list,restore} -
eigenpal workflow list-step-types/get-step-type→workflow step-type \{list,get} -
eigenpal workflow get-execution→workflow execution get -
eigenpal workflow get-active-tenant→workflow active-tenantRemoved: -
eigenpal generate-meta— deleted. The IDs it generated were never read by any other CLI command; the server re-mints IDs on every push. Added: -
eigenpal agent— reserved placeholder namespace. Prints a “Coming soon” message; agentic-process operations will land here in a future release.
-
Minor Changes
-
c15ce88: Add the
eigenpal agentcommand tree for managing agents, triggers, datasets, executions, experiments, and sessions from the terminal. -
c82a215:
eigenpal auth loginnow shows an interactive server picker when no--base-urlflag orEIGENPAL_BASE_URLenv is set:-
Cloud (
https://studio.eigenpal.com) is the default highlighted option. - Any on-prem URLs already in your credentials file are surfaced as separate options labelled with the profile names that use them — re-login against an existing on-prem deployment without retyping the URL.
- Custom URL… prompts for a URL with strict validation. URL handling is now bulletproof in all three input paths (flag, env, picker):
- Trims leading/trailing whitespace, strips trailing slashes.
-
Auto-prepends
https://when you paste a bare hostname (e.g.eigenpal.example.com→https://eigenpal.example.com). -
Rejects non-http(s) schemes (
file://,ws://, etc.) with a clear error. - Rejects URLs with paths, query strings, or hash fragments — baseUrl is an origin, not a route.
-
Warns when authenticating over
http://against a non-loopback host (credentials sent unencrypted). Behavior with--base-urlorEIGENPAL_BASE_URLset is unchanged — explicit overrides skip the picker, and an active profile’s stored baseUrl is still inherited by all non-login commands.
-
Cloud (
-
c330c2a: CLI: bug fixes + new tooling.
success()/info()/dim()now write to stderr, not stdout.cmd | jqno longer mixes status lines with JSON output. (error()andwarn()already correctly used stderr.)workflow execution watch <bad-id>no longer treats 404 as a transient network blip; the loop exits immediately so the caller sees the structuredExecution not founderror and a non-zero exit instead of polling for 30 minutes.eigenpal statusexits non-zero when authentication fails, so scripts caneigenpal status && deploy.- New
eigenpal completion <bash|zsh|fish>command — emits a shell-completion script for the live command tree. Install viaeigenpal completion bash > /usr/local/etc/bash_completion.d/eigenpal(or your shell’s equivalent). - New global
--quiet/-qflag — suppressessuccess()/info()/dim()lines while keepingerror()andwarn()(and JSON output) intact. - New
addJsonFlagMutation()helper alongsideaddJsonFlag(). Mutating commands that do not have a curated table view now use it so--verboseno longer appears in--helpwhere it is a no-op. (Wire-up in mutating handlers happens in a follow-up commit on this branch.)
-
b2c4308: CLI: per-example dataset CRUD.
Three new subcommands under
workflow datasetmean you no longer have to re-zip and re-push the whole folder when you only want to flip one row:-
workflow dataset create-example <wfId> --name <n> [--input-json | --input-file] [--expected-json | --expected-file] [--annotation]— add one example. -
workflow dataset update-example <wfId> <exampleId> [--name] [--input-*] [--expected-*] [--annotation] [--row-order]— partial PATCH; flags you omit are left alone,--annotation ""clears. -
workflow dataset delete-example <wfId> <exampleId> [--yes]—--yesrequired for non-TTY shells.--input-file/--expected-fileaccept a path or-for stdin. Inline--*-jsonand the-filevariant are mutually exclusive. Server: thePOST /api/workflows/:id/eval-examplescreate schema now accepts thenameandannotationfields the dashboard already exposed via the dashboard’s PATCH path. No new endpoints — the v1 mirrors at/api/v1/eval-examples/[id]and/api/v1/workflows/[id]/eval-exampleswere already in place. Skill recipes restored:SKILL.mdandreference/dataset-format.mddocument the three flows (capture corrected output as new GT, add one example, delete one bad row).
-
-
d41ee8b: CLI: drop the
--waitflag fromworkflow execution run. The local-folder runner already waits for every example to reach a terminal state, so the flag was a documented no-op kept “for symmetry withexperiment run --wait.” That symmetry is not worth a dead flag in the user-facing help screen —experiment run --waitis meaningful (it polls batch status),execution run --waitnever was. Behavior is unchanged. Scripts that passed--waitwill now see an unknown-option error from Commander; remove the flag. -
d41ee8b: CLI: drop
--jqand--output-pathfromworkflow execution get. Pipe--jsonthrough realjqinstead. Rationale: our--jqwas dotted-path only (drillJsonPath), not real jq syntax. Calling it--jqwas misleading —gh --jq '.foo[] | select(.bar)'works; ours never could. Realjqis universally installed on dev machines and supports the full filter language. Migration:Updated SKILL.md and reference docs to use the canonical--json | jqpattern. -
c330c2a: CLI: small DX bundle + 5 audit follow-up fixes.
eigenpal status(and every other CLI call) now detects HTML-on-the-wire and throws a structuredHtmlResponseError(“API not reachable at <url> — got HTML response (likely wrong base URL or API not running)”) instead of dumping the 100 KB Next.js 404 body.workflow push --bump <patch|minor|major>reads the server’s current version and computes the next semver, splicing it into the YAML before sending. Pair with--workflow-id. Conflicts with a top-levelversion:in the YAML are rejected.workflow push --set-version <X.Y.Z>for explicit overrides. (Named--set-versionto avoid the global-v, --versionflag.)workflow evaluator-type \{list, get}— parallel namespace tostep-typefor evaluator schemas (exact-diff,llm-judge,custom-script). Returns JSON Schema instead of 404.eigenpal init [name]now works with no name — scaffolds into the current directory using the cwd basename as the workflow slug. Pair witheigenpal workflow execution run <slug>in the same dir;execution rundiscovers both flat (./workflow.yaml+./dataset/examples/<name>/) and nested (./eigenpal/workflows/<slug>/...) layouts.format-errornow appendsRun \eigenpal auth login` or set EIGENPAL_API_KEY.to every 401, and includes the URL we tried +EIGENPAL_BASE_URL/—base-url` hint on connection failures.execution get --jq <jsonpath>is the new canonical name for what was--output-path.--output-pathkeeps working as a hidden alias; help text shows--jqonly (matchesgh --jq/kubectl --jsonpath).workflow pushwithout--filein TTY now prompts for the path; non-TTY (CI) still errors with the existing “Missing —file” message.transform.scriptreference docs include a callout about TDZ on shadowed input names (const located = located || []triggersReferenceError).- Dropped the half-promise comment in
addJsonFlagabout future--fieldsprojection — the canonical answer isjqon the raw payload.
-
c330c2a: CLI: execution + auth polish.
workflow execution rungains--waitand--json. The local-folder runner already polled every example to terminal, so--waitis currently a no-op accepted for symmetry withexperiment run --wait.--jsonemits a single\{ workflowSlug, passed, failed, total }summary on stdout (human progress lines + the “Results:” footer still go to stderr). Exits 1 when any example fails, same as before.workflow execution watch: rename--max-minutes <n>→--max-wait <seconds>(default 1800 = 30 min). Matches the units used byexperiment status --watch --max-wait. Detach behavior unchanged: still exits 2 on timeout so callers distinguish “still running” from “completed”.workflow execution run+auth loginnow ship inlineExamples:blocks viaaddHelpText('after'). Mirrorsgh repo create --helpstyle.
-
d41ee8b: CLI:
workflow execution cancel <executionId>— request cancellation of a running, queued, or just-created execution. Idempotent against thePOST /api/v1/executions/:executionId/cancelendpoint. created/pending transitions straight tocancelled; running/waiting stampscancellationRequestedAtfor the worker to observe; already-terminal (completed / failed / cancelled) is a no-op exit-0 with an info line. Single-row destructive op — TTY proceeds silently (matchesgh run cancel), non-TTY shells (CI, pipes) require--yes. Pass--jsonto print the raw server payload for scripting; 404 → error line + exit 1. -
d41ee8b: CLI:
workflow experiment compare <batchIdA> <batchIdB>. Side-by-side eval-score diff between two experiment batches — no more exporting both result sets to a spreadsheet to spot regressions. eigenpal workflow experiment compare evb_old evb_new —workflow-id wf_abc One row per(example, evaluator)pair shared by both batches withA,B, andΔcolumns. Regressions (Δ below threshold) are flagged with ⚠ and tinted; improvements show in green. Footer aggregatesregressions / improvements / mean Δ / examples shared / only-in-\{A,B}so you can tell at a glance whether the new batch is a net win.-
--sort delta-asc | delta-desc | name— default is|Δ|descending so the biggest movers (in either direction) surface at the top. -
--regression-threshold <n>— default0.05. Tighten to0.10if your evaluators are noisy. -
--json— emits the full diff structure (\{ batchA, batchB, rows, summary }) for piping intojq. -
--workflow-id <id>— required; eval-results is workflow-scoped. Non-overlapping examples are listed in dedicatedonly in A/only in Bblocks before the table so |Δ| sort stays meaningful.
-
-
9dc84b9: CLI: agent-friendly experiment polling, watching, and comparison.
Four small additions so agents (and humans writing monitoring scripts) do not have to roll their own bash pollers, JSON parsers, or aggregators on top of
experiment status.-
experiment status --jsonnow includes a top-levelsummaryrollup —\{ total, terminal, complete, completedCount, failedCount, cancelledCount, rejectedCount, runningCount, pendingCount }. Polling scripts can read.summary.complete/.summary.failedCountdirectly instead of foldingexecutions[].statusthemselves.executions[]stays as-is for backwards compat. -
experiment status --short— single-line, awk-friendly stdout summary (e.g.6/6 done failed=0 cancelled=0 rejected=0). Mutually exclusive with--watch. Drops the failure detail block + closing tip so monitoring loops stay clean. -
New
experiment watch <workflow-id> <batchId>— polls until terminal, then auto-pulls eval results to./results-<batchId>.<format>in one command. Replaces the old “monitor + status + pull + score” recipe. Flags:--interval,--max-wait,--format csv|json,--pull-on-complete <path>to override destination,--no-pullto opt out. Exit codes matchexperiment status --watch(0 clean, 1 any failure, 2 deadline). -
experiment compareadds a per-evaluator aggregate — a “Per-evaluator deltas” table renders above the existing per-row table with rows, mean Δ, regressions, and improvements per evaluator (sorted by |meanΔ| desc).--jsonexposes it atsummary.byEvaluator[]. Updated SKILL.md so the canonical “run an experiment + collect results” recipe usesexperiment watchand adds a “monitoring scripts” section that calls out--shortandsummary.complete.
-
-
3da0612: CLI: pretty default rendering for
list,get, and mutating commands;--jsontoggle for piping.-
workflow definition/dataset/experiment/version/step-type listandworkflow execution listrender aligned ASCII tables.--jsonrestores the legacy raw payload. -
workflow execution get(no other flags) prints the vertical step list viarenderFrameinstead of dumping 1000+ lines of JSON.--jsonfor the full payload;--output-path/--step/--includecontinue to print filtered JSON. -
workflow experiment status(without--watch) prints a one-line summary plus the per-example failure block.--jsonfor raw payload. -
workflow definition push,workflow evaluators push,workflow experiment run, andworkflow version restoreprint one-linesuccess(...)summaries instead of dumping the response.--jsonrecovers legacy output. No breaking changes: every legacy JSON shape is still available behind--json. Status hints route to stderr so<command> | jqkeeps working.
-
-
d41ee8b: Breaking:
workflow step execis now generic over step type. The two hardcoded subcommands (workflow step exec transform.scriptandworkflow step exec ai.extract) becomeworkflow step exec <type>with a unified flag set, mirroring the kubectl-style noun/verb pattern.<type>accepts ANY step type registered inSTEP_SCHEMAS(@eigenpal/types). Unknown types exit 2 with a message pointing toworkflow step-type list; registered-but-unsupported types (everything excepttransform.scriptandai.extracttoday) exit 2 with a “not yet supported locally” message instead of the previous “command not found” Commander error.- Flag set: -
--config-json <json>— full step config as a JSON literal. ---config-file <path|->— full step config from JSON file or stdin. ---inputs k=v— repeatable;vmay be@path-to-file(auto-JSON-parsed when applicable) or a literal string. Fortransform.scriptthese merge into the configinputsmap (flag wins); forai.extractinput=…supplies the document text. ---output-schema <path>— defaults to the step type’s built-inoutputSchemafromSTEP_SCHEMASwhen omitted. - The previous per-type flags (
--code,--inputs-json,--input,--prompt,--schema,--provider,--model,--base-url) are gone — pass them inside--config-json/--config-fileinstead.--timeout-msand--memory-mbsurvive astransform.script-specific resource overrides. - Errors thrown by the handler now route through the same structured-envelope
rendering used elsewhere in
workflow/*commands (alignedfieldcolumn +hintline for\{ issues, hint }payloads), with a graceful fallback toformatCliErrorfor plain errors. Schema violations and unknown-type errors keep their dedicated exit codes (2).
-
c330c2a: CLI: workflow surface restructure — DX polish for the
workflownamespace. Breaking-but-deliberate changes that mirrorgh/vercelconventions:-
dataset \{create,update,delete}-example→dataset example \{create,update,delete}. Verbs now live UNDER theexamplenoun (matchesgh issue create,vercel project add). No aliases — old form is removed. -
New
dataset example get <wfId> <exampleId>— single-row read with prettyInputs / Expected / Metadatasections and--jsonfor the full payload (triggerInput,expectedOutput,annotation,rowOrder, timestamps). Closes the gap betweendataset list(table subset) and the previous “edit-blind” CRUD verbs. -
dataset push --modedefaults toappend. Was previously a required flag. Destructivereplacekeeps its--yesrequirement for non-TTY plus typed-slug TTY confirmation. -
experiment run:--poll-interval-srenamed to--interval(seconds), matchingexperiment status --watch’s flag.--waitno longer exits 1 whenpassRateis undefined (workflow with no graded examples). Now: graded + non-1.0 → exit 1; ungraded → exit 0. -
Mutating commands stop advertising
--verbose.workflow push,evaluators push,dataset push,experiment run,versions restore, and the newdataset example \{create,update,delete,get}useaddJsonFlagMutation(only--json).--verbosewas a no-op on these — removing it from--helpto stop lying. -
Command descriptions shortened. First sentence stays as the description; rich behavioral notes (return types, NOTE warnings) move to
addHelpText('after')blocks. Examples added toworkflow push,dataset push,experiment run, anddataset example create. Skill (SKILL.md+reference/dataset-format.md) updated to use the newdataset example \{create,update,delete,get}shape; recipes mentionexample getfor single-row inspection.
-
-
d41ee8b: CLI:
workflow step exec transform.scriptandworkflow step exec ai.extractfor local single-step iteration. Two new commands under a freshworkflow step execnamespace let you run one step locally — no server, no queue, no worker — so the script/prompt edit loop is sub-second instead of sub-minute:-
workflow step exec transform.script --code <path|-> [--inputs k=v...] [--inputs-json <path|->] [--output-schema <path>] [--timeout-ms 5000] [--memory-mb 10]— runs JS in QuickJS with the same TDZ + strict-mode + deep-frozen-input semantics the worker uses, so a script that crashes locally crashes the same way in production. -
workflow step exec ai.extract --input <path|-> --prompt <path|-> [--schema <path>] [--provider openai] [--model gpt-4o-mini] [--base-url <url>]— one LLM call, structured output when--schemais provided, raw assistant text when omitted. Reads provider/model fromWORKER_LLM_*env vars (orOPENAI_API_KEYfor the openai default). Both commands write the step output as JSON to stdout. Schema validation failures exit 2 with\{ code: 'output_schema_violation', issues: [\{ field, message }] }on stderr — agent-readable so the iteration loop is autonomous.@path syntax (--code @file.js,--inputs items=@arr.json) matchescurl -d @file/gh --body-file -. Bare paths and-(stdin) work too.
-
-
4137eb8: custom-script evaluator: the YAML now carries the entire
function scoreScript(expected, actual) \{ ... }declaration in a singlefunctionfield. Previous formats (code, thenbody) wrapped the text implicitly; the new shape stores the function declaration verbatim so the YAML matches the dashboard editor 1-for-1.workflow validateandworkflow evaluators validateflag YAML where the function fails to parse, the signature does not matchfunction scoreScript(expected, actual)exactly, or the body contains neitherreturnnorthrow— same checks the dashboard runs at form save. The function must return a number in [0, 1]; throws are caught and scored as 0. Existing evaluator YAML must be ported by hand: renamebody(orcode) tofunction, and add thefunction scoreScript(expected, actual) \{... }declaration around your statements. Parameter order is load-bearing — swappingexpectedandactualwould silently invert every score. -
3da0612: All list commands now support
--limit/--offsetpagination consistently.- CLI:
workflow execution list,workflow version list, andworkflow step-type listpreviously had no--offset(or no pagination at all). All six list commands now accept--limit/--offsetwith defaults of 50/0. - Server:
GET /api/workflows/:id/versionsnow reads?limit=and?offset=query params (defaults 50/0; max limit 100). ThelistVersionsrepo method gained an optional\{ limit, offset }argument. step-type listpaginates locally over the static catalog so the UX is uniform;totalreflects the full filtered set so callers can see how many rows are hidden by the page window.
- CLI:
-
3da0612: API: list endpoints now consistently return
\{ data, total }.-
GET /api/workflows/:id/experimentspreviously returned\{ experiments }— now returns\{ data, total }. -
GET /api/workflows/:id/versionspreviously returned a bare array — now returns\{ data, total }. This is a breaking change for direct API consumers. The CLI, frontend, andlistWorkflowVersionscontract are all updated. CLI’srenderListResulthelper is simpler (single envelope, no per-endpointdataKeyworkaround).
-
-
d41ee8b: Add workflow-agnostic eval-results export endpoint
(
GET /api/v1/eval-results/export?batchId=...&format=csv|json) that resolves the owning workflow from the batch id server-side. The CLI’sworkflow experiment compare batchA batchBno longer requires--workflow-id— the flag is dropped (breaking change) and both batches are resolved through the new endpoint instead. The legacy workflow-scoped route stays in place for back-compat. Document the CLI exit-code contract (0 = success, 1 = runtime error, 2 = misuse / recoverable failure) inpackages/cli/CLAUDE.md. -
4b1f81c:
transform.scriptnow accepts a full function declaration viawith.function:, mirroring the custom-script evaluator. Parameter names and order must equalObject.keys(inputs):Authoring is now Monaco-backed in the dashboard, with input keys surfaced as typed declarations soitems.autocompletes against the upstream step’s output schema. The legacywith.code:shape (a bare statement body, with input keys leaking in as globals) still loads — the worker auto-wraps it viawrapBodyAsScriptFunctionat handler entry, and the dashboard migratescode:tofunction:on the next save. New workflows should usefunction:. The schema rejects:- function names other than
script - parameter lists that do not match
Object.keys(inputs)in order async/import()/require()(sandbox-incompatible)- bodies with no
returnorthrow
- function names other than
-
3da0612: CLI: cleaner workflow command tree + matching
--jsonshape. Tree restructure — theworkflow definitionnamespace is gone. Its three verbs are core operations on the workflow itself, so they move up one level:evaluators,dataset,experiment,execution, andstep-typekeep their nested structure.--jsonreturns the table’s columns, not the server envelope. Previously--jsondumped\{ data: [\{...full server rows}], total }. Now it emits an array containing exactly the fields the table renders (e.g.[\{id, name, version, updatedAt}]forworkflow list).totalstill goes to stderr in both modes.--verboseopts into full rows:--json --verboseemits the full server rows for the rare cases you need fields the table does not surface.workflow list --json | jq '.[0].id'— the natural shape for piping.
Patch Changes
- 5909b21: Add execution-scoped agent feedback filters and expected artifact management to the API, CLI, and SDKs.
-
c15ce88: Rename agent API calls to
/v1/agentsand scope execution helpers under their owning workflow or agent. -
71361fd:
auth login: simplified flow. Removed the dead localhost-callback server (the dashboard never POSTed to it — every login fell through to copy-paste anyway). The browser now opens to/developers/api-keys?from=cliwith a clearer prompt (“Create a new key, copy it, paste below”), and the API key prompt hides input as you type or paste — same asgh auth login,stripe login, etc. -
d41ee8b: CLI: two cleanups for the audit follow-ups.
init agentand bareagentnow exit 2 instead of 0 when invoked. Both subcommands are reserved-but-unimplemented placeholders, and exiting 0 made it possible for a CI script that misrouted into one of them to silently no-op. POSIX exit 2 (“command exists but is not usable as invoked”) makes the misroute fail loudly. The human-readable “coming soon” stderr message is unchanged.- New
--no-colorglobal flag. Disables ANSI colors in all output, matching the convention shared bygh,kubectl,npm, andterraform. Equivalent to settingNO_COLOR=1in the environment, but takes effect for the current invocation only. Implemented as a lazy picocolors wrapper so the flip propagates to every helper inlib/ui.ts(and every downstream caller ofpc) regardless of when in the command lifecycle the flag is parsed.
-
809f3d4:
auth list: the org name is now the primary label (bold, immediately after the marker), the profile slug is dimmed beside it (still the value you feed toauth use <name>orEIGENPAL_PROFILE=<name>), and the base URL trails on the right. The redundant(tenantId)column was dropped — the profile slug already disambiguates.auth use(interactive picker) now prints the non-interactive form on success so you learn the slug you picked. -
49a8f94: Fixed a stray
undefinedin theeigenpal auth loginoutro. The success message useddim(...)(a stderr printer that returnsvoid) inside a template literal whereui.dim(...)(the inline string formatter) was meant. The dim “Saved profile X — switch with” hint now renders correctly instead of literalundefined. -
16c6e83: Updated
@eigenpal/cliauthor fromEigenpal <hello@eigenpal.com>toJedr Blaszyk <jedr@eigenpal.com>. Surfaces on the npm package page and innpm view @eigenpal/cli. -
b1d2d77: CLI: default
baseUrlis nowhttps://studio.eigenpal.cominstead ofhttp://localhost:3000. Affects only the fallback when nothing else is set — the priority chain is--base-url > EIGENPAL_BASE_URL > active profile > default. Users who raneigenpal auth loginagainst any environment already have abaseUrlin their profile and are unaffected. CI scripts that setEIGENPAL_API_KEYbut relied on the implicitlocalhost:3000will now hit production; setEIGENPAL_BASE_URL=http://localhost:3000if you want the old behavior. Aligns with the gh / vercel / kubectl convention of falling back to the public service rather than localhost. Also scrubs internal references from CLI source ahead of open-sourcing — generic placeholder names in docstrings and test fixtures, internal-tracker IDs stripped from comments, and one user-facing error message updated to point at the public issue tracker. -
75caaec:
eigenpal workflow pushandeigenpal workflow validatenow surface non-fatal schema-quality warnings when an output schema is loose enough to hurt downstream typing: categorical fields likecategory/status/kinddeclared as plain strings (noenum),transform.scriptfunctions returningany/unknown, untyped objects (type: objectwith noproperties), and untyped arrays (type: arraywith noitems). Warnings print to stderr and never block the push. Updated the bundled CLI skill (workflow-yaml.md) with a “Be specific with types” section teachingenumfields, per-value meanings inline indescription, and howtransform.scriptTS literal unions auto-produce JSON Schema enums. -
be693a3:
EIGENPAL_API_KEYis now a complete profile bypass — when set, the active profile’sbaseUrlis also ignored (previously the env key was paired with the profile’s URL, which is always wrong because an API key is provisioned for one server). In CI:-
Cloud: set
EIGENPAL_API_KEY=eig_live_…and you gethttps://studio.eigenpal.com. -
On-prem: set both
EIGENPAL_API_KEY=…andEIGENPAL_BASE_URL=https://eigenpal.example.com. Interactive profiles are unchanged: when you log in viaeigenpal auth login, the profile stores both apiKey and baseUrl, and every subsequent command derives baseUrl from that profile. SetEIGENPAL_PROFILE=<name>to switch profiles per-shell without modifying state.
-
Cloud: set
-
542fe0e: CLI: surface evaluator entry-level fields (
name,description,weight) so agents and humans can discover the optionaldescriptionfield on every evaluator.workflow evaluator-type get <type>now emitsentrySchemaalongsideconfigSchema. Inspect witheigenpal workflow evaluator-type get llm-judge | jq '.entrySchema'.- The skill reference now renders a “Common entry fields” table per type and a “Writing descriptions for stakeholders” section explaining the convention: each evaluator’s
descriptionshould be a one-sentence, plain-language summary aimed at non-technical reviewers in the dashboard. inittemplatespdf-extractionandtext-classificationnow ship adescription:line on their evaluator entries.
-
d41ee8b: CLI: fix
eigenpal statuserror UX + hardenformatCliErrorfor real-world connection failures.eigenpal statusagainst an unreachable base URL no longer mis-diagnoses the failure as an auth problem. Previously,EIGENPAL_BASE_URL=http://localhost:9999 eigenpal statusprinted “Authentication check failed; tryeigenpal auth login” — the wrong rabbit hole. The status command now rethrows non-auth errors (connection refused, DNS NXDOMAIN, timeout, HTML responses, 5xx) soformatCliErrorupstream can surface the right hint:Could not connect to the server at <url> ... Set EIGENPAL_BASE_URL or pass --base-url <url>.Only real 401/403 responses are treated as auth failures.formatCliErrornow detects connection failures wrapped in any Error shape — bareError('ENOTFOUND ...'),Errorwithcode: 'ECONNREFUSED', andTypeError('fetch failed')whose.causecarries the underlying error. Previously onlyTypeErrorwhose message containedfetch/URLwas recognized, sogetaddrinfo-style errors fell through to the generic raw-message branch and lost the URL context.
-
d41ee8b: CLI: fix
workflow experiment compare --sortadvertised values + thread base URL into error rendering.workflow experiment compare --sortnow advertises all four valid values (abs-delta-desc,delta-asc,delta-desc,name) in--helpand in the error message you get on a typo. Previously the help/error listed three; the defaultabs-delta-descwas accepted but undocumented.- The
action()decorator that wraps everyworkflow ...handler now resolves the base URL from the parsed opts and forwards it toprintApiError. Connection failures (fetch failed, invalid base URL) now echo the URL the CLI tried to reach — set via--base-url,EIGENPAL_BASE_URL, the active profile, or the default — so the user can see at a glance whether they pointed at the wrong server. - Note: the
--jqrename / honesty pass for--output-pathis deferred to a follow-up to avoid a merge conflict with the in-flightexecution cancelchange.
-
d41ee8b: CLI: validate
workflow experiment results --formatlocally; extractexperiment comparehelpers into a sibling file.workflow experiment results --formatnow rejects bad values up-front via a CommanderInvalidArgumentErrorparser-fn (modeled afterintArg).--format yamlpreviously round-tripped to the server and returned a generic HTTP 400; now the command exits 1 witherror: option '--format <csv|json>' argument 'yaml' is invalid. must be 'csv' or 'json'before any network I/O happens.- Internal refactor:
experiment compare’s pure helpers (buildBatchDiff,renderBatchDiffHuman,normalizeCompareSort,formatDelta,fetchEvalResults,exampleLabelOf, plus their types) moved out ofcommands/workflow/index.tsinto a new siblingcommands/workflow/experiment-compare.ts. The Commander registration stays inindex.ts. Tests follow the symbols intoexperiment-compare.test.ts. No user-visible behavior change from the refactor;index.tsshrinks by ~310 LOC.
- 2a15fc9: Add hidden Git-backed source inspection commands for early organization repository testing.
-
c82a215: Polish on
eigenpal auth loginbased on DX review:- Browser fallback — the prompt’s note now repeats the API-keys URL prominently, so when
xdg-open/open/startsilently fails (headless VMs, WSL without xdg-open, restricted containers) the user has a copyable URL right next to the prompt instead of a dim status line above. - Outro is informative — distinguishes “Saved profile X” (first login to this tenant) from “Updated profile X” (re-login overwrote prior creds in place) and shows the exact
eigenpal auth use Xcommand to switch to it later. No more raw(profile org_xxxx)dead-end. - Path-rejection error suggests the corrected origin: instead of “URL must be the server origin only”, it now says e.g. “Try <https://studio.eigenpal.com> instead of ‘/api/v1’”.
- Non-TTY error copy is consistent across CI and pipe-redirect paths — both name the missing piece (interactive terminal) and the workaround (
--base-url/ env var).
- Browser fallback — the prompt’s note now repeats the API-keys URL prominently, so when
-
be693a3:
eigenpal auth loginno longer opens your browser as the very first thing it does. The flow now mirrors mature CLIs (gh auth login,vercel login):- Pick the server (existing select prompt).
- Explain the flow — a clear step-by-step note tells you what is about to happen and surfaces the dashboard URL prominently so you can copy it manually if you prefer.
- Confirm browser launch — “Open the dashboard in your browser?” (default Yes). Users on headless VMs, WSL without xdg-open, or who already have the dashboard open can answer No and skip the best-effort browser launch entirely.
- Paste the API key — same as before.
openBrowserdoes not strand you. -
c82a215:
package.jsonnow carries full npm metadata:license: Apache-2.0,keywords,repository,bugs,homepage, andauthor. The published tarball also bundlesLICENSEandCHANGELOG.mdso the npm page renders the license badge instead of “License: none” and surfaces real keywords for search. -
ee32a73: Three fixes around the public CLI mirror and login UX:
- Public mirror keeps history.
sync_public_clino longer wipesgithub.com/eigenpal/cli’s git history on every release. It now clones the existing public repo, replaces the working tree with the new synth output, and pushes one commit per release. Each release addsrelease: @eigenpal/cli@X.Y.Zon top of the previous one. Tags accumulate too. - CHANGELOG no longer starts from
0.0.0. ThePin CLI version + apply pending changesetsstep insync_public_cliwas runningbunx changeset versionbefore pinning the source0.0.0-placeholderto the real release tag, so changeset wrote CHANGELOG headings like## 0.0.1while the actual published version was e.g.0.4.2. Pin first, then run changeset version, then sed-rewrite the topmost heading to match the real release tag. - Login picker copy + URL cleanup.
- Hint
(saved profile)had double parens because clack auto-wraps hints in parens — was rendering as((saved profile) from acme). Nowsaved profile: <names>. - Dropped
?from=clifrom the dashboard URL — the dashboard ignores the param, and a clean URL is easier to copy/paste/visit manually.
- Hint
- Public mirror keeps history.
-
c82a215: Public README cleanup:
- Removed the duplicated
# @eigenpal/cliheading and redundant install snippet that was being prepended to the public mirror’s README. The bundled CLI README is already self-contained, so the synth header was stacking a second title on top. - Reordered sections so users hit “what to install + what is available” first: Install → Commands → Use it → Primitives. Primitives is the conceptual reference; it now sits below the hands-on sections instead of above them.
- Removed the duplicated
-
ac95906: CLI:
eigenpal skillnow installs into 9 popular AI coding tools instead of 2. Previously the picker only listed Claude Code and Cursor. The new set covers:claude,cursor,codex,gemini,antigravity,opencode,pi,windsurf,github-copilot. Each tool follows the same<root>/skills/eigenpal/SKILL.mdAnthropic-skill convention (paths mirror OpenSpec’s canonical mapping) so a single skill bundle drops into whichever tool you already have installed. Detection heuristics were also broadened — each tool now matches against a list of sentinel files (e.g..claudeorCLAUDE.mdfor Claude Code; the several Copilot config paths for GitHub Copilot) instead of a single directory. The picker surfaces installed tools first, then detected, then the rest. Power-user flags are unchanged:--targetfor a custom path,--toolsfor a comma-separated id list,--force/--yesfor non-interactive flows. -
158d513: Breaking change.
eigenpal skillis now a noun namespace with three explicit verbs:install,uninstall,list. The bareeigenpal skillinvocation exits 2 with a usage error. Before:After:Mirrorseigenpal auth login/logout/list/use— every other CLI surface follows kind → noun → verb;skillwas the only one-shot exception. Now uniform. Migration: scripts usingeigenpal skillshould switch toeigenpal skill install. The--target/--tools/--force/--yesflags moved toinstallunchanged. -
c330c2a: CLI skill (installed by
eigenpal skill) updated to cover the recently-added command surface:-
SKILL.md— three new recipes (local single-step iteration viaworkflow step exec,workflow execution cancel,workflow experiment comparewithout--workflow-id), an “Output convention” section (stdout=data / stderr=status /--json) with the 0/1/2 exit-code contract,workflow push --bump/--set-versionmention,workflow evaluator-type \{list,get}parallel tostep-type. -
New
reference/step-exec.md— full deep-dive onworkflow step exec(the iteration killer for agents): supported step types,--inputs k=vgrammar,--config-\{json,file},--output-schemawith the\{ code: 'output_schema_violation', issues: [...] }envelope, resource limits, two worked examples. -
reference/debugging.md— addedskippedstatus +skippedReason+resolvedConfigfield to the inspection section, fullexecution cancelsection (cooperative; honored insidecontrol.foreach/control.parallel/control.block), fullexperiment comparesection, connection-vs-auth error distinction. -
reference/workflow-yaml.md—--bump/--set-versionpush flags +evaluator-typeintrospection. -
New
reference/cli/*.md— autogen flag reference for every command (auth, status, init, agent, completion, skill, workflow), copied frompackages/cli/docs/bygenerate-skill-reference.tsso agents have offline flag-level reference without--helpround-trips. CI’s--checkmode catches drift. Re-runeigenpal skillafter upgrading to install the new content.
-
-
c82a215: Removed all
localhost:3000references from user-visible help text and source comments. Theauth login --helpexamples no longer suggest--base-url http://localhost:3000— local development is an internal concern, not something CLI users need to think about. Defaults still targethttps://studio.eigenpal.com; advanced users with their own deployments can still override via--base-urlorEIGENPAL_BASE_URL. -
c82a215:
eigenpal --versionnow printsdevfor unpublished local builds instead of the confusing0.0.0-placeholder. If you ever seedev, the binary on your PATH is a local source build (e.g.,bun cli:install:local), not the npm-published one — runwhich -a eigenpalto find it. CI publish also gained a hard check that fails the release ifdist/cli.js --versiondoes not match therelease_tag, so a placeholder version can never reach users via npm. -
71361fd:
eigenpal --version(and-v) now reports the actual published package version instead of a hardcoded0.0.1. For local dev (eigenpal-dev) the placeholder value (0.0.0-placeholder) makes it obvious the bundle came from a workspace build, not npm. -
71361fd: Dataset folder format made symmetric —
expected/mirrorsinput/exactly:-
input/arguments.json(scalar args) ↔expected/output.json(raw data ground truth, no envelope wrapper) -
input/<argName>/<file>(input file binary) ↔expected/<docKey>/<file>(expected file output binary)expected/output.jsonis now the raw user JSON, mirroring the workflow’soutput:shape 1:1. Drop the\{ data, expectedDocuments }envelope requirement at the dataset boundary; the server still stores the envelope shape internally but the translation happens at import/export time, matching input file uploads. This is a pre-launch breaking change to the dataset folder layout — re-export your datasets to pick up the cleaner shape. Server import walksexpected/<docKey>/folders, uploads binaries via the workflow eval expected storage template, builds theexpectedDocumentsmap server-side. Server export reverses: writesexpectedOutput.dataas rawoutput.json, emits eachexpectedDocuments[docKey]entry as a binary underexpected/<docKey>/<filename>. Round-trip clean. CLIvalidate datasetaccepts any JSON object asoutput.json(drops the envelope check), and validatesexpected/<docKey>/folder names match the same[a-z0-9][a-z0-9-_]*pattern as input arg folders.reference/dataset-format.mddocuments the new symmetric layout with a worked example.
-
-
c330c2a: DX review — close 8 sharp edges that surfaced during long debugging loops:
-
execution get --output-pathno longer prints the literal string"undefined"and exits 0 on a missed path. Now exits 2 with a structured error listing the top-level keys + step names actually present. (#3) -
workflow set-dataset --file <folder>now works directly — the CLI zips the folder in memory before upload (matches the skill’s recipes which always said--file ./dataset/). Pre-zipped archives still work. (#5) -
Skill recipes (
SKILL.md,reference/dataset-format.md) no longer reference unregistered commands (workflow create-example/update-example/delete-example). Replaced with the workingset-dataset --mode replacerecipe; per-example CRUD will land later. (#6) -
requireApiKeynow renders through thelib/ui.tsenvelope (red✗icon, dimmed hint lines) instead of rawconsole.error. Matches the rest of the CLI’s error visual. (#8) -
eigenpal execprints→ <example>: <executionId>to stderr immediately when the execute API returns. If polling or artifact-write fails, the user still has a handle to recover withexecution get <id>. (#12) -
workflow get-experiment-results,get-eval-results,get-datasetaccept--outas optional. When omitted, the binary streams to stdout — pipe tojq,> file.csv, etc. Status line moved to stderr so stdout stays clean. (#15) -
auth listshows the canonicalorg_…tenant id alongside the display name, so two profiles for the same tenantName are distinguishable. (#19) -
execution watchexits 2 (timeout) on 30-min auto-detach instead of 0, so CI / agents distinguish “still running” from “completed”. (#21) Addsfflateto@eigenpal/clideps for the in-memory dataset folder zipping.
-
-
71361fd: Breaking (pre-launch):
expected/output.jsonin dataset folders must follow the system-wide envelope\{ data?: object, expectedDocuments?: object }— the same shapeeval_examples.expectedOutputuses everywhere else. Put the workflow output you want to compare underdata. Raw top-level objects (e.g.\{ header, covenants }) are now rejected withcode: invalid_expected_output.eigenpal validate datasetenforces this locally; the import endpoint enforces it server-side. Replaces the previous import-time auto-wrap (which silently mutated user input). -
71361fd:
workflow get-experiment-status --watchnow prints a per-example failure summary on stderr when the batch finishes with any failed/cancelled execution: example name, top-level error (truncated), and the exacteigenpal execution get <exec-id>command to drill into step-by-step detail. Removes the need for the agent to grep through a 1000-line JSON dump to find which example broke and why.reference/debugging.mddocuments the discovery path. -
71361fd:
eigenpal workflow get-experiment-status --watchnow polls until every execution in the batch reaches a terminal state (completed/failed/cancelled), then exits non-zero if any execution failed or was cancelled. Removes the need for agent-side polling loops that didn’t notice all-terminal-already and kept ticking. Renders a one-lineN/M terminal (counts)progress summary on stderr (in-place rewrite when stderr is a TTY), dumps the full JSON on stdout when done.--interval <s>and--max-wait <s>are configurable; defaults are 5s/30min. - c3bc4e9: Fix Git-backed source release and status edge cases.
- 04e4018: Harden hidden Git materialization commands for package-ref installs, frozen lockfile replay, dependency materialization, and source authoring smoke flows.
-
71361fd:
install-skill: multi-tool support via interactive multiselect.- Opens a
@clack/promptsmultiselect picker over supported agent tools (Claude Code, Cursor). Toggle on to install, toggle off to uninstall — one command covers both directions, so the separateuninstall-skillcommand is removed. - Picker pre-selects tools that already have an Eigenpal install or whose project directory exists in the cwd.
- Non-interactive: pass
--tools <ids>(comma-separated) to skip the picker; tools not listed get uninstalled.--yeskeeps the currently installed set. - Help description and CLI grouping no longer hard-code Claude Code.
- Opens a
-
f13cd4d: Release commits on the public CLI mirror (
github.com/eigenpal/cli) are now authored by the EigenPal Release Pal GitHub App instead of an anonymous bot identity. The App has its own logo and renders aseigenpal-release-pal[bot]next to each commit, with the human who triggered the release shown as co-author. No user-visible CLI behavior change — the install path and tarball contents are identical. -
71361fd: Review + simplification pass on the recent eval/dataset/error work:
Bug fixes surfaced by code review:
-
workflow get-experiment-status --watchexited 0 when the first poll raced before the server enqueued executions (0/0 terminalwas vacuously “done”). Now requirestotal > 0to consider the batch terminal. -
workflow set-datasetexited 0 on an unrecognized terminal NDJSON phase (truncated stream or future server-side phase). Now exits 1 with a clearImport response had unexpected terminal phasemessage. -
Dataset import silently dropped extra files when a folder under
input/<argName>/had multiple binaries but the workflow input was declaredtype: file(single). Now rejects up front withcode: single_file_input_overpopulatedand a hint to either trim the folder or change the input to the array form. -
Misleading import error: entries under
expected/that didn’t fit the<docKey>/<file>pattern got an error message saying “entry under input/…” with aninput/example. Message now branches on the actual parent. -
validate datasetcould crash on broken symlinks / FIFOs.statSyncnow wrapped in try/catch with a structured “unreadable filesystem entry” issue. Simplifications (net −21 LOC after both passes): -
Extracted a
uploadFolderhelper insideuploadAllExampleBinaries— the input and expected file-upload loops share a single body. -
set-datasetextractedpostMultipartandparseNdjsonEventshelpers. -
Removed the unused
validateOrThrowshim and the documentaryextractExpectedData/extractActualDataaliases. -
eval/route.ts: extracted sharedfromBodyExamplemapper andloadArtifacthelper. Skill docs (the agent’s pain point):reference/dataset-format.md+reference/workflow-yaml.mdnow spell out the workflow-YAML-input →triggerInputshape mapping in a table —type: filematerializes as\{fileId}(template\{\{ input.<name> }}),type: array, items: \{ type: file }materializes as[\{fileId}, ...](template\{\{ input.<name>[0] }}). Removes the ambiguity that led to incorrect[0]indexing on single-file inputs.
-
-
71361fd: Polish + simplify CLI UX.
install-skill: project-level only (./.claude/skills/eigenpal/). The--scope user|projectflag and the user-level (~/.claude/skills/) install path are removed — workflow projects are the unit of work, the skill belongs alongside them.- Switch interactive prompts to
@inquirer/prompts: arrow-key selection forinittemplate picker and theinstall-skill3-way conflict resolver, hidden-inputpasswordforauth login, proper yes/noconfirmforuninstall-skill. - Color output via
picocolors: green checkmarks for success, red for errors, cyan for info hints, dim for secondary detail. HonorsNO_COLORand non-TTY stdout automatically.
-
71361fd: Add a “Common recipes” section to the bundled skill (
SKILL.md) covering the typical end-to-end command sequences agents need: scaffolding a workflow, validating + pushing workflow YAML, validating + pushing evaluators, building + uploading a dataset (and editing examples server-side), running an experiment + collecting results, and debugging a failing execution. -
71361fd: Generate the skill reference (step types, evaluators, workflow YAML) from the canonical Zod schemas in
@eigenpal/typeson every build, with a CI gate that blocks PRs whose schema changes do not ship the regenerated docs. -
71361fd: Tighten error-message quality across the dataset/eval/execute pipeline so the CLI surfaces actionable feedback instead of opaque dumps.
eigenpal workflow set-datasetnow decodes the import endpoint’s NDJSON terminal event:abortedrenders the structuredissues[]with an aligned field column and exits non-zero;donewarns loudly whenexpectedSet < createdso missingexpected/output.jsonfiles do not slip through silently. Adds smart hints forinvalid_expected_outputandargument_name_collisioncodes.POST /api/v1/executereplaced its legacy\{ error, details }payload with the structured envelope (\{ issues, hint, requestId, docsUrl }) the CLI’sprintApiErroralready consumes.exact-diffevaluator no longer falls back to “compare whole envelope when.datais missing.” That fallback silently absorbed the import shape bugs we fixed; it now requires.dataon both sides (matching the system-wideWorkflowResultandExpectedOutputSchemacontracts) so any future shape regression fails loudly.- Exports
printIssuesfrom the CLI’svalidatecommand so other commands reuse the same field-aligned issue formatter.
-
71361fd: Structured validation errors with introspection hints.
workflow set-definition(and version creation) used to fail with an opaque"Invalid workflow definition"that left agents stuck. Now the response carries the canonical\{ issues, hint, docsUrl, requestId }envelope:-
Each issue has a dotted field path (
steps.2.config.passThreshold) and a human message — the agent can target the exact YAML line. -
The hint points at
eigenpal workflow get-step-type <type>when the failure is scoped to a single step type, oreigenpal workflow list-step-typesfor general schema browsing. Both commands read the authoritative catalog from@eigenpal/types— no docs scraping, no guessing. -
YAML syntax errors include line/column when available.
CLI renders the envelope with an aligned field column, the hint inline, and
the request id for correlation. Legacy
\{ error, details }responses still render best-effort.
-
Each issue has a dotted field path (
SDK
Major Changes
- c15ce88: Rename agent API calls to
/v1/agentsand scope execution helpers under their owning workflow or agent.
Minor Changes
- d1d3260:
client.workflows.run(and agent runs) now accept a Node readable stream (fs.createReadStream('contract.pdf')) as a file input, inferring the upload filename from the stream’s path. Adds atoFile(content, filename, mimeType?)helper for attaching a filename to raw bytes (Buffer,ArrayBuffer, orBlob). Streams are drained to bytes before the request is sent, so a retried request can replay the body.
Patch Changes
- 5909b21: Add execution-scoped agent feedback filters and expected artifact management to the API, CLI, and SDKs.
-
9905f7f: Fix the published SDKs so the README’s default
baseUrlworks. 0.4.10 shipped paths under/v1/...while the actual Next.js routes live at/api/v1/..., so every call from a freshly installed SDK either hit the marketing-site HTML (200 OK silently parsed as a workflow object) or 307’d to a redirect Python wouldn’t follow. Now the OpenAPI spec emits/api/v1/...and the regenerated TS + Python clients call the real URLs without abaseUrl: '.../api'workaround. Also trims the v1 public surface so SDK consumers no longer see internal columns. The legacy app handlers spread the raw DB row, which draggedtenantId,isBlock,currentHistoryId,evalConfigYaml,createdBy, the fullcurrentVersionobject,traceId,spanId,versionId,leaseId,workerId,definitionSnapshot,blockSnapshots,stepResults,evalScore,priority, retry-chain pointers, etc. into typed SDK output. The v1 endpoints now wrap the legacy response withpickPublicWorkflow/pickPublicWorkflowVersion/pickPublicExecutionhelpers (forwarded viaforwardItem/forwardList), so the wire shape matches the schema.WorkflowSummaryis now\{ id, version, createdAt, updatedAt }—versionis the release tag string callers actually want, replacing the internalcurrentHistoryId. Heads-up: workflownameis no longer surfaced as a top-level field (it was previously leaked viacurrentVersion.definition.name). It is authoritative inside the YAML, so fetch it viaclient.workflows.versions(id)[0].yamlContentand parse, or treat the workflowidas the canonical identifier. Tightens response handling on both sides: the client throwsEigenpalErrorwhenever a response carries a non-JSON Content-Type (2xx or 4xx), so the next misconfiguredbaseUrlfails loudly with a “point baseUrl at your EigenPal instance root” message instead of silently returning string-as-object or surfacing a downstreamJSONDecodeError. Renames the constructor:Eigenpal→EigenpalClientin both SDKs. The old name was ambiguous when imported alongsideEigenpalErroretc. and read awkwardly asnew Eigenpal(...)next to the brand (“Eigenpal”);EigenpalClientmatches the convention of every neighbouring class. No backwards-compat alias since 0.4.10 is fresh. Addsbun sdk:smoke:local [ts|py|both]— packs the local SDK as the exact tarball / wheel that ships, installs into a clean tmp workspace, and runs an end-to-end smoke againstEIGENPAL_BASE_URL. Verifies the v1 paths resolve, the trimmed public shape is enforced on the wire, and the HTML-host guard fires.defineRouterejects paths that do not start with/api/so the mismatch cannot reappear by accident.