agentsclimarketplace

Crux cockpit

Skill mehdiforoozandeh/crux/skills/crux-cockpit

A scientific-method lab notebook your AI agent drives — research as a tree of questions and falsifiable hypotheses in plain markdown, with an LLM literature wiki, a browser GUI cockpit, and autoresearch.

Install
npx -y skills add mehdiforoozandeh/crux --skill crux-cockpit

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Launch and manage the crux cockpit — the read-only browser GUI over a crux research vault (`crux serve`). This skill runs it beginning to finish: locate the vault, start the server fresh on localhost, verify it actually serves, and hand the user one clickable URL — plus status / stop / restart, and a setup-or-demo path when no vault exists yet. Works wherever the agent runs: local machine, VS Code Remote-SSH (drives the port forward itself via `code --openExternal`), or a plain SSH terminal (hands the user the exact `ssh -L` tunnel command). Use when a crux user wants to see their vault in a browser. Triggers: "open the cockpit", "launch the crux GUI", "show me the tree in a browser", "serve the vault", "is the cockpit running", "stop the cockpit", "restart the cockpit", crux gui, crux cockpit.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

16.9 KB, as published. Nobody here has run it

crux-cockpit — launch the crux GUI, beginning to finish

The cockpit is crux's read-only browser GUI: crux serve boots a stdlib HTTP server on 127.0.0.1, serves the webui/ frontend, and exposes the vault as /snapshot.json (status-colored tree — pan / zoom / collapse / search / re-orient / focus one question — plus the review queue, node detail with rendered markdown, and a hypothesis's linked report and figures; live-refreshing, zero writes). Either panel can go full-screen ([ / ], Esc restores). The engine does the serving; you (the agent) do the operating: find the vault, launch clean, verify, hand over one URL, and manage the server's lifecycle. The user should never have to touch a terminal on the machine you run on — the one thing you can't do for them is start a local ssh -L tunnel (plain-SSH contexts only; see Local vs remote).

