@eve-horizon/cli 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,51 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.handleAuth = handleAuth;
4
37
  const args_1 = require("../lib/args");
5
38
  const config_1 = require("../lib/config");
6
39
  const client_1 = require("../lib/client");
7
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"));
8
46
  async function handleAuth(subcommand, flags, context, credentials) {
9
47
  const json = Boolean(flags.json);
48
+ const noInteractive = (0, args_1.getBooleanFlag)(flags, ['no-interactive']) ?? false;
10
49
  switch (subcommand) {
11
50
  case 'login': {
12
51
  const status = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true, tokenOverride: '' });
@@ -17,58 +56,92 @@ async function handleAuth(subcommand, flags, context, credentials) {
17
56
  return;
18
57
  }
19
58
  }
20
- const email = (0, args_1.getStringFlag)(flags, ['email']) ?? process.env.EVE_AUTH_EMAIL;
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']);
21
61
  const password = (0, args_1.getStringFlag)(flags, ['password']) ?? process.env.EVE_AUTH_PASSWORD;
22
- if (!email || !password) {
23
- throw new Error('Usage: eve auth login --email <email> --password <password>');
24
- }
25
62
  const supabaseUrl = (0, args_1.getStringFlag)(flags, ['supabase-url']) ||
26
63
  process.env.EVE_SUPABASE_URL ||
27
64
  context.profile.supabase_url;
28
65
  const supabaseAnonKey = (0, args_1.getStringFlag)(flags, ['supabase-anon-key']) ||
29
66
  process.env.EVE_SUPABASE_ANON_KEY ||
30
67
  context.profile.supabase_anon_key;
31
- if (!supabaseUrl || !supabaseAnonKey) {
32
- throw new Error('Missing Supabase config. Provide --supabase-url and --supabase-anon-key.');
33
- }
34
- const loginResponse = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=password`, {
35
- method: 'POST',
36
- headers: {
37
- 'Content-Type': 'application/json',
38
- apikey: supabaseAnonKey,
39
- Authorization: `Bearer ${supabaseAnonKey}`,
40
- },
41
- body: JSON.stringify({ email, password }),
42
- });
43
- const loginText = await loginResponse.text();
44
- let loginData = null;
45
- if (loginText) {
46
- try {
47
- loginData = JSON.parse(loginText);
68
+ const useSupabase = Boolean(password);
69
+ if (useSupabase) {
70
+ if (!email || !password) {
71
+ throw new Error('Usage: eve auth login --email <email> --password <password>');
48
72
  }
49
- catch {
50
- loginData = loginText;
73
+ if (!supabaseUrl || !supabaseAnonKey) {
74
+ throw new Error('Missing Supabase config. Provide --supabase-url and --supabase-anon-key.');
51
75
  }
76
+ const loginResponse = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=password`, {
77
+ method: 'POST',
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ apikey: supabaseAnonKey,
81
+ Authorization: `Bearer ${supabaseAnonKey}`,
82
+ },
83
+ body: JSON.stringify({ email, password }),
84
+ });
85
+ const loginText = await loginResponse.text();
86
+ let loginData = null;
87
+ if (loginText) {
88
+ try {
89
+ loginData = JSON.parse(loginText);
90
+ }
91
+ catch {
92
+ loginData = loginText;
93
+ }
94
+ }
95
+ if (!loginResponse.ok || !loginData || typeof loginData !== 'object') {
96
+ throw new Error(`Supabase login failed: ${loginText}`);
97
+ }
98
+ const payload = loginData;
99
+ if (!payload.access_token) {
100
+ throw new Error('Supabase login response missing access_token');
101
+ }
102
+ const expiresAt = payload.expires_in
103
+ ? Math.floor(Date.now() / 1000) + payload.expires_in
104
+ : undefined;
105
+ credentials.profiles[context.profileName] = {
106
+ access_token: payload.access_token,
107
+ refresh_token: payload.refresh_token,
108
+ expires_at: expiresAt,
109
+ token_type: payload.token_type,
110
+ };
111
+ (0, config_1.saveCredentials)(credentials);
112
+ (0, output_1.outputJson)({ profile: context.profileName, token_type: payload.token_type }, json, '✓ Logged in');
113
+ return;
52
114
  }
