@eve-horizon/cli 0.2.12 → 0.2.13

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.
@@ -1,852 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.handleAuth = handleAuth;
37
- const args_1 = require("../lib/args");
38
- const config_1 = require("../lib/config");
39
- const client_1 = require("../lib/client");
40
- const output_1 = require("../lib/output");
41
- const node_child_process_1 = require("node:child_process");
42
- const node_fs_1 = require("node:fs");
43
- const node_os_1 = require("node:os");
44
- const node_path_1 = require("node:path");
45
- const readline = __importStar(require("node:readline/promises"));
46
- async function handleAuth(subcommand, flags, context, credentials) {
47
- const json = Boolean(flags.json);
48
- const noInteractive = (0, args_1.getBooleanFlag)(flags, ['no-interactive']) ?? false;
49
- switch (subcommand) {
50
- case 'login': {
51
- const status = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true, tokenOverride: '' });
52
- if (status.ok) {
53
- const data = status.data;
54
- if (data.auth_enabled === false) {
55
- (0, output_1.outputJson)(data, json, 'Auth disabled for this stack; login not required.');
56
- return;
57
- }
58
- }
59
- const email = (0, args_1.getStringFlag)(flags, ['email']) ?? process.env.EVE_AUTH_EMAIL ?? context.profile.default_email;
60
- const userId = (0, args_1.getStringFlag)(flags, ['user-id']);
61
- const password = (0, args_1.getStringFlag)(flags, ['password']) ?? process.env.EVE_AUTH_PASSWORD;
62
- const ttlStr = (0, args_1.getStringFlag)(flags, ['ttl']);
63
- const ttlDays = ttlStr ? parseInt(ttlStr, 10) : undefined;
64
- if (ttlDays !== undefined && (isNaN(ttlDays) || ttlDays < 1 || ttlDays > 90)) {
65
- throw new Error('--ttl must be between 1 and 90 days');
66
- }
67
- const supabaseUrl = (0, args_1.getStringFlag)(flags, ['supabase-url']) ||
68
- process.env.EVE_SUPABASE_URL ||
69
- context.profile.supabase_url;
70
- const supabaseAnonKey = (0, args_1.getStringFlag)(flags, ['supabase-anon-key']) ||
71
- process.env.EVE_SUPABASE_ANON_KEY ||
72
- context.profile.supabase_anon_key;
73
- const useSupabase = Boolean(password);
74
- if (useSupabase) {
75
- if (!email || !password) {
76
- throw new Error('Usage: eve auth login --email <email> --password <password>');
77
- }
78
- if (!supabaseUrl || !supabaseAnonKey) {
79
- throw new Error('Missing Supabase config. Provide --supabase-url and --supabase-anon-key.');
80
- }
81
- const loginResponse = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=password`, {
82
- method: 'POST',
83
- headers: {
84
- 'Content-Type': 'application/json',
85
- apikey: supabaseAnonKey,
86
- Authorization: `Bearer ${supabaseAnonKey}`,
87
- },
88
- body: JSON.stringify({ email, password }),
89
- });
90
- const loginText = await loginResponse.text();
91
- let loginData = null;
92
- if (loginText) {
93
- try {
94
- loginData = JSON.parse(loginText);
95
- }
96
- catch {
97
- loginData = loginText;
98
- }
99
- }
100
- if (!loginResponse.ok || !loginData || typeof loginData !== 'object') {
101
- throw new Error(`Supabase login failed: ${loginText}`);
102
- }
103
- const payload = loginData;
104
- if (!payload.access_token) {
105
- throw new Error('Supabase login response missing access_token');
106
- }
107
- const expiresAt = payload.expires_in
108
- ? Math.floor(Date.now() / 1000) + payload.expires_in
109
- : undefined;
110
- credentials.tokens[context.authKey] = {
111
- access_token: payload.access_token,
112
- refresh_token: payload.refresh_token,
113
- expires_at: expiresAt,
114
- token_type: payload.token_type,
115
- };
116
- (0, config_1.saveCredentials)(credentials);
117
- (0, output_1.outputJson)({ profile: context.profileName, token_type: payload.token_type }, json, '✓ Logged in');
118
- return;
119
- }
120
- if (!email && !userId) {
121
- throw new Error('Usage: eve auth login --email <email> or --user-id <id>');
122
- }
123
- // Attempt SSH key login with GitHub key auto-discovery on failure
124
- const loginResult = await attemptSshLogin(context, credentials, flags, email, userId, ttlDays);
125
- if (loginResult.success) {
126
- (0, output_1.outputJson)({ profile: context.profileName, token_type: loginResult.tokenType }, json, '✓ Logged in');
127
- return;
128
- }
129
- // Check if this is a verification failure that could benefit from key registration
130
- const isVerificationFailure = loginResult.error && (loginResult.error.includes('Signature verification failed') ||
131
- loginResult.error.includes('No matching identity') ||
132
- loginResult.error.includes('Identity not found') ||
133
- loginResult.error.includes('Public key not registered'));
134
- if (!isVerificationFailure || noInteractive || json) {
135
- throw new Error(loginResult.error ?? 'Auth verify failed');
136
- }
137
- // Offer GitHub key auto-discovery
138
- const registered = await offerGitHubKeyRegistration(context, email);
139
- if (!registered) {
140
- throw new Error(loginResult.error ?? 'Auth verify failed');
141
- }
142
- // Retry login after key registration
143
- console.log('\nRetrying login with registered keys...');
144
- const retryResult = await attemptSshLogin(context, credentials, flags, email, userId, ttlDays);
145
- if (retryResult.success) {
146
- (0, output_1.outputJson)({ profile: context.profileName, token_type: retryResult.tokenType }, json, '✓ Logged in');
147
- return;
148
- }
149
- throw new Error(retryResult.error ?? 'Auth verify failed after key registration');
150
- }
151
- case 'logout': {
152
- const hadToken = Boolean(credentials.tokens[context.authKey] || credentials.profiles?.[context.profileName]);
153
- if (credentials.tokens[context.authKey]) {
154
- delete credentials.tokens[context.authKey];
155
- }
156
- if (credentials.profiles?.[context.profileName]) {
157
- delete credentials.profiles[context.profileName];
158
- }
159
- if (hadToken) {
160
- (0, config_1.saveCredentials)(credentials);
161
- }
162
- (0, output_1.outputJson)({ profile: context.profileName }, json, '✓ Logged out');
163
- return;
164
- }
165
- case 'status':
166
- case 'whoami': {
167
- const response = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true });
168
- if (!response.ok) {
169
- (0, output_1.outputJson)({ auth_enabled: true, authenticated: false }, json, 'Not authenticated');
170
- return;
171
- }
172
- const data = response.data;
173
- if (data.auth_enabled === false) {
174
- (0, output_1.outputJson)(data, json, 'Auth disabled for this stack.');
175
- return;
176
- }
177
- if (!json && data.permissions && data.permissions.length > 0) {
178
- console.log(`User: ${data.user_id ?? 'unknown'} (${data.email ?? 'no email'})`);
179
- console.log(`Role: ${data.role ?? 'member'}`);
180
- console.log(`Admin: ${data.is_admin ?? false}`);
181
- console.log(`\nPermissions (${data.permissions.length}):`);
182
- for (const perm of data.permissions) {
183
- console.log(` ${perm}`);
184
- }
185
- return;
186
- }
187
- (0, output_1.outputJson)(data, json);
188
- return;
189
- }
190
- case 'permissions': {
191
- const response = await (0, client_1.requestJson)(context, '/auth/permissions');
192
- if (json) {
193
- (0, output_1.outputJson)(response, json);
194
- return;
195
- }
196
- console.log('Permission Matrix:');
197
- console.log('');
198
- const header = 'Permission'.padEnd(24) + 'Member'.padEnd(10) + 'Admin'.padEnd(10) + 'Owner';
199
- console.log(header);
200
- console.log('-'.repeat(header.length));
201
- for (const row of response.matrix) {
202
- const m = row.member ? '✓' : '-';
203
- const a = row.admin ? '✓' : '-';
204
- const o = row.owner ? '✓' : '-';
205
- console.log(`${row.permission.padEnd(24)}${m.padEnd(10)}${a.padEnd(10)}${o}`);
206
- }
207
- return;
208
- }
209
- case 'bootstrap': {
210
- const statusOnly = (0, args_1.getBooleanFlag)(flags, ['status']) ?? false;
211
- // Check bootstrap status first
212
- const statusResponse = await (0, client_1.requestRaw)(context, '/auth/bootstrap/status', {
213
- allowError: true,
214
- tokenOverride: '',
215
- });
216
- let status = null;
217
- if (statusResponse.ok) {
218
- status = statusResponse.data;
219
- }
220
- // Handle --status subcommand
221
- if (statusOnly) {
222
- if (!status) {
223
- throw new Error('Failed to fetch bootstrap status');
224
- }
225
- if (json) {
226
- (0, output_1.outputJson)(status, json);
227
- return;
228
- }
229
- console.log('Bootstrap Status:');
230
- console.log(` Mode: ${status.mode}`);
231
- if (status.completed) {
232
- console.log(' Status: completed');
233
- }
234
- else if (status.window_open) {
235
- const closesAt = status.window_closes_at ? new Date(status.window_closes_at) : null;
236
- const remaining = closesAt ? Math.max(0, Math.round((closesAt.getTime() - Date.now()) / 60000)) : null;
237
- console.log(` Window: open${remaining !== null ? ` (closes in ${remaining} minutes)` : ''}`);
238
- }
239
- else {
240
- console.log(' Window: closed');
241
- }
242
- console.log(` Token required: ${status.requires_token ? 'yes' : 'no'}`);
243
- return;
244
- }
245
- const email = (0, args_1.getStringFlag)(flags, ['email']);
246
- const token = (0, args_1.getStringFlag)(flags, ['token']) ?? process.env.EVE_BOOTSTRAP_TOKEN;
247
- const sshKeyPath = (0, args_1.getStringFlag)(flags, ['ssh-key']) ??
248
- (0, node_path_1.join)((0, node_os_1.homedir)(), '.ssh', 'id_ed25519.pub');
249
- const displayName = (0, args_1.getStringFlag)(flags, ['display-name']);
250
- if (!email) {
251
- throw new Error('Usage: eve auth bootstrap --email <email> [--token <token>] [--ssh-key <path>] [--display-name <name>]');
252
- }
253
- // Check status and handle accordingly
254
- if (status) {
255
- if (status.completed) {
256
- throw new Error('Bootstrap already completed. Use eve auth login instead.');
257
- }
258
- if (!status.requires_token && status.window_open) {
259
- console.log(`Bootstrap window open (${status.mode} mode). Token not required.`);
260
- }
261
- else if (status.requires_token && !token) {
262
- throw new Error('Bootstrap token required. Use --token <token> or set EVE_BOOTSTRAP_TOKEN');
263
- }
264
- }
265
- else if (!token) {
266
- // Could not fetch status, require token as fallback
267
- throw new Error('Bootstrap token required. Use --token <token> or set EVE_BOOTSTRAP_TOKEN');
268
- }
269
- // Read SSH public key
270
- if (!(0, node_fs_1.existsSync)(sshKeyPath)) {
271
- throw new Error(`SSH public key not found: ${sshKeyPath}`);
272
- }
273
- const publicKey = (0, node_fs_1.readFileSync)(sshKeyPath, 'utf8').trim();
274
- // POST to /auth/bootstrap
275
- const bootstrapResponse = await (0, client_1.requestRaw)(context, '/auth/bootstrap', {
276
- method: 'POST',
277
- body: {
278
- token: token ?? undefined,
279
- email,
280
- public_key: publicKey,
281
- display_name: displayName,
282
- },
283
- });
284
- if (!bootstrapResponse.ok) {
285
- const message = typeof bootstrapResponse.data === 'string'
286
- ? bootstrapResponse.data
287
- : bootstrapResponse.text;
288
- throw new Error(`Bootstrap failed: ${message}`);
289
- }
290
- const payload = bootstrapResponse.data;
291
- if (!payload.access_token) {
292
- throw new Error('Bootstrap response missing access_token');
293
- }
294
- credentials.tokens[context.authKey] = {
295
- access_token: payload.access_token,
296
- expires_at: payload.expires_at,
297
- token_type: payload.token_type,
298
- };
299
- (0, config_1.saveCredentials)(credentials);
300
- (0, output_1.outputJson)({ profile: context.profileName, user_id: payload.user_id, token_type: payload.token_type }, json, `✓ Bootstrapped admin user (user_id: ${payload.user_id})`);
301
- return;
302
- }
303
- case 'token': {
304
- // Print the current access token to stdout for use in scripts
305
- const tokenEntry = credentials.tokens[context.authKey] || credentials.profiles?.[context.profileName];
306
- if (!tokenEntry || !tokenEntry.access_token) {
307
- console.error('No valid token found. Please login first with: eve auth login');
308
- process.exit(1);
309
- }
310
- // Print only the token to stdout, nothing else
311
- console.log(tokenEntry.access_token);
312
- return;
313
- }
314
- case 'mint': {
315
- const email = (0, args_1.getStringFlag)(flags, ['email']);
316
- const orgId = (0, args_1.getStringFlag)(flags, ['org']);
317
- const projectId = (0, args_1.getStringFlag)(flags, ['project']);
318
- const role = (0, args_1.getStringFlag)(flags, ['role']) ?? 'member';
319
- const ttlStr = (0, args_1.getStringFlag)(flags, ['ttl']);
320
- const ttlDays = ttlStr ? parseInt(ttlStr, 10) : undefined;
321
- if (ttlDays !== undefined && (isNaN(ttlDays) || ttlDays < 1 || ttlDays > 90)) {
322
- throw new Error('--ttl must be between 1 and 90 days');
323
- }
324
- if (!email) {
325
- throw new Error('Usage: eve auth mint --email <email> [--org <org_id> | --project <project_id>] [--role <role>]');
326
- }
327
- if (!orgId && !projectId) {
328
- throw new Error('Usage: eve auth mint --email <email> [--org <org_id> | --project <project_id>] [--role <role>]');
329
- }
330
- if (!['admin', 'member'].includes(role)) {
331
- throw new Error(`Invalid role: ${role}. Must be one of: admin, member`);
332
- }
333
- const response = await (0, client_1.requestJson)(context, '/auth/mint', {
334
- method: 'POST',
335
- body: {
336
- email,
337
- org_id: orgId,
338
- project_id: projectId,
339
- role,
340
- ttl_days: ttlDays,
341
- },
342
- });
343
- if (json) {
344
- (0, output_1.outputJson)(response, json);
345
- return;
346
- }
347
- if (!response.access_token) {
348
- throw new Error('Mint response missing access_token');
349
- }
350
- // Print only the token to stdout, nothing else
351
- console.log(response.access_token);
352
- return;
353
- }
354
- case 'sync': {
355
- const claudeOnly = (0, args_1.getBooleanFlag)(flags, ['claude']) ?? false;
356
- const codexOnly = (0, args_1.getBooleanFlag)(flags, ['codex']) ?? false;
357
- const dryRun = (0, args_1.getBooleanFlag)(flags, ['dry-run']) ?? false;
358
- const orgIdFlag = (0, args_1.getStringFlag)(flags, ['org']);
359
- const projectIdFlag = (0, args_1.getStringFlag)(flags, ['project']);
360
- let scope;
361
- if (projectIdFlag) {
362
- scope = { type: 'project', projectId: projectIdFlag };
363
- }
364
- else if (orgIdFlag) {
365
- scope = { type: 'org', orgId: orgIdFlag };
366
- }
367
- else {
368
- // Default to user scope - need to fetch user ID
369
- const meResponse = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true });
370
- if (!meResponse.ok) {
371
- throw new Error('Not authenticated. Run "eve auth login" first.');
372
- }
373
- const meData = meResponse.data;
374
- if (!meData.user_id) {
375
- throw new Error('Could not determine user ID. Try specifying --org or --project instead.');
376
- }
377
- scope = { type: 'user', userId: meData.user_id };
378
- }
379
- const extractClaude = !codexOnly; // Extract Claude unless --codex is specified
380
- const extractCodex = !claudeOnly; // Extract Codex unless --claude is specified
381
- const extractedTokens = {};
382
- const platform = process.platform;
383
- // Extract Claude OAuth tokens
384
- if (extractClaude) {
385
- // Try macOS Keychain first
386
- if (platform === 'darwin') {
387
- for (const service of ['Claude Code-credentials', 'anthropic.claude']) {
388
- try {
389
- const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
390
- if (output) {
391
- extractedTokens.CLAUDE_CODE_OAUTH_TOKEN = output;
392
- break;
393
- }
394
- }
395
- catch {
396
- // Token not found in keychain, continue
397
- }
398
- }
399
- }
400
- // Check credential files (all platforms)
401
- if (!extractedTokens.CLAUDE_CODE_OAUTH_TOKEN) {
402
- const credentialPaths = [
403
- `${(0, node_os_1.homedir)()}/.claude/.credentials.json`,
404
- `${(0, node_os_1.homedir)()}/.claude/credentials.json`,
405
- `${(0, node_os_1.homedir)()}/.config/claude/credentials.json`,
406
- ];
407
- for (const credPath of credentialPaths) {
408
- if ((0, node_fs_1.existsSync)(credPath)) {
409
- try {
410
- const content = (0, node_fs_1.readFileSync)(credPath, 'utf8');
411
- const creds = JSON.parse(content);
412
- // Handle nested claudeAiOauth format (current Claude Code format)
413
- const claudeOauth = creds.claudeAiOauth;
414
- if (claudeOauth?.accessToken) {
415
- extractedTokens.CLAUDE_CODE_OAUTH_TOKEN = claudeOauth.accessToken;
416
- break;
417
- }
418
- // Fallback to legacy root-level tokens
419
- if (creds.oauth_token || creds.access_token) {
420
- extractedTokens.CLAUDE_CODE_OAUTH_TOKEN = (creds.oauth_token || creds.access_token);
421
- break;
422
- }
423
- }
424
- catch {
425
- // Failed to parse, continue
426
- }
427
- }
428
- }
429
- }
430
- }
431
- // Extract Codex/Code OAuth tokens
432
- if (extractCodex) {
433
- // Try macOS Keychain first
434
- if (platform === 'darwin') {
435
- for (const service of ['openai.codex', 'Code-credentials']) {
436
- try {
437
- const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
438
- if (output) {
439
- extractedTokens.CODEX_OAUTH_ACCESS_TOKEN = output;
440
- break;
441
- }
442
- }
443
- catch {
444
- // Token not found in keychain, continue
445
- }
446
- }
447
- }
448
- // Check auth files (all platforms): ~/.codex/auth.json and ~/.code/auth.json
449
- if (!extractedTokens.CODEX_OAUTH_ACCESS_TOKEN) {
450
- const codexAuthPaths = [
451
- `${(0, node_os_1.homedir)()}/.codex/auth.json`,
452
- `${(0, node_os_1.homedir)()}/.code/auth.json`,
453
- ];
454
- for (const authPath of codexAuthPaths) {
455
- if ((0, node_fs_1.existsSync)(authPath)) {
456
- try {
457
- const content = (0, node_fs_1.readFileSync)(authPath, 'utf8');
458
- const auth = JSON.parse(content);
459
- // Handle nested tokens format (current Codex CLI format)
460
- const tokens = auth.tokens;
461
- if (tokens?.access_token) {
462
- extractedTokens.CODEX_OAUTH_ACCESS_TOKEN = tokens.access_token;
463
- break;
464
- }
465
- // Fallback to root-level tokens
466
- if (auth.oauth_token || auth.access_token) {
467
- extractedTokens.CODEX_OAUTH_ACCESS_TOKEN = (auth.oauth_token || auth.access_token);
468
- break;
469
- }
470
- // Also check for OPENAI_API_KEY in the auth file
471
- if (auth.OPENAI_API_KEY && typeof auth.OPENAI_API_KEY === 'string') {
472
- extractedTokens.OPENAI_API_KEY = auth.OPENAI_API_KEY;
473
- break;
474
- }
475
- }
476
- catch {
477
- // Failed to parse, continue
478
- }
479
- }
480
- }
481
- }
482
- }
483
- if (Object.keys(extractedTokens).length === 0) {
484
- (0, output_1.outputJson)({ extracted: 0, tokens: [] }, json, 'No tokens found on host machine');
485
- return;
486
- }
487
- // Build target label for output
488
- const targetLabel = scope.type === 'user' ? 'user' :
489
- scope.type === 'org' ? `org ${scope.orgId}` :
490
- `project ${scope.projectId}`;
491
- if (dryRun) {
492
- const tokenList = Object.keys(extractedTokens).map(key => ({
493
- name: key,
494
- value: `${extractedTokens[key].substring(0, 10)}...`,
495
- }));
496
- (0, output_1.outputJson)({ dry_run: true, would_set: tokenList, target: targetLabel, scope }, json, `Would set ${tokenList.length} token(s) on ${targetLabel}:\n${tokenList.map(t => ` - ${t.name}`).join('\n')}`);
497
- return;
498
- }
499
- // Set secrets via API - endpoint depends on scope
500
- const endpoint = scope.type === 'user' ? `/users/${scope.userId}/secrets` :
501
- scope.type === 'org' ? `/orgs/${scope.orgId}/secrets` :
502
- `/projects/${scope.projectId}/secrets`;
503
- const results = [];
504
- for (const [name, value] of Object.entries(extractedTokens)) {
505
- try {
506
- await (0, client_1.requestJson)(context, endpoint, {
507
- method: 'POST',
508
- body: {
509
- name,
510
- value,
511
- },
512
- });
513
- results.push({ name, success: true });
514
- }
515
- catch (error) {
516
- results.push({
517
- name,
518
- success: false,
519
- error: error instanceof Error ? error.message : String(error),
520
- });
521
- }
522
- }
523
- const successCount = results.filter(r => r.success).length;
524
- const failCount = results.filter(r => !r.success).length;
525
- (0, output_1.outputJson)({
526
- target: targetLabel,
527
- scope,
528
- results,
529
- success: successCount,
530
- failed: failCount,
531
- }, json, `✓ Set ${successCount} secret(s) on ${targetLabel}${failCount > 0 ? ` (${failCount} failed)` : ''}`);
532
- return;
533
- }
534
- case 'creds': {
535
- // Show local credential status without syncing
536
- const claudeOnly = (0, args_1.getBooleanFlag)(flags, ['claude']) ?? false;
537
- const codexOnly = (0, args_1.getBooleanFlag)(flags, ['codex']) ?? false;
538
- const checkClaude = !codexOnly;
539
- const checkCodex = !claudeOnly;
540
- const credentials = [];
541
- const plat = process.platform;
542
- // Check Claude credentials
543
- if (checkClaude) {
544
- let claudeFound = false;
545
- let claudeSource = '';
546
- let claudePreview = '';
547
- let claudeExpires;
548
- // Try macOS Keychain
549
- if (plat === 'darwin' && !claudeFound) {
550
- for (const service of ['Claude Code-credentials', 'anthropic.claude']) {
551
- try {
552
- const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
553
- if (output) {
554
- claudeFound = true;
555
- claudeSource = `macOS Keychain (${service})`;
556
- claudePreview = output.substring(0, 15) + '...';
557
- break;
558
- }
559
- }
560
- catch {
561
- // Not found
562
- }
563
- }
564
- }
565
- // Check credential files
566
- if (!claudeFound) {
567
- const credentialPaths = [
568
- `${(0, node_os_1.homedir)()}/.claude/.credentials.json`,
569
- `${(0, node_os_1.homedir)()}/.claude/credentials.json`,
570
- `${(0, node_os_1.homedir)()}/.config/claude/credentials.json`,
571
- ];
572
- for (const credPath of credentialPaths) {
573
- if ((0, node_fs_1.existsSync)(credPath)) {
574
- try {
575
- const content = (0, node_fs_1.readFileSync)(credPath, 'utf8');
576
- const creds = JSON.parse(content);
577
- const claudeOauth = creds.claudeAiOauth;
578
- if (claudeOauth?.accessToken) {
579
- claudeFound = true;
580
- claudeSource = credPath.replace((0, node_os_1.homedir)(), '~');
581
- claudePreview = claudeOauth.accessToken.substring(0, 15) + '...';
582
- if (claudeOauth.expiresAt) {
583
- const expDate = new Date(claudeOauth.expiresAt);
584
- claudeExpires = expDate.toISOString();
585
- }
586
- break;
587
- }
588
- if (creds.oauth_token || creds.access_token) {
589
- claudeFound = true;
590
- claudeSource = credPath.replace((0, node_os_1.homedir)(), '~');
591
- const token = (creds.oauth_token || creds.access_token);
592
- claudePreview = token.substring(0, 15) + '...';
593
- break;
594
- }
595
- }
596
- catch {
597
- // Failed to parse
598
- }
599
- }
600
- }
601
- }
602
- credentials.push({
603
- name: 'Claude Code OAuth',
604
- source: claudeFound ? claudeSource : 'not found',
605
- found: claudeFound,
606
- preview: claudePreview || undefined,
607
- expiresAt: claudeExpires,
608
- });
609
- }
610
- // Check Codex credentials
611
- if (checkCodex) {
612
- let codexFound = false;
613
- let codexSource = '';
614
- let codexPreview = '';
615
- // Try macOS Keychain
616
- if (plat === 'darwin' && !codexFound) {
617
- for (const service of ['openai.codex', 'Code-credentials']) {
618
- try {
619
- const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
620
- if (output) {
621
- codexFound = true;
622
- codexSource = `macOS Keychain (${service})`;
623
- codexPreview = output.substring(0, 15) + '...';
624
- break;
625
- }
626
- }
627
- catch {
628
- // Not found
629
- }
630
- }
631
- }
632
- // Check auth files
633
- if (!codexFound) {
634
- const codexAuthPaths = [
635
- `${(0, node_os_1.homedir)()}/.codex/auth.json`,
636
- `${(0, node_os_1.homedir)()}/.code/auth.json`,
637
- ];
638
- for (const authPath of codexAuthPaths) {
639
- if ((0, node_fs_1.existsSync)(authPath)) {
640
- try {
641
- const content = (0, node_fs_1.readFileSync)(authPath, 'utf8');
642
- const auth = JSON.parse(content);
643
- const tokens = auth.tokens;
644
- if (tokens?.access_token) {
645
- codexFound = true;
646
- codexSource = authPath.replace((0, node_os_1.homedir)(), '~');
647
- codexPreview = tokens.access_token.substring(0, 15) + '...';
648
- break;
649
- }
650
- if (auth.oauth_token || auth.access_token) {
651
- codexFound = true;
652
- codexSource = authPath.replace((0, node_os_1.homedir)(), '~');
653
- const token = (auth.oauth_token || auth.access_token);
654
- codexPreview = token.substring(0, 15) + '...';
655
- break;
656
- }
657
- if (auth.OPENAI_API_KEY && typeof auth.OPENAI_API_KEY === 'string') {
658
- codexFound = true;
659
- codexSource = authPath.replace((0, node_os_1.homedir)(), '~') + ' (API key)';
660
- codexPreview = auth.OPENAI_API_KEY.substring(0, 10) + '...';
661
- break;
662
- }
663
- }
664
- catch {
665
- // Failed to parse
666
- }
667
- }
668
- }
669
- }
670
- credentials.push({
671
- name: 'Codex/Code OAuth',
672
- source: codexFound ? codexSource : 'not found',
673
- found: codexFound,
674
- preview: codexPreview || undefined,
675
- });
676
- }
677
- const foundCount = credentials.filter(c => c.found).length;
678
- if (json) {
679
- (0, output_1.outputJson)({ credentials, found: foundCount }, json);
680
- return;
681
- }
682
- console.log('Local AI Tool Credentials:');
683
- console.log('');
684
- for (const cred of credentials) {
685
- const status = cred.found ? '✓' : '✗';
686
- console.log(` ${status} ${cred.name}`);
687
- console.log(` Source: ${cred.source}`);
688
- if (cred.preview) {
689
- console.log(` Token: ${cred.preview}`);
690
- }
691
- if (cred.expiresAt) {
692
- const expDate = new Date(cred.expiresAt);
693
- const now = new Date();
694
- const isExpired = expDate < now;
695
- const expLabel = isExpired ? '(expired)' : '';
696
- console.log(` Expires: ${cred.expiresAt} ${expLabel}`);
697
- }
698
- console.log('');
699
- }
700
- if (foundCount > 0) {
701
- console.log(`Found ${foundCount} credential(s). Run 'eve auth sync' to sync to Eve.`);
702
- }
703
- else {
704
- console.log('No local credentials found.');
705
- console.log('');
706
- console.log('To set up credentials:');
707
- console.log(' Claude: Run "claude" CLI and log in');
708
- console.log(' Codex: Run "codex" CLI and log in');
709
- }
710
- return;
711
- }
712
- default:
713
- throw new Error('Usage: eve auth <login|logout|status|whoami|bootstrap|sync|creds|token|mint|permissions>');
714
- }
715
- }
716
- function signNonceWithSsh(keyPath, nonce) {
717
- const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'eve-auth-'));
718
- const noncePath = (0, node_path_1.join)(tempDir, 'nonce');
719
- const signaturePath = `${noncePath}.sig`;
720
- try {
721
- (0, node_fs_1.writeFileSync)(noncePath, nonce);
722
- const result = (0, node_child_process_1.spawnSync)('ssh-keygen', ['-Y', 'sign', '-f', keyPath, '-n', 'eve-auth', noncePath], {
723
- encoding: 'utf8',
724
- });
725
- if (result.status !== 0) {
726
- throw new Error(`ssh-keygen failed: ${result.stderr || 'unknown error'}`);
727
- }
728
- return (0, node_fs_1.readFileSync)(signaturePath, 'utf8');
729
- }
730
- finally {
731
- (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
732
- }
733
- }
734
- async function attemptSshLogin(context, credentials, flags, email, userId, ttlDays) {
735
- const challengeResponse = await (0, client_1.requestRaw)(context, '/auth/challenge', {
736
- method: 'POST',
737
- body: {
738
- email,
739
- user_id: userId,
740
- },
741
- });
742
- if (!challengeResponse.ok) {
743
- const message = typeof challengeResponse.data === 'string'
744
- ? challengeResponse.data
745
- : challengeResponse.text;
746
- return { success: false, error: `Challenge failed: ${message}` };
747
- }
748
- const challenge = challengeResponse.data;
749
- if (!challenge.challenge_id || !challenge.nonce) {
750
- return { success: false, error: 'Challenge response missing fields' };
751
- }
752
- const sshKeyPath = (0, args_1.getStringFlag)(flags, ['ssh-key']) ||
753
- process.env.EVE_AUTH_SSH_KEY ||
754
- context.profile.default_ssh_key ||
755
- (0, node_path_1.join)((0, node_os_1.homedir)(), '.ssh', 'id_ed25519');
756
- let signature;
757
- try {
758
- signature = signNonceWithSsh(sshKeyPath, challenge.nonce);
759
- }
760
- catch (err) {
761
- const msg = err instanceof Error ? err.message : String(err);
762
- return { success: false, error: `Failed to sign challenge: ${msg}` };
763
- }
764
- const verifyResponse = await (0, client_1.requestRaw)(context, '/auth/verify', {
765
- method: 'POST',
766
- body: { challenge_id: challenge.challenge_id, signature, ...(ttlDays !== undefined && { ttl_days: ttlDays }) },
767
- });
768
- if (!verifyResponse.ok) {
769
- const message = typeof verifyResponse.data === 'string'
770
- ? verifyResponse.data
771
- : verifyResponse.text;
772
- return { success: false, error: `Auth verify failed: ${message}` };
773
- }
774
- const payload = verifyResponse.data;
775
- if (!payload.access_token) {
776
- return { success: false, error: 'Auth verify response missing access_token' };
777
- }
778
- credentials.tokens[context.authKey] = {
779
- access_token: payload.access_token,
780
- expires_at: payload.expires_at,
781
- token_type: payload.token_type,
782
- };
783
- (0, config_1.saveCredentials)(credentials);
784
- return { success: true, tokenType: payload.token_type };
785
- }
786
- async function fetchGitHubKeys(username) {
787
- const response = await fetch(`https://github.com/${username}.keys`);
788
- if (!response.ok) {
789
- if (response.status === 404) {
790
- throw new Error(`GitHub user not found: ${username}`);
791
- }
792
- throw new Error(`Failed to fetch GitHub keys: HTTP ${response.status}`);
793
- }
794
- const text = await response.text();
795
- return text.trim().split('\n').filter(k => k.length > 0);
796
- }
797
- async function offerGitHubKeyRegistration(context, email) {
798
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
799
- try {
800
- console.log('\nNo registered SSH key found for this user.');
801
- const username = await rl.question('Enter GitHub username to register keys (or press Enter to skip): ');
802
- if (!username.trim()) {
803
- return false;
804
- }
805
- let keys;
806
- try {
807
- keys = await fetchGitHubKeys(username.trim());
808
- }
809
- catch (err) {
810
- const msg = err instanceof Error ? err.message : String(err);
811
- console.error(`Failed to fetch GitHub keys: ${msg}`);
812
- return false;
813
- }
814
- if (keys.length === 0) {
815
- console.log(`No SSH keys found for github.com/${username}`);
816
- return false;
817
- }
818
- console.log(`\nFound ${keys.length} SSH key(s) for github.com/${username.trim()}`);
819
- const confirm = await rl.question('Register them? [Y/n]: ');
820
- if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
821
- return false;
822
- }
823
- // Register each key
824
- let registered = 0;
825
- for (const publicKey of keys) {
826
- try {
827
- await (0, client_1.requestJson)(context, '/auth/identities', {
828
- method: 'POST',
829
- body: {
830
- email,
831
- public_key: publicKey,
832
- label: `github-${username.trim()}`,
833
- },
834
- });
835
- registered += 1;
836
- }
837
- catch (err) {
838
- const msg = err instanceof Error ? err.message : String(err);
839
- console.error(`Failed to register key: ${msg}`);
840
- }
841
- }
842
- if (registered === 0) {
843
- console.log('No keys were registered');
844
- return false;
845
- }
846
- console.log(`✓ Registered ${registered} SSH key(s)`);
847
- return true;
848
- }
849
- finally {
850
- rl.close();
851
- }
852
- }