From ff1d31c39b0d6480fb346c0ebfbfbc94ce1b4fda Mon Sep 17 00:00:00 2001 From: hbrooks Date: Sun, 9 Aug 2026 15:27:18 -0400 Subject: [PATCH] api: drop the /v1 prefix from every route The backend removed the /v1 prefix from the public API routes (ellipsis-dev/ellipsis#6166): everything now answers at /sessions, /reviews, /models, etc. Update every path the CLI builds - the REST client, the WebSocket stream URL, the apiRoutes help lines - plus the tests, docs, skills, and smoke scripts that spell out routes. The one v1 left behind is a comment pointing at the mono repo file ellipsis/src/public_api/routers/v1/v1_router.py, which is a real path, not a route. Config-file 'version: v1' YAML and the v1->v2 config migration are unrelated and untouched. Still pending separately: bump @ellipsis-dev/sdk once 0.6.0 (with the regenerated prefix-free paths) is published from the mono repo. --- README.md | 10 +-- docs/RUN_STREAMING_SPEC.md | 22 +++---- scripts/smoke-local.sh | 4 +- scripts/smoke.sh | 4 +- skills/cli-conventions/SKILL.md | 6 +- skills/ellipsis/SKILL.md | 6 +- src/commands/analytics.ts | 6 +- src/commands/asset.ts | 8 +-- src/commands/config.ts | 18 +++--- src/commands/connect.ts | 6 +- src/commands/github.ts | 4 +- src/commands/help.ts | 2 +- src/commands/integrations.ts | 2 +- src/commands/linear.ts | 2 +- src/commands/me.ts | 2 +- src/commands/model.ts | 2 +- src/commands/ping.ts | 4 +- src/commands/review.ts | 20 +++--- src/commands/sentry.ts | 2 +- src/commands/session.tsx | 44 ++++++------- src/commands/slack.ts | 4 +- src/commands/template.ts | 2 +- src/commands/usage.ts | 4 +- src/commands/variable.ts | 6 +- src/lib/api.ts | 108 ++++++++++++++++---------------- src/lib/config.ts | 2 +- src/lib/laptop.ts | 8 +-- src/lib/sessions.ts | 2 +- src/lib/stream.ts | 4 +- src/lib/types.ts | 41 ++++++------ src/lib/urls.ts | 4 +- src/ui/SessionsApp.tsx | 4 +- src/ui/launch.tsx | 2 +- test/api.test.ts | 48 +++++++------- test/discovery.test.ts | 14 ++--- test/output.test.ts | 10 +-- test/search.test.ts | 6 +- test/stream.test.ts | 4 +- 38 files changed, 224 insertions(+), 223 deletions(-) diff --git a/README.md b/README.md index 4d62dc1..da5b700 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ 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 ping # check authenticated /v1 connectivity +agent ping # check authenticated API connectivity ``` Every command shown is singular. The plural spelling of each (`agent assets`, @@ -117,7 +117,7 @@ left out of `--help`. See 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 +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 legacy `ELLIPSIS_API_BASE`). @@ -184,7 +184,7 @@ npm run compile # single-binary build (bun) - `scripts/smoke-local.sh` is a **fully-automated** end-to-end check against a local `docker compose` backend. It drives the device-code login itself — scraping the verification code and approving it headlessly through the - running `public_api` container — then exercises the authenticated `/v1` calls + running `public_api` container — then exercises the authenticated API calls with a throwaway config dir. One command, no manual approval: ```sh @@ -193,7 +193,7 @@ npm run compile # single-binary build (bun) ``` - `scripts/smoke.sh` is the manual variant for any backend (incl. staging/prod): - it drives login and the `/v1` calls but waits for you to approve in the + it drives login and the API calls but waits for you to approve in the dashboard. See its header for the approval options. ```sh @@ -233,7 +233,7 @@ scoped to that one repo only — no account-wide PAT involved. ### Status -The full `/v1` REST surface (auth, sessions, session search/steps, configs, +The full public REST surface (auth, sessions, session search/steps, configs, integration discovery, budget/usage) is wired against the live API, including live WebSocket streaming and `session stop`. Still pending: replacing the hand-rolled request/response types with the diff --git a/docs/RUN_STREAMING_SPEC.md b/docs/RUN_STREAMING_SPEC.md index e0c080c..fa71ce3 100644 --- a/docs/RUN_STREAMING_SPEC.md +++ b/docs/RUN_STREAMING_SPEC.md @@ -5,15 +5,15 @@ ## 1. Background -The CLI can start runs and read their state over the public `/v1` REST API, but +The CLI can start runs and read their state over the public REST API, but it cannot stream a run's output. `agent run get --watch` exists today and gives a -**status-level** live view by polling `GET /v1/agents/runs/{id}` until the run +**status-level** live view by polling `GET /agents/runs/{id}` until the run reaches a terminal status (`completed`/`error`/`cancelled`/`stopped`). It shows status transitions and the final summary — not the step-by-step output. -**Crucially, the backend already streams steps live — just not over `/v1`.** The +**Crucially, the backend already streams steps live — just not over the public API.** The dashboard consumes a WebSocket stream; this work is about re-exposing that same -stream under `/v1` for bearer-authenticated CLI clients. The bulk of the +stream on the public API for bearer-authenticated CLI clients. The bulk of the machinery (step model, persistence, event bus) already exists and must be reused, not reinvented. @@ -56,14 +56,14 @@ not reinvented. back to REST status-polling when streaming is unavailable. The same flag covers both modes — no new top-level command. -## 3. Server-side requirements (`/v1`) +## 3. Server-side requirements (public API) **Re-export the existing stream; do not build a parallel one.** Reuse `agent_steps`, `CCStep`, and the `AgentEventBus` exactly as the frontend stream -does. The `/v1` endpoint is a thin re-auth + re-encode of `_stream_run_loop`. +does. The public endpoint is a thin re-auth + re-encode of `_stream_run_loop`. -1. **Endpoint:** `GET /v1/agents/runs/{run_id}/stream`, upgraded to WebSocket. -2. **Auth — bearer, not ticket.** The CLI holds a `/v1` bearer token, so resolve +1. **Endpoint:** `GET /agents/runs/{run_id}/stream`, upgraded to WebSocket. +2. **Auth — bearer, not ticket.** The CLI holds a public API bearer token, so resolve it with the same `V1Auth` path as the REST API (Authorization header on the handshake, which non-browser clients *can* set), authorizing the run's customer. The 60s `?ticket=` dance is a browser workaround the CLI doesn't @@ -85,7 +85,7 @@ does. The `/v1` endpoint is a thin re-auth + re-encode of `_stream_run_loop`. ## 4. Client-side requirements (this repo) -1. `agent run get --watch` opens the `/v1` stream and renders frames: +1. `agent run get --watch` opens the public API stream and renders frames: `snapshot`/`steps_append` → render each `CCStep` (assistant text + tool calls, tool stdout/stderr, thinking if `--verbose`); `run` → status transitions; terminal close → final summary. Exit 0 on a successful terminal status, @@ -121,7 +121,7 @@ Mirror the backend's existing codes where possible: time, sharing the `AgentStep`/`CCStep` schema with the dashboard. - Killing the socket mid-run and reconnecting with `?since=` resumes with no lost or duplicated steps and without a full re-snapshot. -- `--watch` against a backend without the `/v1` endpoint transparently falls back +- `--watch` against a backend without the stream endpoint transparently falls back to REST status-polling and still completes. - `--json --watch` emits valid NDJSON, one frame per line. - Unit tests for the client frame handler, the `since` resume cursor, and the @@ -132,7 +132,7 @@ Mirror the backend's existing codes where possible: ## 7. Out of scope - Bidirectional control (stop/input). `run stop` is tracked separately and also - has no `/v1` endpoint yet. + has no stream endpoint yet. - Re-architecting the transport (NOTIFY + DB-as-source-of-truth stays). - Multiplexing multiple runs over one socket. diff --git a/scripts/smoke-local.sh b/scripts/smoke-local.sh index 8a39901..e5ea705 100755 --- a/scripts/smoke-local.sh +++ b/scripts/smoke-local.sh @@ -6,7 +6,7 @@ # this drives the whole device-code flow itself: it starts `agent 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 /v1 surface. Uses a throwaway config dir, so your real token is +# authenticated API surface. Uses a throwaway config dir, so your real token is # never touched. # # Prereqs: docker compose up (public_api reachable at $ELLIPSIS_API_BASE). @@ -99,7 +99,7 @@ LOGIN_PID="" cat "$LOGIN_OUT" echo -echo "== Authenticated /v1 calls ==" +echo "== Authenticated API calls ==" run me run budget run usage diff --git a/scripts/smoke.sh b/scripts/smoke.sh index 54202e1..2659cb7 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -2,7 +2,7 @@ # # Manual end-to-end smoke test of the Ellipsis CLI against a backend. # -# Drives the device-code login flow and then exercises the authenticated /v1 +# Drives the device-code login flow and then exercises the authenticated public API # surface. Uses an isolated config dir so it never touches your real token. # # Usage: @@ -39,7 +39,7 @@ echo echo "== Logging in (approve the printed request, then this continues) ==" npx tsx src/cli.tsx login --no-browser -echo "== Authenticated /v1 calls ==" +echo "== Authenticated API calls ==" run me run budget run usage diff --git a/skills/cli-conventions/SKILL.md b/skills/cli-conventions/SKILL.md index b6c9e1d..c9b0c9a 100644 --- a/skills/cli-conventions/SKILL.md +++ b/skills/cli-conventions/SKILL.md @@ -45,7 +45,7 @@ const asset = alsoKnownAs( apiRoutes( alsoKnownAs(asset.command('delete ').description('...'), 'rm'), - 'DELETE /v1/assets/{id}', + 'DELETE /assets/{id}', ) ``` @@ -93,8 +93,8 @@ Other rules: 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/...')`. + assets, newest first` — not `List assets (GET /assets)`. +- **Routes go in the long help**, last, via `apiRoutes(cmd, 'GET /...')`. 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 diff --git a/skills/ellipsis/SKILL.md b/skills/ellipsis/SKILL.md index f68bd03..c3446c3 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/v1, and the `agent` CLI. All three drive the same `/v1` API. +api.ellipsis.dev, and the `agent` 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. @@ -72,7 +72,7 @@ fee. There are no seats. - **A task that should not block the laptop**: `agent session handoff` pushes a snapshot of the working tree and continues the work in a cloud session. - **Delegation from scripts or CI**: `agent session start` or - `POST /v1/sessions`. With `--watch` it streams into the log and exits nonzero + `POST /sessions`. With `--watch` it streams into the log and exits nonzero unless the session completes, so it works as a gate. Things teams actually build: screenshot every pull request that touches the @@ -359,7 +359,7 @@ cost and latency. ## The agent CLI -One open-source binary named `agent`, a terminal client for the same `/v1` API +One open-source binary named `agent`, 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. diff --git a/src/commands/analytics.ts b/src/commands/analytics.ts index 95c0021..c56f5ab 100644 --- a/src/commands/analytics.ts +++ b/src/commands/analytics.ts @@ -78,7 +78,7 @@ export function registerAnalytics(program: Command): void { .description('Rank who reviews the most PRs, people and apps alike'), 'reviewers', ), - 'GET /v1/analytics/metrics', + 'GET /analytics/metrics', ) .option( '-r, --repo ', @@ -148,7 +148,7 @@ export function registerAnalytics(program: Command): void { .description('Show pull request volume and trend, split human vs bot'), 'prs', ), - 'GET /v1/analytics/pull-requests', + 'GET /analytics/pull-requests', ) .option( '--account-type ', @@ -211,7 +211,7 @@ export function registerAnalytics(program: Command): void { .description('Show review totals, verdicts, and the top reviewers'), 'reviews', ), - 'GET /v1/analytics/reviews', + 'GET /analytics/reviews', ) .option( '-r, --repo ', diff --git a/src/commands/asset.ts b/src/commands/asset.ts index ac01371..2da9ff2 100644 --- a/src/commands/asset.ts +++ b/src/commands/asset.ts @@ -87,7 +87,7 @@ export function registerAsset(program: Command): void { asset .command('upload ') .description('Upload a PNG and print its org-gated URL, ready to paste into a PR comment'), - 'POST /v1/assets', + 'POST /assets', ) .option('--json', 'output raw JSON') .action(async (path: string, opts: { json?: boolean }) => { @@ -106,7 +106,7 @@ export function registerAsset(program: Command): void { asset.command('list').description('List your stored assets, newest first'), 'ls', ), - 'GET /v1/assets', + 'GET /assets', ) .option('--session ', 'only assets uploaded by this agent session') .option('-l, --limit ', 'max results (server cap: 250)', parsePositiveInt) @@ -142,7 +142,7 @@ export function registerAsset(program: Command): void { asset .command('get ') .description("Print one asset's metadata, or download its bytes with -o"), - 'GET /v1/assets/{id}', + 'GET /assets/{id}', 'presigned S3 GET', ) .option('-o, --output ', 'write the file contents to this path') @@ -170,7 +170,7 @@ export function registerAsset(program: Command): void { .description('Delete an asset, so its link stops resolving'), 'rm', ), - 'DELETE /v1/assets/{id}', + 'DELETE /assets/{id}', ) .option('--json', 'output raw JSON') .action(async (assetId: string, opts: { json?: boolean }) => { diff --git a/src/commands/config.ts b/src/commands/config.ts index 7215d86..01f2529 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -29,7 +29,7 @@ export function registerConfig(program: Command): void { config.command('list').description('List your saved agent configs'), 'ls', ), - 'GET /v1/configs', + 'GET /configs', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -59,7 +59,7 @@ export function registerConfig(program: Command): void { config .command('get ') .description('Print one agent config as YAML, or as JSON with --json'), - 'GET /v1/configs/{id}', + 'GET /configs/{id}', ) .option('--json', 'output raw JSON') .action(async (configId: string, opts: { json?: boolean }) => { @@ -85,7 +85,7 @@ export function registerConfig(program: Command): void { config .command('create') .description('Create an agent config by opening a pull request that adds it to a repo'), - 'POST /v1/configs', + 'POST /configs', ) .requiredOption( '-r, --repo ', @@ -148,11 +148,11 @@ export function registerConfig(program: Command): void { .description('Show or set which agent config runs when a session names none'), 'defaults', ), - 'GET /v1/defaults', + 'GET /defaults', ) .option('--json', 'output raw JSON') // Bare `agent config default`: the effective default for the repo you're - // standing in, computed locally from GET /v1/defaults + the origin remote + // standing in, computed locally from GET /defaults + the origin remote // (the same ladder session start resolves server-side). .action(async (opts: { json?: boolean }) => { await runAction(async () => { @@ -189,7 +189,7 @@ export function registerConfig(program: Command): void { .description('List every default that is set, account rung and per-repo rungs'), 'ls', ), - 'GET /v1/defaults', + 'GET /defaults', ) .option('--json', 'output raw JSON') // The group also defines --json (for the bare view), and commander parses @@ -223,7 +223,7 @@ export function registerConfig(program: Command): void { defaults .command('set ') .description('Set the account default agent config, or a repo default with --repo'), - 'PUT /v1/defaults', + 'PUT /defaults', ) .option( '-r, --repo [repository]', @@ -256,7 +256,7 @@ export function registerConfig(program: Command): void { 'rm', 'delete', ), - 'DELETE /v1/defaults', + 'DELETE /defaults', ) .option( '-r, --repo [repository]', @@ -278,7 +278,7 @@ export function registerConfig(program: Command): void { .description( `Scaffold a starter agent config YAML locally (default: ${DEFAULT_CONFIG_PATH})`, ), - 'POST /v1/configs with --template', + 'POST /configs with --template', ) // No `-f` short: CLI-wide, `-f` means an input file (see `config create`). .option('--force', 'overwrite the file if it already exists') diff --git a/src/commands/connect.ts b/src/commands/connect.ts index c4bb8c7..dab3080 100644 --- a/src/commands/connect.ts +++ b/src/commands/connect.ts @@ -14,9 +14,9 @@ import { canHostSessionsUi, defaultStartRequest, runSessionsUi } from '../ui/lau // `agent session connect [sessionId]` — the terminal window into a cloud // session (documents/eng/SESSION_IDE.md §2.6, in the ellipsis monorepo). // -// A pure /v1 client: it renders the conversation so far from the stored +// A pure API client: it renders the conversation so far from the stored // transcript, follows new output live over the session WebSocket, and sends -// what you type through POST /v1/sessions/{id}/messages — the same inbox that +// what you type through POST /sessions/{id}/messages — the same inbox that // delivers webhook events to the agent's Claude Code stdin at the next turn // boundary. It NEVER spawns or attaches a Claude Code process (a second // writer on one CC session corrupts the transcript; the cloud worker is the @@ -59,7 +59,7 @@ export function registerConnect(session: Command): void { headless or from inside the session's own sandbox, where the id is optional. Pass --no-input to follow read-only from a script or agent (no TTY needed). -API: GET /v1/sessions/{id}, GET /v1/sessions/{id}/records, POST /v1/sessions/{id}/messages, WS /v1/sessions/{id}/stream`, +API: GET /sessions/{id}, GET /sessions/{id}/records, POST /sessions/{id}/messages, WS /sessions/{id}/stream`, ) .action(async (sessionId: string | undefined, opts: { records: boolean; input: boolean }) => { await runAction(async () => { diff --git a/src/commands/github.ts b/src/commands/github.ts index ddcd0ca..d121378 100644 --- a/src/commands/github.ts +++ b/src/commands/github.ts @@ -18,7 +18,7 @@ export function registerGithub(program: Command): void { .description('List the repositories the GitHub installation can reach'), 'repo', ), - 'GET /v1/github/repos', + 'GET /github/repos', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -53,7 +53,7 @@ export function registerGithub(program: Command): void { ), 'member', ), - 'GET /v1/github/members', + 'GET /github/members', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/help.ts b/src/commands/help.ts index 106c9f6..233cbaf 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -21,7 +21,7 @@ export function registerHelp(program: Command): void { program .command('help') .description('Show help for a command, or ask the help agent with --interactive'), - 'POST /v1/sessions with --interactive', + 'POST /sessions with --interactive', ) .argument('[command...]', 'command to show help for (e.g. `session start`)') .option( diff --git a/src/commands/integrations.ts b/src/commands/integrations.ts index a7d2b0a..07c7e3e 100644 --- a/src/commands/integrations.ts +++ b/src/commands/integrations.ts @@ -12,7 +12,7 @@ export function registerIntegration(program: Command): void { .description('Show which integrations are connected, in one table'), 'integrations', ), - 'GET /v1/integrations', + 'GET /integrations', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/linear.ts b/src/commands/linear.ts index d22f6aa..842cf15 100644 --- a/src/commands/linear.ts +++ b/src/commands/linear.ts @@ -15,7 +15,7 @@ export function registerLinear(program: Command): void { .description('List the Linear teams, marking which have Ellipsis enabled'), 'team', ), - 'GET /v1/linear/teams', + 'GET /linear/teams', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/me.ts b/src/commands/me.ts index 3768b0e..9b1eb15 100644 --- a/src/commands/me.ts +++ b/src/commands/me.ts @@ -18,7 +18,7 @@ export function renderMe(me: WhoAmI): void { export function registerMe(program: Command): void { apiRoutes( program.command('me').description('Show the identity behind the current credential'), - 'GET /v1/me', + 'GET /me', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/model.ts b/src/commands/model.ts index 3cd037c..87fd2e3 100644 --- a/src/commands/model.ts +++ b/src/commands/model.ts @@ -18,7 +18,7 @@ export function registerModel(program: Command): void { ), 'ls', ), - 'GET /v1/models', + 'GET /models', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 42a7948..bf390f9 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -7,11 +7,11 @@ export function registerPing(program: Command): void { program .command('ping') .description('Check that the API is reachable and the credential is valid'), - 'GET /v1/me', + 'GET /me', ) .action(async () => { // There's no unauthenticated health route on the public API, so we probe - // the lightest authenticated endpoint (/v1/me): a 200 proves the API is + // the lightest authenticated endpoint (/me): a 200 proves the API is // reachable AND the stored token is valid. const api = new ApiClient() try { diff --git a/src/commands/review.ts b/src/commands/review.ts index b99c2e8..bd314e1 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -50,9 +50,9 @@ export function registerReview(program: Command): void { review .command('start [pull-request]', { isDefault: true }) .description('Review a pull request by number, or your working tree if you omit one'), - 'POST /v1/reviews', - 'WS /v1/sessions/{id}/stream', - 'GET /v1/reviews/{id}', + 'POST /reviews', + 'WS /sessions/{id}/stream', + 'GET /reviews/{id}', ) .option('--repo ', 'repository to review (default: this git remote)') .option( @@ -121,7 +121,7 @@ export function registerReview(program: Command): void { review .command('get ') .description("Print a review's findings, scope, and whether it posted"), - 'GET /v1/reviews/{id}', + 'GET /reviews/{id}', ) .option('--json', 'output raw JSON') .action(async (reviewId: string, opts: { json?: boolean }) => { @@ -137,7 +137,7 @@ export function registerReview(program: Command): void { review.command('list').description("List a pull request's reviews, newest first"), 'ls', ), - 'GET /v1/reviews', + 'GET /reviews', ) .option('--repo ', 'only reviews of this repository') .option('--pr ', 'only reviews of this pull request', parsePositiveInt) @@ -285,11 +285,11 @@ function registerReviewDefaults(review: Command): void { .description('Show or set which code review pipeline runs when a review names none'), 'defaults', ), - 'GET /v1/reviews/defaults', + 'GET /reviews/defaults', ) .option('--json', 'output raw JSON') // Bare `agent review default`: the effective default for the repo you're - // standing in, computed locally from GET /v1/reviews/defaults + the origin + // standing in, computed locally from GET /reviews/defaults + the origin // remote (the same ladder the server resolves at review start). .action(async (opts: { json?: boolean }) => { await runAction(async () => { @@ -328,7 +328,7 @@ function registerReviewDefaults(review: Command): void { .description('List every default that is set, account rung and per-repo rungs'), 'ls', ), - 'GET /v1/reviews/defaults', + 'GET /reviews/defaults', ) .option('--json', 'output raw JSON') // The group also defines --json (for the bare view), and commander parses @@ -364,7 +364,7 @@ function registerReviewDefaults(review: Command): void { defaults .command('set ') .description('Set the account default code review pipeline, or a repo default with --repo'), - 'PUT /v1/reviews/defaults', + 'PUT /reviews/defaults', ) .option( '-r, --repo [repository]', @@ -397,7 +397,7 @@ function registerReviewDefaults(review: Command): void { 'rm', 'delete', ), - 'DELETE /v1/reviews/defaults', + 'DELETE /reviews/defaults', ) .option( '-r, --repo [repository]', diff --git a/src/commands/sentry.ts b/src/commands/sentry.ts index 7a54f9f..31af405 100644 --- a/src/commands/sentry.ts +++ b/src/commands/sentry.ts @@ -14,7 +14,7 @@ export function registerSentry(program: Command): void { 'org', 'organizations', ), - 'GET /v1/sentry/organizations', + 'GET /sentry/organizations', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/session.tsx b/src/commands/session.tsx index 5e7cd49..a4c1136 100644 --- a/src/commands/session.tsx +++ b/src/commands/session.tsx @@ -92,8 +92,8 @@ export function registerSession(program: Command): void { apiRoutes( session.command('start').description('Start a new agent session in the cloud'), - 'POST /v1/sessions', - 'WS /v1/sessions/{id}/stream with --watch or --connect', + 'POST /sessions', + 'WS /sessions/{id}/stream with --watch or --connect', ) .argument( '[prompt...]', @@ -325,8 +325,8 @@ export function registerSession(program: Command): void { '\nSources: laptop, react, manual, api, cli, mention, cron. ' + '--since/--until accept ISO 8601 or "today", "yesterday", "N days ago".', ), - 'GET /v1/sessions', - 'GET /v1/github/members to resolve --author', + 'GET /sessions', + 'GET /github/members to resolve --author', ) .option('-c, --config ', 'only sessions run by this saved agent config') .option( @@ -401,8 +401,8 @@ export function registerSession(program: Command): void { 'Sources: laptop, react, manual, api, cli, mention, cron. ' + '--since/--until accept ISO 8601 or "today", "yesterday", "N days ago".', ), - 'GET /v1/sessions/search', - 'GET /v1/github/members to resolve --author', + 'GET /sessions/search', + 'GET /github/members to resolve --author', ) .option( '-a, --author ', @@ -498,7 +498,7 @@ export function registerSession(program: Command): void { .description("Print a session's stored transcript, one line per record"), 'records', ), - 'GET /v1/sessions/{id}/records', + 'GET /sessions/{id}/records', ) .option('--json', 'output raw JSON (full record payloads)') .action(async (sessionId: string, opts: { json?: boolean }) => { @@ -526,7 +526,7 @@ export function registerSession(program: Command): void { .description("Download a session's complete archived log to stdout or a file"), 'logs', ), - 'GET /v1/sessions/{id}/log', + 'GET /sessions/{id}/log', ) .option('-o, --output ', 'write to a file instead of stdout') .option('--gzip', 'keep the concatenated .jsonl.gz bytes as-is (skip gunzip)') @@ -580,8 +580,8 @@ export function registerSession(program: Command): void { session .command('get ') .description("Show one session's status, cost, and dashboard link"), - 'GET /v1/sessions/{id}', - 'WS /v1/sessions/{id}/stream with --watch', + 'GET /sessions/{id}', + 'WS /sessions/{id}/stream with --watch', ) .option( '-w, --watch', @@ -620,8 +620,8 @@ export function registerSession(program: Command): void { session .command('replay ') .description("Re-run an existing session's trigger input as a fresh session"), - 'POST /v1/sessions/{id}/replay', - 'WS /v1/sessions/{id}/stream with --watch', + 'POST /sessions/{id}/replay', + 'WS /sessions/{id}/stream with --watch', ) .option( '-c, --config ', @@ -704,7 +704,7 @@ export function registerSession(program: Command): void { session .command('handoff ') .description('Hand this repo and a synced local session off to a cloud agent'), - 'POST /v1/sessions', + 'POST /sessions', ) .requiredOption( '-p, --parent ', @@ -760,7 +760,7 @@ export function registerSession(program: Command): void { session .command('sync') .description('Sync a local Claude Code transcript up, as the installed hooks do'), - 'POST /v1/sessions/sync', + 'POST /sessions/sync', ) .option('--transcript ', 'transcript JSONL path (default: from hook stdin)') .option('--session-id ', 'Claude Code session id (default: from hook stdin)') @@ -783,7 +783,7 @@ export function registerSession(program: Command): void { apiRoutes( session.command('stop ').description('Stop an in-flight session'), - 'POST /v1/sessions/{id}/stop', + 'POST /sessions/{id}/stop', ) .option('--json', 'output raw JSON') .action(async (sessionId: string, opts: { json?: boolean }) => { @@ -798,7 +798,7 @@ export function registerSession(program: Command): void { }) }) - // The browser IDE into a live session's sandbox (GET /v1/sessions/{id}/ide). + // The browser IDE into a live session's sandbox (GET /sessions/{id}/ide). // The URL is the membership-gated dashboard page for the sandbox — no // credential in it, so it is durable and safe to share with any org member. // 409s (sandbox idle/torn down) carry curated server messages; runAction @@ -814,7 +814,7 @@ export function registerSession(program: Command): void { 'org members. If the session is idle, send it a message to wake it first ' + '(agent session connect).', ), - 'GET /v1/sessions/{id}/ide', + 'GET /sessions/{id}/ide', ) .option('--no-open', 'print the URL without opening a browser') .option('--json', 'output raw JSON') @@ -830,7 +830,7 @@ export function registerSession(program: Command): void { }) }) - // A preview port's link (GET /v1/sessions/{id}/ports/{port}) — a dev + // A preview port's link (GET /sessions/{id}/ports/{port}) — a dev // server the agent or the IDE user started in the sandbox, opened through // the same membership-gated dashboard page as the IDE. apiRoutes( @@ -844,7 +844,7 @@ export function registerSession(program: Command): void { 'port, so it is safe to share with org members. The preview renders while ' + 'something in the sandbox listens on that port.', ), - 'GET /v1/sessions/{id}/ports/{port}', + 'GET /sessions/{id}/ports/{port}', ) .option('--no-open', 'print the URL without opening a browser') .option('--json', 'output raw JSON') @@ -993,7 +993,7 @@ export function exitCodeForStatus(status: string): number { // Poll a session until it reaches a terminal status, printing each status // transition. This is the status-level fallback used when live streaming isn't -// available: the /v1 REST API exposes session state, not the step-by-step stream. +// available: the public REST API exposes session state, not the step-by-step stream. export async function watchSession( api: ApiClient, sessionId: string, @@ -1046,7 +1046,7 @@ function printSessionSummary(s: AgentSession): void { } // Print a clickable dashboard link for a session. The route is scoped by -// account login, which isn't on the session object, so resolve it from /v1/me. +// account login, which isn't on the session object, so resolve it from /me. async function printSessionUrl(api: ApiClient, sessionId: string): Promise { const me = await api.whoami() console.log(` ${sessionUrl(resolveAppBase(), me.customer_login, sessionId)}`) @@ -1179,7 +1179,7 @@ function readMappingFile(path: string, label: string): Record { } // Resolve a --author GitHub login to the account id the API filters by -// (author_id on GET /v1/sessions and /v1/sessions/search), via the org roster. +// (author_id on GET /sessions and /sessions/search), via the org roster. // An unknown login fails with the known logins so the user can self-correct. export async function resolveAuthorId(api: ApiClient, login: string): Promise { const { members } = await api.listGithubMembers() diff --git a/src/commands/slack.ts b/src/commands/slack.ts index 8188f81..4d289ff 100644 --- a/src/commands/slack.ts +++ b/src/commands/slack.ts @@ -13,7 +13,7 @@ export function registerSlack(program: Command): void { slack.command('channels').description('List the channels in the Slack workspace'), 'channel', ), - 'GET /v1/slack/channels', + 'GET /slack/channels', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -46,7 +46,7 @@ export function registerSlack(program: Command): void { .description('List the workspace members, with linked GitHub identities'), 'member', ), - 'GET /v1/slack/members', + 'GET /slack/members', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/template.ts b/src/commands/template.ts index 06d4705..65b7748 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -16,7 +16,7 @@ export function registerTemplate(program: Command): void { .description('List the built-in agent templates and the slugs --template takes'), 'ls', ), - 'GET /v1/templates', + 'GET /templates', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/usage.ts b/src/commands/usage.ts index 28a7153..71627fc 100644 --- a/src/commands/usage.ts +++ b/src/commands/usage.ts @@ -6,7 +6,7 @@ import { printJson, runAction, usd, usdFromMillicents } from '../lib/output' export function registerUsage(program: Command): void { apiRoutes( program.command('budget').description("Show this period's spend against the account budget"), - 'GET /v1/budget', + 'GET /budget', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -28,7 +28,7 @@ export function registerUsage(program: Command): void { program .command('usage') .description("Show this period's tokens and cost, broken down by model"), - 'GET /v1/usage', + 'GET /usage', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/variable.ts b/src/commands/variable.ts index d61f02e..4d65aca 100644 --- a/src/commands/variable.ts +++ b/src/commands/variable.ts @@ -20,7 +20,7 @@ export function registerVariable(program: Command): void { variable.command('list').description('List the variable names (never their values)'), 'ls', ), - 'GET /v1/variables', + 'GET /variables', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -34,7 +34,7 @@ export function registerVariable(program: Command): void { variable .command('set [assignments...]') .description('Create or update variables, e.g. `set A=1 B=2`'), - 'PUT /v1/variables', + 'PUT /variables', ) .option('-f, --from-file ', 'load variables from a .env or .json file') .option('--json', 'output raw JSON') @@ -52,7 +52,7 @@ export function registerVariable(program: Command): void { apiRoutes( alsoKnownAs(variable.command('delete ').description('Delete a variable'), 'rm'), - 'DELETE /v1/variables/{name}', + 'DELETE /variables/{name}', ) .option('--json', 'output raw JSON') .action(async (name: string, opts: { json?: boolean }) => { diff --git a/src/lib/api.ts b/src/lib/api.ts index ef82e24..3657065 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -65,7 +65,7 @@ import type { GetSessionLogResponse, } from './types' -// Thin REST client over the public `/v1` API. The session-stream surface +// Thin REST client over the public API. The session-stream surface // types come from @ellipsis-dev/sdk (generated from the backend's schema, via // lib/types re-exports); the rest of the typed surface remains a hand-rolled // mirror of ellipsis/src/public_api/routers/v1/v1_router.py until the SDK's @@ -128,17 +128,17 @@ export class ApiClient { // ------------------------------- identity ------------------------------- whoami(): Promise { - return this.request('GET', '/v1/me') + return this.request('GET', '/me') } // ----------------------------- usage / budget --------------------------- getBudget(): Promise { - return this.request('GET', '/v1/budget') + return this.request('GET', '/budget') } getUsage(): Promise { - return this.request('GET', '/v1/usage') + return this.request('GET', '/usage') } // ------------------------------- analytics ------------------------------ @@ -151,7 +151,7 @@ export class ApiClient { ): Promise { return this.request( 'GET', - '/v1/analytics/metrics', + '/analytics/metrics', undefined, query as Record | undefined, ) @@ -162,7 +162,7 @@ export class ApiClient { ): Promise { return this.request( 'GET', - '/v1/analytics/pull-requests', + '/analytics/pull-requests', undefined, query as Record | undefined, ) @@ -173,7 +173,7 @@ export class ApiClient { ): Promise { return this.request( 'GET', - '/v1/analytics/reviews', + '/analytics/reviews', undefined, query as Record | undefined, ) @@ -182,13 +182,13 @@ export class ApiClient { // ---------------------------- agent sessions ----------------------------- startAgentSession(req: StartAgentSessionRequest): Promise { - return this.request('POST', '/v1/sessions', req) + return this.request('POST', '/sessions', req) } async listAgentSessions(query?: ListAgentSessionsQuery): Promise { const res = await this.request( 'GET', - '/v1/sessions', + '/sessions', undefined, query as Record | undefined, ) @@ -196,7 +196,7 @@ export class ApiClient { } getAgentSession(sessionId: string): Promise { - return this.request('GET', `/v1/sessions/${encodeURIComponent(sessionId)}`) + return this.request('GET', `/sessions/${encodeURIComponent(sessionId)}`) } // Session-grouped search over step text, recap text, created PRs, and @@ -204,7 +204,7 @@ export class ApiClient { searchSessions(query: SearchSessionsQuery): Promise { return this.request( 'GET', - '/v1/sessions/search', + '/sessions/search', undefined, query as unknown as Record, ) @@ -229,37 +229,37 @@ export class ApiClient { ): Promise { const query = options.afterSeq != null && options.afterSeq > 0 ? `?after_seq=${options.afterSeq}` : '' - return this.request('GET', `/v1/sessions/${encodeURIComponent(sessionId)}/records${query}`) + return this.request('GET', `/sessions/${encodeURIComponent(sessionId)}/records${query}`) } // The session's conversation structure — turns and inbox messages, each // message carrying its pending/delivered status (the server-side "queued" // truth). Empty lists for single-shot sessions. getAgentSessionTurns(sessionId: string): Promise { - return this.request('GET', `/v1/sessions/${encodeURIComponent(sessionId)}/turns`) + return this.request('GET', `/sessions/${encodeURIComponent(sessionId)}/turns`) } // The session-log manifest: the complete history archived into seq-ranged // .jsonl.gz segments, each with a short-lived presigned download URL. Fetch // the URLs immediately; the JSON API never carries the bytes. getSessionLog(sessionId: string): Promise { - return this.request('GET', `/v1/sessions/${encodeURIComponent(sessionId)}/log`) + return this.request('GET', `/sessions/${encodeURIComponent(sessionId)}/log`) } syncAgentSession(req: SyncAgentSessionRequest): Promise { - return this.request('POST', '/v1/sessions/sync', req) + return this.request('POST', '/sessions/sync', req) } replayAgentSession(sessionId: string, req: ReplayAgentSessionRequest): Promise { return this.request( 'POST', - `/v1/sessions/${encodeURIComponent(sessionId)}/replay`, + `/sessions/${encodeURIComponent(sessionId)}/replay`, req, ) } stopAgentSession(sessionId: string): Promise { - return this.request('POST', `/v1/sessions/${encodeURIComponent(sessionId)}/stop`) + return this.request('POST', `/sessions/${encodeURIComponent(sessionId)}/stop`) } // Post a human message into a durable (keyed) session's conversation. The @@ -274,7 +274,7 @@ export class ApiClient { message: string, idempotencyKey?: string, ): Promise { - return this.request('POST', `/v1/sessions/${encodeURIComponent(sessionId)}/messages`, { + return this.request('POST', `/sessions/${encodeURIComponent(sessionId)}/messages`, { message, idempotency_key: idempotencyKey ?? null, } satisfies SendSessionMessageRequest) @@ -286,7 +286,7 @@ export class ApiClient { // no credential and is safe to share with any org member. 409 when the // sandbox isn't running (send the session a message to wake it first). getSessionIde(sessionId: string): Promise { - return this.request('GET', `/v1/sessions/${encodeURIComponent(sessionId)}/ide`) + return this.request('GET', `/sessions/${encodeURIComponent(sessionId)}/ide`) } // A preview port's link (a dev server running in the sandbox): the same @@ -294,7 +294,7 @@ export class ApiClient { getSessionPort(sessionId: string, port: number): Promise { return this.request( 'GET', - `/v1/sessions/${encodeURIComponent(sessionId)}/ports/${port}`, + `/sessions/${encodeURIComponent(sessionId)}/ports/${port}`, ) } @@ -304,7 +304,7 @@ export class ApiClient { // ellipsis repo). v1 is PNG-only with a 10 MiB cap, enforced server-side. uploadAsset(req: CreateAssetRequest): Promise { - return this.request('POST', '/v1/assets', req) + return this.request('POST', '/assets', req) } // Newest-first metadata for the credential's customer's assets. Metadata @@ -312,7 +312,7 @@ export class ApiClient { async listAssets(query?: ListAssetsQuery): Promise { const res = await this.request( 'GET', - '/v1/assets', + '/assets', undefined, query as Record | undefined, ) @@ -323,7 +323,7 @@ export class ApiClient { // the bytes locally, GET download_url immediately (it expires in ~60s; if it // lapses, just call this again for a fresh one). getAsset(assetId: string): Promise { - return this.request('GET', `/v1/assets/${encodeURIComponent(assetId)}`) + return this.request('GET', `/assets/${encodeURIComponent(assetId)}`) } // Delete an asset: it disappears from every read path and its gated link @@ -333,7 +333,7 @@ export class ApiClient { // unknown to the credential's customer, 403 when the token isn't allowed to // delete (e.g. a sandbox token). deleteAsset(assetId: string): Promise { - return this.request('DELETE', `/v1/assets/${encodeURIComponent(assetId)}`) + return this.request('DELETE', `/assets/${encodeURIComponent(assetId)}`) } // -------------------------------- reviews -------------------------------- @@ -343,14 +343,14 @@ export class ApiClient { // (scope, findings, posting outcome) need endpoints of their own. createReview(request: CreateReviewRequest): Promise { - return this.request('POST', '/v1/reviews', request) + return this.request('POST', '/reviews', request) } // The findings only exist once the review finalizes (they're collected from // the sandbox at teardown), so a running review returns findings: [] — hence // the stream-then-re-GET two-step the command uses. getReview(reviewId: string): Promise { - return this.request('GET', `/v1/reviews/${encodeURIComponent(reviewId)}`) + return this.request('GET', `/reviews/${encodeURIComponent(reviewId)}`) } // Newest first, findings omitted (counters only). Includes webhook-triggered @@ -358,7 +358,7 @@ export class ApiClient { async listReviews(query: ListReviewsQuery = {}): Promise { const res = await this.request( 'GET', - '/v1/reviews', + '/reviews', undefined, query, ) @@ -367,45 +367,45 @@ export class ApiClient { // --------------------------- review defaults ----------------------------- // The default code review pipeline ladder (repo default -> account default), - // the code_review twin of the /v1/defaults methods below and addressed the + // the code_review twin of the /defaults methods below and addressed the // same way: `repository` is "owner/name" for a repo rung and null/omitted - // for the account rung — never a row id. Read by POST /v1/reviews when no + // for the account rung — never a row id. Read by POST /reviews when no // config is named; webhook reviews are unaffected. Mutations are refused // for sandbox tokens (403). async listReviewDefaults(): Promise { const res = await this.request( 'GET', - '/v1/reviews/defaults', + '/reviews/defaults', ) return res.defaults } putReviewDefault(req: PutCodeReviewDefaultRequest): Promise { - return this.request('PUT', '/v1/reviews/defaults', req) + return this.request('PUT', '/reviews/defaults', req) } // Clears a rung: the account default when `repository` is omitted, that // repo's default otherwise. 404 when the rung isn't set. deleteReviewDefault(repository?: string): Promise { - return this.request('DELETE', '/v1/reviews/defaults', undefined, { repository }) + return this.request('DELETE', '/reviews/defaults', undefined, { repository }) } // ----------------------------- agent configs ---------------------------- async listAgentConfigs(): Promise { - const res = await this.request('GET', '/v1/configs') + const res = await this.request('GET', '/configs') return res.configs } // Opens a pull request that adds the config's YAML to the repo's agents/ // directory; the agent goes live once it merges and syncs. createAgentConfig(req: CreateAgentConfigRequest): Promise { - return this.request('POST', '/v1/configs', req) + return this.request('POST', '/configs', req) } getAgentConfig(configId: string): Promise { - return this.request('GET', `/v1/configs/${encodeURIComponent(configId)}`) + return this.request('GET', `/configs/${encodeURIComponent(configId)}`) } // ------------------------------ defaults -------------------------------- @@ -415,18 +415,18 @@ export class ApiClient { // refused for sandbox tokens (403). async listAgentDefaults(): Promise { - const res = await this.request('GET', '/v1/defaults') + const res = await this.request('GET', '/defaults') return res.defaults } putAgentDefault(req: PutAgentDefaultRequest): Promise { - return this.request('PUT', '/v1/defaults', req) + return this.request('PUT', '/defaults', req) } // Clears a rung: the account default when `repository` is omitted, that // repo's default otherwise. 404 when the rung isn't set. deleteAgentDefault(repository?: string): Promise { - return this.request('DELETE', '/v1/defaults', undefined, { repository }) + return this.request('DELETE', '/defaults', undefined, { repository }) } // ------------------------------- variables -------------------------------- @@ -434,14 +434,14 @@ export class ApiClient { // mutation), so callers can render the resulting state. async listSandboxVariables(): Promise { - const res = await this.request('GET', '/v1/variables') + const res = await this.request('GET', '/variables') return res.variables } async putSandboxVariables( variables: SandboxVariableInput[], ): Promise { - const res = await this.request('PUT', '/v1/variables', { + const res = await this.request('PUT', '/variables', { variables, }) return res.variables @@ -450,29 +450,29 @@ export class ApiClient { async deleteSandboxVariable(name: string): Promise { const res = await this.request( 'DELETE', - `/v1/variables/${encodeURIComponent(name)}`, + `/variables/${encodeURIComponent(name)}`, ) return res.variables } // ------------------------------- models --------------------------------- - // The models a customer may select for their agent (GET /v1/models) — the + // The models a customer may select for their agent (GET /models) — the // registry behind the dashboard's rate table, most expensive first. async listSupportedModels(): Promise { - const res = await this.request('GET', '/v1/models') + const res = await this.request('GET', '/models') return res.models } // ---------------------------- agent templates --------------------------- async listAgentTemplates(): Promise { - const res = await this.request('GET', '/v1/templates') + const res = await this.request('GET', '/templates') return res.templates } getAgentTemplate(slug: string): Promise { - return this.request('GET', `/v1/templates/${encodeURIComponent(slug)}`) + return this.request('GET', `/templates/${encodeURIComponent(slug)}`) } // ------------------------ integration discovery ------------------------- @@ -481,42 +481,42 @@ export class ApiClient { // (an Ellipsis account is a GitHub account) and Sentry returns an empty list. getIntegrations(): Promise { - return this.request('GET', '/v1/integrations') + return this.request('GET', '/integrations') } listGithubRepositories(): Promise { - return this.request('GET', '/v1/github/repos') + return this.request('GET', '/github/repos') } listGithubMembers(): Promise { - return this.request('GET', '/v1/github/members') + return this.request('GET', '/github/members') } listSlackChannels(): Promise { - return this.request('GET', '/v1/slack/channels') + return this.request('GET', '/slack/channels') } listSlackMembers(): Promise { - return this.request('GET', '/v1/slack/members') + return this.request('GET', '/slack/members') } listLinearTeams(): Promise { - return this.request('GET', '/v1/linear/teams') + return this.request('GET', '/linear/teams') } listSentryOrganizations(): Promise { - return this.request('GET', '/v1/sentry/organizations') + return this.request('GET', '/sentry/organizations') } // --------------------------- device-code auth --------------------------- // Unauthenticated: the CLI has no credential yet — that's what it's obtaining. startCliAuth(): Promise { - return this.request('POST', '/v1/cli-auth/start') + return this.request('POST', '/cli-auth/start') } pollCliAuth(deviceCode: string): Promise { - return this.request('POST', '/v1/cli-auth/poll', { device_code: deviceCode }) + return this.request('POST', '/cli-auth/poll', { device_code: deviceCode }) } } diff --git a/src/lib/config.ts b/src/lib/config.ts index 2abda0e..d6ebe32 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -26,7 +26,7 @@ function legacyConfigFile(): string { } // One Ellipsis instance the CLI can target (prod, beta, or a self-hosted -// deployment). `apiBase` is the /v1 host; `appBase` is the dashboard host used +// deployment). `apiBase` is the API host; `appBase` is the dashboard host used // 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 diff --git a/src/lib/laptop.ts b/src/lib/laptop.ts index 69edb2f..907a7dd 100644 --- a/src/lib/laptop.ts +++ b/src/lib/laptop.ts @@ -6,7 +6,7 @@ // with the session id and the live on-disk transcript path. The sync checks // per-repo enrollment (cwd → git remote → enrolled set; silent no-op // otherwise), redacts client-side (secrets never leave the laptop -// unredacted), gzips, and POSTs to /v1/sessions/sync. Network failures spool +// unredacted), gzips, and POSTs to /sessions/sync. Network failures spool // to disk and are flushed on the next successful sync. import { execFileSync } from 'node:child_process' @@ -279,7 +279,7 @@ export function spooledPendingCount(): number { // --------------------------------------------------------------------------- export type SyncOutcome = - | 'synced' // delivered to POST /v1/sessions/sync + | 'synced' // delivered to POST /sessions/sync | 'skipped_unenrolled' // consent gate: repo not enrolled (or no git remote) | 'not_logged_in' // no token anywhere | 'no_transcript' // transcript path missing/empty on disk @@ -292,7 +292,7 @@ export interface SyncLogEntry { cc_session_id?: string repo?: string reason?: string // stop | session_end - session_id?: string // v1 API session id (synced only) + session_id?: string // public API session id (synced only) event_count?: number // events the server acknowledged (synced only) error?: string // failure detail (non-synced outcomes) } @@ -307,7 +307,7 @@ export interface HookSyncStats { failed_24h: number // not_logged_in / no_transcript / spooled / rejected spooled_pending: number total_synced: number - recent_session_ids: string[] // distinct v1 session ids, most recent first + recent_session_ids: string[] // distinct public API session ids, most recent first } // The log is an audit trail for "did that background sync fail, and why", diff --git a/src/lib/sessions.ts b/src/lib/sessions.ts index aca0694..767b53b 100644 --- a/src/lib/sessions.ts +++ b/src/lib/sessions.ts @@ -234,7 +234,7 @@ export function attentionFlip(prevWord: string | undefined, nextWord: string): b export type ComposerModel = { id: string | null; label: string } -// The composer's model list when GET /v1/models is unavailable (an older +// The composer's model list when GET /models is unavailable (an older // server): the agent-selectable set as of this build, most expensive first. // `null` id = let the server pick (DEFAULT_AGENT_MODEL). Labels are the raw // model ids — the CLI speaks the API's vocabulary, not marketing names. diff --git a/src/lib/stream.ts b/src/lib/stream.ts index 2044913..cc3057d 100644 --- a/src/lib/stream.ts +++ b/src/lib/stream.ts @@ -21,10 +21,10 @@ export function resolveWsBase(apiBase?: string): string { return DEFAULT_WS_BASE } -// The bearer door's stream URL: /v1/sessions/{id}/stream plus the SDK's +// The bearer door's stream URL: /sessions/{id}/stream plus the SDK's // handshake query (`protocol=2`, `after_seq` when resuming). export function buildStreamUrl(wsBase: string, sessionId: string, query: string): string { - return `${wsBase}/v1/sessions/${encodeURIComponent(sessionId)}/stream?${query}` + return `${wsBase}/sessions/${encodeURIComponent(sessionId)}/stream?${query}` } // An OpenSocket over the `ws` package with bearer auth — what every CLI diff --git a/src/lib/types.ts b/src/lib/types.ts index 59f7208..da884d0 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,4 +1,4 @@ -// TypeScript types for the backend `/v1` request/response models. +// TypeScript types for the backend public API request/response models. // // The session-stream surface (records, inbox messages, turns, the enriched // session wire shape, and their request/response DTOs) comes from @@ -6,7 +6,8 @@ // re-exported below under the CLI's historical names. Everything else (the // endpoints outside the SDK's REST surface: session list/start, configs, // sandboxes, integrations, …) remains a hand-rolled mirror of the Pydantic -// models in ellipsis's v1_router until the SDK's OpenAPI surface widens. +// models in ellipsis's public API router (v1_router.py) until the SDK's +// OpenAPI surface widens. // Nested config/input/output payloads are typed loosely (the CLI only // displays summary fields). @@ -157,7 +158,7 @@ export interface AgentSession { cost_fee: number tokens_total: number metadata: Record - // Present on the POST /v1/sessions response only (StartAgentSessionResponse): + // Present on the POST /sessions response only (StartAgentSessionResponse): // which config the session runs under and which rung of the defaults ladder // chose it (null when an explicit config/template bypassed resolution). resolved_config_name?: string | null @@ -179,7 +180,7 @@ export interface SavedAgentConfig { [key: string]: unknown } -// Inline agent config payload accepted by POST /v1/sessions. Opaque to the +// Inline agent config payload accepted by POST /sessions. Opaque to the // CLI — passed straight through from a user-supplied JSON file. export type AgentConfig = Record @@ -235,7 +236,7 @@ export interface StartAgentSessionRequest { force_rebuild?: boolean } -// Replay payload for POST /v1/sessions/{id}/replay. Re-runs an existing +// Replay payload for POST /sessions/{id}/replay. Re-runs an existing // session's trigger input. Reuses the original session's frozen config // snapshot unless config_id is given. The override fields behave exactly as on // StartAgentSessionRequest (mapping or string, not both). `prompt` is omitted @@ -247,7 +248,7 @@ export interface ReplayAgentSessionRequest { prompt?: string } -// One hook-driven transcript sync from this laptop (POST /v1/sessions/sync). +// One hook-driven transcript sync from this laptop (POST /sessions/sync). // The transcript is redacted client-side, gzipped, then base64-encoded. export interface SyncAgentSessionRequest { cc_session_id: string @@ -279,7 +280,7 @@ export interface ListAgentConfigsResponse { configs: SavedAgentConfig[] } -// One rung of the default-config ladder (GET /v1/defaults). Rungs are +// One rung of the default-config ladder (GET /defaults). Rungs are // addressed by `repository`: "owner/name" for a repo default, null for the // account-wide default — never by row id. export interface AgentDefaultView { @@ -298,7 +299,7 @@ export interface ListAgentDefaultsResponse { defaults: AgentDefaultView[] } -// Body of PUT /v1/defaults: point a rung at a config. `repository` omitted +// Body of PUT /defaults: point a rung at a config. `repository` omitted // sets the account default; "owner/name" sets that repo's default. export interface PutAgentDefaultRequest { repository?: string @@ -306,9 +307,9 @@ export interface PutAgentDefaultRequest { } // One rung of the default code review pipeline ladder (GET -// /v1/reviews/defaults) — the code_review twin of AgentDefaultView, pointing +// /reviews/defaults) — the code_review twin of AgentDefaultView, pointing // at a synced `kind: code_review` pipeline (crcfg_…) instead of an agent -// config. Read by POST /v1/reviews when no config is named: repo default -> +// config. Read by POST /reviews when no config is named: repo default -> // account default -> the oldest synced pipeline -> the platform defaults. export interface CodeReviewDefaultView { id: string @@ -326,14 +327,14 @@ export interface ListCodeReviewDefaultsResponse { defaults: CodeReviewDefaultView[] } -// Body of PUT /v1/reviews/defaults: point a rung at a pipeline. `repository` +// Body of PUT /reviews/defaults: point a rung at a pipeline. `repository` // omitted sets the account default; "owner/name" sets that repo's default. export interface PutCodeReviewDefaultRequest { repository?: string config_id: string } -// Create-config payload for POST /v1/configs. Exactly one of `config` (inline) +// Create-config payload for POST /configs. Exactly one of `config` (inline) // or `template_id` (a gallery template slug). `repository` is a bare repo name // in the caller's account — the owner is always the account. export interface CreateAgentConfigRequest { @@ -354,7 +355,7 @@ export interface CreatedAgentConfig { pull_request_url: string } -// A built-in starter template served by GET /v1/templates. `yaml` is the +// A built-in starter template served by GET /templates. `yaml` is the // schema-valid agent config the CLI writes to disk; the rest is display copy. export interface AgentTemplate { slug: string @@ -412,14 +413,14 @@ export interface ListAgentSessionsQuery { start?: string end?: string limit?: number - // A GitHub account id (GET /v1/github/members); scopes the list to sessions + // A GitHub account id (GET /github/members); scopes the list to sessions // attributed to that developer. The CLI resolves it from a --author login. author_id?: number } // ----------------------------- session records --------------------------- -// One immutable archived segment of the session log (GET /v1/sessions/{id}/log): +// One immutable archived segment of the session log (GET /sessions/{id}/log): // its feed_seq range plus a short-lived presigned S3 GET. Segments are gzip // members — download them in order and concatenate for the whole log. export interface SessionLogSegment { @@ -449,14 +450,14 @@ export interface GetSessionLogResponse { segments: SessionLogSegment[] } -// GET /v1/sessions/{id}/ide (`agent session ide`): the live sandbox's +// GET /sessions/{id}/ide (`agent session ide`): the live sandbox's // code-server tunnel URL. Unguessable, customer-scoped at discovery, and dead // once the sandbox is torn down — fetch it fresh on every open, never store it. export interface GetSessionIdeResponse { url: string } -// GET /v1/sessions/{id}/ports/{port} (`agent session port`): the tunnel URL +// GET /sessions/{id}/ports/{port} (`agent session port`): the tunnel URL // for one of the sandbox's preview ports (a dev server the agent or the IDE // user started). Same lifetime/gating as the IDE URL. export interface GetSessionPortResponse { @@ -522,7 +523,7 @@ export interface SearchSessionsResponse { } // -------------------------- integration discovery ------------------------ -// Read-only views of what's connected for the account (GET /v1/integrations +// Read-only views of what's connected for the account (GET /integrations // and the per-provider listings). Responses never include secrets. export interface GithubIntegrationSummary { @@ -572,7 +573,7 @@ export interface GetIntegrationsResponse { } // A repository connected to the installation: a valid `repository` for -// POST /v1/configs and for repository lists in an agent config. +// POST /configs and for repository lists in an agent config. export interface RepositorySummary { id: number name: string @@ -674,7 +675,7 @@ export interface PutSandboxVariablesRequest { } // ------------------------------- analytics ------------------------------- -// Mirrors of the /v1/analytics/* responses (analytics_service.py) — the same +// Mirrors of the /analytics/* responses (analytics_service.py) — the same // aggregation behind the app's /analytics dashboard, token-authed. The CLI // renders the leaderboards and totals; feed items and day buckets it only // passes through to --json are typed loosely. diff --git a/src/lib/urls.ts b/src/lib/urls.ts index 3aed24c..d894a51 100644 --- a/src/lib/urls.ts +++ b/src/lib/urls.ts @@ -1,6 +1,6 @@ // Builders for clickable dashboard (web app) links. Pure string functions so // they're unit-testable; callers pass the resolved app base (resolveAppBase) -// and the customer's account login (from GET /v1/me — the routes are scoped by +// and the customer's account login (from GET /me — the routes are scoped by // login). Mirrors the backend's link format in github_brand.py. // Sessions open on the account page with the session picked out by query @@ -15,7 +15,7 @@ export function configUrl(appBase: string, accountLogin: string, configId: strin } // The device-code approval page for `agent login`. `userCode` is the user_code -// minted by POST /v1/cli-auth/start. Built client-side from the active host's +// minted by POST /cli-auth/start. Built client-side from the active host's // app base (not the server's verification_uri_complete) so the host always // matches the instance the CLI is pointed at: the backend fills its own copy // from an env var that defaults to prod, so a beta / self-hosted login would diff --git a/src/ui/SessionsApp.tsx b/src/ui/SessionsApp.tsx index 669aaf4..59391f4 100644 --- a/src/ui/SessionsApp.tsx +++ b/src/ui/SessionsApp.tsx @@ -294,7 +294,7 @@ export function SessionsApp(props: SessionsAppProps): React.ReactElement { // The composer's picker options, fetched once when the new-session pane // first opens: the dashboard composer's three choices — saved agent // configs, the account's repositories, and the selectable models. A models - // failure (an older server without GET /v1/models) leaves the list empty + // failure (an older server without GET /models) leaves the list empty // and the composer falls back to its built-in set. const [configs, setConfigs] = useState(null) const [repos, setRepos] = useState(null) @@ -833,7 +833,7 @@ function NewSessionPane({ ], [configs], ) - // The server's selectable set (GET /v1/models); before it lands — and on an + // The server's selectable set (GET /models); before it lands — and on an // older server that has no such route — the built-in fallback list. const modelOptions = useMemo(() => composerModelOptions(models ?? []), [models]) // When the cwd names a repo there is no "Default" row: the detected repo diff --git a/src/ui/launch.tsx b/src/ui/launch.tsx index ba347d2..a579d92 100644 --- a/src/ui/launch.tsx +++ b/src/ui/launch.tsx @@ -18,7 +18,7 @@ export interface SessionsUiOptions { // The start response's resolved config name + a caveat for that chat. initialConfigName?: string initialNotice?: string - // Builds the POST /v1/sessions request for a composer-spawned session; the + // Builds the POST /sessions request for a composer-spawned session; the // typed text rides as the prompt. Entry points bake their flags in here. buildStartRequest: (prompt: string) => StartAgentSessionRequest } diff --git a/test/api.test.ts b/test/api.test.ts index da0e8d4..9f63806 100644 --- a/test/api.test.ts +++ b/test/api.test.ts @@ -82,11 +82,11 @@ describe('ApiClient.request', () => { vi.stubGlobal('fetch', fetchMock) const api = new ApiClient('http://api.test', 'tok_123') - const out = await api.request<{ ok: boolean }>('GET', '/v1/me') + const out = await api.request<{ ok: boolean }>('GET', '/me') expect(out).toEqual({ ok: true }) const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/me') + expect(url).toBe('http://api.test/me') expect((init as RequestInit).headers).toMatchObject({ authorization: 'Bearer tok_123' }) }) @@ -97,7 +97,7 @@ describe('ApiClient.request', () => { vi.stubGlobal('fetch', fetchMock) await new ApiClient('http://api.test', 't').listAgentSessions({ limit: 5, source: ['cli'] }) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/sessions?limit=5&source=cli') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/sessions?limit=5&source=cli') }) it('throws ApiError carrying status + server detail on non-2xx', async () => { @@ -136,7 +136,7 @@ describe('ApiClient.request', () => { it('tolerates empty response bodies', async () => { vi.stubGlobal('fetch', vi.fn(async () => new Response(null, { status: 204 }))) const api = new ApiClient('http://api.test', 't') - await expect(api.request('DELETE', '/v1/whatever')).resolves.toBeUndefined() + await expect(api.request('DELETE', '/whatever')).resolves.toBeUndefined() }) it('exposes ApiError as an Error subclass', () => { @@ -160,7 +160,7 @@ describe('ApiClient sandbox variables', () => { const out = await new ApiClient('http://api.test', 't').listSandboxVariables() expect(out).toEqual([{ name: 'A', created_at: '', updated_at: '' }]) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/variables') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/variables') expect((fetchMock.mock.calls[0][1] as RequestInit).method).toBe('GET') }) @@ -172,7 +172,7 @@ describe('ApiClient sandbox variables', () => { await new ApiClient('http://api.test', 't').putSandboxVariables([{ name: 'TOKEN', value: 'x' }]) const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/variables') + expect(url).toBe('http://api.test/variables') expect((init as RequestInit).method).toBe('PUT') expect(JSON.parse((init as RequestInit).body as string)).toEqual({ variables: [{ name: 'TOKEN', value: 'x' }], @@ -187,7 +187,7 @@ describe('ApiClient sandbox variables', () => { await new ApiClient('http://api.test', 't').deleteSandboxVariable('MY/VAR') const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/variables/MY%2FVAR') + expect(url).toBe('http://api.test/variables/MY%2FVAR') expect((init as RequestInit).method).toBe('DELETE') }) }) @@ -212,7 +212,7 @@ describe('ApiClient review defaults', () => { const out = await new ApiClient('http://api.test', 't').listReviewDefaults() expect(out).toEqual([rung]) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/reviews/defaults') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/reviews/defaults') expect((fetchMock.mock.calls[0][1] as RequestInit).method).toBe('GET') }) @@ -222,7 +222,7 @@ describe('ApiClient review defaults', () => { await new ApiClient('http://api.test', 't').putReviewDefault({ config_id: 'crcfg_1' }) let [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/reviews/defaults') + expect(url).toBe('http://api.test/reviews/defaults') expect((init as RequestInit).method).toBe('PUT') expect(JSON.parse((init as RequestInit).body as string)).toEqual({ config_id: 'crcfg_1' }) @@ -243,12 +243,12 @@ describe('ApiClient review defaults', () => { const api = new ApiClient('http://api.test', 't') await api.deleteReviewDefault() - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/reviews/defaults') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/reviews/defaults') expect((fetchMock.mock.calls[0][1] as RequestInit).method).toBe('DELETE') await api.deleteReviewDefault('acme/api') expect(fetchMock.mock.calls[1][0]).toBe( - 'http://api.test/v1/reviews/defaults?repository=acme%2Fapi', + 'http://api.test/reviews/defaults?repository=acme%2Fapi', ) }) }) @@ -271,7 +271,7 @@ describe('getSessionLog', () => { const out = await new ApiClient('http://api.test', 't').getSessionLog('session_1') expect(out).toEqual(body) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/sessions/session_1/log') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/sessions/session_1/log') }) }) @@ -289,7 +289,7 @@ describe('replayAgentSession', () => { }) expect(out.id).toBe('session_2') const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/sessions/session%2F1/replay') + expect(url).toBe('http://api.test/sessions/session%2F1/replay') expect((init as RequestInit).method).toBe('POST') expect(JSON.parse((init as RequestInit).body as string)).toEqual({ config_override: { claude: { model: 'claude-opus-4-8' } }, @@ -309,7 +309,7 @@ describe('stopAgentSession', () => { const out = await new ApiClient('http://api.test', 't').stopAgentSession('session/1') expect(out).toEqual({ id: 'session_1', status: 'stopped' }) const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/sessions/session%2F1/stop') + expect(url).toBe('http://api.test/sessions/session%2F1/stop') expect((init as RequestInit).method).toBe('POST') }) }) @@ -328,7 +328,7 @@ describe('agent templates', () => { const out = await new ApiClient('http://api.test', 't').listAgentTemplates() expect(out.map((t) => t.slug)).toEqual(['a', 'b']) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/templates') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/templates') }) it('fetches a single template by slug (encoded)', async () => { @@ -340,7 +340,7 @@ describe('agent templates', () => { const out = await new ApiClient('http://api.test', 't').getAgentTemplate('ci-failure-triager') expect(out.yaml).toBe('x') - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/templates/ci-failure-triager') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/templates/ci-failure-triager') }) }) @@ -363,7 +363,7 @@ describe('supported models', () => { const out = await new ApiClient('http://api.test', 't').listSupportedModels() expect(out.map((m) => m.id)).toEqual(['claude-opus-5']) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/models') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/models') }) }) @@ -390,7 +390,7 @@ describe('createAgentConfig', () => { }) expect(out.pull_request_url).toBe('https://github.com/octocat/api/pull/7') const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/configs') + expect(url).toBe('http://api.test/configs') expect((init as RequestInit).method).toBe('POST') expect(JSON.parse((init as RequestInit).body as string)).toEqual({ template_id: 'ci-failure-triager', @@ -422,7 +422,7 @@ describe('ApiClient assets', () => { }) expect(out.url).toBe('https://app.ellipsis.dev/assets/a1') const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/assets') + expect(url).toBe('http://api.test/assets') expect((init as RequestInit).method).toBe('POST') expect(JSON.parse((init as RequestInit).body as string)).toEqual({ filename: 'shot.png', @@ -443,7 +443,7 @@ describe('ApiClient assets', () => { }) expect(out).toEqual([{ id: 'a1' }]) expect(fetchMock.mock.calls[0][0]).toBe( - 'http://api.test/v1/assets?agent_session_id=session_1&limit=5', + 'http://api.test/assets?agent_session_id=session_1&limit=5', ) }) @@ -459,7 +459,7 @@ describe('ApiClient assets', () => { const out = await new ApiClient('http://api.test', 't').getAsset('a/1') expect(out.download_url).toBe('d') - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/assets/a%2F1') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/assets/a%2F1') }) it('DELETEs the asset id (encoded) and tolerates a 204 empty body', async () => { @@ -469,7 +469,7 @@ describe('ApiClient assets', () => { const out = await new ApiClient('http://api.test', 't').deleteAsset('a/1') expect(out).toBeUndefined() const [url, init] = fetchMock.mock.calls[0] - expect(url).toBe('http://api.test/v1/assets/a%2F1') + expect(url).toBe('http://api.test/assets/a%2F1') expect((init as RequestInit).method).toBe('DELETE') }) }) @@ -486,7 +486,7 @@ describe('ApiClient session IDE and ports', () => { const out = await new ApiClient('http://api.test', 't').getSessionIde('session_1') expect(out.url).toBe('https://ide.modal.host') - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/sessions/session_1/ide') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/sessions/session_1/ide') expect((fetchMock.mock.calls[0][1] as RequestInit).method).toBe('GET') }) @@ -502,7 +502,7 @@ describe('ApiClient session IDE and ports', () => { const out = await new ApiClient('http://api.test', 't').getSessionPort('session_1', 3000) expect(out).toEqual({ url: 'https://p3000.modal.host', port: 3000 }) expect(fetchMock.mock.calls[0][0]).toBe( - 'http://api.test/v1/sessions/session_1/ports/3000', + 'http://api.test/sessions/session_1/ports/3000', ) }) }) diff --git a/test/discovery.test.ts b/test/discovery.test.ts index 66c208d..94428df 100644 --- a/test/discovery.test.ts +++ b/test/discovery.test.ts @@ -14,13 +14,13 @@ describe('integration discovery endpoints', () => { it('hits the provider-namespaced paths', async () => { const cases: Array<[(api: ApiClient) => Promise, string, unknown]> = [ - [(api) => api.getIntegrations(), '/v1/integrations', { sentry: [] }], - [(api) => api.listGithubRepositories(), '/v1/github/repos', { repositories: [] }], - [(api) => api.listGithubMembers(), '/v1/github/members', { members: [] }], - [(api) => api.listSlackChannels(), '/v1/slack/channels', { channels: [] }], - [(api) => api.listSlackMembers(), '/v1/slack/members', { members: [] }], - [(api) => api.listLinearTeams(), '/v1/linear/teams', { teams: [] }], - [(api) => api.listSentryOrganizations(), '/v1/sentry/organizations', { organizations: [] }], + [(api) => api.getIntegrations(), '/integrations', { sentry: [] }], + [(api) => api.listGithubRepositories(), '/github/repos', { repositories: [] }], + [(api) => api.listGithubMembers(), '/github/members', { members: [] }], + [(api) => api.listSlackChannels(), '/slack/channels', { channels: [] }], + [(api) => api.listSlackMembers(), '/slack/members', { members: [] }], + [(api) => api.listLinearTeams(), '/linear/teams', { teams: [] }], + [(api) => api.listSentryOrganizations(), '/sentry/organizations', { organizations: [] }], ] for (const [call, path, body] of cases) { const fetchMock = stub(body) diff --git a/test/output.test.ts b/test/output.test.ts index f07b234..aedacae 100644 --- a/test/output.test.ts +++ b/test/output.test.ts @@ -58,7 +58,7 @@ describe('friendlyErrorMessage', () => { }) it('maps a 401 to a re-login hint instead of the raw HTTP failure', () => { - const err = new ApiError(401, 'GET', '/v1/me', 'Unauthorized', 'req_1') + const err = new ApiError(401, 'GET', '/me', 'Unauthorized', 'req_1') expect(friendlyErrorMessage(err)).toBe( 'Your login is invalid or has expired. Run `agent login` to re-authenticate.', ) @@ -66,7 +66,7 @@ describe('friendlyErrorMessage', () => { it('blames ELLIPSIS_API_TOKEN when the rejected credential came from the env', () => { process.env.ELLIPSIS_API_TOKEN = 'stale_tok' - const err = new ApiError(401, 'GET', '/v1/me', 'Unauthorized') + const err = new ApiError(401, 'GET', '/me', 'Unauthorized') expect(friendlyErrorMessage(err)).toMatch(/ELLIPSIS_API_TOKEN/) }) @@ -74,7 +74,7 @@ describe('friendlyErrorMessage', () => { const err = new ApiError( 429, 'POST', - '/v1/assets', + '/assets', 'Asset limit reached: your organization is storing 50 of 50 assets. ' + 'Delete assets you no longer need, or email team@ellipsis.dev to raise the limit.', ) @@ -85,9 +85,9 @@ describe('friendlyErrorMessage', () => { }) it('passes other ApiErrors through with the server detail intact', () => { - const err = new ApiError(409, 'POST', '/v1/sessions/s_1/messages', 'Session is closed') + const err = new ApiError(409, 'POST', '/sessions/s_1/messages', 'Session is closed') expect(friendlyErrorMessage(err)).toBe( - 'POST /v1/sessions/s_1/messages failed: 409 Session is closed', + 'POST /sessions/s_1/messages failed: 409 Session is closed', ) }) diff --git a/test/search.test.ts b/test/search.test.ts index 59648d9..d5843d5 100644 --- a/test/search.test.ts +++ b/test/search.test.ts @@ -35,7 +35,7 @@ function session(overrides: Partial = {}): AgentSession { describe('searchSessions', () => { afterEach(() => vi.unstubAllGlobals()) - it('GETs /v1/sessions/search with repeated facet keys', async () => { + it('GETs /sessions/search with repeated facet keys', async () => { const fetchMock = vi.fn( async () => new Response(JSON.stringify({ results: [], attributed_users: {} }), { status: 200 }), @@ -51,7 +51,7 @@ describe('searchSessions', () => { limit: 20, }) const url = fetchMock.mock.calls[0][0] as string - expect(url).toContain('http://api.test/v1/sessions/search?') + expect(url).toContain('http://api.test/sessions/search?') expect(url).toContain('q=shift+trade+webhook') expect(url).toContain('scope=both') expect(url).toContain('author_id=5201153') @@ -72,7 +72,7 @@ describe('getAgentSessionRecords', () => { const out = await new ApiClient('http://api.test', 't').getAgentSessionRecords('session/1') expect(out.map((s) => s.id)).toEqual(['rec_1']) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/sessions/session%2F1/records') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/sessions/session%2F1/records') }) }) diff --git a/test/stream.test.ts b/test/stream.test.ts index 8b132dc..deab8a1 100644 --- a/test/stream.test.ts +++ b/test/stream.test.ts @@ -22,10 +22,10 @@ describe('resolveWsBase', () => { describe('buildStreamUrl', () => { it('builds the bearer-door URL with the SDK handshake query', () => { expect(buildStreamUrl('wss://h', 'session 1', 'protocol=2')).toBe( - 'wss://h/v1/sessions/session%201/stream?protocol=2', + 'wss://h/sessions/session%201/stream?protocol=2', ) expect(buildStreamUrl('wss://h', 's', 'protocol=2&after_seq=7')).toBe( - 'wss://h/v1/sessions/s/stream?protocol=2&after_seq=7', + 'wss://h/sessions/s/stream?protocol=2&after_seq=7', ) }) })