dexto 1.6.8 → 1.6.9

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 (104) hide show
  1. package/README.md +4 -4
  2. package/dist/analytics/wrapper.d.ts.map +1 -1
  3. package/dist/analytics/wrapper.js +43 -9
  4. package/dist/cli/auth/api-client.d.ts +50 -0
  5. package/dist/cli/auth/api-client.d.ts.map +1 -1
  6. package/dist/cli/auth/api-client.js +379 -15
  7. package/dist/cli/auth/browser-launch.d.ts +6 -0
  8. package/dist/cli/auth/browser-launch.d.ts.map +1 -0
  9. package/dist/cli/auth/browser-launch.js +24 -0
  10. package/dist/cli/auth/device.d.ts +14 -0
  11. package/dist/cli/auth/device.d.ts.map +1 -0
  12. package/dist/cli/auth/device.js +93 -0
  13. package/dist/cli/auth/index.d.ts +3 -1
  14. package/dist/cli/auth/index.d.ts.map +1 -1
  15. package/dist/cli/auth/index.js +2 -1
  16. package/dist/cli/auth/login-persistence.d.ts +13 -0
  17. package/dist/cli/auth/login-persistence.d.ts.map +1 -0
  18. package/dist/cli/auth/login-persistence.js +22 -0
  19. package/dist/cli/auth/oauth.d.ts +4 -1
  20. package/dist/cli/auth/oauth.d.ts.map +1 -1
  21. package/dist/cli/auth/oauth.js +6 -2
  22. package/dist/cli/auth/types.d.ts +12 -0
  23. package/dist/cli/auth/types.d.ts.map +1 -0
  24. package/dist/cli/auth/types.js +1 -0
  25. package/dist/cli/commands/agents/register.d.ts +6 -0
  26. package/dist/cli/commands/agents/register.d.ts.map +1 -0
  27. package/dist/cli/commands/agents/register.js +85 -0
  28. package/dist/cli/commands/auth/index.d.ts +1 -1
  29. package/dist/cli/commands/auth/index.d.ts.map +1 -1
  30. package/dist/cli/commands/auth/index.js +1 -1
  31. package/dist/cli/commands/auth/login.d.ts +6 -6
  32. package/dist/cli/commands/auth/login.d.ts.map +1 -1
  33. package/dist/cli/commands/auth/login.js +85 -115
  34. package/dist/cli/commands/auth/logout.d.ts.map +1 -1
  35. package/dist/cli/commands/auth/logout.js +32 -2
  36. package/dist/cli/commands/auth/register.d.ts +3 -0
  37. package/dist/cli/commands/auth/register.d.ts.map +1 -0
  38. package/dist/cli/commands/auth/register.js +94 -0
  39. package/dist/cli/commands/billing/register.d.ts +3 -0
  40. package/dist/cli/commands/billing/register.d.ts.map +1 -0
  41. package/dist/cli/commands/billing/register.js +20 -0
  42. package/dist/cli/commands/helpers/formatters.d.ts.map +1 -1
  43. package/dist/cli/commands/helpers/formatters.js +9 -0
  44. package/dist/cli/commands/image/register.d.ts +6 -0
  45. package/dist/cli/commands/image/register.d.ts.map +1 -0
  46. package/dist/cli/commands/image/register.js +144 -0
  47. package/dist/cli/commands/install.d.ts +2 -2
  48. package/dist/cli/commands/list-agents.d.ts.map +1 -1
  49. package/dist/cli/commands/list-agents.js +3 -3
  50. package/dist/cli/commands/mcp/register.d.ts +6 -0
  51. package/dist/cli/commands/mcp/register.d.ts.map +1 -0
  52. package/dist/cli/commands/mcp/register.js +64 -0
  53. package/dist/cli/commands/plugin/register.d.ts +6 -0
  54. package/dist/cli/commands/plugin/register.d.ts.map +1 -0
  55. package/dist/cli/commands/plugin/register.js +183 -0
  56. package/dist/cli/commands/plugin.d.ts +4 -4
  57. package/dist/cli/commands/register-context.d.ts +12 -0
  58. package/dist/cli/commands/register-context.d.ts.map +1 -0
  59. package/dist/cli/commands/register-context.js +1 -0
  60. package/dist/cli/commands/run/headless.d.ts +20 -0
  61. package/dist/cli/commands/run/headless.d.ts.map +1 -0
  62. package/dist/cli/commands/run/headless.js +275 -0
  63. package/dist/cli/commands/run/register.d.ts +3 -0
  64. package/dist/cli/commands/run/register.d.ts.map +1 -0
  65. package/dist/cli/commands/run/register.js +78 -0
  66. package/dist/cli/commands/search/register.d.ts +3 -0
  67. package/dist/cli/commands/search/register.d.ts.map +1 -0
  68. package/dist/cli/commands/search/register.js +55 -0
  69. package/dist/cli/commands/session/register.d.ts +3 -0
  70. package/dist/cli/commands/session/register.d.ts.map +1 -0
  71. package/dist/cli/commands/session/register.js +75 -0
  72. package/dist/cli/commands/setup.js +4 -4
  73. package/dist/cli/commands/sync-agents.d.ts +3 -3
  74. package/dist/cli/commands/sync-agents.js +4 -4
  75. package/dist/cli/commands/uninstall.d.ts +2 -2
  76. package/dist/cli/modes/cli.d.ts +3 -0
  77. package/dist/cli/modes/cli.d.ts.map +1 -0
  78. package/dist/cli/modes/cli.js +170 -0
  79. package/dist/cli/modes/context.d.ts +20 -0
  80. package/dist/cli/modes/context.d.ts.map +1 -0
  81. package/dist/cli/modes/context.js +1 -0
  82. package/dist/cli/modes/dispatch.d.ts +3 -0
  83. package/dist/cli/modes/dispatch.d.ts.map +1 -0
  84. package/dist/cli/modes/dispatch.js +52 -0
  85. package/dist/cli/modes/mcp.d.ts +3 -0
  86. package/dist/cli/modes/mcp.d.ts.map +1 -0
  87. package/dist/cli/modes/mcp.js +23 -0
  88. package/dist/cli/modes/server.d.ts +3 -0
  89. package/dist/cli/modes/server.d.ts.map +1 -0
  90. package/dist/cli/modes/server.js +36 -0
  91. package/dist/cli/modes/web.d.ts +3 -0
  92. package/dist/cli/modes/web.d.ts.map +1 -0
  93. package/dist/cli/modes/web.js +50 -0
  94. package/dist/cli/utils/setup-utils.js +1 -1
  95. package/dist/index-main.js +150 -991
  96. package/dist/utils/port-utils.d.ts +1 -1
  97. package/dist/utils/port-utils.d.ts.map +1 -1
  98. package/dist/utils/port-utils.js +7 -3
  99. package/dist/webui/assets/index-Bn9YuTdA.css +1 -0
  100. package/dist/webui/assets/index-CNiOYnOb.js +2059 -0
  101. package/dist/webui/index.html +2 -2
  102. package/package.json +12 -12
  103. package/dist/webui/assets/index-d6c-yJNn.js +0 -2059
  104. package/dist/webui/assets/index-yKdFLN1k.css +0 -1
