creatorverse-cli 0.0.0-stage → 1.0.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.
@@ -0,0 +1,678 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CreatorVerse Admin CLI — Stage E1.2 & E1.3 Controlled Admin CLI Integration
4
+ *
5
+ * Secure Administrative Command-Line Interface connecting to:
6
+ * - /api/admin/cli-session (POST)
7
+ * - /api/admin/cli-session/verify (GET)
8
+ * - /api/admin/cli-session/revoke (POST)
9
+ * - /api/admin/cli-session/audit-logs (GET)
10
+ * - /api/cli/auth/exchange (POST)
11
+ *
12
+ * Security Guardrails:
13
+ * 1. ADMIN_CLI_PRODUCTION_ENABLED = false: Disables live production execution pending verified browser auth.
14
+ * 2. Mandatory MFA interactive masking / Browser MFA: TOTP codes are entered privately without exposure in shell history, argv, or process listings.
15
+ * 3. Server-authoritative privilege checks: Local tokens/roles/permissions are never trusted; backend enforces RBAC, device binding, and revocation.
16
+ * 4. AES-256-GCM encrypted credential vault with strict 0o600 file / 0o700 directory permissions.
17
+ * 5. Safe network timeouts (5000ms AbortController) and fail-closed handling on network or authentication failure.
18
+ * 6. Total isolation between production and offline test mode (retaining 100% backward compatibility for existing suites).
19
+ */
20
+ import path from 'path';
21
+ import os from 'os';
22
+ import readline from 'readline';
23
+ import { saveSecureSession, loadSecureSession, clearSecureSession, redactSensitiveData } from './credentialManager.js';
24
+ import { startLoopbackListener, generatePkcePair } from './loopbackListener.js';
25
+ export const VERSION = '1.0.1';
26
+ export const CONFIG_DIR = path.join(os.homedir(), '.creatorverse');
27
+ export const ADMIN_CONFIG_FILE = path.join(CONFIG_DIR, 'admin-auth-secure.enc');
28
+ // Production Gate: CLI disabled for live production access pending verified browser auth flow
29
+ export const ADMIN_CLI_PRODUCTION_ENABLED = false;
30
+ export function getStoredAdminSession() {
31
+ const session = loadSecureSession(ADMIN_CONFIG_FILE);
32
+ if (session && session.expiresAt && Date.now() > session.expiresAt) {
33
+ return null;
34
+ }
35
+ return session;
36
+ }
37
+ export async function fetchWithTimeout(url, options, timeoutMs = 5000) {
38
+ const controller = new AbortController();
39
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
40
+ try {
41
+ const res = await fetch(url, { ...options, signal: controller.signal });
42
+ clearTimeout(timeoutId);
43
+ return res;
44
+ }
45
+ catch (err) {
46
+ clearTimeout(timeoutId);
47
+ if (err.name === 'AbortError') {
48
+ const timeoutErr = new Error(`Network timeout: server failed to respond within ${timeoutMs}ms.`);
49
+ timeoutErr.code = 'NETWORK_TIMEOUT';
50
+ throw timeoutErr;
51
+ }
52
+ const netErr = new Error(`Network error connecting to administrative endpoint: ${err.message}`);
53
+ netErr.code = 'SERVICE_UNAVAILABLE';
54
+ throw netErr;
55
+ }
56
+ }
57
+ export async function promptMfaInteractive(providedCode, isTestMode = false) {
58
+ if (providedCode) {
59
+ if (!isTestMode) {
60
+ throw new Error('MFA_ARGV_DISALLOWED: Passing MFA codes via command line flags outside explicit test mode is prohibited to prevent exposure in shell history and process tables.');
61
+ }
62
+ return providedCode.trim();
63
+ }
64
+ return new Promise((resolve, reject) => {
65
+ if (!process.stdin.isTTY) {
66
+ let input = '';
67
+ process.stdin.setEncoding('utf-8');
68
+ process.stdin.on('data', (chunk) => {
69
+ input += chunk;
70
+ });
71
+ process.stdin.on('end', () => {
72
+ resolve(input.trim());
73
+ });
74
+ process.stdin.on('error', reject);
75
+ return;
76
+ }
77
+ const rl = readline.createInterface({
78
+ input: process.stdin,
79
+ output: process.stdout,
80
+ terminal: true,
81
+ });
82
+ process.stdout.write('Enter Multi-Factor Authentication (TOTP) code: ');
83
+ rl._writeToOutput = function _writeToOutput(stringToWrite) {
84
+ if (stringToWrite.charCodeAt(0) === 13 || stringToWrite.charCodeAt(0) === 10) {
85
+ process.stdout.write('\n');
86
+ }
87
+ else {
88
+ process.stdout.write('*');
89
+ }
90
+ };
91
+ rl.question('', (answer) => {
92
+ rl.close();
93
+ resolve(answer.trim());
94
+ });
95
+ });
96
+ }
97
+ export function validateEnvironmentSecurity(options) {
98
+ const effectiveApiUrl = options.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
99
+ const isProdEnv = process.env.CREATORVERSE_PRODUCTION === 'true' ||
100
+ process.env.NODE_ENV === 'production' ||
101
+ effectiveApiUrl.includes('api.creatorverse.network') ||
102
+ effectiveApiUrl.includes('creatorverse.network');
103
+ if (isProdEnv) {
104
+ if (options.isOffline) {
105
+ const err = new Error('Offline test mode and mock credentials cannot be used in production environments.');
106
+ err.code = 'OFFLINE_MODE_PROHIBITED_IN_PRODUCTION';
107
+ throw err;
108
+ }
109
+ if (options.token) {
110
+ const err = new Error('Direct-token authentication is strictly prohibited in production mode. Interactive browser-based authentication with MFA is required.');
111
+ err.code = 'DIRECT_TOKEN_LOGIN_PROHIBITED';
112
+ throw err;
113
+ }
114
+ if (!ADMIN_CLI_PRODUCTION_ENABLED) {
115
+ const err = new Error('CreatorVerse Admin CLI production access is disabled pending verified browser-based authentication flow. Use local or staging development environment.');
116
+ err.code = 'PRODUCTION_ACCESS_PENDING';
117
+ throw err;
118
+ }
119
+ if (effectiveApiUrl.startsWith('http://')) {
120
+ const err = new Error('Insecure HTTP connection is strictly forbidden in production mode. Use verified HTTPS.');
121
+ err.code = 'INSECURE_PROTOCOL_REJECTED';
122
+ throw err;
123
+ }
124
+ if (options.mfaCode === '123456' || options.mfaCode === 'valid-mfa') {
125
+ const err = new Error('Mock tokens and hardcoded test MFA codes are strictly forbidden in production-facing execution paths.');
126
+ err.code = 'TEST_CREDENTIALS_REJECTED_IN_PRODUCTION';
127
+ throw err;
128
+ }
129
+ }
130
+ }
131
+ export async function executeAdminLogin(options) {
132
+ validateEnvironmentSecurity(options);
133
+ const effectiveApiUrl = options.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
134
+ const isTestMode = options.isTestMode ?? (process.env.CREATORVERSE_TEST_MODE === '1' || process.env.NODE_ENV === 'test');
135
+ const isRemote = options.isRemote || Boolean(options.apiUrl) || Boolean(process.env.CREATORVERSE_API_URL) || process.env.CREATORVERSE_REMOTE === '1' || Boolean(options.isBrowser);
136
+ const isOffline = options.isOffline || (!isRemote && !options.isBrowser);
137
+ const timeoutMs = options.timeoutMs || 5000;
138
+ const token = options.token || 'mock-admin-token-secure';
139
+ const email = options.email || 'admin@creatorverse.local';
140
+ const uid = 'admin-uid-999';
141
+ const role = options.role || 'admin';
142
+ const deviceId = options.deviceId !== undefined ? options.deviceId : 'default-device-id';
143
+ const deviceName = options.deviceName || 'Admin Workstation';
144
+ // 1. Browser-Assisted PKCE Login Flow
145
+ if (options.isBrowser) {
146
+ const { codeVerifier, codeChallenge, state } = generatePkcePair();
147
+ const listener = await startLoopbackListener(state, 120000);
148
+ const authUrl = `${effectiveApiUrl}/cli-auth?port=${listener.port}&state=${state}&code_challenge=${codeChallenge}&client_type=admin&device_id=${encodeURIComponent(deviceId)}`;
149
+ let ticket = '';
150
+ try {
151
+ if (process.stdout.isTTY) {
152
+ console.log(`\nOpen the following URL in your browser to complete administrator authentication & MFA:\n\n ${authUrl}\n`);
153
+ console.log(`Waiting for browser callback on 127.0.0.1:${listener.port} (timeout: 120s)...`);
154
+ }
155
+ ticket = await listener.waitForCallback();
156
+ }
157
+ catch (err) {
158
+ listener.shutdown();
159
+ throw err;
160
+ }
161
+ const exchangeRes = await fetchWithTimeout(`${effectiveApiUrl}/api/cli/auth/exchange`, {
162
+ method: 'POST',
163
+ headers: {
164
+ 'Content-Type': 'application/json',
165
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
166
+ },
167
+ body: JSON.stringify({ ticket, codeVerifier, state }),
168
+ }, timeoutMs);
169
+ const resJson = await exchangeRes.json();
170
+ if (!exchangeRes.ok || !resJson.success) {
171
+ const err = new Error(resJson.error || 'Administrative ticket exchange failed.');
172
+ err.code = resJson.code || 'EXCHANGE_FAILED';
173
+ throw err;
174
+ }
175
+ const sessionRecord = {
176
+ sessionId: resJson.sessionId,
177
+ sessionToken: resJson.sessionToken,
178
+ token: resJson.sessionToken,
179
+ uid: resJson.uid,
180
+ email: resJson.email,
181
+ role: resJson.role,
182
+ permissions: resJson.permissions,
183
+ deviceId: resJson.deviceId,
184
+ apiUrl: effectiveApiUrl,
185
+ mfaVerified: true,
186
+ expiresAt: Date.now() + (resJson.expiresInSeconds * 1000)
187
+ };
188
+ saveSecureSession(ADMIN_CONFIG_FILE, sessionRecord);
189
+ return redactSensitiveData({
190
+ status: 'SUCCESS',
191
+ message: 'Admin session authenticated and registered via browser PKCE authorization.',
192
+ sessionId: resJson.sessionId,
193
+ uid: resJson.uid,
194
+ email: resJson.email,
195
+ role: resJson.role,
196
+ mfaVerified: true,
197
+ token: resJson.sessionToken,
198
+ expiresInSeconds: resJson.expiresInSeconds
199
+ });
200
+ }
201
+ // 2. Direct Remote Session Issuance
202
+ if (isRemote && !isOffline) {
203
+ let mfaCode = '';
204
+ mfaCode = await promptMfaInteractive(options.mfaCode, isTestMode);
205
+ if (!mfaCode) {
206
+ const err = new Error('Administrative authentication requires valid Multi-Factor Authentication.');
207
+ err.code = 'MFA_REQUIRED';
208
+ throw err;
209
+ }
210
+ const response = await fetchWithTimeout(`${effectiveApiUrl}/api/admin/cli-session`, {
211
+ method: 'POST',
212
+ headers: {
213
+ 'Authorization': `Bearer ${token}`,
214
+ 'Content-Type': 'application/json',
215
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
216
+ },
217
+ body: JSON.stringify({ mfaCode, deviceId, deviceName }),
218
+ }, timeoutMs);
219
+ const resJson = await response.json();
220
+ if (!response.ok || !resJson.success) {
221
+ const err = new Error(resJson.error || 'Administrative session issuance failed.');
222
+ err.code = resJson.code || 'AUTHENTICATION_FAILED';
223
+ throw err;
224
+ }
225
+ const sessionRecord = {
226
+ sessionId: resJson.sessionId,
227
+ sessionToken: resJson.sessionToken,
228
+ token: resJson.sessionToken,
229
+ uid: resJson.uid,
230
+ email: resJson.email,
231
+ role: resJson.role,
232
+ permissions: resJson.permissions,
233
+ deviceId: resJson.deviceId,
234
+ apiUrl: effectiveApiUrl,
235
+ mfaVerified: true,
236
+ expiresAt: Date.now() + (resJson.expiresInSeconds * 1000)
237
+ };
238
+ saveSecureSession(ADMIN_CONFIG_FILE, sessionRecord);
239
+ return redactSensitiveData({
240
+ status: 'SUCCESS',
241
+ message: 'Admin session authenticated and registered via secure administrative session service.',
242
+ sessionId: resJson.sessionId,
243
+ uid: resJson.uid,
244
+ email: resJson.email,
245
+ role: resJson.role,
246
+ mfaVerified: true,
247
+ token: resJson.sessionToken,
248
+ expiresInSeconds: resJson.expiresInSeconds
249
+ });
250
+ }
251
+ else {
252
+ // 3. Offline / Sandbox mode
253
+ let mfaProvided = false;
254
+ if (options.mfaCode) {
255
+ if (isTestMode && (options.mfaCode === '123456' || options.mfaCode === 'valid-mfa')) {
256
+ mfaProvided = true;
257
+ }
258
+ else {
259
+ mfaProvided = false;
260
+ }
261
+ }
262
+ if (!mfaProvided) {
263
+ const err = new Error('Administrative authentication requires valid Multi-Factor Authentication. Hardcoded test codes are disabled outside explicit test mode.');
264
+ err.code = 'MFA_REQUIRED';
265
+ throw err;
266
+ }
267
+ if (role !== 'admin' && role !== 'platform-owner' && role !== 'approved-developer') {
268
+ const err = new Error('Insufficient privileges. Admin role verification failed.');
269
+ err.code = 'FORBIDDEN';
270
+ throw err;
271
+ }
272
+ const session = {
273
+ token,
274
+ uid,
275
+ email,
276
+ role,
277
+ mfaVerified: true,
278
+ deviceId,
279
+ expiresAt: Date.now() + 1800 * 1000
280
+ };
281
+ saveSecureSession(ADMIN_CONFIG_FILE, session);
282
+ return redactSensitiveData({
283
+ status: 'SUCCESS',
284
+ message: 'Admin session authenticated with strict MFA verification and encrypted vault storage.',
285
+ uid: session.uid,
286
+ email: session.email,
287
+ role: session.role,
288
+ mfaVerified: true,
289
+ token: session.token,
290
+ expiresInSeconds: 1800
291
+ });
292
+ }
293
+ }
294
+ export async function executeAdminLogout(options = {}) {
295
+ const session = loadSecureSession(ADMIN_CONFIG_FILE);
296
+ const effectiveApiUrl = options.apiUrl || session?.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
297
+ const isRemote = options.isRemote || Boolean(options.apiUrl) || Boolean(session?.apiUrl);
298
+ const timeoutMs = options.timeoutMs || 5000;
299
+ if (session && (session.sessionToken || session.token) && isRemote && !options.isOffline) {
300
+ try {
301
+ await fetchWithTimeout(`${effectiveApiUrl}/api/admin/cli-session/revoke`, {
302
+ method: 'POST',
303
+ headers: {
304
+ 'Authorization': `Bearer ${session.sessionToken || session.token}`,
305
+ 'Content-Type': 'application/json',
306
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
307
+ },
308
+ body: JSON.stringify({ sessionToken: session.sessionToken || session.token })
309
+ }, timeoutMs);
310
+ }
311
+ catch {
312
+ // Local vault is always cleared regardless of remote network status
313
+ }
314
+ }
315
+ clearSecureSession(ADMIN_CONFIG_FILE);
316
+ return {
317
+ status: 'SUCCESS',
318
+ message: 'Admin session revoked and encrypted vault cleared.'
319
+ };
320
+ }
321
+ export async function executeAdminWhoami(options = {}) {
322
+ const session = getStoredAdminSession();
323
+ if (!session || !session.mfaVerified) {
324
+ const err = new Error('Active admin session with MFA verification required. Run "creatorverse-admin login".');
325
+ err.code = 'UNAUTHORIZED';
326
+ throw err;
327
+ }
328
+ const effectiveApiUrl = options.apiUrl || session.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
329
+ const isRemote = options.isRemote || Boolean(options.apiUrl) || Boolean(session.apiUrl);
330
+ const timeoutMs = options.timeoutMs || 5000;
331
+ if (isRemote && !options.isOffline) {
332
+ const response = await fetchWithTimeout(`${effectiveApiUrl}/api/admin/cli-session/verify`, {
333
+ method: 'GET',
334
+ headers: {
335
+ 'Authorization': `Bearer ${session.sessionToken || session.token}`,
336
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
337
+ }
338
+ }, timeoutMs);
339
+ const data = await response.json();
340
+ if (!response.ok || !data.valid) {
341
+ clearSecureSession(ADMIN_CONFIG_FILE);
342
+ const err = new Error(data.error || 'Admin session is invalid, expired or revoked on the server.');
343
+ err.code = data.code || 'UNAUTHORIZED_SESSION_REVOKED';
344
+ throw err;
345
+ }
346
+ return redactSensitiveData({
347
+ authenticated: true,
348
+ sessionId: data.sessionId,
349
+ uid: data.uid,
350
+ email: data.email,
351
+ role: data.role,
352
+ mfaVerified: true,
353
+ deviceId: data.deviceId,
354
+ token: session.sessionToken || session.token,
355
+ expiresAt: new Date(Date.now() + (data.expiresInSeconds * 1000)).toISOString()
356
+ });
357
+ }
358
+ // Offline mode
359
+ return redactSensitiveData({
360
+ authenticated: true,
361
+ uid: session.uid,
362
+ email: session.email,
363
+ role: session.role,
364
+ mfaVerified: session.mfaVerified,
365
+ deviceId: session.deviceId,
366
+ token: session.token,
367
+ expiresAt: new Date(session.expiresAt).toISOString()
368
+ });
369
+ }
370
+ export async function executeAdminPermissions(options = {}) {
371
+ const session = getStoredAdminSession();
372
+ if (!session || !session.mfaVerified) {
373
+ const err = new Error('Admin authentication required to inspect permissions.');
374
+ err.code = 'UNAUTHORIZED';
375
+ throw err;
376
+ }
377
+ const effectiveApiUrl = options.apiUrl || session.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
378
+ const isRemote = options.isRemote || Boolean(options.apiUrl) || Boolean(session.apiUrl);
379
+ const timeoutMs = options.timeoutMs || 5000;
380
+ if (isRemote && !options.isOffline) {
381
+ const response = await fetchWithTimeout(`${effectiveApiUrl}/api/admin/cli-session/verify`, {
382
+ method: 'GET',
383
+ headers: {
384
+ 'Authorization': `Bearer ${session.sessionToken || session.token}`,
385
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
386
+ }
387
+ }, timeoutMs);
388
+ const data = await response.json();
389
+ if (!response.ok || !data.valid) {
390
+ clearSecureSession(ADMIN_CONFIG_FILE);
391
+ const err = new Error(data.error || 'Admin session has expired or been revoked.');
392
+ err.code = data.code || 'UNAUTHORIZED_SESSION_REVOKED';
393
+ throw err;
394
+ }
395
+ return {
396
+ role: data.role,
397
+ explicitPermissions: data.permissions || []
398
+ };
399
+ }
400
+ // Offline mode
401
+ const permissionsMap = {
402
+ 'admin': ['audit-logs:read', 'system-status:read', 'user-moderation:write', 'content-moderation:write'],
403
+ 'platform-owner': ['audit-logs:read', 'system-status:read', 'user-moderation:write', 'content-moderation:write', 'billing:manage', 'platform-config:write'],
404
+ 'approved-developer': ['system-status:read', 'diagnostics:read', 'security-tests:run']
405
+ };
406
+ return {
407
+ role: session.role,
408
+ explicitPermissions: permissionsMap[session.role] || []
409
+ };
410
+ }
411
+ export async function executeAdminSessionStatus(options = {}) {
412
+ const session = getStoredAdminSession();
413
+ if (!session) {
414
+ return { active: false, message: 'No active secure admin session found.' };
415
+ }
416
+ const effectiveApiUrl = options.apiUrl || session.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
417
+ const isRemote = options.isRemote || Boolean(options.apiUrl) || Boolean(session.apiUrl);
418
+ const timeoutMs = options.timeoutMs || 5000;
419
+ if (isRemote && !options.isOffline) {
420
+ const response = await fetchWithTimeout(`${effectiveApiUrl}/api/admin/cli-session/verify`, {
421
+ method: 'GET',
422
+ headers: {
423
+ 'Authorization': `Bearer ${session.sessionToken || session.token}`,
424
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
425
+ }
426
+ }, timeoutMs);
427
+ const data = await response.json();
428
+ if (!response.ok || !data.valid) {
429
+ return {
430
+ active: false,
431
+ error: data.code || 'SESSION_REVOKED',
432
+ message: data.error || 'Admin session is invalid, expired or revoked on the server.'
433
+ };
434
+ }
435
+ return redactSensitiveData({
436
+ active: true,
437
+ sessionId: data.sessionId,
438
+ uid: data.uid,
439
+ email: data.email,
440
+ role: data.role,
441
+ mfaVerified: true,
442
+ token: session.sessionToken || session.token,
443
+ expiresInSeconds: data.expiresInSeconds
444
+ });
445
+ }
446
+ const isValid = Date.now() <= session.expiresAt && session.mfaVerified;
447
+ return redactSensitiveData({
448
+ active: isValid,
449
+ uid: session.uid,
450
+ email: session.email,
451
+ role: session.role,
452
+ mfaVerified: session.mfaVerified,
453
+ token: session.token,
454
+ expiresInSeconds: Math.max(0, Math.floor((session.expiresAt - Date.now()) / 1000))
455
+ });
456
+ }
457
+ export async function executeAdminAuditLogs(options = {}) {
458
+ const session = getStoredAdminSession();
459
+ if (!session || !session.mfaVerified) {
460
+ if (options.isRemote && !options.isOffline) {
461
+ const err = new Error('Admin authentication required.');
462
+ err.code = 'UNAUTHORIZED';
463
+ throw err;
464
+ }
465
+ return {
466
+ mode: 'offline-stage-b-simulated',
467
+ logs: [
468
+ { timestamp: new Date().toISOString(), action: 'SYSTEM_BOOT', status: 'SUCCESS' },
469
+ { timestamp: new Date().toISOString(), action: 'SECURITY_CHECK_STAGE_B', status: 'PASSED' }
470
+ ]
471
+ };
472
+ }
473
+ const effectiveApiUrl = options.apiUrl || session.apiUrl || process.env.CREATORVERSE_API_URL || 'http://localhost:3000';
474
+ const isRemote = options.isRemote || Boolean(options.apiUrl) || Boolean(session.apiUrl);
475
+ const timeoutMs = options.timeoutMs || 5000;
476
+ if (isRemote && !options.isOffline) {
477
+ const response = await fetchWithTimeout(`${effectiveApiUrl}/api/admin/cli-session/audit-logs`, {
478
+ method: 'GET',
479
+ headers: {
480
+ 'Authorization': `Bearer ${session.sessionToken || session.token}`,
481
+ 'User-Agent': `CreatorVerse-Admin-CLI/${VERSION}`
482
+ }
483
+ }, timeoutMs);
484
+ const data = await response.json();
485
+ if (!response.ok) {
486
+ const err = new Error(data.error || 'Failed to access audit logs.');
487
+ err.code = data.code || 'FORBIDDEN';
488
+ throw err;
489
+ }
490
+ return redactSensitiveData(data);
491
+ }
492
+ // Offline mode
493
+ return redactSensitiveData({
494
+ mode: 'secure-stage-c',
495
+ adminUid: session.uid,
496
+ logs: [
497
+ { timestamp: new Date().toISOString(), action: 'ADMIN_LOGIN_SUCCESS', admin: session.email, mfa: true, device: session.deviceId },
498
+ { timestamp: new Date().toISOString(), action: 'AUDIT_LOGS_ACCESSED', status: 'AUTHORIZED' }
499
+ ]
500
+ });
501
+ }
502
+ export function printHelp() {
503
+ console.log(`
504
+ CreatorVerse Admin CLI (v${VERSION})
505
+
506
+ USAGE:
507
+ creatorverse-admin <command> [options]
508
+
509
+ COMMANDS:
510
+ --version, -v Display Admin CLI version
511
+ --help, -h Display this help message
512
+ login Authenticate admin session (browser PKCE or encrypted vault)
513
+ logout Revoke admin session on server and clear local vault
514
+ whoami Display authenticated admin identity (verified with backend)
515
+ permissions Display server-authoritative RBAC permission matrix
516
+ session status Check admin session freshness and server validity
517
+ audit-logs Display server audit logs (requires audit-logs:read permission)
518
+ system-status Show system health status
519
+ verify-mfa-mock Simulate MFA verification check
520
+
521
+ OPTIONS:
522
+ --browser Launch interactive browser-assisted PKCE authorization flow
523
+ --api-url <url> Target backend API endpoint URL
524
+ --remote Execute against backend API instead of local offline sandbox
525
+ --offline Explicit offline test mode
526
+ --token <token> Firebase ID token or test token
527
+ --mfa-code <code> TOTP code (allowed strictly in test mode; interactive prompt required otherwise)
528
+ --device-id <id> Unique workstation device identifier
529
+ --device-name <name> Friendly workstation device name
530
+ --timeout <ms> Network request timeout in milliseconds (default: 5000)
531
+ `);
532
+ }
533
+ export async function main() {
534
+ const args = process.argv.slice(2);
535
+ const command = args[0];
536
+ const subCommand = args[1];
537
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
538
+ printHelp();
539
+ process.exit(0);
540
+ }
541
+ if (command === '--version' || command === '-v' || command === 'version') {
542
+ console.log(`creatorverse-admin v${VERSION}`);
543
+ process.exit(0);
544
+ }
545
+ const options = {};
546
+ for (let i = 1; i < args.length; i++) {
547
+ if (args[i] === '--api-url' && args[i + 1]) {
548
+ options.apiUrl = args[i + 1];
549
+ options.isRemote = true;
550
+ i++;
551
+ }
552
+ else if (args[i] === '--remote') {
553
+ options.isRemote = true;
554
+ }
555
+ else if (args[i] === '--browser') {
556
+ options.isBrowser = true;
557
+ options.isRemote = true;
558
+ }
559
+ else if (args[i] === '--offline') {
560
+ options.isOffline = true;
561
+ }
562
+ else if (args[i] === '--token' && args[i + 1]) {
563
+ options.token = args[i + 1];
564
+ i++;
565
+ }
566
+ else if (args[i] === '--email' && args[i + 1]) {
567
+ options.email = args[i + 1];
568
+ i++;
569
+ }
570
+ else if (args[i] === '--role' && args[i + 1]) {
571
+ options.role = args[i + 1];
572
+ i++;
573
+ }
574
+ else if (args[i] === '--mfa-code' && args[i + 1]) {
575
+ options.mfaCode = args[i + 1];
576
+ i++;
577
+ }
578
+ else if (args[i] === '--device-id' && args[i + 1]) {
579
+ options.deviceId = args[i + 1];
580
+ i++;
581
+ }
582
+ else if (args[i] === '--device-name' && args[i + 1]) {
583
+ options.deviceName = args[i + 1];
584
+ i++;
585
+ }
586
+ else if (args[i] === '--timeout' && args[i + 1]) {
587
+ options.timeoutMs = parseInt(args[i + 1], 10) || 5000;
588
+ i++;
589
+ }
590
+ }
591
+ try {
592
+ switch (command) {
593
+ case 'login': {
594
+ const res = await executeAdminLogin(options);
595
+ console.log(JSON.stringify(res, null, 2));
596
+ break;
597
+ }
598
+ case 'logout': {
599
+ const res = await executeAdminLogout(options);
600
+ console.log(JSON.stringify(res, null, 2));
601
+ break;
602
+ }
603
+ case 'whoami': {
604
+ const res = await executeAdminWhoami(options);
605
+ console.log(JSON.stringify(res, null, 2));
606
+ break;
607
+ }
608
+ case 'permissions': {
609
+ const res = await executeAdminPermissions(options);
610
+ console.log(JSON.stringify(res, null, 2));
611
+ break;
612
+ }
613
+ case 'session': {
614
+ if (subCommand === 'status') {
615
+ const res = await executeAdminSessionStatus(options);
616
+ console.log(JSON.stringify(res, null, 2));
617
+ }
618
+ else {
619
+ console.error(`Error: Unknown session subcommand "${subCommand}".`);
620
+ process.exit(1);
621
+ }
622
+ break;
623
+ }
624
+ case 'audit-logs': {
625
+ const res = await executeAdminAuditLogs(options);
626
+ console.log(JSON.stringify(res, null, 2));
627
+ break;
628
+ }
629
+ case 'system-status': {
630
+ const session = getStoredAdminSession();
631
+ if (!session || !session.mfaVerified) {
632
+ console.log(JSON.stringify({
633
+ environment: 'sandbox',
634
+ database: 'isolated',
635
+ emulators: 'offline',
636
+ status: 'HEALTHY'
637
+ }, null, 2));
638
+ break;
639
+ }
640
+ console.log(JSON.stringify({
641
+ environment: 'secure-sandbox',
642
+ database: 'connected',
643
+ authService: 'firebase-secure',
644
+ mfaEnforced: true,
645
+ status: 'HEALTHY'
646
+ }, null, 2));
647
+ break;
648
+ }
649
+ case 'verify-mfa-mock': {
650
+ console.log(JSON.stringify({
651
+ mfaStatus: 'verified-mock',
652
+ deviceAuthorized: true
653
+ }, null, 2));
654
+ break;
655
+ }
656
+ default:
657
+ console.error(`Error: Unknown admin command "${command}". Run "creatorverse-admin help" for usage.`);
658
+ process.exit(1);
659
+ }
660
+ }
661
+ catch (err) {
662
+ console.error(JSON.stringify({
663
+ error: err.code || 'ERROR',
664
+ message: err.message
665
+ }, null, 2));
666
+ process.exit(1);
667
+ }
668
+ }
669
+ const isMain = process.argv[1] && (process.argv[1].endsWith('creatorverse-admin.ts') ||
670
+ process.argv[1].endsWith('creatorverse-admin.js') ||
671
+ (process.argv[1].includes('creatorverse-admin') && !process.argv[1].includes('.test.')));
672
+ if (isMain) {
673
+ main().catch((err) => {
674
+ console.error(JSON.stringify({ error: 'UNHANDLED_ERROR', message: err.message }, null, 2));
675
+ process.exit(1);
676
+ });
677
+ }
678
+ //# sourceMappingURL=creatorverse-admin.js.map