@eve-horizon/cli 0.2.1 → 0.2.5

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.
@@ -271,6 +271,46 @@ async function handleAuth(subcommand, flags, context, credentials) {
271
271
  console.log(tokenEntry.access_token);
272
272
  return;
273
273
  }
274
+ case 'mint': {
275
+ const email = (0, args_1.getStringFlag)(flags, ['email']);
276
+ const orgId = (0, args_1.getStringFlag)(flags, ['org']);
277
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']);
278
+ const role = (0, args_1.getStringFlag)(flags, ['role']) ?? 'member';
279
+ const ttlStr = (0, args_1.getStringFlag)(flags, ['ttl']);
280
+ const ttlDays = ttlStr ? parseInt(ttlStr, 10) : undefined;
281
+ if (ttlDays !== undefined && (isNaN(ttlDays) || ttlDays < 1 || ttlDays > 90)) {
282
+ throw new Error('--ttl must be between 1 and 90 days');
283
+ }
284
+ if (!email) {
285
+ throw new Error('Usage: eve auth mint --email <email> [--org <org_id> | --project <project_id>] [--role <role>]');
286
+ }
287
+ if (!orgId && !projectId) {
288
+ throw new Error('Usage: eve auth mint --email <email> [--org <org_id> | --project <project_id>] [--role <role>]');
289
+ }
290
+ if (!['admin', 'member'].includes(role)) {
291
+ throw new Error(`Invalid role: ${role}. Must be one of: admin, member`);
292
+ }
293
+ const response = await (0, client_1.requestJson)(context, '/auth/mint', {
294
+ method: 'POST',
295
+ body: {
296
+ email,
297
+ org_id: orgId,
298
+ project_id: projectId,
299
+ role,
300
+ ttl_days: ttlDays,
301
+ },
302
+ });
303
+ if (json) {
304
+ (0, output_1.outputJson)(response, json);
305
+ return;
306
+ }
307
+ if (!response.access_token) {
308
+ throw new Error('Mint response missing access_token');
309
+ }
310
+ // Print only the token to stdout, nothing else
311
+ console.log(response.access_token);
312
+ return;
313
+ }
274
314
  case 'sync': {
275
315
  const claudeOnly = (0, args_1.getBooleanFlag)(flags, ['claude']) ?? false;
276
316
  const codexOnly = (0, args_1.getBooleanFlag)(flags, ['codex']) ?? false;
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleBuild = handleBuild;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ // ---------------------------------------------------------------------------
8
+ // Entry point
9
+ // ---------------------------------------------------------------------------
10
+ async function handleBuild(subcommand, positionals, flags, context) {
11
+ const json = Boolean(flags.json);
12
+ switch (subcommand) {
13
+ case 'create':
14
+ return handleCreate(flags, context, json);
15
+ case 'list':
16
+ return handleList(positionals, flags, context, json);
17
+ case 'show':
18
+ return handleShow(positionals, flags, context, json);
19
+ case 'run':
20
+ return handleRun(positionals, flags, context, json);
21
+ case 'runs':
22
+ return handleRuns(positionals, flags, context, json);
23
+ case 'logs':
24
+ return handleLogs(positionals, flags, context, json);
25
+ case 'artifacts':
26
+ return handleArtifacts(positionals, flags, context, json);
27
+ case 'diagnose':
28
+ return handleDiagnose(positionals, flags, context, json);
29
+ case 'cancel':
30
+ return handleCancel(positionals, flags, context, json);
31
+ default:
32
+ throw new Error('Usage: eve build <create|list|show|run|runs|logs|artifacts|diagnose|cancel>\n' +
33
+ ' create --project <id> --ref <sha> [--services <list>] - create a build spec\n' +
34
+ ' list [--project <id>] - list build specs\n' +
35
+ ' show <build_id> - show build spec details\n' +
36
+ ' run <build_id> - start a build run\n' +
37
+ ' runs <build_id> - list runs for a build\n' +
38
+ ' logs <build_id> [--run <id>] - show build logs\n' +
39
+ ' artifacts <build_id> - list build artifacts\n' +
40
+ ' diagnose <build_id> - show full build state\n' +
41
+ ' cancel <build_id> - cancel active build run');
42
+ }
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Subcommands
46
+ // ---------------------------------------------------------------------------
47
+ async function handleCreate(flags, context, json) {
48
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
49
+ const ref = (0, args_1.getStringFlag)(flags, ['ref']);
50
+ const manifestHash = (0, args_1.getStringFlag)(flags, ['manifest-hash', 'manifest']);
51
+ const services = (0, args_1.getStringFlag)(flags, ['services']);
52
+ if (!projectId || !ref || !manifestHash) {
53
+ throw new Error('Usage: eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>]');
54
+ }
55
+ const body = { git_sha: ref, manifest_hash: manifestHash };
56
+ if (services) {
57
+ body.services = services.split(',').map(s => s.trim());
58
+ }
59
+ const build = await (0, client_1.requestJson)(context, `/projects/${projectId}/builds`, { method: 'POST', body });
60
+ if (json) {
61
+ (0, output_1.outputJson)(build, json);
62
+ return;
63
+ }
64
+ console.log(`Build created: ${build.id}`);
65
+ console.log(` Project: ${build.project_id}`);
66
+ console.log(` SHA: ${build.git_sha}`);
67
+ if (build.manifest_hash) {
68
+ console.log(` Manifest: ${build.manifest_hash.substring(0, 12)}...`);
69
+ }
70
+ console.log(` Created: ${build.created_at}`);
71
+ }
72
+ async function handleList(positionals, flags, context, json) {
73
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
74
+ if (!projectId) {
75
+ throw new Error('Usage: eve build list [--project <id>]');
76
+ }
77
+ const query = buildQuery({
78
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
79
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
80
+ });
81
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/builds${query}`);
82
+ if (json) {
83
+ (0, output_1.outputJson)(response, json);
84
+ return;
85
+ }
86
+ if (response.data.length === 0) {
87
+ console.log('No builds found.');
88
+ return;
89
+ }
90
+ formatBuildList(response.data);
91
+ }
92
+ async function handleShow(positionals, flags, context, json) {
93
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
94
+ if (!buildId) {
95
+ throw new Error('Usage: eve build show <build_id>');
96
+ }
97
+ const build = await (0, client_1.requestJson)(context, `/builds/${buildId}`);
98
+ if (json) {
99
+ (0, output_1.outputJson)(build, json);
100
+ return;
101
+ }
102
+ formatBuildDetail(build);
103
+ }
104
+ async function handleRun(positionals, flags, context, json) {
105
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
106
+ if (!buildId) {
107
+ throw new Error('Usage: eve build run <build_id>');
108
+ }
109
+ const run = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs`, { method: 'POST' });
110
+ if (json) {
111
+ (0, output_1.outputJson)(run, json);
112
+ return;
113
+ }
114
+ console.log(`Build run started: ${run.id}`);
115
+ console.log(` Build: ${run.build_id}`);
116
+ console.log(` Status: ${run.status}`);
117
+ if (run.backend) {
118
+ console.log(` Backend: ${run.backend}`);
119
+ }
120
+ }
121
+ async function handleRuns(positionals, flags, context, json) {
122
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
123
+ if (!buildId) {
124
+ throw new Error('Usage: eve build runs <build_id>');
125
+ }
126
+ const query = buildQuery({
127
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
128
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
129
+ });
130
+ const response = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs${query}`);
131
+ if (json) {
132
+ (0, output_1.outputJson)(response, json);
133
+ return;
134
+ }
135
+ if (response.data.length === 0) {
136
+ console.log('No runs found for this build.');
137
+ return;
138
+ }
139
+ formatRunList(response.data);
140
+ }
141
+ async function handleLogs(positionals, flags, context, json) {
142
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
143
+ const runId = (0, args_1.getStringFlag)(flags, ['run']);
144
+ if (!buildId) {
145
+ throw new Error('Usage: eve build logs <build_id> [--run <run_id>]');
146
+ }
147
+ const query = runId ? `?run_id=${encodeURIComponent(runId)}` : '';
148
+ const logs = await (0, client_1.requestJson)(context, `/builds/${buildId}/logs${query}`);
149
+ if (json) {
150
+ (0, output_1.outputJson)(logs, json);
151
+ return;
152
+ }
153
+ if (logs.logs.length === 0) {
154
+ console.log('No logs available.');
155
+ return;
156
+ }
157
+ for (const entry of logs.logs) {
158
+ const line = entry.line;
159
+ if (typeof line.message === 'string') {
160
+ console.log(line.message);
161
+ continue;
162
+ }
163
+ if (Array.isArray(line.lines)) {
164
+ for (const item of line.lines) {
165
+ if (typeof item === 'string') {
166
+ console.log(item);
167
+ }
168
+ }
169
+ continue;
170
+ }
171
+ console.log(JSON.stringify(line));
172
+ }
173
+ }
174
+ async function handleArtifacts(positionals, flags, context, json) {
175
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
176
+ if (!buildId) {
177
+ throw new Error('Usage: eve build artifacts <build_id>');
178
+ }
179
+ const response = await (0, client_1.requestJson)(context, `/builds/${buildId}/artifacts`);
180
+ if (json) {
181
+ (0, output_1.outputJson)(response, json);
182
+ return;
183
+ }
184
+ if (response.data.length === 0) {
185
+ console.log('No artifacts found for this build.');
186
+ return;
187
+ }
188
+ formatArtifactList(response.data);
189
+ }
190
+ async function handleCancel(positionals, flags, context, json) {
191
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
192
+ if (!buildId) {
193
+ throw new Error('Usage: eve build cancel <build_id>');
194
+ }
195
+ const result = await (0, client_1.requestJson)(context, `/builds/${buildId}/cancel`, { method: 'POST' });
196
+ if (json) {
197
+ (0, output_1.outputJson)(result, json);
198
+ return;
199
+ }
200
+ console.log(`Build cancelled: ${buildId}`);
201
+ }
202
+ async function handleDiagnose(positionals, flags, context, json) {
203
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
204
+ if (!buildId) {
205
+ throw new Error('Usage: eve build diagnose <build_id>');
206
+ }
207
+ const spec = await (0, client_1.requestJson)(context, `/builds/${buildId}`);
208
+ const runs = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs?limit=20&offset=0`);
209
+ const artifacts = await (0, client_1.requestJson)(context, `/builds/${buildId}/artifacts`);
210
+ const latestRun = runs.data[0];
211
+ const logs = latestRun
212
+ ? await (0, client_1.requestJson)(context, `/builds/${buildId}/logs?run_id=${encodeURIComponent(latestRun.id)}`)
213
+ : { logs: [] };
214
+ const payload = { spec, runs: runs.data, artifacts: artifacts.data, logs: logs.logs };
215
+ if (json) {
216
+ (0, output_1.outputJson)(payload, json);
217
+ return;
218
+ }
219
+ console.log(`Build diagnose: ${spec.id}`);
220
+ formatBuildDetail(spec);
221
+ console.log('');
222
+ if (runs.data.length > 0) {
223
+ formatRunList(runs.data);
224
+ }
225
+ else {
226
+ console.log('No runs found.');
227
+ }
228
+ console.log('');
229
+ if (artifacts.data.length > 0) {
230
+ formatArtifactList(artifacts.data);
231
+ }
232
+ else {
233
+ console.log('No artifacts found.');
234
+ }
235
+ console.log('');
236
+ if (logs.logs.length > 0) {
237
+ console.log('Recent logs:');
238
+ for (const entry of logs.logs.slice(-50)) {
239
+ const line = entry.line;
240
+ if (typeof line.message === 'string') {
241
+ console.log(line.message);
242
+ continue;
243
+ }
244
+ if (Array.isArray(line.lines)) {
245
+ for (const item of line.lines) {
246
+ if (typeof item === 'string') {
247
+ console.log(item);
248
+ }
249
+ }
250
+ continue;
251
+ }
252
+ console.log(JSON.stringify(line));
253
+ }
254
+ }
255
+ else {
256
+ console.log('No logs found.');
257
+ }
258
+ }
259
+ // ---------------------------------------------------------------------------
260
+ // Formatters
261
+ // ---------------------------------------------------------------------------
262
+ function formatBuildList(builds) {
263
+ console.log('Build ID'.padEnd(30) + 'SHA'.padEnd(12) + 'Created By'.padEnd(20) + 'Created');
264
+ console.log('-'.repeat(90));
265
+ for (const build of builds) {
266
+ const id = build.id.padEnd(30);
267
+ const sha = build.git_sha.substring(0, 8).padEnd(12);
268
+ const creator = (build.created_by ?? '-').padEnd(20);
269
+ const created = new Date(build.created_at).toLocaleString();
270
+ console.log(`${id}${sha}${creator}${created}`);
271
+ }
272
+ console.log('');
273
+ console.log(`Total: ${builds.length} builds`);
274
+ }
275
+ function formatBuildDetail(build) {
276
+ console.log(`Build: ${build.id}`);
277
+ console.log(` Project: ${build.project_id}`);
278
+ console.log(` SHA: ${build.git_sha}`);
279
+ console.log(` Manifest Hash: ${build.manifest_hash}`);
280
+ console.log(` Created By: ${build.created_by ?? '-'}`);
281
+ console.log(` Created: ${build.created_at}`);
282
+ if (build.services_json) {
283
+ console.log(` Services: ${JSON.stringify(build.services_json)}`);
284
+ }
285
+ if (build.inputs_json) {
286
+ console.log(` Inputs: ${JSON.stringify(build.inputs_json)}`);
287
+ }
288
+ if (build.registry_json) {
289
+ console.log(` Registry: ${JSON.stringify(build.registry_json)}`);
290
+ }
291
+ if (build.cache_json) {
292
+ console.log(` Cache: ${JSON.stringify(build.cache_json)}`);
293
+ }
294
+ }
295
+ function formatRunList(runs) {
296
+ console.log('Run ID'.padEnd(30) + 'Status'.padEnd(15) + 'Backend'.padEnd(15) + 'Started');
297
+ console.log('-'.repeat(80));
298
+ for (const run of runs) {
299
+ const id = run.id.padEnd(30);
300
+ const status = run.status.padEnd(15);
301
+ const backend = (run.backend ?? '-').padEnd(15);
302
+ const started = run.started_at ? new Date(run.started_at).toLocaleString() : '-';
303
+ console.log(`${id}${status}${backend}${started}`);
304
+ if (run.error_message) {
305
+ console.log(` Error: ${run.error_message}`);
306
+ }
307
+ }
308
+ console.log('');
309
+ console.log(`Total: ${runs.length} runs`);
310
+ }
311
+ function formatArtifactList(artifacts) {
312
+ console.log('Service'.padEnd(25) + 'Image'.padEnd(50) + 'Size');
313
+ console.log('-'.repeat(90));
314
+ for (const artifact of artifacts) {
315
+ const service = artifact.service_name.padEnd(25);
316
+ const image = artifact.image_ref.padEnd(50);
317
+ const size = artifact.size_bytes ? formatBytes(artifact.size_bytes) : '-';
318
+ console.log(`${service}${image}${size}`);
319
+ if (artifact.digest) {
320
+ console.log(` Digest: ${artifact.digest}`);
321
+ }
322
+ if (artifact.platforms_json && artifact.platforms_json.length > 0) {
323
+ console.log(` Platforms: ${artifact.platforms_json.join(', ')}`);
324
+ }
325
+ }
326
+ console.log('');
327
+ console.log(`Total: ${artifacts.length} artifacts`);
328
+ }
329
+ function formatBytes(bytes) {
330
+ if (bytes < 1024)
331
+ return `${bytes} B`;
332
+ if (bytes < 1024 * 1024)
333
+ return `${(bytes / 1024).toFixed(1)} KB`;
334
+ if (bytes < 1024 * 1024 * 1024)
335
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
336
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
337
+ }
338
+ // ---------------------------------------------------------------------------
339
+ // Utilities
340
+ // ---------------------------------------------------------------------------
341
+ function buildQuery(params) {
342
+ const search = new URLSearchParams();
343
+ Object.entries(params).forEach(([key, value]) => {
344
+ if (value === undefined || value === '')
345
+ return;
346
+ search.set(key, value);
347
+ });
348
+ const query = search.toString();
349
+ return query ? `?${query}` : '';
350
+ }
@@ -161,7 +161,7 @@ async function handleCreate(positionals, flags, context, json) {
161
161
  }
