From 0f24f543db0cfae9a0679dc5a8eefc02668bcd96 Mon Sep 17 00:00:00 2001 From: hbrooks Date: Fri, 25 Sep 2026 12:31:05 -0400 Subject: [PATCH] Rename the binary to ellipsis and link el next to it The CLI is now `ellipsis`. install.sh installs it as /ellipsis and makes a relative symlink /el beside it, so both spellings resolve to one file. `ellipsis update` needs no change: the link points at the name, and the running executable already resolves to the real file. `ellipsis uninstall` removes the link too, but only when it is a symlink to our binary. Release assets are now ellipsis-.tar.gz, the compile outfile and CI smoke test follow, and the startup-file marker reads "Ellipsis CLI installer". Every command example in help text, README, the skills, and the docs uses the new name. Prose about coding agents is untouched. --- .github/workflows/ci.yml | 23 +++-- .github/workflows/release.yml | 10 +- .gitignore | 3 +- README.md | 161 ++++++++++++++++---------------- docs/RELEASING.md | 4 +- docs/SESSION_STREAMING.md | 4 +- install.sh | 34 +++++-- package.json | 2 +- scripts/compile.sh | 2 +- scripts/smoke-local.sh | 4 +- scripts/smoke.sh | 2 +- skills/cli-conventions/SKILL.md | 14 +-- skills/ellipsis/SKILL.md | 98 +++++++++---------- src/cli.ts | 16 ++-- src/commands/analytics.ts | 4 +- src/commands/auth.ts | 10 +- src/commands/automation.ts | 12 +-- src/commands/environment.ts | 8 +- src/commands/file.ts | 4 +- src/commands/github.ts | 2 +- src/commands/help.ts | 4 +- src/commands/host.ts | 12 +-- src/commands/integrations.ts | 2 +- src/commands/model.ts | 2 +- src/commands/review.ts | 20 ++-- src/commands/session.ts | 12 +-- src/commands/template.ts | 2 +- src/commands/uninstall.ts | 41 +++++++- src/commands/update.ts | 18 ++-- src/lib/api.ts | 2 +- src/lib/args.ts | 12 +-- src/lib/config.ts | 18 ++-- src/lib/help.ts | 2 +- src/lib/images.ts | 2 +- src/lib/install.ts | 10 +- src/lib/metrics.ts | 2 +- src/lib/output.ts | 8 +- src/lib/sessionDiff.ts | 2 +- src/lib/sessions.ts | 2 +- src/lib/update-check.ts | 4 +- src/lib/urls.ts | 2 +- test/args.test.ts | 10 +- test/help.test.ts | 8 +- test/install.test.ts | 21 +++-- test/output.test.ts | 6 +- test/review.test.ts | 2 +- test/sdk-030.test.ts | 2 +- test/session.test.ts | 4 +- 48 files changed, 355 insertions(+), 294 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cdfb93..927e9bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,25 +22,28 @@ jobs: # Compile-smoke: the release Bun-compiles a single binary, which bundles # everything (unlike tsup) and can fail where `build` passes. Catch that # here so a release tag never breaks on it. - - run: bun build src/cli.ts --compile --outfile /tmp/agent - - run: /tmp/agent --version + - run: bun build src/cli.ts --compile --outfile /tmp/ellipsis + - run: /tmp/ellipsis --version # Install-smoke: run install.sh for real against the binary above, laid # out the way GitHub Releases serves it, then let the binary remove # itself. GITHUB_PATH is unset so the startup-file branch is exercised. - - name: install.sh and agent uninstall round trip + - name: install.sh and ellipsis uninstall round trip run: | set -euo pipefail site="$RUNNER_TEMP/releases/latest/download" mkdir -p "$site" "$RUNNER_TEMP/home" - tar -czf "$site/agent-linux-x64.tar.gz" -C /tmp agent - (cd "$site" && sha256sum agent-linux-x64.tar.gz > checksums.txt) + tar -czf "$site/ellipsis-linux-x64.tar.gz" -C /tmp ellipsis + (cd "$site" && sha256sum ellipsis-linux-x64.tar.gz > checksums.txt) python3 -m http.server 8123 --directory "$RUNNER_TEMP/releases" >/dev/null 2>&1 & sleep 1 env -u GITHUB_PATH HOME="$RUNNER_TEMP/home" SHELL=/bin/bash \ ELLIPSIS_DOWNLOAD_BASE=http://127.0.0.1:8123 \ sh install.sh --dir "$RUNNER_TEMP/bin" - "$RUNNER_TEMP/bin/agent" --version - grep -q "Ellipsis agent installer" "$RUNNER_TEMP/home/.bashrc" - HOME="$RUNNER_TEMP/home" "$RUNNER_TEMP/bin/agent" uninstall - test ! -e "$RUNNER_TEMP/bin/agent" - ! grep -q "Ellipsis agent installer" "$RUNNER_TEMP/home/.bashrc" + "$RUNNER_TEMP/bin/ellipsis" --version + test -L "$RUNNER_TEMP/bin/el" + "$RUNNER_TEMP/bin/el" --version + grep -q "Ellipsis CLI installer" "$RUNNER_TEMP/home/.bashrc" + HOME="$RUNNER_TEMP/home" "$RUNNER_TEMP/bin/el" uninstall + test ! -e "$RUNNER_TEMP/bin/ellipsis" + test ! -e "$RUNNER_TEMP/bin/el" + ! grep -q "Ellipsis CLI installer" "$RUNNER_TEMP/home/.bashrc" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7734ff9..59ba029 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: bun run typecheck bun run test - # One tarball per target. install.sh and `agent update` pick the one for + # One tarball per target. install.sh and `ellipsis update` pick the one for # the machine they run on, so the list here must match RELEASE_TARGETS in # src/lib/install.ts (test/install.test.ts checks). - name: Build platform binaries @@ -56,13 +56,13 @@ jobs: mkdir -p dist for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64 linux-x64-musl linux-arm64-musl; do echo "::group::build $t" - bun build src/cli.ts --compile --target=bun-"$t" --outfile agent - tar -czf "dist/agent-$t.tar.gz" agent - rm -f agent + bun build src/cli.ts --compile --target=bun-"$t" --outfile ellipsis + tar -czf "dist/ellipsis-$t.tar.gz" ellipsis + rm -f ellipsis echo "::endgroup::" done - # checksums.txt is what install.sh and `agent update` verify against. + # checksums.txt is what install.sh and `ellipsis update` verify against. - name: Compute checksums run: | set -euo pipefail diff --git a/.gitignore b/.gitignore index 79a1eee..8f23aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules dist -agent +ellipsis +el *.log .DS_Store diff --git a/README.md b/README.md index 9b7c84f..e8c3a95 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ curl -fsSL https://raw.githubusercontent.com/ellipsis-dev/cli/main/install.sh | ``` The script downloads the binary for your OS and CPU from GitHub Releases, -checks its SHA-256, and puts it at `~/.local/bin/agent`. If that directory is +checks its SHA-256, and puts it at `~/.local/bin/ellipsis`, with `el` linked +next to it as a short alias. If that directory is not on your PATH, it appends one line to your shell's startup file (`--no-modify-path` to skip that). Pin a version with `ELLIPSIS_VERSION=2.30.0` or `sh -s -- --version 2.30.0`; choose the directory with `--dir`. @@ -24,8 +25,8 @@ In CI the same line works: inside GitHub Actions the directory is added to entirely. Alpine images get the musl build automatically. ```sh -agent update # replace the binary with the latest release (--check to only look) -agent uninstall # remove the binary and the PATH line (--purge to delete ~/.ellipsis too) +ellipsis update # replace the binary with the latest release (--check to only look) +ellipsis uninstall # remove the binary and the PATH line (--purge to delete ~/.ellipsis too) ``` An installed binary checks for a newer release once a day, in the background, @@ -58,83 +59,83 @@ skills: ## Usage ```sh -agent auth login # device-code auth against the active host -agent auth logout # remove stored credentials (--all for every host) -agent auth status # active host, where the token came from, and who you are - -agent host list # list configured hosts (the active one is marked *) -agent host add beta https://beta-api.ellipsis.dev # add a host and switch to it -agent host use prod # switch the active host -agent host current # show the active host and how it resolves -agent host set beta --rename staging # rename / re-point a host (--api-base / --app-base) -agent host delete beta # remove a host and its stored token - -agent session start -e backend "..." # run a prompt in a saved environment -agent session start --config-file f.json # ...or from an inline config -agent session start --template ellipsis-helper # ...or from a maintained template -agent session start --budget 5 "..." # cap this session's spend, in dollars -agent session start --image shot.png "..." # the agent sees the picture on its first turn -agent session start --watch "..." # start and immediately stream it -agent session list --limit 20 # list recent sessions (filter by --source, --author, --since, …) -agent session get # inspect one session (prints a dashboard link) -agent session get --watch # follow a session until it finishes -agent session record # read a session's stored transcript, one line per record -agent session stop # stop an in-flight session - -agent review 123 # review a pull request now, instead of waiting for a push -agent review get # a review's findings, scope, and whether it posted -agent review list --repo api # list a repository's reviews, newest first -agent review init # scaffold a starter review pipeline (code_review.yaml) - -agent automation list # list your automations -agent automation get # show one automation as YAML (--json for JSON) -agent automation run --input '{"issue": "ENG-42"}' # invoke it exactly as defined -agent automation init [path] # scaffold a starter definition (default: agents/my_agent.yaml) -agent automation create --file agents/foo.yaml # create one, live at once (or --template ) -agent automation create --repo api --file agents/foo.yaml # instead define it as a file, via a pull request -agent automation edit --file agents/foo.yaml # replace its definition, live at once -agent automation delete # delete it; it stops and its name is freed -agent automation link --repo api # move it into a repository, via a pull request -agent automation unlink # take it over from its file, so the API changes it - -agent model list # list selectable agent models (the account default is marked) - -agent integration # every connected integration in one table -agent github repos # repositories connected to the GitHub installation -agent github members # org roster (the logins/ids --author accepts), with linked Slack identities -agent slack channels # channels in the connected Slack workspace -agent slack members # workspace members, with linked GitHub identities -agent linear teams # teams in the connected Linear organization -agent sentry orgs # connected Sentry organizations - -agent file upload shot.png # store a PNG; prints an org-gated link to paste into a PR comment -agent file list # list stored files (--session scopes to one run's uploads) -agent file get -o shot.png # show one file, or download its bytes with -o -agent file delete # delete a file (it disappears from list/get and its link stops resolving) - -agent variable list # list sandbox env variable names (values are write-only) -agent variable set A=1 B=2 # create/update variables (or --from-file .env/.json) -agent variable delete K # delete a variable - -agent budget # current budget summary -agent usage # usage dashboard for the period - -agent analytics reviewer --account-type bot # which apps review the most PRs -agent analytics pr --days 30 # PR volume/trend with human vs bot splits -agent analytics review --repo my-service # review totals + top reviewers -agent update # update the CLI to the latest release (--to for a specific one) -agent uninstall # remove the CLI from this machine (--purge to delete ~/.ellipsis too) +ellipsis auth login # device-code auth against the active host +ellipsis auth logout # remove stored credentials (--all for every host) +ellipsis auth status # active host, where the token came from, and who you are + +ellipsis host list # list configured hosts (the active one is marked *) +ellipsis host add beta https://beta-api.ellipsis.dev # add a host and switch to it +ellipsis host use prod # switch the active host +ellipsis host current # show the active host and how it resolves +ellipsis host set beta --rename staging # rename / re-point a host (--api-base / --app-base) +ellipsis host delete beta # remove a host and its stored token + +ellipsis session start -e backend "..." # run a prompt in a saved environment +ellipsis session start --config-file f.json # ...or from an inline config +ellipsis session start --template ellipsis-helper # ...or from a maintained template +ellipsis session start --budget 5 "..." # cap this session's spend, in dollars +ellipsis session start --image shot.png "..." # the agent sees the picture on its first turn +ellipsis session start --watch "..." # start and immediately stream it +ellipsis session list --limit 20 # list recent sessions (filter by --source, --author, --since, …) +ellipsis session get # inspect one session (prints a dashboard link) +ellipsis session get --watch # follow a session until it finishes +ellipsis session record # read a session's stored transcript, one line per record +ellipsis session stop # stop an in-flight session + +ellipsis review 123 # review a pull request now, instead of waiting for a push +ellipsis review get # a review's findings, scope, and whether it posted +ellipsis review list --repo api # list a repository's reviews, newest first +ellipsis review init # scaffold a starter review pipeline (code_review.yaml) + +ellipsis automation list # list your automations +ellipsis automation get # show one automation as YAML (--json for JSON) +ellipsis automation run --input '{"issue": "ENG-42"}' # invoke it exactly as defined +ellipsis automation init [path] # scaffold a starter definition (default: agents/my_agent.yaml) +ellipsis automation create --file agents/foo.yaml # create one, live at once (or --template ) +ellipsis automation create --repo api --file agents/foo.yaml # instead define it as a file, via a pull request +ellipsis automation edit --file agents/foo.yaml # replace its definition, live at once +ellipsis automation delete # delete it; it stops and its name is freed +ellipsis automation link --repo api # move it into a repository, via a pull request +ellipsis automation unlink # take it over from its file, so the API changes it + +ellipsis model list # list selectable agent models (the account default is marked) + +ellipsis integration # every connected integration in one table +ellipsis github repos # repositories connected to the GitHub installation +ellipsis github members # org roster (the logins/ids --author accepts), with linked Slack identities +ellipsis slack channels # channels in the connected Slack workspace +ellipsis slack members # workspace members, with linked GitHub identities +ellipsis linear teams # teams in the connected Linear organization +ellipsis sentry orgs # connected Sentry organizations + +ellipsis file upload shot.png # store a PNG; prints an org-gated link to paste into a PR comment +ellipsis file list # list stored files (--session scopes to one run's uploads) +ellipsis file get -o shot.png # show one file, or download its bytes with -o +ellipsis file delete # delete a file (it disappears from list/get and its link stops resolving) + +ellipsis variable list # list sandbox env variable names (values are write-only) +ellipsis variable set A=1 B=2 # create/update variables (or --from-file .env/.json) +ellipsis variable delete K # delete a variable + +ellipsis budget # current budget summary +ellipsis usage # usage dashboard for the period + +ellipsis analytics reviewer --account-type bot # which apps review the most PRs +ellipsis analytics pr --days 30 # PR volume/trend with human vs bot splits +ellipsis analytics review --repo my-service # review totals + top reviewers +ellipsis update # update the CLI to the latest release (--to for a specific one) +ellipsis uninstall # remove the CLI from this machine (--purge to delete ~/.ellipsis too) ``` -Every command shown is singular. The plural spelling of each (`agent files`, -`agent sessions`, `agent analytics prs`) is a hidden alias that works but is +Every command shown is singular. The plural spelling of each (`ellipsis files`, +`ellipsis sessions`, `ellipsis analytics prs`) is a hidden alias that works but is left out of `--help`. See [`skills/cli-conventions`](skills/cli-conventions/SKILL.md) for the full argument, flag, and help-text conventions. Most commands accept `--json` to print the raw API response. The CLI talks to the public REST API. Point it at a different instance durably with -`agent host` (below), or per-invocation with `ELLIPSIS_API_BASE_URL` (or the +`ellipsis host` (below), or per-invocation with `ELLIPSIS_API_BASE_URL` (or the legacy `ELLIPSIS_API_BASE`). `--watch` (on both `session start` and `session get`) streams the session's @@ -145,7 +146,7 @@ prints a clickable dashboard link. How the stream works is described in ### Auth -`agent auth login` uses the device-code flow: it requests a code pair, prints a +`ellipsis auth login` uses the device-code flow: it requests a code pair, prints a verification URL (and opens it unless `--no-browser`), and polls until you approve the request in the dashboard. The issued user token is stored under `~/.ellipsis/config.json` (mode 0600) and attributes sessions to you. @@ -155,21 +156,21 @@ environment (`ELLIPSIS_API_TOKEN` / `ELLIPSIS_API_BASE_URL`, with the legacy `ELLIPSIS_API_BASE` accepted as a fallback) → the **active host** in the config file → default (prod). This lets the CLI run headlessly — e.g. inside an Ellipsis cloud sandbox where a per-sandbox token and base URL are injected into -the environment — with no `agent auth login` and no config file on disk. `agent auth logout` only clears the on-disk token (`--all` for every host); a token supplied +the environment — with no `ellipsis auth login` and no config file on disk. `ellipsis auth logout` only clears the on-disk token (`--all` for every host); a token supplied via `ELLIPSIS_API_TOKEN` lives in the environment and keeps working until you unset it. ### Hosts -`agent host` selects which Ellipsis instance the CLI targets — Ellipsis Cloud, +`ellipsis host` selects which Ellipsis instance the CLI targets — Ellipsis Cloud, a preview environment, or a self-hosted deployment — so you can switch without -re-exporting env vars. `agent host add ` registers an instance -and makes it active; `agent host use ` switches; `agent host list` shows +re-exporting env vars. `ellipsis host add ` registers an instance +and makes it active; `ellipsis host use ` switches; `ellipsis host list` shows them all (the active one marked `*`). Each host keeps its own token (so switching doesn't re-authenticate) and its own dashboard/app URL. The app URL is derived from the API URL by default (`api.` → `app.`); a self-hosted instance -whose dashboard host isn't a mechanical swap sets it explicitly with `agent host -add … --app-base ` (or `agent host set --app-base `). `agent auth login` then authenticates the active host, and every link the CLI prints points +whose dashboard host isn't a mechanical swap sets it explicitly with `ellipsis host +add … --app-base ` (or `ellipsis host set --app-base `). `ellipsis auth login` then authenticates the active host, and every link the CLI prints points at that host's dashboard. Hosts and tokens live in `~/.ellipsis/config.json` (mode 0600); set @@ -247,7 +248,7 @@ npm run compile # single-binary build (bun) Pushing a `v*` tag triggers `.github/workflows/release.yml`, which Bun-compiles one binary per target (macOS arm64 and x64, Linux arm64 and x64, both glibc and musl), and publishes a GitHub release with the tarballs and a -`checksums.txt`. `install.sh` and `agent update` download from that release, +`checksums.txt`. `install.sh` and `ellipsis update` download from that release, so publishing it is the whole distribution step. See [`docs/RELEASING.md`](docs/RELEASING.md). diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 28944f4..1531764 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -2,7 +2,7 @@ The CLI ships only as Bun-compiled binaries on GitHub Releases. Users install with `install.sh` (`curl -fsSL https://raw.githubusercontent.com/ellipsis-dev/cli/main/install.sh | sh`) -and stay current with `agent update`. Both download from the release assets +and stay current with `ellipsis update`. Both download from the release assets and verify them against `checksums.txt`. It is never published to npm: `package.json` is `private`, has no `bin`, and there is no `publishConfig`. @@ -37,7 +37,7 @@ validates the committed version; it does not rewrite it. `package.json` is the version source for development, local compiled binaries, and releases. `bun run compile` checks the CLI/SDK pair before building; -`./agent --version` reports `2.30.0` for this version, including local builds. +`./ellipsis --version` reports `2.30.0` for this version, including local builds. Run `bun run check:versions` to check the pair without building, or `bun run check:versions 2.30.0` to also validate an intended release version. diff --git a/docs/SESSION_STREAMING.md b/docs/SESSION_STREAMING.md index 50528b6..5c7c890 100644 --- a/docs/SESSION_STREAMING.md +++ b/docs/SESSION_STREAMING.md @@ -1,9 +1,9 @@ # Session streaming: how `--watch` follows a session -`agent session start --watch` and `agent session get --watch` follow a +`ellipsis session start --watch` and `ellipsis session get --watch` follow a session's output live until it reaches a terminal status. The stream is read-only: the CLI never sends anything to the session. Stopping one is -`agent session stop`. +`ellipsis session stop`. The WebSocket client is `streamSession` from `@ellipsis-dev/sdk/stream`. This repo owns only the transport adapter (`src/lib/stream.ts`) and the rendering diff --git a/install.sh b/install.sh index 96ff561..0275125 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Installs the Ellipsis agent CLI. +# Installs the Ellipsis CLI: the `ellipsis` binary, with `el` linked next to it. # # curl -fsSL https://raw.githubusercontent.com/ellipsis-dev/cli/main/install.sh | sh # @@ -13,19 +13,23 @@ # ELLIPSIS_NO_MODIFY_PATH=1. # # What it does: picks the release asset for this OS and CPU, downloads it and -# checksums.txt from GitHub Releases, checks the SHA-256, and puts the binary -# at /agent. If is not on PATH it appends one marked line to the -# startup file of $SHELL; inside GitHub Actions it appends to $GITHUB_PATH -# instead. `agent update` and `agent uninstall` take it from there. +# checksums.txt from GitHub Releases, checks the SHA-256, puts the binary at +# /ellipsis, and links /el to it. If is not on PATH it appends +# one marked line to the startup file of $SHELL; inside GitHub Actions it +# appends to $GITHUB_PATH instead. `ellipsis update` and `ellipsis uninstall` +# take it from there. # # Needs: curl or wget, tar, and one of sha256sum, shasum, or openssl. set -eu RELEASES="${ELLIPSIS_DOWNLOAD_BASE:-https://github.com/ellipsis-dev/cli/releases}" -BIN="agent" -# Keep in sync with PATH_MARKER in src/lib/install.ts: `agent uninstall` +BIN="ellipsis" +# The short spelling, linked to $BIN in the same directory. Keep in sync with +# ALIAS_NAME in src/lib/install.ts. +ALIAS="el" +# Keep in sync with PATH_MARKER in src/lib/install.ts: `ellipsis uninstall` # removes exactly the startup-file lines that carry this comment. -PATH_MARKER="Ellipsis agent installer" +PATH_MARKER="Ellipsis CLI installer" VERSION="${ELLIPSIS_VERSION:-}" INSTALL_DIR="${ELLIPSIS_INSTALL_DIR:-$HOME/.local/bin}" @@ -183,7 +187,19 @@ fi if ! installed=$("$INSTALL_DIR/$BIN" --version 2>/dev/null); then fail "$INSTALL_DIR/$BIN does not run on this machine" fi -say "Installed $BIN $installed to $INSTALL_DIR/$BIN" + +# `el` is the short spelling of the same binary: a relative symlink next to it. +alias_path="$INSTALL_DIR/$ALIAS" +alias_note="" +if [ -L "$alias_path" ] && [ "$(readlink "$alias_path")" != "$BIN" ]; then + say "Note: $alias_path points somewhere else, so the $ALIAS alias was left alone." +elif [ -e "$alias_path" ] && [ ! -L "$alias_path" ]; then + say "Note: $alias_path already exists, so the $ALIAS alias was left alone." +else + ln -sfn "$BIN" "$alias_path" + alias_note=" (and $ALIAS)" +fi +say "Installed $BIN $installed to $INSTALL_DIR/$BIN$alias_note" # --- PATH -------------------------------------------------------------------- diff --git a/package.json b/package.json index cf2126d..d90a19c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@ellipsis/cli", "version": "2.30.0", - "description": "Ellipsis agent CLI: drive the Ellipsis cloud from your terminal", + "description": "Ellipsis CLI: drive the Ellipsis cloud from your terminal", "license": "MIT", "type": "module", "packageManager": "bun@1.3.14", diff --git a/scripts/compile.sh b/scripts/compile.sh index 981bc00..302a5c9 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -3,4 +3,4 @@ set -euo pipefail cd "$(dirname "$0")/.." bun run check:versions -exec bun build src/cli.ts --compile --outfile agent +exec bun build src/cli.ts --compile --outfile ellipsis diff --git a/scripts/smoke-local.sh b/scripts/smoke-local.sh index 833ee98..51ca6af 100755 --- a/scripts/smoke-local.sh +++ b/scripts/smoke-local.sh @@ -3,7 +3,7 @@ # Fully-automated end-to-end smoke test against a LOCAL docker compose backend. # # Unlike scripts/smoke.sh (which waits for you to approve the login by hand), -# this drives the whole device-code flow itself: it starts `agent auth login`, +# this drives the whole device-code flow itself: it starts `ellipsis auth login`, # scrapes the user code, and approves it headlessly by calling the cli_auth # service inside the running `public_api` container — then exercises the # authenticated API surface. Uses a throwaway config dir, so your real token is @@ -35,7 +35,7 @@ cleanup() { } trap cleanup EXIT -run() { echo "+ agent $*"; npx tsx src/cli.ts "$@"; echo; } +run() { echo "+ ellipsis $*"; npx tsx src/cli.ts "$@"; echo; } echo "API base: $API_BASE" echo "Container: $CONTAINER" diff --git a/scripts/smoke.sh b/scripts/smoke.sh index 899af90..2b9d9be 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -30,7 +30,7 @@ export ELLIPSIS_CONFIG_DIR="$CONFIG_DIR" cleanup() { rm -rf "$CONFIG_DIR"; } trap cleanup EXIT -run() { echo "+ agent $*"; npx tsx src/cli.ts "$@"; echo; } +run() { echo "+ ellipsis $*"; npx tsx src/cli.ts "$@"; echo; } echo "API base: $API_BASE" echo "Config dir: $CONFIG_DIR (temporary)" diff --git a/skills/cli-conventions/SKILL.md b/skills/cli-conventions/SKILL.md index 8774111..c135642 100644 --- a/skills/cli-conventions/SKILL.md +++ b/skills/cli-conventions/SKILL.md @@ -5,7 +5,7 @@ description: How to name commands, arguments, and flags in the Ellipsis agent CL # Ellipsis CLI conventions -The primary reader of `agent --help` is a coding agent deciding its next call. +The primary reader of `ellipsis --help` is a coding agent deciding its next call. It reads once, from a cold start, with no memory of the last release. Every rule here follows from that: **one spelling per concept, intent over transport, no clutter to scan past.** @@ -18,17 +18,17 @@ route text. Singular nouns, one verb per action. ``` -agent file list agent file delete -agent session start agent automation edit +ellipsis file list ellipsis file delete +ellipsis session start ellipsis automation edit ``` - **The noun is singular, always.** `file`, not `files`. `hook`, not `hooks`. `analytics` is the sole exception: it is a mass noun with no singular form. - **The plural still works, hidden.** Register it with `alsoKnownAs`, which - keeps it callable but strips it from every help surface. `agent files list` + keeps it callable but strips it from every help surface. `ellipsis files list` runs and prints nothing extra. -- **A renamed command keeps its old name, hidden.** `agent file` was `agent +- **A renamed command keeps its old name, hidden.** `ellipsis file` was `agent asset`, so it registers `asset` and `assets` alongside `files`. A caller who learned the old spelling is never told it is wrong. - **Read-only integration browsers use a bare plural leaf**: `github repos`, @@ -109,11 +109,11 @@ One line, imperative verb first, no trailing period. - **No `a|b` alias spellings in prose.** `model|models` tells the reader nothing and doubles the width of the term column. - Point at the command that answers the follow-up question: - `(see \`agent model list\`)`, `(see \`agent github members\`)`. + `(see \`ellipsis model list\`)`, `(see \`ellipsis github members\`)`. ## Top-level help -`agent --help` is grouped, not flat: Sessions, Agents, Platform, +`ellipsis --help` is grouped, not flat: Sessions, Agents, Platform, Integrations, Spend, Account. Groups live in `TOP_LEVEL_GROUPS` in `src/lib/help.ts`. **A new top-level command must be added to a group** or it falls through to "Other", which is the signal that someone forgot. diff --git a/skills/ellipsis/SKILL.md b/skills/ellipsis/SKILL.md index 6a34a0a..b356dbf 100644 --- a/skills/ellipsis/SKILL.md +++ b/skills/ellipsis/SKILL.md @@ -55,7 +55,7 @@ the logs of a session they do not own. YAML. Surfaces: the dashboard at app.ellipsis.dev, the REST API at -api.ellipsis.dev, and the `agent` CLI. All three drive the same API. +api.ellipsis.dev, and the `ellipsis` CLI. All three drive the same API. Pricing is usage based, the tokens and compute a session spent plus a platform fee. There are no seats. @@ -69,7 +69,7 @@ fee. There are no seats. The built-in responder needs no configuration and answers in the thread. - **Catching bugs before merge**: turn code review on and every pull request is reviewed, or commit a pipeline file to scope and customize it. -- **Delegation from scripts or CI**: `agent session start` or +- **Delegation from scripts or CI**: `ellipsis session start` or `POST /v1/sessions`. With `--watch` it streams into the log and exits nonzero unless the session completes, so it works as a gate. @@ -157,7 +157,7 @@ access. For an agent a pull request will not trigger, run the file directly instead: ```sh -agent session start --config-file agents/recent-work-summary.yaml --watch +ellipsis session start --config-file agents/recent-work-summary.yaml --watch ``` That runs the file as written, without touching the deployed agent. @@ -367,14 +367,14 @@ cost and latency. ## The agent CLI -One open-source binary named `agent`, a terminal client for the same API +One open-source binary named `ellipsis` (`el` for short), a terminal client for the same API the dashboard uses. Most commands accept `--json` for the raw API response, which makes it as comfortable for a coding agent as for a human. ```sh curl -fsSL https://raw.githubusercontent.com/ellipsis-dev/cli/main/install.sh | sh -agent auth login # device-code flow tied to your GitHub identity -agent auth status # the active host, the credential source, and who you are +ellipsis auth login # device-code flow tied to your GitHub identity +ellipsis auth status # the active host, the credential source, and who you are ``` In CI or any headless environment, skip the login: create an API key in the @@ -385,12 +385,12 @@ wins: the environment variable, then the token stored in `~/.ellipsis/config.jso Start and follow work: ```sh -agent session start "triage the failing CI on api" # a bare ad-hoc session -agent automation run --input '{...}' # invoke an automation as defined -agent session start --config-file agents/my_agent.yaml --watch -agent session start --template ellipsis-helper --watch -agent session get --watch # follow a running session -agent session stop +ellipsis session start "triage the failing CI on api" # a bare ad-hoc session +ellipsis automation run --input '{...}' # invoke an automation as defined +ellipsis session start --config-file agents/my_agent.yaml --watch +ellipsis session start --template ellipsis-helper --watch +ellipsis session get --watch # follow a running session +ellipsis session stop ``` With no config source, a bare `start` runs the bare ad-hoc config — an empty @@ -406,12 +406,12 @@ completes. List and audit what agents have done: ```sh -agent session list --limit 20 # --automation, --source, --author, --days, --since -agent session record # the stored transcript, one line per record -agent session log -o session.jsonl # the complete archived log -agent analytics reviewer --account-type bot # human versus bot PR and review activity -agent budget # this period's spend against the account budget -agent usage # this period's tokens and cost by model +ellipsis session list --limit 20 # --automation, --source, --author, --days, --since +ellipsis session record # the stored transcript, one line per record +ellipsis session log -o session.jsonl # the complete archived log +ellipsis analytics reviewer --account-type bot # human versus bot PR and review activity +ellipsis budget # this period's spend against the account budget +ellipsis usage # this period's tokens and cost by model ``` Search covers transcripts, recaps, and pull request references, with embedding @@ -421,12 +421,12 @@ knowledge. Facets cover repository, author, agent, status, source, and date. Review pull requests on demand, without waiting for a push: ```sh -agent review 519 # review a pull request by number -agent review 519 --full # re-review the whole PR, ignoring earlier reviews -agent review 519 --no-post # print findings instead of posting to GitHub -agent review list --repo api # a repository's reviews, newest first -agent review get # one review's findings, scope, and whether it posted -agent review init # scaffold code_review.yaml for this repository +ellipsis review 519 # review a pull request by number +ellipsis review 519 --full # re-review the whole PR, ignoring earlier reviews +ellipsis review 519 --no-post # print findings instead of posting to GitHub +ellipsis review list --repo api # a repository's reviews, newest first +ellipsis review get # one review's findings, scope, and whether it posted +ellipsis review init # scaffold code_review.yaml for this repository ``` Which pipeline runs is not a parameter. An explicit review resolves the same @@ -437,21 +437,21 @@ than an error. Author and deploy agents: ```sh -agent automation init agents/my_agent.yaml # scaffold a starter definition locally -agent automation list # automations with their source file -agent automation get # one automation as YAML -agent automation run --input '{...}' # invoke it exactly as defined -agent automation create --file agents/my_agent.yaml # create it, live at once -agent automation edit --file agents/my_agent.yaml # replace its definition, live at once -agent automation delete # delete it; it stops and frees its name -agent automation link --repo api # move it into a repo, via a pull request -agent automation unlink # take it over from its file -agent template list # built-in templates and their slugs -agent model list # model ids and their supported harnesses +ellipsis automation init agents/my_agent.yaml # scaffold a starter definition locally +ellipsis automation list # automations with their source file +ellipsis automation get # one automation as YAML +ellipsis automation run --input '{...}' # invoke it exactly as defined +ellipsis automation create --file agents/my_agent.yaml # create it, live at once +ellipsis automation edit --file agents/my_agent.yaml # replace its definition, live at once +ellipsis automation delete # delete it; it stops and frees its name +ellipsis automation link --repo api # move it into a repo, via a pull request +ellipsis automation unlink # take it over from its file +ellipsis template list # built-in templates and their slugs +ellipsis model list # model ids and their supported harnesses ``` An agent is owned by one of two writers, and that is what these verbs move. -`agent automation create` with no `--repo` creates it through the API alone: no +`ellipsis automation create` with no `--repo` creates it through the API alone: no file, live immediately, changed by `config edit`. With `--repo` it instead opens a pull request adding the file, exactly as the dashboard does, and the agent goes live when that merges — thereafter the file is what changes it, and @@ -462,16 +462,16 @@ repository (by pull request; it keeps running unchanged until the merge) and Platform and integrations: ```sh -agent variable set NPM_TOKEN=... # or --from-file .env; values are write-only -agent variable list # names and timestamps only -agent integration # what is connected, in one table -agent github repos # also github members, slack channels, +ellipsis variable set NPM_TOKEN=... # or --from-file .env; values are write-only +ellipsis variable list # names and timestamps only +ellipsis integration # what is connected, in one table +ellipsis github repos # also github members, slack channels, # linear teams, sentry orgs -agent file upload shot.png # store a PNG, print an org-gated link +ellipsis file upload shot.png # store a PNG, print an org-gated link ``` Most singular commands accept the plural spelling as a hidden alias, and -`review` also answers to `cr`. `agent --help` and `agent --help` are +`review` also answers to `cr`. `ellipsis --help` and `ellipsis --help` are authoritative for flags. ## Writing a config @@ -482,8 +482,8 @@ Top-level keys, all optional except `ellipsis`: | --- | --- | | `ellipsis` | `kind: agent`, `version: v1`, `name`, `description`, `metadata`, and `enabled`. Its presence marks the file as a config. | | `trigger` | One trigger, or omit for a manual-only agent. | -| `input` | A JSON Schema for the payload `agent automation run` passes, and the message template it renders into. | -| `session` | What every session runs on; the keys below. The same keys, flattened, are the body of `agent` / `POST /v1/sessions`. | +| `input` | A JSON Schema for the payload `ellipsis automation run` passes, and the message template it renders into. | +| `session` | What every session runs on; the keys below. The same keys, flattened, are the body of `ellipsis` / `POST /v1/sessions`. | Under `session`: @@ -503,7 +503,7 @@ than being silently dropped. Points that decide whether a config works: message verbatim. Put repository guidance in `AGENTS.md`. The former `harness` and `instructions` keys are rejected. - `session.claude_code.model` or `session.codex.model` selects a model. Claude Code - inherits the organization default when omitted. `agent model list` reports + inherits the organization default when omitted. `ellipsis model list` reports the available ids and the harness certified for each. Digest and summary jobs run well on `claude-haiku-4-5-20251001`; judgment jobs earn the frontier model. @@ -548,7 +548,7 @@ Three `environment` fields define the sandbox, each with a different lifetime: A non-zero exit from any of them fails the session with `lifecycle_hook_failed`. The image is cached per repository set, commit, and image definition, so repeat sessions start in seconds instead of reinstalling -dependencies. `agent session start --config-file --rebuild --watch` +dependencies. `ellipsis session start --config-file --rebuild --watch` provisions through a fresh full build and streams every phase, which is how you prove an environment before merging. @@ -571,7 +571,7 @@ Credentials are scoped and short-lived: Because permissions are YAML in git, every agent's blast radius is explicit and reviewed. - Other credentials enter as `environment.variables`. Store the value once with - `agent variable set`, then name it in the config. The name list is the scope, + `ellipsis variable set`, then name it in the config. The name list is the scope, so only agents that name a variable receive it, and a compromised agent never sees the inventory. Stored values are write-only and never readable back through the dashboard, API, or CLI, so rotation is one update in one place. An @@ -639,10 +639,10 @@ npx skills add ellipsis-dev/cli ## Inside an Ellipsis sandbox If `ELLIPSIS_SANDBOX_ID` is set in the environment, you are the agent in an -Ellipsis session. The `agent` CLI is pre-installed and pre-authenticated with a +Ellipsis session. The `ellipsis` CLI is pre-installed and pre-authenticated with a session-scoped token, so you can start child sessions, list the team's sessions, read analytics, and upload screenshots as org-gated links -(`agent file upload shot.png`) with no login. +(`ellipsis file upload shot.png`) with no login. That token is deliberately narrower than a human's. It can list variable names but not set or delete them, cannot delete a file, and cannot repoint an diff --git a/src/cli.ts b/src/cli.ts index 94e4fb3..62dedc6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -27,8 +27,8 @@ import { maybeNudgeUpdate } from './lib/update-check' const program = new Command() program - .name('agent') - .description('Ellipsis agent CLI: drive the Ellipsis cloud from your terminal') + .name('ellipsis') + .description('Ellipsis CLI: drive the Ellipsis cloud from your terminal') .version(VERSION) // Set before the register* calls so every subcommand inherits the same help @@ -56,18 +56,18 @@ registerUpdate(program) registerUninstall(program) registerHelp(program) -// A bare `agent` prints the top-level help, the same page as `agent --help`. +// A bare `ellipsis` prints the top-level help, the same page as `ellipsis --help`. // // Any other invocation that isn't a known subcommand or a top-level -// help/version request is shorthand for `agent session start ...`: -// `agent "fix the tests" --model ...` forwards the prompt and every trailing +// help/version request is shorthand for `ellipsis session start ...`: +// `ellipsis "fix the tests" --model ...` forwards the prompt and every trailing // flag through to a fresh session, which starts and prints its dashboard -// link. `agent --help`, `agent --version`, `agent help`, and every subcommand +// link. `ellipsis --help`, `ellipsis --version`, `ellipsis help`, and every subcommand // dispatch unchanged. // -// The exception is a single bare word (`agent sesion`): see +// The exception is a single bare word (`ellipsis sesion`): see // looksLikeCommandTypo. Quoting does not help, since the shell strips the -// quotes. Use `agent -p word` or `agent -- word` to force it through. +// quotes. Use `ellipsis -p word` or `ellipsis -- word` to force it through. const topLevelCommands = new Set([ 'help', ...program.commands.flatMap((c) => [c.name(), ...c.aliases()]), diff --git a/src/commands/analytics.ts b/src/commands/analytics.ts index f764d27..fce802a 100644 --- a/src/commands/analytics.ts +++ b/src/commands/analytics.ts @@ -10,9 +10,9 @@ import type { ReviewerUsage, } from '../lib/types' -// `agent analytics` is the same aggregation behind the dashboard's analytics +// `ellipsis analytics` is the same aggregation behind the dashboard's analytics // page, so questions like "which apps review the most PRs?" are answerable -// from the terminal (`agent analytics reviewer --account-type bot`). +// from the terminal (`ellipsis analytics reviewer --account-type bot`). // Human-readable tables by default; --json prints the raw API response. // Window flags shared by every subcommand. The server defaults to the last diff --git a/src/commands/auth.ts b/src/commands/auth.ts index ce96469..f00c308 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -79,7 +79,7 @@ export function registerAuth(program: Command): void { .option('--all', 'clear the stored token for every host, not just the active one') .action((opts: { all?: boolean }) => { // Clear only the on-disk token(s); the host entries (api/app base) stay - // so the next `agent auth login` targets the same instance. + // so the next `ellipsis auth login` targets the same instance. if (opts.all) { clearAllTokens() } else { @@ -108,7 +108,7 @@ export function registerAuth(program: Command): void { } // Exit 1 whenever the CLI could not make an authenticated call, so a CI step -// can use `agent auth status` as its readiness check. +// can use `ellipsis auth status` as its readiness check. async function status(json: boolean): Promise { const host = activeHostName() ?? null const apiBase = resolveApiBase() @@ -123,7 +123,7 @@ async function status(json: boolean): Promise { let identity: WhoAmI | null = null let error: string | undefined if (source === 'none') { - error = 'Not logged in. Run `agent auth login`, or set ELLIPSIS_API_TOKEN.' + error = 'Not logged in. Run `ellipsis auth login`, or set ELLIPSIS_API_TOKEN.' } else { try { identity = await api().identity() @@ -131,8 +131,8 @@ async function status(json: boolean): Promise { if (err instanceof APIError && err.status === 401) { error = source === 'env' - ? 'The server rejected ELLIPSIS_API_TOKEN. Check the token, or unset it and run `agent auth login`.' - : 'The stored token is invalid or has expired. Run `agent auth login` again.' + ? 'The server rejected ELLIPSIS_API_TOKEN. Check the token, or unset it and run `ellipsis auth login`.' + : 'The stored token is invalid or has expired. Run `ellipsis auth login` again.' } else if (err instanceof APIError) { error = `${err.status} ${err.message}` } else { diff --git a/src/commands/automation.ts b/src/commands/automation.ts index 43d055f..9837b6a 100644 --- a/src/commands/automation.ts +++ b/src/commands/automation.ts @@ -122,7 +122,7 @@ export function registerAutomation(program: Command): void { // Invoke an automation. It runs exactly as defined — persona, model, // environment, permissions, skills, budget all bind — so the body carries // only its typed input, an optional tighter budget, and metadata. For a - // prompt of your own in an environment, `agent session start` is the door. + // prompt of your own in an environment, `ellipsis session start` is the door. apiRoutes( automation .command('run ') @@ -179,7 +179,7 @@ export function registerAutomation(program: Command): void { const me = await client.identity() console.log(`✓ started ${session.id}`) console.log(` ${sessionUrl(resolveAppBase(), me.customer_login, session.id)}`) - console.log(` follow with: agent session get ${session.id} --watch`) + console.log(` follow with: ellipsis session get ${session.id} --watch`) }) }, ) @@ -202,7 +202,7 @@ export function registerAutomation(program: Command): void { .option('-f, --file ', 'definition file (.yaml/.yml or .json) to add') .option( '--template ', - 'create from an Ellipsis template instead of a file (see `agent template list`)', + 'create from an Ellipsis template instead of a file (see `ellipsis template list`)', ) .option( '--path ', @@ -367,7 +367,7 @@ export function registerAutomation(program: Command): void { .option('--force', 'overwrite the file if it already exists') .option( '-t, --template ', - 'instead scaffold from a template, in a repo, by pull request (see `agent template list`)', + 'instead scaffold from a template, in a repo, by pull request (see `ellipsis template list`)', ) .option( '-r, --repo ', @@ -437,7 +437,7 @@ function printCreated(created: CreatedAutomation): void { `✓ created "${automationName(created.agent)}" (${created.agent.id}) — live now`, ) console.log( - 'It has no file; change it with `agent automation edit`, or `agent automation link` to move it into a repo.', + 'It has no file; change it with `ellipsis automation edit`, or `ellipsis automation link` to move it into a repo.', ) } @@ -457,7 +457,7 @@ ellipsis: name: ${name} description: What this automation does. -# When it runs. Omit for an automation you invoke yourself (\`agent automation run\`). +# When it runs. Omit for an automation you invoke yourself (\`ellipsis automation run\`). # trigger: # type: cron # schedule: "0 9 * * 1-5" # weekdays at 09:00 diff --git a/src/commands/environment.ts b/src/commands/environment.ts index 105b1bd..beba81c 100644 --- a/src/commands/environment.ts +++ b/src/commands/environment.ts @@ -84,7 +84,7 @@ export function registerEnvironment(program: Command): void { console.log( 'Reference it from automations (`environment: ' + e.name + - '`) or start a session in it: `agent session start -e ' + + '`) or start a session in it: `ellipsis session start -e ' + e.name + '`.', ) @@ -150,7 +150,7 @@ export function registerEnvironment(program: Command): void { writeFileSync(target, STARTER_ENVIRONMENT) console.log(`✓ wrote ${target}`) console.log( - 'Commit it to your default branch (Ellipsis syncs it from GitHub), or create it now: `agent environment create -f ' + + 'Commit it to your default branch (Ellipsis syncs it from GitHub), or create it now: `ellipsis environment create -f ' + target + '`.', ) @@ -164,7 +164,7 @@ function environmentSource(e: SavedEnvironment): string { const STARTER_ENVIRONMENT = `# Ellipsis environment: the machine your agents run in, defined once for the # team. Commit to your default branch (synced locations: agents/, .agents/, -# ellipsis/, .ellipsis/), or create it live with \`agent environment create -f\`. +# ellipsis/, .ellipsis/), or create it live with \`ellipsis environment create -f\`. ellipsis: kind: environment name: my-environment @@ -174,7 +174,7 @@ repositories: - name: my-repo # Environment variables injected into the sandbox. Omit \`value\` to resolve -# the name from your stored secrets (\`agent variable set NAME=...\`). +# the name from your stored secrets (\`ellipsis variable set NAME=...\`). variables: - name: MY_TOKEN diff --git a/src/commands/file.ts b/src/commands/file.ts index 19496ad..3e1d8ac 100644 --- a/src/commands/file.ts +++ b/src/commands/file.ts @@ -6,7 +6,7 @@ import { alsoKnownAs, apiRoutes } from '../lib/help' import { formatTs, printJson, printTable, runAction } from '../lib/output' import type { CreateFileRequest, FileView, GetFileResponse } from '../lib/types' -// `agent file `: persist files to Ellipsis platform storage and get back +// `ellipsis file `: persist files to Ellipsis platform storage and get back // an org-membership-gated link. The primary caller is an agent inside a sandbox // that took a screenshot of a UI change and wants a link to paste into a PR // comment: the injected sandbox token authenticates it with zero setup, and the @@ -202,7 +202,7 @@ function renderFile(res: GetFileResponse): void { console.log(`created: ${formatTs(f.created_at)}`) if (f.session_id) console.log(`session: ${f.session_id}`) console.log(`url: ${res.url}`) - console.log(`\ndownload the file with: agent file get ${f.id} -o ${f.filename}`) + console.log(`\ndownload the file with: ellipsis file get ${f.id} -o ${f.filename}`) } // Pull the bytes from the presigned S3 URL. Deliberately bare fetch (no diff --git a/src/commands/github.ts b/src/commands/github.ts index cd1b55b..3df16cd 100644 --- a/src/commands/github.ts +++ b/src/commands/github.ts @@ -68,7 +68,7 @@ export function registerGithub(program: Command): void { return } // SLACK shows the linked slack_user_id when a Slack<->GitHub link row - // exists for the member, so `agent slack members` can go the other way. + // exists for the member, so `ellipsis slack members` can go the other way. printTable( ['LOGIN', 'NAME', 'ROLE', 'SLACK'], res.members.map((m) => [ diff --git a/src/commands/help.ts b/src/commands/help.ts index a925371..9851fc2 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,7 +1,7 @@ import type { Command } from 'commander' // Replaces commander's built-in `help` command. Everything the built-in did -// must keep working: `agent help` prints the top-level help, `agent help +// must keep working: `ellipsis help` prints the top-level help, `ellipsis help // ` prints that subcommand's. Note the built-in only ever resolved // ONE level ("help session start" printed session's help); this walks the // whole path, which is a strict improvement. @@ -25,7 +25,7 @@ export function registerHelp(program: Command): void { } // Walk a command path ("session start") down the tree, matching hidden aliases -// too so `agent help sessions` resolves the same as `agent help session`. +// too so `ellipsis help sessions` resolves the same as `ellipsis help session`. export function resolveCommandPath(program: Command, path: string[]): Command | undefined { let cmd: Command = program for (const name of path) { diff --git a/src/commands/host.ts b/src/commands/host.ts index 7e1f0c1..d7b45b5 100644 --- a/src/commands/host.ts +++ b/src/commands/host.ts @@ -13,9 +13,9 @@ import { import { alsoKnownAs } from '../lib/help' import { printTable } from '../lib/output' -// `agent host …` manages the Ellipsis instances the CLI can target — prod, +// `ellipsis host …` manages the Ellipsis instances the CLI can target — prod, // beta, or a self-hosted deployment — and which one is active. It does NOT -// authenticate: `agent host add` / `use` set WHERE the CLI points; `agent +// authenticate: `ellipsis host add` / `use` set WHERE the CLI points; `ellipsis auth // login` sets the credential for wherever it's pointing. Every other command // resolves against the active host (unless ELLIPSIS_API_BASE_URL / // ELLIPSIS_API_TOKEN override it, e.g. inside a sandbox). @@ -34,7 +34,7 @@ export function registerHost(program: Command): void { .action(() => { const hosts = listHosts() if (hosts.length === 0) { - console.log('No hosts configured. Add one with `agent host add `.') + console.log('No hosts configured. Add one with `ellipsis host add `.') return } printTable( @@ -51,7 +51,7 @@ export function registerHost(program: Command): void { host .command('add ') - .description('Add a host and switch to it, then run `agent auth login` to authenticate') + .description('Add a host and switch to it, then run `ellipsis auth login` to authenticate') .option( '--app-base ', 'dashboard URL for building links / login (default: derived from the API URL)', @@ -59,7 +59,7 @@ export function registerHost(program: Command): void { .action((name: string, apiUrl: string, opts: { appBase?: string }) => { addHost(name, requireUrl(apiUrl, 'api-url'), opts.appBase && requireUrl(opts.appBase, '--app-base')) console.log(`✓ added host "${name}", now active`) - console.log('Run `agent auth login` to authenticate against it.') + console.log('Run `ellipsis auth login` to authenticate against it.') }) host @@ -99,7 +99,7 @@ export function registerHost(program: Command): void { deleteHost(name) console.log(`✓ removed host "${name}"`) if (wasActive) { - console.log('That was the active host. Set a new one with `agent host use `.') + console.log('That was the active host. Set a new one with `ellipsis host use `.') } }) diff --git a/src/commands/integrations.ts b/src/commands/integrations.ts index d849424..340777e 100644 --- a/src/commands/integrations.ts +++ b/src/commands/integrations.ts @@ -24,7 +24,7 @@ export function registerIntegration(program: Command): void { } printTable(['INTEGRATION', 'STATUS', 'DETAILS'], integrationRows(integrations)) console.log( - '\nList resources: agent github repos, agent slack channels, agent linear teams, agent sentry orgs', + '\nList resources: ellipsis github repos, ellipsis slack channels, ellipsis linear teams, ellipsis sentry orgs', ) }) }) diff --git a/src/commands/model.ts b/src/commands/model.ts index 48fa3bd..2d71755 100644 --- a/src/commands/model.ts +++ b/src/commands/model.ts @@ -36,7 +36,7 @@ export function registerModel(program: Command): void { ['ID', 'NAME', 'HARNESS', 'DEFAULT'], models.map((m) => [m.id, m.display_name, m.harness, m.is_default_agent_model ? 'yes' : '']), ) - console.log('\nSet `session.harness.type` and `session.harness.model` in your automation YAML, or use `agent session start --harness --model `.') + console.log('\nSet `session.harness.type` and `session.harness.model` in your automation YAML, or use `ellipsis session start --harness --model `.') }) }) } diff --git a/src/commands/review.ts b/src/commands/review.ts index 57a3429..4869259 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -15,16 +15,16 @@ import type { ReviewScope, } from '../lib/types' -// `agent review`: ask for a code review now, instead of waiting for a push to +// `ellipsis review`: ask for a code review now, instead of waiting for a push to // trigger one. // -// agent review 5975 review that pull request +// ellipsis review 5975 review that pull request // // A review is always of an existing pull request: the range, the checkout, and // the delivery all read PR state, so there is nothing to review without one. // // Which pipeline runs is not a parameter. It is resolved from the repository's -// committed `code_review.yaml` (see `agent review init`), the same way an +// committed `code_review.yaml` (see `ellipsis review init`), the same way an // automatic review resolves it. // // A review is a pipeline of stage sessions, not a single session: its id is a @@ -86,14 +86,14 @@ export function registerReview(program: Command): void { if (opts.json) printJson(started) else { console.log(`✓ started review ${started.id}`) - console.log(` follow with: agent review get ${started.id}`) + console.log(` follow with: ellipsis review get ${started.id}`) } return } // Block-and-stream, then re-read: the findings are collected from the // sandbox at teardown, so they only exist once the review finalizes. - // Same two-step `agent file get` uses. + // Same two-step `ellipsis file get` uses. if (!opts.json) { console.log( `✓ reviewing ${request.owner}/${request.repo}#${started.pull_request.number} ` + @@ -173,9 +173,9 @@ export function registerReview(program: Command): void { registerReviewInit(review) } -// `agent review init`: the code review twin of `agent automation init`. Scaffolds a +// `ellipsis review init`: the code review twin of `ellipsis automation init`. Scaffolds a // starter pipeline YAML locally; you commit it and Ellipsis syncs it from -// GitHub. No API call and no pull request, because `agent automation create` posts +// GitHub. No API call and no pull request, because `ellipsis automation create` posts // an automation and a pipeline is a different kind of file. function registerReviewInit(review: Command): void { review @@ -421,12 +421,12 @@ export function splitRepo(value: string): { owner: string; name: string } { export function parsePullRequest(raw: string): number { const match = /^#?(\d+)$/.exec(raw.trim()) ?? /\/pull\/(\d+)/.exec(raw.trim()) if (!match) { - // `review` reserves the word, so `agent review the auth changes` lands + // `review` reserves the word, so `ellipsis review the auth changes` lands // here rather than starting a session with that prompt. Name the fix. throw new Error( - `'${raw}' is not a pull request number. Pass a number (agent review 123). ` + + `'${raw}' is not a pull request number. Pass a number (ellipsis review 123). ` + 'To run an agent with a prompt that starts with "review", quote it: ' + - `agent "review ${raw} …"`, + `ellipsis "review ${raw} …"`, ) } return Number.parseInt(match[1], 10) diff --git a/src/commands/session.ts b/src/commands/session.ts index 7dc72cd..219f5e8 100644 --- a/src/commands/session.ts +++ b/src/commands/session.ts @@ -91,7 +91,7 @@ export function registerSession(program: Command): void { ) .option( '-f, --config-file ', - 'start from a config file (.yaml/.yml or .json: an automation file, whose session: block is used, or a bare session config); to run a saved automation use `agent automation run`', + 'start from a config file (.yaml/.yml or .json: an automation file, whose session: block is used, or a bare session config); to run a saved automation use `ellipsis automation run`', ) .option( '-t, --template ', @@ -111,7 +111,7 @@ export function registerSession(program: Command): void { ) .option( '--model ', - 'override the selected harness model for this session (see `agent model list`)', + 'override the selected harness model for this session (see `ellipsis model list`)', ) .option('--harness ', 'select claude_code or codex (default: claude_code)', toHarness) .option('--system ', 'retired; put instructions in the prompt or AGENTS.md') @@ -191,7 +191,7 @@ export function registerSession(program: Command): void { throw new Error('provide only one of --config-file / --template') } // An unquoted prompt arrives as one word per argv entry, so join it - // back into a sentence: `agent fix the tests` means one instruction. + // back into a sentence: `ellipsis fix the tests` means one instruction. const promptArg = promptWords.length > 0 ? promptWords.join(' ') : undefined // The prompt is either positional or --prompt, not both. if (promptArg !== undefined && opts.prompt !== undefined) { @@ -209,7 +209,7 @@ export function registerSession(program: Command): void { } // The flat raw-session body: a SessionConfig plus run settings; // there is no base config to merge onto (a saved automation is - // invoked with `agent automation run` instead). + // invoked with `ellipsis automation run` instead). let req: StartAgentSessionRequest = { claude_code: {} } if (opts.configFile) { req = startRequestFromConfig(readConfigFile(opts.configFile)) @@ -304,7 +304,7 @@ export function registerSession(program: Command): void { } console.log(`✓ started session ${session.id} (${session.lifecycle.status})`) await printSessionUrl(client, session.id) - console.log(` follow with: agent session get ${session.id} --watch`) + console.log(` follow with: ellipsis session get ${session.id} --watch`) }) }, ) @@ -330,7 +330,7 @@ export function registerSession(program: Command): void { ) .option( '-a, --author ', - 'only sessions attributed to this GitHub login (see `agent github members`)', + 'only sessions attributed to this GitHub login (see `ellipsis github members`)', ) .option('--days ', 'look back N days', toInt) .option('--since ', 'only sessions at or after this time', (v: string) => parseWhen(v)) diff --git a/src/commands/template.ts b/src/commands/template.ts index 4498559..d2c75e4 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -36,7 +36,7 @@ export function registerTemplate(program: Command): void { ['SLUG', 'NAME', 'DESCRIPTION'], templates.map((t) => [t.slug, t.name, t.description]), ) - console.log('\nCreate one: agent automation init --template --repo ') + console.log('\nCreate one: ellipsis automation init --template --repo ') }) }) } diff --git a/src/commands/uninstall.ts b/src/commands/uninstall.ts index 9e761be..2c73499 100644 --- a/src/commands/uninstall.ts +++ b/src/commands/uninstall.ts @@ -1,8 +1,24 @@ -import { existsSync, readFileSync, realpathSync, rmSync, unlinkSync, writeFileSync } from 'node:fs' +import { + existsSync, + lstatSync, + readFileSync, + readlinkSync, + realpathSync, + rmSync, + unlinkSync, + writeFileSync, +} from 'node:fs' import { homedir } from 'node:os' +import { dirname, join, resolve } from 'node:path' import type { Command } from 'commander' import { configDir } from '../lib/config' -import { installKind, startupFiles, stripInstallerLines } from '../lib/install' +import { + ALIAS_NAME, + BINARY_NAME, + installKind, + startupFiles, + stripInstallerLines, +} from '../lib/install' interface UninstallOptions { purge?: boolean @@ -27,10 +43,10 @@ function uninstall(purge: boolean): void { const execPath = realpathSync(process.execPath) const kind = installKind(execPath) if (kind === 'source') { - throw new Error('agent is running from source; uninstall only removes an installed binary') + throw new Error('ellipsis is running from source; uninstall only removes an installed binary') } if (kind === 'homebrew') { - throw new Error('this agent was installed with Homebrew. Run `brew uninstall agent` instead') + throw new Error('this binary was installed with Homebrew. Run `brew uninstall agent` instead') } // Startup files first, then the config dir, then the binary itself. Deleting @@ -51,8 +67,10 @@ function uninstall(purge: boolean): void { if (purge) rmSync(config, { recursive: true, force: true }) unlinkSync(execPath) + const alias = removeAlias(execPath) console.log(`Removed ${execPath}`) + if (alias) console.log(`Removed ${alias}`) for (const file of cleaned) console.log(`Removed the PATH line from ${file}`) if (cleaned.length > 0) console.log('Open a new shell to drop it from PATH.') if (purge) { @@ -61,3 +79,18 @@ function uninstall(purge: boolean): void { console.log(`Kept ${config} (hosts and credentials). Delete it with: rm -rf ${config}`) } } + +// The `el` link install.sh made next to the binary. Only a symlink that points +// at our binary is ours to remove; anything else with that name is left alone. +function removeAlias(execPath: string): string | undefined { + const alias = join(dirname(execPath), ALIAS_NAME) + try { + if (!lstatSync(alias).isSymbolicLink()) return undefined + const target = readlinkSync(alias) + if (target !== BINARY_NAME && resolve(dirname(alias), target) !== execPath) return undefined + unlinkSync(alias) + return alias + } catch { + return undefined + } +} diff --git a/src/commands/update.ts b/src/commands/update.ts index 4441bda..cd536dc 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -48,11 +48,11 @@ async function check(quiet: boolean): Promise { if (quiet) return const cmp = compareVersions(latest, VERSION) if (cmp > 0) { - console.log(`agent ${latest} is available (you have ${VERSION}). Run \`agent update\`.`) + console.log(`ellipsis ${latest} is available (you have ${VERSION}). Run \`ellipsis update\`.`) } else if (cmp === 0) { - console.log(`agent ${VERSION} is the latest release.`) + console.log(`ellipsis ${VERSION} is the latest release.`) } else { - console.log(`agent ${VERSION} is newer than the latest release (${latest}).`) + console.log(`ellipsis ${VERSION} is newer than the latest release (${latest}).`) } } @@ -60,11 +60,11 @@ async function update(to: string | undefined): Promise { const execPath = realpathSync(process.execPath) const kind = installKind(execPath) if (kind === 'source') { - throw new Error('agent is running from source; update only replaces an installed binary') + throw new Error('ellipsis is running from source; update only replaces an installed binary') } if (kind === 'homebrew') { throw new Error( - 'this agent was installed with Homebrew. Run `brew uninstall agent`, then reinstall with install.sh', + 'this binary was installed with Homebrew. Run `brew uninstall agent`, then reinstall with install.sh', ) } const target = releaseTarget(process.platform, process.arch, isMusl()) @@ -80,19 +80,19 @@ async function update(to: string | undefined): Promise { writeUpdateState({ checkedAt: new Date().toISOString(), latest: version }) const cmp = compareVersions(version, VERSION) if (cmp === 0) { - console.log(`agent ${VERSION} is already the latest release.`) + console.log(`ellipsis ${VERSION} is already the latest release.`) return } if (cmp < 0) { console.log( - `agent ${VERSION} is newer than the latest release (${version}). Pass --to ${version} to downgrade.`, + `ellipsis ${VERSION} is newer than the latest release (${version}). Pass --to ${version} to downgrade.`, ) return } } - console.log(`Updating agent from ${VERSION} to ${version} (${target})...`) + console.log(`Updating ellipsis from ${VERSION} to ${version} (${target})...`) await replaceBinary(execPath, version, target) const reported = execFileSync(execPath, ['--version'], { stdio: 'pipe' }).toString().trim() - console.log(`Updated agent to ${reported} at ${execPath}`) + console.log(`Updated ellipsis to ${reported} at ${execPath}`) } diff --git a/src/lib/api.ts b/src/lib/api.ts index b39f9e8..bdd9ac4 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -26,7 +26,7 @@ function fetchWithUserAgent( // Both args are optional overrides; when omitted, each is resolved through the // precedence chain (explicit → env → config → default) in config.ts. The token // may legitimately be absent: the device-code auth routes are unauthenticated, -// and the server answers 401 for anything else, which reads as "run `agent +// and the server answers 401 for anything else, which reads as "run `ellipsis auth // login`" via friendlyErrorMessage. export function api(base?: string, token?: string): Ellipsis { return new Ellipsis({ diff --git a/src/lib/args.ts b/src/lib/args.ts index 34a2072..b33baa4 100644 --- a/src/lib/args.ts +++ b/src/lib/args.ts @@ -97,8 +97,8 @@ export function parseWhen(value: string, now: Date = new Date()): string { return value } -// A bare `agent ` is shorthand for starting a session with that text as -// the prompt, so a mistyped subcommand like `agent sesion` would silently start +// A bare `ellipsis ` is shorthand for starting a session with that text as +// the prompt, so a mistyped subcommand like `ellipsis sesion` would silently start // a session instead of failing. Guard on shape rather than edit distance: a // real prompt is a sentence, a typo is one word. So one bare word that is not a // known command is treated as a mistake, even when nothing looks close to it. @@ -163,12 +163,12 @@ export function commandTypoMessage(word: string, commands: string[]): string { const similar = similarCommands(word, commands) const lines = [`error: unknown command "${word}"`] if (similar.length === 1) { - lines.push(` did you mean "agent ${similar[0]}"?`) + lines.push(` did you mean "ellipsis ${similar[0]}"?`) } else if (similar.length > 1) { - lines.push(` did you mean one of: ${similar.map((c) => `agent ${c}`).join(', ')}?`) + lines.push(` did you mean one of: ${similar.map((c) => `ellipsis ${c}`).join(', ')}?`) } - lines.push(` to start a session with that prompt: agent -p ${word}`) - lines.push(' to see every command: agent --help') + lines.push(` to start a session with that prompt: ellipsis -p ${word}`) + lines.push(' to see every command: ellipsis --help') return lines.join('\n') } diff --git a/src/lib/config.ts b/src/lib/config.ts index ff80d4b..8377d99 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -23,7 +23,7 @@ function configFile(): string { // to build clickable links and the login verification URL — derived from // `apiBase` by default (api. -> app.), but stored explicitly so a self-hosted // instance whose dashboard host isn't a mechanical swap can set it directly -// (`agent host add … --app-base`). `token` is the credential minted against +// (`ellipsis host add … --app-base`). `token` is the credential minted against // THIS instance. export interface Host { apiBase: string @@ -50,7 +50,7 @@ interface CliConfigV1 { // Swap the `api` host label for `app` (api.ellipsis.dev -> app.ellipsis.dev, // beta-api.ellipsis.dev -> beta-app.ellipsis.dev). An unrecognized host (a // self-hosted deployment whose dashboard isn't a mechanical swap) is returned -// unchanged — set the app base explicitly via `agent host … --app-base`. +// unchanged — set the app base explicitly via `ellipsis host … --app-base`. export function deriveAppBase(apiBase: string): string { const base = apiBase.replace(/\/+$/, '') return base.replace('://api.', '://app.').replace('-api.', '-app.') @@ -58,7 +58,7 @@ export function deriveAppBase(apiBase: string): string { // A friendly default name for a host seeded from a bare API base: the prod URL // is "prod", a `