@agentteams/cli 0.1.94 → 0.1.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.eslintcache +1 -1
  2. package/README.md +61 -1
  3. package/dist/api/linear.d.ts +4 -4
  4. package/dist/api/linear.d.ts.map +1 -1
  5. package/dist/api/linear.js +26 -21
  6. package/dist/api/linear.js.map +1 -1
  7. package/dist/auth/deviceAuthClient.d.ts +63 -0
  8. package/dist/auth/deviceAuthClient.d.ts.map +1 -0
  9. package/dist/auth/deviceAuthClient.js +131 -0
  10. package/dist/auth/deviceAuthClient.js.map +1 -0
  11. package/dist/auth/personalTokenClient.d.ts +44 -0
  12. package/dist/auth/personalTokenClient.d.ts.map +1 -1
  13. package/dist/auth/personalTokenClient.js +116 -0
  14. package/dist/auth/personalTokenClient.js.map +1 -1
  15. package/dist/commands/auth.d.ts +69 -1
  16. package/dist/commands/auth.d.ts.map +1 -1
  17. package/dist/commands/auth.js +127 -4
  18. package/dist/commands/auth.js.map +1 -1
  19. package/dist/commands/index.js +1 -1
  20. package/dist/commands/index.js.map +1 -1
  21. package/dist/commands/init.d.ts +9 -0
  22. package/dist/commands/init.d.ts.map +1 -1
  23. package/dist/commands/init.js +80 -2
  24. package/dist/commands/init.js.map +1 -1
  25. package/dist/commands/linear.d.ts +8 -1
  26. package/dist/commands/linear.d.ts.map +1 -1
  27. package/dist/commands/linear.js +13 -6
  28. package/dist/commands/linear.js.map +1 -1
  29. package/dist/index.js +26 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/catalog.d.ts +13 -0
  32. package/dist/mcp/catalog.d.ts.map +1 -1
  33. package/dist/mcp/catalog.js +35 -1
  34. package/dist/mcp/catalog.js.map +1 -1
  35. package/dist/mcp/writeTools.d.ts.map +1 -1
  36. package/dist/mcp/writeTools.js +77 -40
  37. package/dist/mcp/writeTools.js.map +1 -1
  38. package/dist/mcp-registration/clients.d.ts.map +1 -1
  39. package/dist/mcp-registration/clients.js +75 -2
  40. package/dist/mcp-registration/clients.js.map +1 -1
  41. package/dist/mcp-registration/index.d.ts +3 -1
  42. package/dist/mcp-registration/index.d.ts.map +1 -1
  43. package/dist/mcp-registration/index.js +22 -6
  44. package/dist/mcp-registration/index.js.map +1 -1
  45. package/dist/mcp-registration/install.d.ts +10 -1
  46. package/dist/mcp-registration/install.d.ts.map +1 -1
  47. package/dist/mcp-registration/install.js +18 -1
  48. package/dist/mcp-registration/install.js.map +1 -1
  49. package/dist/mcp-registration/toolProfileSupport.d.ts +25 -0
  50. package/dist/mcp-registration/toolProfileSupport.d.ts.map +1 -0
  51. package/dist/mcp-registration/toolProfileSupport.js +53 -0
  52. package/dist/mcp-registration/toolProfileSupport.js.map +1 -0
  53. package/dist/mcp-registration/types.d.ts +25 -1
  54. package/dist/mcp-registration/types.d.ts.map +1 -1
  55. package/dist/mcp-registration/types.js +1 -0
  56. package/dist/mcp-registration/types.js.map +1 -1
  57. package/dist/utils/agentEntryPoints.d.ts +1 -1
  58. package/dist/utils/agentEntryPoints.d.ts.map +1 -1
  59. package/dist/utils/agentEntryPoints.js +12 -3
  60. package/dist/utils/agentEntryPoints.js.map +1 -1
  61. package/dist/utils/authFormat.d.ts.map +1 -1
  62. package/dist/utils/authFormat.js +15 -0
  63. package/dist/utils/authFormat.js.map +1 -1
  64. package/dist/utils/config.d.ts +18 -0
  65. package/dist/utils/config.d.ts.map +1 -1
  66. package/dist/utils/config.js +56 -0
  67. package/dist/utils/config.js.map +1 -1
  68. package/package.json +2 -2
package/README.md CHANGED
@@ -28,7 +28,67 @@ The `init` command:
28
28
  - Saves the convention template to `.agentteams/convention.md`
29
29
  - Syncs convention files into `.agentteams/<category>/*.md`
30
30
 
