@eve-horizon/cli 0.2.11 → 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.
@@ -5,6 +5,7 @@ const node_child_process_1 = require("node:child_process");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_path_1 = require("node:path");
7
7
  const yaml_1 = require("yaml");
8
+ const shared_1 = require("@eve/shared");
8
9
  const harness_capabilities_js_1 = require("../lib/harness-capabilities.js");
9
10
  const args_1 = require("../lib/args");
10
11
  const client_1 = require("../lib/client");
@@ -67,6 +68,167 @@ function loadAgentsConfig(repoRoot) {
67
68
  }
68
69
  return { source: { type: 'none' }, policy: null };
69
70
  }
71
+ async function resolvePacksAndMerge(repoRoot, manifest, projectSlug) {
72
+ const xEve = (manifest['x-eve'] ?? manifest['x_eve']);
73
+ const packs = (xEve?.packs ?? []);
74
+ if (packs.length === 0)
75
+ return null;
76
+ console.log(`Resolving ${packs.length} pack(s)...`);
77
+ // 1. Resolve all packs
78
+ const resolvedPacks = [];
79
+ for (const entry of packs) {
80
+ const resolved = await (0, shared_1.resolvePack)(entry, projectSlug, repoRoot);
81
+ resolvedPacks.push(resolved);
82
+ console.log(` ✓ ${resolved.id} (${resolved.skillPaths.length} skills)`);
83
+ }
84
+ // 2. Check for agent ID collisions across packs
85
+ const seenAgentIds = new Map();
86
+ for (const pack of resolvedPacks) {
87
+ for (const agentId of Object.keys(pack.agents)) {
88
+ if (seenAgentIds.has(agentId)) {
89
+ throw new Error(`Agent ID collision: "${agentId}" defined in both pack "${seenAgentIds.get(agentId)}" and "${pack.id}". ` +
90
+ `Agent IDs must be unique across all packs.`);
91
+ }
92
+ seenAgentIds.set(agentId, pack.id);
93
+ }
94
+ }
95
+ // 3. Merge pack bases in listed order
96
+ let mergedAgents = {};
97
+ let mergedTeams = {};
98
+ let mergedChat = { routes: [] };
99
+ const xEveFragments = [];
100
+ for (const pack of resolvedPacks) {
101
+ mergedAgents = (0, shared_1.mergeMapConfig)(mergedAgents, pack.agents);
102
+ mergedTeams = (0, shared_1.mergeMapConfig)(mergedTeams, pack.teams);
103
+ if (pack.chat) {
104
+ mergedChat = (0, shared_1.mergeChatConfig)(mergedChat, pack.chat);
105
+ }
106
+ if (pack.xEve) {
107
+ xEveFragments.push(pack.xEve);
108
+ }
109
+ }
110
+ // 4. Load project overlay files and merge on top
111
+ const configPaths = resolveAgentsConfigPaths(repoRoot, manifest);
112
+ if ((0, node_fs_1.existsSync)(configPaths.agentsPath)) {
113
+ const projectAgents = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(configPaths.agentsPath, 'utf-8')) ?? {};
114
+ mergedAgents = (0, shared_1.mergeMapConfig)(mergedAgents, projectAgents);
115
+ }
116
+ if ((0, node_fs_1.existsSync)(configPaths.teamsPath)) {
117
+ const projectTeams = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(configPaths.teamsPath, 'utf-8')) ?? {};
118
+ mergedTeams = (0, shared_1.mergeMapConfig)(mergedTeams, projectTeams);
119
+ }
120
+ if ((0, node_fs_1.existsSync)(configPaths.chatPath)) {
121
+ const projectChat = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(configPaths.chatPath, 'utf-8')) ?? {};
122
+ mergedChat = (0, shared_1.mergeChatConfig)(mergedChat, projectChat);
123
+ }
124
+ // 5. Merge x-eve (strip packs + install_agents from project overlay)
125
+ const projectXEve = (xEve ?? {});
126
+ const { packs: _p, install_agents: _ia, ...projectXEveRest } = projectXEve;
127
+ (0, shared_1.mergeXEve)(xEveFragments, projectXEveRest);
128
+ // 6. Validate effective config
129
+ validateEffectiveConfig(mergedAgents, mergedTeams, mergedChat);
130
+ // 7. Write lockfile
131
+ const lockfile = {
132
+ resolved_at: new Date().toISOString(),
133
+ project_slug: projectSlug,
134
+ packs: resolvedPacks.map((p) => ({
135
+ id: p.id,
136
+ source: p.source,
137
+ ref: p.ref,
138
+ pack_version: 1,
139
+ })),
140
+ effective: {
141
+ agents_count: Object.keys((0, shared_1.extractInnerMap)(mergedAgents, 'agents')).length,
142
+ teams_count: Object.keys((0, shared_1.extractInnerMap)(mergedTeams, 'teams')).length,
143
+ routes_count: (mergedChat.routes ?? []).length,
144
+ profiles_count: 0,
145
+ agents_hash: simpleHash(JSON.stringify(mergedAgents)),
146
+ teams_hash: simpleHash(JSON.stringify(mergedTeams)),
147
+ chat_hash: simpleHash(JSON.stringify(mergedChat)),
148
+ },
149
+ };
150
+ const eveDir = (0, node_path_1.join)(repoRoot, '.eve');
151
+ (0, node_fs_1.mkdirSync)(eveDir, { recursive: true });
152
+ const lockfilePath = (0, node_path_1.join)(eveDir, 'packs.lock.yaml');
153
+ (0, node_fs_1.writeFileSync)(lockfilePath, (0, yaml_1.stringify)(lockfile), 'utf-8');
154
+ console.log(` ✓ Lockfile written: .eve/packs.lock.yaml`);
155
+ // 8. Return effective config as YAML strings
156
+ const packRefs = resolvedPacks.map((p) => ({ id: p.id, source: p.source, ref: p.ref }));
157
+ return {
158
+ agentsYaml: (0, yaml_1.stringify)(mergedAgents),
159
+ teamsYaml: (0, yaml_1.stringify)(mergedTeams),
160
+ chatYaml: (0, yaml_1.stringify)(mergedChat),
161
+ packRefs,
162
+ };
163
+ }
164
+ function validateEffectiveConfig(agents, teams, chat) {
165
+ // Extract inner maps (handles both nested `{ version: 1, agents: { ... } }` and flat formats)
166
+ const agentsMap = (0, shared_1.extractInnerMap)(agents, 'agents');
167
+ const teamsMap = (0, shared_1.extractInnerMap)(teams, 'teams');
168
+ // Check slug uniqueness
169
+ const slugs = new Set();
170
+ for (const [, entry] of Object.entries(agentsMap)) {
171
+ if (typeof entry !== 'object' || entry === null)
172
+ continue;
173
+ const agent = entry;
174
+ const slug = agent.slug;
175
+ if (slug) {
176
+ if (slugs.has(slug)) {
177
+ throw new Error(`Duplicate agent slug "${slug}" in effective config. Slugs must be unique.`);
178
+ }
179
+ slugs.add(slug);
180
+ }
181
+ }
182
+ // Check team references
183
+ const agentIds = new Set(Object.keys(agentsMap));
184
+ for (const [teamId, entry] of Object.entries(teamsMap)) {
185
+ if (typeof entry !== 'object' || entry === null)
186
+ continue;
187
+ const team = entry;
188
+ const lead = team.lead;
189
+ if (lead && !agentIds.has(lead)) {
190
+ throw new Error(`Team "${teamId}" references unknown agent "${lead}" as lead.`);
191
+ }
192
+ const members = (team.members ?? []);
193
+ for (const member of members) {
194
+ if (!agentIds.has(member)) {
195
+ throw new Error(`Team "${teamId}" references unknown agent "${member}" as member.`);
196
+ }
197
+ }
198
+ }
199
+ // Check route targets (supports "team:name" and "agent:name" prefixes)
200
+ const teamIds = new Set(Object.keys(teamsMap));
201
+ const routes = (chat.routes ?? []);
202
+ for (const route of routes) {
203
+ if (!route.target)
204
+ continue;
205
+ let targetId = route.target;
206
+ if (targetId.startsWith('team:')) {
207
+ targetId = targetId.slice(5);
208
+ if (!teamIds.has(targetId)) {
209
+ throw new Error(`Route "${route.id}" targets unknown team "${route.target}".`);
210
+ }
211
+ }
212
+ else if (targetId.startsWith('agent:')) {
213
+ targetId = targetId.slice(6);
214
+ if (!agentIds.has(targetId)) {
215
+ throw new Error(`Route "${route.id}" targets unknown agent "${route.target}".`);
216
+ }
217
+ }
218
+ else if (!agentIds.has(targetId) && !teamIds.has(targetId)) {
219
+ throw new Error(`Route "${route.id}" targets unknown agent/team "${route.target}".`);
220
+ }
221
+ }
222
+ }
223
+ function simpleHash(input) {
224
+ let hash = 0;
225
+ for (let i = 0; i < input.length; i++) {
226
+ const char = input.charCodeAt(i);
227
+ hash = ((hash << 5) - hash) + char;
228
+ hash = hash & hash; // Convert to 32bit integer
229
+ }
230
+ return Math.abs(hash).toString(16).padStart(8, '0');
231
+ }
70
232
  async function handleAgents(subcommand, positionals, flags, context) {
71
233
  const command = subcommand ?? 'config';
72
234
  const json = Boolean(flags.json);
@@ -145,10 +307,25 @@ async function handleAgents(subcommand, positionals, flags, context) {
145
307
  throw new Error(`Missing manifest at ${manifestPath}. Expected .eve/manifest.yaml.`);
146
308
  }
147
309
  const manifest = readYamlFile(manifestPath);
148
- const configPaths = resolveAgentsConfigPaths(repoRoot, manifest);
149
- const agentsYaml = (0, node_fs_1.readFileSync)(ensureFileExists(configPaths.agentsPath, 'agents config'), 'utf-8');
150
- const teamsYaml = (0, node_fs_1.readFileSync)(ensureFileExists(configPaths.teamsPath, 'teams config'), 'utf-8');
151
- const chatYaml = (0, node_fs_1.readFileSync)(ensureFileExists(configPaths.chatPath, 'chat config'), 'utf-8');
310
+ // Pack resolution: if packs exist, resolve and merge before posting
311
+ const projectSlug = manifest.project ?? projectId.replace(/^proj_/, '');
312
+ const packResult = await resolvePacksAndMerge(repoRoot, manifest, projectSlug);
313
+ let agentsYaml;
314
+ let teamsYaml;
315
+ let chatYaml;
316
+ let packRefs;
317
+ if (packResult) {
318
+ agentsYaml = packResult.agentsYaml;
319
+ teamsYaml = packResult.teamsYaml;
320
+ chatYaml = packResult.chatYaml;
321
+ packRefs = packResult.packRefs;
322
+ }
323
+ else {
324
+ const configPaths = resolveAgentsConfigPaths(repoRoot, manifest);
325
+ agentsYaml = (0, node_fs_1.readFileSync)(ensureFileExists(configPaths.agentsPath, 'agents config'), 'utf-8');
326
+ teamsYaml = (0, node_fs_1.readFileSync)(ensureFileExists(configPaths.teamsPath, 'teams config'), 'utf-8');
327
+ chatYaml = (0, node_fs_1.readFileSync)(ensureFileExists(configPaths.chatPath, 'chat config'), 'utf-8');
328
+ }
152
329
  let gitSha;
153
330
  let branch;
154
331
  let gitRef = ref ?? 'local';
@@ -172,6 +349,7 @@ async function handleAgents(subcommand, positionals, flags, context) {
172
349
  git_sha: gitSha,
173
350
  branch,
174
351
  git_ref: gitRef,
352
+ pack_refs: packRefs,
175
353
  },
176
354
  });
