## Summary
- Add repeatable `-I` / `--input KEY=VALUE` CLI overrides for workflow
run inputs on `fabro run`, `fabro create`, and `fabro preflight`. CLI
inputs are sparse per-key overrides that merge over the resolved config
inputs (preserving unrelated inherited values), unlike TOML
`[run.inputs]` which still replaces wholesale.
- Manifest bundling and graph-level goal resolution render workflow
source with the effective inputs before structural scanning, so
input-driven `@prompt`, `import`, and `stack.child_workflow` paths get
bundled correctly.
- Persist raw `KEY=VALUE` strings on `ManifestArgs.input` so server-side
replay applies the same sparse overrides on top of merged config.
- Review-driven cleanups: shared `TemplateContext::for_input_scan`
helper for the recurring "render inputs but defer goal" idiom (replaces
4 sites), `#[derive(Default)]` on `ManifestBuildInput` to drop
boilerplate, inline trivial `apply_input_overrides` wrapper, drop a
redundant clone, and tighten the parser/test helpers.
## Test plan
- [ ] `cargo nextest run -p fabro-cli -p fabro-config -p fabro-server -p
fabro-template -p fabro-workflow`
- [ ] `cargo +nightly-2026-04-14 fmt --check --all`
- [ ] `cargo +nightly-2026-04-14 clippy -p fabro-cli -p fabro-config -p
fabro-server -p fabro-template -p fabro-workflow --all-targets -- -D
warnings`
- [ ] Smoke: `fabro run <workflow> -I key=value --input other=42`
overrides those keys while preserving unrelated inherited inputs
- [ ] Smoke: `-I` accepts strings, integers, floats, booleans, empty
values; rejects arrays, inline tables, datetimes; rejects missing `=`
and empty key
- [ ] Smoke: input-driven `@prompts/{{ inputs.foo }}` and
`stack.child_workflow="{{ inputs.bar }}/workflow.fabro"` paths bundle
correctly when overridden via `-I`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.6 KiB
CLI Input Overrides Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add repeatable -I/--input key=value CLI overrides for workflow run inputs.
Architecture: Keep config-file [run.inputs] whole-map replacement semantics unchanged. Parse CLI input flags as a separate sparse override layer, then apply those key-level overrides after effective workflow settings are resolved. Manifest bundling must use the same effective inputs when scanning structural DOT references so input-driven @prompt, import, and stack.child_workflow paths are included in created run manifests.
Tech Stack: Rust, clap, Axum server manifest flow, OpenAPI/progenitor-generated fabro-api types, generated TypeScript Axios client, TOML value parsing, nextest.
Summary
Add repeatable CLI input overrides for fabro run, fabro create, and fabro preflight so documented -I key=value usage works. CLI inputs override individual keys in the effective run.inputs map while preserving other inherited inputs.
Behavior Decisions
| Input | Result |
|---|---|
foo= |
accepted as empty string |
foo=bar |
accepted as string "bar" via fallback |
foo="bar" |
accepted as TOML string "bar" |
foo=false |
accepted as boolean false |
foo=3 |
accepted as integer 3 |
foo=0.75 |
accepted as float 0.75 |
foo=2026-05-06 |
rejected; datetimes are not input scalars for this CLI flag |
foo=[1] |
rejected; arrays are not supported |
foo={a=1} |
rejected; inline tables are not supported |
foo |
rejected; missing = |
=bar |
rejected; empty key |
| duplicate keys | accepted; last value wins |
Implementation Tasks
-
Add a shared input-override parser in
fabro-config.- Parse raw
KEY=VALUEstrings intoHashMap<String, toml::Value>. - Split only on the first
=. - Apply the behavior table above exactly.
- Return errors that include the key when one is available and explain the failure reason.
- Do not echo full raw
KEY=VALUEstrings in error messages unless the input has no parseable key and the structure itself is the error. - Add unit tests for every row in the behavior table.
- Parse raw
-
Add
-I, --input <KEY=VALUE>to run-like CLI args.- Add a shared clap args struct in
lib/crates/fabro-cli/src/args.rs. - Flatten it into
RunArgsandPreflightArgs;createinheritsRunArgs. - Add parser tests for
fabro run workflow.toml -I foo=bar,fabro create workflow.toml --input foo=bar, andfabro preflight workflow.toml -I foo=bar. - Add a regression test that top-level
fabro -Vstill parses as version.
- Add a shared clap args struct in
-
Apply CLI inputs as sparse settings overrides.
- Do not put parsed CLI inputs directly in
RunLayer.inputs, because that would trigger whole-map replacement semantics. - After
WorkflowSettingsBuilder::build(), extendsettings.run.inputswith parsed CLI overrides. - Make
fabro run/preflight/createpreserve inherited inputs when only one key is overridden by-I. - Keep the existing TOML
[run.inputs]replacement test unchanged and passing.
- Do not put parsed CLI inputs directly in
-
Make manifest bundling input-aware before structural scanning.
- Ensure
build_run_manifest()computes effective settings with CLI input overrides before calling workflow collection. - Render each DOT source used only for manifest scanning with
TemplateContext::new().with_goal("{{ goal }}").with_inputs(effective_inputs.clone())before parsing for structural references. - Keep the manifest's stored workflow and file sources as original source text; rendering is only for discovery.
- Apply this to root workflows and imported workflow files before scanning
goal, nodeprompt, nodeimport, andstack.child_workflow/stack.child_dotfile. - Add manifest-builder tests where
-Isupplies a dynamic@promptpath,importpath, andstack.child_workflowpath, and assert the referenced files/workflows are bundled.
- Ensure
-
Make graph-level manifest goal resolution input-aware.
- Update
resolve_manifest_goal()precedence 3 so graph-levelgoalattributes are parsed from the same rendered root DOT source used for manifest structural scanning. - Keep precedence 1 (
--goal/--goal-file) and precedence 2 (run.goal) unchanged. - Preserve the manifest's stored root workflow source as original source text.
- Add a manifest-builder test for
graph [goal="@prompts/{{ inputs.goal_file }}"]with-I goal_file=goal.mdand assert the manifest goal haspath == "prompts/goal.md"andtext == <contents of prompts/goal.md>.
- Update
-
Persist and replay input overrides through run manifests.
- Add
input: string[]toManifestArgsindocs/public/api-reference/fabro-api.yaml. - Update
run_manifest_args()andpreflight_manifest_args()to include raw repeated input args. - Update every
types::ManifestArgsconstruction site and default/fixture in Rust. - Update
manifest_args_is_empty()so input-only manifests are not dropped. - Add a test where the only CLI override is
-I foo=barand assertmanifest.args.input == ["foo=bar"].
- Add
-
Apply manifest input overrides on the server.
- In
prepare_manifest(), parsemanifest.args.inputwith the shared parser. - Apply parsed inputs after
WorkflowSettingsBuilder::build()and before any prepared settings are used. - Add a server manifest replay test where project/workflow config has multiple inputs, manifest args override one key, and the unrelated inherited key remains.
- In
-
Regenerate API clients.
- Run
cargo build -p fabro-apiso progenitor regenerates Rust API types. - Run
cd lib/packages/fabro-api-client && bun run generateso the TypeScript Axios client includesManifestArgs.input. - Include both Rust and TypeScript generated diffs in the implementation change set.
- Run
-
Update docs.
- Update
docs/public/workflows/variables.mdxto document-I/--input key=value. - Update
docs/public/execution/run-configuration.mdxanddocs/public/administration/server-configuration.mdxso all[run.inputs]precedence and merge-semantics docs distinguish TOML whole-map replacement from CLI per-key overrides. - Refresh generated CLI docs after adding the clap flag.
- State that CLI input flags are highest precedence and merge per key.
- Keep the existing warning that TOML
[run.inputs]layers replace the whole inherited map.
- Update
Test Plan
- Parser tests in
fabro-configcover the full behavior table. - CLI parse tests cover
run,create,preflight,--input,-I, and top-level version parsing. - Settings tests prove CLI input overrides preserve unrelated inherited inputs and TOML
[run.inputs]replacement behavior remains unchanged. - Manifest builder tests cover input-driven
@prompt,import, andstack.child_workflowbundling. - Manifest goal tests cover input-driven graph-level
goal="@..."resolution. - Manifest replay tests cover input-only manifest args and server-side application of input overrides.
- Verification commands:
cargo build -p fabro-apicd lib/packages/fabro-api-client && bun run generatecargo dev docs refreshcargo dev docs check- targeted CLI/config/server tests
cargo nextest run -p fabro-cli -p fabro-config -p fabro-server
Assumptions
- CLI input flags are sparse per-key overrides, not whole-map replacement.
--inputis the long flag;--varis not added.- Input keys remain flat strings.
- TOML datetimes, arrays, and inline tables are rejected for CLI input overrides.