Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ agent session start --config-file f.json # ...or from an inline config
agent session start --template welcome-to-ellipsis # ...or from a maintained template
agent session start --config <id> --config-override "budget:\n session: 5" # override config fields for this session
agent session start --config <id> --watch # start and immediately stream it
agent session list --limit 20 # list recent sessions (filter by --source, --author, --days, …)
agent session list --limit 20 # list recent sessions (filter by --source, --author, --since, …)
agent session search "webhook retries" # search session history: transcripts, recaps, created PRs, similarity
agent session search "acme/api#512" --author tony --since "3 days ago" # PR-shaped queries and facets
agent session get <session-id> # inspect one session (prints a dashboard link)
agent session get <session-id> --watch # follow a session until it finishes
agent session records <session-id> # read a session's stored transcript, one line per record
agent session record <session-id> # read a session's stored transcript, one line per record
agent session connect <session-id> # connect to a session: transcript + live output + send messages
agent session connect # inside an Ellipsis sandbox: connects to the running session
agent session stop <session-id> # stop an in-flight session

agent config list # list saved agent configs
agent config get <config-id> # show one config as YAML (-o json for JSON)
agent config get <config-id> # show one config as YAML (--json for JSON)
agent config init [path] # scaffold a starter config (default: agents/my_agent.yaml)
agent config create --repo api --file agents/foo.yaml # create an agent via a pull request (or --template <slug>)
agent config default # the effective default agent for the repo you are standing in
Expand All @@ -76,7 +76,7 @@ agent config default clear # clear the account default (--repo [owner/nam

agent model list # list selectable agent models (the account default is marked)

agent integrations # every connected integration in one table
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
Expand All @@ -91,17 +91,23 @@ agent asset delete <asset-id> # delete an asset (it disappears from list/get

agent sandbox variable list # list sandbox env variable names (values are write-only)
agent sandbox variable set A=1 B=2 # create/update variables (or --from-file .env/.json)
agent sandbox variable rm K # delete a variable
agent sandbox variable delete K # delete a variable

agent budget # current budget summary
agent usage # usage dashboard for the period

