diff --git a/packages/coding-agent/src/cli/auth-continue.ts b/packages/coding-agent/src/cli/auth-continue.ts index 8147f5f8b..432fc0023 100644 --- a/packages/coding-agent/src/cli/auth-continue.ts +++ b/packages/coding-agent/src/cli/auth-continue.ts @@ -85,11 +85,17 @@ function optionalRegistration(value: unknown, field: string): "dcr" | "cimd" | u return value; } +/** + * Accepts the payload alone or the whole command it came in, such as + * `PI_RADIUS_GATEWAY='…' pi auth --continue ''`: base64url has no spaces, so the payload is + * the last word, without the quotes a shell would remove. Raw JSON is passed through unchanged. + */ function normalizeContinuationInput(inputValue: string): string { const trimmed = inputValue.trim(); if (!trimmed) throw new AuthContinueError("Continuation payload is empty"); - const commandMatch = /^\s*pi\s+auth\s+--continue\s+(.+)$/su.exec(trimmed); - return commandMatch ? commandMatch[1].trim() : trimmed; + if (trimmed.startsWith("{")) return trimmed; + const lastWord = trimmed.split(/\s+/u).at(-1)!; + return /^(['"])(.*)\1$/su.exec(lastWord)?.[2] ?? lastWord; } function decodeBase64Json(inputValue: string): unknown { diff --git a/packages/coding-agent/test/credential-print.test.ts b/packages/coding-agent/test/credential-print.test.ts index 47cc12ee0..cc52d5995 100644 --- a/packages/coding-agent/test/credential-print.test.ts +++ b/packages/coding-agent/test/credential-print.test.ts @@ -170,6 +170,31 @@ describe("credential print commands", () => { expect(decodeAuthContinuationPayload(`pi auth --continue ${encoded}`)).toMatchObject(payload); }); + test("accepts the payload pasted in any command form", () => { + const payload = { + version: 1, + serviceUrl: "https://auth.example", + providerId: "auth-service", + continuationId: "cont_123", + secret: "one-time-secret", + returnUrl: "https://auth.example/connections", + mcp: { serverUrl: "https://mcp.example/mcp", serverName: "example", authType: "none" }, + }; + const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url"); + for (const input of [ + encoded, + ` ${encoded}\n`, + `'${encoded}'`, + `"${encoded}"`, + `pi auth --continue '${encoded}'`, + `pi auth continue ${encoded}`, + `PI_RADIUS_GATEWAY='http://localhost:8787' pi auth --continue '${encoded}'`, + JSON.stringify(payload, null, 2), + ]) { + expect(decodeAuthContinuationPayload(input)).toMatchObject(payload); + } + }); + test("accepts a *.localhost returnUrl for a localhost serviceUrl", () => { const payload = { version: 1,