@ogment-ai/cli 0.4.2 → 0.6.0

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 (87) hide show
  1. package/README.md +0 -9
  2. package/dist/cli/commands.d.ts +37 -0
  3. package/dist/cli/commands.d.ts.map +1 -0
  4. package/dist/cli/commands.js +56 -0
  5. package/dist/cli/execute.d.ts +11 -0
  6. package/dist/cli/execute.d.ts.map +1 -0
  7. package/dist/cli/execute.js +468 -0
  8. package/dist/cli/invocations.d.ts +31 -0
  9. package/dist/cli/invocations.d.ts.map +1 -0
  10. package/dist/cli/invocations.js +1 -0
  11. package/dist/cli/parse-errors.d.ts +17 -0
  12. package/dist/cli/parse-errors.d.ts.map +1 -0
  13. package/dist/cli/parse-errors.js +184 -0
  14. package/dist/cli/program.d.ts +10 -0
  15. package/dist/cli/program.d.ts.map +1 -0
  16. package/dist/cli/program.js +174 -0
  17. package/dist/cli/run.d.ts +6 -0
  18. package/dist/cli/run.d.ts.map +1 -0
  19. package/dist/cli/run.js +83 -0
  20. package/dist/cli/runtime.d.ts +21 -0
  21. package/dist/cli/runtime.d.ts.map +1 -0
  22. package/dist/cli/runtime.js +80 -0
  23. package/dist/cli.d.ts +2 -20
  24. package/dist/cli.d.ts.map +1 -1
  25. package/dist/cli.js +2 -737
  26. package/dist/commands/auth.d.ts +1 -4
  27. package/dist/commands/auth.d.ts.map +1 -1
  28. package/dist/commands/auth.js +0 -8
  29. package/dist/commands/catalog.d.ts +3 -1
  30. package/dist/commands/catalog.d.ts.map +1 -1
  31. package/dist/commands/catalog.js +19 -2
  32. package/dist/commands/invoke.d.ts.map +1 -1
  33. package/dist/commands/invoke.js +53 -3
  34. package/dist/infra/credentials.d.ts.map +1 -1
  35. package/dist/infra/credentials.js +0 -7
  36. package/dist/infra/http.d.ts +5 -1
  37. package/dist/infra/http.d.ts.map +1 -1
  38. package/dist/infra/http.js +62 -5
  39. package/dist/output/envelope.d.ts +5 -2
  40. package/dist/output/envelope.d.ts.map +1 -1
  41. package/dist/output/envelope.js +39 -23
  42. package/dist/output/manager.d.ts +9 -5
  43. package/dist/output/manager.d.ts.map +1 -1
  44. package/dist/output/manager.js +52 -9
  45. package/dist/services/account.d.ts.map +1 -1
  46. package/dist/services/account.js +9 -16
  47. package/dist/services/auth.d.ts +3 -15
  48. package/dist/services/auth.d.ts.map +1 -1
  49. package/dist/services/auth.js +32 -483
  50. package/dist/services/info.d.ts.map +1 -1
  51. package/dist/services/info.js +62 -0
  52. package/dist/services/mcp-error-mapping.d.ts +9 -0
  53. package/dist/services/mcp-error-mapping.d.ts.map +1 -0
  54. package/dist/services/mcp-error-mapping.js +129 -0
  55. package/dist/services/mcp.d.ts +8 -2
  56. package/dist/services/mcp.d.ts.map +1 -1
  57. package/dist/services/mcp.js +24 -14
  58. package/dist/shared/constants.d.ts +0 -2
  59. package/dist/shared/constants.d.ts.map +1 -1
  60. package/dist/shared/constants.js +0 -2
  61. package/dist/shared/error-codes.d.ts +4 -1
  62. package/dist/shared/error-codes.d.ts.map +1 -1
  63. package/dist/shared/error-presentation.d.ts +17 -0
  64. package/dist/shared/error-presentation.d.ts.map +1 -0
  65. package/dist/shared/error-presentation.js +151 -0
  66. package/dist/shared/errors.d.ts +34 -14
  67. package/dist/shared/errors.d.ts.map +1 -1
  68. package/dist/shared/errors.js +126 -25
  69. package/dist/shared/guards.d.ts +2 -1
  70. package/dist/shared/guards.d.ts.map +1 -1
  71. package/dist/shared/guards.js +1 -3
  72. package/dist/shared/recovery.d.ts +5 -0
  73. package/dist/shared/recovery.d.ts.map +1 -0
  74. package/dist/shared/recovery.js +123 -0
  75. package/dist/shared/schemas.d.ts +2 -3
  76. package/dist/shared/schemas.d.ts.map +1 -1
  77. package/dist/shared/schemas.js +2 -3
  78. package/dist/shared/types.d.ts +53 -13
  79. package/dist/shared/types.d.ts.map +1 -1
  80. package/dist/shared/types.js +1 -1
  81. package/package.json +2 -4
  82. package/dist/infra/browser.d.ts +0 -12
  83. package/dist/infra/browser.d.ts.map +0 -1
  84. package/dist/infra/browser.js +0 -20
  85. package/dist/shared/retry.d.ts +0 -17
  86. package/dist/shared/retry.d.ts.map +0 -1
  87. package/dist/shared/retry.js +0 -27
