Migrate all 7 CLI tables to cli-table, which measures column widths
correctly in the presence of ANSI escape codes, fixing misaligned
columns in `fabro ps`. Also fix DIRECTORY to show ~/relative paths
instead of just the last component, and compute elapsed duration for
running jobs instead of showing "-".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default to showing only running processes (use -a for all), remove row
limit, display oldest-first, truncate run IDs to 12 chars, add DIRECTORY
column from host_repo_path, and drop STARTED/COST/LABELS columns.
- Add host_repo_path to Manifest and populate from RunConfig
- Add StatusFilter enum (RunningOnly/All) to filter_runs
- Replace --limit with -a/--all flag (docker-ps semantics)
- Add host_repo_path to RunInfo, extract in scan_runs
- New column layout: RUN ID | WORKFLOW | STATUS | DIRECTORY | DURATION
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR enriches the `fabro ps` output with colored status indicators,
duration/cost columns, relative timestamps, and pagination controls.
Status values are now color-coded (green for success, red for fail, cyan
for running, dim for unknown), the header row is bolded, and
separators/labels are dimmed.
Duration and total cost are now extracted from `conclusion.json` and
displayed as new columns. Start times are shown as human-friendly
relative strings (e.g., "2m ago", "3h ago") instead of raw RFC 3339
timestamps, with full timestamps preserved in `--json` output.
New `--limit N` (default 10) and `--all` flags cap the displayed output,
with a footer indicating how many runs are shown out of the total.
PR: https://github.com/fabro-sh/fabro/pull/2
Move expand_tilde from fabro-config to fabro-util::path so it can be
shared without circular dependencies, and apply it to the goal file
path in resolve_cli_goal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the identical CheckpointSaved hook block from both git and non-git
checkpoint branches to a single block after the if/else. Replace
bsha.to_string() with bsha.clone() for &String → String conversion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the separate CheckpointSaved event — CheckpointCompleted now fires in
both git and non-git paths. git_commit_sha is Optional (None when git is
disabled or for start nodes). The CheckpointSaved hook event is preserved
unchanged for backward compat with user hook configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename GitCheckpoint/GitCheckpointFailed to CheckpointCompleted/CheckpointFailed
to separate checkpoint lifecycle from git operations. Add 7 new granular git
events: GitCommit, GitPush, GitBranch, GitWorktreeAdd, GitWorktreeRemove,
GitFetch, GitReset. Emit at all relevant call sites in engine.rs and parallel.rs.
Update push helpers to return bool for GitPush success tracking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fake StageStarted/StageCompleted events with dedicated retro
variants so consumers can distinguish retro activity from normal stages.
The resume path now emits retro events instead of silently skipping them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix: empty-string status from old logs now defaults to "success"
instead of rendering as red/error
- Add From<&StageUsage> for fabro_llm::Usage to centralize conversion
- Simplify usage aggregation: replace collect+reduce+unwrap with
direct .reduce() on the iterator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds aggregate `status` and `usage` fields to the WorkflowRunCompleted
event so `fabro logs --pretty` can render a complete end-of-run summary
(status, tokens, cache, reasoning) without scanning all StageCompleted
events. Also adds pretty handlers for PullRequestCreated/Failed events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write id.txt and touch empty progress.jsonl in detach_run() before
spawning the child process so that `fabro logs -f ULID` can resolve
the run and tail the file immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API server set RunConfig.dry_run but never called
engine.set_dry_run(), so command/script nodes executed for real
during API-served dry runs. Add the missing call.
Also extract EngineServices::test_default() to replace 11 identical
make_services() bodies and 8 inline struct constructions across
handler test modules (-254/+57 lines).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
--dry-run was not suppressing real git push operations in three places:
pre-run branch sync, post-run auto-PR creation, and engine checkpoint
pushes. Guard all three with dry_run checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Command nodes were running for real during dry-run mode because the
dry_run flag only affected LLM-backed handlers. Propagate dry_run
through EngineServices so CommandHandler can skip execution and return
a simulated success.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Outputs run_id, run_dir, status, manifest, conclusion, checkpoint, and
sandbox as a JSON array (null for missing files). Resolves runs by ID
prefix or workflow name, matching existing `fabro logs` semantics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
URLs like https://x-access-token:TOKEN@github.com/owner/repo.git are
used by Daytona sandboxes. Strip the credentials before matching the
github.com prefix so pr_create and other callers work in those envs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
trycmd's Env struct requires env vars under [env.add], not directly
under [env]. Vars placed directly under [env] are silently ignored by
serde, so the subprocess ran with a fully cleared env. On CI this caused
dirs::home_dir() to fall back to passwd, loading the real cli.toml
(with app_id) but without GITHUB_APP_PRIVATE_KEY → partial config error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-generates a ULID in the parent, passes it to the child via hidden
`--run-id` arg, prints the ULID to stdout, and exits immediately.
Child stdout/stderr go to `{run_dir}/detach.log`. Uses `setsid()` on
unix to detach from the controlling terminal. Existing `fabro ps` and
`fabro logs` work with no changes since `run.pid` and `conclusion.json`
are written by the child as usual.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`inherit = false` clears HOME but `dirs::home_dir()` falls back to the
passwd database, picking up the runner's ~/.fabro/cli.toml. The loaded
app_id without GITHUB_APP_PRIVATE_KEY triggers a partial-config error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Emit reason (condition/preferred_label/suggested_next/unconditional/
jump/fallback), stage_status, preferred_label, suggested_next_ids,
and is_jump so logs explain why an edge was chosen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The doctor dry-run test was failing in CI because it inherited the host
environment. With no LLM API keys set, the doctor reported errors and
exited non-zero. Adding `inherit = false` to all 18 .toml test files
ensures deterministic behavior regardless of the host environment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Markdown was rendered at full terminal width then indented, pushing lines
past the right edge. Now wraps to terminal_width minus indent first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When running `fabro run smoke`, the slug "smoke" was used to locate the
workflow but never persisted. If the DOT graph name diverged from the
directory name (e.g. workflows/foo/ contains digraph Bar), resolve_run
couldn't find the run by slug. Now the slug is extracted from the
workflow path, stored in the manifest, and matched in resolve_run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workflow names in manifests are PascalCase (e.g. "LegacyTool") but
users expect to type the slug (e.g. "legacy-tool"). resolve_run now
compares case-insensitively and with hyphens/underscores stripped,
so both forms work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supports raw JSONL output (pipeable to jq) and --pretty mode with
colored, formatted output showing stages, tool calls, and assistant
messages. Includes --follow, --since, and --tail filtering options.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Subcommands like cp, diff, preview, ssh, and pr previously only
accepted run ID prefixes. The new resolve_run() tries run ID prefix
first, then falls back to workflow name (most recent run), making
these commands more ergonomic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Merge core-concepts/server-mode into administration/deploy-server
- Move Comparison from Getting Started to Reference
- Move Dark Factory from Getting Started to Core Concepts
- Update all internal links to server-mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`sh --version` exits non-zero on dash (Ubuntu default), so the test
only passed on macOS where sh is bash. Use `git` instead which
reliably supports --version on all platforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add python3 to the Daytona Dockerfile so MCP integration tests can run
their test server. Update the smoke workflow to verify fmt, clippy,
cargo test, typecheck, and bun test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The JSONL listener's run_id was initialized to "" and only populated
when WorkflowRunStarted fired, but sandbox events emit before that.
Seed it with the already-generated ULID so all events carry the run_id.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set Daytona as the project-level default sandbox so workflows that don't
specify their own sandbox config run on Daytona automatically. Add a
smoke workflow that verifies the sandbox toolchain (git, rustc, bun).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Writes a starter workflow.fabro (DOT graph) and workflow.toml into the
project's workflows directory. Supports --goal flag and derives the
digraph name from the workflow name using PascalCase conversion. Also
defaults the `graph` field in workflow.toml to "workflow.fabro" so it
can be omitted from generated configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The inline merge in run_command() duplicated the structure of
apply_defaults() with shallower (inconsistent) semantics. This extracts
a proper merge_overlay method that deep-merges compound fields (vars,
hooks, mcp_servers, sandbox sub-fields) consistently with apply_defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The scale(1.25) transform pushed the O's rightmost extent to ~x=1488,
past the old viewBox width of 1455.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add project-level run defaults to fabro.toml so sandbox, LLM, hooks,
MCP servers, and other settings can be shared across workflows instead
of duplicated in each workflow.toml. Precedence: workflow.toml >
fabro.toml > cli.toml/server.toml.
- Rename `directory` → `work_dir` with backwards-compat serde alias
- Add `hooks` and `mcp_servers` to `RunDefaults` with merge logic
- Extend `ProjectConfig` with all run-defaults fields + `into_run_defaults()`
- Remove duplicate `McpServerEntry` from fabro-config (use run_config's)
- Move `hook_config` from ServerConfig into `run_defaults.hooks`
- Wire project config merge and hooks/mcp fallbacks in run_command()
- Update OpenAPI spec and regenerate TypeScript client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>