@eve-horizon/cli 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -264,8 +264,20 @@ Environment variables for agent context:
264
264
  ```bash
265
265
  eve harness list # List available harnesses
266
266
  eve harness get mclaude # Show harness details and auth status
267
+ eve harness list --capabilities # Include model/reasoning capability hints
267
268
  ```
268
269
 
270
+ ### Agents (Orchestration Config)
271
+
272
+ Provide policy + harness context for orchestrating agents (profiles, councils, availability):
273
+
274
+ ```bash
275
+ eve agents config --json
276
+ eve agents config --path /path/to/repo --no-harnesses
277
+ ```
278
+
279
+ Recommended default policy profile name: `primary-orchestrator`.
280
+
269
281
  ## Pagination
270
282
 
271
283
  List endpoints accept `--limit` and `--offset`. Default limit is **10**, newest first.
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleAgents = handleAgents;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const yaml_1 = require("yaml");
7
+ const shared_1 = require("@eve/shared");
8
+ const args_1 = require("../lib/args");
9
+ const client_1 = require("../lib/client");
10
+ const output_1 = require("../lib/output");
11
+ function readYamlFile(filePath) {
12
+ const raw = (0, node_fs_1.readFileSync)(filePath, 'utf-8');
13
+ const parsed = (0, yaml_1.parse)(raw);
14
+ if (!parsed || typeof parsed !== 'object') {
15
+ throw new Error(`Invalid YAML in ${filePath}`);
16
+ }
17
+ return parsed;
18
+ }
19
+ function loadAgentsConfig(repoRoot) {
20
+ const eveDir = (0, node_path_1.join)(repoRoot, '.eve');
21
+ const manifestPath = (0, node_path_1.join)(eveDir, 'manifest.yaml');
22
+ if ((0, node_fs_1.existsSync)(manifestPath)) {
23
+ const manifest = readYamlFile(manifestPath);
24
+ const xEve = manifest['x-eve'] ||
25
+ manifest['x_eve'] ||
26
+ {};
27
+ const policy = xEve['agents'] || null;
28
+ const defaults = xEve['defaults'] || null;
29
+ return { source: { type: 'manifest', path: manifestPath }, policy, manifest_defaults: defaults };
30
+ }
31
+ return { source: { type: 'none' }, policy: null };
32
+ }
33
+ async function handleAgents(subcommand, positionals, flags, context) {
34
+ const command = subcommand ?? 'config';
35
+ const json = Boolean(flags.json);
36
+ const includeHarnesses = !((0, args_1.getBooleanFlag)(flags, ['no-harnesses']) ?? false);
37
+ const repoRoot = (0, node_path_1.resolve)((0, args_1.getStringFlag)(flags, ['path']) ?? process.cwd());
38
+ if (command !== 'config') {
39
+ throw new Error('Usage: eve agents config [--path <dir>] [--no-harnesses]');
40
+ }
41
+ const result = loadAgentsConfig(repoRoot);
42
+ const response = {
43
+ repo_root: repoRoot,
44
+ source: result.source,
45
+ policy: result.policy,
46
+ };
47
+ if (result.manifest_defaults) {
48
+ response.manifest_defaults = result.manifest_defaults;
49
+ }
50
+ if (includeHarnesses) {
51
+ const harnesses = await (0, client_1.requestJson)(context, '/harnesses');
52
+ response.harnesses = harnesses.data;
53
+ response.capabilities = shared_1.HARNESS_CAPABILITIES;
54
+ }
55
+ if (json) {
56
+ (0, output_1.outputJson)(response, json);
57
+ return;
58
+ }
59
+ console.log(`Agents config source: ${result.source.type}`);
60
+ if ('path' in result.source) {
61
+ console.log(`Path: ${result.source.path}`);
62
+ }
63
+ if (!result.policy) {
64
+ console.log('No policy found. Add x-eve.agents to .eve/manifest.yaml.');
65
+ }
66
+ else {
67
+ const profiles = result.policy.profiles || {};
68
+ const profileNames = Object.keys(profiles);
69
+ console.log(`Profiles: ${profileNames.length ? profileNames.join(', ') : 'none'}`);
70
+ }
71
+ if (includeHarnesses) {
72
+ const harnesses = response.harnesses;
73
+ if (harnesses?.length) {
74
+ const ready = harnesses.filter((h) => h.auth.available).length;
75
+ console.log(`Harnesses: ${harnesses.length} (${ready} ready)`);
76
+ }
77
+ }
78
+ }
@@ -6,6 +6,7 @@ const client_1 = require("../lib/client");
6
6
  const output_1 = require("../lib/output");
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
+ const MIGRATION_REGEX = /^(\d{14})_([a-z0-9_]+)\.sql$/;
9
10
  async function handleDb(subcommand, positionals, flags, context) {
10
11
  const jsonOutput = Boolean(flags.json);
11
12
  switch (subcommand) {
@@ -15,11 +16,21 @@ async function handleDb(subcommand, positionals, flags, context) {
15
16
  return handleRls(positionals, flags, context, jsonOutput);
16
17
  case 'sql':
17
18
  return handleSql(positionals, flags, context, jsonOutput);
19
+ case 'migrate':
20
+ return handleMigrate(positionals, flags, context, jsonOutput);
21
+ case 'migrations':
22
+ return handleMigrations(positionals, flags, context, jsonOutput);
23
+ case 'new':
24
+ return handleNew(positionals, flags);
18
25
  default:
19
- throw new Error('Usage: eve db <schema|rls|sql> --env <name> [options]\n' +
20
- ' schema --env <name> - show DB schema info\n' +
21
- ' rls --env <name> - show RLS policies and tables\n' +
22
- ' sql --env <name> --sql <stmt> [--write] - run parameterized SQL');
26
+ throw new Error('Usage: eve db <command> [options]\n\n' +
27
+ 'Commands:\n' +
28
+ ' schema --env <name> Show DB schema info\n' +
29
+ ' rls --env <name> Show RLS policies and tables\n' +
30
+ ' sql --env <name> --sql <stmt> Run parameterized SQL\n' +
31
+ ' migrate --env <name> [--path <dir>] Apply pending migrations\n' +
32
+ ' migrations --env <name> List applied migrations\n' +
33
+ ' new <description> Create new migration file');
23
34
  }
24
35
  }
25
36
  async function handleSchema(positionals, flags, context, jsonOutput) {
@@ -79,3 +90,115 @@ function parseJson(value, label) {
79
90
  throw new Error(`${label} must be valid JSON: ${error.message}`);
80
91
  }
81
92
  }
93
+ async function handleMigrate(positionals, flags, context, jsonOutput) {
94
+ const { projectId, envName } = resolveProjectEnv(positionals, flags, context);
95
+ const migrationsPath = (0, args_1.getStringFlag)(flags, ['path']) ?? 'db/migrations';
96
+ const fullPath = (0, node_path_1.resolve)(migrationsPath);
97
+ if (!(0, node_fs_1.existsSync)(fullPath)) {
98
+ throw new Error(`Migrations directory not found: ${fullPath}`);
99
+ }
100
+ // Read and validate migration files
101
+ const files = (0, node_fs_1.readdirSync)(fullPath)
102
+ .filter((f) => f.endsWith('.sql'))
103
+ .sort();
104
+ if (files.length === 0) {
105
+ console.log('No migration files found');
106
+ return;
107
+ }
108
+ // Validate filenames
109
+ for (const file of files) {
110
+ if (!MIGRATION_REGEX.test(file)) {
111
+ throw new Error(`Invalid migration filename: ${file}\n` +
112
+ `Expected format: YYYYMMDDHHmmss_description.sql\n` +
113
+ `Example: 20260128100000_create_users.sql`);
114
+ }
115
+ }
116
+ // Read file contents
117
+ const migrations = files.map((file) => ({
118
+ name: file,
119
+ sql: (0, node_fs_1.readFileSync)((0, node_path_1.join)(fullPath, file), 'utf-8'),
120
+ }));
121
+ console.log(`Found ${migrations.length} migration files`);
122
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/db/migrate`, {
123
+ method: 'POST',
124
+ body: { migrations },
125
+ });
126
+ if (jsonOutput) {
127
+ (0, output_1.outputJson)(response, jsonOutput);
128
+ }
129
+ else {
130
+ const applied = response.applied ?? [];
131
+ if (applied.length === 0) {
132
+ console.log('No new migrations to apply');
133
+ }
134
+ else {
135
+ console.log(`Applied ${applied.length} migrations:`);
136
+ for (const m of applied) {
137
+ console.log(` ✓ ${m.name}`);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ async function handleMigrations(positionals, flags, context, jsonOutput) {
143
+ const { projectId, envName } = resolveProjectEnv(positionals, flags, context);
144
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/db/migrations`);
145
+ if (jsonOutput) {
146
+ (0, output_1.outputJson)(response, jsonOutput);
147
+ }
148
+ else {
149
+ const migrations = response.migrations ?? [];
150
+ if (migrations.length === 0) {
151
+ console.log('No migrations have been applied');
152
+ }
153
+ else {
154
+ console.log(`Applied migrations (${migrations.length}):\n`);
155
+ console.log(' Name Applied At');
156
+ console.log(' ' + '-'.repeat(70));
157
+ for (const m of migrations) {
158
+ const date = new Date(m.applied_at).toLocaleString();
159
+ console.log(` ${m.name.padEnd(40)} ${date}`);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ function handleNew(positionals, flags) {
165
+ const description = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
166
+ if (!description) {
167
+ throw new Error('Usage: eve db new <description>\nExample: eve db new create_users');
168
+ }
169
+ // Validate description format
170
+ const normalizedDescription = description.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
171
+ if (!normalizedDescription || !/^[a-z0-9_]+$/.test(normalizedDescription)) {
172
+ throw new Error('Description must contain only lowercase letters, numbers, and underscores');
173
+ }
174
+ // Generate timestamp (YYYYMMDDHHmmss in UTC)
175
+ const now = new Date();
176
+ const timestamp = [
177
+ now.getUTCFullYear(),
178
+ String(now.getUTCMonth() + 1).padStart(2, '0'),
179
+ String(now.getUTCDate()).padStart(2, '0'),
180
+ String(now.getUTCHours()).padStart(2, '0'),
181
+ String(now.getUTCMinutes()).padStart(2, '0'),
182
+ String(now.getUTCSeconds()).padStart(2, '0'),
183
+ ].join('');
184
+ const filename = `${timestamp}_${normalizedDescription}.sql`;
185
+ const migrationsPath = (0, args_1.getStringFlag)(flags, ['path']) ?? 'db/migrations';
186
+ const fullPath = (0, node_path_1.resolve)(migrationsPath);
187
+ // Create directory if it doesn't exist
188
+ if (!(0, node_fs_1.existsSync)(fullPath)) {
189
+ (0, node_fs_1.mkdirSync)(fullPath, { recursive: true });
190
+ }
191
+ const filePath = (0, node_path_1.join)(fullPath, filename);
192
+ // Check if file already exists
193
+ if ((0, node_fs_1.existsSync)(filePath)) {
194
+ throw new Error(`Migration file already exists: ${filePath}`);
195
+ }
196
+ const template = `-- Migration: ${normalizedDescription}
197
+ -- Created: ${now.toISOString()}
198
+
199
+ -- Write your SQL migration here
200
+
201
+ `;
202
+ (0, node_fs_1.writeFileSync)(filePath, template);
203
+ console.log(`Created: ${filePath}`);
204
+ }
@@ -19,12 +19,15 @@ async function handleEnv(subcommand, positionals, flags, context) {
19
19
  return handleCreate(positionals, flags, context, json);
20
20
  case 'deploy':
21
21
  return handleDeploy(positionals, flags, context, json);
22
+ case 'logs':
23
+ return handleLogs(positionals, flags, context, json);
22
24
  default:
23
- throw new Error('Usage: eve env <list|show|create|deploy>\n' +
25
+ throw new Error('Usage: eve env <list|show|create|deploy|logs>\n' +
24
26
  ' list [project] - list environments for a project\n' +
25
27
  ' show <project> <name> - show details of an environment\n' +
26
28
  ' create <name> --type=<type> [options] - create an environment\n' +
27
- ' deploy <project> <name> [--tag=<tag>] [--release-tag=<tag>] - deploy to an environment');
29
+ ' deploy <project> <name> [--tag=<tag>] [--release-tag=<tag>] - deploy to an environment\n' +
30
+ ' logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>] - get service logs');
28
31
  }
29
32
  }
30
33
  // ============================================================================
@@ -202,6 +205,35 @@ async function handleDeploy(positionals, flags, context, json) {
202
205
  console.log(` Namespace: ${response.environment.namespace || '(none)'}`);
203
206
  }
204
207
  }
208
+ /**
209
+ * eve env logs <project> <env> <service>
210
+ * Fetch logs for a service in an environment (k8s-only)
211
+ */
212
+ async function handleLogs(positionals, flags, context, json) {
213
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
214
+ const envName = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['env', 'name']);
215
+ const service = positionals[2] ?? (0, args_1.getStringFlag)(flags, ['service']);
216
+ if (!projectId || !envName || !service) {
217
+ throw new Error('Usage: eve env logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>]');
218
+ }
219
+ const query = buildQuery({
220
+ since: (0, args_1.getStringFlag)(flags, ['since']),
221
+ tail: (0, args_1.getStringFlag)(flags, ['tail']),
222
+ grep: (0, args_1.getStringFlag)(flags, ['grep']),
223
+ });
224
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/services/${service}/logs${query}`);
225
+ if (json) {
226
+ (0, output_1.outputJson)(response, json);
227
+ return;
228
+ }
229
+ if (!response.logs.length) {
230
+ console.log('No logs found.');
231
+ return;
232
+ }
233
+ for (const entry of response.logs) {
234
+ console.log(`[${entry.timestamp}] ${entry.line}`);
235
+ }
236
+ }
205
237
  // ============================================================================
206
238
  // Helper Functions
207
239
  // ============================================================================
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleHarness = handleHarness;
4
+ const args_1 = require("../lib/args");
4
5
  const client_1 = require("../lib/client");
5
6
  const output_1 = require("../lib/output");
6
7
  async function handleHarness(subcommand, positionals, flags, context) {
7
8
  const json = Boolean(flags.json);
9
+ const includeCapabilities = (0, args_1.getBooleanFlag)(flags, ['capabilities']) ?? false;
8
10
  switch (subcommand) {
9
11
  case 'list': {
10
12
  const response = await (0, client_1.requestJson)(context, '/harnesses');
@@ -12,7 +14,7 @@ async function handleHarness(subcommand, positionals, flags, context) {
12
14
  (0, output_1.outputJson)(response, json);
13
15
  return;
14
16
  }
15
- renderHarnessList(response.data);
17
+ renderHarnessList(response.data, includeCapabilities);
16
18
  return;
17
19
  }
18
20
  case 'get': {
@@ -31,7 +33,11 @@ async function handleHarness(subcommand, positionals, flags, context) {
31
33
  throw new Error('Usage: eve harness <list|get>');
32
34
  }
33
35
  }
34
- function renderHarnessList(harnesses) {
36
+ function renderHarnessList(harnesses, includeCapabilities) {
37
+ if (includeCapabilities) {
38
+ renderHarnessCapabilities(harnesses);
39
+ return;
40
+ }
35
41
  const rows = harnesses.map((harness) => {
36
42
  const variants = harness.variants
37
43
  .map((variant) => variant.name)
@@ -79,6 +85,48 @@ function renderHarnessDetail(harness) {
79
85
  for (const instruction of harness.auth.instructions) {
80
86
  console.log(` - ${instruction}`);
81
87
  }
88
+ if (harness.capabilities) {
89
+ renderCapabilitiesBlock(harness.capabilities);
90
+ }
91
+ }
92
+ function renderHarnessCapabilities(harnesses) {
93
+ for (const harness of harnesses) {
94
+ console.log(`Harness: ${formatHarnessLabel(harness)}`);
95
+ console.log(` Auth: ${harness.auth.available ? 'ready' : 'missing'} (${harness.auth.reason})`);
96
+ const variants = harness.variants.length
97
+ ? harness.variants.map((variant) => variant.name).join(', ')
98
+ : 'default';
99
+ console.log(` Variants: ${variants}`);
100
+ if (harness.capabilities) {
101
+ renderCapabilitiesBlock(harness.capabilities, ' ');
102
+ }
103
+ else {
104
+ console.log(' Capabilities: (not reported)');
105
+ }
106
+ console.log('');
107
+ }
108
+ }
109
+ function renderCapabilitiesBlock(capabilities, prefix = '') {
110
+ const modelSupport = capabilities.supports_model ? 'supported' : 'not supported';
111
+ const examples = capabilities.model_examples?.length
112
+ ? ` (examples: ${capabilities.model_examples.join(', ')})`
113
+ : '';
114
+ const modelNotes = capabilities.model_notes ? ` — ${capabilities.model_notes}` : '';
115
+ console.log(`${prefix}Model: ${modelSupport}${examples}${modelNotes}`);
116
+ const reasoning = capabilities.reasoning;
117
+ if (!reasoning) {
118
+ console.log(`${prefix}Reasoning: (unknown)`);
119
+ return;
120
+ }
121
+ if (!reasoning.supported) {
122
+ const notes = reasoning.notes ? ` — ${reasoning.notes}` : '';
123
+ console.log(`${prefix}Reasoning: not supported${notes}`);
124
+ return;
125
+ }
126
+ const levels = reasoning.levels?.length ? reasoning.levels.join(', ') : 'unspecified';
127
+ const mode = reasoning.mode ? ` (${reasoning.mode})` : '';
128
+ const notes = reasoning.notes ? ` — ${reasoning.notes}` : '';
129
+ console.log(`${prefix}Reasoning: ${levels}${mode}${notes}`);
82
130
  }
83
131
  function formatHarnessLabel(harness) {
84
132
  if (!harness.aliases?.length)
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleInit = handleInit;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const os = __importStar(require("node:os"));
40
+ const node_child_process_1 = require("node:child_process");
41
+ const args_1 = require("../lib/args");
42
+ // ============================================================================
43
+ // Constants
44
+ // ============================================================================
45
+ const DEFAULT_TEMPLATE = 'https://github.com/incept5/eve-horizon-starter';
46
+ const DEFAULT_BRANCH = 'main';
47
+ // ============================================================================
48
+ // Main Handler
49
+ // ============================================================================
50
+ /**
51
+ * eve init [directory] [--template <url>] [--branch <branch>]
52
+ *
53
+ * Initialize a new Eve Horizon project from a template.
54
+ * Downloads the template, strips git history, and sets up a fresh project.
55
+ */
56
+ async function handleInit(positionals, flags) {
57
+ const targetDir = positionals[0] || '.';
58
+ const template = (0, args_1.getStringFlag)(flags, ['template', 't']) || DEFAULT_TEMPLATE;
59
+ const branch = (0, args_1.getStringFlag)(flags, ['branch', 'b']) || DEFAULT_BRANCH;
60
+ const skipSkills = Boolean(flags['skip-skills']);
61
+ // Resolve target directory
62
+ const resolvedTarget = path.resolve(targetDir);
63
+ const targetName = path.basename(resolvedTarget);
64
+ const isCurrentDir = targetDir === '.';
65
+ // Validate target directory
66
+ if (isCurrentDir) {
67
+ // Current directory must be empty or not exist
68
+ if (fs.existsSync(resolvedTarget)) {
69
+ const entries = fs.readdirSync(resolvedTarget);
70
+ // Allow hidden files like .git, but fail if there's substantial content
71
+ const nonHiddenEntries = entries.filter(e => !e.startsWith('.'));
72
+ if (nonHiddenEntries.length > 0) {
73
+ throw new Error(`Current directory is not empty. Remove existing files or specify a new directory name:\n` +
74
+ ` eve init my-project`);
75
+ }
76
+ }
77
+ }
78
+ else {
79
+ // Named directory must not exist or be empty
80
+ if (fs.existsSync(resolvedTarget)) {
81
+ const entries = fs.readdirSync(resolvedTarget);
82
+ if (entries.length > 0) {
83
+ throw new Error(`Directory '${targetDir}' already exists and is not empty.`);
84
+ }
85
+ }
86
+ }
87
+ console.log(`Initializing Eve Horizon project${isCurrentDir ? '' : ` in '${targetName}'`}...`);
88
+ console.log(`Template: ${template}`);
89
+ console.log(`Branch: ${branch}`);
90
+ console.log('');
91
+ // Create temp directory for clone
92
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eve-init-'));
93
+ try {
94
+ // Clone template
95
+ console.log('Downloading template...');
96
+ const cloneResult = (0, node_child_process_1.spawnSync)('git', ['clone', '--depth=1', `--branch=${branch}`, template, tempDir], {
97
+ encoding: 'utf8',
98
+ stdio: ['pipe', 'pipe', 'pipe'],
99
+ });
100
+ if (cloneResult.status !== 0) {
101
+ throw new Error(`Failed to clone template:\n${cloneResult.stderr || cloneResult.stdout}`);
102
+ }
103
+ // Remove .git from cloned template
104
+ const gitDir = path.join(tempDir, '.git');
105
+ if (fs.existsSync(gitDir)) {
106
+ fs.rmSync(gitDir, { recursive: true, force: true });
107
+ }
108
+ // Create target directory if needed
109
+ if (!fs.existsSync(resolvedTarget)) {
110
+ fs.mkdirSync(resolvedTarget, { recursive: true });
111
+ }
112
+ // Copy template contents to target
113
+ console.log('Setting up project...');
114
+ copyDirRecursive(tempDir, resolvedTarget);
115
+ // Initialize git
116
+ console.log('Initializing git repository...');
117
+ (0, node_child_process_1.execSync)('git init', { cwd: resolvedTarget, stdio: 'pipe' });
118
+ (0, node_child_process_1.execSync)('git add -A', { cwd: resolvedTarget, stdio: 'pipe' });
119
+ (0, node_child_process_1.execSync)('git commit -m "Initial commit from eve-horizon-starter"', {
120
+ cwd: resolvedTarget,
121
+ stdio: 'pipe',
122
+ });
123
+ // Install skills
124
+ if (!skipSkills) {
125
+ console.log('');
126
+ console.log('Installing skills...');
127
+ await installSkills(resolvedTarget);
128
+ }
129
+ // Success message
130
+ console.log('');
131
+ console.log('Project initialized successfully!');
132
+ console.log('');
133
+ console.log('Next steps:');
134
+ if (!isCurrentDir) {
135
+ console.log(` 1. cd ${targetName}`);
136
+ console.log(' 2. Start your AI coding agent (e.g., claude, cursor)');
137
+ console.log(' 3. Ask: "Run the eve-new-project-setup skill"');
138
+ }
139
+ else {
140
+ console.log(' 1. Start your AI coding agent (e.g., claude, cursor)');
141
+ console.log(' 2. Ask: "Run the eve-new-project-setup skill"');
142
+ }
143
+ console.log('');
144
+ console.log('The setup skill will:');
145
+ console.log(' - Install the Eve CLI if needed');
146
+ console.log(' - Configure your profile and authentication');
147
+ console.log(' - Set up your project manifest');
148
+ console.log(' - Help you set up your own Git remote');
149
+ }
150
+ finally {
151
+ // Clean up temp directory
152
+ if (fs.existsSync(tempDir)) {
153
+ fs.rmSync(tempDir, { recursive: true, force: true });
154
+ }
155
+ }
156
+ }
157
+ // ============================================================================
158
+ // Helper Functions
159
+ // ============================================================================
160
+ /**
161
+ * Recursively copy directory contents
162
+ */
163
+ function copyDirRecursive(src, dest) {
164
+ const entries = fs.readdirSync(src, { withFileTypes: true });
165
+ for (const entry of entries) {
166
+ const srcPath = path.join(src, entry.name);
167
+ const destPath = path.join(dest, entry.name);
168
+ if (entry.isDirectory()) {
169
+ if (!fs.existsSync(destPath)) {
170
+ fs.mkdirSync(destPath, { recursive: true });
171
+ }
172
+ copyDirRecursive(srcPath, destPath);
173
+ }
174
+ else if (entry.isSymbolicLink()) {
175
+ const linkTarget = fs.readlinkSync(srcPath);
176
+ if (!fs.existsSync(destPath)) {
177
+ fs.symlinkSync(linkTarget, destPath);
178
+ }
179
+ }
180
+ else {
181
+ fs.copyFileSync(srcPath, destPath);
182
+ }
183
+ }
184
+ }
185
+ /**
186
+ * Install skills from skills.txt
187
+ * This reuses the logic from the skills command
188
+ */
189
+ async function installSkills(projectRoot) {
190
+ const skillsTxt = path.join(projectRoot, 'skills.txt');
191
+ if (!fs.existsSync(skillsTxt)) {
192
+ console.log('No skills.txt found, skipping skill installation');
193
+ return;
194
+ }
195
+ // Check if openskills is available
196
+ const result = (0, node_child_process_1.spawnSync)('which', ['openskills'], { encoding: 'utf8' });
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');
200
+ console.log(' eve skills install');
201
+ return;
202
+ }
203
+ // Import and call the skills install logic
204
+ // We directly call the openskills commands here for simplicity
205
+ try {
206
+ const content = fs.readFileSync(skillsTxt, 'utf-8');
207
+ const lines = content.split('\n')
208
+ .map(line => line.split('#')[0].trim())
209
+ .filter(line => line.length > 0);
210
+ for (const source of lines) {
211
+ 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}`);
221
+ }
222
+ }
223
+ // Ensure symlink
224
+ 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
+ // Commit skill changes
237
+ try {
238
+ (0, node_child_process_1.execSync)('git add -A', { cwd: projectRoot, stdio: 'pipe' });
239
+ const hasChanges = (0, node_child_process_1.spawnSync)('git', ['diff', '--cached', '--quiet'], {
240
+ cwd: projectRoot,
241
+ encoding: 'utf8',
242
+ });
243
+ if (hasChanges.status !== 0) {
244
+ (0, node_child_process_1.execSync)('git commit -m "chore: install skills from skills.txt"', {
245
+ cwd: projectRoot,
246
+ stdio: 'pipe',
247
+ });
248
+ }
249
+ }
250
+ catch {
251
+ // Ignore commit failures
252
+ }
253
+ }
254
+ catch (err) {
255
+ console.log('Warning: Failed to install some skills');
256
+ }
257
+ }
258
+ /**
259
+ * Ensure .claude/skills symlink points to .agent/skills
260
+ */
261
+ function ensureSkillsSymlink(projectRoot) {
262
+ const agentSkills = path.join(projectRoot, '.agent', 'skills');
263
+ const claudeDir = path.join(projectRoot, '.claude');
264
+ const claudeSkills = path.join(claudeDir, 'skills');
265
+ if (!fs.existsSync(agentSkills)) {
266
+ fs.mkdirSync(agentSkills, { recursive: true });
267
+ }
268
+ if (!fs.existsSync(claudeDir)) {
269
+ fs.mkdirSync(claudeDir, { recursive: true });
270
+ }
271
+ if (fs.existsSync(claudeSkills)) {
272
+ const stat = fs.lstatSync(claudeSkills);
273
+ if (stat.isSymbolicLink()) {
274
+ const target = fs.readlinkSync(claudeSkills);
275
+ if (target === '../.agent/skills' || target === agentSkills) {
276
+ return;
277
+ }
278
+ fs.unlinkSync(claudeSkills);
279
+ }
280
+ else {
281
+ return;
282
+ }
283
+ }
284
+ try {
285
+ fs.symlinkSync('../.agent/skills', claudeSkills);
286
+ }
287
+ catch {
288
+ // Ignore symlink failures
289
+ }
290
+ }
@@ -115,6 +115,10 @@ async function handleCreate(flags, context, json) {
115
115
  const dueAt = (0, args_1.getStringFlag)(flags, ['due-at']);
116
116
  // Scheduling hints
117
117
  const harness = (0, args_1.getStringFlag)(flags, ['harness']);
118
+ const profile = (0, args_1.getStringFlag)(flags, ['profile']);
119
+ const variant = (0, args_1.getStringFlag)(flags, ['variant']);
120
+ const model = (0, args_1.getStringFlag)(flags, ['model']);
121
+ const reasoning = (0, args_1.getStringFlag)(flags, ['reasoning']);
118
122
  const workerType = (0, args_1.getStringFlag)(flags, ['worker-type']);
119
123
  const permission = (0, args_1.getStringFlag)(flags, ['permission']);
120
124
  const timeout = (0, args_1.getStringFlag)(flags, ['timeout']);
@@ -171,10 +175,25 @@ async function handleCreate(flags, context, json) {
171
175
  if (dueAt) {
172
176
  body.due_at = dueAt;
173
177
  }
178
+ if (harness) {
179
+ body.harness = harness;
180
+ }
181
+ if (profile) {
182
+ body.harness_profile = profile;
183
+ }
184
+ if (variant || model || reasoning) {
185
+ const allowedEfforts = new Set(['low', 'medium', 'high', 'x-high']);
186
+ if (reasoning && !allowedEfforts.has(reasoning)) {
187
+ throw new Error('--reasoning must be one of: low, medium, high, x-high');
188
+ }
189
+ body.harness_options = {
190
+ ...(variant ? { variant } : {}),
191
+ ...(model ? { model } : {}),
192
+ ...(reasoning ? { reasoning_effort: reasoning } : {}),
193
+ };
194
+ }
174
195
  // Build hints object if any hint flags are provided
175
196
  const hints = {};
176
- if (harness)
177
- hints.harness = harness;
178
197
  if (workerType)
179
198
  hints.worker_type = workerType;
180
199
  if (permission)
@@ -95,6 +95,9 @@ async function handleProject(subcommand, positionals, flags, context) {
95
95
  case 'sync': {
96
96
  const dir = typeof flags.dir === 'string' ? flags.dir : process.cwd();
97
97
  const manifestPath = (0, node_path_1.join)(dir, '.eve', 'manifest.yaml');
98
+ const validateSecretsFlag = flags['validate-secrets'] ?? flags.validate_secrets;
99
+ const validateSecrets = (0, args_1.toBoolean)(validateSecretsFlag) ?? false;
100
+ const strict = (0, args_1.toBoolean)(flags.strict) ?? false;
98
101
  // Read manifest file
99
102
  let yaml;
100
103
  try {
@@ -123,7 +126,13 @@ async function handleProject(subcommand, positionals, flags, context) {
123
126
  const projectIdFromManifest = projectMatch[1];
124
127
  const response = await (0, client_1.requestJson)(context, `/projects/${projectIdFromManifest}/manifest`, {
125
128
  method: 'POST',
126
- body: { yaml, git_sha: gitSha, branch },
129
+ body: {
130
+ yaml,
131
+ git_sha: gitSha,
132
+ branch,
133
+ validate_secrets: validateSecrets || strict,
134
+ strict,
135
+ },
127
136
  });
128
137
  if (json) {
129
138
  (0, output_1.outputJson)(response, json);
@@ -141,6 +150,12 @@ async function handleProject(subcommand, positionals, flags, context) {
141
150
  console.log(` ${key}: ${JSON.stringify(value)}`);
142
151
  });
143
152
  }
153
+ if (response.warnings && response.warnings.length > 0) {
154
+ console.log('\nWarnings:');
155
+ response.warnings.forEach((warning) => {
156
+ console.log(` - ${warning}`);
157
+ });
158
+ }
144
159
  }
145
160
  return;
146
161
  }
@@ -148,7 +163,13 @@ async function handleProject(subcommand, positionals, flags, context) {
148
163
  }
149
164
  const response = await (0, client_1.requestJson)(context, `/projects/${projectIdRaw}/manifest`, {
150
165
  method: 'POST',
151
- body: { yaml, git_sha: gitSha, branch },
166
+ body: {
167
+ yaml,
168
+ git_sha: gitSha,
169
+ branch,
170
+ validate_secrets: validateSecrets || strict,
171
+ strict,
172
+ },
152
173
  });
153
174
  if (json) {
154
175
  (0, output_1.outputJson)(response, json);
@@ -166,6 +187,12 @@ async function handleProject(subcommand, positionals, flags, context) {
166
187
  console.log(` ${key}: ${JSON.stringify(value)}`);
167
188
  });
168
189
  }
190
+ if (response.warnings && response.warnings.length > 0) {
191
+ console.log('\nWarnings:');
192
+ response.warnings.forEach((warning) => {
193
+ console.log(` - ${warning}`);
194
+ });
195
+ }
169
196
  }
170
197
  return;
171
198
  }
