@eve-horizon/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOrg = handleOrg;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ const DEFAULT_ORG_NAME = process.env.EVE_ORG_NAME || 'default-test-org';
8
+ const DEFAULT_ORG_ID = process.env.EVE_ORG_ID || 'org_defaulttestorg';
9
+ async function handleOrg(subcommand, positionals, flags, context) {
10
+ const json = Boolean(flags.json);
11
+ switch (subcommand) {
12
+ case 'ensure': {
13
+ let orgId = typeof flags.id === 'string' ? flags.id : '';
14
+ let orgName = typeof flags.name === 'string' ? flags.name : '';
15
+ const nameOrId = positionals[0];
16
+ if (!orgId && nameOrId) {
17
+ if (/^org_[a-zA-Z0-9]+$/.test(nameOrId)) {
18
+ orgId = nameOrId;
19
+ orgName = orgName || nameOrId;
20
+ }
21
+ else {
22
+ orgName = orgName || nameOrId;
23
+ orgId = normalizeOrgId(nameOrId);
24
+ }
25
+ }
26
+ orgId = orgId || context.orgId || DEFAULT_ORG_ID;
27
+ orgName = orgName || DEFAULT_ORG_NAME;
28
+ const body = { id: orgId, name: orgName };
29
+ const org = await (0, client_1.requestJson)(context, '/orgs/ensure', {
30
+ method: 'POST',
31
+ body,
32
+ });
33
+ (0, output_1.outputJson)(org, json, `✓ Organization ready: ${org.id} (${org.name})`);
34
+ return;
35
+ }
36
+ case 'list': {
37
+ const includeDeletedFlag = flags.include_deleted ?? flags['include-deleted'];
38
+ const query = buildQuery({
39
+ limit: typeof flags.limit === 'string' ? flags.limit : undefined,
40
+ offset: typeof flags.offset === 'string' ? flags.offset : undefined,
41
+ include_deleted: (0, args_1.toBoolean)(includeDeletedFlag) ? 'true' : undefined,
42
+ name: typeof flags.name === 'string' ? flags.name : undefined,
43
+ });
44
+ const response = await (0, client_1.requestJson)(context, `/orgs${query}`);
45
+ (0, output_1.outputJson)(response, json);
46
+ return;
47
+ }
48
+ case 'get': {
49
+ const orgId = positionals[0];
50
+ if (!orgId)
51
+ throw new Error('Usage: eve org get <org_id>');
52
+ const includeDeletedFlag = flags.include_deleted ?? flags['include-deleted'];
53
+ const query = buildQuery({
54
+ include_deleted: (0, args_1.toBoolean)(includeDeletedFlag) ? 'true' : undefined,
55
+ });
56
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}${query}`);
57
+ (0, output_1.outputJson)(response, json);
58
+ return;
59
+ }
60
+ case 'update': {
61
+ const orgId = positionals[0];
62
+ if (!orgId)
63
+ throw new Error('Usage: eve org update <org_id> [--name <name>] [--deleted <bool>]');
64
+ const body = {};
65
+ if (typeof flags.name === 'string')
66
+ body.name = flags.name;
67
+ const deleted = (0, args_1.toBoolean)(flags.deleted);
68
+ if (deleted !== undefined)
69
+ body.deleted = deleted;
70
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}`, { method: 'PATCH', body });
71
+ (0, output_1.outputJson)(response, json);
72
+ return;
73
+ }
74
+ default:
75
+ throw new Error('Usage: eve org <ensure|list|get|update>');
76
+ }
77
+ }
78
+ function normalizeOrgId(raw) {
79
+ if (/^org_[a-zA-Z0-9]+$/.test(raw)) {
80
+ return raw;
81
+ }
82
+ const stripped = raw.replace(/[^a-zA-Z0-9]/g, '');
83
+ if (!stripped) {
84
+ throw new Error('Organization id must contain alphanumeric characters');
85
+ }
86
+ return `org_${stripped}`;
87
+ }
88
+ function buildQuery(params) {
89
+ const search = new URLSearchParams();
90
+ Object.entries(params).forEach(([key, value]) => {
91
+ if (value === undefined || value === '')
92
+ return;
93
+ search.set(key, String(value));
94
+ });
95
+ const query = search.toString();
96
+ return query ? `?${query}` : '';
97
+ }
@@ -0,0 +1,403 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handlePipeline = handlePipeline;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ async function handlePipeline(subcommand, positionals, flags, context) {
8
+ const json = Boolean(flags.json);
9
+ switch (subcommand) {
10
+ case 'list':
11
+ return handleList(positionals, flags, context, json);
12
+ case 'show':
13
+ return handleShow(positionals, flags, context, json);
14
+ case 'run':
15
+ return handleRun(positionals, flags, context, json);
16
+ case 'runs':
17
+ return handleRuns(positionals, flags, context, json);
18
+ case 'show-run':
19
+ return handleShowRun(positionals, flags, context, json);
20
+ case 'approve':
21
+ return handleApprove(positionals, flags, context, json);
22
+ case 'cancel':
23
+ return handleCancel(positionals, flags, context, json);
24
+ case 'logs':
25
+ return handleLogs(positionals, flags, context, json);
26
+ default:
27
+ throw new Error('Usage: eve pipeline <list|show|run|runs|show-run|approve|cancel|logs>\n' +
28
+ ' list [project] - list pipelines for a project\n' +
29
+ ' show <project> <name> - show pipeline definition\n' +
30
+ ' run <name> --ref <sha> - create and run a new pipeline\n' +
31
+ ' Options: --env, --inputs <json>\n' +
32
+ ' runs [project] - list recent pipeline runs\n' +
33
+ ' Options: --limit, --status, --name <pipeline>\n' +
34
+ ' show-run <pipeline> <run-id> - show pipeline run status and steps\n' +
35
+ ' approve <run-id> - approve a blocked pipeline run\n' +
36
+ ' cancel <run-id> [--reason <text>] - cancel pipeline run\n' +
37
+ ' logs <pipeline> <run-id> [--step <name>] - show logs for pipeline run');
38
+ }
39
+ }
40
+ async function handleList(positionals, flags, context, json) {
41
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
42
+ if (!projectId) {
43
+ throw new Error('Usage: eve pipeline list [project] [--project=<id>]');
44
+ }
45
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/pipelines`);
46
+ if (json) {
47
+ (0, output_1.outputJson)(response, json);
48
+ return;
49
+ }
50
+ if (response.data.length === 0) {
51
+ console.log('No pipelines found.');
52
+ return;
53
+ }
54
+ formatPipelines(response.data);
55
+ }
56
+ async function handleShow(positionals, flags, context, json) {
57
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
58
+ const name = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['name']);
59
+ if (!projectId || !name) {
60
+ throw new Error('Usage: eve pipeline show <project> <name> [--project=<id>] [--name=<name>]');
61
+ }
62
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/pipelines/${name}`);
63
+ if (json) {
64
+ (0, output_1.outputJson)(response, json);
65
+ return;
66
+ }
67
+ formatPipeline(response);
68
+ }
69
+ function formatPipelines(pipelines) {
70
+ console.log('Pipelines:');
71
+ for (const pipeline of pipelines) {
72
+ const stepCount = Array.isArray(pipeline.definition?.steps)
73
+ ? pipeline.definition.steps.length
74
+ : null;
75
+ const suffix = stepCount === null ? '' : ` (${stepCount} steps)`;
76
+ console.log(`- ${pipeline.name}${suffix}`);
77
+ }
78
+ }
79
+ function formatPipeline(pipeline) {
80
+ console.log(`Pipeline: ${pipeline.name}`);
81
+ console.log('Definition:');
82
+ console.log(JSON.stringify(pipeline.definition, null, 2));
83
+ }
84
+ function formatPipelineRunList(runs) {
85
+ console.log('Recent pipeline runs:');
86
+ console.log('');
87
+ // Table header
88
+ console.log('Run ID'.padEnd(30) + 'Pipeline'.padEnd(20) + 'Status'.padEnd(20) + 'Created');
89
+ console.log('-'.repeat(100));
90
+ for (const run of runs) {
91
+ const runId = run.id.padEnd(30);
92
+ const pipeline = run.pipeline_name.padEnd(20);
93
+ const status = run.status.padEnd(20);
94
+ const created = new Date(run.created_at).toLocaleString();
95
+ console.log(`${runId}${pipeline}${status}${created}`);
96
+ }
97
+ console.log('');
98
+ console.log(`Total: ${runs.length} runs`);
99
+ }
100
+ function formatPipelineRunDetail(detail) {
101
+ const { run, steps } = detail;
102
+ console.log(`Run ID: ${run.id}`);
103
+ console.log(`Pipeline: ${run.pipeline_name}`);
104
+ console.log(`Status: ${run.status}`);
105
+ if (run.env_name) {
106
+ console.log(`Environment: ${run.env_name}`);
107
+ }
108
+ if (run.git_sha) {
109
+ console.log(`Git SHA: ${run.git_sha}`);
110
+ }
111
+ if (run.started_at) {
112
+ console.log(`Started: ${run.started_at}`);
113
+ }
114
+ if (run.completed_at) {
115
+ console.log(`Completed: ${run.completed_at}`);
116
+ }
117
+ if (run.error_message) {
118
+ console.log(`Error: ${run.error_message}`);
119
+ }
120
+ console.log('');
121
+ console.log('Steps:');
122
+ console.log('');
123
+ if (steps.length === 0) {
124
+ console.log('No steps found.');
125
+ return;
126
+ }
127
+ // Table header
128
+ console.log('Step'.padEnd(25) + 'Type'.padEnd(12) + 'Status'.padEnd(15) + 'Duration');
129
+ console.log('-'.repeat(80));
130
+ for (const step of steps) {
131
+ const name = step.step_name.padEnd(25);
132
+ const type = step.step_type.padEnd(12);
133
+ const status = step.status.padEnd(15);
134
+ let duration = '-';
135
+ if (step.duration_ms !== null) {
136
+ duration = `${step.duration_ms}ms`;
137
+ }
138
+ else if (step.started_at && !step.completed_at) {
139
+ duration = 'running...';
140
+ }
141
+ console.log(`${name}${type}${status}${duration}`);
142
+ if (step.error_message) {
143
+ console.log(` Error: ${step.error_message}`);
144
+ }
145
+ }
146
+ console.log('');
147
+ console.log(`Total: ${steps.length} steps`);
148
+ }
149
+ async function handleRun(positionals, flags, context, json) {
150
+ const pipelineName = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
151
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
152
+ const env = (0, args_1.getStringFlag)(flags, ['env']);
153
+ const ref = (0, args_1.getStringFlag)(flags, ['ref']);
154
+ const wait = Boolean(flags.wait);
155
+ const timeout = (0, args_1.getStringFlag)(flags, ['timeout']);
156
+ const inputsRaw = (0, args_1.getStringFlag)(flags, ['inputs']);
157
+ if (!pipelineName || !projectId || !ref) {
158
+ throw new Error('Usage: eve pipeline run <name> --ref <sha> [--env <env>] [--project <id>] [--wait] [--inputs <json>]');
159
+ }
160
+ let inputs;
161
+ if (inputsRaw) {
162
+ try {
163
+ inputs = JSON.parse(inputsRaw);
164
+ }
165
+ catch (error) {
166
+ throw new Error(`Invalid JSON for --inputs: ${error instanceof Error ? error.message : 'unknown error'}`);
167
+ }
168
+ }
169
+ // Emit event before creating the pipeline run
170
+ await emitPipelineRunEvent(context, projectId, pipelineName, { env, ref, inputs });
171
+ const query = new URLSearchParams();
172
+ if (wait)
173
+ query.set('wait', 'true');
174
+ if (timeout)
175
+ query.set('timeout', timeout);
176
+ const queryString = query.toString();
177
+ const url = `/projects/${projectId}/pipelines/${pipelineName}/run${queryString ? `?${queryString}` : ''}`;
178
+ const response = await (0, client_1.requestJson)(context, url, {
179
+ method: 'POST',
180
+ body: { ref, env, inputs },
181
+ });
182
+ if (json) {
183
+ (0, output_1.outputJson)(response, json);
184
+ return;
185
+ }
186
+ console.log('Pipeline run created.');
187
+ console.log('');
188
+ formatPipelineRunDetail(response);
189
+ }
190
+ async function handleRuns(positionals, flags, context, json) {
191
+ // Support both: eve pipeline runs [project] OR eve pipeline runs <pipeline_name> [project]
192
+ // If first arg looks like a project ID, treat as project filter
193
+ // Otherwise treat as pipeline name for backward compat
194
+ const firstArg = positionals[0];
195
+ const secondArg = positionals[1];
196
+ let projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
197
+ let pipelineName = (0, args_1.getStringFlag)(flags, ['name']);
198
+ // Determine if first arg is project or pipeline name
199
+ if (firstArg) {
200
+ if (firstArg.startsWith('proj_') || firstArg.startsWith('org_')) {
201
+ projectId = firstArg;
202
+ }
203
+ else {
204
+ pipelineName = firstArg;
205
+ if (secondArg) {
206
+ projectId = secondArg;
207
+ }
208
+ }
209
+ }
210
+ if (!projectId) {
211
+ throw new Error('Usage: eve pipeline runs [project] [--project <id>] [--name <pipeline>] [--limit N] [--status <status>]');
212
+ }
213
+ const query = buildQuery({
214
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
215
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
216
+ status: (0, args_1.getStringFlag)(flags, ['status']),
217
+ });
218
+ // If pipeline name specified, use per-pipeline endpoint
219
+ // Otherwise list all runs for the project (when that endpoint exists)
220
+ const url = pipelineName
221
+ ? `/projects/${projectId}/pipelines/${pipelineName}/runs${query}`
222
+ : `/projects/${projectId}/pipelines/${pipelineName || '__all'}/runs${query}`;
223
+ const response = await (0, client_1.requestJson)(context, url);
224
+ if (json) {
225
+ (0, output_1.outputJson)(response, json);
226
+ return;
227
+ }
228
+ if (response.data.length === 0) {
229
+ console.log('No pipeline runs found.');
230
+ return;
231
+ }
232
+ formatPipelineRunList(response.data);
233
+ }
234
+ async function handleShowRun(positionals, flags, context, json) {
235
+ // Signature: eve pipeline show-run <pipeline_name> <run-id>
236
+ const pipelineName = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
237
+ const runId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['run']);
238
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
239
+ if (!pipelineName || !runId || !projectId) {
240
+ throw new Error('Usage: eve pipeline show-run <pipeline_name> <run-id> [--project <id>]');
241
+ }
242
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/pipelines/${pipelineName}/runs/${runId}`);
243
+ if (json) {
244
+ (0, output_1.outputJson)(response, json);
245
+ return;
246
+ }
247
+ formatPipelineRunDetail(response);
248
+ }
249
+ async function handleApprove(positionals, flags, context, json) {
250
+ const runId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['run']);
251
+ if (!runId) {
252
+ throw new Error('Usage: eve pipeline approve <run-id>');
253
+ }
254
+ const response = await (0, client_1.requestJson)(context, `/pipeline-runs/${runId}/approve`, { method: 'POST' });
255
+ if (json) {
256
+ (0, output_1.outputJson)(response, json);
257
+ return;
258
+ }
259
+ console.log(`Approved pipeline run ${runId}.`);
260
+ console.log('');
261
+ formatPipelineRunDetail(response);
262
+ }
263
+ async function handleCancel(positionals, flags, context, json) {
264
+ const runId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['run']);
265
+ const reason = (0, args_1.getStringFlag)(flags, ['reason']);
266
+ if (!runId) {
267
+ throw new Error('Usage: eve pipeline cancel <run-id> [--reason <text>]');
268
+ }
269
+ const response = await (0, client_1.requestJson)(context, `/pipeline-runs/${runId}/cancel`, {
270
+ method: 'POST',
271
+ body: reason ? { reason } : {},
272
+ });
273
+ if (json) {
274
+ (0, output_1.outputJson)(response, json);
275
+ return;
276
+ }
277
+ console.log(`Cancelled pipeline run ${runId}.`);
278
+ console.log('');
279
+ formatPipelineRunDetail(response);
280
+ }
281
+ async function handleLogs(positionals, flags, context, json) {
282
+ const pipelineName = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
283
+ const runId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['run']);
284
+ const stepName = (0, args_1.getStringFlag)(flags, ['step']);
285
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
286
+ if (!pipelineName || !runId || !projectId) {
287
+ throw new Error('Usage: eve pipeline logs <pipeline_name> <run-id> [--step <step_name>] [--project <id>]');
288
+ }
289
+ // First get the run details to know which steps exist
290
+ const runDetail = await (0, client_1.requestJson)(context, `/projects/${projectId}/pipelines/${pipelineName}/runs/${runId}`);
291
+ if (json) {
292
+ (0, output_1.outputJson)(runDetail, json);
293
+ return;
294
+ }
295
+ console.log(`Pipeline run: ${runDetail.run.id}`);
296
+ console.log(`Pipeline: ${runDetail.run.pipeline_name}`);
297
+ console.log(`Status: ${runDetail.run.status}`);
298
+ console.log('');
299
+ // Filter steps if specific step requested
300
+ const stepsToShow = stepName
301
+ ? runDetail.steps.filter(s => s.step_name === stepName)
302
+ : runDetail.steps;
303
+ if (stepsToShow.length === 0) {
304
+ if (stepName) {
305
+ console.log(`No step found with name: ${stepName}`);
306
+ }
307
+ else {
308
+ console.log('No steps found for this pipeline run.');
309
+ }
310
+ return;
311
+ }
312
+ // Show logs for each step
313
+ for (const step of stepsToShow) {
314
+ console.log(`Step: ${step.step_name} (${step.step_type})`);
315
+ console.log(`Status: ${step.status}`);
316
+ if (step.started_at) {
317
+ console.log(`Started: ${step.started_at}`);
318
+ }
319
+ if (step.completed_at) {
320
+ console.log(`Completed: ${step.completed_at}`);
321
+ }
322
+ if (step.duration_ms !== null) {
323
+ console.log(`Duration: ${step.duration_ms}ms`);
324
+ }
325
+ if (step.error_message) {
326
+ console.log(`Error: ${step.error_message}`);
327
+ }
328
+ if (step.result_text) {
329
+ console.log('');
330
+ console.log('Result:');
331
+ console.log(step.result_text);
332
+ }
333
+ if (step.result_json) {
334
+ console.log('');
335
+ console.log('Result JSON:');
336
+ console.log(JSON.stringify(step.result_json, null, 2));
337
+ }
338
+ console.log('');
339
+ console.log('---');
340
+ console.log('');
341
+ }
342
+ if (!stepName) {
343
+ console.log(`Total steps: ${stepsToShow.length}`);
344
+ }
345
+ }
346
+ function buildQuery(params) {
347
+ const search = new URLSearchParams();
348
+ Object.entries(params).forEach(([key, value]) => {
349
+ if (value === undefined || value === '')
350
+ return;
351
+ search.set(key, String(value));
352
+ });
353
+ const query = search.toString();
354
+ return query ? `?${query}` : '';
355
+ }
356
+ /**
357
+ * Emit an event before creating a pipeline run.
358
+ * Fire-and-forget operation - logs warning on failure but doesn't block the command.
359
+ */
360
+ async function emitPipelineRunEvent(context, projectId, pipelineName, options) {
361
+ try {
362
+ const eventBody = {
363
+ type: 'pipeline.run',
364
+ source: 'manual',
365
+ actor_type: 'user',
366
+ actor_id: 'cli-user',
367
+ };
368
+ // Add optional fields
369
+ if (options.env) {
370
+ eventBody.env_name = options.env;
371
+ }
372
+ if (options.ref) {
373
+ // Heuristic: if ref looks like a SHA (40 hex chars), treat as SHA; otherwise as branch
374
+ const isSha = /^[0-9a-f]{40}$/i.test(options.ref);
375
+ if (isSha) {
376
+ eventBody.ref_sha = options.ref;
377
+ }
378
+ else {
379
+ // Could be a branch name, short SHA, or tag - store in both for maximum compatibility
380
+ eventBody.ref_sha = options.ref;
381
+ eventBody.ref_branch = options.ref;
382
+ }
383
+ }
384
+ // Build payload with pipeline name and inputs
385
+ const payload = {
386
+ pipeline_name: pipelineName,
387
+ };
388
+ if (options.inputs) {
389
+ payload.inputs = options.inputs;
390
+ }
391
+ eventBody.payload_json = payload;
392
+ // Fire-and-forget: emit event but don't block on failure
393
+ await (0, client_1.requestJson)(context, `/projects/${projectId}/events`, {
394
+ method: 'POST',
395
+ body: eventBody,
396
+ allowError: true,
397
+ });
398
+ }
399
+ catch (error) {
400
+ // Log warning but don't fail the command
401
+ console.warn('Warning: Failed to emit pipeline.run event:', error instanceof Error ? error.message : 'unknown error');
402
+ }
403
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleProfile = handleProfile;
4
+ const config_1 = require("../lib/config");
5
+ const output_1 = require("../lib/output");
6
+ function handleProfile(subcommand, positionals, flags, config) {
7
+ const json = Boolean(flags.json);
8
+ switch (subcommand) {
9
+ case 'list': {
10
+ const profiles = Object.entries(config.profiles).map(([name, profile]) => ({
11
+ name,
12
+ active: name === config.active_profile,
13
+ ...profile,
14
+ }));
15
+ (0, output_1.outputJson)({ active_profile: config.active_profile, profiles }, json);
16
+ return;
17
+ }
18
+ case 'show': {
19
+ const name = positionals[0] ?? config.active_profile;
20
+ const profile = config.profiles[name];
21
+ if (!profile) {
22
+ throw new Error(`Profile ${name} not found`);
23
+ }
24
+ (0, output_1.outputJson)({ name, ...profile }, json);
25
+ return;
26
+ }
27
+ case 'use': {
28
+ const name = positionals[0];
29
+ if (!name) {
30
+ throw new Error('Usage: eve profile use <name>');
31
+ }
32
+ if (!config.profiles[name]) {
33
+ config.profiles[name] = {};
34
+ }
35
+ config.active_profile = name;
36
+ (0, config_1.saveConfig)(config);
37
+ (0, output_1.outputJson)({ active_profile: name }, json, `✓ Active profile: ${name}`);
38
+ return;
39
+ }
40
+ case 'create': {
41
+ const name = positionals[0];
42
+ if (!name) {
43
+ throw new Error('Usage: eve profile create <name> [--api-url <url> ...]');
44
+ }
45
+ if (config.profiles[name]) {
46
+ throw new Error(`Profile ${name} already exists`);
47
+ }
48
+ config.profiles[name] = applyProfileFlags({}, flags);
49
+ (0, config_1.saveConfig)(config);
50
+ (0, output_1.outputJson)({ name, ...config.profiles[name] }, json, `✓ Profile created: ${name}`);
51
+ return;
52
+ }
53
+ case 'set': {
54
+ const name = positionals[0] ?? config.active_profile;
55
+ if (!config.profiles[name]) {
56
+ config.profiles[name] = {};
57
+ }
58
+ config.profiles[name] = applyProfileFlags(config.profiles[name], flags);
59
+ (0, config_1.saveConfig)(config);
60
+ (0, output_1.outputJson)({ name, ...config.profiles[name] }, json, `✓ Profile updated: ${name}`);
61
+ return;
62
+ }
63
+ case 'remove': {
64
+ const name = positionals[0];
65
+ if (!name) {
66
+ throw new Error('Usage: eve profile remove <name>');
67
+ }
68
+ if (!config.profiles[name]) {
69
+ throw new Error(`Profile ${name} not found`);
70
+ }
71
+ delete config.profiles[name];
72
+ if (config.active_profile === name) {
73
+ const [first] = Object.keys(config.profiles);
74
+ config.active_profile = first ?? 'default';
75
+ if (!config.profiles[config.active_profile]) {
76
+ config.profiles[config.active_profile] = {};
77
+ }
78
+ }
79
+ (0, config_1.saveConfig)(config);
80
+ (0, output_1.outputJson)({ removed: name, active_profile: config.active_profile }, json, `✓ Removed profile ${name}`);
81
+ return;
82
+ }
83
+ default:
84
+ throw new Error('Usage: eve profile <list|show|use|create|set|remove>');
85
+ }
86
+ }
87
+ function applyProfileFlags(profile, flags) {
88
+ const updated = { ...profile };
89
+ if (typeof flags['api-url'] === 'string')
90
+ updated.api_url = flags['api-url'];
91
+ if (typeof flags.org === 'string')
92
+ updated.org_id = flags.org;
93
+ if (typeof flags.project === 'string')
94
+ updated.project_id = flags.project;
95
+ if (typeof flags['supabase-url'] === 'string')
96
+ updated.supabase_url = flags['supabase-url'];
97
+ if (typeof flags['supabase-anon-key'] === 'string')
98
+ updated.supabase_anon_key = flags['supabase-anon-key'];
99
+ // --harness accepts "harness" or "harness:variant" format
100
+ if (typeof flags.harness === 'string')
101
+ updated.default_harness = flags.harness;
102
+ return updated;
103
+ }