@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.
- package/dist/index.js +65918 -137
- package/package.json +6 -5
- package/dist/commands/admin.js +0 -81
- package/dist/commands/agents.js +0 -445
- package/dist/commands/api.js +0 -350
- package/dist/commands/auth.js +0 -852
- package/dist/commands/build.js +0 -397
- package/dist/commands/chat.js +0 -55
- package/dist/commands/db.js +0 -204
- package/dist/commands/env.js +0 -812
- package/dist/commands/event.js +0 -273
- package/dist/commands/harness.js +0 -158
- package/dist/commands/init.js +0 -280
- package/dist/commands/integrations.js +0 -73
- package/dist/commands/job.js +0 -2557
- package/dist/commands/manifest.js +0 -86
- package/dist/commands/migrate.js +0 -136
- package/dist/commands/org.js +0 -148
- package/dist/commands/packs.js +0 -197
- package/dist/commands/pipeline.js +0 -629
- package/dist/commands/profile.js +0 -155
- package/dist/commands/project.js +0 -249
- package/dist/commands/release.js +0 -69
- package/dist/commands/secrets.js +0 -218
- package/dist/commands/skills.js +0 -307
- package/dist/commands/supervise.js +0 -60
- package/dist/commands/system.js +0 -649
- package/dist/commands/thread.js +0 -153
- package/dist/commands/workflow.js +0 -337
- package/dist/lib/args.js +0 -57
- package/dist/lib/client.js +0 -116
- package/dist/lib/config.js +0 -49
- package/dist/lib/context.js +0 -187
- package/dist/lib/git.js +0 -158
- package/dist/lib/harness-capabilities.js +0 -74
- package/dist/lib/help.js +0 -1821
- package/dist/lib/logs.js +0 -51
- package/dist/lib/output.js +0 -14
package/dist/commands/auth.js
DELETED
|
@@ -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
|
-
}
|