@@ -60,22 +60,79 @@ async function handleSecrets(subcommand, positionals, flags, context) {
60
60
  const filePath = typeof flags.file === 'string' ? flags.file : '.env';
61
61
  const fileContents = (0, node_fs_1.readFileSync)(filePath, 'utf8');
62
62
  const entries = parseEnvFile(fileContents);
63
- const filtered = entries.filter(([key]) => key.startsWith('EVE_SECRET_'));
64
- if (filtered.length === 0) {
65
- throw new Error(`No EVE_SECRET_* entries found in ${filePath}`);
63
+ if (entries.length === 0) {
64
+ throw new Error(`No entries found in ${filePath}`);
66
65
  }
67
- for (const [rawKey, rawValue] of filtered) {
68
- const key = rawKey.slice('EVE_SECRET_'.length);
66
+ for (const [key, value] of entries) {
69
67
  await (0, client_1.requestJson)(context, `${scopeBasePath(scope)}`, {
70
68
  method: 'POST',
71
- body: { key, value: rawValue },
69
+ body: { key, value },
70
+ });
71
+ }
72
+ (0, output_1.outputJson)({ imported: entries.length }, json, `✓ Imported ${entries.length} secrets`);
73
+ return;
74
+ }
75
+ case 'validate': {
76
+ const projectId = resolveProjectScope(flags, context);
77
+ const keys = parseKeys(positionals, flags);
78
+ const body = {};
79
+ if (keys.length > 0)
80
+ body.keys = keys;
81
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/secrets/validate`, {
82
+ method: 'POST',
83
+ body,
84
+ });
85
+ if (json) {
86
+ (0, output_1.outputJson)(response, json);
87
+ }
88
+ else if (response.missing.length === 0) {
89
+ console.log('✓ All required secrets are present');
90
+ }
91
+ else {
92
+ console.log('Missing secrets:');
93
+ response.missing.forEach((item) => {
94
+ console.log(`- ${item.key}`);
95
+ item.hints.forEach((hint) => console.log(` ${hint}`));
96
+ });
97
+ }
98
+ return;
99
+ }
100
+ case 'ensure': {
101
+ const projectId = resolveProjectScope(flags, context);
102
+ const keys = parseKeys(positionals, flags);
103
+ if (keys.length === 0) {
104
+ throw new Error('Usage: eve secrets ensure --project <id> --keys <key1,key2>');
105
+ }
106
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/secrets/ensure`, {
107
+ method: 'POST',
108
+ body: { keys },
109
+ });
110
+ (0, output_1.outputJson)(response, json, `✓ Secrets ensured (${response.created.length} created)`);
111
+ return;
112
+ }
113
+ case 'export': {
114
+ const projectId = resolveProjectScope(flags, context);
115
+ const keys = parseKeys(positionals, flags);
116
+ if (keys.length === 0) {
117
+ throw new Error('Usage: eve secrets export --project <id> --keys <key1,key2>');
118
+ }
119
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/secrets/export`, {
120
+ method: 'POST',
121
+ body: { keys },
122
+ });
123
+ if (json) {
124
+ (0, output_1.outputJson)(response, json);
125
+ }
126
+ else {
127
+ console.log('Exported secrets (handle carefully):');
128
+ response.data.forEach((item) => {
129
+ console.log(`${item.key}=${item.value}`);
72
130
  });
73
131
  }
74
- (0, output_1.outputJson)({ imported: filtered.length }, json, `✓ Imported ${filtered.length} secrets`);
75
132
  return;
76
133
  }
77
134
  default:
78
- throw new Error('Usage: eve secrets <set|list|show|delete|import>');
135
+ throw new Error('Usage: eve secrets <set|list|show|delete|import|validate|ensure|export>');
79
136
  }
80
137
  }
81
138
  function resolveScope(flags, context) {
@@ -117,6 +174,13 @@ function scopeBasePath(scope) {
117
174
  return `/users/${scope.scopeId}/secrets`;
118
175
  }
119
176
  }
177
+ function resolveProjectScope(flags, context) {
178
+ const scope = resolveScope(flags, context);
179
+ if (scope.scopeType !== 'project') {
180
+ throw new Error('This command requires --project (or a project default profile).');
181
+ }
182
+ return scope.scopeId;
183
+ }
120
184
  function buildQuery(params) {
121
185
  const search = new URLSearchParams();
122
186
  Object.entries(params).forEach(([key, value]) => {
@@ -145,3 +209,10 @@ function parseEnvFile(contents) {
145
209
  }
146
210
  return entries;
147
211
  }
212
+ function parseKeys(positionals, flags) {
213
+ const positionalKeys = positionals.filter((value) => typeof value === 'string' && value.length > 0);
214
+ const flagKeys = typeof flags.keys === 'string'
215
+ ? flags.keys.split(',').map((value) => value.trim()).filter(Boolean)
216
+ : [];
217
+ return Array.from(new Set([...positionalKeys, ...flagKeys]));
218
+ }
@@ -32,8 +32,10 @@ async function handleSystem(subcommand, positionals, flags, context) {
32
32
  return handleEvents(flags, context, json);
33
33
  case 'config':
34
34
  return handleConfig(context, json);
35
+ case 'settings':
36
+ return handleSettings(positionals, flags, context, json);
35
37
  default:
36
- throw new Error('Usage: eve system <status|health|jobs|envs|logs|pods|events|config>');
38
+ throw new Error('Usage: eve system <status|health|jobs|envs|logs|pods|events|config|settings>');
37
39
  }
38
40
  }
39
41
  // ============================================================================
@@ -316,6 +318,75 @@ async function handleConfig(context, json) {
316
318
  response.deployments.forEach((deployment) => console.log(` - ${deployment}`));
317
319
  }
318
320
  }
321
+ /**
322
+ * eve system settings [get <key>] [set <key> <value>]
323
+ * Admin only: Get or set system settings
324
+ */
325
+ async function handleSettings(positionals, flags, context, json) {
326
+ const action = positionals[0];
327
+ if (action === 'set') {
328
+ const key = positionals[1];
329
+ const value = positionals[2];
330
+ if (!key || !value) {
331
+ throw new Error('Usage: eve system settings set <key> <value>');
332
+ }
333
+ const body = { value };
334
+ const response = await (0, client_1.requestJson)(context, `/system/settings/${key}`, {
335
+ method: 'PUT',
336
+ body: JSON.stringify(body),
337
+ });
338
+ if (json) {
339
+ (0, output_1.outputJson)(response, json);
340
+ }
341
+ else {
342
+ console.log(`Setting '${key}' updated:`);
343
+ console.log(` Value: ${response.value}`);
344
+ console.log(` Updated: ${response.updated_at}`);
345
+ console.log(` By: ${response.updated_by}`);
346
+ }
347
+ return;
348
+ }
349
+ // Default: get all or get specific
350
+ const key = positionals[0];
351
+ if (key) {
352
+ const response = await (0, client_1.requestJson)(context, `/system/settings/${key}`);
353
+ if (json) {
354
+ (0, output_1.outputJson)(response, json);
355
+ }
356
+ else {
357
+ console.log(`Setting: ${response.key}`);
358
+ console.log(` Value: ${response.value}`);
359
+ if (response.description) {
360
+ console.log(` Description: ${response.description}`);
361
+ }
362
+ console.log(` Updated: ${response.updated_at}`);
363
+ console.log(` By: ${response.updated_by}`);
364
+ }
365
+ }
366
+ else {
367
+ const response = await (0, client_1.requestJson)(context, '/system/settings');
368
+ if (json) {
369
+ (0, output_1.outputJson)(response, json);
370
+ }
371
+ else {
372
+ if (response.length === 0) {
373
+ console.log('No system settings found.');
374
+ return;
375
+ }
376
+ console.log('System Settings:');
377
+ console.log('');
378
+ for (const setting of response) {
379
+ console.log(` ${setting.key}:`);
380
+ console.log(` Value: ${setting.value}`);
381
+ if (setting.description) {
382
+ console.log(` Description: ${setting.description}`);
383
+ }
384
+ console.log(` Updated: ${setting.updated_at} by ${setting.updated_by}`);
385
+ console.log('');
386
+ }
387
+ }
388
+ }
389
+ }
319
390
  // ============================================================================
320
391
  // Helper Functions
321
392
  // ============================================================================
package/dist/index.js CHANGED
@@ -21,6 +21,8 @@ const db_1 = require("./commands/db");
21
21
  const event_1 = require("./commands/event");
22
22
  const skills_1 = require("./commands/skills");
23
23
  const admin_1 = require("./commands/admin");
24
+ const agents_1 = require("./commands/agents");
25
+ const init_1 = require("./commands/init");
24
26
  async function main() {
25
27
  const { flags, positionals } = (0, args_1.parseArgs)(process.argv.slice(2));
26
28
  const command = positionals[0];
@@ -91,9 +93,16 @@ async function main() {
91
93
  case 'skills':
92
94
  await (0, skills_1.handleSkills)(subcommand, rest, flags);
93
95
  return;
96
+ case 'agents':
97
+ await (0, agents_1.handleAgents)(subcommand, rest, flags, context);
98
+ return;
94
99
  case 'admin':
95
100
  await (0, admin_1.handleAdmin)(subcommand, rest, flags, context);
96
101
  return;
102
+ case 'init':
103
+ // init doesn't use subcommands - first positional is directory name
104
+ await (0, init_1.handleInit)(subcommand ? [subcommand, ...rest] : rest, flags);
105
+ return;
97
106
  default:
98
107
  (0, help_1.showMainHelp)();
99
108
  }
package/dist/lib/help.js CHANGED
@@ -89,6 +89,8 @@ exports.HELP = {
89
89
  options: [
90
90
  '<project> Project ID (uses profile default if omitted)',
91
91
  '--path <path> Path to manifest (default: .eve/manifest.yaml)',
92
+ '--validate-secrets Validate manifest required secrets',
93
+ '--strict Fail sync if required secrets are missing',
92
94
  ],
93
95
  examples: [
94
96
  'eve project sync',
@@ -125,16 +127,44 @@ exports.HELP = {
125
127
  usage: 'eve secrets delete <key> [--project <id>|--org <id>|--user <id>]',
126
128
  },
127
129
  import: {
128
- description: 'Import EVE_SECRET_* entries from an env file',
130
+ description: 'Import env entries from an env file',
129
131
  usage: 'eve secrets import [--file <path>] [--project <id>|--org <id>|--user <id>]',
130
132
  options: [
131
133
  '--file <path> Defaults to .env',
132
134
  ],
133
135
  },
136
+ validate: {
137
+ description: 'Validate manifest-required secrets for a project',
138
+ usage: 'eve secrets validate --project <id> [--keys <k1,k2>]',
139
+ options: [
140
+ '--project <id> Project ID (uses profile default)',
141
+ '--keys <k1,k2> Explicit keys to validate (default: latest manifest)',
142
+ ],
143
+ },
144
+ ensure: {
145
+ description: 'Ensure safe secrets exist at project scope',
146
+ usage: 'eve secrets ensure --project <id> --keys <k1,k2>',
147
+ options: [
148
+ '--project <id> Project ID (uses profile default)',
149
+ '--keys <k1,k2> Keys to ensure (allowlist only)',
150
+ ],
151
+ },
152
+ export: {
153
+ description: 'Export safe secrets for external configuration',
154
+ usage: 'eve secrets export --project <id> --keys <k1,k2> [--json]',
155
+ options: [
156
+ '--project <id> Project ID (uses profile default)',
157
+ '--keys <k1,k2> Keys to export (allowlist only)',
158
+ '--json JSON output',
159
+ ],
160
+ },
134
161
  },
135
162
  examples: [
136
163
  'eve secrets set GITHUB_TOKEN ghp_xxx --project proj_xxx --type github_token',
137
164
  'eve secrets show GITHUB_TOKEN --project proj_xxx',
165
+ 'eve secrets validate --project proj_xxx',
166
+ 'eve secrets ensure --project proj_xxx --keys GITHUB_WEBHOOK_SECRET',
167
+ 'eve secrets export --project proj_xxx --keys GITHUB_WEBHOOK_SECRET',
138
168
  ],
139
169
  },
140
170
  job: {
@@ -161,8 +191,14 @@ Jobs default to 'ready' phase, making them immediately schedulable.`,
161
191
  '--defer-until <date> Hide until date (ISO 8601)',
162
192
  '--due-at <date> Deadline (ISO 8601)',
163
193
  '',
194
+ 'Harness selection (top-level job fields):',
195
+ '--harness <name> Preferred harness, e.g., mclaude',
196
+ '--profile <name> Harness profile name',
197
+ '--variant <name> Harness variant preset',
198
+ '--model <name> Model override for harness',
199
+ '--reasoning <level> Reasoning effort: low|medium|high|x-high',
200
+ '',
164
201
  'Scheduling hints (used by scheduler when claiming):',
165
- '--harness <name> Preferred harness, e.g., mclaude:fast',
166
202
  '--worker-type <type> Worker type preference',
167
203
  '--permission <policy> Permission policy: default, auto_edit, yolo',
168
204
  '--timeout <seconds> Execution timeout',
@@ -318,7 +354,7 @@ Jobs default to 'ready' phase, making them immediately schedulable.`,
318
354
  usage: 'eve job claim <job-id> [--agent <id>] [--harness <name>]',
319
355
  options: [
320
356
  '--agent <id> Agent identifier (default: $EVE_AGENT_ID or cli-user)',
321
- '--harness <name> Harness to use (overrides hints)',
357
+ '--harness <name> Harness to use (overrides job harness)',
322
358
  '',
323
359
  'NOTE: This is typically called by the scheduler or by agents creating',
324
360
  'sub-jobs. For normal workflows, jobs are auto-scheduled when ready.',
@@ -408,7 +444,10 @@ Jobs default to 'ready' phase, making them immediately schedulable.`,
408
444
  subcommands: {
409
445
  list: {
410
446
  description: 'List available harnesses',
411
- usage: 'eve harness list',
447
+ usage: 'eve harness list [--capabilities]',
448
+ options: [
449
+ '--capabilities Show model/reasoning capability hints',
450
+ ],
412
451
  },
413
452
  get: {
414
453
  description: 'Get harness details and auth requirements',
@@ -418,6 +457,26 @@ Jobs default to 'ready' phase, making them immediately schedulable.`,
418
457
  },
419
458
  examples: ['eve harness list', 'eve harness get mclaude'],
420
459
  },
460
+ agents: {
461
+ description: 'Inspect agent policy config and harness capabilities for orchestration. Default profile: primary-orchestrator.',
462
+ usage: 'eve agents config [--path <dir>] [--no-harnesses]',
463
+ subcommands: {
464
+ config: {
465
+ description: 'Show agent policy (profiles/councils) and harness availability',
466
+ usage: 'eve agents config [--path <dir>] [--no-harnesses]',
467
+ options: [
468
+ '--path <dir> Repository root to inspect (default: cwd)',
469
+ '--no-harnesses Skip harness availability lookup',
470
+ ],
471
+ examples: [
472
+ 'eve agents config',
473
+ 'eve agents config --json',
474
+ 'eve agents config --path ../my-repo',
475
+ ],
476
+ },
477
+ },
478
+ examples: ['eve agents config --json'],
479
+ },
421
480
  profile: {
422
481
  description: `Manage CLI profiles. Profiles store defaults (API URL, org, project) so you don't
423
482
  have to specify them on every command. Useful when working with multiple environments.`,
@@ -608,8 +667,29 @@ for cloud deployments. Credentials are stored per-profile.`,
608
667
  'eve env deploy proj_xxx staging --release-tag v1.2.3',
609
668
  ],
610
669
  },
670
+ logs: {
671
+ description: 'Fetch logs for a service in an environment (k8s-only)',
672
+ usage: 'eve env logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>]',
673
+ options: [
674
+ '<project> Project ID or slug',
675
+ '<env> Environment name (staging, production, test)',
676
+ '<service> Service name from manifest',
677
+ '--since <seconds> Seconds since now (optional)',
678
+ '--tail <n> Tail line count (optional)',
679
+ '--grep <text> Filter lines containing text (optional)',
680
+ ],
681
+ examples: [
682
+ 'eve env logs proj_xxx staging api --tail 200',
683
+ 'eve env logs proj_xxx staging api --since 3600 --grep ERROR',
684
+ ],
685
+ },
611
686
  },
612
- examples: ['eve env list', 'eve env create test --type=persistent', 'eve env deploy proj_xxx staging'],
687
+ examples: [
688
+ 'eve env list',
689
+ 'eve env create test --type=persistent',
690
+ 'eve env deploy proj_xxx staging',
691
+ 'eve env logs proj_xxx staging api --tail 200',
692
+ ],
613
693
  },