162
162
  }
163
163
  /**
164
- * eve env deploy [project] <name> --ref <sha> [--direct] [--inputs <json>]
164
+ * eve env deploy [project] <name> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>]
165
165
  * Deploy to an environment
166
166
  * If project is in profile, can use: eve env deploy <name> --ref <sha>
167
167
  */
@@ -194,7 +194,7 @@ async function handleDeploy(positionals, flags, context, json) {
194
194
  envName = flagName;
195
195
  }
196
196
  if (!projectId || !envName) {
197
- throw new Error('Usage: eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--project=<id>]');
197
+ throw new Error('Usage: eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>] [--project=<id>]');
198
198
  }
199
199
  // --ref flag is now required
200
200
  const gitSha = (0, args_1.getStringFlag)(flags, ['ref']);
@@ -226,6 +226,8 @@ async function handleDeploy(positionals, flags, context, json) {
226
226
  'Example: --inputs \'{"release_id":"rel_xxx","smoke_test":false}\'');
227
227
  }
228
228
  }
229
+ // Parse --image-tag flag (optional)
230
+ const imageTag = (0, args_1.getStringFlag)(flags, ['image-tag', 'image_tag', 'imageTag']);
229
231
  // POST deployment
230
232
  const body = {
231
233
  git_sha: gitSha,
@@ -237,6 +239,9 @@ async function handleDeploy(positionals, flags, context, json) {
237
239
  if (inputs) {
238
240
  body.inputs = inputs;
239
241
  }
242
+ if (imageTag) {
243
+ body.image_tag = imageTag;
244
+ }
240
245
  const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/deploy`, {
241
246
  method: 'POST',
242
247
  body,
@@ -12,6 +12,7 @@ async function handleOrg(subcommand, positionals, flags, context) {
12
12
  case 'ensure': {
13
13
  let orgId = typeof flags.id === 'string' ? flags.id : '';
14
14
  let orgName = typeof flags.name === 'string' ? flags.name : '';
15
+ let orgSlug = typeof flags.slug === 'string' ? flags.slug : '';
15
16
  const nameOrId = positionals[0];
16
17
  if (!orgId && nameOrId) {
17
18
  if (/^org_[a-zA-Z0-9]+$/.test(nameOrId)) {
@@ -25,7 +26,7 @@ async function handleOrg(subcommand, positionals, flags, context) {
25
26
  }
26
27
  orgId = orgId || context.orgId || DEFAULT_ORG_ID;
27
28
  orgName = orgName || DEFAULT_ORG_NAME;
28
- const body = { id: orgId, name: orgName };
29
+ const body = { id: orgId, name: orgName, ...(orgSlug ? { slug: orgSlug } : {}) };
29
30
  const org = await (0, client_1.requestJson)(context, '/orgs/ensure', {
30
31
  method: 'POST',
31
32
  body,
@@ -126,8 +126,9 @@ function handleProfile(subcommand, positionals, flags, config) {
126
126
  config.profiles[name] = {};
127
127
  }
128
128
  config.profiles[name] = applyProfileFlags(config.profiles[name], flags);
129
+ config.active_profile = name;
129
130
  (0, config_1.saveConfig)(config);
130
- (0, output_1.outputJson)({ name, ...config.profiles[name] }, json, `✓ Profile updated: ${name}`);
131
+ (0, output_1.outputJson)({ active_profile: name, ...config.profiles[name] }, json, `✓ Profile set: ${name}`);
131
132
  return;
132
133
  }
133
134
  case 'remove': {
@@ -34,8 +34,10 @@ async function handleSystem(subcommand, positionals, flags, context) {
34
34
  return handleConfig(context, json);
35
35
  case 'settings':
36
36
  return handleSettings(positionals, flags, context, json);
37
+ case 'orchestrator':
38
+ return handleOrchestrator(positionals, flags, context, json);
37
39
  default:
38
- throw new Error('Usage: eve system <status|health|jobs|envs|logs|pods|events|config|settings>');
40
+ throw new Error('Usage: eve system <status|health|jobs|envs|logs|pods|events|config|settings|orchestrator>');
39
41
  }
40
42
  }
41
43
  // ============================================================================
@@ -61,10 +63,10 @@ async function handleStatus(context, json) {
61
63
  }
62
64
  }
63
65
  catch (error) {
64
- // If /system/status doesn't exist yet, fall back to basic health check
66
+ // If /system/status is missing, fall back to basic health check
65
67
  const err = error;
66
68
  if (err.message?.includes('HTTP 404')) {
67
- console.log('Note: Full system status endpoint not yet implemented.');
69
+ console.log('Note: /system/status not available on this API version.');
68
70
  console.log('Falling back to basic health check...');
69
71
  console.log('');
70
72
  return handleHealth(context, json);
@@ -318,6 +320,47 @@ async function handleConfig(context, json) {
318
320
  response.deployments.forEach((deployment) => console.log(` - ${deployment}`));
319
321
  }
320
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
+ }
321
364
  /**
322
365
  * eve system settings [get <key>] [set <key> <value>]
323
366
  * Admin only: Get or set system settings
@@ -526,3 +569,81 @@ function padRight(str, width) {
526
569
  return str;
527
570
  return str + ' '.repeat(width - str.length);
528
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
+ }
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ const agents_1 = require("./commands/agents");
25
25
  const init_1 = require("./commands/init");
26
26
  const release_1 = require("./commands/release");
27
27
  const manifest_1 = require("./commands/manifest");
28
+ const build_1 = require("./commands/build");
28
29
  async function main() {
29
30
  const { flags, positionals } = (0, args_1.parseArgs)(process.argv.slice(2));
30
31
  const command = positionals[0];
@@ -111,6 +112,9 @@ async function main() {
111
112
  case 'manifest':
112
113
  await (0, manifest_1.handleManifest)(subcommand, rest, flags, context);
113
114
  return;
115
+ case 'build':
116
+ await (0, build_1.handleBuild)(subcommand, rest, flags, context);
117
+ return;
114
118
  default:
115
119
  (0, help_1.showMainHelp)();
116
120
  }
package/dist/lib/help.js CHANGED
@@ -598,7 +598,7 @@ have to specify them on every command. Useful when working with multiple environ
598
598
  auth: {
599
599
  description: `Authenticate with Eve Horizon. Auth is optional for local development but required
600
600
  for cloud deployments. Credentials are stored per-profile.`,
601
- usage: 'eve auth <login|logout|status|whoami|bootstrap|sync|creds|token>',
601
+ usage: 'eve auth <login|logout|status|whoami|bootstrap|sync|creds|token|mint>',
602
602
  subcommands: {
603
603
  login: {
604
604
  description: 'Login via GitHub SSH challenge (default) or Supabase (legacy)',
@@ -643,6 +643,23 @@ for cloud deployments. Credentials are stored per-profile.`,
643
643
  'eve auth token # Share with reviewers for PR preview access',
644
644
  ],
645
645
  },