@@ -1,17 +1,9 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
- import { on } from "node:events";
3
- import { createServer } from "node:http";
4
- import { hostname } from "node:os";
5
1
  import { Result } from "better-result";
6
- import { detectExecutionEnvironment } from "../infra/env.js";
7
2
  import { readResponseText } from "../infra/http.js";
8
- import { CLI_CLIENT_NAME, CLI_REDIRECT_HOST } from "../shared/constants.js";
9
3
  import { ERROR_CODE } from "../shared/error-codes.js";
10
4
  import { AuthError, RemoteRequestError, UnexpectedError, ValidationError, } from "../shared/errors.js";
11
5
  import { parseWithSchema } from "../shared/guards.js";
12
- import { remoteRetryConfig } from "../shared/retry.js";
13
- import { browserAgentCallbackSchema, cliExchangeErrorSchema, cliExchangeRequestSchema, cliExchangeSuccessSchema, deviceCodeStartSchema, deviceTokenApprovedSchema, oauthClientRegistrationSchema, oauthTokenSchema, } from "../shared/schemas.js";
14
- const CALLBACK_TIMEOUT_MILLISECONDS = 5 * 60 * 1000;
6
+ import { deviceCodeStartSchema, deviceTokenApprovedSchema } from "../shared/schemas.js";
15
7
  const defaultSleep = async (milliseconds) => {
16
8
  await new Promise((resolve) => {
17
9
  setTimeout(resolve, milliseconds);
@@ -30,237 +22,17 @@ const toPendingPayload = (payload) => {
30
22
  }
31
23
  return null;
32
24
  };
33
- const generateCodeVerifier = () => {
34
- return randomBytes(32).toString("base64url");
35
- };
36
- const generateCodeChallenge = (verifier) => {
37
- return createHash("sha256").update(verifier).digest("base64url");
38
- };
39
- const closeServer = async (server) => {
40
- await Result.tryPromise({
41
- catch: () => undefined,
42
- try: async () => {
43
- await new Promise((resolve) => {
44
- server.close(() => {
45
- resolve();
46
- });
47
- });
48
- },
49
- });
50
- Result.try({
51
- catch: () => undefined,
52
- try: () => {
53
- server.unref();
54
- },
55
- });
56
- };
57
- const successPage = (agentName) => {
58
- return `<!DOCTYPE html>
59
- <html lang="en">
60
- <head><meta charset="utf-8"><title>Ogment - Agent configured</title></head>
61
- <body>
62
- <h1>Agent configured</h1>
63
- <p>Agent ${agentName} is now active. You can close this tab.</p>
64
- </body>
65
- </html>`;
66
- };
67
- const errorPage = (message) => {
68
- return `<!DOCTYPE html>
69
- <html lang="en">
70
- <head><meta charset="utf-8"><title>Ogment - Login failed</title></head>
71
- <body>
72
- <h1>Login failed</h1>
73
- <p>${message}</p>
74
- </body>
75
- </html>`;
76
- };
77
- const startCallbackServerWithPort = async (createServerFn) => {
78
- return Result.tryPromise({
79
- catch: (cause) => new UnexpectedError({
80
- cause,
81
- message: "Failed to start local callback server",
82
- }),
83
- try: async () => {
84
- return new Promise((resolve, reject) => {
85
- const server = createServerFn();
86
- const onError = (error) => {
87
- server.removeListener("error", onError);
88
- reject(error);
89
- };
90
- server.once("error", onError);
91
- server.listen(0, CLI_REDIRECT_HOST, () => {
92
- server.removeListener("error", onError);
93
- const address = server.address();
94
- if (address === null || typeof address === "string") {
95
- reject(new Error("Could not resolve callback server port"));
96
- return;
97
- }
98
- resolve({
99
- port: address.port,
100
- server,
101
- });
102
- });
103
- });
104
- },
105
- });
106
- };
107
- const waitForOAuthCallback = async (server, port) => {
108
- const abortController = new AbortController();
109
- const timeout = setTimeout(() => {
110
- abortController.abort();
111
- }, CALLBACK_TIMEOUT_MILLISECONDS);
112
- timeout.unref();
113
- try {
114
- for await (const event of on(server, "request", { signal: abortController.signal })) {
115
- const request = event[0];
116
- const response = event[1];
117
- const url = new URL(request.url ?? "/", `http://${CLI_REDIRECT_HOST}:${port}`);
118
- if (url.pathname !== "/callback") {
119
- continue;
120
- }
121
- const oauthError = url.searchParams.get("error");
122
- if (oauthError !== null) {
123
- response.writeHead(200, {
124
- "Content-Type": "text/html; charset=utf-8",
125
- });
126
- response.end(errorPage(`OAuth error: ${oauthError}`));
127
- return Result.err(new AuthError({
128
- code: ERROR_CODE.authInvalidCredentials,
129
- message: `OAuth error: ${oauthError}`,
130
- suggestedCommand: "ogment auth login --browser",
131
- }));
132
- }
133
- const code = url.searchParams.get("code");
134
- if (typeof code !== "string" || code.length === 0) {
135
- response.writeHead(400, {
136
- "Content-Type": "text/html; charset=utf-8",
137
- });
138
- response.end(errorPage("No authorization code received."));
139
- return Result.err(new AuthError({
140
- code: ERROR_CODE.authInvalidCredentials,
141
- message: "No authorization code in callback",
142
- suggestedCommand: "ogment auth login --browser",
143
- }));
144
- }
145
- return Result.ok({
146
- code,
147
- response,
148
- state: url.searchParams.get("state") ?? "",
149
- });
150
- }
151
- }
152
- catch (error) {
153
- if (error instanceof Error && error.name === "AbortError") {
154
- return Result.err(new AuthError({
155
- code: ERROR_CODE.authInvalidCredentials,
156
- message: "Login timed out. No callback received within 5 minutes.",
157
- suggestedCommand: "ogment auth login --browser",
158
- }));
159
- }
160
- return Result.err(new AuthError({
161
- code: ERROR_CODE.authInvalidCredentials,
162
- message: "Failed while waiting for OAuth callback.",
163
- suggestedCommand: "ogment auth login --browser",
164
- }));
165
- }
166
- finally {
167
- clearTimeout(timeout);
168
- }
169
- return Result.err(new AuthError({
170
- code: ERROR_CODE.authInvalidCredentials,
171
- message: "Login timed out. No callback received within 5 minutes.",
172
- suggestedCommand: "ogment auth login --browser",
173
- }));
174
- };
175
- const waitForAgentCallback = async (server, port) => {
176
- const abortController = new AbortController();
177
- const timeout = setTimeout(() => {
178
- abortController.abort();
179
- }, CALLBACK_TIMEOUT_MILLISECONDS);
180
- timeout.unref();
181
- try {
182
- for await (const event of on(server, "request", { signal: abortController.signal })) {
183
- const request = event[0];
184
- const response = event[1];
185
- const url = new URL(request.url ?? "/", `http://${CLI_REDIRECT_HOST}:${port}`);
186
- if (url.pathname !== "/agent-callback") {
187
- continue;
188
- }
189
- const parsedCallback = parseWithSchema(browserAgentCallbackSchema, Object.fromEntries(url.searchParams.entries()), "agent callback");
190
- if (Result.isError(parsedCallback)) {
191
- response.writeHead(400, {
192
- "Content-Type": "text/html; charset=utf-8",
193
- });
194
- response.end(errorPage("No exchange code received."));
195
- return Result.err(new AuthError({
196
- code: ERROR_CODE.authInvalidCredentials,
197
- message: "No exchange code in callback",
198
- suggestedCommand: "ogment auth login --browser",
199
- }));
200
- }
201
- const agentName = parsedCallback.value.agent_name ?? "CLI Agent";
202
- response.writeHead(200, {
203
- "Content-Type": "text/html; charset=utf-8",
204
- });
205
- response.end(successPage(agentName));
206
- return Result.ok({
207
- agentName,
208
- exchangeCode: parsedCallback.value.exchange_code,
209
- });
210
- }
211
- }
212
- catch (error) {
213
- if (error instanceof Error && error.name === "AbortError") {
214
- return Result.err(new AuthError({
215
- code: ERROR_CODE.authInvalidCredentials,
216
- message: "Agent selection timed out.",
217
- suggestedCommand: "ogment auth login --browser",
218
- }));
219
- }
220
- return Result.err(new AuthError({
221
- code: ERROR_CODE.authInvalidCredentials,
222
- message: "Failed while waiting for agent callback.",
223
- suggestedCommand: "ogment auth login --browser",
224
- }));
225
- }
226
- finally {
227
- clearTimeout(timeout);
228
- }
229
- return Result.err(new AuthError({
230
- code: ERROR_CODE.authInvalidCredentials,
231
- message: "Agent selection timed out.",
232
- suggestedCommand: "ogment auth login --browser",
233
- }));
234
- };
235
25
  const deviceCodeUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/device/code`;
236
26
  const deviceTokenUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/device/token`;
237
27
  const revokeUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/cli/revoke-self`;
238
- const oauthAuthorizeUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/authorize`;
239
- const oauthRegisterUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/register`;
240
- const oauthTokenUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/token`;
241
- const cliExchangeUrl = (baseUrl) => `${baseUrl}/api/v1/mcp-auth/cli/exchange`;
242
- const agentSelectUrl = (baseUrl) => `${baseUrl}/cli/agent-select`;
243
28
  export const createAuthService = (deps) => {
244
29
  const now = deps.now ?? Date.now;
245
30
  const sleep = deps.sleep ?? defaultSleep;
246
- const createServerFn = deps.createServerFn ?? createServer;
247
- const detectEnvironment = deps.detectEnvironment ?? detectExecutionEnvironment;
248
- const hostnameFn = deps.hostnameFn ?? hostname;
249
- const retryConfig = remoteRetryConfig();
250
- const requestWithRetry = async (input, init) => {
251
- return Result.tryPromise({
252
- catch: (cause) => cause,
253
- try: async () => {
254
- const response = await deps.httpClient.request(input, init);
255
- if (Result.isError(response)) {
256
- throw response.error;
257
- }
258
- return response.value;
259
- },
260
- }, retryConfig);
31
+ const requestRemote = async (input, init) => {
32
+ return deps.httpClient.request(input, init);
261
33
  };
262
34
  const loginWithDevice = async (options) => {
263
- const startFlowResponse = await requestWithRetry(deviceCodeUrl(deps.baseUrl), {
35
+ const startFlowResponse = await requestRemote(deviceCodeUrl(deps.baseUrl), {
264
36
  headers: {
265
37
  "Content-Type": "application/json",
266
38
  },
@@ -273,14 +45,19 @@ export const createAuthService = (deps) => {
273
45
  const body = await readResponseText(startFlowResponse.value);
274
46
  return Result.err(new RemoteRequestError({
275
47
  body,
48
+ httpStatus: startFlowResponse.value.status,
276
49
  message: "Failed to start device login",
277
- status: startFlowResponse.value.status,
50
+ operation: "auth/device/start",
51
+ raw: body,
52
+ source: "http",
278
53
  }));
279
54
  }
280
55
  const startPayload = await Result.tryPromise({
281
56
  catch: () => new RemoteRequestError({
57
+ httpStatus: startFlowResponse.value.status,
282
58
  message: "Failed to parse device login start payload",
283
- status: startFlowResponse.value.status,
59
+ operation: "auth/device/start",
60
+ source: "http",
284
61
  }),
285
62
  try: async () => startFlowResponse.value.json(),
286
63
  });
@@ -299,7 +76,7 @@ export const createAuthService = (deps) => {
299
76
  const interval = parsedStartPayload.value.data.interval * 1000;
300
77
  while (now() < deadline) {
301
78
  await sleep(interval);
302
- const pollResponse = await requestWithRetry(deviceTokenUrl(deps.baseUrl), {
79
+ const pollResponse = await requestRemote(deviceTokenUrl(deps.baseUrl), {
303
80
  body: JSON.stringify({
304
81
  device_code: parsedStartPayload.value.data.device_code,
305
82
  }),
@@ -315,28 +92,33 @@ export const createAuthService = (deps) => {
315
92
  return Result.err(new AuthError({
316
93
  code: ERROR_CODE.authDeviceExpired,
317
94
  message: "Device login code expired. Run `ogment auth login` again.",
318
- suggestedCommand: "ogment auth login",
95
+ recovery: { command: "ogment auth login" },
319
96
  }));
320
97
  }
321
98
  if (pollResponse.value.status === 404) {
322
99
  return Result.err(new AuthError({
323
100
  code: ERROR_CODE.authInvalidCredentials,
324
101
  message: "Invalid device login code. Run `ogment auth login` again.",
325
- suggestedCommand: "ogment auth login",
102
+ recovery: { command: "ogment auth login" },
326
103
  }));
327
104
  }
328
105
  if (!pollResponse.value.ok) {
329
106
  const body = await readResponseText(pollResponse.value);
330
107
  return Result.err(new RemoteRequestError({
331
108
  body,
109
+ httpStatus: pollResponse.value.status,
332
110
  message: "Failed to poll device login status",
333
- status: pollResponse.value.status,
111
+ operation: "auth/device/poll",
112
+ raw: body,
113
+ source: "http",
334
114
  }));
335
115
  }
336
116
  const pollPayload = await Result.tryPromise({
337
117
  catch: () => new RemoteRequestError({
118
+ httpStatus: pollResponse.value.status,
338
119
  message: "Failed to parse device login poll payload",
339
- status: pollResponse.value.status,
120
+ operation: "auth/device/poll",
121
+ source: "http",
340
122
  }),
341
123
  try: async () => pollResponse.value.json(),
342
124
  });
@@ -359,241 +141,16 @@ export const createAuthService = (deps) => {
359
141
  }
360
142
  return Result.ok({
361
143
  agentName: approvedPayload.value.data.agent_name ?? "CLI Agent",
362
- alreadyLoggedIn: false,
144
+ loggedIn: true,
145
+ outcome: "authenticated",
363
146
  });
364
147
  }
365
148
  return Result.err(new AuthError({
366
149
  code: ERROR_CODE.authDeviceExpired,
367
150
  message: "Device login code expired. Run `ogment auth login` again.",
368
- suggestedCommand: "ogment auth login",
151
+ recovery: { command: "ogment auth login" },
369
152
  }));
370
153
  };
371
- const loginWithBrowser = async () => {
372
- const callbackServerResult = await startCallbackServerWithPort(createServerFn);
373
- if (Result.isError(callbackServerResult)) {
374
- return callbackServerResult;
375
- }
376
- const { port, server } = callbackServerResult.value;
377
- const redirectUri = `http://${CLI_REDIRECT_HOST}:${port}/callback`;
378
- const registerResponse = await requestWithRetry(oauthRegisterUrl(deps.baseUrl), {
379
- body: JSON.stringify({
380
- client_name: CLI_CLIENT_NAME,
381
- grant_types: ["authorization_code"],
382
- redirect_uris: [redirectUri],
383
- response_types: ["code"],
384
- token_endpoint_auth_method: "client_secret_post",
385
- }),
386
- headers: {
387
- "Content-Type": "application/json",
388
- },
389
- method: "POST",
390
- });
391
- if (Result.isError(registerResponse)) {
392
- await closeServer(server);
393
- return registerResponse;
394
- }
395
- if (!registerResponse.value.ok) {
396
- const body = await readResponseText(registerResponse.value);
397
- await closeServer(server);
398
- return Result.err(new RemoteRequestError({
399
- body,
400
- message: "Client registration failed",
401
- status: registerResponse.value.status,
402
- }));
403
- }
404
- const registerPayload = await Result.tryPromise({
405
- catch: () => new RemoteRequestError({
406
- message: "Failed to parse client registration payload",
407
- status: registerResponse.value.status,
408
- }),
409
- try: async () => registerResponse.value.json(),
410
- });
411
- if (Result.isError(registerPayload)) {
412
- await closeServer(server);
413
- return registerPayload;
414
- }
415
- const parsedClient = parseWithSchema(oauthClientRegistrationSchema, registerPayload.value, "oauth client registration response");
416
- if (Result.isError(parsedClient)) {
417
- await closeServer(server);
418
- return parsedClient;
419
- }
420
- const codeVerifier = generateCodeVerifier();
421
- const codeChallenge = generateCodeChallenge(codeVerifier);
422
- const state = randomBytes(16).toString("hex");
423
- const authorizeUrl = new URL(oauthAuthorizeUrl(deps.baseUrl));
424
- authorizeUrl.searchParams.set("client_id", parsedClient.value.client_id);
425
- authorizeUrl.searchParams.set("redirect_uri", redirectUri);
426
- authorizeUrl.searchParams.set("response_type", "code");
427
- authorizeUrl.searchParams.set("code_challenge", codeChallenge);
428
- authorizeUrl.searchParams.set("code_challenge_method", "S256");
429
- authorizeUrl.searchParams.set("state", state);
430
- const oauthCallbackPromise = waitForOAuthCallback(server, port);
431
- const openResult = await deps.browserOpener.open(authorizeUrl.toString());
432
- if (Result.isError(openResult)) {
433
- await closeServer(server);
434
- return openResult;
435
- }
436
- const oauthCallbackResult = await oauthCallbackPromise;
437
- if (Result.isError(oauthCallbackResult)) {
438
- await closeServer(server);
439
- return oauthCallbackResult;
440
- }
441
- if (oauthCallbackResult.value.state !== state) {
442
- oauthCallbackResult.value.response.writeHead(302, {
443
- Location: "about:blank",
444
- });
445
- oauthCallbackResult.value.response.end();
446
- await closeServer(server);
447
- return Result.err(new AuthError({
448
- code: ERROR_CODE.authInvalidCredentials,
449
- message: "OAuth state mismatch - possible CSRF attack.",
450
- suggestedCommand: "ogment auth login --browser",
451
- }));
452
- }
453
- const tokenBody = new URLSearchParams({
454
- client_id: parsedClient.value.client_id,
455
- code: oauthCallbackResult.value.code,
456
- code_verifier: codeVerifier,
457
- grant_type: "authorization_code",
458
- redirect_uri: redirectUri,
459
- });
460
- if (typeof parsedClient.value.client_secret === "string" &&
461
- parsedClient.value.client_secret.length > 0) {
462
- tokenBody.set("client_secret", parsedClient.value.client_secret);
463
- }
464
- const tokenResponse = await requestWithRetry(oauthTokenUrl(deps.baseUrl), {
465
- body: tokenBody.toString(),
466
- headers: {
467
- "Content-Type": "application/x-www-form-urlencoded",
468
- },
469
- method: "POST",
470
- });
471
- if (Result.isError(tokenResponse)) {
472
- await closeServer(server);
473
- return tokenResponse;
474
- }
475
- if (!tokenResponse.value.ok) {
476
- const body = await readResponseText(tokenResponse.value);
477
- await closeServer(server);
478
- return Result.err(new RemoteRequestError({
479
- body,
480
- message: "Token exchange failed",
481
- status: tokenResponse.value.status,
482
- }));
483
- }
484
- const tokenPayload = await Result.tryPromise({
485
- catch: () => new RemoteRequestError({
486
- message: "Failed to parse token payload",
487
- status: tokenResponse.value.status,
488
- }),
489
- try: async () => tokenResponse.value.json(),
490
- });
491
- if (Result.isError(tokenPayload)) {
492
- await closeServer(server);
493
- return tokenPayload;
494
- }
495
- const parsedToken = parseWithSchema(oauthTokenSchema, tokenPayload.value, "oauth token response");
496
- if (Result.isError(parsedToken)) {
497
- await closeServer(server);
498
- return parsedToken;
499
- }
500
- const pickerUrl = new URL(agentSelectUrl(deps.baseUrl));
501
- pickerUrl.searchParams.set("token", parsedToken.value.access_token);
502
- pickerUrl.searchParams.set("callback", `http://${CLI_REDIRECT_HOST}:${port}/agent-callback`);
503
- pickerUrl.searchParams.set("env", detectEnvironment());
504
- pickerUrl.searchParams.set("host", hostnameFn());
505
- oauthCallbackResult.value.response.writeHead(302, {
506
- Location: pickerUrl.toString(),
507
- });
508
- oauthCallbackResult.value.response.end();
509
- const agentCallbackResult = await waitForAgentCallback(server, port);
510
- await closeServer(server);
511
- if (Result.isError(agentCallbackResult)) {
512
- return agentCallbackResult;
513
- }
514
- const exchangeRequestPayload = {
515
- exchange_code: agentCallbackResult.value.exchangeCode,
516
- };
517
- const parsedExchangeRequestPayload = parseWithSchema(cliExchangeRequestSchema, exchangeRequestPayload, "browser exchange request");
518
- if (Result.isError(parsedExchangeRequestPayload)) {
519
- return parsedExchangeRequestPayload;
520
- }
521
- const exchangeResponse = await requestWithRetry(cliExchangeUrl(deps.baseUrl), {
522
- body: JSON.stringify(parsedExchangeRequestPayload.value),
523
- headers: {
524
- "Content-Type": "application/json",
525
- },
526
- method: "POST",
527
- });
528
- if (Result.isError(exchangeResponse)) {
529
- return exchangeResponse;
530
- }
531
- if (!exchangeResponse.value.ok) {
532
- const body = await readResponseText(exchangeResponse.value);
533
- const parsedExchangeErrorPayload = Result.try({
534
- catch: () => null,
535
- try: () => JSON.parse(body),
536
- });
537
- if (Result.isOk(parsedExchangeErrorPayload)) {
538
- const parsedExchangeError = parseWithSchema(cliExchangeErrorSchema, parsedExchangeErrorPayload.value, "browser exchange error response");
539
- if (Result.isOk(parsedExchangeError)) {
540
- const exchangeErrorCode = parsedExchangeError.value.error;
541
- if (exchangeErrorCode === "expired_exchange_code") {
542
- return Result.err(new AuthError({
543
- code: ERROR_CODE.authDeviceExpired,
544
- message: "Browser login code expired. Run `ogment auth login --browser` again.",
545
- suggestedCommand: "ogment auth login --browser",
546
- }));
547
- }
548
- if (exchangeErrorCode === "invalid_exchange_code") {
549
- return Result.err(new AuthError({
550
- code: ERROR_CODE.authInvalidCredentials,
551
- message: "Invalid browser login code. Run `ogment auth login --browser` again.",
552
- suggestedCommand: "ogment auth login --browser",
553
- }));
554
- }
555
- if (exchangeErrorCode === "authorization_pending") {
556
- return Result.err(new AuthError({
557
- code: ERROR_CODE.authDevicePending,
558
- message: "Browser login authorization is still pending.",
559
- retryable: true,
560
- suggestedCommand: "ogment auth login --browser",
561
- }));
562
- }
563
- }
564
- }
565
- return Result.err(new RemoteRequestError({
566
- body,
567
- message: "Failed to exchange browser login code",
568
- status: exchangeResponse.value.status,
569
- }));
570
- }
571
- const exchangePayload = await Result.tryPromise({
572
- catch: () => new RemoteRequestError({
573
- message: "Failed to parse browser exchange payload",
574
- status: exchangeResponse.value.status,
575
- }),
576
- try: async () => exchangeResponse.value.json(),
577
- });
578
- if (Result.isError(exchangePayload)) {
579
- return exchangePayload;
580
- }
581
- const parsedExchangePayload = parseWithSchema(cliExchangeSuccessSchema, exchangePayload.value, "browser exchange response");
582
- if (Result.isError(parsedExchangePayload)) {
583
- return parsedExchangePayload;
584
- }
585
- const saveResult = deps.credentialsStore.save({
586
- agentName: parsedExchangePayload.value.data.name,
587
- apiKey: parsedExchangePayload.value.data.apiKey,
588
- });
589
- if (Result.isError(saveResult)) {
590
- return saveResult;
591
- }
592
- return Result.ok({
593
- agentName: parsedExchangePayload.value.data.name,
594
- alreadyLoggedIn: false,
595
- });
596
- };
597
154
  return {
598
155
  login: async (options) => {
599
156
  if (options.mode === "apiKey" && options.apiKey.length > 0) {
@@ -606,21 +163,15 @@ export const createAuthService = (deps) => {
606
163
  }
607
164
  return Result.ok({
608
165
  agentName: "CLI Agent",
609
- alreadyLoggedIn: false,
166
+ loggedIn: true,
167
+ outcome: "authenticated",
610
168
  });
611
169
  }
612
170
  if (options.mode === "apiKey") {
613
171
  return Result.err(new ValidationError({
614
172
  code: ERROR_CODE.validationInvalidInput,
615
173
  message: "Missing API key value. Provide a non-empty API key.",
616
- suggestedCommand: "ogment auth login --api-key <key>",
617
- }));
618
- }
619
- if (options.nonInteractive && options.mode === "browser") {
620
- return Result.err(new ValidationError({
621
- code: ERROR_CODE.validationInvalidInput,
622
- message: "Use `ogment auth login` in non-interactive mode.",
623
- suggestedCommand: "ogment auth login",
174
+ recovery: { command: "ogment auth login --api-key <key>" },
624
175
  }));
625
176
  }
626
177
  const stored = deps.credentialsStore.load();
@@ -630,13 +181,11 @@ export const createAuthService = (deps) => {
630
181
  if (stored.value !== null) {
631
182
  return Result.ok({
632
183
  agentName: stored.value.agentName ?? "CLI Agent",
633
- alreadyLoggedIn: true,
184
+ loggedIn: true,
185
+ outcome: "already_authenticated",
634
186
  });
635
187
  }
636
- if (options.mode === "device") {
637
- return loginWithDevice(options);
638
- }
639
- return loginWithBrowser();
188
+ return loginWithDevice(options);
640
189
  },
641
190
  logout: async () => {
642
191
  const stored = deps.credentialsStore.load();
@@ -649,7 +198,7 @@ export const createAuthService = (deps) => {
649
198
  revoked: false,
650
199
  });
651
200
  }
652
- const revokeResult = await requestWithRetry(revokeUrl(deps.baseUrl), {
201
+ const revokeResult = await requestRemote(revokeUrl(deps.baseUrl), {
653
202
  headers: {
654
203
  Authorization: `Bearer ${stored.value.apiKey}`,
655
204
  },
@@ -682,7 +231,7 @@ export const createAuthService = (deps) => {
682
231
  return Result.err(new AuthError({
683
232
  code: ERROR_CODE.authRequired,
684
233
  message: "Not logged in. Run `ogment auth login` or set OGMENT_API_KEY.",
685
- suggestedCommand: "ogment auth login",
234
+ recovery: { command: "ogment auth login" },
686
235
  }));
687
236
  },
688
237
  status: async (overrideApiKey) => {
@@ -1 +1 @@
1
- {"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../src/services/info.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EAGb,WAAW,EAEZ,MAAM,oBAAoB,CAAC;AAsB5B,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACxD;AAED,UAAU,eAAe;IACvB,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,4BAA4B,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5C,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqFD,eAAO,MAAM,iBAAiB,GAAI,MAAM,eAAe,KAAG,WAgNzD,CAAC"}
1
+ {"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../src/services/info.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EAGb,WAAW,EAEZ,MAAM,oBAAoB,CAAC;AAsB5B,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACxD;AAED,UAAU,eAAe;IACvB,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,4BAA4B,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5C,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAmGD,eAAO,MAAM,iBAAiB,GAAI,MAAM,eAAe,KAAG,WAwQzD,CAAC"}