@eve-horizon/cli 0.2.1 → 0.2.6

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,366 @@
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
+ const node_child_process_1 = require("node:child_process");
8
+ // ---------------------------------------------------------------------------
9
+ // Entry point
10
+ // ---------------------------------------------------------------------------
11
+ async function handleBuild(subcommand, positionals, flags, context) {
12
+ const json = Boolean(flags.json);
13
+ switch (subcommand) {
14
+ case 'create':
15
+ return handleCreate(flags, context, json);
16
+ case 'list':
17
+ return handleList(positionals, flags, context, json);
18
+ case 'show':
19
+ return handleShow(positionals, flags, context, json);
20
+ case 'run':
21
+ return handleRun(positionals, flags, context, json);
22
+ case 'runs':
23
+ return handleRuns(positionals, flags, context, json);
24
+ case 'logs':
25
+ return handleLogs(positionals, flags, context, json);
26
+ case 'artifacts':
27
+ return handleArtifacts(positionals, flags, context, json);
28
+ case 'diagnose':
29
+ return handleDiagnose(positionals, flags, context, json);
30
+ case 'cancel':
31
+ return handleCancel(positionals, flags, context, json);
32
+ default:
33
+ throw new Error('Usage: eve build <create|list|show|run|runs|logs|artifacts|diagnose|cancel>\n' +
34
+ ' create --project <id> --ref <sha> [--services <list>] - create a build spec\n' +
35
+ ' list [--project <id>] - list build specs\n' +
36
+ ' show <build_id> - show build spec details\n' +
37
+ ' run <build_id> - start a build run\n' +
38
+ ' runs <build_id> - list runs for a build\n' +
39
+ ' logs <build_id> [--run <id>] - show build logs\n' +
40
+ ' artifacts <build_id> - list build artifacts\n' +
41
+ ' diagnose <build_id> - show full build state\n' +
42
+ ' cancel <build_id> - cancel active build run');
43
+ }
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Subcommands
47
+ // ---------------------------------------------------------------------------
48
+ async function handleCreate(flags, context, json) {
49
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
50
+ const ref = (0, args_1.getStringFlag)(flags, ['ref']);
51
+ const manifestHash = (0, args_1.getStringFlag)(flags, ['manifest-hash', 'manifest']);
52
+ const services = (0, args_1.getStringFlag)(flags, ['services']);
53
+ if (!projectId || !ref || !manifestHash) {
54
+ throw new Error('Usage: eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>]');
55
+ }
56
+ // Resolve git ref to actual 40-char SHA
57
+ let gitSha;
58
+ try {
59
+ gitSha = (0, node_child_process_1.execSync)(`git rev-parse ${ref}`, {
60
+ encoding: 'utf-8',
61
+ stdio: ['pipe', 'pipe', 'pipe'],
62
+ }).trim();
63
+ if (!json) {
64
+ console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
65
+ }
66
+ }
67
+ catch (error) {
68
+ throw new Error(`Failed to resolve git ref '${ref}': ${error instanceof Error ? error.message : String(error)}\n` +
69
+ 'Make sure you are in a git repository and the ref exists.');
70
+ }
71
+ const body = { git_sha: gitSha, manifest_hash: manifestHash };
72
+ if (services) {
73
+ body.services = services.split(',').map(s => s.trim());
74
+ }
75
+ const build = await (0, client_1.requestJson)(context, `/projects/${projectId}/builds`, { method: 'POST', body });
76
+ if (json) {
77
+ (0, output_1.outputJson)(build, json);
78
+ return;
79
+ }
80
+ console.log(`Build created: ${build.id}`);
81
+ console.log(` Project: ${build.project_id}`);
82
+ console.log(` SHA: ${build.git_sha}`);
83
+ if (build.manifest_hash) {
84
+ console.log(` Manifest: ${build.manifest_hash.substring(0, 12)}...`);
85
+ }
86
+ console.log(` Created: ${build.created_at}`);
87
+ }
88
+ async function handleList(positionals, flags, context, json) {
89
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
90
+ if (!projectId) {
91
+ throw new Error('Usage: eve build list [--project <id>]');
92
+ }
93
+ const query = buildQuery({
94
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
95
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
96
+ });
97
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/builds${query}`);
98
+ if (json) {
99
+ (0, output_1.outputJson)(response, json);
100
+ return;
101
+ }
102
+ if (response.data.length === 0) {
103
+ console.log('No builds found.');
104
+ return;
105
+ }
106
+ formatBuildList(response.data);
107
+ }
108
+ async function handleShow(positionals, flags, context, json) {
109
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
110
+ if (!buildId) {
111
+ throw new Error('Usage: eve build show <build_id>');
112
+ }
113
+ const build = await (0, client_1.requestJson)(context, `/builds/${buildId}`);
114
+ if (json) {
115
+ (0, output_1.outputJson)(build, json);
116
+ return;
117
+ }
118
+ formatBuildDetail(build);
119
+ }
120
+ async function handleRun(positionals, flags, context, json) {
121
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
122
+ if (!buildId) {
123
+ throw new Error('Usage: eve build run <build_id>');
124
+ }
125
+ const run = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs`, { method: 'POST' });
126
+ if (json) {
127
+ (0, output_1.outputJson)(run, json);
128
+ return;
129
+ }
130
+ console.log(`Build run started: ${run.id}`);
131
+ console.log(` Build: ${run.build_id}`);
132
+ console.log(` Status: ${run.status}`);
133
+ if (run.backend) {
134
+ console.log(` Backend: ${run.backend}`);
135
+ }
136
+ }
137
+ async function handleRuns(positionals, flags, context, json) {
138
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
139
+ if (!buildId) {
140
+ throw new Error('Usage: eve build runs <build_id>');
141
+ }
142
+ const query = buildQuery({
143
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
144
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
145
+ });
146
+ const response = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs${query}`);
147
+ if (json) {
148
+ (0, output_1.outputJson)(response, json);
149
+ return;
150
+ }
151
+ if (response.data.length === 0) {
152
+ console.log('No runs found for this build.');
153
+ return;
154
+ }
155
+ formatRunList(response.data);
156
+ }
157
+ async function handleLogs(positionals, flags, context, json) {
158
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
159
+ const runId = (0, args_1.getStringFlag)(flags, ['run']);
160
+ if (!buildId) {
161
+ throw new Error('Usage: eve build logs <build_id> [--run <run_id>]');
162
+ }
163
+ const query = runId ? `?run_id=${encodeURIComponent(runId)}` : '';
164
+ const logs = await (0, client_1.requestJson)(context, `/builds/${buildId}/logs${query}`);
165
+ if (json) {
166
+ (0, output_1.outputJson)(logs, json);
167
+ return;
168
+ }
169
+ if (logs.logs.length === 0) {
170
+ console.log('No logs available.');
171
+ return;
172
+ }
173
+ for (const entry of logs.logs) {
174
+ const line = entry.line;
175
+ if (typeof line.message === 'string') {
176
+ console.log(line.message);
177
+ continue;
178
+ }
179
+ if (Array.isArray(line.lines)) {
180
+ for (const item of line.lines) {
181
+ if (typeof item === 'string') {
182
+ console.log(item);
183
+ }
184
+ }
185
+ continue;
186
+ }
187
+ console.log(JSON.stringify(line));
188
+ }
189
+ }
190
+ async function handleArtifacts(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 artifacts <build_id>');
194
+ }
195
+ const response = await (0, client_1.requestJson)(context, `/builds/${buildId}/artifacts`);
196
+ if (json) {
197
+ (0, output_1.outputJson)(response, json);
198
+ return;
199
+ }
200
+ if (response.data.length === 0) {
201
+ console.log('No artifacts found for this build.');
202
+ return;
203
+ }
204
+ formatArtifactList(response.data);
205
+ }
206
+ async function handleCancel(positionals, flags, context, json) {
207
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
208
+ if (!buildId) {
209
+ throw new Error('Usage: eve build cancel <build_id>');
210
+ }
211
+ const result = await (0, client_1.requestJson)(context, `/builds/${buildId}/cancel`, { method: 'POST' });
212
+ if (json) {
213
+ (0, output_1.outputJson)(result, json);
214
+ return;
215
+ }
216
+ console.log(`Build cancelled: ${buildId}`);
217
+ }
218
+ async function handleDiagnose(positionals, flags, context, json) {
219
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
220
+ if (!buildId) {
221
+ throw new Error('Usage: eve build diagnose <build_id>');
222
+ }
223
+ const spec = await (0, client_1.requestJson)(context, `/builds/${buildId}`);
224
+ const runs = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs?limit=20&offset=0`);
225
+ const artifacts = await (0, client_1.requestJson)(context, `/builds/${buildId}/artifacts`);
226
+ const latestRun = runs.data[0];
227
+ const logs = latestRun
228
+ ? await (0, client_1.requestJson)(context, `/builds/${buildId}/logs?run_id=${encodeURIComponent(latestRun.id)}`)
229
+ : { logs: [] };
230
+ const payload = { spec, runs: runs.data, artifacts: artifacts.data, logs: logs.logs };
231
+ if (json) {
232
+ (0, output_1.outputJson)(payload, json);
233
+ return;
234
+ }
235
+ console.log(`Build diagnose: ${spec.id}`);
236
+ formatBuildDetail(spec);
237
+ console.log('');
238
+ if (runs.data.length > 0) {
239
+ formatRunList(runs.data);
240
+ }
241
+ else {
242
+ console.log('No runs found.');
243
+ }
244
+ console.log('');
245
+ if (artifacts.data.length > 0) {
246
+ formatArtifactList(artifacts.data);
247
+ }
248
+ else {
249
+ console.log('No artifacts found.');
250
+ }
251
+ console.log('');
252
+ if (logs.logs.length > 0) {
253
+ console.log('Recent logs:');
254
+ for (const entry of logs.logs.slice(-50)) {
255
+ const line = entry.line;
256
+ if (typeof line.message === 'string') {
257
+ console.log(line.message);
258
+ continue;
259
+ }
260
+ if (Array.isArray(line.lines)) {
261
+ for (const item of line.lines) {
262
+ if (typeof item === 'string') {
263
+ console.log(item);
264
+ }
265
+ }
266
+ continue;
267
+ }
268
+ console.log(JSON.stringify(line));
269
+ }
270
+ }
271
+ else {
272
+ console.log('No logs found.');
273
+ }
274
+ }
275
+ // ---------------------------------------------------------------------------
276
+ // Formatters
277
+ // ---------------------------------------------------------------------------
278
+ function formatBuildList(builds) {
279
+ console.log('Build ID'.padEnd(30) + 'SHA'.padEnd(12) + 'Created By'.padEnd(20) + 'Created');
280
+ console.log('-'.repeat(90));
281
+ for (const build of builds) {
282
+ const id = build.id.padEnd(30);
283
+ const sha = build.git_sha.substring(0, 8).padEnd(12);
284
+ const creator = (build.created_by ?? '-').padEnd(20);
285
+ const created = new Date(build.created_at).toLocaleString();
286
+ console.log(`${id}${sha}${creator}${created}`);
287
+ }
288
+ console.log('');
289
+ console.log(`Total: ${builds.length} builds`);
290
+ }
291
+ function formatBuildDetail(build) {
292
+ console.log(`Build: ${build.id}`);
293
+ console.log(` Project: ${build.project_id}`);
294
+ console.log(` SHA: ${build.git_sha}`);
295
+ console.log(` Manifest Hash: ${build.manifest_hash}`);
296
+ console.log(` Created By: ${build.created_by ?? '-'}`);
297
+ console.log(` Created: ${build.created_at}`);
298
+ if (build.services_json) {
299
+ console.log(` Services: ${JSON.stringify(build.services_json)}`);
300
+ }
301
+ if (build.inputs_json) {
302
+ console.log(` Inputs: ${JSON.stringify(build.inputs_json)}`);
303
+ }
304
+ if (build.registry_json) {
305
+ console.log(` Registry: ${JSON.stringify(build.registry_json)}`);
306
+ }
307
+ if (build.cache_json) {
308
+ console.log(` Cache: ${JSON.stringify(build.cache_json)}`);
309
+ }
310
+ }
311
+ function formatRunList(runs) {
312
+ console.log('Run ID'.padEnd(30) + 'Status'.padEnd(15) + 'Backend'.padEnd(15) + 'Started');
313
+ console.log('-'.repeat(80));
314
+ for (const run of runs) {
315
+ const id = run.id.padEnd(30);
316
+ const status = run.status.padEnd(15);
317
+ const backend = (run.backend ?? '-').padEnd(15);
318
+ const started = run.started_at ? new Date(run.started_at).toLocaleString() : '-';
319
+ console.log(`${id}${status}${backend}${started}`);
320
+ if (run.error_message) {
321
+ console.log(` Error: ${run.error_message}`);
322
+ }
323
+ }
324
+ console.log('');
325
+ console.log(`Total: ${runs.length} runs`);
326
+ }
327
+ function formatArtifactList(artifacts) {
328
+ console.log('Service'.padEnd(25) + 'Image'.padEnd(50) + 'Size');
329
+ console.log('-'.repeat(90));
330
+ for (const artifact of artifacts) {
331
+ const service = artifact.service_name.padEnd(25);
332
+ const image = artifact.image_ref.padEnd(50);
333
+ const size = artifact.size_bytes ? formatBytes(artifact.size_bytes) : '-';
334
+ console.log(`${service}${image}${size}`);
335
+ if (artifact.digest) {
336
+ console.log(` Digest: ${artifact.digest}`);
337
+ }
338
+ if (artifact.platforms_json && artifact.platforms_json.length > 0) {
339
+ console.log(` Platforms: ${artifact.platforms_json.join(', ')}`);
340
+ }
341
+ }
342
+ console.log('');
343
+ console.log(`Total: ${artifacts.length} artifacts`);
344
+ }
345
+ function formatBytes(bytes) {
346
+ if (bytes < 1024)
347
+ return `${bytes} B`;
348
+ if (bytes < 1024 * 1024)
349
+ return `${(bytes / 1024).toFixed(1)} KB`;
350
+ if (bytes < 1024 * 1024 * 1024)
351
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
352
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
353
+ }
354
+ // ---------------------------------------------------------------------------
355
+ // Utilities
356
+ // ---------------------------------------------------------------------------
357
+ function buildQuery(params) {
358
+ const search = new URLSearchParams();
359
+ Object.entries(params).forEach(([key, value]) => {
360
+ if (value === undefined || value === '')
361
+ return;
362
+ search.set(key, value);
363
+ });
364
+ const query = search.toString();
365
+ return query ? `?${query}` : '';
366
+ }
@@ -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,13 +194,28 @@ 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
- const gitSha = (0, args_1.getStringFlag)(flags, ['ref']);
201
- if (!gitSha) {
200
+ const ref = (0, args_1.getStringFlag)(flags, ['ref']);
201
+ if (!ref) {
202
202
  throw new Error('Usage: eve env deploy <env> --ref <sha> [options]\n\nThe --ref flag is required (git SHA or commit reference)');
203
203
  }
204
+ // Resolve git ref to actual 40-char SHA
205
+ let gitSha;
206
+ try {
207
+ gitSha = (0, child_process_1.execSync)(`git rev-parse ${ref}`, {
208
+ encoding: 'utf-8',
209
+ stdio: ['pipe', 'pipe', 'pipe'],
210
+ }).trim();
211
+ if (!json) {
212
+ console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
213
+ }
214
+ }
215
+ catch (error) {
216
+ throw new Error(`Failed to resolve git ref '${ref}': ${error instanceof Error ? error.message : String(error)}\n` +
217
+ 'Make sure you are in a git repository and the ref exists.');
218
+ }
204
219
  if (!json) {
205
220
  console.log(`Deploying commit ${gitSha.substring(0, 8)} to ${envName}...`);
206
221
  }
@@ -226,6 +241,8 @@ async function handleDeploy(positionals, flags, context, json) {
226
241
  'Example: --inputs \'{"release_id":"rel_xxx","smoke_test":false}\'');
227
242
  }
