@eve-horizon/cli 0.2.10 → 0.2.12

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.
@@ -57,16 +57,19 @@ async function handleEnv(subcommand, positionals, flags, context) {
57
57
  return handleLogs(positionals, flags, context, json);
58
58
  case 'diagnose':
59
59
  return handleDiagnose(positionals, flags, context, json);
60
+ case 'services':
61
+ return handleServices(positionals, flags, context, json);
60
62
  case 'delete':
61
63
  return handleDelete(positionals, flags, context, json);
62
64
  default:
63
- throw new Error('Usage: eve env <list|show|create|deploy|logs|delete>\n' +
65
+ throw new Error('Usage: eve env <list|show|create|deploy|logs|diagnose|services|delete>\n' +
64
66
  ' list [project] - list environments for a project\n' +
65
67
  ' show <project> <name> - show details of an environment\n' +
66
68
  ' create <name> --type=<type> [options] - create an environment\n' +
67
69
  ' deploy <env> --ref <sha> [--direct] [--inputs <json>] [--repo-dir <path>] - deploy to an environment\n' +
68
70
  ' logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>] - get service logs\n' +
69
71
  ' diagnose <project> <env> - diagnose deployment health and events\n' +
72
+ ' services <project> <env> - show per-service pod status summary\n' +
70
73
  ' delete <name> [--project=<id>] [--force] - delete an environment');
71
74
  }
72
75
  }
@@ -318,12 +321,16 @@ async function handleLogs(positionals, flags, context, json) {
318
321
  const envName = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['env', 'name']);
319
322
  const service = positionals[2] ?? (0, args_1.getStringFlag)(flags, ['service']);
320
323
  if (!projectId || !envName || !service) {
321
- throw new Error('Usage: eve env logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>]');
324
+ throw new Error('Usage: eve env logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>] [--pod <name>] [--container <name>] [--previous] [--all-pods]');
322
325
  }
323
326
  const query = buildQuery({
324
327
  since: (0, args_1.getStringFlag)(flags, ['since']),
325
328
  tail: (0, args_1.getStringFlag)(flags, ['tail']),
326
329
  grep: (0, args_1.getStringFlag)(flags, ['grep']),
330
+ pod: (0, args_1.getStringFlag)(flags, ['pod']),
331
+ container: (0, args_1.getStringFlag)(flags, ['container']),
332
+ previous: (0, args_1.toBoolean)(flags.previous) ?? undefined,
333
+ all_pods: (0, args_1.toBoolean)(flags['all-pods']) ?? (0, args_1.toBoolean)(flags.all_pods) ?? undefined,
327
334
  });