53
- if (!loginResponse.ok || !loginData || typeof loginData !== 'object') {
54
- throw new Error(`Supabase login failed: ${loginText}`);
115
+ if (!email && !userId) {
116
+ throw new Error('Usage: eve auth login --email <email> or --user-id <id>');
55
117
  }
56
- const payload = loginData;
57
- if (!payload.access_token) {
58
- throw new Error('Supabase login response missing access_token');
118
+ // Attempt SSH key login with GitHub key auto-discovery on failure
119
+ const loginResult = await attemptSshLogin(context, credentials, flags, email, userId);
120
+ if (loginResult.success) {
121
+ (0, output_1.outputJson)({ profile: context.profileName, token_type: loginResult.tokenType }, json, '✓ Logged in');
122
+ return;
59
123
  }
60
- const expiresAt = payload.expires_in
61
- ? Math.floor(Date.now() / 1000) + payload.expires_in
62
- : undefined;
63
- credentials.profiles[context.profileName] = {
64
- access_token: payload.access_token,
65
- refresh_token: payload.refresh_token,
66
- expires_at: expiresAt,
67
- token_type: payload.token_type,
68
- };
69
- (0, config_1.saveCredentials)(credentials);
70
- (0, output_1.outputJson)({ profile: context.profileName, token_type: payload.token_type }, json, '✓ Logged in');
71
- return;
124
+ // Check if this is a verification failure that could benefit from key registration
125
+ const isVerificationFailure = loginResult.error && (loginResult.error.includes('Signature verification failed') ||
126
+ loginResult.error.includes('No matching identity') ||
127
+ loginResult.error.includes('Identity not found') ||
128
+ loginResult.error.includes('Public key not registered'));
129
+ if (!isVerificationFailure || noInteractive || json) {
130
+ throw new Error(loginResult.error ?? 'Auth verify failed');
131
+ }
132
+ // Offer GitHub key auto-discovery
133
+ const registered = await offerGitHubKeyRegistration(context, email);
134
+ if (!registered) {
135
+ throw new Error(loginResult.error ?? 'Auth verify failed');
136
+ }
137
+ // Retry login after key registration
138
+ console.log('\nRetrying login with registered keys...');
139
+ const retryResult = await attemptSshLogin(context, credentials, flags, email, userId);
140
+ if (retryResult.success) {
141
+ (0, output_1.outputJson)({ profile: context.profileName, token_type: retryResult.tokenType }, json, '✓ Logged in');
142
+ return;
143
+ }
144
+ throw new Error(retryResult.error ?? 'Auth verify failed after key registration');
72
145
  }
73
146
  case 'logout': {
74
147
  if (credentials.profiles[context.profileName]) {
@@ -93,7 +166,361 @@ async function handleAuth(subcommand, flags, context, credentials) {
93
166
  (0, output_1.outputJson)(data, json);
94
167
  return;
95
168
  }
169
+ case 'bootstrap': {
170
+ const statusOnly = (0, args_1.getBooleanFlag)(flags, ['status']) ?? false;
171
+ // Check bootstrap status first
172
+ const statusResponse = await (0, client_1.requestRaw)(context, '/auth/bootstrap/status', {
173
+ allowError: true,
174
+ tokenOverride: '',
175
+ });
176
+ let status = null;
177
+ if (statusResponse.ok) {
178
+ status = statusResponse.data;
179
+ }
180
+ // Handle --status subcommand
181
+ if (statusOnly) {
182
+ if (!status) {
183
+ throw new Error('Failed to fetch bootstrap status');
184
+ }
185
+ if (json) {
186
+ (0, output_1.outputJson)(status, json);
187
+ return;
188
+ }
189
+ console.log('Bootstrap Status:');
190
+ console.log(` Mode: ${status.mode}`);
191
+ if (status.completed) {
192
+ console.log(' Status: completed');
193
+ }
194
+ else if (status.window_open) {
195
+ const closesAt = status.window_closes_at ? new Date(status.window_closes_at) : null;
196
+ const remaining = closesAt ? Math.max(0, Math.round((closesAt.getTime() - Date.now()) / 60000)) : null;
197
+ console.log(` Window: open${remaining !== null ? ` (closes in ${remaining} minutes)` : ''}`);
198
+ }
199
+ else {
200
+ console.log(' Window: closed');
201
+ }
202
+ console.log(` Token required: ${status.requires_token ? 'yes' : 'no'}`);
203
+ return;
204
+ }
205
+ const email = (0, args_1.getStringFlag)(flags, ['email']);
206
+ const token = (0, args_1.getStringFlag)(flags, ['token']) ?? process.env.EVE_BOOTSTRAP_TOKEN;
207
+ const sshKeyPath = (0, args_1.getStringFlag)(flags, ['ssh-key']) ??
208
+ (0, node_path_1.join)((0, node_os_1.homedir)(), '.ssh', 'id_ed25519.pub');
209
+ const displayName = (0, args_1.getStringFlag)(flags, ['display-name']);
210
+ if (!email) {
211
+ throw new Error('Usage: eve auth bootstrap --email <email> [--token <token>] [--ssh-key <path>] [--display-name <name>]');
212
+ }
213
+ // Check status and handle accordingly
214
+ if (status) {
215
+ if (status.completed) {
216
+ throw new Error('Bootstrap already completed. Use eve auth login instead.');
217
+ }
218
+ if (!status.requires_token && status.window_open) {
219
+ console.log(`Bootstrap window open (${status.mode} mode). Token not required.`);
220
+ }
221
+ else if (status.requires_token && !token) {
222
+ throw new Error('Bootstrap token required. Use --token <token> or set EVE_BOOTSTRAP_TOKEN');
223
+ }
224
+ }
225
+ else if (!token) {
226
+ // Could not fetch status, require token as fallback
227
+ throw new Error('Bootstrap token required. Use --token <token> or set EVE_BOOTSTRAP_TOKEN');
228
+ }
229
+ // Read SSH public key
230
+ if (!(0, node_fs_1.existsSync)(sshKeyPath)) {
231
+ throw new Error(`SSH public key not found: ${sshKeyPath}`);
232
+ }
233
+ const publicKey = (0, node_fs_1.readFileSync)(sshKeyPath, 'utf8').trim();
234
+ // POST to /auth/bootstrap
235
+ const bootstrapResponse = await (0, client_1.requestRaw)(context, '/auth/bootstrap', {
236
+ method: 'POST',
237
+ body: {
238
+ token: token ?? undefined,
239
+ email,
240
+ public_key: publicKey,
241
+ display_name: displayName,
242
+ },
243
+ });
244
+ if (!bootstrapResponse.ok) {
245
+ const message = typeof bootstrapResponse.data === 'string'
246
+ ? bootstrapResponse.data
247
+ : bootstrapResponse.text;
248
+ throw new Error(`Bootstrap failed: ${message}`);
249
+ }
250
+ const payload = bootstrapResponse.data;
251
+ if (!payload.access_token) {
252
+ throw new Error('Bootstrap response missing access_token');
253
+ }
254
+ credentials.profiles[context.profileName] = {
255
+ access_token: payload.access_token,
256
+ expires_at: payload.expires_at,
257
+ token_type: payload.token_type,
258
+ };
259
+ (0, config_1.saveCredentials)(credentials);
260
+ (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})`);
261
+ return;
262
+ }
263
+ case 'sync': {
264
+ const claudeOnly = (0, args_1.getBooleanFlag)(flags, ['claude']) ?? false;
265
+ const codexOnly = (0, args_1.getBooleanFlag)(flags, ['codex']) ?? false;
266
+ const dryRun = (0, args_1.getBooleanFlag)(flags, ['dry-run']) ?? false;
267
+ const system = (0, args_1.getBooleanFlag)(flags, ['system']) ?? false;
268
+ const projectIdFlag = (0, args_1.getStringFlag)(flags, ['project']);
269
+ const projectId = projectIdFlag ?? context.projectId;
270
+ if (!system && !projectId) {
271
+ throw new Error('No project specified. Use --project <id> or set default in profile, or use --system for system secrets');
272
+ }
273
+ const extractClaude = !codexOnly; // Extract Claude unless --codex is specified
274
+ const extractCodex = !claudeOnly; // Extract Codex unless --claude is specified
275
+ const extractedTokens = {};
276
+ const platform = process.platform;
277
+ // Extract Claude OAuth tokens
278
+ if (extractClaude) {
279
+ if (platform === 'darwin') {
280
+ try {
281
+ const output = (0, node_child_process_1.execSync)('security find-generic-password -s "anthropic.claude" -w', { encoding: 'utf8' }).trim();
282
+ if (output) {
283
+ extractedTokens.CLAUDE_OAUTH_TOKEN = output;
284
+ }
285
+ }
286
+ catch (error) {
287
+ // Token not found in keychain, continue
288
+ }
289
+ }
290
+ else {
291
+ // Linux: Check various credential file locations
292
+ const credentialPaths = [
293
+ `${(0, node_os_1.homedir)()}/.claude/.credentials.json`,
294
+ `${(0, node_os_1.homedir)()}/.claude/credentials.json`,
295
+ `${(0, node_os_1.homedir)()}/.config/claude/credentials.json`,
296
+ ];
297
+ for (const path of credentialPaths) {
298
+ if ((0, node_fs_1.existsSync)(path)) {
299
+ try {
300
+ const content = (0, node_fs_1.readFileSync)(path, 'utf8');
301
+ const creds = JSON.parse(content);
302
+ if (creds.oauth_token || creds.access_token) {
303
+ extractedTokens.CLAUDE_OAUTH_TOKEN = creds.oauth_token || creds.access_token;
304
+ break;
305
+ }
306
+ }
307
+ catch {
308
+ // Failed to parse, continue
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ // Extract Codex OAuth tokens
315
+ if (extractCodex) {
316
+ if (platform === 'darwin') {
317
+ try {
318
+ const output = (0, node_child_process_1.execSync)('security find-generic-password -s "openai.codex" -w', { encoding: 'utf8' }).trim();
319
+ if (output) {
320
+ extractedTokens.CODEX_OAUTH_TOKEN = output;
321
+ }
322
+ }
323
+ catch (error) {
324
+ // Token not found in keychain, continue
325
+ }
326
+ }
327
+ // Check ~/.codex/auth.json for Codex tokens (all platforms)
328
+ const codexAuthPath = `${(0, node_os_1.homedir)()}/.codex/auth.json`;
329
+ if ((0, node_fs_1.existsSync)(codexAuthPath)) {
330
+ try {
331
+ const content = (0, node_fs_1.readFileSync)(codexAuthPath, 'utf8');
332
+ const auth = JSON.parse(content);
333
+ if (auth.oauth_token || auth.access_token) {
334
+ extractedTokens.CODEX_OAUTH_TOKEN = auth.oauth_token || auth.access_token;
335
+ }
336
+ }
337
+ catch {
338
+ // Failed to parse, continue
339
+ }
340
+ }
341
+ }
342
+ if (Object.keys(extractedTokens).length === 0) {
343
+ (0, output_1.outputJson)({ extracted: 0, tokens: [] }, json, 'No tokens found on host machine');
344
+ return;
345
+ }
346
+ if (dryRun) {
347
+ const tokenList = Object.keys(extractedTokens).map(key => ({
348
+ name: key,
349
+ value: `${extractedTokens[key].substring(0, 10)}...`,
350
+ }));
351
+ (0, output_1.outputJson)({ dry_run: true, would_set: tokenList, target: system ? 'system' : `project ${projectId}` }, json, `Would set ${tokenList.length} token(s) on ${system ? 'system' : `project ${projectId}`}:\n${tokenList.map(t => ` - ${t.name}`).join('\n')}`);
352
+ return;
353
+ }
354
+ // Set secrets via API
355
+ const endpoint = system ? '/system/secrets' : `/projects/${projectId}/secrets`;
356
+ const results = [];
357
+ for (const [name, value] of Object.entries(extractedTokens)) {
358
+ try {
359
+ await (0, client_1.requestJson)(context, endpoint, {
360
+ method: 'POST',
361
+ body: {
362
+ name,
363
+ value,
364
+ },
365
+ });
366
+ results.push({ name, success: true });
367
+ }
368
+ catch (error) {
369
+ results.push({
370
+ name,
371
+ success: false,
372
+ error: error instanceof Error ? error.message : String(error),
373
+ });
374
+ }
375
+ }
376
+ const successCount = results.filter(r => r.success).length;
377
+ const failCount = results.filter(r => !r.success).length;
378
+ (0, output_1.outputJson)({
379
+ target: system ? 'system' : `project ${projectId}`,
380
+ results,
381
+ success: successCount,
382
+ failed: failCount,
383
+ }, json, `✓ Set ${successCount} secret(s) on ${system ? 'system' : `project ${projectId}`}${failCount > 0 ? ` (${failCount} failed)` : ''}`);
384
+ return;
385
+ }
96
386
  default:
97
- throw new Error('Usage: eve auth <login|logout|status|whoami>');
387
+ throw new Error('Usage: eve auth <login|logout|status|whoami|bootstrap|sync>');
388
+ }
389
+ }
390
+ function signNonceWithSsh(keyPath, nonce) {
391
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'eve-auth-'));
392
+ const noncePath = (0, node_path_1.join)(tempDir, 'nonce');
393
+ const signaturePath = `${noncePath}.sig`;
394
+ try {
395
+ (0, node_fs_1.writeFileSync)(noncePath, nonce);
396
+ const result = (0, node_child_process_1.spawnSync)('ssh-keygen', ['-Y', 'sign', '-f', keyPath, '-n', 'eve-auth', noncePath], {
397
+ encoding: 'utf8',
398
+ });
399
+ if (result.status !== 0) {
400
+ throw new Error(`ssh-keygen failed: ${result.stderr || 'unknown error'}`);
401
+ }
402
+ return (0, node_fs_1.readFileSync)(signaturePath, 'utf8');
403
+ }
404
+ finally {
405
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
406
+ }
407
+ }
408
+ async function attemptSshLogin(context, credentials, flags, email, userId) {
409
+ const challengeResponse = await (0, client_1.requestRaw)(context, '/auth/challenge', {
410
+ method: 'POST',
411
+ body: {
412
+ email,
413
+ user_id: userId,
414
+ },
415
+ });
416
+ if (!challengeResponse.ok) {
417
+ const message = typeof challengeResponse.data === 'string'
418
+ ? challengeResponse.data
419
+ : challengeResponse.text;
420
+ return { success: false, error: `Challenge failed: ${message}` };
421
+ }
422
+ const challenge = challengeResponse.data;
423
+ if (!challenge.challenge_id || !challenge.nonce) {
424
+ return { success: false, error: 'Challenge response missing fields' };
425
+ }
426
+ const sshKeyPath = (0, args_1.getStringFlag)(flags, ['ssh-key']) ||
427
+ process.env.EVE_AUTH_SSH_KEY ||
428
+ context.profile.default_ssh_key ||
429
+ (0, node_path_1.join)((0, node_os_1.homedir)(), '.ssh', 'id_ed25519');
430
+ let signature;
431
+ try {
432
+ signature = signNonceWithSsh(sshKeyPath, challenge.nonce);
433
+ }
434
+ catch (err) {
435
+ const msg = err instanceof Error ? err.message : String(err);
436
+ return { success: false, error: `Failed to sign challenge: ${msg}` };
437
+ }
438
+ const verifyResponse = await (0, client_1.requestRaw)(context, '/auth/verify', {
439
+ method: 'POST',
440
+ body: { challenge_id: challenge.challenge_id, signature },
441
+ });
442
+ if (!verifyResponse.ok) {
443
+ const message = typeof verifyResponse.data === 'string'
444
+ ? verifyResponse.data
445
+ : verifyResponse.text;
446
+ return { success: false, error: `Auth verify failed: ${message}` };
447
+ }
448
+ const payload = verifyResponse.data;
449
+ if (!payload.access_token) {
450
+ return { success: false, error: 'Auth verify response missing access_token' };
451
+ }
452
+ credentials.profiles[context.profileName] = {
453
+ access_token: payload.access_token,
454
+ expires_at: payload.expires_at,
455
+ token_type: payload.token_type,
456
+ };
457
+ (0, config_1.saveCredentials)(credentials);
458
+ return { success: true, tokenType: payload.token_type };
459
+ }
460
+ async function fetchGitHubKeys(username) {
461
+ const response = await fetch(`https://github.com/${username}.keys`);
462
+ if (!response.ok) {
463
+ if (response.status === 404) {
464
+ throw new Error(`GitHub user not found: ${username}`);
465
+ }
466
+ throw new Error(`Failed to fetch GitHub keys: HTTP ${response.status}`);
467
+ }
468
+ const text = await response.text();
469
+ return text.trim().split('\n').filter(k => k.length > 0);
470
+ }
471
+ async function offerGitHubKeyRegistration(context, email) {
472
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
473
+ try {
474
+ console.log('\nNo registered SSH key found for this user.');
475
+ const username = await rl.question('Enter GitHub username to register keys (or press Enter to skip): ');
476
+ if (!username.trim()) {
477
+ return false;
478
+ }
479
+ let keys;
480
+ try {
481
+ keys = await fetchGitHubKeys(username.trim());
482
+ }
483
+ catch (err) {
484
+ const msg = err instanceof Error ? err.message : String(err);
485
+ console.error(`Failed to fetch GitHub keys: ${msg}`);
486
+ return false;
487
+ }
488
+ if (keys.length === 0) {
489
+ console.log(`No SSH keys found for github.com/${username}`);
490
+ return false;
491
+ }
492
+ console.log(`\nFound ${keys.length} SSH key(s) for github.com/${username.trim()}`);
493
+ const confirm = await rl.question('Register them? [Y/n]: ');
494
+ if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
495
+ return false;
496
+ }
497
+ // Register each key
498
+ let registered = 0;
499
+ for (const publicKey of keys) {
500
+ try {
501
+ await (0, client_1.requestJson)(context, '/auth/identities', {
502
+ method: 'POST',
503
+ body: {
504
+ email,
505
+ public_key: publicKey,
506
+ label: `github-${username.trim()}`,
507
+ },
508
+ });
509
+ registered += 1;
510
+ }
511
+ catch (err) {
512
+ const msg = err instanceof Error ? err.message : String(err);
513
+ console.error(`Failed to register key: ${msg}`);
514
+ }
515
+ }
516
+ if (registered === 0) {
517
+ console.log('No keys were registered');
518
+ return false;
519
+ }
520
+ console.log(`✓ Registered ${registered} SSH key(s)`);
521
+ return true;
522
+ }
523
+ finally {
524
+ rl.close();
98
525
  }
99
526
  }
@@ -6,7 +6,6 @@ const client_1 = require("../lib/client");
6
6
  const output_1 = require("../lib/output");
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
- const yaml_1 = require("yaml");
10
9
  async function handleDb(subcommand, positionals, flags, context) {
11
10
  const jsonOutput = Boolean(flags.json);
12
11
  switch (subcommand) {
@@ -16,14 +15,11 @@ async function handleDb(subcommand, positionals, flags, context) {
16
15
  return handleRls(positionals, flags, context, jsonOutput);
17
16
  case 'sql':
18
17
  return handleSql(positionals, flags, context, jsonOutput);
19
- case 'migrate':
20
- return handleMigrate(positionals, flags, context, jsonOutput);
21
18
  default:
22
- throw new Error('Usage: eve db <schema|rls|sql|migrate> --env <name> [options]\n' +
19
+ throw new Error('Usage: eve db <schema|rls|sql> --env <name> [options]\n' +
23
20
  ' schema --env <name> - show DB schema info\n' +
24
21
  ' rls --env <name> - show RLS policies and tables\n' +
25
- ' sql --env <name> --sql <stmt> [--write] - run parameterized SQL\n' +
26
- ' migrate --env <name> [--component <name>] [--path <dir>] - apply pending migrations');
22
+ ' sql --env <name> --sql <stmt> [--write] - run parameterized SQL');
27
23
  }
28
24
  }
29
25
  async function handleSchema(positionals, flags, context, jsonOutput) {
@@ -67,20 +63,6 @@ async function handleSql(positionals, flags, context, jsonOutput) {
67
63
  });
68
64
  (0, output_1.outputJson)(response, jsonOutput);
69
65
  }
70
- async function handleMigrate(positionals, flags, context, jsonOutput) {
71
- const { projectId, envName } = resolveProjectEnv(positionals, flags, context);
72
- const manifest = loadLocalManifest();
73
- const migrationsPath = getMigrationsPath(flags, manifest);
74
- const migrations = loadMigrations(migrationsPath);
75
- if (migrations.length === 0) {
76
- throw new Error(`No migrations found in ${migrationsPath}`);
77
- }
78
- const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/migrate`, {
79
- method: 'POST',
80
- body: { migrations },
81
- });
82
- (0, output_1.outputJson)(response, jsonOutput);
83
- }
84
66
  function resolveProjectEnv(positionals, flags, context) {
85
67
  const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
86
68
  const envName = (0, args_1.getStringFlag)(flags, ['env']) ?? positionals[0];
@@ -89,43 +71,6 @@ function resolveProjectEnv(positionals, flags, context) {
89
71
  }
90
72
  return { projectId, envName };
91
73
  }
92
- function loadLocalManifest() {
93
- const manifestPath = (0, node_path_1.resolve)('.eve/manifest.yaml');
94
- if (!(0, node_fs_1.existsSync)(manifestPath)) {
95
- return undefined;
96
- }
97
- const content = (0, node_fs_1.readFileSync)(manifestPath, 'utf-8');
98
- return (0, yaml_1.parse)(content);
99
- }
100
- function getMigrationsPath(flags, manifest) {
101
- // 1. --path flag takes precedence
102
- const pathFlag = (0, args_1.getStringFlag)(flags, ['path']);
103
- if (pathFlag) {
104
- return (0, node_path_1.resolve)(pathFlag);
105
- }
106
- // 2. Check --component flag or find first database component
107
- const componentName = (0, args_1.getStringFlag)(flags, ['component']);
108
- const components = manifest?.components;
109
- if (components) {
110
- if (componentName) {
111
- // Specific component requested
112
- const component = components[componentName];
113
- if (component?.migrations?.path) {
114
- return (0, node_path_1.resolve)(component.migrations.path);
115
- }
116
- }
117
- else {
118
- // Find first database component with migrations
119
- for (const [name, component] of Object.entries(components)) {
120
- if (component?.type === 'database' && component?.migrations?.path) {
121
- return (0, node_path_1.resolve)(component.migrations.path);
122
- }
123
- }
124
- }
125
- }
126
- // 3. Default to .eve/migrations
127
- return (0, node_path_1.resolve)('.eve/migrations');
128
- }
129
74
  function parseJson(value, label) {
130
75
  try {
131
76
  return JSON.parse(value);
@@ -134,16 +79,3 @@ function parseJson(value, label) {
134
79
  throw new Error(`${label} must be valid JSON: ${error.message}`);
135
80
  }
136
81
  }
137
- function loadMigrations(dir) {
138
- if (!(0, node_fs_1.existsSync)(dir)) {
139
- throw new Error(`Migrations directory not found: ${dir}`);
140
- }
141
- const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })
142
- .filter((entry) => entry.isFile() && entry.name.endsWith('.sql'))
143
- .map((entry) => entry.name)
144
- .sort();
145
- return entries.map((name) => ({
146
- name,
147
- sql: (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(dir, name), 'utf-8'),
148
- }));
149
- }