package/README.md CHANGED
@@ -433,10 +433,10 @@ Pre-built agents for common use cases:
433
433
 
434
434
  ```bash
435
435
  # List available agents
436
- dexto list-agents
436
+ dexto agents list
437
437
 
438
438
  # Install and run
439
- dexto install coding-agent podcast-agent
439
+ dexto agents install coding-agent podcast-agent
440
440
  dexto --agent coding-agent
441
441
  ```
442
442
 
@@ -606,8 +606,8 @@ Options:
606
606
 
607
607
  Commands:
608
608
  setup Configure global preferences
609
- install <agents...> Install agents from registry
610
- list-agents List available agents
609
+ agents install <agents...> Install agents from registry
610
+ agents list List available agents
611
611
  session list|history Manage sessions
612
612
  search <query> Search conversation history
613
613
  ```
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/analytics/wrapper.ts"],"names":[],"mappings":"AAkDA,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,EAC1D,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACvC,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAiE5B;AAED,qBAAa,UAAW,SAAQ,KAAK;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;gBACrB,IAAI,GAAE,MAAU,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;CAOtE;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAEtF"}
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/analytics/wrapper.ts"],"names":[],"mappings":"AAqEA,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,EAC1D,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACvC,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAmF5B;AAED,qBAAa,UAAW,SAAQ,KAAK;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;gBACrB,IAAI,GAAE,MAAU,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;CAOtE;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAEtF"}
@@ -1,6 +1,21 @@
1
1
  // packages/cli/src/analytics/wrapper.ts
2
- import { onCommandStart, onCommandEnd, capture } from './index.js';
3
2
  import { COMMAND_TIMEOUT_MS } from './constants.js';
3
+ let analyticsModulePromise = null;
4
+ let analyticsInitialized = false;
5
+ async function getAnalyticsModule() {
6
+ if (!analyticsModulePromise) {
7
+ analyticsModulePromise = import('./index.js');
8
+ }
9
+ return analyticsModulePromise;
10
+ }
11
+ async function ensureAnalyticsInitialized() {
12
+ const analytics = await getAnalyticsModule();
13
+ if (!analyticsInitialized) {
14
+ await analytics.initAnalytics({ appVersion: process.env.DEXTO_CLI_VERSION || '0.0.0' });
15
+ analyticsInitialized = true;
16
+ }
17
+ return analytics;
18
+ }
4
19
  function sanitizeOptions(obj) {
5
20
  const redactedKeys = /key|token|secret|password|api[_-]?key|authorization|auth/i;
6
21
  const truncate = (s, max = 256) => (s.length > max ? s.slice(0, max) + '…' : s);
@@ -45,7 +60,14 @@ export function withAnalytics(commandName, handler, opts) {
45
60
  const timeoutMs = opts?.timeoutMs ?? COMMAND_TIMEOUT_MS;
46
61
  return async (...args) => {
47
62
  const argsMeta = buildArgsPayload(args);
48
- await onCommandStart(commandName, { args: argsMeta });
63
+ let analytics = null;
64
+ try {
65
+ analytics = await ensureAnalyticsInitialized();
66
+ await analytics.onCommandStart(commandName, { args: argsMeta });
67
+ }
68
+ catch {
69
+ analytics = null;
70
+ }
49
71
  const timeout = timeoutMs > 0
50
72
  ? (() => {
51
73
  const t = setTimeout(() => {
@@ -56,7 +78,13 @@ export function withAnalytics(commandName, handler, opts) {
56
78
  timeoutMs,
57
79
  args: argsMeta,
58
80
  };
59
- capture('dexto_cli_command', payload);
81
+ if (analytics) {
82
+ analytics.capture('dexto_cli_command', payload);
83
+ return;
84
+ }
85
+ void getAnalyticsModule()
86
+ .then((mod) => mod.capture('dexto_cli_command', payload))
87
+ .catch(() => { });
60
88
  }
61
89
  catch {
62
90
  // Timeout instrumentation must never throw.
@@ -70,7 +98,9 @@ export function withAnalytics(commandName, handler, opts) {
70
98
  try {
71
99
  const result = await handler(...args);
72
100
  const success = (typeof process.exitCode === 'number' ? process.exitCode : 0) === 0;
73
- await onCommandEnd(commandName, success, { args: argsMeta });
101
+ if (analytics) {
102
+ await analytics.onCommandEnd(commandName, success, { args: argsMeta });
103
+ }
74
104
  return result;
75
105
  }
76
106
  catch (err) {
@@ -85,7 +115,9 @@ export function withAnalytics(commandName, handler, opts) {
85
115
  if (err.commandName) {
86
116
  endMeta.command = err.commandName;
87
117
  }
88
- await onCommandEnd(commandName, exitCode === 0, endMeta);
118
+ if (analytics) {
119
+ await analytics.onCommandEnd(commandName, exitCode === 0, endMeta);
120
+ }
89
121
  }
90
122
  catch {
91
123
  // Ignore analytics errors when propagating ExitSignal.
@@ -94,10 +126,12 @@ export function withAnalytics(commandName, handler, opts) {
94
126
  process.exit(exitCode);
95
127
  }
96
128
  try {
97
- await onCommandEnd(commandName, false, {
98
- error: err instanceof Error ? err.message : String(err),
99
- args: argsMeta,
100
- });
129
+ if (analytics) {
130
+ await analytics.onCommandEnd(commandName, false, {
131
+ error: err instanceof Error ? err.message : String(err),
132
+ args: argsMeta,
133
+ });
134
+ }
101
135
  }
102
136
  catch {
103
137
  // Ignore analytics errors when recording failures.
@@ -1,3 +1,32 @@
1
+ import type { AuthenticatedUser } from './types.js';
2
+ export interface DeviceCodeStartResponse {
3
+ deviceCode: string;
4
+ userCode: string;
5
+ verificationUrl: string;
6
+ verificationUrlComplete: string | null;
7
+ expiresIn: number;
8
+ interval: number;
9
+ }
10
+ export interface DeviceCodeTokenResponse {
11
+ accessToken: string;
12
+ refreshToken?: string | null;
13
+ expiresIn?: number | null;
14
+ expiresAt?: number | null;
15
+ }
16
+ export type DeviceCodePollResponse = {
17
+ status: 'pending';
18
+ } | {
19
+ status: 'slowDown';
20
+ } | {
21
+ status: 'expired';
22
+ } | {
23
+ status: 'denied';
24
+ } | {
25
+ status: 'transientError';
26
+ } | {
27
+ status: 'success';
28
+ token: DeviceCodeTokenResponse;
29
+ };
1
30
  export interface UsageSummaryResponse {
2
31
  credits_usd: number;
3
32
  mtd_usage: {
@@ -17,6 +46,9 @@ export interface UsageSummaryResponse {
17
46
  output_tokens: number;
18
47
  }>;
19
48
  }
49
+ interface RequestOptions {
50
+ signal?: AbortSignal | undefined;
51
+ }
20
52
  /**
21
53
  * Dexto API client for key management
22
54
  */
@@ -24,6 +56,7 @@ export declare class DextoApiClient {
24
56
  private readonly baseUrl;
25
57
  private readonly timeoutMs;
26
58
  constructor(baseUrl?: string);
59
+ private createRequestSignal;
27
60
  /**
28
61
  * Validate if a Dexto API key is valid
29
62
  */
@@ -41,9 +74,26 @@ export declare class DextoApiClient {
41
74
  * Get usage summary (balance + MTD usage + recent history)
42
75
  */
43
76
  getUsageSummary(apiKey: string): Promise<UsageSummaryResponse>;
77
+ /**
78
+ * Start device code login at the gateway.
79
+ */
80
+ startDeviceCodeLogin(client?: string, options?: RequestOptions): Promise<DeviceCodeStartResponse>;
81
+ /**
82
+ * Poll the gateway for device-code login completion.
83
+ */
84
+ pollDeviceCodeLogin(deviceCode: string, options?: RequestOptions): Promise<DeviceCodePollResponse>;
85
+ /**
86
+ * Fetch Supabase user profile for a bearer token.
87
+ */
88
+ fetchSupabaseUser(accessToken: string, options?: RequestOptions): Promise<AuthenticatedUser | undefined>;
89
+ /**
90
+ * Validate Supabase access token by checking if /auth/v1/user resolves.
91
+ */
92
+ validateSupabaseAccessToken(accessToken: string, options?: RequestOptions): Promise<boolean>;
44
93
  }
45
94
  /**
46
95
  * Get default Dexto API client
47
96
  */
48
97
  export declare function getDextoApiClient(): DextoApiClient;
98
+ export {};
49
99
  //# sourceMappingURL=api-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../../src/cli/auth/api-client.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,oBAAoB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACP,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,EAAE,MAAM,CACZ,MAAM,EACN;YACI,QAAQ,EAAE,MAAM,CAAC;YACjB,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC;SAClB,CACJ,CAAC;KACL,CAAC;IACF,MAAM,EAAE,KAAK,CAAC;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;CACN;AAED;;GAEG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;gBAExB,OAAO,GAAE,MAAsB;IAI3C;;OAEG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB3D;;;OAGG;IACG,oBAAoB,CACtB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAAwB,EAC9B,UAAU,GAAE,OAAe,GAC5B,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0DrE;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAwBvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAElD"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../../src/cli/auth/api-client.ts"],"names":[],"mappings":"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"}
@@ -5,18 +5,247 @@
5
5
  // 1. Migrate dexto-web APIs to Hono and use @hono/client (like packages/server)
6
6
  // 2. Use openapi-fetch with generated types from OpenAPI spec
7
7
  // 3. Use tRPC if we want full-stack type safety
8
- // Currently using plain fetch() with manual type definitions.
8
+ // Currently using plain fetch() with runtime response validation.
9
9
  import { logger } from '@dexto/core';
10
- import { DEXTO_API_URL } from './constants.js';
10
+ import { DEXTO_API_URL, SUPABASE_ANON_KEY, SUPABASE_URL } from './constants.js';
11
+ function parseString(value) {
12
+ return typeof value === 'string' && value.trim().length > 0 ? value : null;
13
+ }
14
+ function parseBoolean(value) {
15
+ return typeof value === 'boolean' ? value : null;
16
+ }
17
+ function parseNumber(value) {
18
+ if (typeof value === 'number' && Number.isFinite(value)) {
19
+ return value;
20
+ }
21
+ if (typeof value === 'string') {
22
+ const parsed = Number.parseFloat(value);
23
+ if (Number.isFinite(parsed)) {
24
+ return parsed;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ function parseProvisionResponse(payload) {
30
+ if (typeof payload !== 'object' || payload === null) {
31
+ throw new Error('Invalid response from API');
32
+ }
33
+ const success = parseBoolean(Reflect.get(payload, 'success'));
34
+ if (success === null) {
35
+ throw new Error('Invalid response from API');
36
+ }
37
+ const dextoApiKey = parseString(Reflect.get(payload, 'dextoApiKey')) ?? undefined;
38
+ const keyId = parseString(Reflect.get(payload, 'keyId')) ?? undefined;
39
+ const isNewKey = parseBoolean(Reflect.get(payload, 'isNewKey')) ?? undefined;
40
+ const error = parseString(Reflect.get(payload, 'error')) ?? undefined;
41
+ const result = {
42
+ success,
43
+ };
44
+ if (dextoApiKey) {
45
+ result.dextoApiKey = dextoApiKey;
46
+ }
47
+ if (keyId) {
48
+ result.keyId = keyId;
49
+ }
50
+ if (isNewKey !== undefined) {
51
+ result.isNewKey = isNewKey;
52
+ }
53
+ if (error) {
54
+ result.error = error;
55
+ }
56
+ return result;
57
+ }
58
+ function parseDeviceCodeStartResponse(payload) {
59
+ if (typeof payload !== 'object' || payload === null) {
60
+ throw new Error('Invalid device start response');
61
+ }
62
+ const deviceCode = parseString(Reflect.get(payload, 'device_code')) ??
63
+ parseString(Reflect.get(payload, 'deviceCode'));
64
+ const userCode = parseString(Reflect.get(payload, 'user_code')) ??
65
+ parseString(Reflect.get(payload, 'userCode'));
66
+ const verificationUrl = parseString(Reflect.get(payload, 'verification_uri')) ??
67
+ parseString(Reflect.get(payload, 'verificationUrl'));
68
+ const verificationUrlComplete = parseString(Reflect.get(payload, 'verification_uri_complete')) ??
69
+ parseString(Reflect.get(payload, 'verificationUrlComplete'));
70
+ const expiresIn = parseNumber(Reflect.get(payload, 'expires_in')) ??
71
+ parseNumber(Reflect.get(payload, 'expiresIn'));
72
+ const interval = parseNumber(Reflect.get(payload, 'interval'));
73
+ if (!deviceCode || !userCode || !verificationUrl || !expiresIn || !interval) {
74
+ throw new Error('Device start response missing required fields');
75
+ }
76
+ return {
77
+ deviceCode,
78
+ userCode,
79
+ verificationUrl,
80
+ verificationUrlComplete,
81
+ expiresIn,
82
+ interval,
83
+ };
84
+ }
85
+ function parseDeviceCodeTokenResponse(payload) {
86
+ if (typeof payload !== 'object' || payload === null) {
87
+ return null;
88
+ }
89
+ const accessToken = parseString(Reflect.get(payload, 'accessToken')) ??
90
+ parseString(Reflect.get(payload, 'access_token'));
91
+ if (!accessToken) {
92
+ return null;
93
+ }
94
+ const refreshToken = parseString(Reflect.get(payload, 'refreshToken')) ??
95
+ parseString(Reflect.get(payload, 'refresh_token'));
96
+ const expiresIn = parseNumber(Reflect.get(payload, 'expiresIn')) ??
97
+ parseNumber(Reflect.get(payload, 'expires_in'));
98
+ const expiresAt = parseNumber(Reflect.get(payload, 'expiresAt')) ??
99
+ parseNumber(Reflect.get(payload, 'expires_at'));
100
+ return {
101
+ accessToken,
102
+ refreshToken,
103
+ expiresIn,
104
+ expiresAt,
105
+ };
106
+ }
107
+ function parseDeviceErrorCode(payload) {
108
+ if (typeof payload !== 'object' || payload === null) {
109
+ return null;
110
+ }
111
+ return parseString(Reflect.get(payload, 'error'));
112
+ }
113
+ function isTransientDevicePollErrorCode(errorCode) {
114
+ return (errorCode === 'server_error' ||
115
+ errorCode === 'temporary_unavailable' ||
116
+ errorCode === 'temporarily_unavailable');
117
+ }
118
+ function parseSupabaseUser(payload) {
119
+ if (typeof payload !== 'object' || payload === null) {
120
+ return null;
121
+ }
122
+ const id = parseString(Reflect.get(payload, 'id'));
123
+ const email = parseString(Reflect.get(payload, 'email'));
124
+ if (!id || !email) {
125
+ return null;
126
+ }
127
+ const userMetadata = Reflect.get(payload, 'user_metadata');
128
+ const fullName = typeof userMetadata === 'object' && userMetadata !== null
129
+ ? parseString(Reflect.get(userMetadata, 'full_name'))
130
+ : null;
131
+ return {
132
+ id,
133
+ email,
134
+ name: fullName ?? email,
135
+ };
136
+ }
137
+ function parseUsageByModel(payload) {
138
+ if (typeof payload !== 'object' || payload === null) {
139
+ return {};
140
+ }
141
+ const byModel = {};
142
+ for (const [model, value] of Object.entries(payload)) {
143
+ if (typeof value !== 'object' || value === null) {
144
+ continue;
145
+ }
146
+ const requests = parseNumber(Reflect.get(value, 'requests'));
147
+ const costUsd = parseNumber(Reflect.get(value, 'cost_usd'));
148
+ const tokens = parseNumber(Reflect.get(value, 'tokens'));
149
+ if (requests === null || costUsd === null || tokens === null) {
150
+ continue;
151
+ }
152
+ byModel[model] = {
153
+ requests,
154
+ cost_usd: costUsd,
155
+ tokens,
156
+ };
157
+ }
158
+ return byModel;
159
+ }
160
+ function parseRecentUsage(payload) {
161
+ if (!Array.isArray(payload)) {
162
+ return [];
163
+ }
164
+ const entries = [];
165
+ for (const entry of payload) {
166
+ if (typeof entry !== 'object' || entry === null) {
167
+ continue;
168
+ }
169
+ const timestamp = parseString(Reflect.get(entry, 'timestamp'));
170
+ const model = parseString(Reflect.get(entry, 'model'));
171
+ const costUsd = parseNumber(Reflect.get(entry, 'cost_usd'));
172
+ const inputTokens = parseNumber(Reflect.get(entry, 'input_tokens'));
173
+ const outputTokens = parseNumber(Reflect.get(entry, 'output_tokens'));
174
+ if (!timestamp ||
175
+ !model ||
176
+ costUsd === null ||
177
+ inputTokens === null ||
178
+ outputTokens === null) {
179
+ continue;
180
+ }
181
+ entries.push({
182
+ timestamp,
183
+ model,
184
+ cost_usd: costUsd,
185
+ input_tokens: inputTokens,
186
+ output_tokens: outputTokens,
187
+ });
188
+ }
189
+ return entries;
190
+ }
191
+ function parseUsageSummaryResponse(payload) {
192
+ if (typeof payload !== 'object' || payload === null) {
193
+ throw new Error('Invalid response from API');
194
+ }
195
+ const creditsUsd = parseNumber(Reflect.get(payload, 'credits_usd'));
196
+ const mtdUsage = Reflect.get(payload, 'mtd_usage');
197
+ if (creditsUsd === null || typeof mtdUsage !== 'object' || mtdUsage === null) {
198
+ throw new Error('Invalid response from API');
199
+ }
200
+ const totalCostUsd = parseNumber(Reflect.get(mtdUsage, 'total_cost_usd'));
201
+ const totalRequests = parseNumber(Reflect.get(mtdUsage, 'total_requests'));
202
+ if (totalCostUsd === null || totalRequests === null) {
203
+ throw new Error('Invalid response from API');
204
+ }
205
+ const byModel = parseUsageByModel(Reflect.get(mtdUsage, 'by_model'));
206
+ const recent = parseRecentUsage(Reflect.get(payload, 'recent'));
207
+ return {
208
+ credits_usd: creditsUsd,
209
+ mtd_usage: {
210
+ total_cost_usd: totalCostUsd,
211
+ total_requests: totalRequests,
212
+ by_model: byModel,
213
+ },
214
+ recent,
215
+ };
216
+ }
217
+ function parseValidateResponse(payload) {
218
+ if (typeof payload !== 'object' || payload === null) {
219
+ return false;
220
+ }
221
+ const valid = parseBoolean(Reflect.get(payload, 'valid'));
222
+ return valid ?? false;
223
+ }
224
+ function formatHttpFailure(status, payload, rawText) {
225
+ if (rawText.trim().length > 0) {
226
+ return `${status} ${rawText}`;
227
+ }
228
+ if (payload != null) {
229
+ return `${status} ${JSON.stringify(payload)}`;
230
+ }
231
+ return `${status}`;
232
+ }
11
233
  /**
12
234
  * Dexto API client for key management
13
235
  */
14
236
  export class DextoApiClient {
15
237
  baseUrl;
16
- timeoutMs = 10_000; // 10 second timeout for API calls
238
+ timeoutMs = 10_000;
17
239
  constructor(baseUrl = DEXTO_API_URL) {
18
240
  this.baseUrl = baseUrl;
19
241
  }
242
+ createRequestSignal(signal) {
243
+ const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
244
+ if (!signal) {
245
+ return timeoutSignal;
246
+ }
247
+ return AbortSignal.any([signal, timeoutSignal]);
248
+ }
20
249
  /**
21
250
  * Validate if a Dexto API key is valid
22
251
  */
@@ -28,13 +257,13 @@ export class DextoApiClient {
28
257
  headers: {
29
258
  Authorization: `Bearer ${apiKey}`,
30
259
  },
31
- signal: AbortSignal.timeout(this.timeoutMs),
260
+ signal: this.createRequestSignal(undefined),
32
261
  });
33
262
  if (!response.ok) {
34
263
  return false;
35
264
  }
36
- const result = await response.json();
37
- return result.valid;
265
+ const payload = await response.json();
266
+ return parseValidateResponse(payload);
38
267
  }
39
268
  catch (error) {
40
269
  logger.error(`Error validating Dexto API key: ${error}`);
@@ -55,13 +284,14 @@ export class DextoApiClient {
55
284
  'Content-Type': 'application/json',
56
285
  },
57
286
  body: JSON.stringify({ name, regenerate }),
58
- signal: AbortSignal.timeout(this.timeoutMs),
287
+ signal: this.createRequestSignal(undefined),
59
288
  });
60
289
  if (!response.ok) {
61
- const errorText = await response.text();
62
- throw new Error(`API request failed: ${response.status} ${errorText}`);
290
+ const rawText = await response.text();
291
+ throw new Error(`API request failed: ${formatHttpFailure(response.status, null, rawText)}`);
63
292
  }
64
- const result = await response.json();
293
+ const payload = await response.json();
294
+ const result = parseProvisionResponse(payload);
65
295
  if (!result.success) {
66
296
  throw new Error(result.error || 'Failed to provision Dexto API key');
67
297
  }
@@ -104,20 +334,154 @@ export class DextoApiClient {
104
334
  headers: {
105
335
  Authorization: `Bearer ${apiKey}`,
106
336
  },
107
- signal: AbortSignal.timeout(this.timeoutMs),
337
+ signal: this.createRequestSignal(undefined),
108
338
  });
109
339
  if (!response.ok) {
110
- const errorText = await response.text();
111
- throw new Error(`API request failed: ${response.status} ${errorText}`);
340
+ const rawText = await response.text();
341
+ throw new Error(`API request failed: ${formatHttpFailure(response.status, null, rawText)}`);
112
342
  }
113
- const result = await response.json();
114
- return result;
343
+ const payload = await response.json();
344
+ return parseUsageSummaryResponse(payload);
115
345
  }
116
346
  catch (error) {
117
347
  logger.error(`Error fetching usage summary: ${error}`);
118
348
  throw error;
119
349
  }
120
350
  }
351
+ /**
352
+ * Start device code login at the gateway.
353
+ */
354
+ async startDeviceCodeLogin(client = 'dexto-cli', options = {}) {
355
+ const response = await fetch(`${this.baseUrl}/auth/device/start`, {
356
+ method: 'POST',
357
+ headers: {
358
+ 'Content-Type': 'application/json',
359
+ },
360
+ body: JSON.stringify({ client }),
361
+ signal: this.createRequestSignal(options.signal),
362
+ });
363
+ if (!response.ok) {
364
+ if (response.status === 404 || response.status === 403) {
365
+ throw new Error('Device code login is not enabled for this Dexto server');
366
+ }
367
+ const rawText = await response.text();
368
+ throw new Error(`Device code login failed to start: ${formatHttpFailure(response.status, null, rawText)}`);
369
+ }
370
+ const payload = await response.json();
371
+ return parseDeviceCodeStartResponse(payload);
372
+ }
373
+ /**
374
+ * Poll the gateway for device-code login completion.
375
+ */
376
+ async pollDeviceCodeLogin(deviceCode, options = {}) {
377
+ let response;
378
+ try {
379
+ response = await fetch(`${this.baseUrl}/auth/device/poll`, {
380
+ method: 'POST',
381
+ headers: {
382
+ 'Content-Type': 'application/json',
383
+ },
384
+ body: JSON.stringify({ device_code: deviceCode }),
385
+ signal: this.createRequestSignal(options.signal),
386
+ });
387
+ }
388
+ catch (error) {
389
+ if (options.signal?.aborted) {
390
+ throw options.signal.reason instanceof Error
391
+ ? options.signal.reason
392
+ : new Error('Authentication cancelled');
393
+ }
394
+ logger.debug(`Device login poll request failed transiently: ${error instanceof Error ? error.message : String(error)}`);
395
+ return { status: 'transientError' };
396
+ }
397
+ const payload = await response.json().catch(() => null);
398
+ if (!response.ok) {
399
+ if (response.status === 404 || response.status === 403) {
400
+ throw new Error('Device code login is not enabled for this Dexto server');
401
+ }
402
+ if (response.status === 429) {
403
+ return { status: 'slowDown' };
404
+ }
405
+ const errorCode = parseDeviceErrorCode(payload);
406
+ if (errorCode === 'authorization_pending') {
407
+ return { status: 'pending' };
408
+ }
409
+ if (errorCode === 'slow_down') {
410
+ return { status: 'slowDown' };
411
+ }
412
+ if (errorCode === 'expired_token') {
413
+ return { status: 'expired' };
414
+ }
415
+ if (errorCode === 'access_denied') {
416
+ return { status: 'denied' };
417
+ }
418
+ if (errorCode && isTransientDevicePollErrorCode(errorCode)) {
419
+ return { status: 'transientError' };
420
+ }
421
+ if (response.status >= 500) {
422
+ return { status: 'transientError' };
423
+ }
424
+ if (errorCode) {
425
+ throw new Error(`Device login failed: ${errorCode}`);
426
+ }
427
+ throw new Error(`Device login polling failed with status ${response.status}`);
428
+ }
429
+ const token = parseDeviceCodeTokenResponse(payload);
430
+ if (token) {
431
+ return {
432
+ status: 'success',
433
+ token,
434
+ };
435
+ }
436
+ const errorCode = parseDeviceErrorCode(payload);
437
+ if (errorCode === 'authorization_pending') {
438
+ return { status: 'pending' };
439
+ }
440
+ if (errorCode === 'slow_down') {
441
+ return { status: 'slowDown' };
442
+ }
443
+ if (errorCode === 'expired_token') {
444
+ return { status: 'expired' };
445
+ }
446
+ if (errorCode === 'access_denied') {
447
+ return { status: 'denied' };
448
+ }
449
+ if (errorCode && isTransientDevicePollErrorCode(errorCode)) {
450
+ return { status: 'transientError' };
451
+ }
452
+ return { status: 'pending' };
453
+ }
454
+ /**
455
+ * Fetch Supabase user profile for a bearer token.
456
+ */
457
+ async fetchSupabaseUser(accessToken, options = {}) {
458
+ try {
459
+ const response = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
460
+ headers: {
461
+ Authorization: `Bearer ${accessToken}`,
462
+ apikey: SUPABASE_ANON_KEY,
463
+ 'User-Agent': 'dexto-cli/1.0.0',
464
+ },
465
+ signal: this.createRequestSignal(options.signal),
466
+ });
467
+ if (!response.ok) {
468
+ return undefined;
469
+ }
470
+ const payload = await response.json();
471
+ return parseSupabaseUser(payload) ?? undefined;
472
+ }
473
+ catch (error) {
474
+ logger.debug(`Failed to fetch Supabase user profile: ${error instanceof Error ? error.message : String(error)}`);
475
+ return undefined;
476
+ }
477
+ }
478
+ /**
479
+ * Validate Supabase access token by checking if /auth/v1/user resolves.
480
+ */
481
+ async validateSupabaseAccessToken(accessToken, options = {}) {
482
+ const user = await this.fetchSupabaseUser(accessToken, options);
483
+ return Boolean(user?.id);
484
+ }
121
485
  }
122
486
  /**
123
487
  * Get default Dexto API client
@@ -0,0 +1,6 @@
1
+ export interface BrowserLaunchContext {
2
+ env: NodeJS.ProcessEnv;
3
+ platform: NodeJS.Platform;
4
+ }
5
+ export declare function shouldAttemptBrowserLaunch(context?: BrowserLaunchContext): boolean;
6
+ //# sourceMappingURL=browser-launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-launch.d.ts","sourceRoot":"","sources":["../../../src/cli/auth/browser-launch.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACjC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC7B;AAKD,wBAAgB,0BAA0B,CACtC,OAAO,GAAE,oBAAuE,GACjF,OAAO,CAwBT"}