31
- In SSH/remote environments, open the URL printed in the terminal manually.
31
+ ### Choosing a login method
32
+
33
+ The browser callback is always the default. Nothing is auto-detected — you pick a
34
+ method by what you type.
35
+
36
+ | Situation | What to run | How it authenticates |
37
+ | ------------------------------------------------------------ | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
38
+ | Local machine with a browser (default) | `agentteams init` / `agentteams auth login` | The browser calls back to a temporary port on **this** machine |
39
+ | Remote or headless shell (SSH, container, WSL, no `DISPLAY`) | `agentteams init --device-auth` / `agentteams auth login --device-auth` | A short code you approve in a browser on **any other** device — no local port |
40
+ | CI and unattended automation | `AGENTTEAMS_API_KEY`, or a project service token | No human approval step at all |
41
+
42
+ > The default path opens a local port and waits for the browser to call back. Over
43
+ > SSH the browser runs on your laptop, so the callback reaches your laptop's
44
+ > `localhost` and never the remote shell — that is the case `--device-auth` exists
45
+ > for. Device authorization is **not** a CI mechanism: it always requires a person
46
+ > to approve the request.
47
+
48
+ #### `--device-auth`
49
+
50
+ ```bash
51
+ # Log in only
52
+ agentteams auth login --device-auth
53
+
54
+ # Initialize a project (team/project/agent are chosen on the approval screen)
55
+ agentteams init --device-auth
56
+ ```
57
+
58
+ The terminal prints a verification URL and an 8-character code. Open the URL on any
59
+ device, sign in, check that the code on screen matches the one in your terminal, and
60
+ approve. The CLI stores the login and continues.
61
+
62
+ To avoid typing the flag on every command on a remote machine, declare it once for
63
+ that machine:
64
+
65
+ ```bash
66
+ # Persist it in ~/.agentteams/config.json (never in a project config)
67
+ agentteams auth login --device-auth --set-default
68
+
69
+ # Or per shell / per service unit
70
+ export AGENTTEAMS_DEVICE_AUTH=1
71
+ ```
72
+
73
+ `agentteams auth status` reports whether this machine defaults to device
74
+ authorization. To turn it back off, remove `"deviceAuth"` from
75
+ `~/.agentteams/config.json` and unset `AGENTTEAMS_DEVICE_AUTH`.
76
+
77
+ Before starting, the CLI checks that this machine has a usable OS credential store
78
+ (macOS Keychain, Windows Credential Manager, or libsecret on Linux). On a minimal
79
+ Linux server install `libsecret` and unlock a keyring, e.g.
80
+ `sudo apt install libsecret-tools` — otherwise use `AGENTTEAMS_API_KEY` instead.
81
+
82
+ #### Troubleshooting
83
+
84
+ | Message | Cause | What to do |
85
+ | ------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
86
+ | `OAuth callback timed out after 60 seconds.` | The default flow was used from a shell the browser cannot call back to | Re-run with `--device-auth` |
87
+ | `does not support device authorization` | The server is an older AgentTeams API | Re-run **without** `--device-auth`, or upgrade the server |
88
+ | `device authorization turned off because the server has no APP_URL` | Server configuration | Ask the operator to set `APP_URL`; use the default flow meanwhile |
89
+ | `The device code expired before it was approved` | The code is valid for 15 minutes | Run the command again for a new code |
90
+ | `The sign-in request was denied in the browser` | Deny was clicked | Run the command again if that was a mistake |
91
+ | `Cannot start device authorization: ... credential store` | No usable OS credential store | Install/unlock libsecret, or use `AGENTTEAMS_API_KEY` |
32
92
 
33
93
  ### Service URLs (Defaults and Overrides)
34
94
 
@@ -1,6 +1,6 @@
1
1
  export declare function getLinearIssue(apiUrl: string, headers: any, issueId: string, projectId?: string): Promise<any>;
2
- export declare function createLinearIssue(apiUrl: string, headers: any, title: string, description?: string, state?: string, teamId?: string, parentId?: string): Promise<any>;
3
- export declare function updateLinearIssue(apiUrl: string, headers: any, issueId: string, state: string): Promise<any>;
4
- export declare function listLinearComments(apiUrl: string, headers: any, issueId: string): Promise<any>;
5
- export declare function createLinearComment(apiUrl: string, headers: any, issueId: string, body: string): Promise<any>;
2
+ export declare function createLinearIssue(apiUrl: string, headers: any, title: string, description?: string, state?: string, teamId?: string, parentId?: string, projectId?: string): Promise<any>;
3
+ export declare function updateLinearIssue(apiUrl: string, headers: any, issueId: string, state: string, projectId?: string): Promise<any>;
4
+ export declare function listLinearComments(apiUrl: string, headers: any, issueId: string, projectId?: string): Promise<any>;
5
+ export declare function createLinearComment(apiUrl: string, headers: any, issueId: string, body: string, projectId?: string): Promise<any>;
6
6
  //# sourceMappingURL=linear.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"linear.d.ts","sourceRoot":"","sources":["../../src/api/linear.ts"],"names":[],"mappings":"AAEA,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAYpH;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,CASd;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAIlH;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAIpG;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAInH"}
