Skip to content
Back to skills

Develop

DSecurity

Run Chimaera locally and iterate on it — the daemon + Vite web-UI dev loop, ports, dev-mode auth, the build-embeds-UI gotcha, the isolated per-worktree daemon (chimaerad-isolated), and where things live. Use when starting to develop or run the app, when a change needs the real daemon/UI to see, when working in a git worktree or alongside another daemon, or when the dev server won't come up (port in use, Node version, blank UI).

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 29, 2026
ai-agentspythonrustgoshellbashnodegitapi

Works with

  • terminal
  • cli
  • api

Security analysis

D50/100
  • criticalAccesses sensitive system or user directories
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study

Pro scans all 3 files and shows the line behind each finding

Scanned October 7, 2026

npx -y skills add martinappberg/chimaera --skill develop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Develop?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Develop
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/martinappberg-develop-chimaera/badge)](https://www.skillsdirectory.com/skills/martinappberg-develop-chimaera)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: develop
description: Run Chimaera locally and iterate on it — the daemon + Vite web-UI dev loop, ports, dev-mode auth, the build-embeds-UI gotcha, the isolated per-worktree daemon (chimaerad-isolated), and where things live. Use when starting to develop or run the app, when a change needs the real daemon/UI to see, when working in a git worktree or alongside another daemon, or when the dev server won't come up (port in use, Node version, blank UI).
---

# Developing Chimaera locally

Chimaera is a Rust daemon (`crates/`) that embeds and serves a Svelte web UI
(`web-ui/`), plus a standalone Tauri app (`crates/chimaera-app`). See
[AGENTS.md](../../../AGENTS.md) for the repo map; this skill is the run loop.

## The dev loop (do this to develop the UI + daemon)

Run the daemon and the Vite dev server side by side, then work against
`http://localhost:5173`. The launch configs are in
[`.claude/launch.json`](../../launch.json):

- **chimaerad** → `cargo run -p chimaera -- serve --port 9700`
- **web-ui** → Vite on 5173, proxying `/api`, `/ws`, `/raw` to the daemon on 9700

Prefer the preview tooling to start them (`preview_start` with the `chimaerad`
then `web-ui` config), or `just`:

```sh
just dev-ui        # Vite dev server (assumes a daemon is already running)
# in another shell:
cargo run -p chimaera -- serve --port 9700
```

**Auth depends on which daemon Vite targets.** Its dev-only `/dev/manifest`
middleware reads `~/.chimaera/manifest.json`; an unstamped debug daemon instead
uses `~/.chimaera-dev` by default. Open Vite with the debug daemon's printed
`#token=…` fragment to authenticate. `CHIMAERA_DEV_TARGET=http://127.0.0.1:<port>`
changes the proxy target (see `web-ui-sandbox`), but does not change that manifest
path. The isolated served-UI loop below avoids this mismatch.

## Isolated per-worktree run (coding agents — use this in a worktree)

The default `chimaerad` / `web-ui` configs hardcode ports 9700 / 5173. Unstamped
Rust builds also share the default `~/.chimaera-dev` state dir. A second worktree
can collide on ports or state. `serve` writes its manifest at startup and removes
its owned record at shutdown; separate worktrees should use separate state dirs. When another
daemon may be up, run **`chimaerad-isolated`** instead — a self-contained
daemon just for this worktree:

```
preview_start  →  config "chimaerad-isolated"
```

It runs [`serve-isolated.sh`](serve-isolated.sh), which sets
`CHIMAERA_HOME=<worktree>/.chimaera-dev` (its own manifest/workspaces/recents,
gitignored) and takes an **auto-assigned free port** (`autoPort`, via `$PORT`).
`CHIMAERA_HOME` moves only the daemon's bookkeeping — spawned shells/agents keep
the real `$HOME`, so `~/.claude` auth still works. No Vite, so no 5173 clash and
no Node needed at run time.

**One-time build first** (the script fails fast if any is missing):

```sh
nvm use 22 && npm --prefix web-ui ci \
  && npm --prefix web-ui run build            # Node version is pinned in .nvmrc
cargo build -p chimaera                       # rust-embed requires web-ui/dist to exist first
```

No plugin build: the daemon embeds only `plugins/plugins.lock`, never a
plugin's bytes, so a fresh debug daemon has **no plugins** until you install
one — from the Extensions tab, with `chimaera plugin add <id>` (a first-party
plugin at the lock's version), or with `chimaera plugin add --path <dir>` for a
local build (a directory holding `plugin.wasm` and `plugin.toml`; the same
version again replaces it, and a running session's next `tools/list` is the new
build). From a shell, point the CLI at this worktree's isolated daemon with
`CHIMAERA_HOME="$PWD/.chimaera-dev" target/debug/chimaera plugin …`.
`bash scripts/build-plugins.sh` is for the tests only (`just check` runs it):
it builds the fixture and downloads the locked releases into
`plugins/dist-test` (see [plugins/AGENTS.md](../../../plugins/AGENTS.md)).

Then `preview_start chimaerad-isolated`, read the printed
`http://127.0.0.1:<port>/#token=…` from `preview_logs`, and navigate the preview
there (serve mode has no `/dev/manifest` auto-auth — the token rides the URL
fragment). A **debug** daemon reads `web-ui/dist` from disk per request, so after
a UI change just rebuild the UI and reload the page — no daemon restart.
Plugin installs take effect at once, with no restart either.

If `preview_start` is unavailable, run the same launcher in a managed terminal:

```sh
bash .claude/skills/develop/serve-isolated.sh
```

It chooses a free port when `PORT` is unset. Read the token URL from its output
and open it with the available browser tooling. Keep the terminal attached, or
redirect stdout and stderr to a file when starting a background process. Use that
terminal or file for daemon logs; browser inspection depends on the tools exposed
in your session.

To reset this worktree's isolated daemon state, stop its daemon first, then delete
`.chimaera-dev/`.

## Running the built daemon (production-like)

The daemon **embeds `web-ui/dist` at compile time** (rust-embed). So a non-dev
run needs the UI built first — otherwise you serve a stale or empty bundle.
In a **fresh worktree with no `web-ui/dist` at all**, `cargo clippy` / `just check`
don't even compile `chimaera-server` (`Assets::get` not found — the derive over a
missing folder): build the UI once, or copy a built `dist/` in from another checkout.

```sh
just serve         # builds web-ui/dist, then `cargo run -p chimaera -- serve`
# equivalently:
npm --prefix web-ui run build && cargo run -p chimaera -- serve
```

`serve` prints the UI URL and a bearer token. `chimaera status` / `chimaera kill`
inspect and stop a running local daemon.

## Native app

Its own cargo workspace — never built by the root `cargo` commands.

```sh
just app-dev            # cd crates/chimaera-app && cargo run  (builds the UI first)
just app-dev-isolated   # the app on an ISOLATED state dir — use this in a worktree / alongside your real app
just app-build          # bundle the .app/.dmg
```

**Isolated app — the app counterpart of `chimaerad-isolated`.** Unstamped
`app-dev` builds use `~/.chimaera-dev`, separate from the released app's
`~/.chimaera`, but two dev worktrees still share state and app identity. `app-dev-isolated` runs
[`run-app-isolated.sh`](run-app-isolated.sh), which sets `CHIMAERA_HOME=
~/.chimaera-dev-app/<worktree-key>` before launching the built binary. The key
includes a stable checksum of the absolute worktree path because linked
worktrees commonly all end in `chimaera`. Because the
app, the `--daemon` it spawns (a free port, THIS worktree's build), and the
shells that daemon spawns all inherit `CHIMAERA_HOME`, the whole stack is isolated
— nothing lands in the shared `~/.chimaera` — yet still per-worktree.

The launcher also runs a generated `chimaera-dev` identity rather than
`chimaera` (on macOS, a small `target/debug/chimaera-dev.app` wrapper with its
own bundle id; elsewhere, a `target/debug/chimaera-dev` hard link). It also
builds with a per-worktree Tauri identifier: the single-instance plugin keys
its socket/service from compiled configuration, not wrapper metadata. This keeps
the isolated GUI process distinct in Activity Monitor and Computer Use while
the released app is open; for live automation, target **`chimaera-dev`**, never
the user's `chimaera`.

**Why `~/…` and not `<worktree>/.chimaera-dev-app`:** the state dir holds unix
sockets (the askpass relay, the ssh ControlMaster) whose *full path* must fit the
~104-byte `sun_path` limit. A CHIMAERA_HOME inside the deep worktree path
overshoots it — the socket binds fail (askpass can't reach the app, so ssh auth
dies; ssh mux fails with "ControlPath too long"). Anchoring under a short `~`
base keeps every socket path legal.

It opens on an empty home (no saved workspaces/hosts), which is how you tell it
apart from your real window. Use it to exercise the full binary end to end — the
native clipboard command, the reauth overlay, the daemon changes — which the
browser preview can't reach. A debug daemon reads `web-ui/dist` from disk, so
after a UI change rebuild the UI and reload the window; no app restart.
(For a REMOTE flow, pair it with a **dev host** — the next section — so the
host side is isolated too.)

## Remote (HPC / dev server)

```sh
just dist                       # one-time: static musl binaries into ~/.chimaera/dist
cargo run -p chimaera -- connect <host>   # install-if-missing, start-or-attach, tunnel, open UI
```

`connect` shells out to system `ssh`, inheriting `~/.ssh/config`.

## Remote isolated dev daemon (dev is dev — no toggle)

Dev-ness is the BUILD's property: a dev build (never release-stamped —
`chimaera_core::is_dev_build()`, the `0.0.1` sentinel) targets
`~/.chimaera-dev` on BOTH ends, always. Locally, a dev build with no
`CHIMAERA_HOME` defaults its own state to `~/.chimaera-dev` (the isolated-rig
script still overrides with its per-worktree home); remotely, every `connect`
from a dev build runs against the host's `~/.chimaera-dev`. A release always
targets `~/.chimaera`. There is no flag, no per-host toggle — neither world
can reach the other's daemon:

```sh
just dist                                  # musl-build THIS worktree into ~/.chimaera/dist
cargo run -p chimaera -- connect <host>    # dev build ⇒ deploy + start under ~/.chimaera-dev
cargo run -p chimaera -- status <host>     # dev build ⇒ the dev daemon's manifest/port/pid/build
```

The whole remote side effect is scoped to `~/.chimaera-dev` on the host: the
binary (`~/.chimaera-dev/bin/chimaera`), the daemon (started under
`CHIMAERA_HOME=~/.chimaera-dev`, so its manifest and state live in
`~/.chimaera-dev/data/`), and the probe/reuse/replace decision. The real daemon
is never probed, stopped, or replaced — the two run side by side. A dev connect
never downloads a release: no local build (`just dist` stash or `--binary`) is
a hard error, so a release binary can't silently impersonate your build. The
same principle runs the other way: a dev app never OFFERS a release update
(`check_app_update` reports none) — an "update" would swap the build under
test.

In the native app every host row wears the amber `dev` pill (the build is dev,
so every connection is). The isolated app (`just app-dev-isolated`) + any host
is the full end-to-end rig — your app build tunneling to your daemon build on
a real cluster — which is how you exercise remote-only paths like OSC 52
clipboard from a remote TUI or the reauth overlay on a tunnel drop.

Gotchas:

- **Same-commit rebuilds look identical**: build ids compare the git hash, so
  a rebuilt (even dirty) tree won't auto-replace a running dev daemon. Force it
  with `--update-daemon` / the row's "update" action.
- **A dev build cannot attach to the real `~/.chimaera` daemon at all** (and a
  release can't attach to a dev one). To exercise the real daemon, use a
  release build.
- **Old hosts.json entries with a `"dev"` key** (from the toggle era) still
  parse; the key is ignored.

## Handing the human a live app to test (the flow to prompt them with)

When you finish a change and want **the human** to test it in the real app
(not your headless preview), give them a running isolated app — and if they want
to test a **remote host**, stage the cross-built daemon first so their in-app
connect Just Works. This is the flow to walk them through.

**1. Launch the isolated native app** (a real window, isolated state, won't touch
their real `~/.chimaera` app). `just app-dev-isolated`, or when `just` is absent,
its two steps — the last one **in the background** (it's a long-running GUI):

```sh
nvm use 22 && npm --prefix web-ui run build          # UI is embedded/served from dist
bash .claude/skills/develop/run-app-isolated.sh      # builds + runs in background — opens the window
```

State lives under `CHIMAERA_HOME=~/.chimaera-dev-app/<worktree-key>`; the script
scrubs `CLAUDE*`/`ANTHROPIC*` env (else the app's spawned agents die on start)
and launches the generated `chimaera-dev` app identity. Computer Use must
target `chimaera-dev`, which prevents it from driving the user's released app.
Then tell the human: open a folder → start an agent → here's what to click.

On macOS the script copies the built binary into the bundle (codesign rewrites
the file it signs, so a hard link would alter the Cargo binary), records the
isolated `CHIMAERA_HOME` and `PATH` in the bundle's `LSEnvironment` so a Dock or
Finder relaunch stays isolated, and fails if `codesign --verify --deep --strict`
does not pass. For a hand-made preview bundle, keep the real built Mach-O as
`CFBundleExecutable` (no shell or Python launcher in its place) and sign only the
finished bundle: an unsealed bundle still opens a window but silently loses OS
features such as notifications and Keychain access. Never change Keychain ACLs
to get past a prompt; ad-hoc builds change identity on every build.

**2. To test against a REMOTE host from that isolated app** (e.g. an HPC cluster): a
dev connect deploys **your** build, never a release, so a musl daemon of this
branch must exist where THIS app looks. The trap: the in-app hint says
`~/.chimaera/dist`, but an **isolated** app reads its own
`$CHIMAERA_HOME/data/dist` — cross-build and stage there by the exact
`chimaera-<arch>-linux-musl` name (`dist_name`, `chimaera-remote`):

```sh
# needs zig + cargo-zigbuild; arch = the host's (most clusters are x86_64)
cargo zigbuild --release --target x86_64-unknown-linux-musl -p chimaera
HOME_DIR="$(bash .claude/skills/develop/run-app-isolated.sh --print-home)"
mkdir -p "$HOME_DIR/data/dist"
cp target/x86_64-unknown-linux-musl/release/chimaera \
   "$HOME_DIR/data/dist/chimaera-x86_64-linux-musl"
```

Then tell the human: **click the host row (it wears the amber `dev` pill) → connect.**
The app deploys your build to the host's `~/.chimaera-dev`, starts the remote
daemon, tunnels, and opens the remote workspace; an ssh password/Duo prompt
surfaces in the app's askpass overlay. Now they can exercise remote-only paths
(uploads streaming to the host, remote terminal links, reconnect/reauth).

## Which build is actually running? (debug workflow)

- **Health = ground truth.** Read `port` + `token` from the manifest —
  `$CHIMAERA_HOME/data/manifest.json` for an isolated daemon/app,
  `~/.chimaera-dev/data/manifest.json` for a default unstamped dev build, or
  `~/.chimaera/manifest.json` for a release — then
  query `/api/v1/health`. Keep the token out of process arguments by passing
  curl configuration on stdin (replace the manifest path for your daemon):

  ```sh
  jq -r '"url = \"http://127.0.0.1:\(.port)/api/v1/health\"\nheader = \"Authorization: Bearer \(.token)\""' \
    .chimaera-dev/data/manifest.json | curl --silent --show-error --fail --config -
  ```

  Its `build` must match your HEAD (`git rev-parse --short HEAD`, `-dirty` if
  the tree is). Run the same check against the app's TUNNEL port to verify a
  remote dev daemon end to end; `chimaera status <host>` (from the dev build) asks the host
  directly.
- **Logs.** The app logs to the terminal that launched it; its local daemon to
  `$CHIMAERA_HOME/data/logs/serve.log`; a remote dev daemon to
  `~/.chimaera-dev/data/logs/serve.log` on the host (real: `~/.chimaera/logs/`).
- **Sockets are health.** While the app runs, its askpass relay socket must
  EXIST — `$CHIMAERA_HOME/run/askpass.sock` (missing = the bind failed and ssh
  auth will die promptless). For a too-deep home, both it and the ssh
  ControlMaster fall back to a short `/tmp/chimaera-<hash>/` dir to stay under
  the ~104-byte `sun_path` limit — the app's "askpass ready on …" log line
  says where it actually bound.
- **Build skew is by design, not an error:** connect Reuses a matching build,
  Updates a mismatched idle daemon, and attaches "outdated" to a mismatched
  busy one (the row/CLI then offer the explicit update).

## Common gotchas

- **Blank UI on a built daemon** → you didn't rebuild `web-ui/dist`. Use `just serve`.
- **Port already in use** → a daemon is still running; `chimaera kill` or pick another `--port`.
- **UI change not showing** → in the dev loop Vite HMRs; a built daemon won't — rebuild the UI.
- **Editing the app doesn't affect the daemon** and vice versa — separate workspaces.

## Before you claim it works

Verify against the real thing, not just `cargo test`. See the **verify-app** skill.

Files in this skill

  • SKILL.md14.4 KB
  • run-app-isolated.sh6.2 KB
  • serve-isolated.sh1.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.