@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.
- package/dist/commands/agents.js +257 -8
- package/dist/commands/auth.js +30 -1
- package/dist/commands/env.js +142 -3
- package/dist/commands/init.js +16 -26
- package/dist/commands/job.js +20 -0
- package/dist/commands/migrate.js +136 -0
- package/dist/commands/org.js +45 -3
- package/dist/commands/packs.js +197 -0
- package/dist/commands/project.js +38 -1
- package/dist/commands/skills.js +15 -98
- package/dist/commands/supervise.js +60 -0
- package/dist/commands/system.js +1 -1
- package/dist/commands/thread.js +153 -0
- package/dist/index.js +16 -0
- package/dist/lib/help.js +267 -18
- package/package.json +3 -2
package/dist/commands/agents.js
CHANGED
|
@@ -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
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -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) {
|
package/dist/commands/env.js
CHANGED
|
@@ -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
|
-
|
|
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('');
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
|
196
|
-
const result = (0, node_child_process_1.spawnSync)('which', ['
|
|
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('
|
|
199
|
-
console.log(' npm install -g
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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' });
|
package/dist/commands/job.js
CHANGED
|
@@ -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
|
*/
|