328
335
  const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/services/${service}/logs${query}`);
329
336
  if (json) {
@@ -335,7 +342,13 @@ async function handleLogs(positionals, flags, context, json) {
335
342
  return;
336
343
  }
337
344
  for (const entry of response.logs) {
338
- console.log(`[${entry.timestamp}] ${entry.line}`);
345
+ const prefixParts = [];
346
+ if (entry.pod)
347
+ prefixParts.push(entry.pod);
348
+ if (entry.container)
349
+ prefixParts.push(entry.container);
350
+ const prefix = prefixParts.length > 0 ? ` ${prefixParts.join('/')}` : '';
351
+ console.log(`[${entry.timestamp}]${prefix} ${entry.line}`);
339
352
  }
340
353
  }
341
354
  /**
@@ -358,6 +371,34 @@ async function handleDiagnose(positionals, flags, context, json) {
358
371
  }
359
372
  formatEnvDiagnose(response);
360
373
  }
374
+ /**
375
+ * eve env services <project> <env>
376
+ * Show a per-service summary using env diagnose data.
377
+ */
378
+ async function handleServices(positionals, flags, context, json) {
379
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
380
+ const envName = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['env']);
381
+ if (!projectId || !envName) {
382
+ throw new Error('Usage: eve env services <project> <env>');
383
+ }
384
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/diagnose`);
385
+ const services = buildServiceSummaries(response);
386
+ if (json) {
387
+ (0, output_1.outputJson)({
388
+ project_id: response.project_id,
389
+ env_name: response.env_name,
390
+ namespace: response.namespace,
391
+ status: response.status,
392
+ ready: response.ready,
393
+ checked_at: response.checked_at,
394
+ services,
395
+ deployments: response.deployments,
396
+ warnings: response.warnings,
397
+ }, json);
398
+ return;
399
+ }
400
+ formatEnvServices(response, services);
401
+ }
361
402
  /**
362
403
  * eve env delete <name> [--project=<id>] [--force]
363
404
  * Delete an environment
@@ -498,6 +539,104 @@ function formatEnvironmentDetails(env, health) {
498
539
  console.log(` Created: ${formatDate(env.created_at)}`);
499
540
  console.log(` Updated: ${formatDate(env.updated_at)}`);
500
541
  }
542
+ function buildServiceSummaries(report) {
543
+ const summaries = new Map();
544
+ for (const pod of report.pods) {
545
+ const component = getPodComponent(pod.labels);
546
+ const existing = summaries.get(component) ?? {
547
+ name: component,
548
+ pods_total: 0,
549
+ pods_ready: 0,
550
+ restarts: 0,
551
+ phases: [],
552
+ };
553
+ existing.pods_total += 1;
554
+ if (pod.ready) {
555
+ existing.pods_ready += 1;
556
+ }
557
+ existing.restarts += pod.restarts;
558
+ if (!existing.phases.includes(pod.phase)) {
559
+ existing.phases.push(pod.phase);
560
+ }
561
+ summaries.set(component, existing);
562
+ }
563
+ return Array.from(summaries.values()).sort((a, b) => a.name.localeCompare(b.name));
564
+ }
565
+ function formatEnvServices(report, services) {
566
+ console.log(`Environment Services: ${report.env_name}`);
567
+ console.log('');
568
+ console.log(` Namespace: ${report.namespace || '(none)'}`);
569
+ console.log(` Status: ${report.status}`);
570
+ console.log(` Ready: ${report.ready ? 'yes' : 'no'}`);
571
+ console.log(` K8s: ${report.k8s_available ? 'available' : 'unavailable'}`);
572
+ if (report.warnings && report.warnings.length > 0) {
573
+ console.log('');
574
+ console.log('Warnings:');
575
+ for (const warning of report.warnings) {
576
+ console.log(` - ${warning}`);
577
+ }
578
+ }
579
+ if (services.length === 0) {
580
+ console.log('');
581
+ console.log('No service pods found.');
582
+ }
583
+ else {
584
+ console.log('');
585
+ console.log('Services (from pods):');
586
+ const nameWidth = Math.max(7, ...services.map((s) => s.name.length));
587
+ const totalWidth = Math.max(4, ...services.map((s) => String(s.pods_total).length));
588
+ const readyWidth = Math.max(5, ...services.map((s) => String(s.pods_ready).length));
589
+ const restartsWidth = Math.max(8, ...services.map((s) => String(s.restarts).length));
590
+ const phasesWidth = Math.max(6, ...services.map((s) => s.phases.join(', ').length));
591
+ const header = [
592
+ padRight('Service', nameWidth),
593
+ padRight('Pods', totalWidth),
594
+ padRight('Ready', readyWidth),
595
+ padRight('Restarts', restartsWidth),
596
+ padRight('Phases', phasesWidth),
597
+ ].join(' ');
598
+ console.log(header);
599
+ console.log('-'.repeat(header.length));
600
+ for (const service of services) {
601
+ console.log([
602
+ padRight(service.name, nameWidth),
603
+ padRight(String(service.pods_total), totalWidth),
604
+ padRight(String(service.pods_ready), readyWidth),
605
+ padRight(String(service.restarts), restartsWidth),
606
+ padRight(service.phases.join(', '), phasesWidth),
607
+ ].join(' '));
608
+ }
609
+ }
610
+ if (report.deployments.length > 0) {
611
+ console.log('');
612
+ console.log('Deployments:');
613
+ const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
614
+ const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
615
+ const header = [
616
+ padRight('Name', nameWidth),
617
+ padRight('Ready', readyWidth),
618
+ 'Status',
619
+ ].join(' ');
620
+ console.log(header);
621
+ console.log('-'.repeat(header.length));
622
+ for (const deployment of report.deployments) {
623
+ const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
624
+ const status = deployment.ready ? 'ready' : 'not-ready';
625
+ console.log([
626
+ padRight(deployment.name, nameWidth),
627
+ padRight(readiness, readyWidth),
628
+ status,
629
+ ].join(' '));
630
+ }
631
+ }
632
+ }
633
+ function getPodComponent(labels) {
634
+ return (labels['eve.component'] ||
635
+ labels['app.kubernetes.io/name'] ||
636
+ labels['app'] ||
637
+ labels['component'] ||
638
+ 'unknown');
639
+ }
501
640
  function formatEnvDiagnose(report) {
502
641
  console.log(`Environment Diagnose: ${report.env_name}`);
503
642
  console.log('');
@@ -192,16 +192,15 @@ async function installSkills(projectRoot) {
192
192
  console.log('No skills.txt found, skipping skill installation');
193
193
  return;
194
194
  }
195
- // Check if openskills is available
196
- const result = (0, node_child_process_1.spawnSync)('which', ['openskills'], { encoding: 'utf8' });
195
+ // Check if skills CLI is available
196
+ const result = (0, node_child_process_1.spawnSync)('which', ['skills'], { encoding: 'utf8' });
197
197
  if (result.status !== 0) {
198
- console.log('openskills CLI not found. Skills will be installed when you run:');
199
- console.log(' npm install -g @anthropic/openskills');
198
+ console.log('skills CLI not found. Skills will be installed when you run:');
199
+ console.log(' npm install -g skills');
200
200
  console.log(' eve skills install');
201
201
  return;
202
202
  }
203
- // Import and call the skills install logic
204
- // We directly call the openskills commands here for simplicity
203
+ const agents = ['claude-code', 'codex', 'gemini-cli'];
205
204
  try {
206
205
  const content = fs.readFileSync(skillsTxt, 'utf-8');
207
206
  const lines = content.split('\n')
@@ -209,30 +208,21 @@ async function installSkills(projectRoot) {
209
208
  .filter(line => line.length > 0);
210
209
  for (const source of lines) {
211
210
  console.log(` Installing: ${source}`);
212
- try {
213
- (0, node_child_process_1.execSync)(`openskills install ${JSON.stringify(source)} --universal --yes`, {
214
- cwd: projectRoot,
215
- stdio: 'inherit',
216
- timeout: 120000,
217
- });
218
- }
219
- catch {
220
- console.log(` Warning: Failed to install ${source}`);
211
+ for (const agent of agents) {
212
+ try {
213
+ (0, node_child_process_1.execSync)(`skills add ${JSON.stringify(source)} -a ${agent} -y --all`, {
214
+ cwd: projectRoot,
215
+ stdio: 'inherit',
216
+ timeout: 120000,
217
+ });
218
+ }
219
+ catch {
220
+ console.log(` Warning: Failed to install ${source} for ${agent}`);
221
+ }
221
222
  }
222
223
  }
223
224
  // Ensure symlink
224
225
  ensureSkillsSymlink(projectRoot);
225
- // Sync AGENTS.md
226
- try {
227
- (0, node_child_process_1.execSync)('openskills sync --yes', {
228
- cwd: projectRoot,
229
- stdio: 'inherit',
230
- timeout: 30000,
231
- });
232
- }
233
- catch {
234
- console.log('Warning: Failed to sync AGENTS.md');
235
- }
236
226
  // Commit skill changes
237
227
  try {
238
228
  (0, node_child_process_1.execSync)('git add -A', { cwd: projectRoot, stdio: 'pipe' });
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleIntegrations = handleIntegrations;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ async function handleIntegrations(subcommand, positionals, flags, context) {
8
+ const json = Boolean(flags.json);
9
+ const orgId = (0, args_1.getStringFlag)(flags, ['org']) ?? context.orgId;
10
+ switch (subcommand) {
11
+ case 'list': {
12
+ if (!orgId) {
13
+ throw new Error('Usage: eve integrations list --org <org_id>');
14
+ }
15
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/integrations`);
16
+ (0, output_1.outputJson)(response, json);
17
+ return;
18
+ }
19
+ case 'slack': {
20
+ const action = positionals[0];
21
+ if (action !== 'connect') {
22
+ throw new Error('Usage: eve integrations slack connect --org <org_id> --team-id <team_id> [--token <token>]');
23
+ }
24
+ if (!orgId) {
25
+ throw new Error('Missing org id. Provide --org or set a profile default.');
26
+ }
27
+ const teamId = (0, args_1.getStringFlag)(flags, ['team-id']);
28
+ if (!teamId) {
29
+ throw new Error('Missing --team-id');
30
+ }
31
+ const token = (0, args_1.getStringFlag)(flags, ['token']);
32
+ const tokensJsonFlag = (0, args_1.getStringFlag)(flags, ['tokens-json']);
33
+ const status = (0, args_1.getStringFlag)(flags, ['status']);
34
+ let tokensJson;
35
+ if (tokensJsonFlag) {
36
+ try {
37
+ const parsed = JSON.parse(tokensJsonFlag);
38
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
39
+ tokensJson = parsed;
40
+ }
41
+ }
42
+ catch (error) {
43
+ throw new Error('Invalid --tokens-json (must be JSON object)');
44
+ }
45
+ }
46
+ if (!tokensJson && token) {
47
+ tokensJson = { access_token: token };
48
+ }
49
+ const body = { team_id: teamId };
50
+ if (tokensJson)
51
+ body.tokens_json = tokensJson;
52
+ if (status)
53
+ body.status = status;
54
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/integrations/slack/connect`, { method: 'POST', body });
55
+ (0, output_1.outputJson)(response, json, `✓ Slack integration connected: ${response.id}`);
56
+ return;
57
+ }
58
+ case 'test': {
59
+ const integrationId = positionals[0];
60
+ if (!integrationId) {
61
+ throw new Error('Usage: eve integrations test <integration_id>');
62
+ }
63
+ if (!orgId) {
64
+ throw new Error('Missing org id. Provide --org or set a profile default.');
65
+ }
66
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/integrations/${integrationId}/test`, { method: 'POST' });
67
+ (0, output_1.outputJson)(response, json, response.ok ? '✓ Integration test ok' : 'Integration test failed');
68
+ return;
69
+ }
70
+ default:
71
+ throw new Error('Usage: eve integrations <list|slack|test>');
72
+ }
73
+ }
@@ -1192,6 +1192,17 @@ function formatDiagnose(job, attempts, latestResult, logs) {
1192
1192
  console.log(' ✓ Job completed successfully');
1193
1193
  }
1194
1194
  else if (job.phase === 'cancelled') {
1195
+ const agentRuntimeError = findAgentRuntimeError(job, latestResult);
1196
+ if (agentRuntimeError) {
1197
+ console.log(' ✗ Agent runtime error');
1198
+ console.log(` ${agentRuntimeError}`);
1199
+ console.log(' Hint: Check agent runtime status and logs:');
1200
+ console.log(' eve system status');
1201
+ console.log(' eve system logs agent-runtime --tail 200');
1202
+ console.log(' eve system pods');
1203
+ console.log('');
1204
+ return;
1205
+ }
1195
1206
  // Cancelled jobs may have a failure reason in close_reason or attempt error
1196
1207
  if (latestResult?.errorMessage?.includes('git clone')) {
1197
1208
  console.log(' ✗ Git clone failed - check repo URL and credentials');
@@ -1232,6 +1243,15 @@ function formatDiagnose(job, attempts, latestResult, logs) {
1232
1243
  }
1233
1244
  }
1234
1245
  }
1246
+ function findAgentRuntimeError(job, latestResult) {
1247
+ const candidates = [latestResult?.errorMessage, job.close_reason].filter(Boolean);
1248
+ for (const message of candidates) {
1249
+ if (message.includes('Agent runtime')) {
1250
+ return message;
1251
+ }
1252
+ }
1253
+ return null;
1254
+ }
1235
1255
  /**
1236
1256
  * Format job hierarchy as a tree
1237
1257
  */
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleMigrate = handleMigrate;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const yaml_1 = require("yaml");
7
+ const git_js_1 = require("../lib/git.js");
8
+ // ============================================================================
9
+ // Main Handler
10
+ // ============================================================================
11
+ async function handleMigrate(subcommand, _rest, _flags) {
12
+ switch (subcommand) {
13
+ case 'skills-to-packs':
14
+ await migrateSkillsToPacks();
15
+ return;
16
+ default:
17
+ console.log('Usage: eve migrate <subcommand>');
18
+ console.log('');
19
+ console.log('Subcommands:');
20
+ console.log(' skills-to-packs Generate AgentPack config from skills.txt');
21
+ return;
22
+ }
23
+ }
24
+ // ============================================================================
25
+ // Subcommand Handlers
26
+ // ============================================================================
27
+ /**
28
+ * Read skills.txt and generate a suggested x-eve.packs YAML fragment
29
+ * for migration to AgentPacks.
30
+ */
31
+ async function migrateSkillsToPacks() {
32
+ const repoRoot = (0, git_js_1.getGitRoot)();
33
+ if (!repoRoot) {
34
+ throw new Error('Not in a git repository. Run this from your project root.');
35
+ }
36
+ const skillsTxtPath = (0, node_path_1.join)(repoRoot, 'skills.txt');
37
+ if (!(0, node_fs_1.existsSync)(skillsTxtPath)) {
38
+ console.log('No skills.txt found at repository root. Nothing to migrate.');
39
+ return;
40
+ }
41
+ const content = (0, node_fs_1.readFileSync)(skillsTxtPath, 'utf-8');
42
+ const lines = content.split('\n');
43
+ const localSources = [];
44
+ const remoteSources = [];
45
+ for (const rawLine of lines) {
46
+ const line = rawLine.split('#')[0].trim();
47
+ if (!line)
48
+ continue;
49
+ // Skip glob patterns -- they need manual review
50
+ if (line.includes('*')) {
51
+ console.log(` [skip] Glob pattern needs manual review: ${line}`);
52
+ continue;
53
+ }
54
+ const sourceType = classifySource(line);
55
+ if (sourceType === 'local') {
56
+ localSources.push(line);
57
+ }
58
+ else {
59
+ remoteSources.push(line);
60
+ }
61
+ }
62
+ if (localSources.length === 0 && remoteSources.length === 0) {
63
+ console.log('skills.txt is empty or contains only comments/globs. Nothing to migrate.');
64
+ return;
65
+ }
66
+ // Build the packs array
67
+ const packs = [];
68
+ for (const source of remoteSources) {
69
+ packs.push({ source: normalizeRemoteSource(source) });
70
+ }
71
+ for (const source of localSources) {
72
+ packs.push({ source });
73
+ }
74
+ // Build the YAML fragment
75
+ const fragment = {
76
+ 'x-eve': {
77
+ packs,
78
+ },
79
+ };
80
+ console.log('# Suggested AgentPack configuration for .eve/manifest.yaml');
81
+ console.log('# Review and merge into your existing manifest under the x-eve key.');
82
+ console.log('#');
83
+ if (localSources.length > 0) {
84
+ console.log('# Local paths are kept as-is. Consider publishing them as packs.');
85
+ }
86
+ console.log('');
87
+ console.log((0, yaml_1.stringify)(fragment, { indent: 2 }).trimEnd());
88
+ console.log('');
89
+ console.log('# After adding packs to your manifest:');
90
+ console.log('# 1. Run: eve agents sync');
91
+ console.log('# 2. Delete skills.txt from your repo');
92
+ }
93
+ // ============================================================================
94
+ // Helper Functions
95
+ // ============================================================================
96
+ /**
97
+ * Classify a skills.txt line as local or remote.
98
+ */
99
+ function classifySource(line) {
100
+ // URLs
101
+ if (line.startsWith('https://') || line.startsWith('http://')) {
102
+ return 'remote';
103
+ }
104
+ // Explicit GitHub prefix
105
+ if (line.startsWith('github:')) {
106
+ return 'remote';
107
+ }
108
+ // Local paths: absolute, home-relative, or dot-relative
109
+ if (line.startsWith('/') || line.startsWith('~') || line.startsWith('./') || line.startsWith('../')) {
110
+ return 'local';
111
+ }
112
+ // GitHub shorthand: owner/repo (single slash, no spaces)
113
+ if (line.includes('/') && !line.includes(' ') && !line.startsWith('.')) {
114
+ return 'remote';
115
+ }
116
+ // Default: treat as local
117
+ return 'local';
118
+ }
119
+ /**
120
+ * Normalize remote source references for pack config.
121
+ * - `github:owner/repo` -> `owner/repo`
122
+ * - GitHub URLs -> `owner/repo`
123
+ * - owner/repo -> owner/repo (pass through)
124
+ */
125
+ function normalizeRemoteSource(source) {
126
+ // Strip github: prefix
127
+ if (source.startsWith('github:')) {
128
+ return source.slice(7);
129
+ }
130
+ // Extract owner/repo from GitHub URL
131
+ const ghMatch = source.match(/github\.com\/([^/]+\/[^/]+)/);
132
+ if (ghMatch) {
133
+ return ghMatch[1].replace(/\.git$/, '');
134
+ }
135
+ return source;
136
+ }
@@ -60,14 +60,19 @@ async function handleOrg(subcommand, positionals, flags, context) {
60
60
  }
