diff --git a/AGENTS.md b/AGENTS.md index 729931d..c396f2a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,30 +1,32 @@ # AGENTS.md -Guidance for Codex (and humans) working in this repo. +Guidance for Codex, Grok, and humans working in this repo. ## What this repo is A small collection of shell scripts and an installer that set up a comfortable -Claude Code and/or Codex working environment on a fresh Linux machine: a tmux -login session picker, agent status information, tmux copy/scrollback config, +Claude Code, Codex, and/or Grok working environment on a fresh Linux machine: a +tmux login session picker, agent status information, tmux copy/scrollback config, and 1Password-backed SSH key login. There is no build system. ## Layout - `scripts/` contains canonical files installed into `$HOME`. Edit these, not installed copies. -- `setup.sh` is the interactive, idempotent installer. +- `setup.sh` is the interactive, idempotent installer + (`--codex` / `--claude` / `--grok` / `--both` / `--all`). - `scripts/statusline.sh` is Claude Code-specific. Codex uses its native - `tui.status_line` setting instead. + `tui.status_line` setting; Grok uses its built-in TUI status. - `prompts/machine-setup.md` is the self-contained Claude Code setup prompt. - `prompts/codex-machine-setup.md` is the Codex setup prompt. +- `prompts/grok-machine-setup.md` is the Grok setup prompt. ## Conventions - `scripts/tmux-login.sh` is POSIX sh. Verify it with `sh -n`. - `scripts/statusline.sh` and `setup.sh` are Bash. Verify them with `bash -n`. - Keep `CLAUDE.md` and `AGENTS.md` aligned when shared repository guidance - changes. + changes. Grok loads both automatically. - Keep edits small, readable, and idempotent. ## Safety @@ -34,6 +36,21 @@ authentication automatically. Preserve the interactive, SSH, tmux, and terminfo guards in `scripts/profile-snippet.sh`. After login-flow changes, test from a second SSH session while keeping the first session open. +## Multi-agent handoffs + +When handing work between Claude, Codex, and Grok, write `handoff/.md`: + +1. Goal (one sentence) +2. Exact file list in scope +3. Worked example if the change is mechanical +4. Acceptance command(s) (e.g. `./tests/test.sh`) +5. Explicit DO-NOT-TOUCH: `profile-snippet.sh`, login-path semantics of + `tmux-login.sh`, the `infocmp` guard, anything that disables SSH password auth + +Never hand off SSH login-path or installer safety semantics without human review. +Grok can resume recent Claude/Codex sessions via its `resume-claude` / +`resume-codex` skills. + ## Testing ```bash diff --git a/CLAUDE.md b/CLAUDE.md index 3e17379..6eddada 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,21 +5,23 @@ Guidance for Claude Code (and humans) working in this repo. ## What this repo is A small collection of shell scripts + an installer that set up a standard Claude -Code and/or Codex working environment on a fresh Linux machine: a tmux login -session picker, agent status information, tmux copy/scrollback config, and +Code, Codex, and/or Grok working environment on a fresh Linux machine: a tmux +login session picker, agent status information, tmux copy/scrollback config, and 1Password-backed SSH key login. No build system. ## Layout - `scripts/` — the canonical files that get installed to `$HOME`. **Edit these**, not copies. `setup.sh` installs from here, so there is one source of truth. -- `setup.sh` — interactive installer. Reads from `scripts/`; does not embed copies. +- `setup.sh` — interactive installer (`--codex` / `--claude` / `--grok` / + `--both` / `--all`). Reads from `scripts/`; does not embed copies. - `prompts/machine-setup.md` — the setup written as a Claude Code prompt. It **does** embed the script bodies (so it's self-contained when pasted into a fresh machine). If you change a script in `scripts/`, update the embedded copy in this prompt to match. -- `AGENTS.md` — the equivalent repository guidance Codex loads automatically. +- `AGENTS.md` — the equivalent repository guidance Codex (and Grok) load. - `prompts/codex-machine-setup.md` — a Codex-native setup prompt. +- `prompts/grok-machine-setup.md` — a Grok-native setup prompt. ## Conventions @@ -30,6 +32,7 @@ session picker, agent status information, tmux copy/scrollback config, and Verify with `bash -n`. It must always `exit 0` and tolerate empty/garbage stdin. - The statusline reads all JSON fields in **one** `jq` call (perf: it runs on every render). Keep it that way — don't reintroduce per-field `jq` calls. +- Keep `CLAUDE.md` and `AGENTS.md` aligned when shared guidance changes. - Keep edits small and readable; match the surrounding comment density. ## Safety (important) @@ -44,6 +47,13 @@ These scripts run in the SSH login path. A bug can lock someone out of a machine resolvable `$TERM` via `infocmp`). The `infocmp` check is what prevents an unknown terminal from exec'ing a tmux that instantly dies and strands the login. +## Multi-agent handoffs + +When handing work to Codex or Grok, write `handoff/.md` with: goal, exact +file list, worked example if mechanical, acceptance command(s), and DO-NOT-TOUCH +(login path, `infocmp`, password auth). Never hand off SSH login-path or installer +safety semantics without human review. See `AGENTS.md` for the same protocol. + ## Testing ```bash diff --git a/README.md b/README.md index 5cbcb77..6155b35 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ # coding-agent-machine-setup Turn a fresh Linux box (VM, LXC container, server) into a comfortable -[Codex](https://developers.openai.com/codex/) and/or -[Claude Code](https://claude.com/claude-code) working environment in one go: +[Codex](https://developers.openai.com/codex/), +[Claude Code](https://claude.com/claude-code), and/or +[Grok Build](https://x.ai/cli) working environment in one go: - 🔑 **SSH key login via 1Password** — key lives in the 1Password SSH agent, unlocked by Touch ID / your vault, never on disk. - 🪟 **tmux + an interactive login session picker** — every SSH login lands on a menu: attach an existing session, jump straight into a project, start a new named session, or drop to a plain shell. - 🖱 **tmux copy / scrollback / history tuning** — mouse selection + wheel scrollback, 50k-line history. -- 📊 **Agent-native status information** — Codex gets a native footer with directory, Git branch, model/reasoning, and context remaining; Claude Code keeps the custom two-line statusline with rate-limit meters. -- 🧭 **Native instruction files** — `AGENTS.md` for Codex and `CLAUDE.md` for Claude Code carry equivalent repository guidance. +- 📊 **Agent-native status information** — Codex gets a native footer with directory, Git branch, model/reasoning, and context remaining; Claude Code keeps the custom two-line statusline with rate-limit meters; Grok uses its built-in TUI status. +- 🧭 **Native instruction files** — `AGENTS.md` (Codex + Grok) and `CLAUDE.md` (Claude Code; Grok loads both) carry equivalent repository guidance. It's all small, readable shell — no frameworks, no daemons. @@ -46,11 +47,13 @@ Choose a specific agent when desired: ```bash ./setup.sh --codex ./setup.sh --claude -./setup.sh --both +./setup.sh --grok +./setup.sh --both # Claude + Codex +./setup.sh --all # Claude + Codex + Grok ``` -With no flag, the installer detects installed agents; if neither is present it -defaults to Codex setup. +With no flag, the installer detects installed agents and configures those present; +if none are present it defaults to Codex setup. The installer is interactive and conservative: it shows what it changes, skips anything already done, asks before installing packages, and **never disables SSH @@ -60,8 +63,8 @@ password auth for you** (do that yourself once key login is confirmed). It will: 2. Optionally add a public key to `~/.ssh/authorized_keys` (and print the laptop-side 1Password steps). 3. Write `~/.tmux-login.sh` with the projects you enter, and wire the guarded hook into `~/.profile`. 4. Install `~/.tmux.conf` (or merge in the mouse + history settings). -5. Configure Codex's native footer in `~/.codex/config.toml`, install Claude's - custom statusline, or both, according to the selected mode. +5. Per selected mode: configure Codex's native footer, install Claude's custom + statusline, and/or ensure Grok's `~/.grok/bin` is on PATH. > **Test from a second SSH session.** Because this touches the login path, open a > new terminal and confirm everything works **while keeping your current session @@ -74,8 +77,29 @@ password auth for you** (do that yourself once key login is confirmed). It will: interactively, asks before risky actions, and is handy when a machine's quirks need judgment the installer doesn't have. -For Codex, use `prompts/codex-machine-setup.md`. Codex also reads this -repository's `AGENTS.md` automatically. +| Agent | Setup prompt | Instruction file | +|-------|--------------|------------------| +| Claude Code | `prompts/machine-setup.md` | `CLAUDE.md` | +| Codex | `prompts/codex-machine-setup.md` | `AGENTS.md` | +| Grok Build | `prompts/grok-machine-setup.md` | `AGENTS.md` + `CLAUDE.md` | + +## Working with multiple agents + +Shared shell environment (tmux picker, scrollback, SSH) is agent-agnostic. Project +rules live in `AGENTS.md` / `CLAUDE.md` — keep them aligned. Grok also picks up +Claude Code skills and MCP config via harness compatibility by default. + +Rough strengths (not hard rules): + +| Agent | Good fit | +|-------|----------| +| **Claude** | Judgment calls, SSH login-path safety, installer design, ambiguous UX | +| **Codex** | Bulk mechanical edits with machine-checkable acceptance (`sh -n`, `./tests/test.sh`) | +| **Grok** | Parallel explore/subagents, cost-efficient sweeps, web/research; `resume-claude` / `resume-codex` skills | + +Cross-agent handoffs: write `handoff/.md` with goal, exact file list, acceptance +command(s), and an explicit DO-NOT-TOUCH list (login path, `infocmp` guard, password auth). +Never hand off SSH login-path or installer safety semantics without human review. ## What goes where @@ -86,9 +110,11 @@ repository's `AGENTS.md` automatically. | `scripts/tmux.conf` | `~/.tmux.conf` | Mouse, 50k scrollback, copy-mode notes. | | `scripts/statusline.sh` | `~/.claude/statusline.sh` | The Claude Code statusline (bash). | | native Codex configuration | `~/.codex/config.toml` | Codex footer; no wrapper script required. | +| Grok PATH block | `~/.bashrc` | Ensures `~/.grok/bin` is on PATH (idempotent). | | `setup.sh` | — | Interactive installer that wires it all up. | | `prompts/machine-setup.md` | — | The "let Claude set it up" prompt. | | `prompts/codex-machine-setup.md` | — | The equivalent Codex setup prompt. | +| `prompts/grok-machine-setup.md` | — | The equivalent Grok setup prompt. | ## Customizing @@ -101,6 +127,8 @@ repository's `AGENTS.md` automatically. - **Codex footer:** edit `tui.status_line` in `~/.codex/config.toml`. Run `/status` for full session configuration and `/model` to change model or reasoning effort. +- **Grok:** edit `~/.grok/config.toml` for UI/model defaults. Auth with + `grok login` or `grok login --device-auth` on headless SSH hosts. - **tmux clipboard / vi keys:** uncomment the optional lines at the bottom of `~/.tmux.conf`, then `tmux source-file ~/.tmux.conf`. @@ -114,6 +142,8 @@ repository's `AGENTS.md` automatically. sure the file your shell sources pulls in the snippet. - **Unknown `$TERM`:** the login hook skips the picker (rather than stranding you) if your `$TERM` isn't in the server's terminfo db, and prints how to install it. +- **Agent binaries are never auto-installed** — the installer prints official + install URLs for missing agents and continues with configuration only. ## License diff --git a/prompts/grok-machine-setup.md b/prompts/grok-machine-setup.md new file mode 100644 index 0000000..7a34c8a --- /dev/null +++ b/prompts/grok-machine-setup.md @@ -0,0 +1,26 @@ +# Set up this Linux machine for Grok Build + +Help me turn this Linux machine into a comfortable Grok Build CLI environment. +Clone this repository if it is not already present, inspect the current machine +without overwriting existing configuration, and run: + +```bash +./setup.sh --grok +``` + +Work interactively and preserve these rules: + +- Never disable SSH password authentication. +- Before changing the SSH login path, show what will change. +- Keep the current SSH connection open and test from a second connection. +- Preserve existing tmux and Grok configuration. +- If Grok is absent, offer the official installer + (`curl -fsSL https://x.ai/cli/install.sh | bash`) and ask before running it. +- After setup, verify `grok --version`. On headless SSH hosts prefer + `grok login --device-auth`; otherwise `grok login` is fine. +- Confirm that Grok loads this repository's `AGENTS.md` and/or `CLAUDE.md`. + +The installer ensures `~/.grok/bin` is on PATH. Grok uses a built-in TUI status +(no external statusline script) and already reads Claude Code skills/MCP via +harness compatibility. Do not install the Claude Code statusline for a +Grok-only setup. diff --git a/setup.sh b/setup.sh index 62362c9..a6c35b4 100755 --- a/setup.sh +++ b/setup.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# setup.sh — set up tmux session picker + Claude Code statusline on this machine. +# setup.sh — set up tmux session picker + agent status for Claude / Codex / Grok. # # Usage: git clone && cd && ./setup.sh # @@ -17,86 +17,128 @@ note() { printf '%s\n' " ${c_dim}$*${c_rst}"; } ok() { printf '%s\n' " ${c_grn}✓${c_rst} $*"; } warn() { printf '%s\n' " ${c_ylw}!${c_rst} $*"; } ask() { # ask "prompt" "default" -> echoes answer - local q="$1" def="${2:-}" a - if [ -n "$def" ]; then printf '%s [%s]: ' "$q" "$def" >&2; else printf '%s: ' "$q" >&2; fi - read -r a || a="" - [ -z "$a" ] && a="$def" - printf '%s' "$a" + local q="$1" def="${2:-}" a + if [ -n "$def" ]; then printf '%s [%s]: ' "$q" "$def" >&2; else printf '%s: ' "$q" >&2; fi + read -r a || a="" + [ -z "$a" ] && a="$def" + printf '%s' "$a" } confirm() { # confirm "prompt" -> returns 0 if yes - local a; a="$(ask "$1 (y/N)" "")"; case "$a" in y|Y|yes|YES) return 0;; *) return 1;; esac + local a; a="$(ask "$1 (y/N)" "")"; case "$a" in y|Y|yes|YES) return 0;; *) return 1;; esac } write_codex_status() { - local config="$1" tmp - local value='status_line = ["current-dir", "git-branch", "model-with-reasoning", "context-remaining"]' - mkdir -p "$(dirname "$config")" - [ -f "$config" ] || : > "$config" - tmp="$(mktemp)" - awk -v value="$value" ' - BEGIN { in_tui=0; saw_tui=0; wrote=0 } - /^[[:space:]]*\[[^]]+\][[:space:]]*$/ { - if (in_tui && !wrote) { print value; wrote=1 } - in_tui = ($0 ~ /^[[:space:]]*\[tui\][[:space:]]*$/) - if (in_tui) saw_tui=1 - } - in_tui && /^[[:space:]]*status_line[[:space:]]*=/ { - if (!wrote) { print value; wrote=1 } - next - } - { print } - END { - if (in_tui && !wrote) print value - if (!saw_tui) { - print "" - print "[tui]" - print value - } - } - ' "$config" > "$tmp" && mv "$tmp" "$config" + local config="$1" tmp + local value='status_line = ["current-dir", "git-branch", "model-with-reasoning", "context-remaining"]' + mkdir -p "$(dirname "$config")" + [ -f "$config" ] || : > "$config" + tmp="$(mktemp)" + awk -v value="$value" ' + BEGIN { in_tui=0; saw_tui=0; wrote=0 } + /^[[:space:]]*\[[^]]+\][[:space:]]*$/ { + if (in_tui && !wrote) { print value; wrote=1 } + in_tui = ($0 ~ /^[[:space:]]*\[tui\][[:space:]]*$/) + if (in_tui) saw_tui=1 + } + in_tui && /^[[:space:]]*status_line[[:space:]]*=/ { + if (!wrote) { print value; wrote=1 } + next + } + { print } + END { + if (in_tui && !wrote) print value + if (!saw_tui) { + print "" + print "[tui]" + print value + } + } + ' "$config" > "$tmp" && mv "$tmp" "$config" +} + +# Ensure ~/.grok/bin is on PATH for interactive bash (matches the official Grok installer block). +# Idempotent: skips if the marker or a .grok/bin PATH export is already present. +ensure_grok_path() { + local rc="${1:-$HOME/.bashrc}" + mkdir -p "$HOME/.grok/bin" + [ -f "$rc" ] || : > "$rc" + if grep -qF '>>> grok installer >>>' "$rc" 2>/dev/null \ + || grep -qF '.grok/bin' "$rc" 2>/dev/null; then + return 0 + fi + { + printf '\n' + printf '# >>> grok installer >>>\n' + printf 'export PATH="$HOME/.grok/bin:$PATH"\n' + printf '[[ -r "$HOME/.grok/completions/bash/grok.bash" ]] && source "$HOME/.grok/completions/bash/grok.bash"\n' + printf '# <<< grok installer <<<\n' + } >> "$rc" } if [ "${1:-}" = "--write-codex-status" ]; then - [ -n "${2:-}" ] || { echo "usage: $0 --write-codex-status CONFIG"; exit 2; } - write_codex_status "$2" - exit 0 + [ -n "${2:-}" ] || { echo "usage: $0 --write-codex-status CONFIG"; exit 2; } + write_codex_status "$2" + exit 0 +fi + +if [ "${1:-}" = "--ensure-grok-path" ]; then + ensure_grok_path "${2:-$HOME/.bashrc}" + exit 0 fi mode="auto" case "${1:-}" in - "") ;; - --codex) mode="codex" ;; - --claude) mode="claude" ;; - --both) mode="both" ;; - -h|--help) - echo "usage: $0 [--codex|--claude|--both]" - exit 0 - ;; - *) echo "unknown option: $1" >&2; exit 2 ;; + "") ;; + --codex) mode="codex" ;; + --claude) mode="claude" ;; + --grok) mode="grok" ;; + --both) mode="both" ;; + --all) mode="all" ;; + -h|--help) + echo "usage: $0 [--codex|--claude|--grok|--both|--all]" + exit 0 + ;; + *) echo "unknown option: $1" >&2; exit 2 ;; esac [ -d "$SCRIPTS" ] || { printf '%s\n' "${c_red}error:${c_rst} run this from the cloned repo (scripts/ not found next to setup.sh)"; exit 1; } printf '\n%s\n\n' "${c_bold}AI coding agent machine setup${c_rst}" note "Files this can create/modify:" -note " ~/.tmux-login.sh, ~/.profile, ~/.tmux.conf, ~/.codex/config.toml," -note " ~/.claude/statusline.sh, ~/.claude/settings.json, ~/.ssh/authorized_keys" +note " ~/.tmux-login.sh, ~/.profile, ~/.bashrc, ~/.tmux.conf," +note " ~/.codex/config.toml, ~/.claude/statusline.sh, ~/.claude/settings.json," +note " ~/.ssh/authorized_keys" echo +want_codex=0; want_claude=0; want_grok=0 if [ "$mode" = "auto" ]; then - if command -v codex >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then mode="both" - elif command -v codex >/dev/null 2>&1; then mode="codex" - elif command -v claude >/dev/null 2>&1; then mode="claude" - else mode="codex" - fi + # Enable whichever agents are installed; default to Codex when none are present. + command -v codex >/dev/null 2>&1 && want_codex=1 + command -v claude >/dev/null 2>&1 && want_claude=1 + command -v grok >/dev/null 2>&1 && want_grok=1 + if [ "$want_codex$want_claude$want_grok" = "000" ]; then + want_codex=1 + mode="codex" + elif [ "$want_codex" = 1 ] && [ "$want_claude" = 1 ] && [ "$want_grok" = 0 ]; then + mode="both" # historical Claude+Codex label + elif [ "$want_codex" = 1 ] && [ "$want_claude" = 0 ] && [ "$want_grok" = 0 ]; then + mode="codex" + elif [ "$want_codex" = 0 ] && [ "$want_claude" = 1 ] && [ "$want_grok" = 0 ]; then + mode="claude" + elif [ "$want_codex" = 0 ] && [ "$want_claude" = 0 ] && [ "$want_grok" = 1 ]; then + mode="grok" + else + mode="all" # any other mix (includes three-way) + fi +else + case "$mode" in + codex) want_codex=1 ;; + claude) want_claude=1 ;; + grok) want_grok=1 ;; + both) want_codex=1; want_claude=1 ;; + all) want_codex=1; want_claude=1; want_grok=1 ;; + esac fi - -want_codex=0; want_claude=0 -case "$mode" in - codex) want_codex=1 ;; - claude) want_claude=1 ;; - both) want_codex=1; want_claude=1 ;; -esac note "agent mode: $mode" echo @@ -116,15 +158,15 @@ fi missing="" for t in tmux jq git; do command -v "$t" >/dev/null 2>&1 || missing="$missing $t"; done if [ -n "$missing" ]; then - warn "missing:$missing" - if [ -n "$PKG" ] && confirm "Install with: $PKG$missing ?"; then - # shellcheck disable=SC2086 - $PKG $missing && ok "installed$missing" || warn "install failed — install$missing manually and re-run" - else - warn "skipping install — these are required for the statusline/picker to work" - fi + warn "missing:$missing" + if [ -n "$PKG" ] && confirm "Install with: $PKG$missing ?"; then + # shellcheck disable=SC2086 + $PKG $missing && ok "installed$missing" || warn "install failed — install$missing manually and re-run" + else + warn "skipping install — these are required for the statusline/picker to work" + fi else - ok "tmux, jq, git all present" + ok "tmux, jq, git all present" fi echo @@ -138,23 +180,23 @@ note " (macOS socket: '~/Library/Group Containers/2BUA8C4S2C.com.1password/t note " 4) Copy the PUBLIC key (the 'ssh-ed25519 AAAA…' string) and paste it below." echo if confirm "Add a public key to ~/.ssh/authorized_keys on this machine now?"; then - pubkey="$(ask "Paste public key" "")" - if [ -n "$pubkey" ]; then - mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh" - touch "$HOME/.ssh/authorized_keys"; chmod 600 "$HOME/.ssh/authorized_keys" - if grep -qF "$pubkey" "$HOME/.ssh/authorized_keys" 2>/dev/null; then - ok "key already present — nothing to do" - else - printf '%s\n' "$pubkey" >> "$HOME/.ssh/authorized_keys" - ok "key added to ~/.ssh/authorized_keys" - fi - warn "TEST IT from a new terminal before closing this session." - warn "Disabling password auth is intentionally NOT done here — do it manually once key login works." - else - note "no key entered — skipping" - fi + pubkey="$(ask "Paste public key" "")" + if [ -n "$pubkey" ]; then + mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh" + touch "$HOME/.ssh/authorized_keys"; chmod 600 "$HOME/.ssh/authorized_keys" + if grep -qF "$pubkey" "$HOME/.ssh/authorized_keys" 2>/dev/null; then + ok "key already present — nothing to do" + else + printf '%s\n' "$pubkey" >> "$HOME/.ssh/authorized_keys" + ok "key added to ~/.ssh/authorized_keys" + fi + warn "TEST IT from a new terminal before closing this session." + warn "Disabling password auth is intentionally NOT done here — do it manually once key login works." + else + note "no key entered — skipping" + fi else - note "skipping SSH key step" + note "skipping SSH key step" fi echo @@ -165,34 +207,34 @@ note " key = menu letter, name = tmux session name (no '.' or ':'), directory = note "Enter one per line; blank line to finish. Press enter immediately to use the default (root only)." proj_lines="" while :; do - line="$(ask " project (key name dir)" "")" - [ -z "$line" ] && break - proj_lines="${proj_lines}${line}"$'\n' + line="$(ask " project (key name dir)" "")" + [ -z "$line" ] && break + proj_lines="${proj_lines}${line}"$'\n' done if [ -z "$proj_lines" ]; then - proj_block='projects="r root $HOME"' # single-quoted: $HOME stays literal, expands at login - note "using default: root only" + proj_block='projects="r root $HOME"' # single-quoted: $HOME stays literal, expands at login + note "using default: root only" else - proj_block="projects=\"$(printf '%s' "$proj_lines" | sed '/^$/d')\"" + proj_block="projects=\"$(printf '%s' "$proj_lines" | sed '/^$/d')\"" fi if [ -f "$HOME/.tmux-login.sh" ] && ! confirm "~/.tmux-login.sh exists — overwrite?"; then - note "keeping existing ~/.tmux-login.sh" + note "keeping existing ~/.tmux-login.sh" else - # Copy the canonical script, swapping in the chosen projects= assignment. - awk -v repl="$proj_block" '/^projects="/{print repl; skip=1; next} skip&&/^$/{skip=0} !skip{print}' \ - "$SCRIPTS/tmux-login.sh" > "$HOME/.tmux-login.sh" - ok "wrote ~/.tmux-login.sh" + # Copy the canonical script, swapping in the chosen projects= assignment. + awk -v repl="$proj_block" '/^projects="/{print repl; skip=1; next} skip&&/^$/{skip=0} !skip{print}' \ + "$SCRIPTS/tmux-login.sh" > "$HOME/.tmux-login.sh" + ok "wrote ~/.tmux-login.sh" fi # Wire into the login profile. prof="$HOME/.profile" [ -f "$HOME/.bash_profile" ] && prof="$HOME/.bash_profile" if grep -q 'tmux-login.sh' "$prof" 2>/dev/null; then - ok "login hook already present in $prof" + ok "login hook already present in $prof" else - { echo ""; cat "$SCRIPTS/profile-snippet.sh"; } >> "$prof" - ok "appended login hook to $prof" + { echo ""; cat "$SCRIPTS/profile-snippet.sh"; } >> "$prof" + ok "appended login hook to $prof" fi note "If your shell doesn't source $prof on login, add '. $prof' to the file it does source." echo @@ -201,64 +243,91 @@ echo say "tmux config (~/.tmux.conf — mouse, scrollback, history)" tmuxconf="$HOME/.tmux.conf" if [ ! -f "$tmuxconf" ]; then - install -m 0644 "$SCRIPTS/tmux.conf" "$tmuxconf" - ok "wrote ~/.tmux.conf" + install -m 0644 "$SCRIPTS/tmux.conf" "$tmuxconf" + ok "wrote ~/.tmux.conf" else - # Append only the settings that aren't already configured (idempotent). - added=0 - grep -q '^[[:space:]]*set\(w\)\?[[:space:]].*mouse' "$tmuxconf" || { echo 'set -g mouse on' >> "$tmuxconf"; added=1; } - grep -q '^[[:space:]]*set\(w\)\?[[:space:]].*history-limit' "$tmuxconf" || { echo 'set -g history-limit 50000' >> "$tmuxconf"; added=1; } - if [ "$added" = 1 ]; then ok "appended missing tmux settings to existing ~/.tmux.conf"; else ok "~/.tmux.conf already has mouse + history-limit"; fi + # Append only the settings that aren't already configured (idempotent). + added=0 + grep -q '^[[:space:]]*set\(w\)\?[[:space:]].*mouse' "$tmuxconf" || { echo 'set -g mouse on' >> "$tmuxconf"; added=1; } + grep -q '^[[:space:]]*set\(w\)\?[[:space:]].*history-limit' "$tmuxconf" || { echo 'set -g history-limit 50000' >> "$tmuxconf"; added=1; } + if [ "$added" = 1 ]; then ok "appended missing tmux settings to existing ~/.tmux.conf"; else ok "~/.tmux.conf already has mouse + history-limit"; fi fi note "Reload in a running tmux with: tmux source-file ~/.tmux.conf" echo # --- Part 3: agent status information -------------------------------------- if [ "$want_codex" = 1 ]; then - say "Codex CLI" - if command -v codex >/dev/null 2>&1; then - ok "found $(codex --version 2>/dev/null || echo codex)" - else - warn "Codex is not installed." - note "Official installer: curl -fsSL https://chatgpt.com/codex/install.sh | sh" - note "Install it, then re-run this setup. It is not run automatically." - fi - codex_config="${CODEX_HOME:-$HOME/.codex}/config.toml" - if write_codex_status "$codex_config"; then - ok "configured Codex native footer in $codex_config" - note "footer: directory · git branch · model/reasoning · context remaining" - else - warn "couldn't update $codex_config" - fi - note "Run 'codex login' if needed, then start Codex in a project directory." - note "Useful commands: /status · /model · /permissions · /review · /exit" - echo + say "Codex CLI" + if command -v codex >/dev/null 2>&1; then + ok "found $(codex --version 2>/dev/null || echo codex)" + else + warn "Codex is not installed." + note "Official installer: curl -fsSL https://chatgpt.com/codex/install.sh | sh" + note "Install it, then re-run this setup. It is not run automatically." + fi + codex_config="${CODEX_HOME:-$HOME/.codex}/config.toml" + if write_codex_status "$codex_config"; then + ok "configured Codex native footer in $codex_config" + note "footer: directory · git branch · model/reasoning · context remaining" + else + warn "couldn't update $codex_config" + fi + note "Run 'codex login' if needed, then start Codex in a project directory." + note "Useful commands: /status · /model · /permissions · /review · /exit" + echo fi if [ "$want_claude" = 1 ]; then - say "Claude Code statusline (~/.claude/statusline.sh)" - mkdir -p "$HOME/.claude" - install -m 0755 "$SCRIPTS/statusline.sh" "$HOME/.claude/statusline.sh" - ok "installed ~/.claude/statusline.sh" + say "Claude Code statusline (~/.claude/statusline.sh)" + mkdir -p "$HOME/.claude" + install -m 0755 "$SCRIPTS/statusline.sh" "$HOME/.claude/statusline.sh" + ok "installed ~/.claude/statusline.sh" + + settings="$HOME/.claude/settings.json" + [ -f "$settings" ] || echo '{}' > "$settings" + if command -v jq >/dev/null 2>&1; then + tmp="$(mktemp)" + if jq '.statusLine = {type:"command", command:"~/.claude/statusline.sh", padding:0}' "$settings" > "$tmp" 2>/dev/null; then + mv "$tmp" "$settings"; ok "registered statusLine in settings.json" + else + rm -f "$tmp"; warn "couldn't parse $settings — add the statusLine block manually (see README)" + fi + else + warn "jq missing — add the statusLine block to $settings manually (see README)" + fi + echo +fi - settings="$HOME/.claude/settings.json" - [ -f "$settings" ] || echo '{}' > "$settings" - if command -v jq >/dev/null 2>&1; then - tmp="$(mktemp)" - if jq '.statusLine = {type:"command", command:"~/.claude/statusline.sh", padding:0}' "$settings" > "$tmp" 2>/dev/null; then - mv "$tmp" "$settings"; ok "registered statusLine in settings.json" - else - rm -f "$tmp"; warn "couldn't parse $settings — add the statusLine block manually (see README)" - fi - else - warn "jq missing — add the statusLine block to $settings manually (see README)" - fi - echo +if [ "$want_grok" = 1 ]; then + say "Grok Build CLI" + if command -v grok >/dev/null 2>&1; then + ok "found $(grok --version 2>/dev/null | head -1 || echo grok)" + else + warn "Grok is not installed." + note "Official installer: curl -fsSL https://x.ai/cli/install.sh | bash" + note "Install it, then re-run this setup. It is not run automatically." + fi + bashrc="$HOME/.bashrc" + if ensure_grok_path "$bashrc"; then + if grep -qF '.grok/bin' "$bashrc" 2>/dev/null; then + ok "PATH includes ~/.grok/bin (in $bashrc)" + else + ok "ensured ~/.grok/bin PATH entry in $bashrc" + fi + else + warn "couldn't update $bashrc for Grok PATH" + fi + note "Grok uses a built-in TUI status (no external statusline script)." + note "It loads AGENTS.md and CLAUDE.md as project rules; Claude skills/MCP via harness compat." + note "Auth: 'grok login' (browser) or 'grok login --device-auth' on headless SSH hosts." + note "Useful: /settings · /model · resume-claude · resume-codex skills · /exit" + echo fi # --- Done ------------------------------------------------------------------ say "Done" ok "Open a NEW SSH session to see the tmux picker (keep this one open as a safety net)." -[ "$want_codex" = 1 ] && ok "The configured footer appears in the next Codex session." +[ "$want_codex" = 1 ] && ok "The configured footer appears in the next Codex session." [ "$want_claude" = 1 ] && ok "The statusline appears at the bottom of Claude Code on its next render." +[ "$want_grok" = 1 ] && ok "Grok is ready once PATH/auth are set — start it with: grok" note "Add projects later by editing the projects=\"...\" block in ~/.tmux-login.sh." diff --git a/tests/test.sh b/tests/test.sh index e17eb54..5a59c96 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -25,4 +25,17 @@ printf 'model = "test"\n' > "$tmp_dir/config.toml" bash setup.sh --write-codex-status "$tmp_dir/config.toml" grep -q '^\[tui\]$' "$tmp_dir/config.toml" +# Help lists all agent flags. +help_out="$(bash setup.sh --help)" +printf '%s\n' "$help_out" | grep -q -- '--grok' +printf '%s\n' "$help_out" | grep -q -- '--all' +printf '%s\n' "$help_out" | grep -q -- '--both' + +# Grok PATH helper: adds once, stays idempotent. +printf 'export PATH="/usr/bin"\n' > "$tmp_dir/bashrc" +bash setup.sh --ensure-grok-path "$tmp_dir/bashrc" +grep -qF '.grok/bin' "$tmp_dir/bashrc" +bash setup.sh --ensure-grok-path "$tmp_dir/bashrc" +[ "$(grep -c '>>> grok installer >>>' "$tmp_dir/bashrc")" -eq 1 ] + echo "all tests passed"