177
355
  if (json) {
@@ -179,9 +357,16 @@ async function handleAgents(subcommand, positionals, flags, context) {
179
357
  return;
180
358
  }
181
359
  console.log(`✓ Agents config synced to ${projectId}`);
182
- console.log(` Agents: ${configPaths.agentsPath}`);
183
- console.log(` Teams: ${configPaths.teamsPath}`);
184
- console.log(` Chat: ${configPaths.chatPath}`);
360
+ if (packRefs) {
361
+ console.log(` Packs: ${packRefs.map((p) => p.id).join(', ')}`);
362
+ console.log(` Source: merged (packs + project overlay)`);
363
+ }
364
+ else {
365
+ const configPaths = resolveAgentsConfigPaths(repoRoot, manifest);
366
+ console.log(` Agents: ${configPaths.agentsPath}`);
367
+ console.log(` Teams: ${configPaths.teamsPath}`);
368
+ console.log(` Chat: ${configPaths.chatPath}`);
369
+ }
185
370
  if (gitSha)
186
371
  console.log(` Git SHA: ${gitSha.substring(0, 8)}`);
187
372
  if (branch)
@@ -190,7 +375,71 @@ async function handleAgents(subcommand, positionals, flags, context) {
190
375
  console.log(' Warning: working tree dirty — marked non-deployable');
191
376
  return;
192
377
  }
378
+ case 'runtime-status': {
379
+ const orgId = (0, args_1.getStringFlag)(flags, ['org']) ?? context.orgId;
380
+ if (!orgId) {
381
+ throw new Error('Missing org id. Provide --org or set a profile default.');
382
+ }
383
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/agent-runtime/status`);
384
+ if (json) {
385
+ (0, output_1.outputJson)(response, json);
386
+ return;
387
+ }
388
+ formatAgentRuntimeStatus(response, orgId);
389
+ return;
390
+ }
193
391
  default:
194
- throw new Error('Usage: eve agents <config|sync>');
392
+ throw new Error('Usage: eve agents <config|sync|runtime-status>');
393
+ }
394
+ }
395
+ function formatAgentRuntimeStatus(response, orgId) {
396
+ console.log(`Agent Runtime Status: ${orgId}`);
397
+ if (response.pods.length === 0) {
398
+ console.log('');
399
+ console.log('No agent runtime pods found.');
400
+ return;
401
+ }
402
+ const nameWidth = Math.max(7, ...response.pods.map((pod) => pod.pod_name.length));
403
+ const statusWidth = Math.max(6, ...response.pods.map((pod) => pod.status.length));
404
+ const capacityWidth = Math.max(8, ...response.pods.map((pod) => String(pod.capacity).length));
405
+ const ageWidth = Math.max(3, ...response.pods.map((pod) => formatAgeSeconds(pod.last_heartbeat_at).length));
406
+ console.log('');
407
+ const header = [
408
+ padRight('Pod', nameWidth),
409
+ padRight('Status', statusWidth),
410
+ padRight('Capacity', capacityWidth),
411
+ padRight('Age', ageWidth),
412
+ 'Last Heartbeat',
413
+ ].join(' ');
414
+ console.log(header);
415
+ console.log('-'.repeat(header.length));
416
+ for (const pod of response.pods) {
417
+ const age = formatAgeSeconds(pod.last_heartbeat_at);
418
+ console.log([
419
+ padRight(pod.pod_name, nameWidth),
420
+ padRight(pod.status, statusWidth),
421
+ padRight(String(pod.capacity), capacityWidth),
422
+ padRight(age, ageWidth),
423
+ pod.last_heartbeat_at,
424
+ ].join(' '));
425
+ }
426
+ }
427
+ function padRight(value, width) {
428
+ return value.length >= width ? value : `${value}${' '.repeat(width - value.length)}`;
429
+ }
430
+ function formatAgeSeconds(isoDate) {
431
+ const timestamp = Date.parse(isoDate);
432
+ if (!Number.isFinite(timestamp)) {
433
+ return '-';
434
+ }
435
+ const diffSeconds = Math.max(0, Math.floor((Date.now() - timestamp) / 1000));
436
+ if (diffSeconds < 60) {
437
+ return `${diffSeconds}s`;
438
+ }
439
+ const diffMinutes = Math.floor(diffSeconds / 60);
440
+ if (diffMinutes < 60) {
441
+ return `${diffMinutes}m`;
195
442
  }
443
+ const diffHours = Math.floor(diffMinutes / 60);
444
+ return `${diffHours}h`;
196
445
  }
@@ -174,9 +174,38 @@ async function handleAuth(subcommand, flags, context, credentials) {
174
174
  (0, output_1.outputJson)(data, json, 'Auth disabled for this stack.');
175
175
  return;
176
176
  }
177
+ if (!json && data.permissions && data.permissions.length > 0) {
178
+ console.log(`User: ${data.user_id ?? 'unknown'} (${data.email ?? 'no email'})`);
179
+ console.log(`Role: ${data.role ?? 'member'}`);
180
+ console.log(`Admin: ${data.is_admin ?? false}`);
181
+ console.log(`\nPermissions (${data.permissions.length}):`);
182
+ for (const perm of data.permissions) {
183
+ console.log(` ${perm}`);
184
+ }
185
+ return;
186
+ }
177
187
  (0, output_1.outputJson)(data, json);
178
188
  return;
179
189
  }
190
+ case 'permissions': {
191
+ const response = await (0, client_1.requestJson)(context, '/auth/permissions');
192
+ if (json) {
193
+ (0, output_1.outputJson)(response, json);
194
+ return;
195
+ }
196
+ console.log('Permission Matrix:');
197
+ console.log('');
198
+ const header = 'Permission'.padEnd(24) + 'Member'.padEnd(10) + 'Admin'.padEnd(10) + 'Owner';
199
+ console.log(header);
200
+ console.log('-'.repeat(header.length));
201
+ for (const row of response.matrix) {
202
+ const m = row.member ? '✓' : '-';
203
+ const a = row.admin ? '✓' : '-';
204
+ const o = row.owner ? '✓' : '-';
205
+ console.log(`${row.permission.padEnd(24)}${m.padEnd(10)}${a.padEnd(10)}${o}`);
206
+ }
207
+ return;
208
+ }
180
209
  case 'bootstrap': {
181
210
  const statusOnly = (0, args_1.getBooleanFlag)(flags, ['status']) ?? false;
182
211
  // Check bootstrap status first
@@ -681,7 +710,7 @@ async function handleAuth(subcommand, flags, context, credentials) {
681
710
  return;
682
711
  }
683
712
  default:
684
- throw new Error('Usage: eve auth <login|logout|status|whoami|bootstrap|sync|creds|token>');
713
+ throw new Error('Usage: eve auth <login|logout|status|whoami|bootstrap|sync|creds|token|mint|permissions>');
685
714
  }
686
715
  }
687
716
  function signNonceWithSsh(keyPath, nonce) {
@@ -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' });
@@ -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
  */