diff --git a/.cursor/environment.json b/.cursor/environment.json index 7d9a146..6136199 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -1,5 +1,5 @@ { - "snapshot": "bld-20260805-abcb8552-a57a-438f-a10e-1f7408849ceb", - "install": "bash scripts/ensure-rad-remote.sh", - "start": "bash scripts/ensure-rad-remote.sh" + "name": "sleek-nix", + "install": "bash .cursor/install.sh", + "start": "bash .cursor/install.sh ensure-nix" } diff --git a/.cursor/install.sh b/.cursor/install.sh new file mode 100755 index 0000000..889421e --- /dev/null +++ b/.cursor/install.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# Cursor Cloud install — idempotent; safe to re-run on cached Builds. +# +# environment.json: +# install → bash .cursor/install.sh +# start → bash .cursor/install.sh ensure-nix +# +# Full install bakes Nix, flake deps, sibling path crates, and a host cargo +# build into the snapshot. `ensure-nix` is the per-pod boot path (daemon + rad). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +SOCKET="/nix/var/nix/daemon-socket/socket" + +log() { + echo "sleek-install: $*" >&2 +} + +run_root() { + if [[ "$(id -u)" -eq 0 ]]; then + "$@" + else + sudo "$@" + fi +} + +load_nix_env() { + # shellcheck disable=SC1091 + if [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]]; then + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + elif [[ -f "$HOME/.nix-profile/etc/profile.d/nix.sh" ]]; then + . "$HOME/.nix-profile/etc/profile.d/nix.sh" + fi + export PATH="/nix/var/nix/profiles/default/bin:${HOME}/.nix-profile/bin:${PATH}" + if nix_daemon_ping; then + export NIX_REMOTE=daemon + else + unset NIX_REMOTE || true + fi +} + +nix_daemon_ping() { + command -v nix >/dev/null 2>&1 || return 1 + [[ -S "$SOCKET" ]] || return 1 + NIX_REMOTE=daemon nix store ping --store daemon >/dev/null 2>&1 +} + +nix_store_ok() { + command -v nix >/dev/null 2>&1 || return 1 + if nix_daemon_ping; then + export NIX_REMOTE=daemon + return 0 + fi + unset NIX_REMOTE || true + nix store ping --store local >/dev/null 2>&1 && [[ -w /nix/var/nix ]] +} + +remove_stale_nix_socket() { + # --init none leaves a socket path after reboot/snapshot with no daemon. + if [[ -e "$SOCKET" ]] && ! nix_daemon_ping; then + log "removing stale nix daemon socket at $SOCKET" + run_root rm -f "$SOCKET" + unset NIX_REMOTE || true + fi +} + +start_nix_daemon() { + if nix_daemon_ping; then + export NIX_REMOTE=daemon + return 0 + fi + + remove_stale_nix_socket + + local bin="/nix/var/nix/profiles/default/bin/nix" + if [[ ! -x "$bin" ]]; then + bin="$(command -v nix || true)" + fi + [[ -n "$bin" ]] || return 1 + + run_root mkdir -p /nix/var/nix/daemon-socket + run_root chmod 755 /nix/var/nix/daemon-socket + log "starting nix daemon (no systemd)…" + run_root bash -c "setsid '$bin' daemon >>/tmp/nix-daemon.log 2>&1 < /dev/null &" || true + + local i + for i in $(seq 1 60); do + if nix_daemon_ping; then + export NIX_REMOTE=daemon + log "nix daemon is up" + return 0 + fi + sleep 0.25 + done + + log "nix daemon did not become reachable at $SOCKET" + return 1 +} + +ensure_nix_daemon_ready() { + load_nix_env + if nix_store_ok; then + return 0 + fi + start_nix_daemon || true + load_nix_env + nix_store_ok +} + +ensure_rad_remote() { + if [[ -x "$ROOT/scripts/ensure-rad-remote.sh" ]]; then + bash "$ROOT/scripts/ensure-rad-remote.sh" + fi +} + +ensure_path_deps() { + local parent dest + parent="$(cd "$ROOT/.." && pwd)" + if [[ -f "$parent/vidya/Cargo.toml" && -f "$parent/freeq/freeq-sdk/Cargo.toml" ]]; then + log "sibling path deps present ($parent/vidya, $parent/freeq)" + return 0 + fi + if ! command -v nix >/dev/null 2>&1 || ! nix_store_ok; then + log "cannot sync path deps — nix store unavailable" + return 1 + fi + log "materializing flake.lock path deps…" + bash "$ROOT/scripts/sync-flake-path-deps.sh" +} + +warm_host_build() { + if ! nix_store_ok; then + log "skipping host cargo build (nix store unavailable)" + return 0 + fi + log "warming host cargo build (baked into snapshot)…" + nix develop "$ROOT" --command cargo build --manifest-path host/Cargo.toml +} + +# ── start (per-pod) ────────────────────────────────────────────────── +if [[ "${1:-}" == "ensure-nix" ]]; then + if ! ensure_nix_daemon_ready; then + log "nix store is not reachable" + exit 1 + fi + log "nix daemon ready ($(nix --version 2>/dev/null | head -1))" + ensure_rad_remote + # Snapshot usually already has siblings; re-sync only if missing. + ensure_path_deps || true + exit 0 +fi + +# ── install (snapshot bake) ────────────────────────────────────────── +# Codespace/Cursor helpers (chromium) when an X display is present. +export SLEEK_CODESPACE="${SLEEK_CODESPACE:-1}" + +log "running codespace-bootstrap (nix, cachix, flake warm)…" +bash "$ROOT/scripts/codespace-bootstrap.sh" + +load_nix_env +if ! nix_store_ok; then + start_nix_daemon || true + load_nix_env +fi +if ! nix_store_ok; then + log "nix store still unreachable after bootstrap" + exit 1 +fi + +ensure_path_deps +ensure_rad_remote +warm_host_build + +log "done" diff --git a/.cursor/rules/radicle-review-proposals.mdc b/.cursor/rules/radicle-review-proposals.mdc new file mode 100644 index 0000000..ac89aa1 --- /dev/null +++ b/.cursor/rules/radicle-review-proposals.mdc @@ -0,0 +1,12 @@ +--- +description: Prefer boxci Radicle patches over local rad push for review proposals +alwaysApply: true +--- + +# Radicle review proposals + +For code-review proposals on this repo, follow `AGENTS.md` → **Cursor Cloud specific instructions**: + +- Use the **`boxci-github-patch`** skill to open/update Radicle patches from a GitHub commit. +- Do **not** use `git push rad`, the `rad-patch` skill, or Radicle MCP `create_patch` unless the user explicitly requests a local `rad` push. +- RID: `rad:z9mjPzpVK472QXaaP1picc5U9xBR` diff --git a/.cursor/skills/boxci-github-patch/SKILL.md b/.cursor/skills/boxci-github-patch/SKILL.md index ccaf9bf..5c16c45 100644 --- a/.cursor/skills/boxci-github-patch/SKILL.md +++ b/.cursor/skills/boxci-github-patch/SKILL.md @@ -12,10 +12,18 @@ description: >- Cloud agents often finish work on a **GitHub** clone. To land that commit on **Radicle**, call boxci — do **not** try to `git push rad` from the cloud VM -unless Radicle is already set up there. +unless Radicle is already set up there. Prefer this over radicle MCP +`create_patch` or local `git push rad` on cloud VMs. Base URL (default): `https://boxci.boxd.sh` +Sleek defaults: + +| Field | Value | +|-------|--------| +| `repo` | `rad:z9mjPzpVK472QXaaP1picc5U9xBR` | +| `github_repo_url` | `https://github.com/codegod100/sleek.git` | + ## When to use - User (or task) has a GitHub commit SHA that should become a Radicle patch diff --git a/.cursor/skills/radicle-patch/SKILL.md b/.cursor/skills/radicle-patch/SKILL.md index 86515ad..74555cc 100644 --- a/.cursor/skills/radicle-patch/SKILL.md +++ b/.cursor/skills/radicle-patch/SKILL.md @@ -1,70 +1,48 @@ --- name: radicle-patch description: > - Open or update a Radicle patch with a stable per-environment device identity. - Use when publishing a patch via the radicle MCP (create_patch / issue_device_key), - when an agent would otherwise run rad auth, or when the user asks to reuse the - same Radicle DID / id for a Cursor environment. + Open a Radicle patch for sleek. Cloud / GitHub agents must use the + boxci-github-patch skill (POST boxci /api/patches/from-github) — not the + radicle MCP and not local rad auth. Use this skill only as a pointer to + boxci-github-patch, or when a machine already has RADICLE_SECRET_KEY and + a working rad remote for a direct git push rad. --- -# Radicle patches (stable DID per Cursor env) +# Radicle patches (sleek) -MCP source: [`rad:z4J6Hk21QrvWvQJKpH145GMAcACga`](https://nandi.radicle.garden/rad:z4J6Hk21QrvWvQJKpH145GMAcACga) -(hosted `https://mcp.boxd.sh/radicle/mcp`). The server auto-scopes to -`CURSOR_ENVIRONMENT_ID` / `x-cursor-environment-id` when `env_name` is omitted. +## Cloud / Cursor agents (default) -## Rule +**Do not use the radicle MCP** (`create_patch` / `issue_device_key` on +`mcp.boxd.sh`). Hosted MCP cannot see the agent git checkout. -**One Cursor environment → one Radicle device DID.** Never mint a new identity -with `rad auth` for patch publishing unless a dedicated CI key was already -loaded into `$RAD_HOME` from OpenBao/Buildkite. +**Do not** run `rad auth` to mint a device DID in the cloud VM. -## Resolve `env_name` +Follow **`.cursor/skills/boxci-github-patch/`** instead: -1. Call MCP `cursor-cloud` → `environment-info`. -2. Set `env_name` to `environment.environmentPublicId` (UUID). -3. Pass the same `env_name` on every radicle MCP tool call in this run. +1. Push the commit to GitHub (`origin`). +2. `POST https://boxci.boxd.sh/api/patches/from-github` with sleek’s RID, + `https://github.com/codegod100/sleek.git`, and the commit SHA. +3. Poll the boxci run and surface `https://boxci.boxd.sh/runs/` plus + `patch_id` from the step output. -Optional alias (first issue only): `cursor-env-` (e.g. -`cursor-env-codegod100-sleek`). Do not change the alias on later loads. +Sleek defaults: -## Issue or load the identity +| Field | Value | +|-------|--------| +| `repo` | `rad:z9mjPzpVK472QXaaP1picc5U9xBR` | +| `github_repo_url` | `https://github.com/codegod100/sleek.git` | +| `branch` | `main` | -```text -radicle / issue_device_key - env_name: - alias: cursor-env-… # only matters on first create - start_node: false # default - force: false # never true unless rotating -``` - -Expect `created: false` and the same `did` on subsequent runs in this env. +## Local / CI exception -## Open a patch +Only when `RADICLE_SECRET_KEY` (or an existing `$RAD_HOME`) is already loaded +and `git remote get-url rad` works — e.g. a CI job that pulled the key from +OpenBao `secret/data/radicle` — you may open a patch with: -```text -radicle / create_patch - env_name: # required — same as above - title: … - body: … # full description, not title-only - branch: … - commit: … # optional: commit then patch +```bash +git push rad HEAD:refs/patches \ + -o patch.message="Title" \ + -o patch.message="Body paragraph" ``` -`create_patch` auto-issues credentials when needed, but still pass `env_name` -so it does not fall back to an unscoped / ephemeral home. - -## Do not - -- Run `rad auth --alias …` in the agent VM to “get a key quickly”. -- Omit `env_name` (unscoped home is shared/ambiguous across envs). -- Commit `$RAD_HOME/keys` or paste private keys into the repo/PR. -- Use `force: true` on `issue_device_key` unless the user asked to rotate. - -## CI exception - -Buildkite issue→agent loads a **dedicated CI** identity from -`RADICLE_SECRET_KEY` / OpenBao `secret/data/radicle` via -`scripts/buildkite/bootstrap.sh`. That path is separate from the Cursor -env-scoped MCP identity. Prefer MCP + `env_name` for interactive/cloud-agent -patches; use the CI key only inside Buildkite bootstrap. +Never commit key material under `.radicle/` or elsewhere.