1
+ {"version":3,"file":"linear.d.ts","sourceRoot":"","sources":["../../src/api/linear.ts"],"names":[],"mappings":"AAmBA,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAIpH;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CASd;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAId;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAId;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAId"}
@@ -1,18 +1,23 @@
1
1
  import httpClient from '../utils/httpClient.js';
2
+ // The routes pick the project from `request.user.projectId` first and the
3
+ // `projectId` query second. Only an agent API key carries the former, so a
4
+ // personal-token or JWT session that omits the query has no project to look
5
+ // the Linear token up against and comes back 401 — which reads as "expired
6
+ // credential" rather than "missing scope". Send it whenever the caller knows it.
7
+ // The query stays harmless for agent API keys because the route prefers the
8
+ // credential's own project.
9
+ const withProjectId = (headers, projectId) => projectId ? { headers, params: { projectId } } : { headers };
10
+ // A LINEAR_ISSUE reference locator is not an AgentTeams id, so it is not
11
+ // UUID-validated upstream — encode it so it stays a single path segment.
12
+ // Every issue-scoped helper builds its URL here so the encoding cannot drift
13
+ // back into being asymmetric as helpers are added.
14
+ const issueUrl = (apiUrl, issueId, suffix = '') => `${apiUrl}/api/linear/issues/${encodeURIComponent(issueId)}${suffix}`;
2
15
  export async function getLinearIssue(apiUrl, headers, issueId, projectId) {
3
- // A LINEAR_ISSUE reference locator is not an AgentTeams id, so it is not
4
- // UUID-validated upstream encode it so it stays a single path segment.
5
- const baseUrl = `${apiUrl}/api/linear/issues/${encodeURIComponent(issueId)}`;
6
- // The route picks the project from `request.user.projectId` first and the
7
- // `projectId` query second. Only an agent API key carries the former, so a
8
- // personal-token or JWT session that omits the query has no project to look
9
- // the Linear token up against and comes back 401 — which reads as "expired
10
- // credential" rather than "missing scope". Send it whenever the caller knows it.
11
- const requestConfig = projectId ? { headers, params: { projectId } } : { headers };
12
- const response = await httpClient.get(baseUrl, requestConfig);
16
+ const baseUrl = issueUrl(apiUrl, issueId);
17
+ const response = await httpClient.get(baseUrl, withProjectId(headers, projectId));
13
18
  return response.data;
14
19
  }