The engine rides in the crux skill: ENGINE = <crux skill>/scaffold/crux.py — the crux skill is this skill's sibling directory in your skills dir (in a repo clone, skills/crux). If that engine file is missing, the crux skill isn't installed: install it first (npx skills add mehdiforoozandeh/crux --all, or the repo's ./install.sh) before continuing.

Ground rules (load-bearing)

  • Never report an unverified URL. Before telling the user the cockpit is up, both / and /snapshot.json must have answered 200 from your curl. A URL you haven't verified does not leave your mouth.
  • URL only — never pop a browser. Always launch with --no-open and deliver the URL as a clickable link. The user opens it when ready. One carve-out: in VS Code Remote-SSH the sanctioned way to create the port forward is an open call (code --openExternal — see the ladder below); there, opening the user's browser is the mechanism, not a discourtesy.
  • Always fresh, per vault. A launch first kills any cockpit already serving that vault — and never touches cockpits serving other vaults (several can coexist on different ports).
  • The cockpit is read-only. No route writes; every mutation stays in the agent/CLI. Never present it as an editor.
  • Surface engine-drift warnings. If the launch log warns that the vault was stamped by a different engine version, relay that warning to the user verbatim.

Launch protocol

1 — Locate the vault. The nearest directory at-or-above cwd containing .crux.yaml. If the user named a vault, use that. If more than one vault is plausible in the project, ask which — don't guess.

2 — No vault anywhere? Offer two paths and let the user pick:

  • Set up a real vault — hand off to the crux skill's setup interview (seed outline → approve → init --from), then come back here.
  • A disposable demo vault — so they can explore the GUI in seconds: python3 <crux skill>/scaffold/selftest.py --keep <tmpdir>/crux-demo-vault (build it outside their repo, e.g. under $TMPDIR, and say clearly that it's throwaway sample data).

3 — Kill stale servers for this vault (fresh-start rule, scoped by process cwd):

VAULT="$(cd <vault> && pwd -P)"   # -P: physical path — a symlinked route must hash/match identically
VID="$(python3 -c 'import hashlib,sys;print(hashlib.sha256(sys.argv[1].encode()).hexdigest()[:12])' "$VAULT")"
for pid in $(pgrep -f 'crux\.py (serve|gui|ui|cockpit)'); do
  cwd="$(lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p')"
  case "$cwd" in "$VAULT"|"$VAULT"/*) kill "$pid" ;; esac
done

(The pgrep pattern can also match a wrapper shell — e.g. a zsh -c … crux.py serve … parent. Its cwd is the same vault, so the loop kills it too; that's correct, it dies with its child. kill is asynchronous: if you're about to relaunch on a pinned port, first wait until lsof -nP -iTCP:<port> -sTCP:LISTEN returns nothing — a still-dying listener makes the pinned relaunch fail with "cannot bind".)

(No lsof on the box? Minimal cluster images sometimes lack it — and lsof: command not found exits non-zero, which reads exactly like "no match / port free". Never let a missing tool pass a check. Stdlib substitutes: a port is free iff python3 -c 'import socket;socket.socket().bind(("127.0.0.1",<port>))' succeeds, and on Linux a PID's cwd is readlink /proc/<pid>/cwd.)

4 — Launch, backgrounded, log captured. Run from inside the vault so the engine resolves it; never block your shell on the server. Remote? Pin first — if the session classifies as any remote per Local vs remote (SSH markers, or the tunnel-remote probes: $CODESPACES / $REMOTE_CONTAINERS / /.dockerenv), derive $PORT from the pin block below before launching, and append --port "$PORT" to this line:

LOG="${TMPDIR:-/tmp}/crux-cockpit-$VID.log"
cd "$VAULT" && nohup python3 "$ENGINE" serve --no-open >"$LOG" 2>&1 &   # remote: + --port "$PORT"

(If your harness has a native run-in-background facility, prefer it — same command, same captured log. And if shell state doesn't persist between your commands, re-derive $VAULT/$LOG/$URL in each call rather than assuming the variables survive.)

Running remotely? (any remote classification — SSH-marked or tunnel-based; see Local vs remote below.) Pin the port with --port, and make the pin outlive your session — an auto-picked port drifts on an always-fresh relaunch (8787 → 8789), silently breaking a tunnel or forward the user already has open, and "remember it in conversation" dies with the session:

PIN="$HOME/.cache/crux-cockpit/$VID.port"   # $VID from step 3
mkdir -p "${PIN%/*}"
PORT="$(cat "$PIN" 2>/dev/null)"
if [ -z "$PORT" ]; then
  PORT=8787
  while lsof -nP -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; do PORT=$((PORT+1)); done
  echo "$PORT" >"$PIN"
fi

Launch with --port "$PORT". If a recorded pin is busy at launch even after the step-3 kill loop, a foreign process took it: re-pick, update the pin file, and tell the user their tunnel/forward target changed. Truly local launches keep auto-pick — no pin file.

5 — Parse the URL from the banner. The server prints exactly one line carrying it (crux cockpit (read-only) → http://localhost:<port>; port auto-picked from 8787, or pin one with --port). Poll the log rather than racing it:

for i in 1 2 3 4 5 6 7 8 9 10; do
  URL="$(grep -o 'http://localhost:[0-9]*' "$LOG" | head -1)"
  [ -n "$URL" ] && break; sleep 0.5
done

(No URL after ~5s → the server died on startup; see Failure modes.)

6 — Verify before reporting. Both routes, from your own shell:

curl -fsS "$URL/" >/dev/null
curl -fsS "$URL/snapshot.json" | python3 -c \
  'import json,sys; d=json.load(sys.stdin); print(d["project"]["title"], "·", len(d["nodes"]), "nodes")'

If either fails: read $LOG, diagnose, fix, re-verify. Do not hand over the URL on a failed check.

7 — Report. One message: the clickable URL, which vault it serves (project title + node count from step 6), that it's read-only, and how to end it ("say stop the cockpit when you're done"). First classify the context (next section): on a local machine the URL is the whole story; in any remote context your curl proved the server is up on the remote — the report must also carry whatever gets the user's local browser to it.

Local vs remote — where the browser lives

The server always binds 127.0.0.1 on the machine you're running on. Step 6 proves it's serving there; whether the user's browser can reach it is a separate question. Classify from your environment before reporting:

SSH markers = any of $SSH_CONNECTION, $SSH_CLIENT, $SSH_TTY. VS Code markers = $TERM_PROGRAM = vscode or $VSCODE_IPC_HOOK_CLI set.

your envcontextwhat reaches the user
none of the SSH markers setlocal — your machine is their machine (after ruling out the tunnel-based remotes below)the URL, as-is
SSH markers plus VS Code markersVS Code Remote-SSHsame URL, once the port is forwarded — you drive the forward (below)
SSH markers, no VS Code markersplain SSH terminalnothing yet — they must open a tunnel; you hand them the command
  • Local — report the URL; done.

  • VS Code Remote-SSHcreate the forward yourself; never rely on auto-forwarding. It degrades silently, especially on shared hosts (HPC login nodes): once >20 ports have ever auto-forwarded, VS Code flips remote.autoForwardPortsSource to hybrid and stops detecting; ports already listening when VS Code connected are never auto-forwarded; and your launch writes the banner to a log file, so the output-watcher never sees the URL. The ladder, in order:

    1. Drive it from the remote CLI (zero user action):

      [ -n "$VSCODE_IPC_HOOK_CLI" ] && command -v code >/dev/null && code --openExternal "$URL"
      

      This asks the VS Code client to open the URL; the client creates the port forward itself and opens the user's local default browser — it works even with auto-forwarding disabled. ("$BROWSER" "$URL" is the same mechanism — VS Code points $BROWSER at a helper that calls --openExternal.) Tell the user what happened: "your browser just opened the cockpit; the port is forwarded." You cannot confirm the forward from the remote side, so ask once — "did it open?". A failed guard counts the same as a "no". VS Code forks (Cursor, Windsurf, …) set the same env markers and ship the same remote CLI under their own binary name — if code isn't on PATH, try the fork's name (cursor, windsurf) with the same --openExternal flag before leaving this rung.

    2. Cmd/Ctrl-click in the integrated terminal. First echo "$URL" in the terminal so a terminal-rendered link exists — a localhost URL clicked in the terminal forwards on the fly (remote.forwardOnOpen, default on). A URL clicked in a chat/webview panel does NOT forward — it opens the local browser on an unforwarded port and fails. Say this explicitly; it is the classic trap.

    3. Manual forward: Ports panel (the tab next to TERMINAL — or Cmd/Ctrl+Shift+P → Ports: Focus on Ports View) → Forward a Port<port>.

    4. The filled-in ssh -L one-liner (SSH remotes only — skip this rung in tunnel-based remotes, which have no SSH endpoint; there, rung 3 is the last resort).

    Move down a rung whenever the current rung's guard fails or the user says it didn't work — and deliver rungs 2–4 together in one message, not one at a time.

  • Plain SSH — the tunnel must be started from their local machine; you cannot create it from the remote side. Give them the exact command, filled in:

    ssh -L <port>:localhost:<port> <user>@<host>    # run on your LOCAL machine; keep it open
    

    then the same http://localhost:<port> works in their local browser. Fill <user> from whoami and <host> from hostname -f; if that's not the name they actually SSH to (common on clusters), field 3 of $SSH_CONNECTION is the server IP — or simply ask "what do you type to connect here?". But when the typed name ≠ this machine, don't put it after -L. Two cluster cases: you're on a compute node behind a login node, or the typed name is a round-robin alias (cedar.computecanada.ca-style) that may land the tunnel on a different login node than the one running the server. Either way, jump through the typed name and pin the far end to this host: ssh -J <typed-name> -L <port>:localhost:<port> $(hostname -f). Two things to say out loud: run it locally, and keep that terminal open while browsing. (serve's own banner prints a VS Code-flavoured forwarding hint even here — your tunnel instructions supersede it; don't relay it.)

In any remote context, word the report so verified and reachable aren't conflated: "serving on the remote and verified there; it opens in your browser once the forward/tunnel is up."

Tunnel-based remotes: VS Code markers, no SSH markers. Not every remote arrives over SSH — GitHub Codespaces, VS Code Tunnels, and Dev Containers set the VS Code markers but no SSH_* at all, and localhost-as-is does not reach the user there. Rule them out before calling a session local: $CODESPACES = true (Codespaces), $REMOTE_CONTAINERS = true or /.dockerenv exists (a container; podman writes /run/.containerenv instead). Any of those → use the VS Code ladder: rung 1 (code --openExternal) works over tunnels exactly as over SSH; there is no ssh -L rung, so the Ports panel is the true last resort. WSL ($WSL_DISTRO_NAME set) is the benign case — WSL2 forwards localhost to Windows by default, so the URL usually works as-is; if it doesn't, same ladder.

When the markers lie. tmux/screen can strip or preserve stale env ($SSH_* gone, or a $VSCODE_IPC_HOOK_CLI left over from an earlier VS Code session); mosh sets no SSH vars at all. Concrete probes when the table's answer smells wrong: $SSH_CLIENT often survives where $SSH_CONNECTION didn't; $TMUX set means the env may predate the current connection; who am i shows the connecting host for SSH logins. When still unsure, ask one question — "are you SSH'd into this machine?" — and classify from the answer. And because a stale VS Code marker misroutes a plain-SSH user, every SSH-remote report includes the filled-in ssh -L one-liner as the last-resort fallback, even in VS Code mode. (Tunnel-based remotes are the exception — no SSH endpoint exists, so omit it and make the Ports panel the last resort there.)

Other editors and agents — nothing above may leak into the local flow. Everything remote-specific (pin files, the forwarding ladder, tunnel commands, opening a browser) is gated on the env markers. In their absence — Claude Code desktop, Codex, Cursor or Copilot on the user's own machine, any plain local terminal — the flow is exactly the local one: auto-picked port, no pin file, report the verified URL, never open a browser. VS Code forks over SSH (Cursor, Windsurf) set the same markers and carry the same remote-CLI mechanism under their own binary name; each ladder rung degrades to the next if a fork lacks a piece.

Status · stop · restart

  • Status ("is the cockpit running?") — enumerate pgrep -f 'crux\.py (serve|gui|ui|cockpit)'; for each PID report its vault (cwd via lsof -a -p <pid> -d cwd -Fn) and port (lsof -a -p <pid> -iTCP -sTCP:LISTEN -P), and confirm the URL still answers before calling it alive. Count only PIDs that actually hold a LISTEN port — a wrapper shell can match the pattern while listening on nothing. No servers → say so plainly.
  • Stop ("stop/close the cockpit") — kill that vault's PID(s) (step-3 loop), then confirm: process gone (kill -0 <pid> fails) and port freed (lsof -nP -iTCP:<port> -sTCP:LISTEN returns nothing). Report it stopped. If servers for several vaults are running and the user didn't say which, ask.
  • Restart — just run the launch protocol; the fresh-start rule is the restart.

Failure modes worth knowing

  • Banner never appears / process exits immediately → read $LOG: a crux: error: line means the vault didn't resolve (wrong dir) or a pinned --port was busy. Fix and relaunch.
  • URL answers but /snapshot.json 500s → the vault is structurally broken; run python3 "$ENGINE" validate in the vault and surface the findings.
  • Zombie from a crashed session holding a port → the step-3 kill loop clears it; on local (auto-pick) launches a survivor you shouldn't kill (another vault's server) never blocks a new launch. On pinned remote launches it does block — that's the step-4 re-pick rule: new port, update the pin file, tell the user the forward/tunnel target changed.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.