@eterna-hybrid-exchange/cli 0.1.2 → 0.2.1
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 +10 -0
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,16 @@ Config is stored in `~/.eterna/config.json`:
|
|
|
67
67
|
|
|
68
68
|
Override the config directory with the `ETERNA_CONFIG_DIR` environment variable.
|
|
69
69
|
|
|
70
|
+
## Environment Variables
|
|
71
|
+
|
|
72
|
+
| Variable | Description | Default |
|
|
73
|
+
| -------------------- | --------------------------------------------------------------------------------------------- | --------------------------------- |
|
|
74
|
+
| `ETERNA_ENDPOINT` | API endpoint URL | `https://ai-api.eterna.exchange` |
|
|
75
|
+
| `ETERNA_AUTH_ISSUER` | OAuth authorization server URL | `https://ai-auth.eterna.exchange` |
|
|
76
|
+
| `ETERNA_MCP_URL` | MCP gateway URL | `https://mcp.eterna.exchange` |
|
|
77
|
+
| `ETERNA_CONFIG_DIR` | Config/credentials directory | `~/.eterna` |
|
|
78
|
+
| `ETERNA_MCP_KEY` | Legacy mcp-gateway API key — if set, `eterna login` links it to your OAuth identity automatically, preserving your existing Bybit subaccount | _(none)_ |
|
|
79
|
+
|
|
70
80
|
## License
|
|
71
81
|
|
|
72
82
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,11 @@ function getConfigDir() {
|
|
|
32
32
|
function getAuthIssuer() {
|
|
33
33
|
return process.env.ETERNA_AUTH_ISSUER ?? AUTH_ISSUER;
|
|
34
34
|
}
|
|
35
|
+
function getMcpEndpoint() {
|
|
36
|
+
if (process.env.ETERNA_MCP_URL) return process.env.ETERNA_MCP_URL;
|
|
37
|
+
const endpoint = process.env.ETERNA_ENDPOINT ?? DEFAULT_ENDPOINT;
|
|
38
|
+
return endpoint.replace("ai-api.", "mcp.");
|
|
39
|
+
}
|
|
35
40
|
function ensureConfigDir() {
|
|
36
41
|
const dir = getConfigDir();
|
|
37
42
|
if (!fs.existsSync(dir)) {
|
|
@@ -39,6 +44,9 @@ function ensureConfigDir() {
|
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
function readConfig() {
|
|
47
|
+
if (process.env.ETERNA_ENDPOINT) {
|
|
48
|
+
return { endpoint: process.env.ETERNA_ENDPOINT };
|
|
49
|
+
}
|
|
42
50
|
const configPath = path.join(getConfigDir(), "config.json");
|
|
43
51
|
try {
|
|
44
52
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
@@ -302,6 +310,46 @@ var TokenManager = class {
|
|
|
302
310
|
}
|
|
303
311
|
};
|
|
304
312
|
|
|
313
|
+
// src/auth/link-legacy-key.ts
|
|
314
|
+
async function linkLegacyKey(accessToken, legacyApiKey) {
|
|
315
|
+
const mcpEndpoint = getMcpEndpoint();
|
|
316
|
+
let res;
|
|
317
|
+
try {
|
|
318
|
+
res = await fetch(`${mcpEndpoint}/migrate/link-legacy-key`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: {
|
|
321
|
+
Authorization: `Bearer ${accessToken}`,
|
|
322
|
+
"Content-Type": "application/json"
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({ legacyApiKey })
|
|
325
|
+
});
|
|
326
|
+
} catch {
|
|
327
|
+
console.warn(
|
|
328
|
+
"Warning: Could not reach mcp-gateway to link legacy key \u2014 your existing Bybit subaccount may not be preserved."
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (res.ok) {
|
|
333
|
+
const data = await res.json();
|
|
334
|
+
if (data.linked) {
|
|
335
|
+
console.log(
|
|
336
|
+
`\u2713 Legacy account linked \u2014 existing Bybit subaccount preserved`
|
|
337
|
+
);
|
|
338
|
+
} else {
|
|
339
|
+
console.log(`\u2713 Account already linked`);
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (res.status === 409) {
|
|
344
|
+
console.warn(
|
|
345
|
+
"Warning: Legacy key is already linked to a different OAuth account"
|
|
346
|
+
);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
350
|
+
console.warn(`Warning: Could not link legacy key (${res.status}): ${errText}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
305
353
|
// src/util/spinner.ts
|
|
306
354
|
import ora from "ora";
|
|
307
355
|
var activeSpinner = null;
|
|
@@ -354,6 +402,10 @@ async function loginWithLocalhost(resource, tokenManager) {
|
|
|
354
402
|
resource
|
|
355
403
|
});
|
|
356
404
|
console.log("Logged in successfully!");
|
|
405
|
+
const legacyKey = process.env.ETERNA_MCP_KEY;
|
|
406
|
+
if (legacyKey) {
|
|
407
|
+
await linkLegacyKey(tokens.access_token, legacyKey);
|
|
408
|
+
}
|
|
357
409
|
} catch (err) {
|
|
358
410
|
stopSpinner(false, "Authentication failed");
|
|
359
411
|
console.error(err.message);
|
|
@@ -382,6 +434,10 @@ async function loginWithDeviceCode(resource, tokenManager) {
|
|
|
382
434
|
resource
|
|
383
435
|
});
|
|
384
436
|
console.log("Logged in successfully!");
|
|
437
|
+
const legacyKey = process.env.ETERNA_MCP_KEY;
|
|
438
|
+
if (legacyKey) {
|
|
439
|
+
await linkLegacyKey(tokens.access_token, legacyKey);
|
|
440
|
+
}
|
|
385
441
|
} catch (err) {
|
|
386
442
|
console.error(`Authentication failed: ${err.message}`);
|
|
387
443
|
process.exitCode = 1;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/login.ts","../src/auth/detect.ts","../src/auth/localhost-flow.ts","../src/auth/config.ts","../src/auth/device-flow.ts","../src/auth/token-manager.ts","../src/util/spinner.ts","../src/commands/logout.ts","../src/commands/execute.ts","../src/api/client.ts","../src/util/format.ts","../src/commands/sdk.ts","../src/commands/balance.ts","../src/commands/positions.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { loginCommand } from \"./commands/login.js\";\nimport { logoutCommand } from \"./commands/logout.js\";\nimport { executeCommand } from \"./commands/execute.js\";\nimport { sdkCommand } from \"./commands/sdk.js\";\nimport { balanceCommand } from \"./commands/balance.js\";\nimport { positionsCommand } from \"./commands/positions.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"eterna\")\n .description(\"Eterna CLI — execute trading strategies from your terminal\")\n .version(\"0.1.0\");\n\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(executeCommand);\nprogram.addCommand(sdkCommand);\nprogram.addCommand(balanceCommand);\nprogram.addCommand(positionsCommand);\n\nprogram.parse();\n","import { Command } from \"commander\";\nimport { detectAuthFlow } from \"../auth/detect.js\";\nimport { localhostAuthFlow } from \"../auth/localhost-flow.js\";\nimport { deviceCodeFlow } from \"../auth/device-flow.js\";\nimport { TokenManager } from \"../auth/token-manager.js\";\nimport { readConfig } from \"../auth/config.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\n\nconst CLIENT_ID = \"eterna-cli\";\n\nexport const loginCommand = new Command(\"login\")\n .description(\"Authenticate with Eterna\")\n .option(\"--no-browser\", \"Use device code flow (no browser redirect)\")\n .action(async (opts: { browser: boolean }) => {\n const tokenManager = new TokenManager();\n const config = readConfig();\n const resource = config.endpoint;\n\n if (tokenManager.isAuthenticated() && !tokenManager.needsRefresh()) {\n console.log(\n \"Already authenticated. Use `eterna logout` first to re-authenticate.\",\n );\n return;\n }\n\n const flowType = detectAuthFlow(!opts.browser);\n\n if (flowType === \"localhost\") {\n await loginWithLocalhost(resource, tokenManager);\n } else {\n await loginWithDeviceCode(resource, tokenManager);\n }\n });\n\nasync function loginWithLocalhost(\n resource: string,\n tokenManager: TokenManager,\n): Promise<void> {\n startSpinner(\"Opening browser for authentication...\");\n\n try {\n stopSpinner(undefined);\n console.log(\"Waiting for authorization in browser...\\n\");\n\n const tokens = await localhostAuthFlow(resource);\n\n tokenManager.saveTokens({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresIn: tokens.expires_in,\n clientId: CLIENT_ID,\n resource,\n });\n\n console.log(\"Logged in successfully!\");\n } catch (err) {\n stopSpinner(false, \"Authentication failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n}\n\nasync function loginWithDeviceCode(\n resource: string,\n tokenManager: TokenManager,\n): Promise<void> {\n try {\n const tokens = await deviceCodeFlow(resource, {\n onUserCode: (userCode, _verificationUri, verificationUriComplete) => {\n console.log(\"\");\n console.log(\" To authenticate, visit:\");\n console.log(` ${verificationUriComplete}`);\n console.log(\"\");\n console.log(` Or go to the URL above and enter code: ${userCode}`);\n console.log(\"\");\n },\n onPolling: () => {\n // Could show a spinner dot, but keeping it quiet\n },\n });\n\n tokenManager.saveTokens({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresIn: tokens.expires_in,\n clientId: CLIENT_ID,\n resource,\n });\n\n console.log(\"Logged in successfully!\");\n } catch (err) {\n console.error(`Authentication failed: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n}\n","export type AuthFlowType = \"localhost\" | \"device\";\n\nexport function detectAuthFlow(noBrowser: boolean): AuthFlowType {\n if (noBrowser) return \"device\";\n if (process.env.SSH_CLIENT || process.env.SSH_TTY) return \"device\";\n return \"localhost\";\n}\n","import {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { randomBytes, createHash } from \"node:crypto\";\nimport open from \"open\";\nimport { getAuthIssuer } from \"./config.js\";\n\nconst CLIENT_ID = \"eterna-cli\";\nconst CALLBACK_PORT = 9876;\nconst REDIRECT_URI = `http://127.0.0.1:${CALLBACK_PORT}/callback`;\nconst SCOPE = \"mcp:full\";\n\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport async function computeCodeChallenge(verifier: string): Promise<string> {\n const hash = createHash(\"sha256\").update(verifier).digest();\n return hash.toString(\"base64url\");\n}\n\ninterface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\nfunction buildAuthorizationUrl(\n codeChallenge: string,\n state: string,\n resource: string,\n): string {\n const issuer = getAuthIssuer();\n const params = new URLSearchParams({\n response_type: \"code\",\n client_id: CLIENT_ID,\n redirect_uri: REDIRECT_URI,\n scope: SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n resource,\n });\n return `${issuer}/oauth/authorize?${params.toString()}`;\n}\n\nexport async function localhostAuthFlow(\n resource: string,\n): Promise<TokenResponse> {\n const issuer = getAuthIssuer();\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await computeCodeChallenge(codeVerifier);\n const state = randomBytes(16).toString(\"hex\");\n\n // Build auth URL and start callback server before opening browser\n const authUrl = buildAuthorizationUrl(codeChallenge, state, resource);\n\n const { code, receivedState } = await new Promise<{\n code: string;\n receivedState: string;\n }>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", `http://127.0.0.1:${CALLBACK_PORT}`);\n\n if (url.pathname === \"/callback\") {\n const code = url.searchParams.get(\"code\");\n const receivedState = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>\",\n );\n server.close();\n reject(new Error(`OAuth error: ${error}`));\n return;\n }\n\n if (!code || !receivedState) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\"<html><body><h1>Missing parameters</h1></body></html>\");\n server.close();\n reject(new Error(\"Missing code or state in callback\"));\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorized!</h1><p>You can close this window and return to your terminal.</p></body></html>\",\n );\n server.close();\n resolve({ code, receivedState });\n }\n });\n\n server.listen(CALLBACK_PORT, \"127.0.0.1\", () => {\n // Server ready — open browser with the same PKCE state\n open(authUrl).catch(() => {\n server.close();\n reject(new Error(\"Failed to open browser\"));\n });\n });\n\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authorization timed out after 5 minutes\"));\n }, 300_000);\n });\n\n if (receivedState !== state) {\n throw new Error(\"State mismatch — possible CSRF attack\");\n }\n\n const tokenUrl = `${issuer}/api/oauth/token`;\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: CLIENT_ID,\n redirect_uri: REDIRECT_URI,\n code_verifier: codeVerifier,\n resource,\n });\n\n const tokenRes = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n `Token exchange failed: ${(err as Record<string, string>).error_description ?? tokenRes.statusText}`,\n );\n }\n\n return tokenRes.json() as Promise<TokenResponse>;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\n\nconst DEFAULT_ENDPOINT = \"https://ai-api.eterna.exchange\";\nconst AUTH_ISSUER = \"https://ai-auth.eterna.exchange\";\n\nexport interface EternaConfig {\n endpoint: string;\n}\n\nexport interface EternaCredentials {\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix timestamp in milliseconds\n clientId: string;\n resource: string;\n}\n\nexport function getConfigDir(): string {\n return process.env.ETERNA_CONFIG_DIR ?? path.join(os.homedir(), \".eterna\");\n}\n\nexport function getAuthIssuer(): string {\n return process.env.ETERNA_AUTH_ISSUER ?? AUTH_ISSUER;\n}\n\nfunction ensureConfigDir(): void {\n const dir = getConfigDir();\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n}\n\nexport function readConfig(): EternaConfig {\n const configPath = path.join(getConfigDir(), \"config.json\");\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<EternaConfig>;\n return {\n endpoint: parsed.endpoint ?? DEFAULT_ENDPOINT,\n };\n } catch {\n return { endpoint: DEFAULT_ENDPOINT };\n }\n}\n\nexport function writeConfig(config: Partial<EternaConfig>): void {\n ensureConfigDir();\n const existing = readConfig();\n const merged = { ...existing, ...config };\n const configPath = path.join(getConfigDir(), \"config.json\");\n fs.writeFileSync(configPath, JSON.stringify(merged, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n}\n\nexport function readCredentials(): EternaCredentials | null {\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n try {\n const raw = fs.readFileSync(credPath, \"utf-8\");\n const parsed = JSON.parse(raw) as EternaCredentials;\n if (!parsed.accessToken || !parsed.refreshToken) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport function writeCredentials(creds: EternaCredentials): void {\n ensureConfigDir();\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n fs.writeFileSync(credPath, JSON.stringify(creds, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n}\n\nexport function clearConfig(): void {\n const configPath = path.join(getConfigDir(), \"config.json\");\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n try {\n fs.unlinkSync(configPath);\n } catch {\n /* ignore */\n }\n try {\n fs.unlinkSync(credPath);\n } catch {\n /* ignore */\n }\n}\n\nexport function clearCredentials(): void {\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n try {\n fs.unlinkSync(credPath);\n } catch {\n /* ignore */\n }\n}\n","// src/auth/device-flow.ts\nimport { getAuthIssuer } from \"./config.js\";\n\nconst CLIENT_ID = \"eterna-cli\";\nconst SCOPE = \"mcp:full\";\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\ninterface DeviceFlowCallbacks {\n onUserCode: (\n userCode: string,\n verificationUri: string,\n verificationUriComplete: string,\n ) => void;\n onPolling: () => void;\n}\n\nexport async function deviceCodeFlow(\n resource: string,\n callbacks: DeviceFlowCallbacks,\n): Promise<TokenResponse> {\n const issuer = getAuthIssuer();\n\n const deviceRes = await fetch(`${issuer}/api/oauth/device/code`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_id: CLIENT_ID,\n scope: SCOPE,\n resource,\n }),\n });\n\n if (!deviceRes.ok) {\n const err = await deviceRes.json().catch(() => ({}));\n throw new Error(\n `Device code request failed: ${(err as Record<string, string>).error_description ?? deviceRes.statusText}`,\n );\n }\n\n const deviceData = (await deviceRes.json()) as DeviceCodeResponse;\n\n callbacks.onUserCode(\n deviceData.user_code,\n deviceData.verification_uri,\n deviceData.verification_uri_complete,\n );\n\n let interval = deviceData.interval * 1000;\n const deadline = Date.now() + deviceData.expires_in * 1000;\n\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, interval));\n callbacks.onPolling();\n\n const tokenRes = await fetch(`${issuer}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n device_code: deviceData.device_code,\n client_id: CLIENT_ID,\n resource,\n }).toString(),\n });\n\n if (tokenRes.ok) {\n return tokenRes.json() as Promise<TokenResponse>;\n }\n\n const err = (await tokenRes.json()) as {\n error: string;\n error_description?: string;\n };\n\n switch (err.error) {\n case \"authorization_pending\":\n continue;\n case \"slow_down\":\n interval += 5000;\n continue;\n case \"expired_token\":\n throw new Error(\"Device code expired. Please try again.\");\n case \"access_denied\":\n throw new Error(\"Authorization denied by user.\");\n default:\n throw new Error(\n `Unexpected error: ${err.error_description ?? err.error}`,\n );\n }\n }\n\n throw new Error(\"Device code expired. Please try again.\");\n}\n","import {\n readCredentials,\n writeCredentials,\n clearCredentials,\n getAuthIssuer,\n} from \"./config.js\";\n\nconst REFRESH_BUFFER_MS = 60_000;\n\nexport interface SaveTokenParams {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n clientId: string;\n resource: string;\n}\n\nexport class TokenManager {\n isAuthenticated(): boolean {\n const creds = readCredentials();\n return creds !== null;\n }\n\n getAccessToken(): string | null {\n const creds = readCredentials();\n if (!creds) return null;\n return creds.accessToken;\n }\n\n needsRefresh(): boolean {\n const creds = readCredentials();\n if (!creds) return false;\n return Date.now() >= creds.expiresAt - REFRESH_BUFFER_MS;\n }\n\n saveTokens(params: SaveTokenParams): void {\n writeCredentials({\n accessToken: params.accessToken,\n refreshToken: params.refreshToken,\n expiresAt: Date.now() + params.expiresIn * 1000,\n clientId: params.clientId,\n resource: params.resource,\n });\n }\n\n async refreshIfNeeded(): Promise<string | null> {\n const creds = readCredentials();\n if (!creds) return null;\n\n if (!this.needsRefresh()) {\n return creds.accessToken;\n }\n\n const issuer = getAuthIssuer();\n const res = await fetch(`${issuer}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: creds.refreshToken,\n client_id: creds.clientId,\n resource: creds.resource,\n }).toString(),\n });\n\n if (!res.ok) {\n clearCredentials();\n return null;\n }\n\n const data = (await res.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n this.saveTokens({\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in,\n clientId: creds.clientId,\n resource: creds.resource,\n });\n\n return data.access_token;\n }\n\n logout(): void {\n clearCredentials();\n }\n}\n","import ora, { type Ora } from \"ora\";\n\nlet activeSpinner: Ora | null = null;\n\nexport function startSpinner(text: string): Ora {\n activeSpinner = ora(text).start();\n return activeSpinner;\n}\n\nexport function stopSpinner(success?: boolean, text?: string): void {\n if (!activeSpinner) return;\n if (success === true) {\n activeSpinner.succeed(text);\n } else if (success === false) {\n activeSpinner.fail(text);\n } else {\n activeSpinner.stop();\n }\n activeSpinner = null;\n}\n","import { Command } from \"commander\";\nimport { TokenManager } from \"../auth/token-manager.js\";\n\nexport const logoutCommand = new Command(\"logout\")\n .description(\"Remove stored credentials\")\n .action(() => {\n const tokenManager = new TokenManager();\n\n if (!tokenManager.isAuthenticated()) {\n console.log(\"Not currently authenticated.\");\n return;\n }\n\n tokenManager.logout();\n console.log(\"Logged out. Credentials removed from ~/.eterna/\");\n });\n","import { Command } from \"commander\";\nimport * as fs from \"node:fs\";\nimport { ApiClient } from \"../api/client.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\nimport { formatExecutionResult } from \"../util/format.js\";\n\nexport function readCodeFromFile(filePath: string): string {\n if (!fs.existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n return fs.readFileSync(filePath, \"utf-8\");\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk as Buffer);\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n\nexport const executeCommand = new Command(\"execute\")\n .description(\"Execute trading code in the Eterna sandbox\")\n .argument(\"[file]\", \"TypeScript file to execute, or - for stdin\")\n .action(async (file?: string) => {\n let code: string;\n\n if (file === \"-\" || (!file && !process.stdin.isTTY)) {\n code = await readStdin();\n } else if (file) {\n code = await readCodeFromFile(file);\n } else {\n console.error(\"Usage: eterna execute <file.ts> or pipe code via stdin\");\n process.exitCode = 1;\n return;\n }\n\n if (!code.trim()) {\n console.error(\"Error: empty code input\");\n process.exitCode = 1;\n return;\n }\n\n const client = new ApiClient();\n startSpinner(\"Executing...\");\n\n try {\n const result = await client.execute(code);\n stopSpinner(result.success, result.success ? \"Done\" : \"Execution failed\");\n console.log(formatExecutionResult(result));\n if (!result.success) process.exitCode = 1;\n } catch (err) {\n stopSpinner(false, \"Failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n","import { readConfig } from \"../auth/config.js\";\nimport { TokenManager } from \"../auth/token-manager.js\";\n\nexport interface ExecutionResult {\n success: boolean;\n result: unknown;\n error?: string;\n logs: string[];\n stats: {\n durationMs: number;\n apiCallsMade: number;\n };\n validationErrors?: Array<{ field: string; message: string }>;\n}\n\nexport interface SdkSearchResult {\n text: string;\n}\n\nexport class ApiClient {\n private tokenManager = new TokenManager();\n\n private async getAuthHeaders(): Promise<Record<string, string>> {\n let token = this.tokenManager.getAccessToken();\n\n if (!token || this.tokenManager.needsRefresh()) {\n token = await this.tokenManager.refreshIfNeeded();\n }\n\n if (!token) {\n throw new Error(\"Not authenticated. Run `eterna login` first.\");\n }\n\n return { Authorization: `Bearer ${token}` };\n }\n\n private getBaseUrl(): string {\n return readConfig().endpoint;\n }\n\n async execute(code: string): Promise<ExecutionResult> {\n const headers = await this.getAuthHeaders();\n const res = await fetch(`${this.getBaseUrl()}/sandbox/execute`, {\n method: \"POST\",\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code }),\n });\n\n if (res.status === 401) {\n const newToken = await this.tokenManager.refreshIfNeeded();\n if (!newToken) {\n throw new Error(\n \"Session expired. Run `eterna login` to re-authenticate.\",\n );\n }\n const retryRes = await fetch(`${this.getBaseUrl()}/sandbox/execute`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${newToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ code }),\n });\n if (!retryRes.ok) {\n throw new Error(`Execution failed: ${retryRes.statusText}`);\n }\n return retryRes.json() as Promise<ExecutionResult>;\n }\n\n if (!res.ok) {\n const errBody = await res.text();\n throw new Error(`Execution failed (${res.status}): ${errBody}`);\n }\n\n return res.json() as Promise<ExecutionResult>;\n }\n\n async sdkSearch(\n query?: string,\n detailLevel: string = \"summary\",\n ): Promise<SdkSearchResult> {\n const params = new URLSearchParams({ detail_level: detailLevel });\n if (query) params.set(\"query\", query);\n\n const res = await fetch(\n `${this.getBaseUrl()}/sdk/search?${params.toString()}`,\n );\n\n if (!res.ok) {\n throw new Error(`SDK search failed (${res.status}): ${res.statusText}`);\n }\n\n return res.json() as Promise<SdkSearchResult>;\n }\n}\n","import type { ExecutionResult } from \"../api/client.js\";\n\nexport function formatExecutionResult(result: ExecutionResult): string {\n const lines: string[] = [];\n\n if (result.success) {\n if (result.result !== undefined && result.result !== null) {\n lines.push(\n typeof result.result === \"string\"\n ? result.result\n : JSON.stringify(result.result, null, 2),\n );\n }\n } else {\n lines.push(`Error: ${result.error ?? \"Unknown error\"}`);\n }\n\n if (result.logs.length > 0) {\n lines.push(\"\");\n lines.push(\"--- Logs ---\");\n lines.push(...result.logs);\n }\n\n if (result.validationErrors && result.validationErrors.length > 0) {\n lines.push(\"\");\n lines.push(\"--- Validation Errors ---\");\n for (const ve of result.validationErrors) {\n lines.push(` ${ve.field}: ${ve.message}`);\n }\n }\n\n lines.push(\"\");\n lines.push(\n `(${result.stats.durationMs}ms, ${result.stats.apiCallsMade} API calls)`,\n );\n\n return lines.join(\"\\n\");\n}\n\nexport function formatJson(data: unknown): string {\n return JSON.stringify(data, null, 2);\n}\n","import { Command } from \"commander\";\nimport { ApiClient } from \"../api/client.js\";\n\nexport const sdkCommand = new Command(\"sdk\")\n .description(\"Browse the Eterna SDK reference\")\n .option(\"--search <query>\", \"Search for SDK methods by keyword\")\n .option(\n \"--detail <level>\",\n \"Detail level: list, summary, full, params, keywords\",\n \"summary\",\n )\n .action(async (opts: { search?: string; detail: string }) => {\n const client = new ApiClient();\n\n try {\n const result = await client.sdkSearch(opts.search, opts.detail);\n console.log(result.text);\n } catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n","import { Command } from \"commander\";\nimport { ApiClient } from \"../api/client.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\nimport { formatExecutionResult } from \"../util/format.js\";\n\nexport const balanceCommand = new Command(\"balance\")\n .description(\"Get your account balance\")\n .action(async () => {\n const client = new ApiClient();\n startSpinner(\"Fetching balance...\");\n\n try {\n const result = await client.execute(\n \"const balance = await eterna.getBalance();\\nreturn balance;\",\n );\n stopSpinner(result.success, result.success ? \"Done\" : \"Failed\");\n console.log(formatExecutionResult(result));\n if (!result.success) process.exitCode = 1;\n } catch (err) {\n stopSpinner(false, \"Failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n","import { Command } from \"commander\";\nimport { ApiClient } from \"../api/client.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\nimport { formatExecutionResult } from \"../util/format.js\";\n\nexport const positionsCommand = new Command(\"positions\")\n .description(\"Get your open positions\")\n .action(async () => {\n const client = new ApiClient();\n startSpinner(\"Fetching positions...\");\n\n try {\n const result = await client.execute(\n \"const positions = await eterna.getPositions();\\nreturn positions;\",\n );\n stopSpinner(result.success, result.success ? \"Done\" : \"Failed\");\n console.log(formatExecutionResult(result));\n if (!result.success) process.exitCode = 1;\n } catch (err) {\n stopSpinner(false, \"Failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n"],"mappings":"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","names":["Command","code","receivedState","CLIENT_ID","SCOPE","CLIENT_ID","Command","Command","Command","fs","Command","Command","Command","Command","Command","Command","Command","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/login.ts","../src/auth/detect.ts","../src/auth/localhost-flow.ts","../src/auth/config.ts","../src/auth/device-flow.ts","../src/auth/token-manager.ts","../src/auth/link-legacy-key.ts","../src/util/spinner.ts","../src/commands/logout.ts","../src/commands/execute.ts","../src/api/client.ts","../src/util/format.ts","../src/commands/sdk.ts","../src/commands/balance.ts","../src/commands/positions.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { loginCommand } from \"./commands/login.js\";\nimport { logoutCommand } from \"./commands/logout.js\";\nimport { executeCommand } from \"./commands/execute.js\";\nimport { sdkCommand } from \"./commands/sdk.js\";\nimport { balanceCommand } from \"./commands/balance.js\";\nimport { positionsCommand } from \"./commands/positions.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"eterna\")\n .description(\"Eterna CLI — execute trading strategies from your terminal\")\n .version(\"0.1.0\");\n\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(executeCommand);\nprogram.addCommand(sdkCommand);\nprogram.addCommand(balanceCommand);\nprogram.addCommand(positionsCommand);\n\nprogram.parse();\n","import { Command } from \"commander\";\nimport { detectAuthFlow } from \"../auth/detect.js\";\nimport { localhostAuthFlow } from \"../auth/localhost-flow.js\";\nimport { deviceCodeFlow } from \"../auth/device-flow.js\";\nimport { TokenManager } from \"../auth/token-manager.js\";\nimport { readConfig } from \"../auth/config.js\";\nimport { linkLegacyKey } from \"../auth/link-legacy-key.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\n\nconst CLIENT_ID = \"eterna-cli\";\n\nexport const loginCommand = new Command(\"login\")\n .description(\"Authenticate with Eterna\")\n .option(\"--no-browser\", \"Use device code flow (no browser redirect)\")\n .action(async (opts: { browser: boolean }) => {\n const tokenManager = new TokenManager();\n const config = readConfig();\n const resource = config.endpoint;\n\n if (tokenManager.isAuthenticated() && !tokenManager.needsRefresh()) {\n console.log(\n \"Already authenticated. Use `eterna logout` first to re-authenticate.\",\n );\n return;\n }\n\n const flowType = detectAuthFlow(!opts.browser);\n\n if (flowType === \"localhost\") {\n await loginWithLocalhost(resource, tokenManager);\n } else {\n await loginWithDeviceCode(resource, tokenManager);\n }\n });\n\nasync function loginWithLocalhost(\n resource: string,\n tokenManager: TokenManager,\n): Promise<void> {\n startSpinner(\"Opening browser for authentication...\");\n\n try {\n stopSpinner(undefined);\n console.log(\"Waiting for authorization in browser...\\n\");\n\n const tokens = await localhostAuthFlow(resource);\n\n tokenManager.saveTokens({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresIn: tokens.expires_in,\n clientId: CLIENT_ID,\n resource,\n });\n\n console.log(\"Logged in successfully!\");\n\n const legacyKey = process.env.ETERNA_MCP_KEY;\n if (legacyKey) {\n await linkLegacyKey(tokens.access_token, legacyKey);\n }\n } catch (err) {\n stopSpinner(false, \"Authentication failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n}\n\nasync function loginWithDeviceCode(\n resource: string,\n tokenManager: TokenManager,\n): Promise<void> {\n try {\n const tokens = await deviceCodeFlow(resource, {\n onUserCode: (userCode, _verificationUri, verificationUriComplete) => {\n console.log(\"\");\n console.log(\" To authenticate, visit:\");\n console.log(` ${verificationUriComplete}`);\n console.log(\"\");\n console.log(` Or go to the URL above and enter code: ${userCode}`);\n console.log(\"\");\n },\n onPolling: () => {\n // Could show a spinner dot, but keeping it quiet\n },\n });\n\n tokenManager.saveTokens({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresIn: tokens.expires_in,\n clientId: CLIENT_ID,\n resource,\n });\n\n console.log(\"Logged in successfully!\");\n\n const legacyKey = process.env.ETERNA_MCP_KEY;\n if (legacyKey) {\n await linkLegacyKey(tokens.access_token, legacyKey);\n }\n } catch (err) {\n console.error(`Authentication failed: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n}\n","export type AuthFlowType = \"localhost\" | \"device\";\n\nexport function detectAuthFlow(noBrowser: boolean): AuthFlowType {\n if (noBrowser) return \"device\";\n if (process.env.SSH_CLIENT || process.env.SSH_TTY) return \"device\";\n return \"localhost\";\n}\n","import {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { randomBytes, createHash } from \"node:crypto\";\nimport open from \"open\";\nimport { getAuthIssuer } from \"./config.js\";\n\nconst CLIENT_ID = \"eterna-cli\";\nconst CALLBACK_PORT = 9876;\nconst REDIRECT_URI = `http://127.0.0.1:${CALLBACK_PORT}/callback`;\nconst SCOPE = \"mcp:full\";\n\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport async function computeCodeChallenge(verifier: string): Promise<string> {\n const hash = createHash(\"sha256\").update(verifier).digest();\n return hash.toString(\"base64url\");\n}\n\ninterface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\nfunction buildAuthorizationUrl(\n codeChallenge: string,\n state: string,\n resource: string,\n): string {\n const issuer = getAuthIssuer();\n const params = new URLSearchParams({\n response_type: \"code\",\n client_id: CLIENT_ID,\n redirect_uri: REDIRECT_URI,\n scope: SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n resource,\n });\n return `${issuer}/oauth/authorize?${params.toString()}`;\n}\n\nexport async function localhostAuthFlow(\n resource: string,\n): Promise<TokenResponse> {\n const issuer = getAuthIssuer();\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await computeCodeChallenge(codeVerifier);\n const state = randomBytes(16).toString(\"hex\");\n\n // Build auth URL and start callback server before opening browser\n const authUrl = buildAuthorizationUrl(codeChallenge, state, resource);\n\n const { code, receivedState } = await new Promise<{\n code: string;\n receivedState: string;\n }>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", `http://127.0.0.1:${CALLBACK_PORT}`);\n\n if (url.pathname === \"/callback\") {\n const code = url.searchParams.get(\"code\");\n const receivedState = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>\",\n );\n server.close();\n reject(new Error(`OAuth error: ${error}`));\n return;\n }\n\n if (!code || !receivedState) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\"<html><body><h1>Missing parameters</h1></body></html>\");\n server.close();\n reject(new Error(\"Missing code or state in callback\"));\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorized!</h1><p>You can close this window and return to your terminal.</p></body></html>\",\n );\n server.close();\n resolve({ code, receivedState });\n }\n });\n\n server.listen(CALLBACK_PORT, \"127.0.0.1\", () => {\n // Server ready — open browser with the same PKCE state\n open(authUrl).catch(() => {\n server.close();\n reject(new Error(\"Failed to open browser\"));\n });\n });\n\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authorization timed out after 5 minutes\"));\n }, 300_000);\n });\n\n if (receivedState !== state) {\n throw new Error(\"State mismatch — possible CSRF attack\");\n }\n\n const tokenUrl = `${issuer}/api/oauth/token`;\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: CLIENT_ID,\n redirect_uri: REDIRECT_URI,\n code_verifier: codeVerifier,\n resource,\n });\n\n const tokenRes = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n `Token exchange failed: ${(err as Record<string, string>).error_description ?? tokenRes.statusText}`,\n );\n }\n\n return tokenRes.json() as Promise<TokenResponse>;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\n\nconst DEFAULT_ENDPOINT = \"https://ai-api.eterna.exchange\";\nconst AUTH_ISSUER = \"https://ai-auth.eterna.exchange\";\nconst DEFAULT_MCP_ENDPOINT = \"https://mcp.eterna.exchange\";\n\nexport interface EternaConfig {\n endpoint: string;\n}\n\nexport interface EternaCredentials {\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix timestamp in milliseconds\n clientId: string;\n resource: string;\n}\n\nexport function getConfigDir(): string {\n return process.env.ETERNA_CONFIG_DIR ?? path.join(os.homedir(), \".eterna\");\n}\n\nexport function getAuthIssuer(): string {\n return process.env.ETERNA_AUTH_ISSUER ?? AUTH_ISSUER;\n}\n\nexport function getMcpEndpoint(): string {\n if (process.env.ETERNA_MCP_URL) return process.env.ETERNA_MCP_URL;\n // Derive from ETERNA_ENDPOINT so staging users don't need a separate var.\n // https://ai-api.eterna.exchange → https://mcp.eterna.exchange\n // https://ai-api.staging.eterna.exchange → https://mcp.staging.eterna.exchange\n const endpoint = process.env.ETERNA_ENDPOINT ?? DEFAULT_ENDPOINT;\n return endpoint.replace(\"ai-api.\", \"mcp.\");\n}\n\nfunction ensureConfigDir(): void {\n const dir = getConfigDir();\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n}\n\nexport function readConfig(): EternaConfig {\n if (process.env.ETERNA_ENDPOINT) {\n return { endpoint: process.env.ETERNA_ENDPOINT };\n }\n const configPath = path.join(getConfigDir(), \"config.json\");\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<EternaConfig>;\n return {\n endpoint: parsed.endpoint ?? DEFAULT_ENDPOINT,\n };\n } catch {\n return { endpoint: DEFAULT_ENDPOINT };\n }\n}\n\nexport function writeConfig(config: Partial<EternaConfig>): void {\n ensureConfigDir();\n const existing = readConfig();\n const merged = { ...existing, ...config };\n const configPath = path.join(getConfigDir(), \"config.json\");\n fs.writeFileSync(configPath, JSON.stringify(merged, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n}\n\nexport function readCredentials(): EternaCredentials | null {\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n try {\n const raw = fs.readFileSync(credPath, \"utf-8\");\n const parsed = JSON.parse(raw) as EternaCredentials;\n if (!parsed.accessToken || !parsed.refreshToken) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport function writeCredentials(creds: EternaCredentials): void {\n ensureConfigDir();\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n fs.writeFileSync(credPath, JSON.stringify(creds, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n}\n\nexport function clearConfig(): void {\n const configPath = path.join(getConfigDir(), \"config.json\");\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n try {\n fs.unlinkSync(configPath);\n } catch {\n /* ignore */\n }\n try {\n fs.unlinkSync(credPath);\n } catch {\n /* ignore */\n }\n}\n\nexport function clearCredentials(): void {\n const credPath = path.join(getConfigDir(), \"credentials.json\");\n try {\n fs.unlinkSync(credPath);\n } catch {\n /* ignore */\n }\n}\n","// src/auth/device-flow.ts\nimport { getAuthIssuer } from \"./config.js\";\n\nconst CLIENT_ID = \"eterna-cli\";\nconst SCOPE = \"mcp:full\";\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\ninterface DeviceFlowCallbacks {\n onUserCode: (\n userCode: string,\n verificationUri: string,\n verificationUriComplete: string,\n ) => void;\n onPolling: () => void;\n}\n\nexport async function deviceCodeFlow(\n resource: string,\n callbacks: DeviceFlowCallbacks,\n): Promise<TokenResponse> {\n const issuer = getAuthIssuer();\n\n const deviceRes = await fetch(`${issuer}/api/oauth/device/code`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_id: CLIENT_ID,\n scope: SCOPE,\n resource,\n }),\n });\n\n if (!deviceRes.ok) {\n const err = await deviceRes.json().catch(() => ({}));\n throw new Error(\n `Device code request failed: ${(err as Record<string, string>).error_description ?? deviceRes.statusText}`,\n );\n }\n\n const deviceData = (await deviceRes.json()) as DeviceCodeResponse;\n\n callbacks.onUserCode(\n deviceData.user_code,\n deviceData.verification_uri,\n deviceData.verification_uri_complete,\n );\n\n let interval = deviceData.interval * 1000;\n const deadline = Date.now() + deviceData.expires_in * 1000;\n\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, interval));\n callbacks.onPolling();\n\n const tokenRes = await fetch(`${issuer}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n device_code: deviceData.device_code,\n client_id: CLIENT_ID,\n resource,\n }).toString(),\n });\n\n if (tokenRes.ok) {\n return tokenRes.json() as Promise<TokenResponse>;\n }\n\n const err = (await tokenRes.json()) as {\n error: string;\n error_description?: string;\n };\n\n switch (err.error) {\n case \"authorization_pending\":\n continue;\n case \"slow_down\":\n interval += 5000;\n continue;\n case \"expired_token\":\n throw new Error(\"Device code expired. Please try again.\");\n case \"access_denied\":\n throw new Error(\"Authorization denied by user.\");\n default:\n throw new Error(\n `Unexpected error: ${err.error_description ?? err.error}`,\n );\n }\n }\n\n throw new Error(\"Device code expired. Please try again.\");\n}\n","import {\n readCredentials,\n writeCredentials,\n clearCredentials,\n getAuthIssuer,\n} from \"./config.js\";\n\nconst REFRESH_BUFFER_MS = 60_000;\n\nexport interface SaveTokenParams {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n clientId: string;\n resource: string;\n}\n\nexport class TokenManager {\n isAuthenticated(): boolean {\n const creds = readCredentials();\n return creds !== null;\n }\n\n getAccessToken(): string | null {\n const creds = readCredentials();\n if (!creds) return null;\n return creds.accessToken;\n }\n\n needsRefresh(): boolean {\n const creds = readCredentials();\n if (!creds) return false;\n return Date.now() >= creds.expiresAt - REFRESH_BUFFER_MS;\n }\n\n saveTokens(params: SaveTokenParams): void {\n writeCredentials({\n accessToken: params.accessToken,\n refreshToken: params.refreshToken,\n expiresAt: Date.now() + params.expiresIn * 1000,\n clientId: params.clientId,\n resource: params.resource,\n });\n }\n\n async refreshIfNeeded(): Promise<string | null> {\n const creds = readCredentials();\n if (!creds) return null;\n\n if (!this.needsRefresh()) {\n return creds.accessToken;\n }\n\n const issuer = getAuthIssuer();\n const res = await fetch(`${issuer}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: creds.refreshToken,\n client_id: creds.clientId,\n resource: creds.resource,\n }).toString(),\n });\n\n if (!res.ok) {\n clearCredentials();\n return null;\n }\n\n const data = (await res.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n this.saveTokens({\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in,\n clientId: creds.clientId,\n resource: creds.resource,\n });\n\n return data.access_token;\n }\n\n logout(): void {\n clearCredentials();\n }\n}\n","import { getMcpEndpoint } from \"./config.js\";\n\ninterface LinkResult {\n linked: boolean;\n agentName: string;\n bybitSubMemberId: string | null;\n}\n\n/**\n * Links a legacy mcp-gateway API key to the caller's OAuth identity.\n * Called automatically by `eterna login` when ETERNA_MCP_KEY is set.\n *\n * After linking, the existing Bybit subaccount is preserved — mcp-gateway\n * resolves the OAuth JWT to the same agent row instead of creating a new one.\n */\nexport async function linkLegacyKey(\n accessToken: string,\n legacyApiKey: string,\n): Promise<void> {\n const mcpEndpoint = getMcpEndpoint();\n\n let res: Response;\n try {\n res = await fetch(`${mcpEndpoint}/migrate/link-legacy-key`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ legacyApiKey }),\n });\n } catch {\n console.warn(\n \"Warning: Could not reach mcp-gateway to link legacy key — your existing Bybit subaccount may not be preserved.\",\n );\n return;\n }\n\n if (res.ok) {\n const data = (await res.json()) as LinkResult;\n if (data.linked) {\n console.log(\n `✓ Legacy account linked — existing Bybit subaccount preserved`,\n );\n } else {\n console.log(`✓ Account already linked`);\n }\n return;\n }\n\n if (res.status === 409) {\n console.warn(\n \"Warning: Legacy key is already linked to a different OAuth account\",\n );\n return;\n }\n\n const errText = await res.text().catch(() => res.statusText);\n console.warn(`Warning: Could not link legacy key (${res.status}): ${errText}`);\n}\n","import ora, { type Ora } from \"ora\";\n\nlet activeSpinner: Ora | null = null;\n\nexport function startSpinner(text: string): Ora {\n activeSpinner = ora(text).start();\n return activeSpinner;\n}\n\nexport function stopSpinner(success?: boolean, text?: string): void {\n if (!activeSpinner) return;\n if (success === true) {\n activeSpinner.succeed(text);\n } else if (success === false) {\n activeSpinner.fail(text);\n } else {\n activeSpinner.stop();\n }\n activeSpinner = null;\n}\n","import { Command } from \"commander\";\nimport { TokenManager } from \"../auth/token-manager.js\";\n\nexport const logoutCommand = new Command(\"logout\")\n .description(\"Remove stored credentials\")\n .action(() => {\n const tokenManager = new TokenManager();\n\n if (!tokenManager.isAuthenticated()) {\n console.log(\"Not currently authenticated.\");\n return;\n }\n\n tokenManager.logout();\n console.log(\"Logged out. Credentials removed from ~/.eterna/\");\n });\n","import { Command } from \"commander\";\nimport * as fs from \"node:fs\";\nimport { ApiClient } from \"../api/client.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\nimport { formatExecutionResult } from \"../util/format.js\";\n\nexport function readCodeFromFile(filePath: string): string {\n if (!fs.existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n return fs.readFileSync(filePath, \"utf-8\");\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk as Buffer);\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n\nexport const executeCommand = new Command(\"execute\")\n .description(\"Execute trading code in the Eterna sandbox\")\n .argument(\"[file]\", \"TypeScript file to execute, or - for stdin\")\n .action(async (file?: string) => {\n let code: string;\n\n if (file === \"-\" || (!file && !process.stdin.isTTY)) {\n code = await readStdin();\n } else if (file) {\n code = await readCodeFromFile(file);\n } else {\n console.error(\"Usage: eterna execute <file.ts> or pipe code via stdin\");\n process.exitCode = 1;\n return;\n }\n\n if (!code.trim()) {\n console.error(\"Error: empty code input\");\n process.exitCode = 1;\n return;\n }\n\n const client = new ApiClient();\n startSpinner(\"Executing...\");\n\n try {\n const result = await client.execute(code);\n stopSpinner(result.success, result.success ? \"Done\" : \"Execution failed\");\n console.log(formatExecutionResult(result));\n if (!result.success) process.exitCode = 1;\n } catch (err) {\n stopSpinner(false, \"Failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n","import { readConfig } from \"../auth/config.js\";\nimport { TokenManager } from \"../auth/token-manager.js\";\n\nexport interface ExecutionResult {\n success: boolean;\n result: unknown;\n error?: string;\n logs: string[];\n stats: {\n durationMs: number;\n apiCallsMade: number;\n };\n validationErrors?: Array<{ field: string; message: string }>;\n}\n\nexport interface SdkSearchResult {\n text: string;\n}\n\nexport class ApiClient {\n private tokenManager = new TokenManager();\n\n private async getAuthHeaders(): Promise<Record<string, string>> {\n let token = this.tokenManager.getAccessToken();\n\n if (!token || this.tokenManager.needsRefresh()) {\n token = await this.tokenManager.refreshIfNeeded();\n }\n\n if (!token) {\n throw new Error(\"Not authenticated. Run `eterna login` first.\");\n }\n\n return { Authorization: `Bearer ${token}` };\n }\n\n private getBaseUrl(): string {\n return readConfig().endpoint;\n }\n\n async execute(code: string): Promise<ExecutionResult> {\n const headers = await this.getAuthHeaders();\n const res = await fetch(`${this.getBaseUrl()}/sandbox/execute`, {\n method: \"POST\",\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code }),\n });\n\n if (res.status === 401) {\n const newToken = await this.tokenManager.refreshIfNeeded();\n if (!newToken) {\n throw new Error(\n \"Session expired. Run `eterna login` to re-authenticate.\",\n );\n }\n const retryRes = await fetch(`${this.getBaseUrl()}/sandbox/execute`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${newToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ code }),\n });\n if (!retryRes.ok) {\n throw new Error(`Execution failed: ${retryRes.statusText}`);\n }\n return retryRes.json() as Promise<ExecutionResult>;\n }\n\n if (!res.ok) {\n const errBody = await res.text();\n throw new Error(`Execution failed (${res.status}): ${errBody}`);\n }\n\n return res.json() as Promise<ExecutionResult>;\n }\n\n async sdkSearch(\n query?: string,\n detailLevel: string = \"summary\",\n ): Promise<SdkSearchResult> {\n const params = new URLSearchParams({ detail_level: detailLevel });\n if (query) params.set(\"query\", query);\n\n const res = await fetch(\n `${this.getBaseUrl()}/sdk/search?${params.toString()}`,\n );\n\n if (!res.ok) {\n throw new Error(`SDK search failed (${res.status}): ${res.statusText}`);\n }\n\n return res.json() as Promise<SdkSearchResult>;\n }\n}\n","import type { ExecutionResult } from \"../api/client.js\";\n\nexport function formatExecutionResult(result: ExecutionResult): string {\n const lines: string[] = [];\n\n if (result.success) {\n if (result.result !== undefined && result.result !== null) {\n lines.push(\n typeof result.result === \"string\"\n ? result.result\n : JSON.stringify(result.result, null, 2),\n );\n }\n } else {\n lines.push(`Error: ${result.error ?? \"Unknown error\"}`);\n }\n\n if (result.logs.length > 0) {\n lines.push(\"\");\n lines.push(\"--- Logs ---\");\n lines.push(...result.logs);\n }\n\n if (result.validationErrors && result.validationErrors.length > 0) {\n lines.push(\"\");\n lines.push(\"--- Validation Errors ---\");\n for (const ve of result.validationErrors) {\n lines.push(` ${ve.field}: ${ve.message}`);\n }\n }\n\n lines.push(\"\");\n lines.push(\n `(${result.stats.durationMs}ms, ${result.stats.apiCallsMade} API calls)`,\n );\n\n return lines.join(\"\\n\");\n}\n\nexport function formatJson(data: unknown): string {\n return JSON.stringify(data, null, 2);\n}\n","import { Command } from \"commander\";\nimport { ApiClient } from \"../api/client.js\";\n\nexport const sdkCommand = new Command(\"sdk\")\n .description(\"Browse the Eterna SDK reference\")\n .option(\"--search <query>\", \"Search for SDK methods by keyword\")\n .option(\n \"--detail <level>\",\n \"Detail level: list, summary, full, params, keywords\",\n \"summary\",\n )\n .action(async (opts: { search?: string; detail: string }) => {\n const client = new ApiClient();\n\n try {\n const result = await client.sdkSearch(opts.search, opts.detail);\n console.log(result.text);\n } catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n","import { Command } from \"commander\";\nimport { ApiClient } from \"../api/client.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\nimport { formatExecutionResult } from \"../util/format.js\";\n\nexport const balanceCommand = new Command(\"balance\")\n .description(\"Get your account balance\")\n .action(async () => {\n const client = new ApiClient();\n startSpinner(\"Fetching balance...\");\n\n try {\n const result = await client.execute(\n \"const balance = await eterna.getBalance();\\nreturn balance;\",\n );\n stopSpinner(result.success, result.success ? \"Done\" : \"Failed\");\n console.log(formatExecutionResult(result));\n if (!result.success) process.exitCode = 1;\n } catch (err) {\n stopSpinner(false, \"Failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n","import { Command } from \"commander\";\nimport { ApiClient } from \"../api/client.js\";\nimport { startSpinner, stopSpinner } from \"../util/spinner.js\";\nimport { formatExecutionResult } from \"../util/format.js\";\n\nexport const positionsCommand = new Command(\"positions\")\n .description(\"Get your open positions\")\n .action(async () => {\n const client = new ApiClient();\n startSpinner(\"Fetching positions...\");\n\n try {\n const result = await client.execute(\n \"const positions = await eterna.getPositions();\\nreturn positions;\",\n );\n stopSpinner(result.success, result.success ? \"Done\" : \"Failed\");\n console.log(formatExecutionResult(result));\n if (!result.success) process.exitCode = 1;\n } catch (err) {\n stopSpinner(false, \"Failed\");\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n });\n"],"mappings":"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","names":["Command","code","receivedState","CLIENT_ID","SCOPE","CLIENT_ID","Command","Command","Command","fs","Command","Command","Command","Command","Command","Command","Command","Command"]}
|