dexto 1.6.7 → 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.
- package/README.md +4 -4
- package/dist/analytics/wrapper.d.ts.map +1 -1
- package/dist/analytics/wrapper.js +43 -9
- package/dist/cli/auth/api-client.d.ts +50 -0
- package/dist/cli/auth/api-client.d.ts.map +1 -1
- package/dist/cli/auth/api-client.js +379 -15
- package/dist/cli/auth/browser-launch.d.ts +6 -0
- package/dist/cli/auth/browser-launch.d.ts.map +1 -0
- package/dist/cli/auth/browser-launch.js +24 -0
- package/dist/cli/auth/device.d.ts +14 -0
- package/dist/cli/auth/device.d.ts.map +1 -0
- package/dist/cli/auth/device.js +93 -0
- package/dist/cli/auth/index.d.ts +3 -1
- package/dist/cli/auth/index.d.ts.map +1 -1
- package/dist/cli/auth/index.js +2 -1
- package/dist/cli/auth/login-persistence.d.ts +13 -0
- package/dist/cli/auth/login-persistence.d.ts.map +1 -0
- package/dist/cli/auth/login-persistence.js +22 -0
- package/dist/cli/auth/oauth.d.ts +4 -1
- package/dist/cli/auth/oauth.d.ts.map +1 -1
- package/dist/cli/auth/oauth.js +6 -2
- package/dist/cli/auth/types.d.ts +12 -0
- package/dist/cli/auth/types.d.ts.map +1 -0
- package/dist/cli/auth/types.js +1 -0
- package/dist/cli/commands/agents/register.d.ts +6 -0
- package/dist/cli/commands/agents/register.d.ts.map +1 -0
- package/dist/cli/commands/agents/register.js +85 -0
- package/dist/cli/commands/auth/index.d.ts +1 -1
- package/dist/cli/commands/auth/index.d.ts.map +1 -1
- package/dist/cli/commands/auth/index.js +1 -1
- package/dist/cli/commands/auth/login.d.ts +6 -6
- package/dist/cli/commands/auth/login.d.ts.map +1 -1
- package/dist/cli/commands/auth/login.js +85 -115
- package/dist/cli/commands/auth/logout.d.ts.map +1 -1
- package/dist/cli/commands/auth/logout.js +32 -2
- package/dist/cli/commands/auth/register.d.ts +3 -0
- package/dist/cli/commands/auth/register.d.ts.map +1 -0
- package/dist/cli/commands/auth/register.js +94 -0
- package/dist/cli/commands/billing/register.d.ts +3 -0
- package/dist/cli/commands/billing/register.d.ts.map +1 -0
- package/dist/cli/commands/billing/register.js +20 -0
- package/dist/cli/commands/helpers/formatters.d.ts.map +1 -1
- package/dist/cli/commands/helpers/formatters.js +9 -0
- package/dist/cli/commands/image/register.d.ts +6 -0
- package/dist/cli/commands/image/register.d.ts.map +1 -0
- package/dist/cli/commands/image/register.js +144 -0
- package/dist/cli/commands/install.d.ts +2 -2
- package/dist/cli/commands/list-agents.d.ts.map +1 -1
- package/dist/cli/commands/list-agents.js +3 -3
- package/dist/cli/commands/mcp/register.d.ts +6 -0
- package/dist/cli/commands/mcp/register.d.ts.map +1 -0
- package/dist/cli/commands/mcp/register.js +64 -0
- package/dist/cli/commands/plugin/register.d.ts +6 -0
- package/dist/cli/commands/plugin/register.d.ts.map +1 -0
- package/dist/cli/commands/plugin/register.js +183 -0
- package/dist/cli/commands/plugin.d.ts +4 -4
- package/dist/cli/commands/register-context.d.ts +12 -0
- package/dist/cli/commands/register-context.d.ts.map +1 -0
- package/dist/cli/commands/register-context.js +1 -0
- package/dist/cli/commands/run/headless.d.ts +20 -0
- package/dist/cli/commands/run/headless.d.ts.map +1 -0
- package/dist/cli/commands/run/headless.js +275 -0
- package/dist/cli/commands/run/register.d.ts +3 -0
- package/dist/cli/commands/run/register.d.ts.map +1 -0
- package/dist/cli/commands/run/register.js +78 -0
- package/dist/cli/commands/search/register.d.ts +3 -0
- package/dist/cli/commands/search/register.d.ts.map +1 -0
- package/dist/cli/commands/search/register.js +55 -0
- package/dist/cli/commands/session/register.d.ts +3 -0
- package/dist/cli/commands/session/register.d.ts.map +1 -0
- package/dist/cli/commands/session/register.js +75 -0
- package/dist/cli/commands/setup.js +4 -4
- package/dist/cli/commands/sync-agents.d.ts +3 -3
- package/dist/cli/commands/sync-agents.js +4 -4
- package/dist/cli/commands/uninstall.d.ts +2 -2
- package/dist/cli/modes/cli.d.ts +3 -0
- package/dist/cli/modes/cli.d.ts.map +1 -0
- package/dist/cli/modes/cli.js +170 -0
- package/dist/cli/modes/context.d.ts +20 -0
- package/dist/cli/modes/context.d.ts.map +1 -0
- package/dist/cli/modes/context.js +1 -0
- package/dist/cli/modes/dispatch.d.ts +3 -0
- package/dist/cli/modes/dispatch.d.ts.map +1 -0
- package/dist/cli/modes/dispatch.js +52 -0
- package/dist/cli/modes/mcp.d.ts +3 -0
- package/dist/cli/modes/mcp.d.ts.map +1 -0
- package/dist/cli/modes/mcp.js +23 -0
- package/dist/cli/modes/server.d.ts +3 -0
- package/dist/cli/modes/server.d.ts.map +1 -0
- package/dist/cli/modes/server.js +36 -0
- package/dist/cli/modes/web.d.ts +3 -0
- package/dist/cli/modes/web.d.ts.map +1 -0
- package/dist/cli/modes/web.js +50 -0
- package/dist/cli/utils/setup-utils.js +1 -1
- package/dist/index-main.js +150 -991
- package/dist/utils/port-utils.d.ts +1 -1
- package/dist/utils/port-utils.d.ts.map +1 -1
- package/dist/utils/port-utils.js +7 -3
- package/dist/webui/assets/index-Bn9YuTdA.css +1 -0
- package/dist/webui/assets/index-CNiOYnOb.js +2059 -0
- package/dist/webui/index.html +2 -2
- package/package.json +12 -12
- package/dist/webui/assets/index-d6c-yJNn.js +0 -2059
- 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
|
|
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...>
|
|
610
|
-
list
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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":"
|
|
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
|
|
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;
|
|
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:
|
|
260
|
+
signal: this.createRequestSignal(undefined),
|
|
32
261
|
});
|
|
33
262
|
if (!response.ok) {
|
|
34
263
|
return false;
|
|
35
264
|
}
|
|
36
|
-
const
|
|
37
|
-
return
|
|
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:
|
|
287
|
+
signal: this.createRequestSignal(undefined),
|
|
59
288
|
});
|
|
60
289
|
if (!response.ok) {
|
|
61
|
-
const
|
|
62
|
-
throw new Error(`API request failed: ${response.status
|
|
290
|
+
const rawText = await response.text();
|
|
291
|
+
throw new Error(`API request failed: ${formatHttpFailure(response.status, null, rawText)}`);
|
|
63
292
|
}
|
|
64
|
-
const
|
|
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:
|
|
337
|
+
signal: this.createRequestSignal(undefined),
|
|
108
338
|
});
|
|
109
339
|
if (!response.ok) {
|
|
110
|
-
const
|
|
111
|
-
throw new Error(`API request failed: ${response.status
|
|
340
|
+
const rawText = await response.text();
|
|
341
|
+
throw new Error(`API request failed: ${formatHttpFailure(response.status, null, rawText)}`);
|
|
112
342
|
}
|
|
113
|
-
const
|
|
114
|
-
return
|
|
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 @@
|
|
|
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"}
|