diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b62e96..e7c4ed2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,19 +42,19 @@ jobs: if: runner.os != 'Windows' run: | cd target/${{ matrix.target }}/release - tar czf ../../../term-${{ matrix.target }}.tar.gz term + tar czf ../../../ridge-${{ matrix.target }}.tar.gz ridge cd ../../.. - name: Package (Windows) if: runner.os == 'Windows' run: | cd target/${{ matrix.target }}/release - 7z a ../../../term-${{ matrix.target }}.zip term.exe + 7z a ../../../ridge-${{ matrix.target }}.zip ridge.exe cd ../../.. - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: term-${{ matrix.target }} - path: term-${{ matrix.target }}.* + name: ridge-${{ matrix.target }} + path: ridge-${{ matrix.target }}.* release: name: Create Release @@ -67,37 +67,5 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: term-* + files: ridge-* generate_release_notes: true - - # Updates the Homebrew tap formula after a release. Runs only when the - # HOMEBREW_TAP_TOKEN secret is configured (mapped to env for the `if`). - update-tap: - name: Update Homebrew Tap - needs: release - runs-on: ubuntu-latest - env: - TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} - TAG_NAME: ${{ github.ref_name }} - steps: - - name: Has token? - id: guard - if: env.TAP_TOKEN != '' - run: echo "present=1" >> "$GITHUB_OUTPUT" - - if: steps.guard.outputs.present == '1' - uses: actions/checkout@v4 - with: - repository: GrayCodeAI/homebrew-tap - token: ${{ secrets.HOMEBREW_TAP_TOKEN }} - path: tap - - if: steps.guard.outputs.present == '1' - name: Update formula - run: | - SHA=$(curl -fsSL "https://github.com/GrayCodeAI/term/archive/refs/tags/${TAG_NAME}.tar.gz" | sha256sum | awk '{print $1}') - cd tap - sed -i "s#url \".*\"#url \"https://github.com/GrayCodeAI/term/archive/refs/tags/${TAG_NAME}.tar.gz\"#" Formula/term.rb - sed -i "s/sha256 \".*\"/sha256 \"${SHA}\"/" Formula/term.rb - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - git commit -am "chore: update term to ${TAG_NAME}" || echo "no changes" - git push diff --git a/.gitignore b/.gitignore index b2a9e07..cca0aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,10 @@ coverage/ .cargo/vendor/ *.rs.bak -# Term application data +# Ridge application data +.ridge/ +.ridge-*.json +.ridge-worktrees/ .term/ .term-*.json .term-worktrees/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fab28ac..53659d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ -# Contributing to Term +# Contributing to Ridge -Thank you for considering contributing to Term! We welcome bug reports, feature +Thank you for considering contributing to Ridge! We welcome bug reports, feature requests, and pull requests from the community. ## Getting Started 1. Fork the repository on GitHub. -2. Clone your fork: `git clone https://github.com//term.git` +2. Clone your fork: `git clone https://github.com//ridge.git` 3. Create a feature branch: `git checkout -b feature/your-feature-name` 4. Install Rust: https://rustup.rs/ 5. Build: `cargo build` @@ -27,7 +27,7 @@ requests, and pull requests from the community. ## Pull Request Process 1. Push your branch to your fork. -2. Open a pull request against `main` on `GrayCodeAI/term`. +2. Open a pull request against `main` on `GrayCodeAI/ridge`. 3. A maintainer will review your changes and may request changes. 4. Once approved, a maintainer will merge your pull request. @@ -40,7 +40,7 @@ requests, and pull requests from the community. ## Reporting Bugs -Open an issue at https://github.com/GrayCodeAI/term/issues with: +Open an issue at https://github.com/GrayCodeAI/ridge/issues with: - A clear title and description - Steps to reproduce diff --git a/Cargo.lock b/Cargo.lock index f237200..81bd836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,7 +1540,7 @@ dependencies = [ ] [[package]] -name = "term" +name = "ridge" version = "1.0.0" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 22423af..c1ffe8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "term" +name = "ridge" version = "1.0.0" edition = "2024" -description = "Mission control for your AI agents. Terminal-native TUI with 11 agent adapters, task orchestration, diff review, and session management." +description = "Mission control for your AI agents. Terminal-native TUI with 12 agent adapters, task orchestration, diff review, and session management." license = "MIT" -repository = "https://github.com/GrayCodeAI/term" -homepage = "https://github.com/GrayCodeAI/term" -documentation = "https://github.com/GrayCodeAI/term/tree/main/docs" +repository = "https://github.com/GrayCodeAI/ridge" +homepage = "https://github.com/GrayCodeAI/ridge" +documentation = "https://github.com/GrayCodeAI/ridge/tree/main/docs" readme = "README.md" keywords = ["tui", "terminal", "ai", "agents", "claude"] categories = ["command-line-utilities", "development-tools"] @@ -14,7 +14,7 @@ authors = ["GrayCodeAI"] rust-version = "1.87" [[bin]] -name = "term" +name = "ridge" path = "src/main.rs" [dependencies] diff --git a/Formula/ridge.rb b/Formula/ridge.rb new file mode 100644 index 0000000..89daafb --- /dev/null +++ b/Formula/ridge.rb @@ -0,0 +1,16 @@ +class Ridge < Formula + desc "Mission control for your AI agents. Terminal-native TUI." + homepage "https://github.com/GrayCodeAI/ridge" + license "MIT" + head "https://github.com/GrayCodeAI/ridge.git", branch: "main" + + depends_on "rust" => :build + + def install + system "cargo", "install", *std_cargo_args + end + + test do + assert_match "ridge", shell_output("#{bin}/ridge --version") + end +end diff --git a/Formula/term.rb b/Formula/term.rb deleted file mode 100644 index fd3514b..0000000 --- a/Formula/term.rb +++ /dev/null @@ -1,18 +0,0 @@ -class Term < Formula - desc "Mission control for your AI agents. Terminal-native TUI." - homepage "https://gryacodeai.com/term" - url "https://github.com/GrayCodeAI/term/archive/refs/tags/v0.1.0.tar.gz" - sha256 "3ac9fed1033461acaedf9ace6ec9718dceed05a300fd3f5d4806ae437b336be6" - license "MIT" - head "https://github.com/GrayCodeAI/term.git", branch: "main" - - depends_on "rust" => :build - - def install - system "cargo", "install", *std_cargo_args - end - - test do - assert_match "term", shell_output("#{bin}/term --version") - end -end \ No newline at end of file diff --git a/Makefile b/Makefile index c418c8f..6e65587 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Term — Makefile +# Ridge — Makefile .PHONY: build test clippy fmt check run install clean help @@ -32,7 +32,7 @@ check: fmt-check clippy test ## Run all checks (fmt, clippy, test) run: build ## Build and run the binary $(CARGO) run -install: ## Install term to ~/.cargo/bin +install: ## Install ridge to ~/.cargo/bin $(CARGO) install --path . clean: ## Remove build artifacts diff --git a/README.md b/README.md index 0408c3b..74fa857 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ -# Term +# Ridge Mission control for your AI agents. Terminal-native TUI. Part of [GrayCodeAI](https://github.com/GrayCodeAI). -[![CI](https://github.com/GrayCodeAI/term/actions/workflows/ci.yml/badge.svg)](https://github.com/GrayCodeAI/term/actions/workflows/ci.yml) -[![Release](https://github.com/GrayCodeAI/term/actions/workflows/release.yml/badge.svg)](https://github.com/GrayCodeAI/term/actions/workflows/release.yml) -[![Security Audit](https://github.com/GrayCodeAI/term/actions/workflows/audit.yml/badge.svg)](https://github.com/GrayCodeAI/term/actions/workflows/audit.yml) -[![Crates.io](https://img.shields.io/crates/v/term.svg)](https://crates.io/crates/term) +Formerly **Term**. The command and package are now `ridge`. Existing Term configuration, session data, task boards, race history, worktrees, and module manifests are read when their Ridge equivalents do not exist. + +[![CI](https://github.com/GrayCodeAI/ridge/actions/workflows/ci.yml/badge.svg)](https://github.com/GrayCodeAI/ridge/actions/workflows/ci.yml) +[![Release](https://github.com/GrayCodeAI/ridge/actions/workflows/release.yml/badge.svg)](https://github.com/GrayCodeAI/ridge/actions/workflows/release.yml) +[![Security Audit](https://github.com/GrayCodeAI/ridge/actions/workflows/audit.yml/badge.svg)](https://github.com/GrayCodeAI/ridge/actions/workflows/audit.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ## Status -Full mission control: 11 agent adapters (Claude, Codex, OpenCode, Gemini, Copilot, Aider, Kimi, Pi, Grok, Cursor, Generic), PTY terminal emulation, task orchestration with worktrees/leases/quality-gates/merge, session persistence, cost tracking, modules, themes, and mobile-width mode. 23 tests, CI on 3 OS. See `CHANGELOG.md`. +Full mission control: 12 agent adapters (Reflect, Claude, Codex, OpenCode, Gemini, Copilot, Aider, Kimi, Pi, Grok, Cursor, Generic), PTY terminal emulation, task orchestration with worktrees/leases/quality-gates/merge, session persistence, cost tracking, modules, themes, and mobile-width mode. See `CHANGELOG.md`. ## Docs @@ -18,13 +19,13 @@ Full mission control: 11 agent adapters (Claude, Codex, OpenCode, Gemini, Copilo ## Scripting & diagnostics -- `term --headless` — machine-readable agent/branch/status/task facts -- `term --dashboard` — full mission-control state as JSON -- `term --doctor` — check which agent CLIs / tools are installed +- `ridge --headless` — machine-readable agent/branch/status/task facts +- `ridge --dashboard` — full mission-control state as JSON +- `ridge --doctor` — check which agent CLIs / tools are installed ## Session timelines -Every pane's raw PTY output is continuously logged to `/timelines/pane-.log` — replay what an agent did while you were away with `less ~/.local/share/term/timelines/pane-1.log`, `:timeline view ` (live replay pane inside Term, follows new output), or `:timeline export postmortem.log`. A companion `.meta` file records timestamped byte-offset milestones for time-scanning. SSH/remote panes get status detection (blocked/idle/working) via the generic heuristics. +Every pane's raw PTY output is continuously logged to `/timelines/pane-.log` — replay what an agent did while you were away with `less ~/.local/share/ridge/timelines/pane-1.log`, `:timeline view ` (live replay pane inside Ridge, follows new output), or `:timeline export postmortem.log`. A companion `.meta` file records timestamped byte-offset milestones for time-scanning. SSH/remote panes get status detection (blocked/idle/working) via the generic heuristics. ## Build @@ -39,13 +40,11 @@ cargo run # From source cargo install --path . -# Via install script -curl -fsSL https://gryacodeai.com/term/install.sh | sh +# Via install script (after the first Ridge release) +curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/ridge/main/install.sh | sh -# Via Homebrew (once tap is published) -brew install gryacodeai/tap/term # or restore last session's panes -term --restore +ridge --restore ``` ## CI / Release diff --git a/SECURITY.md b/SECURITY.md index 1b4d59d..3838d96 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,7 +3,7 @@ ## Reporting a vulnerability Please report security issues privately via -[GitHub Security Advisories](https://github.com/GrayCodeAI/term/security/advisories/new) +[GitHub Security Advisories](https://github.com/GrayCodeAI/ridge/security/advisories/new) rather than opening a public issue. ## Hardening in this codebase @@ -14,7 +14,7 @@ rather than opening a public issue. empty branch segments. - **Read-only diff review**: the diff tab never mutates the repository. - **Modules**: run as local executables you installed yourself from - `~/.config/term/modules/` — like any script in your PATH, only add ones you trust. + `~/.config/ridge/modules/` — like any script in your PATH, only add ones you trust. ## Audits diff --git a/docs/README.md b/docs/README.md index d6288d1..2c55a51 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,12 @@ -# Term Documentation +# Ridge Documentation -Term is a terminal-native mission control for AI coding agents. +Ridge is a terminal-native mission control for AI coding agents. ## Quick start ```sh -cargo install --git https://github.com/GrayCodeAI/term -term +cargo install --git https://github.com/GrayCodeAI/ridge +ridge ``` ## Keybindings @@ -15,12 +15,12 @@ See [README](../README.md#keybindings). ## Commands -`:help` inside Term lists all commands. See README for full list. +`:help` inside Ridge lists all commands. See README for full list. ## Modules -Place executable scripts in `~/.config/term/modules/`. They receive a `status` argument and print one line to stdout. +Place executable scripts in `~/.config/ridge/modules/`. They receive a `status` argument and print one line to stdout. ## Configuration -`~/.config/term/term.toml` — theme, keybindings, accounts. +`~/.config/ridge/ridge.toml` — theme, keybindings, accounts. diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c631adc..78b73e8 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,6 +1,6 @@ -# Term Documentation +# Ridge Documentation -Welcome to Term docs. +Welcome to Ridge docs. ## Getting Started - [Installation](./installation.md) diff --git a/docs/agents.md b/docs/agents.md index ad6a550..59e97bc 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -1,6 +1,6 @@ # Agent Adapters -Term uses an `Agent` trait to support multiple coding agents. +Ridge uses an `Agent` trait to support multiple coding agents. ## Implementing a new adapter diff --git a/docs/book.toml b/docs/book.toml index 2aef415..d5ff099 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -1,5 +1,5 @@ [book] -title = "Term — Mission Control for AI Agents" +title = "Ridge — Mission Control for AI Agents" authors = ["GrayCodeAI"] description = "Terminal-native TUI mission control for coding agents." diff --git a/docs/commands.md b/docs/commands.md index 8645a54..d037c4e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -28,7 +28,7 @@ Type `:` to enter command mode. - `task subagent ` — spawn subagent - `race ` — start a fan-out race: claude, codex, and opencode each get their own git worktree + branch, and the prompt is delegated to them immediately - `race` / `race status` — show contestants, statuses, gates, and the winner (★) -- `race leaderboard` — aggregated wins per agent from past races (persisted to `/.term-races.json`, last 50 outcomes) plus the three most recent races +- `race leaderboard` — aggregated wins per agent from past races (persisted to `/.ridge-races.json`, last 50 outcomes) plus the three most recent races - `race gate ` — run the quality gate in one contestant's worktree (`cargo test` by default for Rust repos; `:race gate-cmd ` overrides) - `race winner [force]` — gate the contestant, merge its branch, and clean up the losers; a failed gate blocks promotion unless `force` is given - `race cancel` — close all race panes and remove their worktrees @@ -46,23 +46,23 @@ Type `:` to enter command mode. ## Headless -Run `term --headless` (no TUI) to print machine-readable facts for scripts: registered agents, current branch, `git status` lines, and persisted tasks. +Run `ridge --headless` (no TUI) to print machine-readable facts for scripts: registered agents, current branch, `git status` lines, and persisted tasks. ## Dashboard JSON -Run `term --dashboard` (no TUI) to print the full mission-control state as pretty JSON: pane list (status/cost/attention/session id), active race with contestants and gate results, queued prompts, inbox counters, task-board summary with ready ids, module count, and race-history length. +Run `ridge --dashboard` (no TUI) to print the full mission-control state as pretty JSON: pane list (status/cost/attention/session id), active race with contestants and gate results, queued prompts, inbox counters, task-board summary with ready ids, module count, and race-history length. ## Doctor -Run `term --doctor` to sanity-check the environment and exit: each agent CLI found on PATH (claude, codex, opencode, gemini, copilot, aider, kimi, pi, grok, cursor-agent), git and gh availability, and that the data dir is writable. Exit code stays 0; missing items are reported with hints. Add `--fix` (`term --doctor --fix`) to print concrete copy-paste install steps (`→ [Agent] npm install -g …`) for every failing check. +Run `ridge --doctor` to sanity-check the environment and exit: each agent CLI found on PATH (claude, codex, opencode, gemini, copilot, aider, kimi, pi, grok, cursor-agent), git and gh availability, and that the data dir is writable. Exit code stays 0; missing items are reported with hints. Add `--fix` (`ridge --doctor --fix`) to print concrete copy-paste install steps (`→ [Agent] npm install -g …`) for every failing check. ## Timelines Every pane's raw output is continuously logged under `/timelines/pane-.log`. A companion `pane-.meta` file records timestamped byte-offset milestones (every 4 KiB of activity or 30 s of silence) so post-mortems can be scrubbed over time: ```sh -less ~/.local/share/term/timelines/pane-1.log # raw session replay -cat ~/.local/share/term/timelines/pane-1.meta # "epoch_ms bytes" milestones +less ~/.local/share/ridge/timelines/pane-1.log # raw session replay +cat ~/.local/share/ridge/timelines/pane-1.meta # "epoch_ms bytes" milestones ``` -Inside Term, `:timeline view ` opens a replay pane that streams the log live. +Inside Ridge, `:timeline view ` opens a replay pane that streams the log live. diff --git a/docs/installation.md b/docs/installation.md index ba7ffde..ec175c5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,16 +2,10 @@ ## From source ```sh -cargo install --git https://github.com/GrayCodeAI/term +cargo install --git https://github.com/GrayCodeAI/ridge ``` -## Install script +## Install script (after the first Ridge release) ```sh -curl -fsSL https://gryacodeai.com/term/install.sh | sh -``` - -## Homebrew -```sh -brew tap GrayCodeAI/tap -brew install term +curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/ridge/main/install.sh | sh ``` diff --git a/docs/modules.md b/docs/modules.md index b7c3746..5d5c644 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -1,12 +1,12 @@ # Modules -Term supports modules in any language via a stdio JSON protocol. +Ridge supports modules in any language via a stdio JSON protocol. ## Location -Place executable scripts in `~/.config/term/modules/`. +Place executable scripts in `~/.config/ridge/modules/`. ## Protocol -When Term runs a module, it invokes it with a single argument: `status`. The module should print one line to stdout. That line becomes the widget text in the Settings tab and status bar. +When Ridge runs a module, it invokes it with a single argument: `status`. The module should print one line to stdout. That line becomes the widget text in the Settings tab and status bar. ## Example ```sh @@ -17,15 +17,15 @@ case "$1" in esac ``` -Make it executable: `chmod +x ~/.config/term/modules/hello.sh` +Make it executable: `chmod +x ~/.config/ridge/modules/hello.sh` ## Environment -Modules receive live state via the `TERM_JSON` environment variable: +Modules receive live state via the `RIDGE_JSON` environment variable: ```sh #!/bin/sh -echo "panes=$TERM_JSON" # {"panes":2,"blocked":1,"tasks":5} +echo "panes=$RIDGE_JSON" # {"panes":2,"blocked":1,"tasks":5} ``` ## Refresh @@ -33,5 +33,5 @@ Modules are refreshed every 20 ticks (~5 seconds). Output is displayed in the Se ## Marketplace -`:module install [name]` downloads a script into ~/.config/term/modules/ (via curl, made executable). `:module list` / `:module remove ` manage them. Only install scripts you have read and trust — modules run with your user privileges. +`:module install [name]` downloads a script into ~/.config/ridge/modules/ (via curl, made executable). `:module list` / `:module remove ` manage them. Only install scripts you have read and trust — modules run with your user privileges. A module registry is planned for v1.0. diff --git a/docs/src/agents.md b/docs/src/agents.md index ad6a550..59e97bc 100644 --- a/docs/src/agents.md +++ b/docs/src/agents.md @@ -1,6 +1,6 @@ # Agent Adapters -Term uses an `Agent` trait to support multiple coding agents. +Ridge uses an `Agent` trait to support multiple coding agents. ## Implementing a new adapter diff --git a/docs/src/commands.md b/docs/src/commands.md index 8645a54..d037c4e 100644 --- a/docs/src/commands.md +++ b/docs/src/commands.md @@ -28,7 +28,7 @@ Type `:` to enter command mode. - `task subagent ` — spawn subagent - `race ` — start a fan-out race: claude, codex, and opencode each get their own git worktree + branch, and the prompt is delegated to them immediately - `race` / `race status` — show contestants, statuses, gates, and the winner (★) -- `race leaderboard` — aggregated wins per agent from past races (persisted to `/.term-races.json`, last 50 outcomes) plus the three most recent races +- `race leaderboard` — aggregated wins per agent from past races (persisted to `/.ridge-races.json`, last 50 outcomes) plus the three most recent races - `race gate ` — run the quality gate in one contestant's worktree (`cargo test` by default for Rust repos; `:race gate-cmd ` overrides) - `race winner [force]` — gate the contestant, merge its branch, and clean up the losers; a failed gate blocks promotion unless `force` is given - `race cancel` — close all race panes and remove their worktrees @@ -46,23 +46,23 @@ Type `:` to enter command mode. ## Headless -Run `term --headless` (no TUI) to print machine-readable facts for scripts: registered agents, current branch, `git status` lines, and persisted tasks. +Run `ridge --headless` (no TUI) to print machine-readable facts for scripts: registered agents, current branch, `git status` lines, and persisted tasks. ## Dashboard JSON -Run `term --dashboard` (no TUI) to print the full mission-control state as pretty JSON: pane list (status/cost/attention/session id), active race with contestants and gate results, queued prompts, inbox counters, task-board summary with ready ids, module count, and race-history length. +Run `ridge --dashboard` (no TUI) to print the full mission-control state as pretty JSON: pane list (status/cost/attention/session id), active race with contestants and gate results, queued prompts, inbox counters, task-board summary with ready ids, module count, and race-history length. ## Doctor -Run `term --doctor` to sanity-check the environment and exit: each agent CLI found on PATH (claude, codex, opencode, gemini, copilot, aider, kimi, pi, grok, cursor-agent), git and gh availability, and that the data dir is writable. Exit code stays 0; missing items are reported with hints. Add `--fix` (`term --doctor --fix`) to print concrete copy-paste install steps (`→ [Agent] npm install -g …`) for every failing check. +Run `ridge --doctor` to sanity-check the environment and exit: each agent CLI found on PATH (claude, codex, opencode, gemini, copilot, aider, kimi, pi, grok, cursor-agent), git and gh availability, and that the data dir is writable. Exit code stays 0; missing items are reported with hints. Add `--fix` (`ridge --doctor --fix`) to print concrete copy-paste install steps (`→ [Agent] npm install -g …`) for every failing check. ## Timelines Every pane's raw output is continuously logged under `/timelines/pane-.log`. A companion `pane-.meta` file records timestamped byte-offset milestones (every 4 KiB of activity or 30 s of silence) so post-mortems can be scrubbed over time: ```sh -less ~/.local/share/term/timelines/pane-1.log # raw session replay -cat ~/.local/share/term/timelines/pane-1.meta # "epoch_ms bytes" milestones +less ~/.local/share/ridge/timelines/pane-1.log # raw session replay +cat ~/.local/share/ridge/timelines/pane-1.meta # "epoch_ms bytes" milestones ``` -Inside Term, `:timeline view ` opens a replay pane that streams the log live. +Inside Ridge, `:timeline view ` opens a replay pane that streams the log live. diff --git a/docs/src/installation.md b/docs/src/installation.md index ba7ffde..ec175c5 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -2,16 +2,10 @@ ## From source ```sh -cargo install --git https://github.com/GrayCodeAI/term +cargo install --git https://github.com/GrayCodeAI/ridge ``` -## Install script +## Install script (after the first Ridge release) ```sh -curl -fsSL https://gryacodeai.com/term/install.sh | sh -``` - -## Homebrew -```sh -brew tap GrayCodeAI/tap -brew install term +curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/ridge/main/install.sh | sh ``` diff --git a/docs/src/intro.md b/docs/src/intro.md index d6288d1..2c55a51 100644 --- a/docs/src/intro.md +++ b/docs/src/intro.md @@ -1,12 +1,12 @@ -# Term Documentation +# Ridge Documentation -Term is a terminal-native mission control for AI coding agents. +Ridge is a terminal-native mission control for AI coding agents. ## Quick start ```sh -cargo install --git https://github.com/GrayCodeAI/term -term +cargo install --git https://github.com/GrayCodeAI/ridge +ridge ``` ## Keybindings @@ -15,12 +15,12 @@ See [README](../README.md#keybindings). ## Commands -`:help` inside Term lists all commands. See README for full list. +`:help` inside Ridge lists all commands. See README for full list. ## Modules -Place executable scripts in `~/.config/term/modules/`. They receive a `status` argument and print one line to stdout. +Place executable scripts in `~/.config/ridge/modules/`. They receive a `status` argument and print one line to stdout. ## Configuration -`~/.config/term/term.toml` — theme, keybindings, accounts. +`~/.config/ridge/ridge.toml` — theme, keybindings, accounts. diff --git a/docs/src/modules.md b/docs/src/modules.md index b7c3746..5d5c644 100644 --- a/docs/src/modules.md +++ b/docs/src/modules.md @@ -1,12 +1,12 @@ # Modules -Term supports modules in any language via a stdio JSON protocol. +Ridge supports modules in any language via a stdio JSON protocol. ## Location -Place executable scripts in `~/.config/term/modules/`. +Place executable scripts in `~/.config/ridge/modules/`. ## Protocol -When Term runs a module, it invokes it with a single argument: `status`. The module should print one line to stdout. That line becomes the widget text in the Settings tab and status bar. +When Ridge runs a module, it invokes it with a single argument: `status`. The module should print one line to stdout. That line becomes the widget text in the Settings tab and status bar. ## Example ```sh @@ -17,15 +17,15 @@ case "$1" in esac ``` -Make it executable: `chmod +x ~/.config/term/modules/hello.sh` +Make it executable: `chmod +x ~/.config/ridge/modules/hello.sh` ## Environment -Modules receive live state via the `TERM_JSON` environment variable: +Modules receive live state via the `RIDGE_JSON` environment variable: ```sh #!/bin/sh -echo "panes=$TERM_JSON" # {"panes":2,"blocked":1,"tasks":5} +echo "panes=$RIDGE_JSON" # {"panes":2,"blocked":1,"tasks":5} ``` ## Refresh @@ -33,5 +33,5 @@ Modules are refreshed every 20 ticks (~5 seconds). Output is displayed in the Se ## Marketplace -`:module install [name]` downloads a script into ~/.config/term/modules/ (via curl, made executable). `:module list` / `:module remove ` manage them. Only install scripts you have read and trust — modules run with your user privileges. +`:module install [name]` downloads a script into ~/.config/ridge/modules/ (via curl, made executable). `:module list` / `:module remove ` manage them. Only install scripts you have read and trust — modules run with your user privileges. A module registry is planned for v1.0. diff --git a/docs/src/socket-api.md b/docs/src/socket-api.md index 0baccf3..83ae14e 100644 --- a/docs/src/socket-api.md +++ b/docs/src/socket-api.md @@ -1,19 +1,19 @@ -# Socket API (`term --api`) +# Socket API (`ridge --api`) -Term can run as a background socket server, accepting JSON commands from +Ridge can run as a background socket server, accepting JSON commands from external processes (agents, scripts, orchestrators). This is the same agent-native model used by Herdr. ## Start the Server ```bash -term --api +ridge --api ``` This starts a Unix domain socket server at: ``` -~/.local/share/term/term.sock +~/.local/share/ridge/ridge.sock ``` ## Protocol @@ -153,7 +153,7 @@ Close a pane by its ID. ```python import socket, json -SOCK = "/home/user/.local/share/term/term.sock" +SOCK = "/home/user/.local/share/ridge/ridge.sock" def send(cmd): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) @@ -178,5 +178,5 @@ print(send({"cmd": "SendMessage", "pane": 1, "message": "hello"})) ```bash # Note: curl doesn't support Unix domain sockets on all versions. # Use socat or nc -U instead: -echo '{"cmd": "ListPanes"}' | socat - UNIX-CONNECT:~/.local/share/term/term.sock +echo '{"cmd": "ListPanes"}' | socat - UNIX-CONNECT:~/.local/share/ridge/ridge.sock ``` diff --git a/docs/src/uhp.md b/docs/src/uhp.md index b4d3ba7..ccb4060 100644 --- a/docs/src/uhp.md +++ b/docs/src/uhp.md @@ -1,9 +1,9 @@ # UHP 1.0 — Universal Harness Protocol -Term implements the Universal Harness Protocol (UHP) 1.0, a versioned +Ridge implements the Universal Harness Protocol (UHP) 1.0, a versioned method registry for orchestrators and external automation. UHP runs over the same Unix domain socket as the agent-native socket API -(`term --api`), but uses JSON-RPC 2.0 method calls. +(`ridge --api`), but uses JSON-RPC 2.0 method calls. ## Protocol @@ -86,7 +86,7 @@ receive event objects when they fire. ```python import socket, json -SOCK = "/home/user/.local/share/term/term.sock" +SOCK = "/home/user/.local/share/ridge/ridge.sock" def uhp_call(method, params=None, id=1): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) diff --git a/examples/modules/README.md b/examples/modules/README.md index 120d563..9437043 100644 --- a/examples/modules/README.md +++ b/examples/modules/README.md @@ -1,17 +1,17 @@ -# Term Example Modules +# Ridge Example Modules -This directory contains example modules demonstrating Term's module system +This directory contains example modules demonstrating Ridge's module system (see `src/core/modules.rs`). ## Installing a Module -Copy a module directory to `~/.config/term/modules/`: +Copy a module directory to `~/.config/ridge/modules/`: ```bash -cp -r examples/modules/demo-module ~/.config/term/modules/ +cp -r examples/modules/demo-module ~/.config/ridge/modules/ ``` -Then in Term, run: +Then in Ridge, run: ``` :module list @@ -22,7 +22,7 @@ Then in Term, run: ## Module System Modules are executables or scripts dropped into -`~/.config/term/modules/`. Each module can declare a `term-module.toml` +`~/.config/ridge/modules/`. Each module can declare a `ridge-module.toml` manifest with: - **startup** hooks - commands run when the module loads @@ -33,7 +33,7 @@ manifest with: - **settings** - user-editable configuration - **worktree_provider** - custom worktree create/remove via JSON protocol -Modules receive `$TERM_JSON` (live app state as JSON) on their stdin/stdout. +Modules receive `$RIDGE_JSON` (live app state as JSON) on their stdin/stdout. ## Example: demo-module @@ -42,7 +42,7 @@ The `demo-module` demonstrates all manifest features: ``` :module list # List installed modules :module run say-hello # Run an action -:module run show-panes # Run an action with TERM_JSON +:module run show-panes # Run an action with RIDGE_JSON ``` See `src/core/modules.rs` for the full module API specification. diff --git a/examples/modules/demo-module/demo-module.sh b/examples/modules/demo-module/demo-module.sh index 8375b0a..f9f1b1d 100644 --- a/examples/modules/demo-module/demo-module.sh +++ b/examples/modules/demo-module/demo-module.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Demo module for Term - demonstrates the module system. -# Drop this into ~/.config/term/modules/ and run 'module list' in Term. +# Demo module for Ridge - demonstrates the module system. +# Drop this into ~/.config/ridge/modules/ and run 'module list' in Ridge. # -# Receives $TERM_JSON environment variable with live app state. +# Receives $RIDGE_JSON environment variable with live app state. # Outputs a status line that appears in `module list`. read -r term_json diff --git a/examples/modules/demo-module/term-module.toml b/examples/modules/demo-module/ridge-module.toml similarity index 85% rename from examples/modules/demo-module/term-module.toml rename to examples/modules/demo-module/ridge-module.toml index af6477d..9554f6a 100644 --- a/examples/modules/demo-module/term-module.toml +++ b/examples/modules/demo-module/ridge-module.toml @@ -1,7 +1,7 @@ id = "demo-module" name = "Demo Module" version = "1.0.0" -description = "Example module demonstrating the Term module system" +description = "Example module demonstrating the Ridge module system" platforms = ["linux", "macos"] # Startup hooks run when the module is loaded @@ -19,7 +19,7 @@ command = ["echo", "Hello from demo-module!"] id = "show-panes" name = "Show Panes" description = "Lists active panes" -command = ["sh", "-c", "echo 'Active panes: $TERM_JSON' | python3 -c 'import sys,json;d=json.load(sys.stdin);[print(f\" {p}\") for p in range(d.get(\"panes\",0))]'"] +command = ["sh", "-c", "echo 'Active panes: $RIDGE_JSON' | python3 -c 'import sys,json;d=json.load(sys.stdin);[print(f\" {p}\") for p in range(d.get(\"panes\",0))]'"] # Event hooks fire on app events [[events]] diff --git a/examples/modules/hello/hello.sh b/examples/modules/hello/hello.sh index 8d2192b..0c6fd45 100755 --- a/examples/modules/hello/hello.sh +++ b/examples/modules/hello/hello.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Example Term module. Place in ~/.config/term/modules/ and chmod +x. +# Example Ridge module. Place in ~/.config/ridge/modules/ and chmod +x. case "$1" in status) echo "hello from module" diff --git a/install.sh b/install.sh index 0a9dc57..ba327cd 100755 --- a/install.sh +++ b/install.sh @@ -1,11 +1,11 @@ #!/bin/sh -# Install Term — mission control for your AI agents. -# Usage: curl -fsSL https://gryacodeai.com/term/install.sh | sh +# Install Ridge — mission control for your AI agents. +# Usage: curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/ridge/main/install.sh | sh set -eu -REPO="GrayCodeAI/term" -BIN="term" -INSTALL_DIR="${TERM_INSTALL_DIR:-$HOME/.local/bin}" +REPO="GrayCodeAI/ridge" +BIN="ridge" +INSTALL_DIR="${RIDGE_INSTALL_DIR:-$HOME/.local/bin}" OS="$(uname -s)" ARCH="$(uname -m)" @@ -26,23 +26,26 @@ else TARGET="${ARCH_SLUG}-${OS_SLUG}" fi -TAG="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*//p')" +TAG="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')" if [ -z "$TAG" ]; then echo "Could not determine latest release." >&2 exit 1 fi -URL="https://github.com/${REPO}/releases/download/${TAG}/term-${TARGET}.tar.gz" -echo "Downloading Term ${TAG} (${TARGET})..." +URL="https://github.com/${REPO}/releases/download/${TAG}/ridge-${TARGET}.tar.gz" +echo "Downloading Ridge ${TAG} (${TARGET})..." mkdir -p "$INSTALL_DIR" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT -curl -fsSL "$URL" -o "$TMP/term.tar.gz" -tar xzf "$TMP/term.tar.gz" -C "$TMP" -install -m 0755 "$TMP/term" "$INSTALL_DIR/$BIN" +if ! curl -fsSL "$URL" -o "$TMP/ridge.tar.gz"; then + echo "No Ridge release asset found. Build from source: cargo install --git https://github.com/GrayCodeAI/ridge" >&2 + exit 1 +fi +tar xzf "$TMP/ridge.tar.gz" -C "$TMP" +install -m 0755 "$TMP/ridge" "$INSTALL_DIR/$BIN" echo "Installed $BIN to $INSTALL_DIR/$BIN" case ":$PATH:" in *":$INSTALL_DIR:"*) ;; - *) echo "Add $INSTALL_DIR to your PATH to use 'term'." ;; + *) echo "Add $INSTALL_DIR to your PATH to use 'ridge'." ;; esac diff --git a/term-plan.md b/ridge-plan.md similarity index 93% rename from term-plan.md rename to ridge-plan.md index 901bb01..b55fdca 100644 --- a/term-plan.md +++ b/ridge-plan.md @@ -1,18 +1,18 @@ -# Term — Product Plan +# Ridge — Product Plan > Mission control for your AI agents. Terminal-native TUI. Part of Gryacodeai.com. ## 1. Vision & Positioning -**Term** is a Rust TUI that gives you one place to run, watch, resume, and orchestrate every coding agent — without leaving your terminal or breaking SSH/mobile workflows. +**Ridge** is a Rust TUI that gives you one place to run, watch, resume, and orchestrate every coding agent — without leaving your terminal or breaking SSH/mobile workflows. **Positioning vs. the field:** - vs. **tmux/Zellij**: adds agent-aware state, cost tracking, git/diff/files, orchestration -- vs. **Luvus**: similar scope, but Term is the Gryacodeai-branded, open-source take with a cleaner module system +- vs. **Luvus**: similar scope, but Ridge is the Gryacodeai-branded, open-source take with a cleaner module system - vs. **Herdr**: broader (not just runtime state — full mission control) - vs. **Orca/cmux**: terminal-native, no GUI lock-in, SSH-first -**Tagline:** "Term — mission control for your AI agents." +**Tagline:** "Ridge — mission control for your AI agents." ## 2. Target Users @@ -28,16 +28,16 @@ - **Async runtime:** tokio - **PTY:** portable-pty - **Git:** git2 (libgit2 bindings) + shelling out to `git`/`gh` where needed -- **Config:** serde + toml (`~/.config/term/term.toml`) +- **Config:** serde + toml (`~/.config/ridge/ridge.toml`) - **State persistence:** SQLite (rusqlite) for sessions/workspaces, or JSON files for v0.1 - **CLI args:** clap - **Logging:** tracing -- **Distribution:** single static binary via `cargo build --release`, plus `cargo install term` +- **Distribution:** single static binary via `cargo build --release`, plus `cargo install ridge` ## 4. Architecture ``` -term/ +ridge/ ├── src/ │ ├── main.rs # entrypoint, CLI (clap) │ ├── app.rs # top-level App state + event loop @@ -183,7 +183,7 @@ Even though v0.1 target is "full mission control", ship in phases to stay sane: ### Phase 6 — Release - README, docs, install script (`curl … | sh`) -- crates.io publish (`cargo install term`) +- crates.io publish (`cargo install ridge`) - GitHub releases with prebuilt binaries (Linux/macOS/Windows) - Homebrew tap @@ -231,12 +231,12 @@ Even though v0.1 target is "full mission control", ship in phases to stay sane: - [ ] Review a diff and send line comments to an agent - [ ] Create a task → worktree → pane → quality gate → merge flow end-to-end - [ ] Works over SSH from a phone (mobile-width mode) -- [ ] Single binary, `cargo install term` works +- [ ] Single binary, `cargo install ridge` works - [ ] README + install script + docs ## 11. Next Steps -1. Create repo `term` under your GitHub account +1. Create repo `ridge` under your GitHub account 2. Scaffold Cargo project (Phase 0) 3. Implement PTY + agent detection (Phase 1) 4. Iterate diff --git a/scripts/install.sh b/scripts/install.sh index 41bf77b..ebd499a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# term install script -# Installs the Term TUI mission control for AI agents. +# ridge install script +# Installs the Ridge TUI mission control for AI agents. set -euo pipefail -VERSION="${TERM_VERSION:-1.0.0}" -REPO="GrayCodeAI/term" -BINARY_NAME="term" +VERSION="${RIDGE_VERSION:-1.0.0}" +REPO="GrayCodeAI/ridge" +BINARY_NAME="ridge" # Detect platform OS="$(uname -s | tr '[:upper:]' '[:lower:]')" @@ -24,14 +24,14 @@ case "$OS" in esac # Determine install dir -INSTALL_DIR="${TERM_INSTALL_DIR:-$HOME/.local/bin}" +INSTALL_DIR="${RIDGE_INSTALL_DIR:-$HOME/.local/bin}" mkdir -p "$INSTALL_DIR" # Download from GitHub releases -ASSET_NAME="term-${VERSION}-${PLATFORM}.tar.gz" +ASSET_NAME="ridge-${PLATFORM}.tar.gz" DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${ASSET_NAME}" -echo "Downloading term ${VERSION} for ${PLATFORM}..." +echo "Downloading ridge ${VERSION} for ${PLATFORM}..." TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT @@ -47,20 +47,20 @@ tar -xzf "$TMPDIR/$ASSET_NAME" -C "$TMPDIR" # Find the binary BIN_PATH="" -if [ -f "$TMPDIR/term" ]; then - BIN_PATH="$TMPDIR/term" -elif [ -f "$TMPDIR/term-${VERSION}-${PLATFORM}/term" ]; then - BIN_PATH="$TMPDIR/term-${VERSION}-${PLATFORM}/term" +if [ -f "$TMPDIR/ridge" ]; then + BIN_PATH="$TMPDIR/ridge" +elif [ -f "$TMPDIR/ridge-${VERSION}-${PLATFORM}/ridge" ]; then + BIN_PATH="$TMPDIR/ridge-${VERSION}-${PLATFORM}/ridge" else - echo "Could not find term binary in archive" >&2; exit 1 + echo "Could not find ridge binary in archive" >&2; exit 1 fi -cp "$BIN_PATH" "$INSTALL_DIR/term" -chmod +x "$INSTALL_DIR/term" +cp "$BIN_PATH" "$INSTALL_DIR/ridge" +chmod +x "$INSTALL_DIR/ridge" -echo "Installed term to $INSTALL_DIR/term" +echo "Installed ridge to $INSTALL_DIR/ridge" echo "" echo "Add $INSTALL_DIR to your PATH if it's not already:" echo " export PATH=\"$INSTALL_DIR:\$PATH\"" echo "" -echo "Run 'term --help' to get started." +echo "Run 'ridge --help' to get started." diff --git a/src/agents/claude.rs b/src/agents/claude.rs index 904ec52..718810e 100644 --- a/src/agents/claude.rs +++ b/src/agents/claude.rs @@ -132,7 +132,7 @@ mod extra_tests { #[test] fn parse_cost_from_jsonl() { - let dir = std::env::temp_dir().join("term-claude-test"); + let dir = std::env::temp_dir().join("ridge-claude-test"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("session.jsonl"); diff --git a/src/agents/mod.rs b/src/agents/mod.rs index 46d443b..64856ee 100644 --- a/src/agents/mod.rs +++ b/src/agents/mod.rs @@ -9,6 +9,7 @@ pub mod grok; pub mod kimi; pub mod opencode; pub mod pi; +pub mod reflect; #[cfg(test)] mod detect_tests { @@ -26,7 +27,7 @@ mod detect_tests { use crate::agents::{ aider::AiderAgent, claude::ClaudeAgent, codex::CodexAgent, copilot::CopilotAgent, cursor::CursorAgent, gemini::GeminiAgent, grok::GrokAgent, kimi::KimiAgent, - opencode::OpenCodeAgent, pi::PiAgent, + opencode::OpenCodeAgent, pi::PiAgent, reflect::ReflectAgent, }; // (detect function, matching process name, matching cmdline) @@ -78,6 +79,11 @@ mod detect_tests { "cursor-agent", "cursor-agent -p x", ), + ( + Box::new(|p, c| ReflectAgent.detect(p, c)), + "reflect", + "reflect work", + ), ]; for (f, proc_name, cmdline) in &cases { assert!( @@ -90,7 +96,7 @@ mod detect_tests { #[test] fn registry_returns_all_adapters() { let r = crate::core::agent::AgentRegistry::new(); - assert_eq!(r.all().len(), 11); - assert_eq!(r.all()[10].id(), "generic"); // generic always last (fallback) + assert_eq!(r.all().len(), 12); + assert_eq!(r.all()[11].id(), "generic"); // generic always last (fallback) } } diff --git a/src/agents/reflect.rs b/src/agents/reflect.rs new file mode 100644 index 0000000..134cf83 --- /dev/null +++ b/src/agents/reflect.rs @@ -0,0 +1,107 @@ +use crate::core::agent::{Agent, AgentStatus, CostInfo}; +use portable_pty::CommandBuilder; +use std::path::{Path, PathBuf}; + +pub struct ReflectAgent; + +impl Agent for ReflectAgent { + fn exe(&self) -> Option<&'static str> { + Some("reflect") + } + fn id(&self) -> &'static str { + "reflect" + } + fn name(&self) -> &'static str { + "Reflect" + } + fn detect(&self, process_name: &str, cmdline: &str) -> bool { + let process = Path::new(process_name) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(""); + let command = cmdline.split_whitespace().next().unwrap_or(""); + process.eq_ignore_ascii_case("reflect") + || Path::new(command) + .file_name() + .and_then(|s| s.to_str()) + .map(|s| s.eq_ignore_ascii_case("reflect")) + .unwrap_or(false) + } + fn spawn_cmd(&self, cwd: &Path) -> CommandBuilder { + let mut cmd = CommandBuilder::new("reflect"); + cmd.cwd(cwd); + cmd + } + fn resume_cmd(&self, session_id: &str, cwd: &Path) -> Option { + let mut cmd = CommandBuilder::new("reflect"); + if session_id == "last" { + cmd.arg("--continue"); + } else { + cmd.arg("--resume"); + cmd.arg(session_id); + } + cmd.cwd(cwd); + Some(cmd) + } + fn session_files(&self, _cwd: &Path) -> Vec { + let base = std::env::var_os("REFLECT_STATE_DIR") + .map(PathBuf::from) + .or_else(|| { + std::env::var_os("XDG_STATE_HOME").map(|p| PathBuf::from(p).join("reflect")) + }) + .or_else(|| { + std::env::var_os("XDG_CONFIG_HOME") + .map(|p| PathBuf::from(p).join("reflect").join("state")) + }) + .or_else(|| { + std::env::var_os("HOME").map(|p| { + PathBuf::from(p) + .join(".config") + .join("reflect") + .join("state") + }) + }); + let Some(dir) = base.map(|p| p.join("sessions")) else { + return Vec::new(); + }; + let Ok(entries) = std::fs::read_dir(dir) else { + return Vec::new(); + }; + entries + .flatten() + .map(|e| e.path()) + .filter(|p| p.extension().map(|s| s == "jsonl").unwrap_or(false)) + .collect() + } + fn parse_status(&self, output_tail: &str) -> AgentStatus { + let tail = output_tail.to_lowercase(); + if tail.contains("allow this") || tail.contains("permission required") { + return AgentStatus::Blocked; + } + if tail.contains("ready for review") || tail.contains("to resume this session") { + return AgentStatus::Done; + } + if tail.contains("verifying commit") || tail.contains("thinking") { + return AgentStatus::Working; + } + AgentStatus::Unknown + } + fn parse_cost(&self, _session_file: &Path) -> Option { + None + } + fn default_resume_arg(&self) -> Option<&'static str> { + Some("last") + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn detects_only_reflect_process() { + let a = ReflectAgent; + assert!(a.detect("reflect", "reflect work --request hello")); + assert!(a.detect("/usr/local/bin/reflect", "/usr/local/bin/reflect")); + assert!(!a.detect("bash", "echo reflect")); + } +} diff --git a/src/app.rs b/src/app.rs index 6be933f..f2191d0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -234,7 +234,7 @@ impl App { config, current_tab: Tab::Agents, should_quit: false, - status_message: "Term — mission control for your AI agents".to_string(), + status_message: "Ridge — mission control for your AI agents".to_string(), tick_count: 0, registry: AgentRegistry::new(), panes: Vec::new(), @@ -427,7 +427,7 @@ impl App { lines } - /// Machine-readable dashboard state for `term --dashboard`. + /// Machine-readable dashboard state for `ridge --dashboard`. pub fn dashboard_json(&self) -> String { use serde_json::json; let panes: Vec<_> = self @@ -687,7 +687,7 @@ impl App { } } - /// Compact summary of live state, exposed to modules via $TERM_JSON. + /// Compact summary of live state, exposed to modules via $RIDGE_JSON. pub fn module_state_json(&self) -> String { let panes = self.panes.len(); let blocked = self @@ -1417,7 +1417,7 @@ impl App { p.cost_usd )); } - let path = self.cwd.join("term-costs.csv"); + let path = self.cwd.join("ridge-costs.csv"); match std::fs::write(&path, csv) { Ok(()) => { self.status_message = @@ -1427,7 +1427,7 @@ impl App { } } else if parts.len() >= 3 && parts[2] == "session" { // export session — snapshot of open panes as JSON - let file = parts.get(3).copied().unwrap_or("term-session.json"); + let file = parts.get(3).copied().unwrap_or("ridge-session.json"); let panes: Vec = self .panes .iter() @@ -1836,7 +1836,7 @@ impl App { Err(e) => self.status_message = format!("plugin remove failed: {e}"), } } else if parts.len() >= 2 && parts[1] == "marketplace" { - self.status_message = "plugin marketplace: https://github.com/GrayCodeAI/term-plugins\n :plugin install [name] — install from any URL\n :plugin list — list installed plugins\n :plugin remove — remove a plugin".to_string(); + self.status_message = "Plugins:\n :plugin install [name] — install from a URL\n :plugin list — list installed plugins\n :plugin remove — remove a plugin".to_string(); } else { self.status_message = "usage: plugin list | install [name] | remove | marketplace" @@ -2246,8 +2246,8 @@ pub struct PaneSnapshot { pub title: String, } -/// Persist open panes (agent ids + titles) to ~/.local/share/term/session.json -/// so `term --restore` can bring them back by resuming each agent's last session. +/// Persist open panes (agent ids + titles) to ~/.local/share/ridge/session.json +/// so `ridge --restore` can bring them back by resuming each agent's last session. pub fn save_session_file(app: &App) { let dir = crate::config::data_dir(); if std::fs::create_dir_all(&dir).is_err() { @@ -2429,7 +2429,7 @@ pub async fn run(config: Config, cli: Cli) -> Result<()> { } else { "finished" }; - crate::core::notify::alert(&format!("Term — {}", pane.title), why); + crate::core::notify::alert(&format!("Ridge — {}", pane.title), why); } } pane.status = new_status; @@ -2439,7 +2439,7 @@ pub async fn run(config: Config, cli: Cli) -> Result<()> { attention_hits += 1; if hooks_on { crate::core::notify::alert( - &format!("Term — {}", pane.title), + &format!("Ridge — {}", pane.title), "session ended", ); } @@ -2500,7 +2500,7 @@ pub async fn run(config: Config, cli: Cli) -> Result<()> { } } - // Persist open panes so the next `term --restore` can bring them back. + // Persist open panes so the next `ridge --restore` can bring them back. save_session_file(&app); disable_raw_mode()?; @@ -3084,7 +3084,7 @@ mod race_gate_tests { use std::process::Command; fn make_repo(suffix: &str) -> PathBuf { - let dir = std::env::temp_dir().join(format!("term-race-gate-repo-{suffix}")); + let dir = std::env::temp_dir().join(format!("ridge-race-gate-repo-{suffix}")); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); for c in [ @@ -3183,7 +3183,7 @@ mod race_gate_tests { #[test] fn default_gate_cmd_heuristic() { - let dir = std::env::temp_dir().join("term-gate-heur"); + let dir = std::env::temp_dir().join("ridge-gate-heur"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); assert_eq!(default_gate_cmd(&dir), "true"); @@ -3308,7 +3308,7 @@ mod race_history_tests { use std::process::Command; fn make_repo(suffix: &str) -> std::path::PathBuf { - let dir = std::env::temp_dir().join(format!("term-race-history-{suffix}")); + let dir = std::env::temp_dir().join(format!("ridge-race-history-{suffix}")); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); for c in [ @@ -3389,7 +3389,7 @@ mod check_command_tests { use std::process::Command; fn quiet_repo(suffix: &str) -> std::path::PathBuf { - let dir = std::env::temp_dir().join(format!("term-check-cmd-{suffix}")); + let dir = std::env::temp_dir().join(format!("ridge-check-cmd-{suffix}")); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); for c in [ @@ -3648,7 +3648,7 @@ mod dashboard_json_tests { #[test] fn timelines_tick_writes_pane_log() { - let base = std::env::temp_dir().join(format!("term-timeline-base-{}", std::process::id())); + let base = std::env::temp_dir().join(format!("ridge-timeline-base-{}", std::process::id())); let _ = std::fs::remove_dir_all(&base); let mut a = App::new(Config::default()); @@ -3770,7 +3770,7 @@ mod dashboard_json_tests { #[test] #[cfg(unix)] fn timeline_view_opens_replaying_pane() { - let base = std::env::temp_dir().join(format!("term-timeline-view-{}", std::process::id())); + let base = std::env::temp_dir().join(format!("ridge-timeline-view-{}", std::process::id())); let _ = std::fs::remove_dir_all(&base); let mut a = App::new(Config::default()); diff --git a/src/cli.rs b/src/cli.rs index def3a9c..db95298 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use clap::Parser; #[derive(Parser, Debug)] -#[command(name = "term", version, about = "Mission control for your AI agents")] +#[command(name = "ridge", version, about = "Mission control for your AI agents")] pub struct Cli { /// Working directory to start in. #[arg(short, long)] @@ -43,7 +43,7 @@ pub struct Cli { #[arg(long)] pub web: bool, - /// Unix domain socket path to connect to a running `term --api` instance. + /// Unix domain socket path to connect to a running `ridge --api` instance. /// When used with --web, the dashboard shows real panes from the server. #[arg(long)] pub socket: Option, diff --git a/src/config.rs b/src/config.rs index 8be4a7b..fc1e7b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,30 +84,46 @@ impl Config { } pub fn config_dir() -> PathBuf { - if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { - return PathBuf::from(xdg).join("term"); - } - if let Ok(home) = std::env::var("HOME") { - return PathBuf::from(home).join(".config").join("term"); - } - PathBuf::from(".config").join("term") + let base = if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { + PathBuf::from(xdg) + } else if let Ok(home) = std::env::var("HOME") { + PathBuf::from(home).join(".config") + } else { + PathBuf::from(".config") + }; + prefer_existing_dir(base) } pub fn config_file() -> PathBuf { - config_dir().join("term.toml") + let dir = config_dir(); + let current = dir.join("ridge.toml"); + let legacy = dir.join("term.toml"); + if !current.exists() && legacy.exists() { + legacy + } else { + current + } } pub fn data_dir() -> PathBuf { - if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { - return PathBuf::from(xdg).join("term"); - } - if let Ok(home) = std::env::var("HOME") { - return PathBuf::from(home) - .join(".local") - .join("share") - .join("term"); + let base = if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { + PathBuf::from(xdg) + } else if let Ok(home) = std::env::var("HOME") { + PathBuf::from(home).join(".local").join("share") + } else { + PathBuf::from(".local").join("share") + }; + prefer_existing_dir(base) +} + +fn prefer_existing_dir(base: PathBuf) -> PathBuf { + let current = base.join("ridge"); + let legacy = base.join("term"); + if !current.exists() && legacy.is_dir() { + legacy + } else { + current } - PathBuf::from(".local").join("share").join("term") } #[cfg(test)] @@ -121,4 +137,17 @@ mod tests { let parsed: Config = toml::from_str(&s).unwrap(); assert_eq!(parsed.theme, cfg.theme); } + + #[test] + fn legacy_directory_is_used_until_ridge_directory_exists() { + let base = std::env::temp_dir().join(format!("ridge-config-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&base); + let legacy = base.join("term"); + std::fs::create_dir_all(&legacy).unwrap(); + assert_eq!(prefer_existing_dir(base.clone()), legacy); + let current = base.join("ridge"); + std::fs::create_dir_all(¤t).unwrap(); + assert_eq!(prefer_existing_dir(base.clone()), current); + std::fs::remove_dir_all(base).unwrap(); + } } diff --git a/src/core/agent.rs b/src/core/agent.rs index 80cae73..fca163b 100644 --- a/src/core/agent.rs +++ b/src/core/agent.rs @@ -96,6 +96,7 @@ impl AgentRegistry { reg.register(Box::new(crate::agents::pi::PiAgent)); reg.register(Box::new(crate::agents::grok::GrokAgent)); reg.register(Box::new(crate::agents::cursor::CursorAgent)); + reg.register(Box::new(crate::agents::reflect::ReflectAgent)); reg.register(Box::new(crate::agents::generic::GenericAgent)); reg } diff --git a/src/core/api_server.rs b/src/core/api_server.rs index 9a21bf9..a87bc2e 100644 --- a/src/core/api_server.rs +++ b/src/core/api_server.rs @@ -18,9 +18,9 @@ use std::sync::{Arc, Mutex}; #[cfg(unix)] use std::thread; -/// Socket path under the term data dir. +/// Socket path under the ridge data dir. pub fn socket_path() -> std::path::PathBuf { - crate::config::data_dir().join("term.sock") + crate::config::data_dir().join("ridge.sock") } /// Incoming API command. @@ -159,7 +159,7 @@ pub fn start_server(app: Arc>) -> std::io::Result<()> { } let _ = std::fs::remove_file(&path); // remove stale socket let listener = UnixListener::bind(&path)?; - eprintln!("term api socket: {}", path.display()); + eprintln!("ridge api socket: {}", path.display()); loop { let (stream, _) = listener.accept()?; let app = Arc::clone(&app); @@ -175,7 +175,7 @@ pub fn start_server(app: Arc>) -> std::io::Result<()> { pub fn start_server(_app: Arc>) -> std::io::Result<()> { Err(std::io::Error::new( std::io::ErrorKind::Unsupported, - "term --api requires Unix-domain sockets (not available on Windows)", + "ridge --api requires Unix-domain sockets (not available on Windows)", )) } @@ -186,6 +186,6 @@ mod tests { #[test] fn socket_path_under_data_dir() { let p = socket_path(); - assert!(p.to_string_lossy().contains("term")); + assert!(p.to_string_lossy().contains("ridge")); } } diff --git a/src/core/clipboard_image.rs b/src/core/clipboard_image.rs index 116fa4f..b975121 100644 --- a/src/core/clipboard_image.rs +++ b/src/core/clipboard_image.rs @@ -50,7 +50,7 @@ pub fn read_clipboard_image() -> Result, String> { } } -/// Write clipboard image data to a file in the term data dir. +/// Write clipboard image data to a file in the ridge data dir. /// Returns the file path. pub fn save_clipboard_image(data: &[u8]) -> Result { let dir = crate::config::data_dir().join("clipboard"); diff --git a/src/core/doctor.rs b/src/core/doctor.rs index 9fa7d8a..11fe8ec 100644 --- a/src/core/doctor.rs +++ b/src/core/doctor.rs @@ -1,4 +1,4 @@ -//! `term --doctor`: environment sanity checks (agent binaries on PATH, git, +//! `ridge --doctor`: environment sanity checks (agent binaries on PATH, git, //! gh, writable data dir) printed as a plain report, then exit. use crate::core::agent::AgentRegistry; @@ -127,7 +127,7 @@ pub fn run_checks(registry: &AgentRegistry) -> Vec { /// CLI entry: print the report; with `fix`, add copy-paste suggestions. pub fn run_cli_checks(with_fix: bool) { - println!("term doctor — environment checks\n"); + println!("ridge doctor — environment checks\n"); let registry = AgentRegistry::new(); let checks = run_checks(®istry); let mut missing = 0usize; @@ -143,7 +143,7 @@ pub fn run_cli_checks(with_fix: bool) { println!("all good ✓ — agents, tools, and data dir are ready"); } else { println!( - "{missing} check{} need{} attention above; Term still runs with the rest", + "{missing} check{} need{} attention above; Ridge still runs with the rest", if missing == 1 { "" } else { "s" }, if missing == 1 { "s" } else { "" } ); @@ -155,7 +155,7 @@ pub fn run_cli_checks(with_fix: bool) { } } } else { - println!("hint: rerun with `term --doctor --fix` for copy-paste install steps,"); + println!("hint: rerun with `ridge --doctor --fix` for copy-paste install steps,"); println!(" or use `spawn generic` / shell panes for missing agents."); } } @@ -168,7 +168,7 @@ mod tests { #[cfg(unix)] #[test] fn finds_executable_in_path_in() { - let dir = std::env::temp_dir().join("term-doctor-bin"); + let dir = std::env::temp_dir().join("ridge-doctor-bin"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let fake = dir.join("fake-agent"); @@ -191,8 +191,8 @@ mod tests { fn run_checks_lists_tools_and_agents() { let registry = AgentRegistry::new(); let checks = run_checks(®istry); - // 10 real agents + git + gh + data dir (generic has no exe → skipped) - assert_eq!(checks.len(), 10 + 3); + // 11 real agents + git + gh + data dir (generic has no exe → skipped) + assert_eq!(checks.len(), 11 + 3); assert!(checks.iter().any(|c| c.name == "git")); let git = checks.iter().find(|c| c.name == "git").unwrap(); assert!(git.ok, "git is required in CI and dev environments"); diff --git a/src/core/git.rs b/src/core/git.rs index b6a94c8..997240a 100644 --- a/src/core/git.rs +++ b/src/core/git.rs @@ -97,7 +97,7 @@ mod tests { #[test] fn run_git_in_temp_repo() { - let dir = std::env::temp_dir().join("term-test-repo"); + let dir = std::env::temp_dir().join("ridge-test-repo"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); Command::new("git") diff --git a/src/core/machine.rs b/src/core/machine.rs index 75d7ecb..f70ef2d 100644 --- a/src/core/machine.rs +++ b/src/core/machine.rs @@ -89,7 +89,7 @@ impl MachineManager { cmd.push_str(&format!(" -i {}", key.display())); } if let Some(session) = &m.session { - cmd.push_str(&format!(" -t 'term --attach-session {}'", session)); + cmd.push_str(&format!(" -t 'ridge --attach-session {}'", session)); } cmd }) @@ -110,7 +110,7 @@ impl MachineManager { } if let Some(session) = &profile.session { cmd.arg("-t") - .arg(format!("term --attach-session {session}")); + .arg(format!("ridge --attach-session {session}")); } let status = cmd.status().map_err(|e| e.to_string())?; diff --git a/src/core/modules.rs b/src/core/modules.rs index 5ae327c..c871aa6 100644 --- a/src/core/modules.rs +++ b/src/core/modules.rs @@ -1,9 +1,9 @@ -//! Module system — Luvus-inspired extensibility for Term. +//! Module system — Luvus-inspired extensibility for Ridge. //! //! Modules are executables or scripts dropped into -//! `~/.config/term/modules/`. Each module can declare a -//! `term-module.toml` manifest with actions, startup hooks, events, -//! settings, and bar widgets. Modules receive `$TERM_JSON` (live app state) +//! `~/.config/ridge/modules/`. Each module can declare a +//! `ridge-module.toml` manifest with actions, startup hooks, events, +//! settings, and bar widgets. Modules receive `$RIDGE_JSON` (live app state) //! on every tick and can register actions invokable from the commander or //! command mode. @@ -31,7 +31,7 @@ pub fn discover() -> Vec { out } -/// Run a module script with the `status` command and `$TERM_JSON` env. +/// Run a module script with the `status` command and `$RIDGE_JSON` env. /// Returns (module_name, output_text). pub fn run_module(path: &PathBuf, json: &str) -> (String, String) { let name = path @@ -40,6 +40,7 @@ pub fn run_module(path: &PathBuf, json: &str) -> (String, String) { .unwrap_or_default(); match std::process::Command::new(path) .arg("status") + .env("RIDGE_JSON", json) .env("TERM_JSON", json) .output() { @@ -59,7 +60,7 @@ pub fn run_all(json: &str) -> Vec<(String, String)> { .collect() } -/// `term-module.toml` manifest — declarative module definition. +/// `ridge-module.toml` manifest — declarative module definition. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ModuleManifest { pub id: String, @@ -187,12 +188,11 @@ pub struct WorktreeProviderResponse { pub error: Option, } -/// Load a module manifest from a directory (looks for term-module.toml). +/// Load a module manifest from a directory (looks for ridge-module.toml). pub fn load_manifest(module_dir: &Path) -> Option { - let manifest_path = module_dir.join("term-module.toml"); - if !manifest_path.exists() { - return None; - } + let current = module_dir.join("ridge-module.toml"); + let legacy = module_dir.join("term-module.toml"); + let manifest_path = if current.exists() { current } else { legacy }; let raw = std::fs::read_to_string(&manifest_path).ok()?; toml::from_str(&raw).ok() } @@ -451,8 +451,16 @@ mod tests { #[test] fn modules_dir_path() { let d = modules_dir(); - assert!(d.to_string_lossy().contains("term")); - assert!(d.to_string_lossy().contains("modules")); + assert_eq!( + d.file_name().and_then(|name| name.to_str()), + Some("modules") + ); + assert!(matches!( + d.parent() + .and_then(|parent| parent.file_name()) + .and_then(|name| name.to_str()), + Some("ridge" | "term") + )); } #[test] diff --git a/src/core/notify.rs b/src/core/notify.rs index 5d60f85..2575f6c 100644 --- a/src/core/notify.rs +++ b/src/core/notify.rs @@ -23,7 +23,7 @@ pub fn command_for(title: &str, body: &str) -> Option<(String, Vec)> { "notify-send".to_string(), vec![ "-a".to_string(), - "term".to_string(), + "ridge".to_string(), title.to_string(), body.to_string(), ], @@ -99,10 +99,10 @@ mod tests { #[cfg(target_os = "linux")] #[test] fn linux_uses_notify_send_with_argument_vector() { - let (prog, args) = command_for("Term — Claude #1", "agent is BLOCKED").unwrap(); + let (prog, args) = command_for("Ridge — Claude #1", "agent is BLOCKED").unwrap(); assert_eq!(prog, "notify-send"); - assert_eq!(args[1], "term"); - assert_eq!(args[2], "Term — Claude #1"); + assert_eq!(args[1], "ridge"); + assert_eq!(args[2], "Ridge — Claude #1"); // argument vectors: no shell, so quotes are literal-safe assert_eq!(args[3], "agent is BLOCKED"); } @@ -126,7 +126,7 @@ mod tests { fn notify_never_panics_on_unsupported_platforms() { // On unsupported platforms this is a safe no-op returning false; // on supported ones it spawns (result depends on the environment). - let _ = notify("term test", "if you can read this in a toast, hooks work"); + let _ = notify("ridge test", "if you can read this in a toast, hooks work"); } #[test] diff --git a/src/core/orchestration.rs b/src/core/orchestration.rs index f634bae..84d5653 100644 --- a/src/core/orchestration.rs +++ b/src/core/orchestration.rs @@ -89,17 +89,19 @@ impl TaskBoard { self.next_id = id; } - /// Persist the board to `/.term-tasks.json`. + /// Persist the board to `/.ridge-tasks.json`. pub fn save(&self, repo: &Path) -> Result<(), String> { - let path = repo.join(".term-tasks.json"); + let path = repo.join(".ridge-tasks.json"); let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?; std::fs::write(&path, json).map_err(|e| e.to_string())?; Ok(()) } - /// Load a board from `/.term-tasks.json`, or a fresh one. + /// Load a board from `/.ridge-tasks.json`, or a fresh one. pub fn load(repo: &Path) -> TaskBoard { - let path = repo.join(".term-tasks.json"); + let current = repo.join(".ridge-tasks.json"); + let legacy = repo.join(".term-tasks.json"); + let path = if current.exists() { current } else { legacy }; if let Ok(s) = std::fs::read_to_string(&path) { if let Ok(board) = serde_json::from_str::(&s) { return board; @@ -190,7 +192,7 @@ impl TaskBoard { if task.status != TaskStatus::Pending { return Err("not pending".into()); } - let branch = format!("term/task-{id}"); + let branch = format!("ridge/task-{id}"); let base = worktree::worktree_base(repo); let _ = std::fs::create_dir_all(&base); let path = base.join(format!("task-{id}")); @@ -448,7 +450,7 @@ mod notes_tests { #[test] fn notes_survive_save_load() { - let dir = std::env::temp_dir().join("term-notes-roundtrip"); + let dir = std::env::temp_dir().join("ridge-notes-roundtrip"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let mut b = TaskBoard::default(); @@ -508,7 +510,7 @@ mod checklist_tests { #[test] fn checklist_survives_persistence() { - let dir = std::env::temp_dir().join("term-checklist-roundtrip"); + let dir = std::env::temp_dir().join("ridge-checklist-roundtrip"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let mut b = TaskBoard::default(); diff --git a/src/core/race.rs b/src/core/race.rs index 62c3558..8d9cebd 100644 --- a/src/core/race.rs +++ b/src/core/race.rs @@ -125,7 +125,9 @@ impl RaceHistory { } pub fn load(repo: &std::path::Path) -> Self { - let path = repo.join(".term-races.json"); + let current = repo.join(".ridge-races.json"); + let legacy = repo.join(".term-races.json"); + let path = if current.exists() { current } else { legacy }; std::fs::read_to_string(&path) .ok() .and_then(|s| serde_json::from_str::(&s).ok()) @@ -133,7 +135,7 @@ impl RaceHistory { } pub fn save(&self, repo: &std::path::Path) -> Result<(), String> { - let path = repo.join(".term-races.json"); + let path = repo.join(".ridge-races.json"); let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?; std::fs::write(&path, json).map_err(|e| e.to_string()) } @@ -218,7 +220,7 @@ mod history_tests { #[test] fn save_load_roundtrip() { - let dir = std::env::temp_dir().join("term-race-history"); + let dir = std::env::temp_dir().join("ridge-race-history"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let mut h = RaceHistory::default(); @@ -227,8 +229,8 @@ mod history_tests { let h2 = RaceHistory::load(&dir); assert_eq!(h2.records.len(), 1); assert_eq!(h2.records[0].winner_agent.as_deref(), Some("opencode")); - let path = dir.join(".term-races.json"); - assert_eq!(path, dir.join(".term-races.json")); + let path = dir.join(".ridge-races.json"); + assert_eq!(path, dir.join(".ridge-races.json")); assert!(path.exists()); let _ = std::fs::remove_dir_all(&dir); } diff --git a/src/core/server.rs b/src/core/server.rs index 6120a53..c862d6e 100644 --- a/src/core/server.rs +++ b/src/core/server.rs @@ -1,7 +1,7 @@ //! Background server + detach/reattach (from Herdr). //! -//! `term --server` runs a background daemon that owns the App state. -//! `term --attach` connects as a thin TUI client. Sessions survive +//! `ridge --server` runs a background daemon that owns the App state. +//! `ridge --attach` connects as a thin TUI client. Sessions survive //! client disconnect — detach and reattach without losing panes or agents. use std::io::{Read, Write}; @@ -41,14 +41,14 @@ pub enum ClientToServer { /// Socket path for the background server. pub fn server_socket_path() -> std::path::PathBuf { - crate::config::data_dir().join("term-server.sock") + crate::config::data_dir().join("ridge-server.sock") } /// Attach to a running background server as a TUI client. pub fn attach_client() -> std::io::Result<()> { let path = server_socket_path(); let mut stream = UnixStream::connect(&path)?; - eprintln!("term client: attached to {}", path.display()); + eprintln!("ridge client: attached to {}", path.display()); // Simple read-echo loop: read state updates from server, forward key input. let mut buf = [0u8; 65536]; @@ -97,7 +97,7 @@ pub fn start_server() -> std::io::Result<()> { crate::config::Config::load().map_err(|e| std::io::Error::other(e.to_string()))?, ))); let listener = UnixListener::bind(&path)?; - eprintln!("term server: {}", path.display()); + eprintln!("ridge server: {}", path.display()); for stream in listener.incoming() { match stream { @@ -206,6 +206,6 @@ mod tests { #[test] fn server_socket_path_under_data_dir() { let p = server_socket_path(); - assert!(p.to_string_lossy().contains("term")); + assert!(p.to_string_lossy().contains("ridge")); } } diff --git a/src/core/store.rs b/src/core/store.rs index ec67cd7..76504a0 100644 --- a/src/core/store.rs +++ b/src/core/store.rs @@ -11,7 +11,14 @@ static DB_COUNTER: AtomicU64 = AtomicU64::new(0); type StoreResult = Result; pub fn db_path() -> PathBuf { - crate::config::data_dir().join("term.db") + let dir = crate::config::data_dir(); + let current = dir.join("ridge.db"); + let legacy = dir.join("term.db"); + if !current.exists() && legacy.exists() { + legacy + } else { + current + } } fn open_db_at(path: PathBuf) -> StoreResult { @@ -37,7 +44,7 @@ pub fn open_db() -> StoreResult { // Fall back to temp dir with unique name per call // (avoids sled lock conflicts when running multiple instances) let tmp_path = std::env::temp_dir().join(format!( - "term-{}-{}.db", + "ridge-{}-{}.db", std::process::id(), DB_COUNTER.fetch_add(1, Ordering::Relaxed) )); @@ -60,7 +67,7 @@ pub fn open_db() -> StoreResult { #[cfg(test)] pub fn open_db() -> StoreResult { let path = std::env::temp_dir().join(format!( - "term-test-{}-{}.db", + "ridge-test-{}-{}.db", std::process::id(), DB_COUNTER.fetch_add(1, Ordering::Relaxed) )); diff --git a/src/core/uhp.rs b/src/core/uhp.rs index db6a164..7f5980f 100644 --- a/src/core/uhp.rs +++ b/src/core/uhp.rs @@ -191,7 +191,7 @@ impl UhpcServer { pub fn start(self) -> std::io::Result<()> { Err(std::io::Error::new( std::io::ErrorKind::Unsupported, - "term --uhp requires Unix-domain sockets (not available on Windows)", + "ridge --uhp requires Unix-domain sockets (not available on Windows)", )) } } diff --git a/src/core/update.rs b/src/core/update.rs index 4589156..5d407e1 100644 --- a/src/core/update.rs +++ b/src/core/update.rs @@ -1,6 +1,6 @@ //! Self-update command (from Luvus's `luvus update`). //! -//! Checks GitHub releases for a newer version of term and downloads/installs it. +//! Checks GitHub releases for a newer version of ridge and downloads/installs it. //! Uses `curl` via std::process::Command (no HTTP crate needed). use serde::Deserialize; @@ -40,7 +40,7 @@ pub fn check_update(current_version: &str) -> UpdateStatus { let output = std::process::Command::new("curl") .args([ "-fsSL", - "https://api.github.com/repos/GrayCodeAI/term/releases/latest", + "https://api.github.com/repos/GrayCodeAI/ridge/releases/latest", ]) .output(); @@ -89,11 +89,11 @@ pub fn check_update(current_version: &str) -> UpdateStatus { /// Download and install an update. /// Downloads the tarball with curl, extracts, and replaces the current binary. pub fn install_update(asset_url: &str) -> Result<()> { - let tmp_dir = std::env::temp_dir().join("term-update"); + let tmp_dir = std::env::temp_dir().join("ridge-update"); let _ = std::fs::remove_dir_all(&tmp_dir); std::fs::create_dir_all(&tmp_dir).map_err(|e| anyhow!("create tmp dir: {e}"))?; - let tarball = tmp_dir.join("term.tar.gz"); + let tarball = tmp_dir.join("ridge.tar.gz"); // Download with curl. let download = std::process::Command::new("curl") @@ -126,22 +126,22 @@ pub fn install_update(asset_url: &str) -> Result<()> { )); } - // Find the term binary in the extracted archive. - let mut term_bin = None; + // Find the ridge binary in the extracted archive. + let mut ridge_bin = None; for entry in std::fs::read_dir(&extract_dir).map_err(|e| anyhow!("read extract dir: {e}"))? { let entry = entry.map_err(|e| anyhow!("read entry: {e}"))?; let path = entry.path(); if path.is_file() { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - if name == "term" || name.starts_with("term-") { - term_bin = Some(path); + if name == "ridge" || name.starts_with("ridge-") { + ridge_bin = Some(path); break; } } } } - let new_bin = term_bin.ok_or_else(|| anyhow!("term binary not found in archive"))?; + let new_bin = ridge_bin.ok_or_else(|| anyhow!("ridge binary not found in archive"))?; // Get current binary path. let current_bin = std::env::current_exe().map_err(|e| anyhow!("current exe: {e}"))?; @@ -165,18 +165,18 @@ pub fn install_update(asset_url: &str) -> Result<()> { Ok(()) } -/// Entry point for `term update`. Checks for updates and offers to install. +/// Entry point for `ridge update`. Checks for updates and offers to install. pub fn run_update(current_version: &str) -> Result<()> { match check_update(current_version) { UpdateStatus::UpToDate { current, latest } => { - println!("term {current} is up to date (latest: {latest})"); + println!("ridge {current} is up to date (latest: {latest})"); } UpdateStatus::UpdateAvailable { current, latest, asset_url, } => { - println!("term update available: {current} → {latest}"); + println!("ridge update available: {current} → {latest}"); print!("Download and install now? [y/N] "); use std::io::{self, Write}; io::stdout().flush()?; @@ -184,7 +184,7 @@ pub fn run_update(current_version: &str) -> Result<()> { io::stdin().read_line(&mut input)?; if input.trim().eq_ignore_ascii_case("y") { install_update(&asset_url)?; - println!("term updated to {latest}. Please restart term."); + println!("ridge updated to {latest}. Please restart ridge."); } else { println!("Update cancelled."); } @@ -221,7 +221,7 @@ mod tests { let status = UpdateStatus::UpdateAvailable { current: "0.9.0".to_string(), latest: "1.0.0".to_string(), - asset_url: "https://example.com/term.tar.gz".to_string(), + asset_url: "https://example.com/ridge.tar.gz".to_string(), }; match status { UpdateStatus::UpdateAvailable { diff --git a/src/core/web.rs b/src/core/web.rs index c7975a6..da0b382 100644 --- a/src/core/web.rs +++ b/src/core/web.rs @@ -1,6 +1,6 @@ -//! Luvus Web — browser client for live workspaces, terminals, and agent sessions. +//! Ridge Web — browser client for live workspaces, terminals, and agent sessions. //! -//! Can run standalone (mock data) or connect to a running `term --api` instance +//! Can run standalone (mock data) or connect to a running `ridge --api` instance //! via the Unix domain socket to show real panes and stream live PTY output. use axum::{ @@ -19,9 +19,9 @@ use std::sync::{Arc, Mutex}; use tokio::net::TcpListener; use tokio::sync::broadcast; -/// Path to the term API socket (for connecting to a running `term --api` instance). +/// Path to the ridge API socket (for connecting to a running `ridge --api` instance). pub fn socket_path() -> PathBuf { - crate::config::data_dir().join("term.sock") + crate::config::data_dir().join("ridge.sock") } /// WebSocket message from client to server. @@ -51,7 +51,7 @@ enum ServerMessage { List { panes: Vec }, } -/// Fetch pane output from a running `term --api` instance via the Unix socket. +/// Fetch pane output from a running `ridge --api` instance via the Unix socket. #[cfg(unix)] fn fetch_pane_output(socket_path: &Path, pane_id: usize, cursor: usize) -> Option<(String, usize)> { use std::io::{Read, Write}; @@ -115,7 +115,7 @@ pub struct WebState { panes: Arc>>, /// Broadcast channel for live PTY output (pane_id, output_text) output_tx: broadcast::Sender<(usize, String)>, - /// Optional socket path to connect to a running `term --api` instance. + /// Optional socket path to connect to a running `ridge --api` instance. socket_path: Option, /// Per-pane output cursors for polling PTY output. cursor_tracker: Arc, @@ -163,7 +163,7 @@ impl WebState { } } -/// Fetch pane list from a running `term --api` instance via the Unix socket. +/// Fetch pane list from a running `ridge --api` instance via the Unix socket. #[cfg(unix)] fn fetch_panes_from_api(socket_path: &Path) -> Option> { use std::io::{Read, Write}; @@ -244,7 +244,7 @@ const DASHBOARD_HTML: &str = r#" - Term — Luvus Web + Ridge Web