61
61
  case 'update': {
62
62
  const orgId = positionals[0];
63
- if (!orgId)
64
- throw new Error('Usage: eve org update <org_id> [--name <name>] [--deleted <bool>]');
63
+ if (!orgId) {
64
+ throw new Error('Usage: eve org update <org_id> [--name <name>] [--deleted <bool>] [--default-agent <slug>]');
65
+ }
65
66
  const body = {};
66
67
  if (typeof flags.name === 'string')
67
68
  body.name = flags.name;
68
69
  const deleted = (0, args_1.toBoolean)(flags.deleted);
69
70
  if (deleted !== undefined)
70
71
  body.deleted = deleted;
72
+ if (typeof flags['default-agent'] === 'string') {
73
+ const value = flags['default-agent'].trim();
74
+ body.default_agent_slug = (value === '' || value === 'none' || value === 'null') ? null : value;
75
+ }
71
76
  const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}`, { method: 'PATCH', body });
72
77
  (0, output_1.outputJson)(response, json);
73
78
  return;
@@ -80,8 +85,45 @@ async function handleOrg(subcommand, positionals, flags, context) {
80
85
  (0, output_1.outputJson)(response, json, `✓ Organization deleted: ${orgId}`);
81
86
  return;
82
87
  }
88
+ case 'members': {
89
+ const action = positionals[0]; // list | add | remove
90
+ const orgId = typeof flags.org === 'string' ? flags.org : context.orgId;
91
+ if (!orgId) {
92
+ throw new Error('Missing org id. Provide --org or set a profile default.');
93
+ }
94
+ switch (action) {
95
+ case 'add': {
96
+ const email = (0, args_1.getStringFlag)(flags, ['email']) ?? positionals[1];
97
+ const role = (0, args_1.getStringFlag)(flags, ['role']) ?? 'member';
98
+ if (!email) {
99
+ throw new Error('Usage: eve org members add <email> [--role member|admin|owner]');
100
+ }
101
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/members`, {
102
+ method: 'POST',
103
+ body: { email, role },
104
+ });
105
+ (0, output_1.outputJson)(response, json, `✓ Member added to org ${orgId}`);
106
+ return;
107
+ }
108
+ case 'remove': {
109
+ const userId = positionals[1];
110
+ if (!userId) {
111
+ throw new Error('Usage: eve org members remove <user_id>');
112
+ }
113
+ await (0, client_1.requestRaw)(context, `/orgs/${orgId}/members/${userId}`, { method: 'DELETE' });
114
+ (0, output_1.outputJson)({ org_id: orgId, user_id: userId, removed: true }, json, `✓ Member ${userId} removed from org ${orgId}`);
115
+ return;
116
+ }
117
+ case 'list':
118
+ default: {
119
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/members`);
120
+ (0, output_1.outputJson)(response, json);
121
+ return;
122
+ }
123
+ }
124
+ }
83
125
  default:
84
- throw new Error('Usage: eve org <ensure|list|get|update|delete>');
126
+ throw new Error('Usage: eve org <ensure|list|get|update|delete|members>');
85
127
  }
86
128
  }
87
129
  function normalizeOrgId(raw) {