614
694
  api: {
615
695
  description: 'Explore project API sources and call them with Eve auth.',
@@ -873,6 +953,38 @@ for cloud deployments. Credentials are stored per-profile.`,
873
953
  'eve admin invite --email user@example.com --github octocat --org org_xxx',
874
954
  ],
875
955
  },
956
+ init: {
957
+ description: `Initialize a new Eve Horizon project from a template.
958
+
959
+ Downloads the starter template, strips git history, initializes a fresh repo,
960
+ and installs skills. After init, start your AI coding agent and run the
961
+ eve-new-project-setup skill to complete configuration.`,
962
+ usage: 'eve init [directory] [--template <url>] [--branch <branch>]',
963
+ subcommands: {
964
+ '': {
965
+ description: 'Initialize project in current or specified directory',
966
+ usage: 'eve init [directory] [options]',
967
+ options: [
968
+ '[directory] Target directory (default: current directory)',
969
+ '--template <url> Template repository URL',
970
+ ' (default: https://github.com/incept5/eve-horizon-starter)',
971
+ '--branch <branch> Branch to use (default: main)',
972
+ '--skip-skills Skip automatic skill installation',
973
+ ],
974
+ examples: [
975
+ 'eve init',
976
+ 'eve init my-project',
977
+ 'eve init my-project --template https://github.com/myorg/my-template',
978
+ 'eve init . --branch develop',
979
+ ],
980
+ },
981
+ },
982
+ examples: [
983
+ 'eve init my-project',
984
+ 'eve init',
985
+ 'eve init my-app --template https://github.com/myorg/custom-starter',
986
+ ],
987
+ },
876
988
  system: {
877
989
  description: 'System administration and health checks.',
878
990
  usage: 'eve system <subcommand> [options]',
@@ -961,6 +1073,9 @@ function showMainHelp() {
961
1073
  console.log('');
962
1074
  console.log('Usage: eve <command> [subcommand] [options]');
963
1075
  console.log('');
1076
+ console.log('Getting Started:');
1077
+ console.log(' init Initialize a new project from template');
1078
+ console.log('');
964
1079
  console.log('Commands:');
965
1080
  console.log(' org Manage organizations');
966
1081
  console.log(' project Manage projects');
@@ -973,6 +1088,7 @@ function showMainHelp() {
973
1088
  console.log(' event Emit and inspect events (app integration)');
974
1089
  console.log(' secrets Manage secrets (project/org/user scope)');
975
1090
  console.log(' harness Inspect harnesses and auth status');
1091
+ console.log(' agents Inspect agent policy and harness capabilities');
976
1092
  console.log(' profile Manage CLI profiles (API URL, defaults)');
977
1093
  console.log(' auth Authentication (login, logout, status)');
978
1094
  console.log(' admin User and identity management (invite)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eve-horizon/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Eve Horizon CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,9 +17,6 @@
17
17
  "dist",
18
18
  "README.md"
19
19
  ],
20
- "scripts": {
21
- "build": "tsc -p tsconfig.json"
22
- },
23
20
  "publishConfig": {
24
21
  "access": "public"
25
22
  },
@@ -27,10 +24,14 @@
27
24
  "node": ">=22.0.0"
28
25
  },
29
26
  "dependencies": {
30
- "yaml": "^2.5.1"
27
+ "yaml": "^2.5.1",
28
+ "@eve/shared": "0.0.1"
31
29
  },
32
30
  "devDependencies": {
33
31
  "@types/node": "^22.0.0",
34
32
  "typescript": "^5.7.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json"
35
36
  }
36
- }
37
+ }