agent analytics reviewers --account-type bot # which apps review the most PRs
agent analytics prs --days 30 # PR volume/trend with human vs bot splits
agent analytics reviews --repo my-service # review totals + top reviewers
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 ping # check authenticated /v1 connectivity
```

Every command shown is singular. The plural spelling of each (`agent assets`,
`agent sessions`, `agent 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 `/v1` REST API. Point it at a different instance durably with
`agent host` (below), or per-invocation with `ELLIPSIS_API_BASE_URL` (or the
Expand Down
2 changes: 1 addition & 1 deletion scripts/smoke-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ run me
run budget
run usage
run config list
run run list --limit 5
run session list --limit 5

echo "== Logout should clear the token (next call 401s) =="
run logout
Expand Down
2 changes: 1 addition & 1 deletion scripts/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ run me
run budget
run usage
run config list
run run list --limit 5
run session list --limit 5

echo "== Logout should clear the token (next call 401s) =="
run logout
Expand Down
125 changes: 125 additions & 0 deletions skills/cli-conventions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
name: cli-conventions
description: How to name commands, arguments, and flags in the Ellipsis agent CLI, and how to write its --help text. Use when adding or renaming a command, adding a flag, writing or reviewing a command description, or changing anything under src/commands/ in the ellipsis-dev/cli repo.
---

# Ellipsis CLI conventions

The primary reader of `agent --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.**

Helpers live in `src/lib/help.ts`. Use them; do not hand-roll `.alias()` or
route text.

## Command names

Singular nouns, one verb per action.

```
agent asset list agent asset delete <asset-id>
agent session start agent config default set <config-id>
```

- **The noun is singular, always.** `asset`, not `assets`. `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 assets list`
runs and prints nothing extra.
- **Read-only integration browsers use a bare plural leaf**: `github repos`,
`slack channels`, `linear teams`, `sentry orgs`. They have no
get/create/delete to disambiguate against, so the extra `list` is noise.
Anything with more than one verb gets `<noun> <verb>`: `asset list`,
`asset get`, `asset upload`, `asset delete`.
- **`delete` is the shown verb**, with `rm` as a hidden alias. Never the
reverse.
- **`list` is the shown verb**, with `ls` hidden.

```ts
const asset = alsoKnownAs(
program.command('asset').description('...'),
'assets',
)

apiRoutes(
alsoKnownAs(asset.command('delete <asset-id>').description('...'), 'rm'),
'DELETE /v1/assets/{id}',
)
```

## Arguments

Kebab-case placeholders: `<session-id>`, `<config-id>`, `<asset-id>`,
`<api-url>`, `<owner/name>`. Never camelCase, and never a bare `<id>` when the
type matters.

## Flags

One meaning per short flag, across the whole CLI. The reserved ones:

| Short | Long | Meaning |
| ----- | ---- | ------- |
| `-o` | `--output <path>` | a file to write to. Never a format. |
| `-d` | `--detach` | start and return. Never `--days`. |
| `-c` | `--config <config-id>` | a saved agent config |
| `-f` | `--file` / `--from-file` / `--config-file` | an input file. Never `--force`. |
| `-r` | `--repo` | a repository |
| `-s` | `--source` | a session source |
| `-a` | `--author` | a GitHub login |
| `-l` | `--limit <n>` | a result cap |
| `-t` | `--template <slug>` | a template |
| `-p` | `--prompt` / `--parent` | (context-dependent, both session-scoped) |
| `-w` | `--watch` | block and stream |
| `-m` | `--metadata` | repeatable key=value |
| `-n` | `--tail <n>` | tail N entries |

Other rules:

- **`--json` is the only way to ask for JSON.** There is no `-o json`, no
`--format`. Description: `output raw JSON`, plus a parenthetical when the
JSON differs from the table (`output raw JSON (full record payloads)`).
- **Time windows are `--since` / `--until`, plus `--days <n>`.** Both accept
ISO 8601 and `today`, `yesterday`, `N days ago` via `parseWhen`. `--start` /
`--end` are dead; do not reintroduce them.
- **Repeatable flags say so**: `(repeatable)` at the end of the description.
- **Coerce and validate in `src/lib/args.ts`**, so a typo fails locally with
the full list of valid values instead of a server 422.

## Descriptions

One line, imperative verb first, no trailing period.

- **Say what the caller gets, not which endpoint answers.** `List your stored
assets, newest first` — not `List assets (GET /v1/assets)`.
- **Routes go in the long help**, last, via `apiRoutes(cmd, 'GET /v1/...')`.
When a command also has an `addHelpText('after', ...)` usage note, chain the
note *inside* the `apiRoutes()` call so the route line still lands last.
- **Name the concept the same way every time.** The object under `agent
config` is an **agent config** — never "configuration", never bare "agent".
- **No em dashes or en dashes** in any new user-facing string. Use a colon, a
comma, or two sentences. (Legacy table placeholders still hold `—`; do not
add more, and prefer `-` for new ones.)
- **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\`)`.

## Top-level help

`agent --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.

## Checklist for a new command

1. Singular name; plural registered via `alsoKnownAs`.
2. Kebab-case argument placeholders.
3. Short flags match the reserved table, or have none.
4. Description: imperative, one line, no route, no em dash.
5. Routes via `apiRoutes`, chained outside any usage note.
6. `--json` if it prints structured data.
7. New top-level command added to `TOP_LEVEL_GROUPS`.
8. `npm run typecheck && npm test`, then read the actual `--help` output.
40 changes: 19 additions & 21 deletions skills/ellipsis/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,22 @@ sandbox image actually builds, then deploy. The steps below are that flow.
agent sandbox variable set --from-file .env
```

5. Draft a config and prove its environment before anything deploys.
`agent sandbox build` runs the config's environment definition (base image
plus your Dockerfile layers, repository checkout, dependency setup, and
with `--hooks` the lifecycle hooks) with no agent and no token spend,
streaming the build output. A broken toolchain install fails here in
minutes with its full log, not inside your first real session, and a green
build is cached so that first session starts warm.
5. Draft a config and run it from the local file before anything deploys.
Starting from `--config-file` provisions the sandbox from that config's
environment definition (base image plus your Dockerfile layers, repository
checkout, dependency setup, lifecycle hooks) and streams the startup output,
so a broken toolchain install surfaces with its full log on this run rather
than after the config is merged. A successful build is cached, so the next
session starts warm.

```sh
agent config init agents/my_agent.yaml
agent sandbox build start --config-file agents/my_agent.yaml --watch
agent sandbox build start --config-file agents/my_agent.yaml --hooks --watch
agent session start --config-file agents/my_agent.yaml --watch
```

6. Test a session from the local file, then deploy it as a pull request:
6. Deploy it as a pull request:

```sh
agent session start --config-file agents/my_agent.yaml --watch
agent config create --repo api --file agents/my_agent.yaml
```

