@eve-horizon/cli 0.2.0 → 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/dist/commands/admin.js +15 -18
- package/dist/commands/auth.js +277 -42
- package/dist/commands/env.js +189 -5
- package/dist/commands/manifest.js +86 -0
- package/dist/index.js +4 -0
- package/dist/lib/help.js +63 -7
- package/package.json +1 -1
package/dist/commands/admin.js
CHANGED
|
@@ -18,11 +18,26 @@ async function handleAdmin(subcommand, positionals, flags, context) {
|
|
|
18
18
|
if (!['owner', 'admin', 'member'].includes(role)) {
|
|
19
19
|
throw new Error(`Invalid role: ${role}. Must be one of: owner, admin, member`);
|
|
20
20
|
}
|
|
21
|
+
if (!orgId) {
|
|
22
|
+
throw new Error('No org specified. Use --org <org_id> or set a default org in your profile.');
|
|
23
|
+
}
|
|
21
24
|
const results = {
|
|
22
25
|
keys_registered: 0,
|
|
23
26
|
identities: [],
|
|
24
27
|
};
|
|
28
|
+
// Add user to org first - this creates the user if they don't exist
|
|
29
|
+
if (orgId) {
|
|
30
|
+
const membership = await (0, client_1.requestJson)(context, `/orgs/${orgId}/members`, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
body: {
|
|
33
|
+
email,
|
|
34
|
+
role,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
results.membership = membership;
|
|
38
|
+
}
|
|
25
39
|
// Fetch and register GitHub SSH keys if username provided
|
|
40
|
+
// Now that user exists (created via org membership), identity registration will work
|
|
26
41
|
if (githubUsername) {
|
|
27
42
|
const keys = await fetchGitHubKeys(githubUsername);
|
|
28
43
|
if (keys.length === 0) {
|
|
@@ -41,24 +56,6 @@ async function handleAdmin(subcommand, positionals, flags, context) {
|
|
|
41
56
|
results.keys_registered += 1;
|
|
42
57
|
}
|
|
43
58
|
}
|
|
44
|
-
// Add user to org if org_id provided
|
|
45
|
-
if (orgId) {
|
|
46
|
-
// Get user_id from the first registered identity, or look up by email
|
|
47
|
-
let userId;
|
|
48
|
-
if (results.identities.length > 0) {
|
|
49
|
-
userId = results.identities[0].user_id;
|
|
50
|
-
}
|
|
51
|
-
if (userId) {
|
|
52
|
-
const membership = await (0, client_1.requestJson)(context, `/orgs/${orgId}/members`, {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
body: {
|
|
55
|
-
user_id: userId,
|
|
56
|
-
role,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
results.membership = membership;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
59
|
const summary = [
|
|
63
60
|
`Invited ${email}`,
|
|
64
61
|
results.keys_registered > 0 ? `${results.keys_registered} SSH key(s) registered` : null,
|
package/dist/commands/auth.js
CHANGED
|
@@ -275,11 +275,26 @@ async function handleAuth(subcommand, flags, context, credentials) {
|
|
|
275
275
|
const claudeOnly = (0, args_1.getBooleanFlag)(flags, ['claude']) ?? false;
|
|
276
276
|
const codexOnly = (0, args_1.getBooleanFlag)(flags, ['codex']) ?? false;
|
|
277
277
|
const dryRun = (0, args_1.getBooleanFlag)(flags, ['dry-run']) ?? false;
|
|
278
|
-
const
|
|
278
|
+
const orgIdFlag = (0, args_1.getStringFlag)(flags, ['org']);
|
|
279
279
|
const projectIdFlag = (0, args_1.getStringFlag)(flags, ['project']);
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
280
|
+
let scope;
|
|
281
|
+
if (projectIdFlag) {
|
|
282
|
+
scope = { type: 'project', projectId: projectIdFlag };
|
|
283
|
+
}
|
|
284
|
+
else if (orgIdFlag) {
|
|
285
|
+
scope = { type: 'org', orgId: orgIdFlag };
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// Default to user scope - need to fetch user ID
|
|
289
|
+
const meResponse = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true });
|
|
290
|
+
if (!meResponse.ok) {
|
|
291
|
+
throw new Error('Not authenticated. Run "eve auth login" first.');
|
|
292
|
+
}
|
|
293
|
+
const meData = meResponse.data;
|
|
294
|
+
if (!meData.user_id) {
|
|
295
|
+
throw new Error('Could not determine user ID. Try specifying --org or --project instead.');
|
|
296
|
+
}
|
|
297
|
+
scope = { type: 'user', userId: meData.user_id };
|
|
283
298
|
}
|
|
284
299
|
const extractClaude = !codexOnly; // Extract Claude unless --codex is specified
|
|
285
300
|
const extractCodex = !claudeOnly; // Extract Codex unless --claude is specified
|
|
@@ -287,31 +302,42 @@ async function handleAuth(subcommand, flags, context, credentials) {
|
|
|
287
302
|
const platform = process.platform;
|
|
288
303
|
// Extract Claude OAuth tokens
|
|
289
304
|
if (extractClaude) {
|
|
305
|
+
// Try macOS Keychain first
|
|
290
306
|
if (platform === 'darwin') {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
307
|
+
for (const service of ['Claude Code-credentials', 'anthropic.claude']) {
|
|
308
|
+
try {
|
|
309
|
+
const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
|
|
310
|
+
if (output) {
|
|
311
|
+
extractedTokens.CLAUDE_CODE_OAUTH_TOKEN = output;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Token not found in keychain, continue
|
|
295
317
|
}
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
// Token not found in keychain, continue
|
|
299
318
|
}
|
|
300
319
|
}
|
|
301
|
-
|
|
302
|
-
|
|
320
|
+
// Check credential files (all platforms)
|
|
321
|
+
if (!extractedTokens.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
303
322
|
const credentialPaths = [
|
|
304
323
|
`${(0, node_os_1.homedir)()}/.claude/.credentials.json`,
|
|
305
324
|
`${(0, node_os_1.homedir)()}/.claude/credentials.json`,
|
|
306
325
|
`${(0, node_os_1.homedir)()}/.config/claude/credentials.json`,
|
|
307
326
|
];
|
|
308
|
-
for (const
|
|
309
|
-
if ((0, node_fs_1.existsSync)(
|
|
327
|
+
for (const credPath of credentialPaths) {
|
|
328
|
+
if ((0, node_fs_1.existsSync)(credPath)) {
|
|
310
329
|
try {
|
|
311
|
-
const content = (0, node_fs_1.readFileSync)(
|
|
330
|
+
const content = (0, node_fs_1.readFileSync)(credPath, 'utf8');
|
|
312
331
|
const creds = JSON.parse(content);
|
|
332
|
+
// Handle nested claudeAiOauth format (current Claude Code format)
|
|
333
|
+
const claudeOauth = creds.claudeAiOauth;
|
|
334
|
+
if (claudeOauth?.accessToken) {
|
|
335
|
+
extractedTokens.CLAUDE_CODE_OAUTH_TOKEN = claudeOauth.accessToken;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
// Fallback to legacy root-level tokens
|
|
313
339
|
if (creds.oauth_token || creds.access_token) {
|
|
314
|
-
extractedTokens.
|
|
340
|
+
extractedTokens.CLAUDE_CODE_OAUTH_TOKEN = (creds.oauth_token || creds.access_token);
|
|
315
341
|
break;
|
|
316
342
|
}
|
|
317
343
|
}
|
|
@@ -322,48 +348,78 @@ async function handleAuth(subcommand, flags, context, credentials) {
|
|
|
322
348
|
}
|
|
323
349
|
}
|
|
324
350
|
}
|
|
325
|
-
// Extract Codex OAuth tokens
|
|
351
|
+
// Extract Codex/Code OAuth tokens
|
|
326
352
|
if (extractCodex) {
|
|
353
|
+
// Try macOS Keychain first
|
|
327
354
|
if (platform === 'darwin') {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
355
|
+
for (const service of ['openai.codex', 'Code-credentials']) {
|
|
356
|
+
try {
|
|
357
|
+
const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
|
|
358
|
+
if (output) {
|
|
359
|
+
extractedTokens.CODEX_OAUTH_ACCESS_TOKEN = output;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Token not found in keychain, continue
|
|
332
365
|
}
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
// Token not found in keychain, continue
|
|
336
366
|
}
|
|
337
367
|
}
|
|
338
|
-
// Check ~/.codex/auth.json
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
368
|
+
// Check auth files (all platforms): ~/.codex/auth.json and ~/.code/auth.json
|
|
369
|
+
if (!extractedTokens.CODEX_OAUTH_ACCESS_TOKEN) {
|
|
370
|
+
const codexAuthPaths = [
|
|
371
|
+
`${(0, node_os_1.homedir)()}/.codex/auth.json`,
|
|
372
|
+
`${(0, node_os_1.homedir)()}/.code/auth.json`,
|
|
373
|
+
];
|
|
374
|
+
for (const authPath of codexAuthPaths) {
|
|
375
|
+
if ((0, node_fs_1.existsSync)(authPath)) {
|
|
376
|
+
try {
|
|
377
|
+
const content = (0, node_fs_1.readFileSync)(authPath, 'utf8');
|
|
378
|
+
const auth = JSON.parse(content);
|
|
379
|
+
// Handle nested tokens format (current Codex CLI format)
|
|
380
|
+
const tokens = auth.tokens;
|
|
381
|
+
if (tokens?.access_token) {
|
|
382
|
+
extractedTokens.CODEX_OAUTH_ACCESS_TOKEN = tokens.access_token;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
// Fallback to root-level tokens
|
|
386
|
+
if (auth.oauth_token || auth.access_token) {
|
|
387
|
+
extractedTokens.CODEX_OAUTH_ACCESS_TOKEN = (auth.oauth_token || auth.access_token);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
// Also check for OPENAI_API_KEY in the auth file
|
|
391
|
+
if (auth.OPENAI_API_KEY && typeof auth.OPENAI_API_KEY === 'string') {
|
|
392
|
+
extractedTokens.OPENAI_API_KEY = auth.OPENAI_API_KEY;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// Failed to parse, continue
|
|
398
|
+
}
|
|
346
399
|
}
|
|
347
400
|
}
|
|
348
|
-
catch {
|
|
349
|
-
// Failed to parse, continue
|
|
350
|
-
}
|
|
351
401
|
}
|
|
352
402
|
}
|
|
353
403
|
if (Object.keys(extractedTokens).length === 0) {
|
|
354
404
|
(0, output_1.outputJson)({ extracted: 0, tokens: [] }, json, 'No tokens found on host machine');
|
|
355
405
|
return;
|
|
356
406
|
}
|
|
407
|
+
// Build target label for output
|
|
408
|
+
const targetLabel = scope.type === 'user' ? 'user' :
|
|
409
|
+
scope.type === 'org' ? `org ${scope.orgId}` :
|
|
410
|
+
`project ${scope.projectId}`;
|
|
357
411
|
if (dryRun) {
|
|
358
412
|
const tokenList = Object.keys(extractedTokens).map(key => ({
|
|
359
413
|
name: key,
|
|
360
414
|
value: `${extractedTokens[key].substring(0, 10)}...`,
|
|
361
415
|
}));
|
|
362
|
-
(0, output_1.outputJson)({ dry_run: true, would_set: tokenList, target:
|
|
416
|
+
(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')}`);
|
|
363
417
|
return;
|
|
364
418
|
}
|
|
365
|
-
// Set secrets via API
|
|
366
|
-
const endpoint =
|
|
419
|
+
// Set secrets via API - endpoint depends on scope
|
|
420
|
+
const endpoint = scope.type === 'user' ? `/users/${scope.userId}/secrets` :
|
|
421
|
+
scope.type === 'org' ? `/orgs/${scope.orgId}/secrets` :
|
|
422
|
+
`/projects/${scope.projectId}/secrets`;
|
|
367
423
|
const results = [];
|
|
368
424
|
for (const [name, value] of Object.entries(extractedTokens)) {
|
|
369
425
|
try {
|
|
@@ -387,15 +443,194 @@ async function handleAuth(subcommand, flags, context, credentials) {
|
|
|
387
443
|
const successCount = results.filter(r => r.success).length;
|
|
388
444
|
const failCount = results.filter(r => !r.success).length;
|
|
389
445
|
(0, output_1.outputJson)({
|
|
390
|
-
target:
|
|
446
|
+
target: targetLabel,
|
|
447
|
+
scope,
|
|
391
448
|
results,
|
|
392
449
|
success: successCount,
|
|
393
450
|
failed: failCount,
|
|
394
|
-
}, json, `✓ Set ${successCount} secret(s) on ${
|
|
451
|
+
}, json, `✓ Set ${successCount} secret(s) on ${targetLabel}${failCount > 0 ? ` (${failCount} failed)` : ''}`);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
case 'creds': {
|
|
455
|
+
// Show local credential status without syncing
|
|
456
|
+
const claudeOnly = (0, args_1.getBooleanFlag)(flags, ['claude']) ?? false;
|
|
457
|
+
const codexOnly = (0, args_1.getBooleanFlag)(flags, ['codex']) ?? false;
|
|
458
|
+
const checkClaude = !codexOnly;
|
|
459
|
+
const checkCodex = !claudeOnly;
|
|
460
|
+
const credentials = [];
|
|
461
|
+
const plat = process.platform;
|
|
462
|
+
// Check Claude credentials
|
|
463
|
+
if (checkClaude) {
|
|
464
|
+
let claudeFound = false;
|
|
465
|
+
let claudeSource = '';
|
|
466
|
+
let claudePreview = '';
|
|
467
|
+
let claudeExpires;
|
|
468
|
+
// Try macOS Keychain
|
|
469
|
+
if (plat === 'darwin' && !claudeFound) {
|
|
470
|
+
for (const service of ['Claude Code-credentials', 'anthropic.claude']) {
|
|
471
|
+
try {
|
|
472
|
+
const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
|
|
473
|
+
if (output) {
|
|
474
|
+
claudeFound = true;
|
|
475
|
+
claudeSource = `macOS Keychain (${service})`;
|
|
476
|
+
claudePreview = output.substring(0, 15) + '...';
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
// Not found
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Check credential files
|
|
486
|
+
if (!claudeFound) {
|
|
487
|
+
const credentialPaths = [
|
|
488
|
+
`${(0, node_os_1.homedir)()}/.claude/.credentials.json`,
|
|
489
|
+
`${(0, node_os_1.homedir)()}/.claude/credentials.json`,
|
|
490
|
+
`${(0, node_os_1.homedir)()}/.config/claude/credentials.json`,
|
|
491
|
+
];
|
|
492
|
+
for (const credPath of credentialPaths) {
|
|
493
|
+
if ((0, node_fs_1.existsSync)(credPath)) {
|
|
494
|
+
try {
|
|
495
|
+
const content = (0, node_fs_1.readFileSync)(credPath, 'utf8');
|
|
496
|
+
const creds = JSON.parse(content);
|
|
497
|
+
const claudeOauth = creds.claudeAiOauth;
|
|
498
|
+
if (claudeOauth?.accessToken) {
|
|
499
|
+
claudeFound = true;
|
|
500
|
+
claudeSource = credPath.replace((0, node_os_1.homedir)(), '~');
|
|
501
|
+
claudePreview = claudeOauth.accessToken.substring(0, 15) + '...';
|
|
502
|
+
if (claudeOauth.expiresAt) {
|
|
503
|
+
const expDate = new Date(claudeOauth.expiresAt);
|
|
504
|
+
claudeExpires = expDate.toISOString();
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
if (creds.oauth_token || creds.access_token) {
|
|
509
|
+
claudeFound = true;
|
|
510
|
+
claudeSource = credPath.replace((0, node_os_1.homedir)(), '~');
|
|
511
|
+
const token = (creds.oauth_token || creds.access_token);
|
|
512
|
+
claudePreview = token.substring(0, 15) + '...';
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
// Failed to parse
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
credentials.push({
|
|
523
|
+
name: 'Claude Code OAuth',
|
|
524
|
+
source: claudeFound ? claudeSource : 'not found',
|
|
525
|
+
found: claudeFound,
|
|
526
|
+
preview: claudePreview || undefined,
|
|
527
|
+
expiresAt: claudeExpires,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
// Check Codex credentials
|
|
531
|
+
if (checkCodex) {
|
|
532
|
+
let codexFound = false;
|
|
533
|
+
let codexSource = '';
|
|
534
|
+
let codexPreview = '';
|
|
535
|
+
// Try macOS Keychain
|
|
536
|
+
if (plat === 'darwin' && !codexFound) {
|
|
537
|
+
for (const service of ['openai.codex', 'Code-credentials']) {
|
|
538
|
+
try {
|
|
539
|
+
const output = (0, node_child_process_1.execSync)(`security find-generic-password -s "${service}" -w`, { encoding: 'utf8' }).trim();
|
|
540
|
+
if (output) {
|
|
541
|
+
codexFound = true;
|
|
542
|
+
codexSource = `macOS Keychain (${service})`;
|
|
543
|
+
codexPreview = output.substring(0, 15) + '...';
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
// Not found
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Check auth files
|
|
553
|
+
if (!codexFound) {
|
|
554
|
+
const codexAuthPaths = [
|
|
555
|
+
`${(0, node_os_1.homedir)()}/.codex/auth.json`,
|
|
556
|
+
`${(0, node_os_1.homedir)()}/.code/auth.json`,
|
|
557
|
+
];
|
|
558
|
+
for (const authPath of codexAuthPaths) {
|
|
559
|
+
if ((0, node_fs_1.existsSync)(authPath)) {
|
|
560
|
+
try {
|
|
561
|
+
const content = (0, node_fs_1.readFileSync)(authPath, 'utf8');
|
|
562
|
+
const auth = JSON.parse(content);
|
|
563
|
+
const tokens = auth.tokens;
|
|
564
|
+
if (tokens?.access_token) {
|
|
565
|
+
codexFound = true;
|
|
566
|
+
codexSource = authPath.replace((0, node_os_1.homedir)(), '~');
|
|
567
|
+
codexPreview = tokens.access_token.substring(0, 15) + '...';
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
if (auth.oauth_token || auth.access_token) {
|
|
571
|
+
codexFound = true;
|
|
572
|
+
codexSource = authPath.replace((0, node_os_1.homedir)(), '~');
|
|
573
|
+
const token = (auth.oauth_token || auth.access_token);
|
|
574
|
+
codexPreview = token.substring(0, 15) + '...';
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
if (auth.OPENAI_API_KEY && typeof auth.OPENAI_API_KEY === 'string') {
|
|
578
|
+
codexFound = true;
|
|
579
|
+
codexSource = authPath.replace((0, node_os_1.homedir)(), '~') + ' (API key)';
|
|
580
|
+
codexPreview = auth.OPENAI_API_KEY.substring(0, 10) + '...';
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// Failed to parse
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
credentials.push({
|
|
591
|
+
name: 'Codex/Code OAuth',
|
|
592
|
+
source: codexFound ? codexSource : 'not found',
|
|
593
|
+
found: codexFound,
|
|
594
|
+
preview: codexPreview || undefined,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
const foundCount = credentials.filter(c => c.found).length;
|
|
598
|
+
if (json) {
|
|
599
|
+
(0, output_1.outputJson)({ credentials, found: foundCount }, json);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
console.log('Local AI Tool Credentials:');
|
|
603
|
+
console.log('');
|
|
604
|
+
for (const cred of credentials) {
|
|
605
|
+
const status = cred.found ? '✓' : '✗';
|
|
606
|
+
console.log(` ${status} ${cred.name}`);
|
|
607
|
+
console.log(` Source: ${cred.source}`);
|
|
608
|
+
if (cred.preview) {
|
|
609
|
+
console.log(` Token: ${cred.preview}`);
|
|
610
|
+
}
|
|
611
|
+
if (cred.expiresAt) {
|
|
612
|
+
const expDate = new Date(cred.expiresAt);
|
|
613
|
+
const now = new Date();
|
|
614
|
+
const isExpired = expDate < now;
|
|
615
|
+
const expLabel = isExpired ? '(expired)' : '';
|
|
616
|
+
console.log(` Expires: ${cred.expiresAt} ${expLabel}`);
|
|
617
|
+
}
|
|
618
|
+
console.log('');
|
|
619
|
+
}
|
|
620
|
+
if (foundCount > 0) {
|
|
621
|
+
console.log(`Found ${foundCount} credential(s). Run 'eve auth sync' to sync to Eve.`);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
console.log('No local credentials found.');
|
|
625
|
+
console.log('');
|
|
626
|
+
console.log('To set up credentials:');
|
|
627
|
+
console.log(' Claude: Run "claude" CLI and log in');
|
|
628
|
+
console.log(' Codex: Run "codex" CLI and log in');
|
|
629
|
+
}
|
|
395
630
|
return;
|
|
396
631
|
}
|
|
397
632
|
default:
|
|
398
|
-
throw new Error('Usage: eve auth <login|logout|status|whoami|bootstrap|sync|token>');
|
|
633
|
+
throw new Error('Usage: eve auth <login|logout|status|whoami|bootstrap|sync|creds|token>');
|
|
399
634
|
}
|
|
400
635
|
}
|
|
401
636
|
function signNonceWithSsh(keyPath, nonce) {
|
package/dist/commands/env.js
CHANGED
|
@@ -55,6 +55,8 @@ async function handleEnv(subcommand, positionals, flags, context) {
|
|
|
55
55
|
return handleDeploy(positionals, flags, context, json);
|
|
56
56
|
case 'logs':
|
|
57
57
|
return handleLogs(positionals, flags, context, json);
|
|
58
|
+
case 'diagnose':
|
|
59
|
+
return handleDiagnose(positionals, flags, context, json);
|
|
58
60
|
case 'delete':
|
|
59
61
|
return handleDelete(positionals, flags, context, json);
|
|
60
62
|
default:
|
|
@@ -64,6 +66,7 @@ async function handleEnv(subcommand, positionals, flags, context) {
|
|
|
64
66
|
' create <name> --type=<type> [options] - create an environment\n' +
|
|
65
67
|
' deploy <env> --ref <sha> [--direct] [--inputs <json>] - deploy to an environment\n' +
|
|
66
68
|
' logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>] - get service logs\n' +
|
|
69
|
+
' diagnose <project> <env> - diagnose deployment health and events\n' +
|
|
67
70
|
' delete <name> [--project=<id>] [--force] - delete an environment');
|
|
68
71
|
}
|
|
69
72
|
}
|
|
@@ -106,11 +109,12 @@ async function handleShow(positionals, flags, context, json) {
|
|
|
106
109
|
throw new Error('Usage: eve env show <project> <name> [--project=<id>] [--name=<name>]');
|
|
107
110
|
}
|
|
108
111
|
const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}`);
|
|
112
|
+
const health = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/health`);
|
|
109
113
|
if (json) {
|
|
110
|
-
(0, output_1.outputJson)(response, json);
|
|
114
|
+
(0, output_1.outputJson)({ ...response, health }, json);
|
|
111
115
|
}
|
|
112
116
|
else {
|
|
113
|
-
formatEnvironmentDetails(response);
|
|
117
|
+
formatEnvironmentDetails(response, health);
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
/**
|
|
@@ -241,11 +245,46 @@ async function handleDeploy(positionals, flags, context, json) {
|
|
|
241
245
|
(0, output_1.outputJson)(response, json);
|
|
242
246
|
}
|
|
243
247
|
else {
|
|
248
|
+
if (response.pipeline_run) {
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log('Pipeline deployment queued.');
|
|
251
|
+
console.log(` Pipeline Run: ${response.pipeline_run.run.id}`);
|
|
252
|
+
console.log(` Pipeline: ${response.pipeline_run.run.pipeline_name}`);
|
|
253
|
+
console.log(` Status: ${response.pipeline_run.run.status}`);
|
|
254
|
+
console.log(` Environment: ${response.environment.name}`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
244
257
|
console.log('');
|
|
245
|
-
console.log(`Deployment
|
|
246
|
-
|
|
258
|
+
console.log(`Deployment submitted.`);
|
|
259
|
+
if (response.release) {
|
|
260
|
+
console.log(` Release ID: ${response.release.id}`);
|
|
261
|
+
}
|
|
247
262
|
console.log(` Environment: ${response.environment.name}`);
|
|
248
263
|
console.log(` Namespace: ${response.environment.namespace || '(none)'}`);
|
|
264
|
+
if (response.deployment_status?.k8s_status) {
|
|
265
|
+
const status = response.deployment_status.k8s_status;
|
|
266
|
+
const readiness = `${status.available_replicas}/${status.desired_replicas}`;
|
|
267
|
+
console.log(` Status: ${response.deployment_status.state} (${readiness} ready)`);
|
|
268
|
+
}
|
|
269
|
+
else if (response.deployment_status?.state) {
|
|
270
|
+
console.log(` Status: ${response.deployment_status.state}`);
|
|
271
|
+
}
|
|
272
|
+
const warnings = response.warnings ?? getDeploymentWarnings(response.deployment_status);
|
|
273
|
+
if (warnings.length > 0) {
|
|
274
|
+
console.log('');
|
|
275
|
+
console.log('Warnings:');
|
|
276
|
+
for (const warning of warnings) {
|
|
277
|
+
console.log(` - ${warning}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const watchFlag = (0, args_1.toBoolean)(flags.watch);
|
|
281
|
+
const shouldWatch = (watchFlag ?? true)
|
|
282
|
+
&& response.deployment_status?.state !== 'ready';
|
|
283
|
+
if (shouldWatch) {
|
|
284
|
+
const timeoutRaw = (0, args_1.getStringFlag)(flags, ['timeout']);
|
|
285
|
+
const timeoutSeconds = timeoutRaw ? parseInt(timeoutRaw, 10) : 120;
|
|
286
|
+
await watchDeploymentStatus(context, projectId, envName, Number.isFinite(timeoutSeconds) ? timeoutSeconds : 120);
|
|
287
|
+
}
|
|
249
288
|
}
|
|
250
289
|
}
|
|
251
290
|
/**
|
|
@@ -277,6 +316,26 @@ async function handleLogs(positionals, flags, context, json) {
|
|
|
277
316
|
console.log(`[${entry.timestamp}] ${entry.line}`);
|
|
278
317
|
}
|
|
279
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* eve env diagnose <project> <env>
|
|
321
|
+
* Diagnose deployment health for an environment (k8s-only)
|
|
322
|
+
*/
|
|
323
|
+
async function handleDiagnose(positionals, flags, context, json) {
|
|
324
|
+
const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
|
|
325
|
+
const envName = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['env', 'name']);
|
|
326
|
+
if (!projectId || !envName) {
|
|
327
|
+
throw new Error('Usage: eve env diagnose <project> <env> [--events <n>]');
|
|
328
|
+
}
|
|
329
|
+
const query = buildQuery({
|
|
330
|
+
events: (0, args_1.getStringFlag)(flags, ['events']),
|
|
331
|
+
});
|
|
332
|
+
const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/diagnose${query}`);
|
|
333
|
+
if (json) {
|
|
334
|
+
(0, output_1.outputJson)(response, json);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
formatEnvDiagnose(response);
|
|
338
|
+
}
|
|
280
339
|
/**
|
|
281
340
|
* eve env delete <name> [--project=<id>] [--force]
|
|
282
341
|
* Delete an environment
|
|
@@ -399,7 +458,7 @@ function formatEnvironmentsTable(environments) {
|
|
|
399
458
|
/**
|
|
400
459
|
* Format a single environment's details
|
|
401
460
|
*/
|
|
402
|
-
function formatEnvironmentDetails(env) {
|
|
461
|
+
function formatEnvironmentDetails(env, health) {
|
|
403
462
|
console.log(`Environment: ${env.name}`);
|
|
404
463
|
console.log('');
|
|
405
464
|
console.log(` ID: ${env.id}`);
|
|
@@ -408,6 +467,19 @@ function formatEnvironmentDetails(env) {
|
|
|
408
467
|
console.log(` Namespace: ${env.namespace || '(none)'}`);
|
|
409
468
|
console.log(` Database Ref: ${env.db_ref || '(none)'}`);
|
|
410
469
|
console.log(` Current Release: ${env.current_release_id || '(none)'}`);
|
|
470
|
+
if (health) {
|
|
471
|
+
console.log('');
|
|
472
|
+
console.log(` Deployment Status: ${health.status}`);
|
|
473
|
+
if (health.deployment) {
|
|
474
|
+
console.log(` Deployment Ready: ${health.deployment.available_replicas}/${health.deployment.desired_replicas}`);
|
|
475
|
+
}
|
|
476
|
+
if (health.warnings && health.warnings.length > 0) {
|
|
477
|
+
console.log(' Warnings:');
|
|
478
|
+
for (const warning of health.warnings) {
|
|
479
|
+
console.log(` - ${warning}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
411
483
|
if (env.overrides && Object.keys(env.overrides).length > 0) {
|
|
412
484
|
console.log('');
|
|
413
485
|
console.log(' Overrides:');
|
|
@@ -419,6 +491,118 @@ function formatEnvironmentDetails(env) {
|
|
|
419
491
|
console.log(` Created: ${formatDate(env.created_at)}`);
|
|
420
492
|
console.log(` Updated: ${formatDate(env.updated_at)}`);
|
|
421
493
|
}
|
|
494
|
+
function formatEnvDiagnose(report) {
|
|
495
|
+
console.log(`Environment Diagnose: ${report.env_name}`);
|
|
496
|
+
console.log('');
|
|
497
|
+
console.log(` Namespace: ${report.namespace || '(none)'}`);
|
|
498
|
+
console.log(` Status: ${report.status}`);
|
|
499
|
+
console.log(` Ready: ${report.ready ? 'yes' : 'no'}`);
|
|
500
|
+
console.log(` K8s: ${report.k8s_available ? 'available' : 'unavailable'}`);
|
|
501
|
+
if (report.warnings && report.warnings.length > 0) {
|
|
502
|
+
console.log('');
|
|
503
|
+
console.log('Warnings:');
|
|
504
|
+
for (const warning of report.warnings) {
|
|
505
|
+
console.log(` - ${warning}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (report.deployments.length > 0) {
|
|
509
|
+
console.log('');
|
|
510
|
+
console.log('Deployments:');
|
|
511
|
+
const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
|
|
512
|
+
const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
|
|
513
|
+
const header = [
|
|
514
|
+
padRight('Name', nameWidth),
|
|
515
|
+
padRight('Ready', readyWidth),
|
|
516
|
+
'Status',
|
|
517
|
+
].join(' ');
|
|
518
|
+
console.log(header);
|
|
519
|
+
console.log('-'.repeat(header.length));
|
|
520
|
+
for (const deployment of report.deployments) {
|
|
521
|
+
const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
|
|
522
|
+
const status = deployment.ready ? 'ready' : 'not-ready';
|
|
523
|
+
console.log([
|
|
524
|
+
padRight(deployment.name, nameWidth),
|
|
525
|
+
padRight(readiness, readyWidth),
|
|
526
|
+
status,
|
|
527
|
+
].join(' '));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (report.pods.length > 0) {
|
|
531
|
+
console.log('');
|
|
532
|
+
console.log('Pods:');
|
|
533
|
+
const nameWidth = Math.max(4, ...report.pods.map((p) => p.name.length));
|
|
534
|
+
const phaseWidth = Math.max(5, ...report.pods.map((p) => p.phase.length));
|
|
535
|
+
const restartsWidth = Math.max(8, ...report.pods.map((p) => String(p.restarts).length));
|
|
536
|
+
const header = [
|
|
537
|
+
padRight('Name', nameWidth),
|
|
538
|
+
padRight('Phase', phaseWidth),
|
|
539
|
+
padRight('Restarts', restartsWidth),
|
|
540
|
+
'Ready',
|
|
541
|
+
'Age',
|
|
542
|
+
].join(' ');
|
|
543
|
+
console.log(header);
|
|
544
|
+
console.log('-'.repeat(header.length));
|
|
545
|
+
for (const pod of report.pods) {
|
|
546
|
+
console.log([
|
|
547
|
+
padRight(pod.name, nameWidth),
|
|
548
|
+
padRight(pod.phase, phaseWidth),
|
|
549
|
+
padRight(String(pod.restarts), restartsWidth),
|
|
550
|
+
pod.ready ? 'yes' : 'no',
|
|
551
|
+
pod.age,
|
|
552
|
+
].join(' '));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (report.events.length > 0) {
|
|
556
|
+
console.log('');
|
|
557
|
+
console.log('Events:');
|
|
558
|
+
for (const event of report.events) {
|
|
559
|
+
const timestamp = event.timestamp ?? 'unknown';
|
|
560
|
+
const reason = event.reason ?? 'Unknown';
|
|
561
|
+
const message = event.message ?? '';
|
|
562
|
+
console.log(` [${timestamp}] ${event.type} ${reason}: ${message}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function getDeploymentWarnings(status) {
|
|
567
|
+
if (!status)
|
|
568
|
+
return [];
|
|
569
|
+
const warnings = [];
|
|
570
|
+
if (status.state !== 'ready') {
|
|
571
|
+
warnings.push(`Deployment state: ${status.state}`);
|
|
572
|
+
}
|
|
573
|
+
if (status.k8s_status) {
|
|
574
|
+
const { available_replicas, desired_replicas, ready, conditions } = status.k8s_status;
|
|
575
|
+
if (!ready) {
|
|
576
|
+
warnings.push(`Deployment replicas not ready (${available_replicas}/${desired_replicas})`);
|
|
577
|
+
}
|
|
578
|
+
for (const condition of conditions) {
|
|
579
|
+
if (condition.status !== 'True' && condition.message) {
|
|
580
|
+
warnings.push(`${condition.type}: ${condition.message}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return Array.from(new Set(warnings));
|
|
585
|
+
}
|
|
586
|
+
async function watchDeploymentStatus(context, projectId, envName, timeoutSeconds) {
|
|
587
|
+
const start = Date.now();
|
|
588
|
+
const pollIntervalMs = 3000;
|
|
589
|
+
console.log('');
|
|
590
|
+
console.log('Watching deployment status...');
|
|
591
|
+
while ((Date.now() - start) / 1000 < timeoutSeconds) {
|
|
592
|
+
const health = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/health`);
|
|
593
|
+
const elapsed = Math.floor((Date.now() - start) / 1000);
|
|
594
|
+
const readiness = health.deployment
|
|
595
|
+
? `${health.deployment.available_replicas}/${health.deployment.desired_replicas}`
|
|
596
|
+
: 'n/a';
|
|
597
|
+
console.log(` [${elapsed}s] ${health.status} (${readiness} ready)`);
|
|
598
|
+
if (health.ready) {
|
|
599
|
+
console.log(' Deployment is ready.');
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
603
|
+
}
|
|
604
|
+
console.log(` Timeout after ${timeoutSeconds}s. Run "eve env diagnose ${projectId} ${envName}" for details.`);
|
|
605
|
+
}
|
|
422
606
|
/**
|
|
423
607
|
* Format a date string for display
|
|
424
608
|
*/
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleManifest = handleManifest;
|
|
4
|
+
const args_1 = require("../lib/args");
|
|
5
|
+
const client_1 = require("../lib/client");
|
|
6
|
+
const output_1 = require("../lib/output");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
async function handleManifest(subcommand, positionals, flags, context) {
|
|
10
|
+
const json = Boolean(flags.json);
|
|
11
|
+
switch (subcommand) {
|
|
12
|
+
case 'validate': {
|
|
13
|
+
const useLatest = (0, args_1.toBoolean)(flags.latest) ?? false;
|
|
14
|
+
const strict = (0, args_1.toBoolean)(flags.strict) ?? false;
|
|
15
|
+
const validateSecretsFlag = flags['validate-secrets'] ?? flags.validate_secrets;
|
|
16
|
+
const validateSecrets = (0, args_1.toBoolean)(validateSecretsFlag) ?? false;
|
|
17
|
+
const dir = typeof flags.dir === 'string' ? flags.dir : process.cwd();
|
|
18
|
+
const manifestPath = (0, args_1.getStringFlag)(flags, ['path']) ?? (0, node_path_1.join)(dir, '.eve', 'manifest.yaml');
|
|
19
|
+
let manifestYaml;
|
|
20
|
+
if (!useLatest) {
|
|
21
|
+
try {
|
|
22
|
+
manifestYaml = (0, node_fs_1.readFileSync)(manifestPath, 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new Error(`Failed to read manifest at ${manifestPath}: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
let projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
|
|
29
|
+
if (!projectId && manifestYaml) {
|
|
30
|
+
const match = manifestYaml.match(/^project:\s*(\S+)/m);
|
|
31
|
+
if (match) {
|
|
32
|
+
projectId = match[1];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!projectId) {
|
|
36
|
+
throw new Error('Missing project id. Provide --project, set a profile default, or add "project: proj_xxx" to manifest.');
|
|
37
|
+
}
|
|
38
|
+
const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/manifest/validate`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
body: {
|
|
41
|
+
manifest_yaml: manifestYaml,
|
|
42
|
+
validate_secrets: validateSecrets || strict,
|
|
43
|
+
strict,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if (json) {
|
|
47
|
+
(0, output_1.outputJson)(response, json);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (response.valid) {
|
|
51
|
+
console.log('✓ Manifest valid');
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('✗ Manifest invalid');
|
|
55
|
+
}
|
|
56
|
+
if (response.manifest_hash) {
|
|
57
|
+
console.log(` Hash: ${response.manifest_hash.substring(0, 12)}...`);
|
|
58
|
+
}
|
|
59
|
+
if (response.errors && response.errors.length > 0) {
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log('Errors:');
|
|
62
|
+
response.errors.forEach((error) => console.log(` - ${error}`));
|
|
63
|
+
}
|
|
64
|
+
if (response.warnings && response.warnings.length > 0) {
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log('Warnings:');
|
|
67
|
+
response.warnings.forEach((warning) => console.log(` - ${warning}`));
|
|
68
|
+
}
|
|
69
|
+
if (response.secret_validation?.missing?.length) {
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('Missing secrets:');
|
|
72
|
+
response.secret_validation.missing.forEach((item) => {
|
|
73
|
+
const hint = item.hints?.[0] ? ` (${item.hints[0]})` : '';
|
|
74
|
+
console.log(` - ${item.key}${hint}`);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!response.valid) {
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
default:
|
|
84
|
+
throw new Error('Usage: eve manifest <validate>');
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const admin_1 = require("./commands/admin");
|
|
|
24
24
|
const agents_1 = require("./commands/agents");
|
|
25
25
|
const init_1 = require("./commands/init");
|
|
26
26
|
const release_1 = require("./commands/release");
|
|
27
|
+
const manifest_1 = require("./commands/manifest");
|
|
27
28
|
async function main() {
|
|
28
29
|
const { flags, positionals } = (0, args_1.parseArgs)(process.argv.slice(2));
|
|
29
30
|
const command = positionals[0];
|
|
@@ -107,6 +108,9 @@ async function main() {
|
|
|
107
108
|
case 'release':
|
|
108
109
|
await (0, release_1.handleRelease)(subcommand, rest, flags, context);
|
|
109
110
|
return;
|
|
111
|
+
case 'manifest':
|
|
112
|
+
await (0, manifest_1.handleManifest)(subcommand, rest, flags, context);
|
|
113
|
+
return;
|
|
110
114
|
default:
|
|
111
115
|
(0, help_1.showMainHelp)();
|
|
112
116
|
}
|
package/dist/lib/help.js
CHANGED
|
@@ -106,6 +106,28 @@ exports.HELP = {
|
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
108
|
},
|
|
109
|
+
manifest: {
|
|
110
|
+
description: 'Validate project manifests for schema and required secrets.',
|
|
111
|
+
usage: 'eve manifest <subcommand> [options]',
|
|
112
|
+
subcommands: {
|
|
113
|
+
validate: {
|
|
114
|
+
description: 'Validate a manifest (schema + secrets)',
|
|
115
|
+
usage: 'eve manifest validate [--project <id>] [--path <path>] [--latest]',
|
|
116
|
+
options: [
|
|
117
|
+
'--project <id> Project ID (uses profile default)',
|
|
118
|
+
'--path <path> Path to manifest (default: .eve/manifest.yaml)',
|
|
119
|
+
'--latest Validate latest synced manifest instead of local file',
|
|
120
|
+
'--validate-secrets Validate required secrets (from manifest)',
|
|
121
|
+
'--strict Fail validation if required secrets are missing',
|
|
122
|
+
],
|
|
123
|
+
examples: [
|
|
124
|
+
'eve manifest validate',
|
|
125
|
+
'eve manifest validate --project proj_xxx',
|
|
126
|
+
'eve manifest validate --latest --project proj_xxx',
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
109
131
|
secrets: {
|
|
110
132
|
description: 'Manage secrets at system/org/user/project scope. Values are never returned in plaintext.',
|
|
111
133
|
usage: 'eve secrets <subcommand> [options]',
|
|
@@ -576,7 +598,7 @@ have to specify them on every command. Useful when working with multiple environ
|
|
|
576
598
|
auth: {
|
|
577
599
|
description: `Authenticate with Eve Horizon. Auth is optional for local development but required
|
|
578
600
|
for cloud deployments. Credentials are stored per-profile.`,
|
|
579
|
-
usage: 'eve auth <login|logout|status|whoami|bootstrap|sync|token>',
|
|
601
|
+
usage: 'eve auth <login|logout|status|whoami|bootstrap|sync|creds|token>',
|
|
580
602
|
subcommands: {
|
|
581
603
|
login: {
|
|
582
604
|
description: 'Login via GitHub SSH challenge (default) or Supabase (legacy)',
|
|
@@ -647,18 +669,35 @@ for cloud deployments. Credentials are stored per-profile.`,
|
|
|
647
669
|
},
|
|
648
670
|
sync: {
|
|
649
671
|
description: 'Extract OAuth tokens from host and set as Eve secrets',
|
|
650
|
-
usage: 'eve auth sync [--claude] [--codex] [--
|
|
672
|
+
usage: 'eve auth sync [--claude] [--codex] [--org <id>] [--project <id>] [--dry-run]',
|
|
651
673
|
options: [
|
|
652
674
|
'--claude Only extract Claude/Anthropic tokens',
|
|
653
675
|
'--codex Only extract Codex/OpenAI tokens',
|
|
654
|
-
'--
|
|
655
|
-
'--
|
|
676
|
+
'--org <id> Set as org-level secrets',
|
|
677
|
+
'--project <id> Set as project-level secrets',
|
|
656
678
|
'--dry-run Show what would be set without actually setting',
|
|
679
|
+
'',
|
|
680
|
+
'Scope priority: --project > --org > user (default)',
|
|
681
|
+
'Default scope is user-level, so credentials are available to all your jobs.',
|
|
657
682
|
],
|
|
658
683
|
examples: [
|
|
659
|
-
'eve auth sync',
|
|
660
|
-
'eve auth sync --
|
|
661
|
-
'eve auth sync --project proj_xxx',
|
|
684
|
+
'eve auth sync # Sync to user-level (default)',
|
|
685
|
+
'eve auth sync --org org_xxx # Sync to org-level',
|
|
686
|
+
'eve auth sync --project proj_xxx # Sync to project-level',
|
|
687
|
+
'eve auth sync --dry-run # Preview without syncing',
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
creds: {
|
|
691
|
+
description: 'Show local AI tool credentials (Claude Code, Codex/Code) without syncing',
|
|
692
|
+
usage: 'eve auth creds [--claude] [--codex]',
|
|
693
|
+
options: [
|
|
694
|
+
'--claude Only check Claude/Anthropic credentials',
|
|
695
|
+
'--codex Only check Codex/OpenAI credentials',
|
|
696
|
+
],
|
|
697
|
+
examples: [
|
|
698
|
+
'eve auth creds',
|
|
699
|
+
'eve auth creds --claude',
|
|
700
|
+
'eve auth creds --json',
|
|
662
701
|
],
|
|
663
702
|
},
|
|
664
703
|
},
|
|
@@ -704,6 +743,8 @@ for cloud deployments. Credentials are stored per-profile.`,
|
|
|
704
743
|
'--direct Bypass pipeline and do direct deploy',
|
|
705
744
|
'--inputs <json> JSON inputs for the deployment (e.g., \'{"release_id":"rel_xxx"}\')',
|
|
706
745
|
'--project <id> Project ID or slug (uses profile default if omitted)',
|
|
746
|
+
'--watch Poll deployment status until ready (default: true)',
|
|
747
|
+
'--timeout <seconds> Watch timeout in seconds (default: 120)',
|
|
707
748
|
],
|
|
708
749
|
examples: [
|
|
709
750
|
'eve env deploy staging --ref abc123',
|
|
@@ -712,6 +753,19 @@ for cloud deployments. Credentials are stored per-profile.`,
|
|
|
712
753
|
'eve env deploy staging --ref abc123 --direct --inputs \'{"release_id":"rel_xxx"}\'',
|
|
713
754
|
],
|
|
714
755
|
},
|
|
756
|
+
diagnose: {
|
|
757
|
+
description: 'Diagnose environment deployments (k8s-only)',
|
|
758
|
+
usage: 'eve env diagnose <project> <env> [--events <n>]',
|
|
759
|
+
options: [
|
|
760
|
+
'<project> Project ID or slug',
|
|
761
|
+
'<env> Environment name',
|
|
762
|
+
'--events <n> Limit number of recent events',
|
|
763
|
+
],
|
|
764
|
+
examples: [
|
|
765
|
+
'eve env diagnose proj_xxx staging',
|
|
766
|
+
'eve env diagnose proj_xxx staging --events 20',
|
|
767
|
+
],
|
|
768
|
+
},
|
|
715
769
|
logs: {
|
|
716
770
|
description: 'Fetch logs for a service in an environment (k8s-only)',
|
|
717
771
|
usage: 'eve env logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>]',
|
|
@@ -748,6 +802,7 @@ for cloud deployments. Credentials are stored per-profile.`,
|
|
|
748
802
|
'eve env create test --type=persistent',
|
|
749
803
|
'eve env deploy staging --ref abc123',
|
|
750
804
|
'eve env logs proj_xxx staging api --tail 200',
|
|
805
|
+
'eve env diagnose proj_xxx staging',
|
|
751
806
|
],
|
|
752
807
|
},
|
|
753
808
|
api: {
|
|
@@ -1162,6 +1217,7 @@ function showMainHelp() {
|
|
|
1162
1217
|
console.log('Commands:');
|
|
1163
1218
|
console.log(' org Manage organizations');
|
|
1164
1219
|
console.log(' project Manage projects');
|
|
1220
|
+
console.log(' manifest Validate manifests (schema, secrets)');
|
|
1165
1221
|
console.log(' job Manage jobs (create, list, show, update, claim, etc.)');
|
|
1166
1222
|
console.log(' env Manage environments (list, show, deploy)');
|
|
1167
1223
|
console.log(' release Manage and inspect releases');
|