228
243
  }
244
+ // Parse --image-tag flag (optional)
245
+ const imageTag = (0, args_1.getStringFlag)(flags, ['image-tag', 'image_tag', 'imageTag']);
229
246
  // POST deployment
230
247
  const body = {
231
248
  git_sha: gitSha,
@@ -237,6 +254,9 @@ async function handleDeploy(positionals, flags, context, json) {
237
254
  if (inputs) {
238
255
  body.inputs = inputs;
239
256
  }
257
+ if (imageTag) {
258
+ body.image_tag = imageTag;
259
+ }
240
260
  const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/deploy`, {
241
261
  method: 'POST',
242
262
  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,
@@ -4,6 +4,7 @@ exports.handlePipeline = handlePipeline;
4
4
  const args_1 = require("../lib/args");
5
5
  const client_1 = require("../lib/client");
6
6
  const output_1 = require("../lib/output");
7
+ const node_child_process_1 = require("node:child_process");
7
8
  async function handlePipeline(subcommand, positionals, flags, context) {
8
9
  const json = Boolean(flags.json);
9
10
  switch (subcommand) {
@@ -165,6 +166,21 @@ async function handleRun(positionals, flags, context, json) {
165
166
  if (!pipelineName || !projectId || !ref) {
166
167
  throw new Error('Usage: eve pipeline run <name> --ref <sha> [--env <env>] [--project <id>] [--wait] [--inputs <json>] [--only <step>]');
167
168
  }
169
+ // Resolve git ref to actual 40-char SHA
170
+ let gitSha;
171
+ try {
172
+ gitSha = (0, node_child_process_1.execSync)(`git rev-parse ${ref}`, {
173
+ encoding: 'utf-8',
174
+ stdio: ['pipe', 'pipe', 'pipe'],
175
+ }).trim();
176
+ if (!json) {
177
+ console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
178
+ }
179
+ }
180
+ catch (error) {
181
+ throw new Error(`Failed to resolve git ref '${ref}': ${error instanceof Error ? error.message : String(error)}\n` +
182
+ 'Make sure you are in a git repository and the ref exists.');
183
+ }
168
184
  let inputs;
169
185
  if (inputsRaw) {
170
186
  try {
@@ -175,11 +191,11 @@ async function handleRun(positionals, flags, context, json) {
175
191
  }
176
192
  }
177
193
  // Emit event before creating the pipeline run
178
- await emitPipelineRunEvent(context, projectId, pipelineName, { env, ref, inputs });
194
+ await emitPipelineRunEvent(context, projectId, pipelineName, { env, ref: gitSha, inputs });
179
195
  if (only) {
180
196
  const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/pipelines/${pipelineName}/runs`, {
181
197
  method: 'POST',
182
- body: { git_sha: ref, env_name: env, inputs, only },
198
+ body: { git_sha: gitSha, env_name: env, inputs, only },
183
199
  });
184
200
  if (json) {
185
201
  (0, output_1.outputJson)(response, json);
@@ -203,7 +219,7 @@ async function handleRun(positionals, flags, context, json) {
203
219
  const url = `/projects/${projectId}/pipelines/${pipelineName}/run${queryString ? `?${queryString}` : ''}`;
204
220
  const response = await (0, client_1.requestJson)(context, url, {
205
221
  method: 'POST',
206
- body: { ref, env, inputs },
222
+ body: { ref: gitSha, env, inputs },
207
223
  });
208
224
  if (json) {
209
225
  (0, output_1.outputJson)(response, json);
@@ -64,10 +64,10 @@ function handleProfile(subcommand, positionals, flags, config) {
64
64
  return;
65
65
  }
66
66
  case 'use': {
67
- const isLocal = Boolean(flags.local);
67
+ const isGlobal = Boolean(flags.global);
68
68
  const isClear = Boolean(flags.clear);
69
- // Handle --local --clear: remove local profile
70
- if (isLocal && isClear) {
69
+ // Handle --clear: remove local profile
70
+ if (isClear) {
71
71
  const removed = (0, context_1.removeRepoProfile)();
72
72
  if (removed) {
73
73
  (0, output_1.outputJson)({ cleared: true, path: (0, context_1.getRepoProfilePath)() }, json, `✓ Removed local profile`);
@@ -79,15 +79,21 @@ function handleProfile(subcommand, positionals, flags, config) {
79
79
  }
80
80
  const name = positionals[0];
81
81
  if (!name) {
82
- throw new Error('Usage: eve profile use <name> [--local]');
82
+ throw new Error('Usage: eve profile use <name> [--global]');
83
83
  }
84
- // Validate profile exists in global config
84
+ // Validate profile exists in global config (auto-create if needed)
85
85
  if (!config.profiles[name]) {
86
86
  config.profiles[name] = {};
87
87
  (0, config_1.saveConfig)(config);
88
88
  }
89
- if (isLocal) {
90
- // Write to .eve/profile.yaml
89
+ if (isGlobal) {
90
+ // Explicit: set global active profile in ~/.eve/config.json
91
+ config.active_profile = name;
92
+ (0, config_1.saveConfig)(config);
93
+ (0, output_1.outputJson)({ active_profile: name }, json, `✓ Active profile: ${name} (global)`);
94
+ }
95
+ else {
96
+ // Default: write to .eve/profile.yaml (local project mode)
91
97
  const repoProfile = { profile: name };
92
98
  // Apply any override flags
93
99
  if (typeof flags['api-url'] === 'string')
@@ -99,12 +105,6 @@ function handleProfile(subcommand, positionals, flags, config) {
99
105
  (0, context_1.saveRepoProfile)(repoProfile);
100
106
  (0, output_1.outputJson)({ profile: name, local: true, path: (0, context_1.getRepoProfilePath)(), ...repoProfile }, json, `✓ Local profile set: ${name} (${(0, context_1.getRepoProfilePath)()})`);
101
107
  }
102
- else {
103
- // Set global active profile
104
- config.active_profile = name;
105
- (0, config_1.saveConfig)(config);
106
- (0, output_1.outputJson)({ active_profile: name }, json, `✓ Active profile: ${name}`);
107
- }
108
108
  return;
109
109
  }
110
110
  case 'create': {
@@ -121,13 +121,42 @@ function handleProfile(subcommand, positionals, flags, config) {
121
121
  return;
122
122
  }
123
123
  case 'set': {
124
- const name = positionals[0] ?? config.active_profile;
125
- if (!config.profiles[name]) {
126
- config.profiles[name] = {};
124
+ const isGlobal = Boolean(flags.global);
125
+ if (isGlobal) {
126
+ // Explicit: write to global ~/.eve/config.json
127
+ const name = positionals[0] ?? config.active_profile;
128
+ if (!config.profiles[name]) {
129
+ config.profiles[name] = {};
130
+ }
131
+ config.profiles[name] = applyProfileFlags(config.profiles[name], flags);
132
+ config.active_profile = name;
133
+ (0, config_1.saveConfig)(config);
134
+ (0, output_1.outputJson)({ active_profile: name, ...config.profiles[name] }, json, `✓ Profile set: ${name} (global)`);
135
+ }
136
+ else {
137
+ // Default: write to local .eve/profile.yaml
138
+ const globalOnlyFields = ['supabase-url', 'supabase-anon-key', 'default-email', 'default-ssh-key'];
139
+ const usedGlobalFields = globalOnlyFields.filter(f => typeof flags[f] === 'string');
140
+ if (usedGlobalFields.length > 0) {
141
+ throw new Error(`${usedGlobalFields.map(f => `--${f}`).join(', ')} are user-level settings.\n` +
142
+ `Use --global to write them to ~/.eve/config.json`);
143
+ }
144
+ const existing = (0, context_1.loadRepoProfile)() ?? {};
145
+ const repoProfile = { ...existing };
146
+ const name = positionals[0];
147
+ if (name)
148
+ repoProfile.profile = name;
149
+ if (typeof flags['api-url'] === 'string')
150
+ repoProfile.api_url = flags['api-url'];
151
+ if (typeof flags.org === 'string')
152
+ repoProfile.org_id = flags.org;
153
+ if (typeof flags.project === 'string')
154
+ repoProfile.project_id = flags.project;
155
+ if (typeof flags.harness === 'string')
156
+ repoProfile.default_harness = flags.harness;
157
+ (0, context_1.saveRepoProfile)(repoProfile);
158
+ (0, output_1.outputJson)({ ...repoProfile, path: (0, context_1.getRepoProfilePath)() }, json, `✓ Profile set: ${repoProfile.profile ?? 'local'} (${(0, context_1.getRepoProfilePath)()})`);
127
159
  }
128
- config.profiles[name] = applyProfileFlags(config.profiles[name], flags);
129
- (0, config_1.saveConfig)(config);
130
- (0, output_1.outputJson)({ name, ...config.profiles[name] }, json, `✓ Profile updated: ${name}`);
131
160
  return;
132
161
  }
133
162
  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
@@ -530,7 +530,11 @@ Jobs default to 'ready' phase, making them immediately schedulable.`,
530
530
  },
531
531
  profile: {
532
532
  description: `Manage CLI profiles. Profiles store defaults (API URL, org, project) so you don't
533
- have to specify them on every command. Useful when working with multiple environments.`,
533
+ have to specify them on every command.
534
+
535
+ By default, set and use write to .eve/profile.yaml (project-local). Use --global to
536
+ write to ~/.eve/config.json instead. This lets you work on multiple projects without
537
+ clobbering your global config.`,
534
538
  usage: 'eve profile <subcommand> [options]',
535
539
  subcommands: {
536
540
  list: {
@@ -538,17 +542,28 @@ have to specify them on every command. Useful when working with multiple environ
538
542
  usage: 'eve profile list',
539
543
  },
540
544
  show: {
541
- description: 'Show profile details',
545
+ description: 'Show profile details (local overrides applied)',
542
546
  usage: 'eve profile show [name]',
543
547
  examples: ['eve profile show', 'eve profile show prod'],
544
548
  },
545
549
  use: {
546
- description: 'Switch active profile',
547
- usage: 'eve profile use <name>',
548
- examples: ['eve profile use prod'],
550
+ description: 'Switch active profile (defaults to local .eve/profile.yaml)',
551
+ usage: 'eve profile use <name> [--global] [--org <id>] [--project <id>]',
552
+ options: [
553
+ '--global Write to ~/.eve/config.json instead of local project',
554
+ '--org <id> Set org override',
555
+ '--project <id> Set project override',
556
+ '--api-url <url> Set API URL override',
557
+ '--clear Remove local .eve/profile.yaml',
558
+ ],
559
+ examples: [
560
+ 'eve profile use staging --org org_xxx --project proj_yyy',
561
+ 'eve profile use staging --global',
562
+ 'eve profile use --clear',
563
+ ],
549
564
  },
550
565
  create: {
551
- description: 'Create a new profile',
566
+ description: 'Create a new named profile (always global)',
552
567
  usage: 'eve profile create <name> [--api-url <url>] [--org <id>] [--project <id>]',
553
568
  options: [
554
569
  '--api-url <url> API base URL',
@@ -566,13 +581,16 @@ have to specify them on every command. Useful when working with multiple environ
566
581
  ],
567
582
  },
568
583
  set: {
569
- description: 'Update profile settings',
570
- usage: 'eve profile set [name] [--api-url <url>] [--org <id>] [--project <id>]',
584
+ description: 'Update profile settings (defaults to local .eve/profile.yaml)',
585
+ usage: 'eve profile set [name] [--org <id>] [--project <id>] [--global]',
571
586
  options: [
572
- '--api-url <url> API base URL',
573
587
  '--org <id> Default organization ID',
574
588
  '--project <id> Default project ID',
589
+ '--api-url <url> API base URL',
575
590
  '--harness <name> Default harness',
591
+ '--global Write to ~/.eve/config.json instead of local project',
592
+ '',
593
+ 'Global-only (require --global):',
576
594
  '--supabase-url <url> Supabase URL',
577
595
  '--supabase-anon-key <key> Supabase anon key',
578
596
  '--default-email <email> Default email for auth login',
@@ -580,25 +598,25 @@ have to specify them on every command. Useful when working with multiple environ
580
598
  ],
581
599
  examples: [
582
600
  'eve profile set --org org_xxx --project proj_yyy',
583
- 'eve profile set prod --api-url https://new-api.example.com',
584
- 'eve profile set --default-email user@example.com --default-ssh-key ~/.ssh/id_rsa',
601
+ 'eve profile set staging --org org_xxx --project proj_yyy',
602
+ 'eve profile set --global --default-email user@example.com',
585
603
  ],
586
604
  },
587
605
  remove: {
588
- description: 'Remove a profile',
606
+ description: 'Remove a named profile (global)',
589
607
  usage: 'eve profile remove <name>',
590
608
  },
591
609
  },
592
610
  examples: [
593
- 'eve profile create local --api-url http://localhost:4701',
594
- 'eve profile use local',
595
- 'eve profile set --org org_xxx',
611
+ 'eve profile set --org org_xxx --project proj_yyy # writes .eve/profile.yaml',
612
+ 'eve profile use staging --org org_xxx # writes .eve/profile.yaml',
613
+ 'eve profile set --global --default-email me@dev.com # writes ~/.eve/config.json',
596
614
  ],
597
615
  },
598
616
  auth: {
599
617
  description: `Authenticate with Eve Horizon. Auth is optional for local development but required
600
618
  for cloud deployments. Credentials are stored per-profile.`,
601
- usage: 'eve auth <login|logout|status|whoami|bootstrap|sync|creds|token>',
619
+ usage: 'eve auth <login|logout|status|whoami|bootstrap|sync|creds|token|mint>',
602
620
  subcommands: {
603
621
  login: {
604
622
  description: 'Login via GitHub SSH challenge (default) or Supabase (legacy)',
@@ -643,6 +661,23 @@ for cloud deployments. Credentials are stored per-profile.`,
643
661
  'eve auth token # Share with reviewers for PR preview access',
644
662
  ],
645
663
  },
664
+ mint: {
665
+ description: 'Mint a user token (admin-only, no SSH login required)',
666
+ usage: 'eve auth mint --email <email> [--org <org_id> | --project <project_id>] [--role <role>] [--ttl <days>]',
667
+ options: [
668
+ '--email <email> Target user email (created if missing)',
669
+ '--org <org_id> Org scope for membership and permission checks',
670
+ '--project <id> Project scope for membership and permission checks',
671
+ '--role <role> Role to assign (member|admin), default member',
672
+ '--ttl <days> Token TTL in days (1-90, default: server configured)',
673
+ ],
674
+ examples: [
675
+ 'eve auth mint --email app-bot@example.com --org org_xxx',
676
+ 'eve auth mint --email app-bot@example.com --project proj_xxx',
677
+ 'eve auth mint --email app-bot@example.com --project proj_xxx --role admin',
678
+ 'eve auth mint --email bot@example.com --org org_xxx --ttl 90',
679
+ ],
680
+ },
646
681
  bootstrap: {
647
682
  description: 'Bootstrap the first admin user with flexible security modes',
648
683
  usage: 'eve auth bootstrap --email <email> [--token <token>] [options]',
@@ -736,12 +771,13 @@ for cloud deployments. Credentials are stored per-profile.`,
736
771
  },
737
772
  deploy: {
738
773
  description: 'Deploy to an environment',
739
- usage: 'eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--project <id>]',
774
+ usage: 'eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>] [--project <id>]',
740
775
  options: [
741
776
  '<env> Environment name (staging, production, test)',
742
777
  '--ref <sha> Git SHA or commit reference (required)',
743
778
  '--direct Bypass pipeline and do direct deploy',
744
779
  '--inputs <json> JSON inputs for the deployment (e.g., \'{"release_id":"rel_xxx"}\')',
780
+ '--image-tag <tag> Use a specific image tag for deploy (direct only)',
745
781
  '--project <id> Project ID or slug (uses profile default if omitted)',
746
782
  '--watch Poll deployment status until ready (default: true)',
747
783
  '--timeout <seconds> Watch timeout in seconds (default: 120)',
@@ -751,6 +787,7 @@ for cloud deployments. Credentials are stored per-profile.`,
751
787
  'eve env deploy staging --ref abc123 --direct',
752
788
  'eve env deploy staging --ref abc123 --inputs \'{"release_id":"rel_xxx","smoke_test":false}\'',
753
789
  'eve env deploy staging --ref abc123 --direct --inputs \'{"release_id":"rel_xxx"}\'',
790
+ 'eve env deploy staging --ref abc123 --direct --image-tag demo-abc123',
754
791
  ],
755
792
  },
756
793
  diagnose: {
@@ -1091,6 +1128,105 @@ for cloud deployments. Credentials are stored per-profile.`,
1091
1128
  'eve release resolve v1.2.3 --json',
1092
1129
  ],
1093
1130
  },
1131
+ build: {
1132
+ description: 'Manage builds. Builds are first-class primitives for container image creation (specs, runs, artifacts).',
1133
+ usage: 'eve build <subcommand> [options]',
1134
+ subcommands: {
1135
+ create: {
1136
+ description: 'Create a new build spec',
1137
+ usage: 'eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>]',
1138
+ options: [
1139
+ '--project <id> Project ID (uses profile default)',
1140
+ '--ref <sha> Git SHA or commit reference (required)',
1141
+ '--manifest-hash <h> Manifest hash (required)',
1142
+ '--services <list> Comma-separated service names to build',
1143
+ ],
1144
+ examples: [
1145
+ 'eve build create --ref abc123 --manifest-hash mfst_123',
1146
+ 'eve build create --project proj_xxx --ref abc123 --manifest-hash mfst_123 --services api,web',
1147
+ ],
1148
+ },
1149
+ list: {
1150
+ description: 'List build specs for a project',
1151
+ usage: 'eve build list [--project <id>] [--limit <n>] [--offset <n>]',
1152
+ options: [
1153
+ '--project <id> Project ID (uses profile default)',
1154
+ '--limit <n> Number of results',
1155
+ '--offset <n> Skip first n results',
1156
+ ],
1157
+ examples: [
1158
+ 'eve build list',
1159
+ 'eve build list --project proj_xxx --limit 20',
1160
+ ],
1161
+ },
1162
+ show: {
1163
+ description: 'Show build spec details',
1164
+ usage: 'eve build show <build_id>',
1165
+ examples: [
1166
+ 'eve build show build_xxx',
1167
+ ],
1168
+ },
1169
+ run: {
1170
+ description: 'Start a build run for an existing build spec',
1171
+ usage: 'eve build run <build_id>',
1172
+ examples: [
1173
+ 'eve build run build_xxx',
1174
+ ],
1175
+ },
1176
+ runs: {
1177
+ description: 'List runs for a build spec',
1178
+ usage: 'eve build runs <build_id> [--limit <n>]',
1179
+ options: [
1180
+ '--limit <n> Number of results',
1181
+ '--offset <n> Skip first n results',
1182
+ ],
1183
+ examples: [
1184
+ 'eve build runs build_xxx',
1185
+ ],
1186
+ },
1187
+ logs: {
1188
+ description: 'Show build logs',
1189
+ usage: 'eve build logs <build_id> [--run <run_id>]',
1190
+ options: [
1191
+ '--run <id> Specific run ID (default: latest)',
1192
+ ],
1193
+ examples: [
1194
+ 'eve build logs build_xxx',
1195
+ 'eve build logs build_xxx --run brun_yyy',
1196
+ ],
1197
+ },
1198
+ artifacts: {
1199
+ description: 'List build artifacts (images produced)',
1200
+ usage: 'eve build artifacts <build_id>',
1201
+ examples: [
1202
+ 'eve build artifacts build_xxx',
1203
+ ],
1204
+ },
1205
+ diagnose: {
1206
+ description: 'Show full build state (spec, runs, artifacts, logs)',
1207
+ usage: 'eve build diagnose <build_id>',
1208
+ examples: [
1209
+ 'eve build diagnose build_xxx',
1210
+ ],
1211
+ },
1212
+ cancel: {
1213
+ description: 'Cancel an active build run',
1214
+ usage: 'eve build cancel <build_id>',
1215
+ examples: [
1216
+ 'eve build cancel build_xxx',
1217
+ ],
1218
+ },
1219
+ },
1220
+ examples: [
1221
+ 'eve build create --ref abc123 --manifest-hash mfst_123 --services api,web',
1222
+ 'eve build list',
1223
+ 'eve build show build_xxx',
1224
+ 'eve build run build_xxx',
1225
+ 'eve build logs build_xxx',
1226
+ 'eve build artifacts build_xxx',
1227
+ 'eve build diagnose build_xxx',
1228
+ ],
1229
+ },
1094
1230
  init: {
1095
1231
  description: `Initialize a new Eve Horizon project from a template.
1096
1232
 
@@ -1196,6 +1332,31 @@ eve-new-project-setup skill to complete configuration.`,
1196
1332
  usage: 'eve system config',
1197
1333
  examples: ['eve system config'],
1198
1334
  },
1335
+ settings: {
1336
+ description: 'Get or set system settings (admin only)',
1337
+ usage: 'eve system settings [get <key>] [set <key> <value>]',
1338
+ options: [
1339
+ 'get <key> Get specific setting',
1340
+ 'set <key> <value> Update setting value',
1341
+ ],
1342
+ examples: [
1343
+ 'eve system settings',
1344
+ 'eve system settings get some-key',
1345
+ 'eve system settings set some-key some-value',
1346
+ ],
1347
+ },
1348
+ orchestrator: {
1349
+ description: 'Manage orchestrator concurrency settings',
1350
+ usage: 'eve system orchestrator <status|set-concurrency>',
1351
+ options: [
1352
+ 'status Show concurrency status',
1353
+ 'set-concurrency <n> Set concurrency limit',
1354
+ ],
1355
+ examples: [
1356
+ 'eve system orchestrator status',
1357
+ 'eve system orchestrator set-concurrency 8',
1358
+ ],
1359
+ },
1199
1360
  },
1200
1361
  examples: [
1201
1362
  'eve system health',
@@ -1203,6 +1364,7 @@ eve-new-project-setup skill to complete configuration.`,
1203
1364
  'eve system jobs',
1204
1365
  'eve system envs',
1205
1366
  'eve system logs api',
1367
+ 'eve system orchestrator status',
1206
1368
  ],
1207
1369
  },
1208
1370
  };
@@ -1220,6 +1382,7 @@ function showMainHelp() {
1220
1382
  console.log(' manifest Validate manifests (schema, secrets)');
1221
1383
  console.log(' job Manage jobs (create, list, show, update, claim, etc.)');
1222
1384
  console.log(' env Manage environments (list, show, deploy)');
1385
+ console.log(' build Manage builds (create, run, logs, artifacts)');
1223
1386
  console.log(' release Manage and inspect releases');
1224
1387
  console.log(' api Explore API sources and call endpoints');
1225
1388
  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.6",
4
4
  "description": "Eve Horizon CLI",
5
5
  "license": "MIT",
6
6
  "repository": {