Expand All @@ -121,8 +119,8 @@ The same flow with transcripts: https://www.ellipsis.dev/docs/guides/cli-setup
- **Sandbox**: each session gets an isolated cloud sandbox with Python, Node,
git, the `gh` CLI, and the repositories pre-cloned. Dependency installs are
cached into the image, so repeat sessions start in seconds. Compute,
lifecycle hooks, and extra image layers are per-agent YAML, and
`agent sandbox build` tests the image on its own, before any agent runs.
lifecycle hooks, and extra image layers are per-agent YAML, and a
`--config-file` start exercises them before the config is merged.
- **Secrets and permissions**: credentials are stored once in a write-only
variable store and injected by name; nothing in a sandbox can read values
back. Each agent's GitHub token narrows to the permissions and repositories
Expand Down Expand Up @@ -173,7 +171,7 @@ agent login # device-code auth tied to GitHub identity
Start and follow work:

```sh
agent session start --config <id> --watch # start a session and stream it
agent session start --config <config-id> --watch # start a session and stream it
agent session start --template welcome-to-ellipsis
agent session list --limit 20
agent session get <session-id> --watch # follow until it finishes
Expand All @@ -188,16 +186,16 @@ Search and audit what agents have done:

```sh
agent session search "webhook retries" # transcripts, recaps, PRs, similarity
agent session records <session-id> # stored feed, one line per record
agent session record <session-id> # stored feed, one line per record
agent session log <session-id> # download the complete session log
agent analytics reviewers --account-type bot # human vs bot PR analytics
agent analytics reviewer --account-type bot # human vs bot PR analytics
```

Hand local work to the cloud, and sync local Claude Code sessions into the
same searchable history:

```sh
agent hooks install # transcript sync via CC hooks
agent hook install # transcript sync via CC hooks
agent session handoff "finish the validator; tests fail on shift boundaries"
```

Expand All @@ -206,13 +204,13 @@ Author and inspect agents:
```sh
agent config init # scaffold agents/my_agent.yaml
agent config create --template code-reviewer --repo api # deploy via PR
agent config default set <configId> # the agent a bare start runs (--repo for one repo)
agent config default set <config-id> # the config a bare start runs (--repo for one repo)
agent template list # browse maintained templates
agent model list # the model ids valid under `claude:`
agent integrations # connected GitHub/Slack/Linear/Sentry
agent integration # connected GitHub/Slack/Linear/Sentry
agent sandbox variable set LINEAR_API_KEY=...
agent sandbox build start --config-file agents/my_agent.yaml --watch
# prove the image before deploying
agent session start --config-file agents/my_agent.yaml --watch
# prove a config before deploying
```

## Defining an agent
Expand Down
16 changes: 9 additions & 7 deletions src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { registerSession } from './commands/session'
import { registerConfig } from './commands/config'
import { registerSandbox } from './commands/sandbox'
import { registerAsset } from './commands/asset'
import { registerHooks } from './commands/hooks'
import { registerHook } from './commands/hooks'
import { registerTemplate } from './commands/template'
import { registerModel } from './commands/model'
import { registerIntegrations } from './commands/integrations'
import { registerIntegration } from './commands/integrations'
import { registerGithub } from './commands/github'
import { registerSlack } from './commands/slack'
import { registerLinear } from './commands/linear'
Expand All @@ -18,6 +18,7 @@ import { registerUsage } from './commands/usage'
import { registerAnalytics } from './commands/analytics'
import { registerPing } from './commands/ping'
import { VERSION } from './lib/constants'
import { configureCliHelp } from './lib/help'
import { canHostSessionsUi, defaultStartRequest, runSessionsUi } from './ui/launch'

const program = new Command()
Expand All @@ -26,9 +27,10 @@ program
.name('agent')
.description('Ellipsis agent CLI: drive the Ellipsis cloud from your terminal')
.version(VERSION)
// Set before the register* calls so every subcommand inherits it and lists
// its own subcommands alphabetically too.
.configureHelp({ sortSubcommands: true })

// Set before the register* calls so every subcommand inherits the same help
// rendering (sorted, alias-free, grouped at the top level).
configureCliHelp(program)

registerLogin(program)
registerHost(program)
Expand All @@ -37,10 +39,10 @@ registerSession(program)
registerConfig(program)
registerSandbox(program)
registerAsset(program)
registerHooks(program)
registerHook(program)
registerTemplate(program)
registerModel(program)
registerIntegrations(program)
registerIntegration(program)
registerGithub(program)
registerSlack(program)
registerLinear(program)
Expand Down
Loading