Commit graph

127 commits

Author SHA1 Message Date
brynary-fabro[bot]
dc34fb0671 fabro upgrade command (#13)
This PR adds a `fabro upgrade` command that downloads and installs new
releases from GitHub, along with a passive daily auto-check that
notifies users when a newer version is available. The upgrade flow
supports two download backends: the `gh` CLI (preferred, for auth and
rate-limit benefits) with an automatic fallback to plain HTTPS via
`reqwest` when `gh` is missing or not authenticated. The command
includes SHA256 checksum verification, atomic binary replacement with
rollback on failure, downgrade protection with interactive confirmation,
and `--dry-run`/`--force` flags.

A background upgrade check runs automatically on common commands (`run`,
`exec`, `init`, `install`), caching results in
`~/.fabro/last_upgrade_check.json` to avoid hitting GitHub more than
once per 24 hours. Users can disable this via `upgrade_check = false` in
`~/.fabro/cli.toml` or the `--no-upgrade-check` global flag. The check
is spawned as an async task and its notice prints to stderr after the
main command completes, ensuring it never blocks or breaks normal
operation—all errors are silently swallowed.

The implementation follows a test-first approach with unit tests
covering platform detection, version parsing, SHA256 verification,
upgrade check state serialization/staleness, and the new `upgrade_check`
config field. Dependencies `tempfile` (promoted from dev-dependencies)
and `sha2` are added to `fabro-cli`.

### Fabro Details

<details>
<summary>Ran 7 stages in 18m 39s for $5.61</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.92 | 0 |
| simplify | 0s | $2.68 | 0 |
| verify | 0s | – | 0 |
| **Total** | **18m 39s** | **$5.61** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; model: claude-opus-4-6;}
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-15 19:54:57 -04:00
Bryan Helmkamp
b1ab7bbe66 Cargo.lock 2026-03-15 18:49:46 -04:00
dependabot[bot]
5475926f8d Bump quinn-proto from 0.11.13 to 0.11.14 (#1)
Bumps [quinn-proto](https://github.com/quinn-rs/quinn) from 0.11.13 to
0.11.14.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/quinn-rs/quinn/releases">quinn-proto's
releases</a>.</em></p>
<blockquote>
<h2>quinn-proto 0.11.14</h2>
<p><a href="https://github.com/jxs"><code>@​jxs</code></a> reported a
denial of service issue in quinn-proto 5 days ago:</p>
<ul>
<li><a
href="https://github.com/quinn-rs/quinn/security/advisories/GHSA-6xvm-j4wr-6v98">https://github.com/quinn-rs/quinn/security/advisories/GHSA-6xvm-j4wr-6v98</a></li>
</ul>
<p>We coordinated with them to release this version to patch the issue.
Unfortunately the maintainers missed these issues during code review and
we did not have enough fuzzing coverage -- we regret the oversight and
have added an additional fuzzing target.</p>
<p>Organizations that want to participate in coordinated disclosure can
contact us privately to discuss terms.</p>
<h2>What's Changed</h2>
<ul>
<li>Fix over-permissive proto dependency edge by <a
href="https://github.com/Ralith"><code>@​Ralith</code></a> in <a
href="https://redirect.github.com/quinn-rs/quinn/pull/2385">quinn-rs/quinn#2385</a></li>
<li>0.11.x: avoid unwrapping VarInt decoding during parameter parsing by
<a href="https://github.com/djc"><code>@​djc</code></a> in <a
href="https://redirect.github.com/quinn-rs/quinn/pull/2559">quinn-rs/quinn#2559</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2c315aa7f9"><code>2c315aa</code></a>
proto: bump version to 0.11.14</li>
<li><a
href="8ad47f431e"><code>8ad47f4</code></a>
Use newer rustls-pki-types PEM parser API</li>
<li><a
href="c81c0289ab"><code>c81c028</code></a>
ci: fix workflow syntax</li>
<li><a
href="0050172969"><code>0050172</code></a>
ci: pin wasm-bindgen-cli version</li>
<li><a
href="8a6f82c58d"><code>8a6f82c</code></a>
Take semver-compatible dependency updates</li>
<li><a
href="e52db4ad8d"><code>e52db4a</code></a>
Apply suggestions from clippy 1.91</li>
<li><a
href="6df7275c58"><code>6df7275</code></a>
chore: Fix <code>unnecessary_unwrap</code> clippy</li>
<li><a
href="c8eefa07e0"><code>c8eefa0</code></a>
proto: avoid unwrapping varint decoding during parameters parsing</li>
<li><a
href="9723a97775"><code>9723a97</code></a>
fuzz: add fuzzing target for parsing transport parameters</li>
<li><a
href="eaf0ef3025"><code>eaf0ef3</code></a>
Fix over-permissive proto dependency edge (<a
href="https://redirect.github.com/quinn-rs/quinn/issues/2385">#2385</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.13...quinn-proto-0.11.14">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=quinn-proto&package-manager=cargo&previous-version=0.11.13&new-version=0.11.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/fabro-sh/fabro/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-15 17:41:32 -04:00
arc-1e68f1[bot]
a199c9cbd1 Plan: Add prevent_idle_sleep to fabro via fabro-beastie (#5)
This PR introduces `fabro-beastie`, a new cross-platform idle sleep
prevention crate (named after Beastie Boys — *No Sleep Till Brooklyn*),
and wires it into `fabro-cli` behind an opt-in `sleep_inhibitor` Cargo
feature. Long-running `fabro run` and `fabro exec` commands can be
killed by OS idle sleep, so when the feature is compiled in and
`prevent_idle_sleep = true` is set in `cli.toml`, an RAII guard keeps
the system awake for the duration of the command.

The `fabro-beastie` crate provides platform-specific backends: on macOS
it uses IOKit power assertions (`PreventUserIdleSystemSleep`), on Linux
it spawns `systemd-inhibit` (with `gnome-session-inhibit` as fallback)
and sets `PR_SET_PDEATHSIG` to prevent orphaned processes. Both fall
back to a no-op dummy backend if the platform backend is unavailable.
The public API is a single `guard(bool)` function returning an
`Option<SleepInhibitorGuard>` that releases on drop.

On the integration side, a `prevent_idle_sleep` boolean field is added
to `CliConfig` (defaulting to `false`), and `cfg`-guarded sleep guards
are placed at the entry points of both the `exec` and `run` command
paths in `fabro-cli`. Since the feature is off by default, there is zero
impact on normal builds — `fabro-beastie` is only pulled in when
explicitly enabled via `--features fabro-cli/sleep_inhibitor`.

### Fabro Details

<details>
<summary>Ran 7 stages in 20m 33s for $3.24</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.26 | 1 |
| simplify | 0s | $1.98 | 0 |
| verify | 0s | – | 0 |
| **Total** | **20m 33s** | **$3.24** | **1** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; model: claude-opus-4-6;}
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-15 17:41:20 -04:00
Bryan Helmkamp
fcd229f3e9 Adopt cli-table for ANSI-aware table rendering and fix fabro ps bugs
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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
1bcb4323e6 Fix --goal-file not expanding ~ to home directory
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>
2026-03-15 14:57:38 -04:00
Bryan Helmkamp
589a98ec6e Add fabro run --detach to fork workflows as background processes
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>
2026-03-14 17:18:50 -04:00
Bryan Helmkamp
cdbfc72719 Fix fabro-util build.rs rerun-if-changed path to prevent constant recompilation
The relative path to .git/HEAD was wrong (../../ instead of ../../../),
pointing to a nonexistent file. Cargo reruns build scripts when a
rerun-if-changed target doesn't exist, causing a full workspace rebuild
on every invocation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 14:53:11 -04:00
Bryan Helmkamp
2117d31d55 Cargo.lock 2026-03-13 13:18:39 -04:00
Bryan Helmkamp
28884ae093 rename Arc to Fabro in all Rust crates, symbols, env vars, and supporting files
- Rename 20 crate directories lib/crates/arc-* → fabro-*
- Update all Cargo.toml: crate names, dep paths, feature flags, bin name
- Rename arc_server module → fabro_server in fabro-llm
- ArcError → FabroError across 30+ files
- ARC_VERSION/ARC_GIT_SHA/ARC_BUILD_DATE → FABRO_* constants
- All use/qualified paths: arc_agent:: → fabro_agent::, etc. (~1500 occurrences)
- Env vars ARC_* → FABRO_* in string literals and shell scripts
- String literals: X-Arc-Demo, arc-bot, arc@local, arc-web, arc-mcp, etc.
- Path strings: .arc/ → .fabro/, arc.toml → fabro.toml, refs/arc/ → refs/fabro/
- arc-api.yaml → fabro-api.yaml (OpenAPI spec)
- skills/arc-create-workflow → fabro-create-workflow
- trycmd fixtures: $ arc → $ fabro
- Inline snapshots (insta) updated
- CI, Docker, install.sh, scripts, CLAUDE.md, AGENTS.md
- TypeScript app: env vars, headers, JWT issuer
- Docs: page slugs, git refs, config paths, sandbox names, repo URLs
- Repo references: brynary/arc → fabro-sh/fabro

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:25:58 -04:00
Bryan Helmkamp
111b730eef feat(tracker): add Tracker trait with Linear and GitHub Projects V2 impls
- New arc-tracker crate defines the Tracker trait and shared Issue/BlockerRef types
- arc-linear: replace Issue/BlockerRef with arc-tracker re-exports, add LinearTracker struct implementing Tracker, remove free functions
- arc-github: add GitHubTracker implementing Tracker via Projects V2 GraphQL API, with lazy project node ID resolution (org → user fallback) and create_installation_access_token_for_projects helper
- 47 tests in arc-github (15 new), 24 tests in arc-linear (updated to use LinearTracker)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 23:32:03 -04:00
Bryan Helmkamp
b509277e15 Add arc-linear crate for Linear issue tracker API integration
GraphQL client with 6 public functions: execute_graphql, fetch_viewer_id,
create_comment, update_issue_state, fetch_candidate_issues, and
fetch_issues_by_ids. Includes Issue/BlockerRef domain types with manual
normalization from GraphQL JSON responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-11 22:36:33 -04:00
Bryan Helmkamp
e0b3c4cde4 Add SSH sandbox provider (arc-ssh) for user-provided hosts
Adds a generic SSH sandbox that connects to any user-provided host via
openssh, without VM lifecycle management. Supports git clone with GitHub
App credentials, configurable working directory, and arc cp reconnection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-11 22:29:51 -04:00
Bryan Helmkamp
87435aef11 Preserve ts, run_id, event field order in progress.jsonl
Enable serde_json's preserve_order feature so Map uses insertion-order
IndexMap instead of sorted BTreeMap. Extract build_event_envelope()
from the on_event closure to deduplicate the envelope construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-11 13:01:17 -04:00
Bryan Helmkamp
3c5d8ff3e7 Add indicatif to Cargo.lock for arc doctor spinner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-11 09:37:54 -04:00
Bryan Helmkamp
e8067740f7 Wire arc-devcontainer into sandbox creation flow
When `devcontainer = true` in TOML sandbox config, Arc now resolves
devcontainer.json from the repo, uses its Dockerfile for the Daytona
snapshot, runs lifecycle hooks (on_create, post_create, post_start)
in the sandbox, and merges devcontainer env vars under TOML env.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 22:54:59 -04:00
Bryan Helmkamp
11c17bffab Add CLI telemetry event tracking with command sanitization and git hashing
Activate the dormant Segment-based telemetry infrastructure by wiring
lifecycle events in main.rs. Each CLI invocation now emits a "Command Run"
or "Command Error" event with sanitized args, duration, hashed repo
identifier, and CI detection.

- Add sanitize.rs: redacts sensitive CLI args (paths, values) while
  keeping flags, booleans, and numeric patterns
- Add git.rs: MD5-hashes normalized origin remote URL so HTTPS/SSH
  variants produce the same identifier
- Extend command_name to include nested subcommands (e.g. "pr create",
  "llm prompt", "system prune", "model list")
- Restructure main/main_inner to capture timing and return command name
  alongside result for telemetry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 22:32:54 -04:00
Bryan Helmkamp
b864c1fa94 Add trycmd --help tests for each visible CLI subcommand
Covers run, validate, cp, preview, ssh, model, doctor, init, install,
pr, and system. Each test verifies --help exits 0 and produces output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 22:10:28 -04:00
Bryan Helmkamp
bd96b94acf Introduce insta snapshot testing for arc-devcontainer and arc-cli
Replace assert_eq!/contains() chains with insta::assert_snapshot! in
dockerfile unit tests (11 inline snapshots) and CLI model/help tests
(7 file-based snapshots), making test output easier to read and update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 21:09:15 -04:00
Bryan Helmkamp
f9d114c521 Rename doctor "Sandbox" to "Cloud sandbox" and remove Docker checks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 19:05:47 -04:00
Bryan Helmkamp
4a7c0e7c11 Integrate OpenAI OAuth into arc install via Codex detection
When the codex binary is found on PATH, arc install now offers browser-based
OpenAI OAuth login before the standard provider picker. Falls back to manual
API key entry if OAuth fails or user declines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 17:21:43 -04:00
Bryan Helmkamp
d26149bf33 Add arc-openai-oauth crate for OpenAI OAuth PKCE token acquisition
Standalone library implementing OpenAI's OAuth PKCE flow (browser + device)
for authenticating with ChatGPT subscriptions instead of API keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 15:31:16 -04:00
Bryan Helmkamp
4ac4fc4ea9 Add GitHub App manifest setup to arc install
Adds a temporary local HTTP server in `arc install` that handles the
GitHub App manifest registration flow, enabling standalone users to
register a GitHub App without running the arc-web server.

Also gates server-only install steps (pre-flight checks, server.toml,
secrets/certs, start servers) behind #[cfg(feature = "server")] and
removes the `gh` CLI from system dependency checks. Reloads .env before
running arc doctor so it picks up freshly written credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 14:36:13 -04:00
Bryan Helmkamp
54ecae9586 Write per-run tracing logs to cli.log inside each run directory
Adds a switchable tracing layer that writes to ~/.arc/logs/{run-dir}/cli.log,
so all diagnostic info for a run is co-located with its other artifacts instead
of buried in the shared daily log file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 09:30:00 -04:00
Bryan Helmkamp
d426eebf1a Add sandbox MCP transport for running MCP servers inside Daytona sandboxes
Adds McpTransport::Sandbox variant that starts an MCP server inside the
sandbox, waits for it to listen, gets a Daytona preview URL, and rewrites
to HTTP transport for the MCP client connection.

Key changes:
- Sandbox trait: add get_preview_url(port) for authenticated port access
- DaytonaSandbox: implement get_preview_url via Daytona SDK preview links
- McpTransport::Sandbox: new variant with command, port, env fields
- Session: resolve_sandbox_mcp_servers() starts servers and rewrites to HTTP
- Integration test: end-to-end Playwright MCP in Daytona - navigates to
  example.com and verifies "Example Domain" in accessibility snapshot

Config example:
  [mcp_servers.playwright]
  type = "sandbox"
  command = ["npx", "@playwright/mcp@latest", "--port", "3100", "--headless"]
  port = 3100

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 02:09:04 -04:00
Bryan Helmkamp
11414c15b2 Add arc-config crate and make arc-api optional via server feature flag
Centralizes config types (ServerConfig, CliConfig, expand_tilde) into a
new arc-config crate. Makes arc-api an optional dependency of arc-cli
behind a default-on "server" feature flag, so CLI-only builds skip heavy
server deps (axum, tower, hyper, sqlx, arc-db, arc-types).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:50:41 -04:00
Bryan Helmkamp
832c48ce5b Improve fatal error display with colored output and workflow suggestions
Show colored "error:" prefix with cause chain for all CLI errors. When a
workflow name isn't found in a project with arc.toml, list available
workflows and suggest the closest match using Levenshtein distance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 19:29:01 -04:00
arc-1e68f1[bot]
6cdb91e5fa arc system df (#11)
* arc(01KK9YGJ6EBM3XKQBZC23GQ88F): toolchain (success)

Arc-Run: 01KK9YGJ6EBM3XKQBZC23GQ88F
Arc-Completed: 2
Arc-Checkpoint: cf6f964b878309dc8d79a9aeea0fa9c7bd4cff8f

* arc(01KK9YGJ6EBM3XKQBZC23GQ88F): preflight_compile (success)

Arc-Run: 01KK9YGJ6EBM3XKQBZC23GQ88F
Arc-Completed: 3
Arc-Checkpoint: ff8dba601f46c90b5b37d78804e77c42bbc4e712

* arc(01KK9YGJ6EBM3XKQBZC23GQ88F): preflight_lint (success)

Arc-Run: 01KK9YGJ6EBM3XKQBZC23GQ88F
Arc-Completed: 4
Arc-Checkpoint: 0d6b2bd079847ef5be18f8e3100ed76fc846ded3

* arc(01KK9YGJ6EBM3XKQBZC23GQ88F): implement (success)

Arc-Run: 01KK9YGJ6EBM3XKQBZC23GQ88F
Arc-Completed: 5
Arc-Checkpoint: 4bd461b413389877d773ef248ecdd798bce43471

* arc(01KK9YGJ6EBM3XKQBZC23GQ88F): simplify (success)

Arc-Run: 01KK9YGJ6EBM3XKQBZC23GQ88F
Arc-Completed: 6
Arc-Checkpoint: c8ad93c70cc29c0dd5680a75c22292ca7bac874c

* arc(01KK9YGJ6EBM3XKQBZC23GQ88F): verify (success)

Arc-Run: 01KK9YGJ6EBM3XKQBZC23GQ88F
Arc-Completed: 7
Arc-Checkpoint: efb5421ea6bd57a350ba3606b1ab20e98142d68f

---------

Co-authored-by: arc <arc@local>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:15:27 -04:00
Bryan Helmkamp
3d21b0cd40 Extract arc-github crate; ExeSandbox owns credentials directly
Move GitHub App authentication (types, JWT signing, token creation,
credential resolution) into a new leaf crate `arc-github`. ExeSandbox
now owns `github_app` directly and resolves clone credentials on-demand,
eliminating the GitCredentialSandbox decorator and its wrapping code in
cli/run.rs. GitCloneParams simplified to a single clean `url` field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:24:10 -04:00
Bryan Helmkamp
aae1efab98 Add arc cp command for copying files to/from run sandboxes
Introduces sandbox reconnection via persisted SandboxRecord, adds
upload_file_from_local to the Sandbox trait, and migrates arc-sprites
shell quoting to shlex::try_quote.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:03:10 -04:00
Bryan Helmkamp
6e1b99734e Shell-quote all script-assembled arguments with shlex::try_quote
Replace ad-hoc replace('\'', "'\\''") escaping and unquoted interpolation
with a shell_quote() helper backed by shlex::try_quote to prevent command
injection via branch names, clone URLs, image names, env vars, file paths,
and grep patterns. Covers both arc-exe (SSH transport) and arc-workflows
(Daytona sandbox).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:52:22 -04:00
Bryan Helmkamp
e24d20b439 Update daytona-sdk-rust to 06033ca and apply clippy/fmt fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:13:43 -04:00
Bryan Helmkamp
7f6f356bae TOML Config Format for MCP Servers (#2)
* arc(01KK6WHD2MEDZ0JVYTHEK9HFNF): implement (success)

Arc-Run: 01KK6WHD2MEDZ0JVYTHEK9HFNF
Arc-Completed: 2
Arc-Checkpoint: 25f71de745f9001a09e566706d62a6ac2ea6f827

* arc(01KK6WHD2MEDZ0JVYTHEK9HFNF): simplify (success)

Arc-Run: 01KK6WHD2MEDZ0JVYTHEK9HFNF
Arc-Completed: 3
Arc-Checkpoint: fed4325308110df4799171944ddb5170de59044c

* Fix Rust formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: arc <arc@local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:26:46 -04:00
Bryan Helmkamp
8ee27278dd Add conformance test to detect ServerConfig ↔ OpenAPI spec drift
Recursively compares serialized ServerConfig keys against the
OpenAPI ServerConfiguration schema properties. Also fixes 7 fields
that were missing from the spec: log, checkpoint, git.slug,
git.author, git.webhooks, sandbox.exe, sandbox.env.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:44:59 -04:00
Bryan Helmkamp
d838fda004 Add GitHub webhook listener via Tailscale funnel
Adds a webhook subsystem that receives GitHub App webhooks when configured
in server.toml with [git.webhooks] strategy = "tailscale_funnel". On startup,
it binds a local HTTP listener on a random port, exposes it via `tailscale funnel`,
and patches the GitHub App webhook URL. Incoming webhooks are verified with
HMAC-SHA256 before processing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 12:37:56 -04:00
Bryan Helmkamp
916e95764c Bump daytona-sdk-rust to 03b7c06 (revert envs field)
The toolbox API does not support envs in /process/execute, so the
SDK now matches the Go SDK: accepts env in options but does not send
it. Arc uses export prepending as the workaround.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 00:25:35 -05:00
Bryan Helmkamp
8dffe80527 Bump daytona-sdk-rust and remove exec_command env workaround
The upstream SDK now passes env vars through ExecuteRequest.envs,
so we no longer need to prepend export statements to commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 00:12:50 -05:00
Bryan Helmkamp
f4b530c691 Split anonymous ID into server (UUID-on-disk) and CLI (MAC-based hash) strategies
Server is long-lived on a fixed host, so a persisted UUID at ~/.arc/.id is
appropriate. CLI runs ephemerally, so an MD5 of the MAC address avoids file
I/O and is stable per-machine. CLI falls back to ~/.arc/.id if it exists
for migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:00:44 -05:00
Bryan Helmkamp
2b8d6a40a1 Add Segment-based telemetry module to arc-util
Adds a telemetry library for product analytics with Segment integration.
Includes Track/User wire types, persistent anonymous ID (~/.arc/anonymous_id),
OS/arch/locale context, fire-and-forget sender via tokio::spawn, and
ARC_TELEMETRY env var control (off/errors/all). No CLI or server integration yet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:47:03 -05:00
Bryan Helmkamp
21f94f48de Add POST /completions endpoint with Anthropic-style SSE streaming
Adds a completions API endpoint that supports both streaming (SSE) and
non-streaming (JSON) modes, with structured output via JSON Schema.
Wires up the CLI `arc llm prompt` command to use the server when
`--mode server` is specified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
c3ec637f09 Add arc-sprites crate for Sprites (Fly.io) VM sandbox
Implements the Sandbox trait backed by the `sprite` CLI binary.
Includes 29 unit tests with mock runner and an e2e integration test
against the live Sprites service.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
73d2d7918c Add --mode standalone|server CLI support for arc models list
Commands can now delegate to a running Arc API server instead of
executing in-process. Adds ExecutionMode, ServerDefaults, and
ClientTlsConfig to cli.toml parsing with CLI flag > config > default
precedence. The models list command fetches from GET /models when in
server mode, with mTLS client certificate auth when [server.tls] is
configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:03:55 -05:00
Bryan Helmkamp
5df7e178ac Add SandboxProvider::Exe for exe.dev VM sandboxes
New `arc-exe` crate that runs agent tool operations inside ephemeral
exe.dev VMs via SSH. Uses two SSH connections: a management plane
(`ssh exe.dev`) for VM lifecycle and a data plane (`ssh vmname.exe.xyz`)
for command execution and file I/O.

Includes SshRunner trait with MockSshRunner for unit tests and
OpensshRunner for real SSH, with raw_mode for the exe.dev management
plane's custom command handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 09:18:00 -05:00
Bryan Helmkamp
4d9b0828c1 Fix ensure_cli for Daytona: rootless Node.js install, better errors
- Replace apt-get/NodeSource install (requires root) with direct Node.js
  binary download to ~/.local (works as non-root daytona user)
- Run node install + npm install in single shell so PATH persists
- Add ~/.local/bin to PATH in env file and version check
- Fall back to stdout for error details when stderr is empty (Daytona
  always returns empty stderr)
- Add e2e assertion that cli_stdout.log is written during poll
- Verified on Daytona with haiku: ensure_cli installs in 2s, full
  workflow succeeds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 02:01:47 -05:00
Bryan Helmkamp
92dc29eb6f Sessions API review fixes: add updated_at to create response, UUID session IDs, remove misleading SSE schemas
- Add updated_at to CreateSessionResponse for consistency with SessionListItem/SessionDetail
- Add format: uuid to session ID fields and parameter across the OpenAPI spec
- Remove SessionEvent discriminated union and SessionEvent* wrapper schemas that conflated
  SSE transport-level event names with JSON data payload fields
- Update demo data to use proper UUIDs instead of string IDs
- Add uuid dependency to arc-types crate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:51:50 -05:00
Bryan Helmkamp
681791db3f Improve Sessions API schema: discriminated turns, date-time formats, consistent naming
- Refactor SessionTurn into discriminated union (UserTurn, AssistantTurn, ToolTurn)
  using oneOf + discriminator so invalid states are unrepresentable
- Add format: date-time to all timestamp fields for proper codegen types
- Rename CreateSessionRequest.prompt to .content for consistency with SendMessageRequest
- Change sendSessionMessage from 200 to 202 (async processing via SSE)
- Extract inline response to SendMessageResponse schema
- Add updated_at to SessionListItem for sort-by-activity support
- Extract SessionId parameter to components/parameters (DRY)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:33:51 -05:00
Bryan Helmkamp
cf50625824 Switch Daytona git cloning from gh CLI to GitHub App Installation Access Tokens
Replace `gh auth token` with GitHub App IATs scoped to `contents: read` for
Daytona sandbox git cloning. Public repos are auto-detected and cloned without
credentials. Private repos get short-lived, repo-scoped tokens. Clear error
messages for each failure mode (app not installed, suspended, no repo access,
auth failure). Falls back gracefully when no GitHub App is configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 22:31:40 -05:00
Bryan Helmkamp
e5354c3028 Simplify arc-slack: use thiserror, remove dead params, extract helpers
- Use thiserror for SlackApiError and ConnectionError (project convention)
- Remove misleading PartialEq/Eq on DispatchAction; use matches!() in tests
- Remove unused _slack_client and _default_channel params from event loop
- Extract check_ok() helper to deduplicate 3 ok-check sites in client.rs
- Make bot_token private on SlackClient; add http() accessor
- Make SlackClient Clone; eliminate duplicate instance in e2e example
- Reuse reqwest::Client in open_socket_url instead of creating a new one
- Filter empty env vars in resolve_credentials; remove redundant is_enabled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:24:22 -05:00
Bryan Helmkamp
fb76c0ec3e Add arc-slack crate: Slack Socket Mode integration for interviewer
Implements a complete Slack integration for the interviewer system using
Socket Mode (WebSocket-based, no public URL required). Supports all five
question types: YesNo, Confirmation, MultipleChoice, MultiSelect, and
Freeform (via thread replies with @mention).

Modules: config, client, blocks, interaction, socket, dispatch,
connection, threads. 72 unit tests + e2e example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 09:16:22 -05:00
Bryan Helmkamp
97594b3ab0 Add HTTP hook executor with env var interpolation
HTTP hooks (type = "http") now actually execute instead of failing with
"no command specified". The executor POSTs the hook context as JSON,
parses HookDecision from the response, and fails open on errors.

Header values support $VAR/${VAR} interpolation gated by an
allowed_env_vars whitelist on the hook definition. Renames
CommandHookExecutor to HookExecutorImpl since it now handles both
command and HTTP hook types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:59:38 -05:00