@eve-horizon/cli 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,649 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleSystem = handleSystem;
4
- const args_1 = require("../lib/args");
5
- const client_1 = require("../lib/client");
6
- const output_1 = require("../lib/output");
7
- // ============================================================================
8
- // Main Handler
9
- // ============================================================================
10
- /**
11
- * System administration commands.
12
- *
13
- * NOTE: When auth is implemented, these commands should be restricted to
14
- * system administrators only - not regular org users.
15
- */
16
- async function handleSystem(subcommand, positionals, flags, context) {
17
- const json = Boolean(flags.json);
18
- switch (subcommand) {
19
- case 'status':
20
- return handleStatus(context, json);
21
- case 'health':
22
- return handleHealth(context, json);
23
- case 'jobs':
24
- return handleJobs(flags, context, json);
25
- case 'envs':
26
- return handleEnvs(flags, context, json);
27
- case 'logs':
28
- return handleLogs(positionals, flags, context, json);
29
- case 'pods':
30
- return handlePods(context, json);
31
- case 'events':
32
- return handleEvents(flags, context, json);
33
- case 'config':
34
- return handleConfig(context, json);
35
- case 'settings':
36
- return handleSettings(positionals, flags, context, json);
37
- case 'orchestrator':
38
- return handleOrchestrator(positionals, flags, context, json);
39
- default:
40
- throw new Error('Usage: eve system <status|health|jobs|envs|logs|pods|events|config|settings|orchestrator>');
41
- }
42
- }
43
- // ============================================================================
44
- // Subcommand Handlers
45
- // ============================================================================
46
- /**
47
- * eve system status
48
- * Show comprehensive system status via API endpoint.
49
- *
50
- * Note: This calls GET /system/status on the API. The API is responsible for
51
- * aggregating health from internal services (orchestrator, worker, etc.).
52
- * The CLI only talks to the API - never directly to other services.
53
- */
54
- async function handleStatus(context, json) {
55
- try {
56
- // Try the full system status endpoint first
57
- const status = await (0, client_1.requestJson)(context, '/system/status');
58
- if (json) {
59
- (0, output_1.outputJson)(status, json);
60
- }
61
- else {
62
- formatStatus(status);
63
- }
64
- }
65
- catch (error) {
66
- // If /system/status is missing, fall back to basic health check
67
- const err = error;
68
- if (err.message?.includes('HTTP 404')) {
69
- console.log('Note: /system/status not available on this API version.');
70
- console.log('Falling back to basic health check...');
71
- console.log('');
72
- return handleHealth(context, json);
73
- }
74
- throw error;
75
- }
76
- }
77
- /**
78
- * eve system health
79
- * Quick health check of the API.
80
- *
81
- * Note: This only checks the API's health endpoint. The API is the gateway -
82
- * if the API is healthy and connected to the database, the system is operational.
83
- */
84
- async function handleHealth(context, json) {
85
- try {
86
- const health = await (0, client_1.requestJson)(context, '/health');
87
- if (json) {
88
- (0, output_1.outputJson)(health, json);
89
- }
90
- else {
91
- const isHealthy = health.status === 'ok' || health.status === 'healthy';
92
- const icon = isHealthy ? '✓' : '✗';
93
- const status = isHealthy ? 'Healthy' : 'Unhealthy';
94
- console.log('System Health Check');
95
- console.log('═══════════════════════════════════════');
96
- console.log('');
97
- console.log(` ${icon} API: ${status}`);
98
- if (health.database) {
99
- console.log(` Database: ${health.database}`);
100
- }
101
- if (health.version) {
102
- console.log(` Version: ${health.version}`);
103
- }
104
- console.log('');
105
- if (isHealthy) {
106
- console.log('API is operational.');
107
- }
108
- else {
109
- console.log('API is not healthy. Check server logs.');
110
- }
111
- }
112
- }
113
- catch (error) {
114
- const err = error;
115
- if (json) {
116
- (0, output_1.outputJson)({
117
- api: { healthy: false, error: err.message },
118
- }, json);
119
- }
120
- else {
121
- console.log('System Health Check');
122
- console.log('═══════════════════════════════════════');
123
- console.log('');
124
- console.log(' ✗ API: Unreachable');
125
- console.log(` ${err.message}`);
126
- console.log('');
127
- console.log(`Make sure the API is running and EVE_API_URL is set correctly.`);
128
- console.log(`Current: ${context.apiUrl}`);
129
- }
130
- }
131
- }
132
- // ============================================================================
133
- // Formatting Helpers
134
- // ============================================================================
135
- function formatStatus(status) {
136
- console.log('╭────────────────────────────────────────────────────────────────╮');
137
- console.log('│ System Status │');
138
- console.log('╰────────────────────────────────────────────────────────────────╯');
139
- console.log('');
140
- // Services
141
- console.log('Services:');
142
- formatServiceHealth(' API', status.api.healthy, status.api.version);
143
- formatServiceHealth(' Orchestrator', status.orchestrator.healthy);
144
- formatServiceHealth(' Worker', status.worker.healthy);
145
- console.log('');
146
- // Queue
147
- console.log('Job Queue:');
148
- console.log(` Ready: ${status.queue.ready}`);
149
- console.log(` Active: ${status.queue.active}`);
150
- console.log(` Blocked: ${status.queue.blocked}`);
151
- console.log('');
152
- // Overall assessment
153
- const allHealthy = status.api.healthy && status.orchestrator.healthy && status.worker.healthy;
154
- if (allHealthy) {
155
- console.log('Status: All systems operational');
156
- }
157
- else {
158
- const unhealthy = [];
159
- if (!status.api.healthy)
160
- unhealthy.push('API');
161
- if (!status.orchestrator.healthy)
162
- unhealthy.push('Orchestrator');
163
- if (!status.worker.healthy)
164
- unhealthy.push('Worker');
165
- console.log(`Status: Issues detected with: ${unhealthy.join(', ')}`);
166
- }
167
- }
168
- function formatServiceHealth(name, healthy, version) {
169
- const icon = healthy ? '✓' : '✗';
170
- const status = healthy ? 'healthy' : 'unhealthy';
171
- const versionStr = version ? ` (v${version})` : '';
172
- console.log(`${name}: ${icon} ${status}${versionStr}`);
173
- }
174
- /**
175
- * eve system jobs [--org=X] [--project=X] [--phase=X] [--limit=50] [--offset=0]
176
- * Admin view: list all jobs across all projects
177
- */
178
- async function handleJobs(flags, context, json) {
179
- const query = buildQuery({
180
- org_id: (0, args_1.getStringFlag)(flags, ['org']),
181
- project_id: (0, args_1.getStringFlag)(flags, ['project']),
182
- phase: (0, args_1.getStringFlag)(flags, ['phase']),
183
- limit: (0, args_1.getStringFlag)(flags, ['limit']) ?? '50',
184
- offset: (0, args_1.getStringFlag)(flags, ['offset']),
185
- });
186
- const response = await (0, client_1.requestJson)(context, `/jobs${query}`);
187
- if (json) {
188
- (0, output_1.outputJson)(response, json);
189
- }
190
- else {
191
- if (response.jobs.length === 0) {
192
- console.log('No jobs found.');
193
- return;
194
- }
195
- console.log('System Jobs (admin view):');
196
- console.log('');
197
- formatJobsTable(response.jobs);
198
- }
199
- }
200
- /**
201
- * eve system envs [--org=X] [--project=X] [--limit=50] [--offset=0]
202
- * Admin view: list all environments across all projects
203
- */
204
- async function handleEnvs(flags, context, json) {
205
- const query = buildQuery({
206
- org_id: (0, args_1.getStringFlag)(flags, ['org']),
207
- project_id: (0, args_1.getStringFlag)(flags, ['project']),
208
- limit: (0, args_1.getStringFlag)(flags, ['limit']) ?? '50',
209
- offset: (0, args_1.getStringFlag)(flags, ['offset']),
210
- });
211
- try {
212
- const response = await (0, client_1.requestJson)(context, `/system/envs${query}`);
213
- if (json) {
214
- (0, output_1.outputJson)(response, json);
215
- }
216
- else {
217
- if (response.environments.length === 0) {
218
- console.log('No environments found.');
219
- return;
220
- }
221
- console.log('System Environments (admin view):');
222
- console.log('');
223
- formatEnvsTable(response.environments);
224
- }
225
- }
226
- catch (error) {
227
- const err = error;
228
- if (err.message?.includes('HTTP 404')) {
229
- console.error('Error: The /system/envs endpoint is not yet implemented.');
230
- console.error('Please use "eve env list" within a project context instead.');
231
- process.exit(1);
232
- }
233
- throw error;
234
- }
235
- }
236
- /**
237
- * eve system logs <service> [--tail=100]
238
- * Fetch recent logs for a service via API.
239
- */
240
- async function handleLogs(positionals, flags, context, json) {
241
- const service = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['service']);
242
- if (!service) {
243
- throw new Error('Usage: eve system logs <api|orchestrator|worker|agent-runtime|postgres> [--tail=<n>]');
244
- }
245
- const tail = (0, args_1.getStringFlag)(flags, ['tail']);
246
- const query = buildQuery({ tail: tail ?? '100' });
247
- const response = await (0, client_1.requestJson)(context, `/system/logs/${service}${query}`);
248
- if (json) {
249
- (0, output_1.outputJson)(response, json);
250
- return;
251
- }
252
- if (response.length === 0) {
253
- console.log('No logs found.');
254
- return;
255
- }
256
- for (const entry of response) {
257
- console.log(`[${entry.timestamp}] ${entry.line}`);
258
- }
259
- }
260
- /**
261
- * eve system pods
262
- * List pods via API.
263
- */
264
- async function handlePods(context, json) {
265
- const response = await (0, client_1.requestJson)(context, '/system/pods');
266
- if (json) {
267
- (0, output_1.outputJson)(response, json);
268
- return;
269
- }
270
- if (response.length === 0) {
271
- console.log('No pods found.');
272
- return;
273
- }
274
- console.log('Pods:');
275
- console.log('');
276
- formatPodsTable(response);
277
- }
278
- /**
279
- * eve system events [--limit=50]
280
- * List recent cluster events via API.
281
- */
282
- async function handleEvents(flags, context, json) {
283
- const limit = (0, args_1.getStringFlag)(flags, ['limit']);
284
- const query = buildQuery({ limit: limit ?? '50' });
285
- const response = await (0, client_1.requestJson)(context, `/system/events${query}`);
286
- if (json) {
287
- (0, output_1.outputJson)(response, json);
288
- return;
289
- }
290
- if (response.length === 0) {
291
- console.log('No events found.');
292
- return;
293
- }
294
- console.log('Recent Events:');
295
- for (const event of response) {
296
- const target = `${event.involvedObject.kind}/${event.involvedObject.name}`;
297
- console.log(`[${event.timestamp}] ${event.type} ${event.reason} ${target} - ${event.message}`);
298
- }
299
- }
300
- /**
301
- * eve system config
302
- * Show cluster config summary via API.
303
- */
304
- async function handleConfig(context, json) {
305
- const response = await (0, client_1.requestJson)(context, '/system/config');
306
- if (json) {
307
- (0, output_1.outputJson)(response, json);
308
- return;
309
- }
310
- console.log('Cluster Config:');
311
- console.log(` Namespace: ${response.namespace}`);
312
- if (response.clusterVersion) {
313
- console.log(` Cluster Version: ${response.clusterVersion}`);
314
- }
315
- if (response.nodeCount !== undefined) {
316
- console.log(` Node Count: ${response.nodeCount}`);
317
- }
318
- if (response.deployments.length > 0) {
319
- console.log(' Deployments:');
320
- response.deployments.forEach((deployment) => console.log(` - ${deployment}`));
321
- }
322
- }
323
- /**
324
- * eve system orchestrator <status|set-concurrency>
325
- * Manage orchestrator concurrency settings
326
- */
327
- async function handleOrchestrator(positionals, flags, context, json) {
328
- const subcommand = positionals[0];
329
- if (subcommand === 'status') {
330
- const response = await requestOrchestratorJson(context, '/system/orchestrator/status');
331
- if (json) {
332
- (0, output_1.outputJson)(response, json);
333
- }
334
- else {
335
- formatOrchestratorStatus(response);
336
- }
337
- return;
338
- }
339
- if (subcommand === 'set-concurrency') {
340
- const limitStr = positionals[1];
341
- if (!limitStr) {
342
- throw new Error('Usage: eve system orchestrator set-concurrency <n>');
343
- }
344
- const limit = parseInt(limitStr, 10);
345
- if (isNaN(limit) || limit < 1) {
346
- throw new Error('Concurrency limit must be a positive integer');
347
- }
348
- const response = await requestOrchestratorJson(context, '/system/orchestrator/concurrency', {
349
- method: 'POST',
350
- body: JSON.stringify({ limit }),
351
- });
352
- if (json) {
353
- (0, output_1.outputJson)(response, json);
354
- }
355
- else {
356
- console.log(`Concurrency limit updated to ${limit}`);
357
- console.log('');
358
- formatOrchestratorStatus(response);
359
- }
360
- return;
361
- }
362
- throw new Error('Usage: eve system orchestrator <status|set-concurrency>');
363
- }
364
- /**
365
- * eve system settings [get <key>] [set <key> <value>]
366
- * Admin only: Get or set system settings
367
- */
368
- async function handleSettings(positionals, flags, context, json) {
369
- const action = positionals[0];
370
- if (action === 'set') {
371
- const key = positionals[1];
372
- const value = positionals[2];
373
- if (!key || !value) {
374
- throw new Error('Usage: eve system settings set <key> <value>');
375
- }
376
- const body = { value };
377
- const response = await (0, client_1.requestJson)(context, `/system/settings/${key}`, {
378
- method: 'PUT',
379
- body: JSON.stringify(body),
380
- });
381
- if (json) {
382
- (0, output_1.outputJson)(response, json);
383
- }
384
- else {
385
- console.log(`Setting '${key}' updated:`);
386
- console.log(` Value: ${response.value}`);
387
- console.log(` Updated: ${response.updated_at}`);
388
- console.log(` By: ${response.updated_by}`);
389
- }
390
- return;
391
- }
392
- // Default: get all or get specific
393
- const key = positionals[0];
394
- if (key) {
395
- const response = await (0, client_1.requestJson)(context, `/system/settings/${key}`);
396
- if (json) {
397
- (0, output_1.outputJson)(response, json);
398
- }
399
- else {
400
- console.log(`Setting: ${response.key}`);
401
- console.log(` Value: ${response.value}`);
402
- if (response.description) {
403
- console.log(` Description: ${response.description}`);
404
- }
405
- console.log(` Updated: ${response.updated_at}`);
406
- console.log(` By: ${response.updated_by}`);
407
- }
408
- }
409
- else {
410
- const response = await (0, client_1.requestJson)(context, '/system/settings');
411
- if (json) {
412
- (0, output_1.outputJson)(response, json);
413
- }
414
- else {
415
- if (response.length === 0) {
416
- console.log('No system settings found.');
417
- return;
418
- }
419
- console.log('System Settings:');
420
- console.log('');
421
- for (const setting of response) {
422
- console.log(` ${setting.key}:`);
423
- console.log(` Value: ${setting.value}`);
424
- if (setting.description) {
425
- console.log(` Description: ${setting.description}`);
426
- }
427
- console.log(` Updated: ${setting.updated_at} by ${setting.updated_by}`);
428
- console.log('');
429
- }
430
- }
431
- }
432
- }
433
- // ============================================================================
434
- // Helper Functions
435
- // ============================================================================
436
- function buildQuery(params) {
437
- const search = new URLSearchParams();
438
- Object.entries(params).forEach(([key, value]) => {
439
- if (value === undefined || value === '')
440
- return;
441
- search.set(key, String(value));
442
- });
443
- const query = search.toString();
444
- return query ? `?${query}` : '';
445
- }
446
- /**
447
- * Format jobs as a human-readable table
448
- */
449
- function formatJobsTable(jobs) {
450
- if (jobs.length === 0) {
451
- console.log('No jobs found.');
452
- return;
453
- }
454
- // Calculate column widths
455
- const idWidth = Math.max(6, ...jobs.map((j) => j.id.length));
456
- const projectWidth = Math.max(10, ...jobs.map((j) => j.project_id.length));
457
- const phaseWidth = Math.max(5, ...jobs.map((j) => j.phase.length));
458
- const titleWidth = Math.min(40, Math.max(5, ...jobs.map((j) => j.title.length)));
459
- // Header
460
- const header = [
461
- padRight('Job ID', idWidth),
462
- padRight('Project', projectWidth),
463
- padRight('Phase', phaseWidth),
464
- padRight('P', 2),
465
- padRight('Title', titleWidth),
466
- 'Assignee',
467
- ].join(' ');
468
- console.log(header);
469
- console.log('-'.repeat(header.length));
470
- // Rows
471
- for (const job of jobs) {
472
- const title = job.title.length > titleWidth ? job.title.slice(0, titleWidth - 3) + '...' : job.title;
473
- const row = [
474
- padRight(job.id, idWidth),
475
- padRight(job.project_id, projectWidth),
476
- padRight(job.phase, phaseWidth),
477
- padRight(`P${job.priority}`, 2),
478
- padRight(title, titleWidth),
479
- job.assignee ?? '-',
480
- ].join(' ');
481
- console.log(row);
482
- }
483
- console.log('');
484
- console.log(`Total: ${jobs.length} job(s)`);
485
- }
486
- /**
487
- * Format environments as a human-readable table
488
- */
489
- function formatEnvsTable(envs) {
490
- if (envs.length === 0) {
491
- console.log('No environments found.');
492
- return;
493
- }
494
- // Calculate column widths
495
- const projectWidth = Math.max(10, ...envs.map((e) => e.project_id.length));
496
- const nameWidth = Math.max(12, ...envs.map((e) => e.name.length));
497
- const typeWidth = Math.max(4, ...envs.map((e) => e.type.length));
498
- const namespaceWidth = Math.max(9, ...envs.map((e) => (e.namespace ?? '-').length));
499
- const releaseWidth = Math.max(7, ...envs.map((e) => (e.current_release ?? '-').length));
500
- // Header
501
- const header = [
502
- padRight('Project', projectWidth),
503
- padRight('Environment', nameWidth),
504
- padRight('Type', typeWidth),
505
- padRight('Namespace', namespaceWidth),
506
- padRight('Release', releaseWidth),
507
- ].join(' ');
508
- console.log(header);
509
- console.log('-'.repeat(header.length));
510
- // Rows
511
- for (const env of envs) {
512
- const row = [
513
- padRight(env.project_id, projectWidth),
514
- padRight(env.name, nameWidth),
515
- padRight(env.type, typeWidth),
516
- padRight(env.namespace ?? '-', namespaceWidth),
517
- padRight(env.current_release ?? '-', releaseWidth),
518
- ].join(' ');
519
- console.log(row);
520
- }
521
- console.log('');
522
- console.log(`Total: ${envs.length} environment(s)`);
523
- }
524
- function formatPodsTable(pods) {
525
- if (pods.length === 0) {
526
- console.log('No pods found.');
527
- return;
528
- }
529
- const nameWidth = Math.max(8, ...pods.map((p) => p.name.length));
530
- const nsWidth = Math.max(8, ...pods.map((p) => p.namespace.length));
531
- const phaseWidth = Math.max(5, ...pods.map((p) => p.phase.length));
532
- const readyWidth = 5;
533
- const restartsWidth = Math.max(8, ...pods.map((p) => String(p.restarts).length));
534
- const ageWidth = Math.max(3, ...pods.map((p) => p.age.length));
535
- const header = [
536
- padRight('Name', nameWidth),
537
- padRight('Namespace', nsWidth),
538
- padRight('Phase', phaseWidth),
539
- padRight('Ready', readyWidth),
540
- padRight('Restarts', restartsWidth),
541
- padRight('Age', ageWidth),
542
- 'Component',
543
- 'Org',
544
- 'Project',
545
- 'Env',
546
- ].join(' ');
547
- console.log(header);
548
- console.log('-'.repeat(header.length));
549
- for (const pod of pods) {
550
- console.log([
551
- padRight(pod.name, nameWidth),
552
- padRight(pod.namespace, nsWidth),
553
- padRight(pod.phase, phaseWidth),
554
- padRight(pod.ready ? 'yes' : 'no', readyWidth),
555
- padRight(String(pod.restarts), restartsWidth),
556
- padRight(pod.age, ageWidth),
557
- padRight(pod.component ?? '-', 10),
558
- padRight(pod.orgId ?? '-', 10),
559
- padRight(pod.projectId ?? '-', 10),
560
- padRight(pod.env ?? '-', 10),
561
- ].join(' '));
562
- }
563
- }
564
- /**
565
- * Pad a string to the right with spaces
566
- */
567
- function padRight(str, width) {
568
- if (str.length >= width)
569
- return str;
570
- return str + ' '.repeat(width - str.length);
571
- }
572
- /**
573
- * Get orchestrator URL from context.
574
- * Derives from API URL by replacing the port with the orchestrator port.
575
- */
576
- function getOrchestratorUrl(context) {
577
- const envUrl = process.env.EVE_ORCHESTRATOR_URL;
578
- if (envUrl) {
579
- return envUrl;
580
- }
581
- // Derive from API URL by changing the port
582
- const orchPort = process.env.EVE_ORCHESTRATOR_PORT || '4802';
583
- const apiUrl = new URL(context.apiUrl);
584
- apiUrl.port = orchPort;
585
- return apiUrl.toString().replace(/\/$/, '');
586
- }
587
- /**
588
- * Request JSON from the orchestrator service.
589
- * Similar to requestJson but targets the orchestrator directly.
590
- */
591
- async function requestOrchestratorJson(context, path, options) {
592
- const orchUrl = getOrchestratorUrl(context);
593
- const url = `${orchUrl}${path}`;
594
- const headers = {};
595
- if (options?.body) {
596
- headers['Content-Type'] = 'application/json';
597
- }
598
- if (context.token) {
599
- headers.Authorization = `Bearer ${context.token}`;
600
- }
601
- const response = await fetch(url, {
602
- method: options?.method ?? 'GET',
603
- headers,
604
- body: options?.body,
605
- });
606
- const text = await response.text();
607
- let data = null;
608
- if (text) {
609
- try {
610
- data = JSON.parse(text);
611
- }
612
- catch {
613
- data = text;
614
- }
615
- }
616
- if (!response.ok) {
617
- const message = typeof data === 'string' ? data : text;
618
- throw new Error(`HTTP ${response.status}: ${message}`);
619
- }
620
- return data;
621
- }
622
- /**
623
- * Format orchestrator status for human-readable output
624
- */
625
- function formatOrchestratorStatus(status) {
626
- console.log('Orchestrator Concurrency Status');
627
- console.log('═══════════════════════════════════════');
628
- console.log('');
629
- console.log(` Limit: ${status.limit}`);
630
- console.log(` In-Flight: ${status.inFlight}`);
631
- console.log(` Uptime: ${formatUptime(status.uptimeSeconds)}`);
632
- console.log(` Last Change: ${status.lastChange}`);
633
- console.log('');
634
- }
635
- /**
636
- * Format uptime seconds to human-readable format
637
- */
638
- function formatUptime(seconds) {
639
- const hours = Math.floor(seconds / 3600);
640
- const minutes = Math.floor((seconds % 3600) / 60);
641
- const secs = seconds % 60;
642
- if (hours > 0) {
643
- return `${hours}h ${minutes}m ${secs}s`;
644
- }
645
- if (minutes > 0) {
646
- return `${minutes}m ${secs}s`;
647
- }
648
- return `${secs}s`;
649
- }