spawn/.claude/rules/shell-scripts.md
L 61bcedc0eb
feat: migrate to openrouter.ai/labs/spawn CDN + release artifact version checks (#2178)
* feat: migrate shell script URLs to openrouter.ai/labs/spawn CDN

Users on older CLI versions can't auto-update because the repo was restructured
(cli/ → packages/cli/), so old version-check URLs 404. This decouples the CLI
from the repo's internal directory structure:

- Shell script URLs (install, agent scripts, github-auth) now use
  openrouter.ai/labs/spawn/* as primary with GitHub raw as fallback
- Version checks now use GitHub release artifact (cli-latest/version)
  as primary — a static URL that never changes regardless of repo layout
- CI workflow updated to publish a `version` file alongside cli.js
- Remove GITHUB_RAW_URL_PATTERN validation (no longer needed since
  install URL is now a hardcoded CDN string, not interpolated)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: fix biome formatting in update-check test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: CLAUDE.md says biome lint but should say biome check

biome lint only checks lint rules, not formatting. biome check does both.
The hooks and CI already run biome check — the docs were out of sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(hooks): PostToolUse hook wasn't running biome on CLI source files

Two bugs in validate-file.ts:

1. Config search only checked 1-2 levels up from the edited file, but
   biome.json is at packages/cli/ — 3 levels above src/__tests__/*.ts.
   Fix: walk up directories until biome.json is found (or hit root).

2. Ran `biome format` (prints formatted output, always exits 0) instead
   of `biome format --check` (exits non-zero if file needs formatting).
   Fix: use `biome check` which does lint + format check in one pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 23:34:58 -08:00

2.7 KiB

Shell Script Rules

These rules are non-negotiable — violating them breaks remote execution for all users.

curl|bash Compatibility

Every script MUST work when executed via bash <(curl -fsSL URL):

  • NEVER use relative paths for sourcing (source ./lib/..., source ../shared/...)
  • NEVER rely on $0, dirname $0, or BASH_SOURCE resolving to a real filesystem path

macOS bash 3.x Compatibility

macOS ships bash 3.2. All scripts MUST work on it:

  • NO echo -e — use printf for escape sequences
  • NO source <(cmd) inside bash <(curl ...) — use eval "$(cmd)" instead
  • NO ((var++)) with set -e — use var=$((var + 1)) (avoids falsy-zero exit)
  • NO local keyword inside ( ... ) & subshells — not function scope
  • NO set -u (nounset) — use ${VAR:-} for optional env var checks instead

Conventions

  • #!/bin/bash + set -eo pipefail (no u flag)
  • Use ${VAR:-} for all optional env var checks (OPENROUTER_API_KEY, cloud tokens, etc.)
  • Primary script URL: https://openrouter.ai/labs/spawn/{path} (CDN proxy, maps to repo sh/, e.g., {cloud}/{agent}.sh)
  • Fallback script URL: https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/sh/{path}
  • Version check URL: https://github.com/OpenRouterTeam/spawn/releases/download/cli-latest/version (GitHub release artifact)
  • All env vars documented in the cloud's sh/{cloud}/README.md

Use Bun + TypeScript for Inline Scripting — NEVER python/python3

When shell scripts need JSON processing, HTTP calls, crypto, or any non-trivial logic:

  • ALWAYS use bun eval '...' or write a temp .ts file and bun run it
  • NEVER use python3 -c or python -c for inline scripting — python is not a project dependency
  • Prefer jq for simple JSON extraction; fall back to bun eval when jq is unavailable
  • Pass data to bun via environment variables (e.g., _DATA="${var}" bun eval "...") or temp files — never interpolate untrusted values into JS strings
  • For complex operations (SigV4 signing, API calls with retries), write a heredoc .ts file and bun run it

ESM Only — NEVER use require() or CommonJS

All TypeScript code in packages/cli/src/ MUST use ESM (import/export):

  • NEVER use require() — always use import (static or dynamic await import())
  • NEVER use module.exports — always use export / export default
  • NEVER use createRequire — it's a CJS compatibility hack that triggers Bun bugs
  • The project is "type": "module" in package.json — CJS is not supported
  • For Node.js built-ins: import fs from "fs", import path from "path", etc.
  • For dynamic imports: const mod = await import("./module.ts")