646
+ mint: {
647
+ description: 'Mint a user token (admin-only, no SSH login required)',
648
+ usage: 'eve auth mint --email <email> [--org <org_id> | --project <project_id>] [--role <role>] [--ttl <days>]',
649
+ options: [
650
+ '--email <email> Target user email (created if missing)',
651
+ '--org <org_id> Org scope for membership and permission checks',
652
+ '--project <id> Project scope for membership and permission checks',
653
+ '--role <role> Role to assign (member|admin), default member',
654
+ '--ttl <days> Token TTL in days (1-90, default: server configured)',
655
+ ],
656
+ examples: [
657
+ 'eve auth mint --email app-bot@example.com --org org_xxx',
658
+ 'eve auth mint --email app-bot@example.com --project proj_xxx',
659
+ 'eve auth mint --email app-bot@example.com --project proj_xxx --role admin',
660
+ 'eve auth mint --email bot@example.com --org org_xxx --ttl 90',
661
+ ],
662
+ },
646
663
  bootstrap: {
647
664
  description: 'Bootstrap the first admin user with flexible security modes',
648
665
  usage: 'eve auth bootstrap --email <email> [--token <token>] [options]',
@@ -736,12 +753,13 @@ for cloud deployments. Credentials are stored per-profile.`,
736
753
  },
737
754
  deploy: {
738
755
  description: 'Deploy to an environment',
739
- usage: 'eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--project <id>]',
756
+ usage: 'eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>] [--project <id>]',
740
757
  options: [
741
758
  '<env> Environment name (staging, production, test)',
742
759
  '--ref <sha> Git SHA or commit reference (required)',
743
760
  '--direct Bypass pipeline and do direct deploy',
744
761
  '--inputs <json> JSON inputs for the deployment (e.g., \'{"release_id":"rel_xxx"}\')',
762
+ '--image-tag <tag> Use a specific image tag for deploy (direct only)',
745
763
  '--project <id> Project ID or slug (uses profile default if omitted)',
746
764
  '--watch Poll deployment status until ready (default: true)',
747
765
  '--timeout <seconds> Watch timeout in seconds (default: 120)',
@@ -751,6 +769,7 @@ for cloud deployments. Credentials are stored per-profile.`,
751
769
  'eve env deploy staging --ref abc123 --direct',
752
770
  'eve env deploy staging --ref abc123 --inputs \'{"release_id":"rel_xxx","smoke_test":false}\'',
753
771
  'eve env deploy staging --ref abc123 --direct --inputs \'{"release_id":"rel_xxx"}\'',
772
+ 'eve env deploy staging --ref abc123 --direct --image-tag demo-abc123',
754
773
  ],