15
- export async function createLinearIssue(apiUrl, headers, title, description, state, teamId, parentId) {
20
+ export async function createLinearIssue(apiUrl, headers, title, description, state, teamId, parentId, projectId) {
16
21
  const baseUrl = `${apiUrl}/api/linear/issues`;
17
22
  const body = { title };
18
23
  if (teamId)
@@ -23,22 +28,22 @@ export async function createLinearIssue(apiUrl, headers, title, description, sta
23
28
  body.state = state;
24
29
  if (parentId)
25
30
  body.parentId = parentId;
26
- const response = await httpClient.post(baseUrl, body, { headers });
31
+ const response = await httpClient.post(baseUrl, body, withProjectId(headers, projectId));
27
32
  return response.data;
28
33
  }
29
- export async function updateLinearIssue(apiUrl, headers, issueId, state) {
30
- const baseUrl = `${apiUrl}/api/linear/issues/${issueId}`;
31
- const response = await httpClient.patch(baseUrl, { state }, { headers });
34
+ export async function updateLinearIssue(apiUrl, headers, issueId, state, projectId) {
35
+ const baseUrl = issueUrl(apiUrl, issueId);
36
+ const response = await httpClient.patch(baseUrl, { state }, withProjectId(headers, projectId));
32
37
  return response.data;
33
38
  }
34
- export async function listLinearComments(apiUrl, headers, issueId) {
35
- const baseUrl = `${apiUrl}/api/linear/issues/${issueId}/comments`;
36
- const response = await httpClient.get(baseUrl, { headers });
39
+ export async function listLinearComments(apiUrl, headers, issueId, projectId) {
40
+ const baseUrl = issueUrl(apiUrl, issueId, '/comments');
41
+ const response = await httpClient.get(baseUrl, withProjectId(headers, projectId));
37
42
  return response.data;
38
43
  }
39
- export async function createLinearComment(apiUrl, headers, issueId, body) {
40
- const baseUrl = `${apiUrl}/api/linear/issues/${issueId}/comments`;
41
- const response = await httpClient.post(baseUrl, { body }, { headers });
44
+ export async function createLinearComment(apiUrl, headers, issueId, body, projectId) {
45
+ const baseUrl = issueUrl(apiUrl, issueId, '/comments');
46
+ const response = await httpClient.post(baseUrl, { body }, withProjectId(headers, projectId));
42
47
  return response.data;
43
48
  }
44
49
  //# sourceMappingURL=linear.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/api/linear.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,wBAAwB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,OAAY,EAAE,OAAe,EAAE,SAAkB;IACpG,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,OAAO,GAAG,GAAG,MAAM,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7E,0EAA0E;IAC1E,2EAA2E;IAC3E,4EAA4E;IAC5E,2EAA2E;IAC3E,iFAAiF;IACjF,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;IACnF,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC9D,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAc,EACd,OAAY,EACZ,KAAa,EACb,WAAoB,EACpB,KAAc,EACd,MAAe,EACf,QAAiB;IAEjB,MAAM,OAAO,GAAG,GAAG,MAAM,oBAAoB,CAAC;IAC9C,MAAM,IAAI,GAA2B,EAAE,KAAK,EAAE,CAAC;IAC/C,IAAI,MAAM;QAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACjC,IAAI,WAAW;QAAE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChD,IAAI,KAAK;QAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IAC9B,IAAI,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACnE,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAc,EAAE,OAAY,EAAE,OAAe,EAAE,KAAa;IAClG,MAAM,OAAO,GAAG,GAAG,MAAM,sBAAsB,OAAO,EAAE,CAAC;IACzD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,OAAY,EAAE,OAAe;IACpF,MAAM,OAAO,GAAG,GAAG,MAAM,sBAAsB,OAAO,WAAW,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5D,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,OAAY,EAAE,OAAe,EAAE,IAAY;IACnG,MAAM,OAAO,GAAG,GAAG,MAAM,sBAAsB,OAAO,WAAW,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACvE,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/api/linear.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * RFC 8628 device authorization for the CLI.
3
+ *
4
+ * The point of this flow is that **no local port is opened**. The terminal shows a
5
+ * short code, the user approves it in a browser on any other machine, and this
6
+ * module polls the server until that happens. It is only ever entered through the
7
+ * explicit `--device-auth` flag (or the machine-wide default the user declared);
8
+ * nothing here inspects SSH/container/WSL state to choose a flow.
9
+ */
10
+ import { type DeviceSetupResult, type PersonalTokenIdentity } from './personalTokenClient.js';
11
+ export type DeviceAuthFlow = 'login' | 'setup';
12
+ export interface DeviceAuthorizationStart {
13
+ deviceCode: string;
14
+ /** Display form (`XXXX-XXXX`). Shown to the user; never used as a credential. */
15
+ userCode: string;
16
+ /** Server-owned. Never rebuilt from the CLI's own web URL — dev/prod must not cross. */
17
+ verificationUri: string;
18
+ verificationUriComplete: string;
19
+ expiresIn: number;
20
+ interval: number;
21
+ }
22
+ export interface DeviceAuthStartInput {
23
+ apiUrl: string;
24
+ flow: DeviceAuthFlow;
25
+ projectName?: string;
26
+ osType?: string;
27
+ machineId?: string;
28
+ /** Encrypted working directory; `init` sends it so path-based runner binding matches loopback. */
29
+ authPathEnc?: string;
30
+ fetchImpl?: typeof fetch;
31
+ }
32
+ /**
33
+ * Ask the server to open a device session.
34
+ *
35
+ * The two failure shapes are kept distinct on purpose. A 404 means the server is
36
+ * too old to know this endpoint — the fix is to re-run *without* the flag. A 503
37
+ * means the server knows it but is not configured for it — re-running without the
38
+ * flag also works, but the operator has something to fix. Collapsing them into one
39
+ * message sends half the users after the wrong problem.
40
+ */
41
+ export declare function startDeviceAuthorization(input: DeviceAuthStartInput): Promise<DeviceAuthorizationStart>;
42
+ export interface DeviceAuthPollInput {
43
+ apiUrl: string;
44
+ start: DeviceAuthorizationStart;
45
+ /** Ctrl+C. Aborting stops the loop immediately rather than after the current sleep. */
46
+ signal?: AbortSignal;
47
+ /** Injected in tests so fake timers drive the interval instead of real waiting. */
48
+ sleep?: (ms: number) => Promise<void>;
49
+ now?: () => number;
50
+ }
51
+ export interface DeviceAuthResult {
52
+ identity: PersonalTokenIdentity;
53
+ setup: DeviceSetupResult | null;
54
+ }
55
+ /**
56
+ * Poll until the session is approved, denied, or expires.
57
+ *
58
+ * Interval discipline is the whole job: the server's `interval` is honoured as
59
+ * given, `slow_down` raises it, and the loop also stops locally at `expiresIn` so
60
+ * a server that never answers cannot turn into an unbounded poll.
61
+ */
62
+ export declare function pollDeviceAuthorization(input: DeviceAuthPollInput): Promise<DeviceAuthResult>;
63
+ //# sourceMappingURL=deviceAuthClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviceAuthClient.d.ts","sourceRoot":"","sources":["../../src/auth/deviceAuthClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAC;AAalC,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,MAAM,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAsE7G;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,wBAAwB,CAAC;IAChC,uFAAuF;IACvF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mFAAmF;IACnF,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACjC;AAID;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgEnG"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * RFC 8628 device authorization for the CLI.
3
+ *
4
+ * The point of this flow is that **no local port is opened**. The terminal shows a
5
+ * short code, the user approves it in a browser on any other machine, and this
6
+ * module polls the server until that happens. It is only ever entered through the
7
+ * explicit `--device-auth` flag (or the machine-wide default the user declared);
8
+ * nothing here inspects SSH/container/WSL state to choose a flow.
9
+ */
10
+ import { CLI_OAUTH_CLIENT_ID, getPersonalTokenClient, PersonalTokenError, } from './personalTokenClient.js';
11
+ /** Fallback poll interval when the server does not send one. The server value always wins. */
12
+ const FALLBACK_INTERVAL_SECONDS = 5;
13
+ /** RFC 8628 `slow_down` step, used only when the server does not name the new interval. */
14
+ const SLOW_DOWN_STEP_SECONDS = 5;
15
+ /** Consecutive network failures tolerated before giving up. */
16
+ const MAX_TRANSIENT_FAILURES = 5;
17
+ const START_REQUEST_TIMEOUT_MS = 15_000;
18
+ /**
19
+ * Ask the server to open a device session.
20
+ *
21
+ * The two failure shapes are kept distinct on purpose. A 404 means the server is
22
+ * too old to know this endpoint — the fix is to re-run *without* the flag. A 503
23
+ * means the server knows it but is not configured for it — re-running without the
24
+ * flag also works, but the operator has something to fix. Collapsing them into one
25
+ * message sends half the users after the wrong problem.
26
+ */
27
+ export async function startDeviceAuthorization(input) {
28
+ const doFetch = input.fetchImpl ?? globalThis.fetch;
29
+ const baseUrl = input.apiUrl.replace(/\/+$/, '');
30
+ let response;
31
+ try {
32
+ response = await doFetch(`${baseUrl}/api/auth/desktop/device/start`, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({
36
+ clientId: CLI_OAUTH_CLIENT_ID,
37
+ flow: input.flow,
38
+ ...(input.projectName ? { projectName: input.projectName } : {}),
39
+ ...(input.osType ? { osType: input.osType } : {}),
40
+ ...(input.machineId ? { machineId: input.machineId } : {}),
41
+ ...(input.authPathEnc ? { authPathEnc: input.authPathEnc } : {}),
42
+ }),
43
+ signal: AbortSignal.timeout(START_REQUEST_TIMEOUT_MS),
44
+ });
45
+ }
46
+ catch (error) {
47
+ throw new PersonalTokenError('TRANSIENT', `Could not reach ${baseUrl} to start device authorization: ${error instanceof Error ? error.message : String(error)}.`);
48
+ }
49
+ if (response.status === 404) {
50
+ throw new PersonalTokenError('TRANSIENT', `${baseUrl} does not support device authorization (it is running an older AgentTeams API). ` +
51
+ 'Run the same command without --device-auth to use the browser callback login.');
52
+ }
53
+ if (response.status === 503) {
54
+ throw new PersonalTokenError('TRANSIENT', `${baseUrl} has device authorization turned off because the server has no APP_URL configured. ` +
55
+ 'Ask the server operator to set APP_URL, or run the same command without --device-auth for now.');
56
+ }
57
+ if (!response.ok) {
58
+ throw new PersonalTokenError('TRANSIENT', `Device authorization could not be started (HTTP ${response.status}).`);
59
+ }
60
+ const body = (await response.json().catch(() => null));
61
+ const data = body?.data;
62
+ if (!data ||
63
+ typeof data.deviceCode !== 'string' ||
64
+ typeof data.userCode !== 'string' ||
65
+ typeof data.verificationUri !== 'string') {
66
+ throw new PersonalTokenError('MALFORMED_RESPONSE', 'The server returned an unexpected device authorization payload.');
67
+ }
68
+ return {
69
+ deviceCode: data.deviceCode,
70
+ userCode: data.userCode,
71
+ verificationUri: data.verificationUri,
72
+ verificationUriComplete: typeof data.verificationUriComplete === 'string' ? data.verificationUriComplete : data.verificationUri,
73
+ expiresIn: typeof data.expiresIn === 'number' ? data.expiresIn : 900,
74
+ interval: typeof data.interval === 'number' && data.interval > 0 ? data.interval : FALLBACK_INTERVAL_SECONDS,
75
+ };
76
+ }
77
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
78
+ /**
79
+ * Poll until the session is approved, denied, or expires.
80
+ *
81
+ * Interval discipline is the whole job: the server's `interval` is honoured as
82
+ * given, `slow_down` raises it, and the loop also stops locally at `expiresIn` so
83
+ * a server that never answers cannot turn into an unbounded poll.
84
+ */
85
+ export async function pollDeviceAuthorization(input) {
86
+ const client = getPersonalTokenClient(input.apiUrl);
87
+ const sleep = input.sleep ?? defaultSleep;
88
+ const now = input.now ?? Date.now;
89
+ const deadline = now() + input.start.expiresIn * 1000;
90
+ let intervalSeconds = input.start.interval;
91
+ let transientFailures = 0;
92
+ while (true) {
93
+ if (input.signal?.aborted) {
94
+ throw new PersonalTokenError('TRANSIENT', 'Device authorization was cancelled.');
95
+ }
96
+ await sleep(intervalSeconds * 1000);
97
+ if (input.signal?.aborted) {
98
+ throw new PersonalTokenError('TRANSIENT', 'Device authorization was cancelled.');
99
+ }
100
+ if (now() >= deadline) {
101
+ throw new PersonalTokenError('INVALID_GRANT', 'The device code expired before it was approved. Run the command again to get a new code.');
102
+ }
103
+ const outcome = await client.pollDeviceToken(input.start.deviceCode);
104
+ switch (outcome.kind) {
105
+ case 'approved':
106
+ return { identity: outcome.session.identity, setup: outcome.setup };
107
+ case 'pending':
108
+ transientFailures = 0;
109
+ break;
110
+ case 'slowDown':
111
+ transientFailures = 0;
112
+ intervalSeconds = outcome.intervalSeconds ?? intervalSeconds + SLOW_DOWN_STEP_SECONDS;
113
+ break;
114
+ case 'denied':
115
+ throw new PersonalTokenError('INVALID_GRANT', 'The sign-in request was denied in the browser.');
116
+ case 'expired':
117
+ throw new PersonalTokenError('INVALID_GRANT', 'The device code expired before it was approved. Run the command again to get a new code.');
118
+ case 'invalid':
119
+ throw new PersonalTokenError('INVALID_GRANT', 'The device code is no longer valid. Run the command again to get a new code.');
120
+ case 'transient':
121
+ // Back off but keep going: the user may still be finishing the approval.
122
+ transientFailures += 1;
123
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
124
+ throw new PersonalTokenError('TRANSIENT', `Lost contact with ${input.apiUrl} while waiting for approval: ${outcome.detail}.`);
125
+ }
126
+ intervalSeconds += SLOW_DOWN_STEP_SECONDS;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ //# sourceMappingURL=deviceAuthClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviceAuthClient.js","sourceRoot":"","sources":["../../src/auth/deviceAuthClient.ts"],"names":[],"mappings":"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"}
@@ -115,6 +115,40 @@ type ParsedTokenResponse = {
115
115
  * a truncated body). Only the first justifies destroying local credentials.
116
116
  */
117
117
  export declare function parseTokenResponse(response: Response): Promise<ParsedTokenResponse>;
118
+ /** `flow: 'setup'` result the approval screen chose; `init` writes these into the project config. */
119
+ export interface DeviceSetupResult {
120
+ teamId: string;
121
+ projectId: string;
122
+ agentConfigId: string;
123
+ agentName: string;
124
+ seedPlanId: string | null;
125
+ }
126
+ /**
127
+ * One poll's outcome, in the server's own vocabulary (RFC 8628 error codes).
128
+ *
129
+ * `transient` is deliberately separate from `denied`/`expired`: only the latter
130
+ * two are decisions, and treating a dropped connection as a decision would end a
131
+ * login the user is still completing.
132
+ */
133
+ export type DevicePollOutcome = {
134
+ kind: 'approved';
135
+ session: PersonalTokenSession;
136
+ setup: DeviceSetupResult | null;
137
+ } | {
138
+ kind: 'pending';
139
+ } | {
140
+ kind: 'slowDown';
141
+ intervalSeconds: number | null;
142
+ } | {
143
+ kind: 'denied';
144
+ } | {
145
+ kind: 'expired';
146
+ } | {
147
+ kind: 'invalid';
148
+ } | {
149
+ kind: 'transient';
150
+ detail: string;
151
+ };
118
152
  export declare class PersonalTokenClient {
119
153
  private readonly deps;
120
154
  private accessToken;
@@ -126,6 +160,16 @@ export declare class PersonalTokenClient {
126
160
  constructor(deps: PersonalTokenClientDeps);
127
161
  state(): PersonalTokenState;
128
162
  hasCredential(): boolean;
163
+ /**
164
+ * One poll of the RFC 8628 device token endpoint.
165
+ *
166
+ * Lives here rather than in the polling loop because the *storage* invariants
167
+ * are the same as a PKCE exchange: a credential the next process cannot find
168
+ * is not a login, so an unstorable token pair is revoked instead of orphaned.
169
+ * The loop only decides *when* to call this; the outcome vocabulary below is
170
+ * the server's error contract verbatim.
171
+ */
172
+ pollDeviceToken(deviceCode: string): Promise<DevicePollOutcome>;
129
173
  /**
130
174
  * Trade a PKCE authorization code for the first token pair.
131
175
  *
@@ -1 +1 @@
1
- {"version":3,"file":"personalTokenClient.d.ts","sourceRoot":"","sources":["../../src/auth/personalTokenClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"personalTokenClient.d.ts","sourceRoot":"","sources":["../../src/auth/personalTokenClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAyB,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC9G,OAAO,EAA+C,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC/G,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAE1B,kGAAkG;AAClG,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAyC,CAAC;AAE/E,MAAM,MAAM,sBAAsB,GAC9B,eAAe,GACf,WAAW,GACX,eAAe,GACf,oBAAoB,GACpB,eAAe;AACjB,qFAAqF;GACnF,mBAAmB,CAAC;AAExB,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,IAAI,EAAE,sBAAsB;gBAA5B,IAAI,EAAE,sBAAsB,EACrC,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,2BAA2B,GAAG,SAAS,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAE7F,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,YAAY,EAAE,mBAAmB,CAAC;IAClC,WAAW,EAAE,qBAAqB,CAAC;IACnC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACvC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,0EAA0E;IAC1E,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kFAAkF;IAClF,cAAc,EAAE,2BAA2B,GAAG,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,kBAAkB,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,UAAU,aAAa;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAED,KAAK,mBAAmB,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+CzF;AAED,qGAAqG;AACrG,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAC;IAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAA;CAAE,GACpF;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAwB1C,qBAAa,mBAAmB;IAQlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPjC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,eAAe,CAAuC;gBAEjC,IAAI,EAAE,uBAAuB;IAE1D,KAAK,IAAI,kBAAkB;IAc3B,aAAa,IAAI,OAAO;IAIxB;;;;;;;;OAQG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAoGrE;;;;;;;;;OASG;IACG,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAyC7F;;;;;;OAMG;YACW,uBAAuB;IAwBrC;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAc9C,6FAA6F;IAC7F,qBAAqB,IAAI,IAAI;IAK7B;;;;;;OAMG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAuC7B;;;;;OAKG;IACH,qBAAqB,IAAI,IAAI;YAIf,OAAO;IA0BrB,kEAAkE;YACpD,MAAM;YAmCN,SAAS;IAWvB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,OAAO;CAGhB;AAWD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAc1E;AAED,6EAA6E;AAC7E,wBAAgB,iCAAiC,IAAI,IAAI,CAExD"}
@@ -88,6 +88,24 @@ export async function parseTokenResponse(response) {
88
88
  },
89
89
  };
90
90
  }
91
+ const parseDeviceSetupResult = (value) => {
92
+ if (!value || typeof value !== 'object')
93
+ return null;
94
+ const setup = value;
95
+ if (typeof setup.teamId !== 'string' ||
96
+ typeof setup.projectId !== 'string' ||
97
+ typeof setup.agentConfigId !== 'string' ||
98
+ typeof setup.agentName !== 'string') {
99
+ return null;
100
+ }
101
+ return {
102
+ teamId: setup.teamId,
103
+ projectId: setup.projectId,
104
+ agentConfigId: setup.agentConfigId,
105
+ agentName: setup.agentName,
106
+ seedPlanId: typeof setup.seedPlanId === 'string' ? setup.seedPlanId : null,
107
+ };
108
+ };
91
109
  export class PersonalTokenClient {
92
110
  deps;
93
111
  accessToken = null;
@@ -115,6 +133,104 @@ export class PersonalTokenClient {
115
133
  hasCredential() {
116
134
  return this.deps.store.read() !== null;
117
135
  }
136
+ /**
137
+ * One poll of the RFC 8628 device token endpoint.
138
+ *
139
+ * Lives here rather than in the polling loop because the *storage* invariants
140
+ * are the same as a PKCE exchange: a credential the next process cannot find
141
+ * is not a login, so an unstorable token pair is revoked instead of orphaned.
142
+ * The loop only decides *when* to call this; the outcome vocabulary below is
143
+ * the server's error contract verbatim.
144
+ */
145
+ async pollDeviceToken(deviceCode) {
146
+ const doFetch = this.deps.fetch ?? globalThis.fetch;
147
+ let response;
148
+ try {
149
+ response = await doFetch(`${this.baseUrl()}/api/auth/desktop/device/token`, {
150
+ method: 'POST',
151
+ headers: { 'Content-Type': 'application/json' },
152
+ body: JSON.stringify({ clientId: CLI_OAUTH_CLIENT_ID, deviceCode }),
153
+ signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
154
+ });
155
+ }
156
+ catch (error) {
157
+ // A flaky network is not a denial. Reporting it as one would abort a login
158
+ // the user is still in the middle of completing in their browser.
159
+ return { kind: 'transient', detail: error instanceof Error ? error.message : String(error) };
160
+ }
161
+ if (!response.ok) {
162
+ let body;
163
+ try {
164
+ body = await response.json();
165
+ }
166
+ catch {
167
+ return { kind: 'transient', detail: `HTTP ${response.status} with an unreadable body` };
168
+ }
169
+ const payload = (body ?? {});
170
+ switch (payload.error) {
171
+ case 'authorization_pending':
172
+ return { kind: 'pending' };
173
+ case 'slow_down':
174
+ return {
175
+ kind: 'slowDown',
176
+ intervalSeconds: typeof payload.interval === 'number' ? payload.interval : null,
177
+ };
178
+ case 'access_denied':
179
+ return { kind: 'denied' };
180
+ case 'expired_token':
181
+ return { kind: 'expired' };
182
+ case 'invalid_grant':
183
+ case 'invalid_client':
184
+ return { kind: 'invalid' };
185
+ default:
186
+ return { kind: 'transient', detail: `HTTP ${response.status}` };
187
+ }
188
+ }
189
+ let body;
190
+ try {
191
+ body = await response.json();
192
+ }
193
+ catch {
194
+ return { kind: 'transient', detail: 'the server returned an unreadable token payload' };
195
+ }
196
+ const data = body && typeof body === 'object' && 'data' in body
197
+ ? body.data
198
+ : undefined;
199
+ const identity = data?.identity && typeof data.identity === 'object' ? data.identity : undefined;
200
+ if (typeof data?.accessToken !== 'string' ||
201
+ typeof data.refreshToken !== 'string' ||
202
+ typeof data.expiresIn !== 'number' ||
203
+ !identity ||
204
+ typeof identity.memberId !== 'string' ||
205
+ typeof identity.email !== 'string' ||
206
+ typeof identity.nickname !== 'string') {
207
+ return { kind: 'transient', detail: 'the server returned an unexpected token payload' };
208
+ }
209
+ const tokens = {
210
+ accessToken: data.accessToken,
211
+ refreshToken: data.refreshToken,
212
+ expiresIn: data.expiresIn,
213
+ identity: {
214
+ memberId: identity.memberId,
215
+ email: identity.email,
216
+ nickname: identity.nickname,
217
+ },
218
+ };
219
+ const stored = this.acceptTokens(tokens);
220
+ this.reconnectRequired = false;
221
+ if (!stored.persisted) {
222
+ await this.discardUnstorableTokens(stored);
223
+ }
224
+ return {
225
+ kind: 'approved',
226
+ session: {
227
+ accessToken: tokens.accessToken,
228
+ expiresAt: this.accessExpiresAt,
229
+ identity: tokens.identity,
230
+ },
231
+ setup: parseDeviceSetupResult(data.setup),
232
+ };
233
+ }
118
234
  /**
119
235
  * Trade a PKCE authorization code for the first token pair.
120
236
  *