From 61f08992c8f32e5d0b68684d5b7dae08b4f38b3a Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 8 Nov 2026 05:06:47 -0600 Subject: [PATCH] fix(api): tell agents how a person connects their terminal The CLI auth operations in the agent API catalog had bare summaries, so a chat agent asked to connect a person's terminal could find the endpoints but not who starts the flow, and spent its whole turn reading source code to find the command. Describe the person-run paperclipai auth login flow, the browser approval, whoami/logout, and that agents must not create or approve challenges, hand out agent keys or ask for passwords. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit a042ee497dc3b32f4c6d65366a7d428f18023b8b) --- .../cli-auth-terminal-guidance.test.ts | 26 +++++++++++++++++++ server/src/routes/openapi.ts | 6 ++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 server/src/__tests__/cli-auth-terminal-guidance.test.ts diff --git a/server/src/__tests__/cli-auth-terminal-guidance.test.ts b/server/src/__tests__/cli-auth-terminal-guidance.test.ts new file mode 100644 index 00000000000..f9963bd4210 --- /dev/null +++ b/server/src/__tests__/cli-auth-terminal-guidance.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { searchRunnerApi } from "../services/native-runtime/runner-api-catalog.js"; + +// A chat agent asked to "connect my terminal to Paperclip as me" searched the +// API catalog, found the CLI auth operations without any statement of who starts +// the flow, and spent its whole turn reading source code to find the command. +describe("CLI terminal login guidance in the agent API catalog", () => { + it.each([ + "connect my terminal to Paperclip as me", + "terminal connect CLI login pair device link personal access", + "cli-auth login code approve session token terminal", + ])("search %j finds the person-run CLI login command", query => { + const { results } = searchRunnerApi({ query, limit: 5 }); + const challenge = results.find(operation => operation.operationId === "POST /api/cli-auth/challenges"); + expect(challenge, query).toBeDefined(); + expect(challenge!.description).toContain("`npx paperclipai auth login --api-base `"); + expect(challenge!.description).toContain("never by an agent on their behalf"); + expect(challenge!.description).toMatch(/never hand out an agent key, create or approve a challenge yourself, or ask for the person's password/); + }); + + it("tells agents that only the signed-in person can approve", () => { + const { results } = searchRunnerApi({ query: "POST /api/cli-auth/challenges/{id}/approve" }); + expect(results[0]?.description).toContain("agent API keys receive 401"); + expect(results[0]?.description).toContain("`npx paperclipai auth login`"); + }); +}); diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 84189c0d6db..2adc7420fbd 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -7401,6 +7401,7 @@ registry.registerPath({ path: "/api/cli-auth/me", tags: ["access"], summary: "Get current CLI auth session", + description: "Board-credential read used by `npx paperclipai auth whoami` in the person's own terminal. Agent API keys receive 401: an agent cannot sign a terminal in as a person. To connect someone's terminal, see POST /api/cli-auth/challenges.", responses: { 200: r.ok(), 401: r.unauthorized }, }); @@ -7420,7 +7421,8 @@ registry.registerPath({ method: "post", path: "/api/cli-auth/challenges", tags: ["access"], - summary: "Create a CLI auth challenge", + summary: "Connect a person's terminal to Paperclip: create a CLI auth challenge for terminal login", + description: "Called by the Paperclip CLI in a person's own terminal, never by an agent on their behalf. To connect someone's terminal to Paperclip as themselves, tell them to run `npx paperclipai auth login --api-base ` in that terminal (add `--company-id ` to name the company). The CLI prints an approval link; they open it in a browser where they are signed in, review the requesting command, company and Board access, and approve. Only that CLI receives the resulting Board credential. `npx paperclipai auth whoami` confirms who it is signed in as and `npx paperclipai auth logout` revokes it. Agent API keys are a separate identity: never hand out an agent key, create or approve a challenge yourself, or ask for the person's password in chat.", request: { body: jsonBody(createCliAuthChallengeSchema) }, responses: { 200: r.ok(), 400: r.badRequest }, }); @@ -7430,6 +7432,7 @@ registry.registerPath({ path: "/api/cli-auth/challenges/{id}/approve", tags: ["access"], summary: "Approve a CLI auth challenge", + description: "Only the signed-in person approves, from the approval page their own CLI printed; agent API keys receive 401. To connect someone's terminal, give them the `npx paperclipai auth login` command (see POST /api/cli-auth/challenges).", request: { params: z.object({ id: cliAuthChallengeIdParamSchema }), body: jsonBody(resolveCliAuthChallengeSchema), @@ -7459,6 +7462,7 @@ registry.registerPath({ path: "/api/cli-auth/revoke-current", tags: ["access"], summary: "Revoke current CLI auth session", + description: "Used by `npx paperclipai auth logout` with the CLI's own Board credential; it revokes only that credential and leaves agent API keys untouched.", responses: { 200: r.ok(), 401: r.unauthorized }, });