755
774
  },
756
775
  diagnose: {
@@ -1091,6 +1110,105 @@ for cloud deployments. Credentials are stored per-profile.`,
1091
1110
  'eve release resolve v1.2.3 --json',
1092
1111
  ],
1093
1112
  },
1113
+ build: {
1114
+ description: 'Manage builds. Builds are first-class primitives for container image creation (specs, runs, artifacts).',
1115
+ usage: 'eve build <subcommand> [options]',
1116
+ subcommands: {
1117
+ create: {
1118
+ description: 'Create a new build spec',
1119
+ usage: 'eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>]',
1120
+ options: [
1121
+ '--project <id> Project ID (uses profile default)',
1122
+ '--ref <sha> Git SHA or commit reference (required)',
1123
+ '--manifest-hash <h> Manifest hash (required)',
1124
+ '--services <list> Comma-separated service names to build',
1125
+ ],
1126
+ examples: [
1127
+ 'eve build create --ref abc123 --manifest-hash mfst_123',
1128
+ 'eve build create --project proj_xxx --ref abc123 --manifest-hash mfst_123 --services api,web',
1129
+ ],
1130
+ },
1131
+ list: {
1132
+ description: 'List build specs for a project',
1133
+ usage: 'eve build list [--project <id>] [--limit <n>] [--offset <n>]',
1134
+ options: [
1135
+ '--project <id> Project ID (uses profile default)',
1136
+ '--limit <n> Number of results',
1137
+ '--offset <n> Skip first n results',
1138
+ ],
1139
+ examples: [
1140
+ 'eve build list',
1141
+ 'eve build list --project proj_xxx --limit 20',
1142
+ ],
1143
+ },
1144
+ show: {
1145
+ description: 'Show build spec details',
1146
+ usage: 'eve build show <build_id>',
1147
+ examples: [
1148
+ 'eve build show build_xxx',
1149
+ ],
1150
+ },
1151
+ run: {
1152
+ description: 'Start a build run for an existing build spec',
1153
+ usage: 'eve build run <build_id>',
1154
+ examples: [
1155
+ 'eve build run build_xxx',
1156
+ ],
1157
+ },
1158
+ runs: {
1159
+ description: 'List runs for a build spec',
1160
+ usage: 'eve build runs <build_id> [--limit <n>]',
1161
+ options: [
1162
+ '--limit <n> Number of results',
1163
+ '--offset <n> Skip first n results',
1164
+ ],
1165
+ examples: [
1166
+ 'eve build runs build_xxx',
1167
+ ],
1168
+ },
1169
+ logs: {
1170
+ description: 'Show build logs',
1171
+ usage: 'eve build logs <build_id> [--run <run_id>]',
1172
+ options: [
1173
+ '--run <id> Specific run ID (default: latest)',
1174
+ ],
1175
+ examples: [
1176
+ 'eve build logs build_xxx',
1177
+ 'eve build logs build_xxx --run brun_yyy',
1178
+ ],
1179
+ },
1180
+ artifacts: {
1181
+ description: 'List build artifacts (images produced)',
1182
+ usage: 'eve build artifacts <build_id>',
1183
+ examples: [
1184
+ 'eve build artifacts build_xxx',
1185
+ ],
1186
+ },
1187
+ diagnose: {
1188
+ description: 'Show full build state (spec, runs, artifacts, logs)',
1189
+ usage: 'eve build diagnose <build_id>',
1190
+ examples: [
1191
+ 'eve build diagnose build_xxx',
1192
+ ],
1193
+ },
1194
+ cancel: {
1195
+ description: 'Cancel an active build run',
1196
+ usage: 'eve build cancel <build_id>',
1197
+ examples: [
1198
+ 'eve build cancel build_xxx',
1199
+ ],
1200
+ },
1201
+ },
1202
+ examples: [
1203
+ 'eve build create --ref abc123 --manifest-hash mfst_123 --services api,web',
1204
+ 'eve build list',
1205
+ 'eve build show build_xxx',
1206
+ 'eve build run build_xxx',
1207
+ 'eve build logs build_xxx',
1208
+ 'eve build artifacts build_xxx',
1209
+ 'eve build diagnose build_xxx',
1210
+ ],
1211
+ },
1094
1212
  init: {
1095
1213
  description: `Initialize a new Eve Horizon project from a template.
1096
1214
 
@@ -1196,6 +1314,31 @@ eve-new-project-setup skill to complete configuration.`,
1196
1314
  usage: 'eve system config',
1197
1315
  examples: ['eve system config'],
1198
1316
  },
1317
+ settings: {
1318
+ description: 'Get or set system settings (admin only)',
1319
+ usage: 'eve system settings [get <key>] [set <key> <value>]',
1320
+ options: [
1321
+ 'get <key> Get specific setting',
1322
+ 'set <key> <value> Update setting value',
1323
+ ],
1324
+ examples: [
1325
+ 'eve system settings',
1326
+ 'eve system settings get some-key',
1327
+ 'eve system settings set some-key some-value',
1328
+ ],
1329
+ },
1330
+ orchestrator: {
1331
+ description: 'Manage orchestrator concurrency settings',
1332
+ usage: 'eve system orchestrator <status|set-concurrency>',
1333
+ options: [
1334
+ 'status Show concurrency status',
1335
+ 'set-concurrency <n> Set concurrency limit',
1336
+ ],
1337
+ examples: [
1338
+ 'eve system orchestrator status',
1339
+ 'eve system orchestrator set-concurrency 8',
1340
+ ],
1341
+ },
1199
1342
  },
1200
1343
  examples: [
1201
1344
  'eve system health',
@@ -1203,6 +1346,7 @@ eve-new-project-setup skill to complete configuration.`,
1203
1346
  'eve system jobs',
1204
1347
  'eve system envs',
1205
1348
  'eve system logs api',
1349
+ 'eve system orchestrator status',
1206
1350
  ],
1207
1351
  },
1208
1352
  };
@@ -1220,6 +1364,7 @@ function showMainHelp() {
1220
1364
  console.log(' manifest Validate manifests (schema, secrets)');
1221
1365
  console.log(' job Manage jobs (create, list, show, update, claim, etc.)');
1222
1366
  console.log(' env Manage environments (list, show, deploy)');
1367
+ console.log(' build Manage builds (create, run, logs, artifacts)');
1223
1368
  console.log(' release Manage and inspect releases');
1224
1369
  console.log(' api Explore API sources and call endpoints');
1225
1370
  console.log(' db Inspect env DB schema, RLS, and SQL');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eve-horizon/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "Eve Horizon CLI",
5
5
  "license": "MIT",
6
6
  "repository": {