@eve-horizon/cli 0.2.9 → 0.2.11

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
@@ -6,18 +6,38 @@ Published CLI (npx) for interacting with the Eve Horizon API. Dev/ops tooling li
6
6
  npx @eve/cli --help
7
7
  ```
8
8
 
9
+ ## Local Development & Global Parity
10
+
11
+ When testing CLI changes locally, you have two options:
12
+
13
+ ```bash
14
+ # Option 1: run the repo-local build directly
15
+ pnpm -C packages/cli build
16
+ node packages/cli/bin/eve.js --help
17
+
18
+ # Option 2: link the CLI so the global `eve` binary matches your local build
19
+ pnpm -C packages/cli build
20
+ cd packages/cli && npm link
21
+ eve --help
22
+ ```
23
+
24
+ To update global installs for others, publish a new CLI version via the `cli-v*` tag flow
25
+ (see root AGENTS.md for release instructions).
26
+
9
27
  ## Philosophy
10
28
 
11
29
  The CLI is the **primary interface** for Eve Horizon, designed for humans and AI agents. The REST API is the substrate: every operation is exposed via HTTP, and the CLI is a thin wrapper that handles argument parsing and output formatting. It does not bypass the API or contain business logic.
12
30
 
13
31
  See [API Philosophy](../../docs/system/api-philosophy.md) and [OpenAPI](../../docs/system/openapi.md).
14
32
 
15
- ## Profiles (defaults + credentials)
33
+ ## Profiles (repo-local defaults)
16
34
 
17
35
  Profiles store API URL, default org/project IDs, default harness, and Supabase auth config.
36
+ Profiles are **repo-local** and live in `.eve/profile.yaml` so each project keeps its own
37
+ defaults without impacting other checkouts.
18
38
 
19
39
  ```bash
20
- # Create or update a profile
40
+ # Create or update a profile (repo-local)
21
41
  eve profile set local \
22
42
  --api-url http://localhost:4801 \
23
43
  --org org_defaulttestorg \
@@ -27,7 +47,7 @@ eve profile set local \
27
47
  eve profile set --harness mclaude
28
48
  eve profile set --harness mclaude:fast # harness with variant
29
49
 
30
- # Set active profile
50
+ # Set active profile (repo-local)
31
51
  eve profile use local
32
52
 
33
53
  # Show active profile
@@ -54,12 +74,16 @@ eve profile set prod \
54
74
  Auth is **required** for cloud stacks. The default flow uses GitHub SSH keys; Supabase remains an
55
75
  optional adapter for legacy deployments.
56
76
 
57
- The CLI will refresh Supabase access tokens automatically if a refresh token is available.
77
+ Credentials are stored globally in `~/.eve/credentials.json`, keyed by API URL. The CLI will
78
+ refresh Supabase access tokens automatically if a refresh token is available.
58
79
 
59
80
  ```bash
60
81
  # SSH login (default)
61
82
  eve auth login --email you@example.com --ssh-key ~/.ssh/id_ed25519
62
83
 
84
+ # SSH login with custom token TTL (1-90 days)
85
+ eve auth login --email you@example.com --ttl 30
86
+
63
87
  # Supabase login (optional)
64
88
  eve auth login --email you@example.com --password '...' \
65
89
  --supabase-url https://your-project.supabase.co \
@@ -151,6 +175,32 @@ eve job approve MyProj-abc123 --comment "LGTM"
151
175
  eve job reject MyProj-abc123 --reason "Missing tests"
152
176
  ```
153
177
 
178
+ ### Agents
179
+
180
+ ```bash
181
+ # Inspect agent policy + harness readiness
182
+ eve agents config --json
183
+
184
+ # Sync agents/teams/chat config from repo (deterministic)
185
+ eve agents sync --project proj_xxx --ref main
186
+
187
+ # Local dev sync (requires local API + allow dirty)
188
+ eve agents sync --project proj_xxx --local --allow-dirty
189
+ ```
190
+
191
+ ### Integrations + Chat
192
+
193
+ ```bash
194
+ # Connect Slack workspace (stub OAuth)
195
+ eve integrations slack connect --org org_xxx --team-id T123 --token xoxb-...
196
+
197
+ # List integrations for org
198
+ eve integrations list --org org_xxx
199
+
200
+ # Simulate inbound Slack message
201
+ eve chat simulate --project proj_xxx --team-id T123 --channel-id C123 --user-id U123 --text "hello"
202
+ ```
203
+
154
204
  #### Job Results
155
205
 
156
206
  Fetch and display completed job results:
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleAgents = handleAgents;
4
+ const node_child_process_1 = require("node:child_process");
4
5
  const node_fs_1 = require("node:fs");
5
6
  const node_path_1 = require("node:path");
6
7
  const yaml_1 = require("yaml");
@@ -8,6 +9,7 @@ const harness_capabilities_js_1 = require("../lib/harness-capabilities.js");
8
9
  const args_1 = require("../lib/args");
9
10
  const client_1 = require("../lib/client");
10
11
  const output_1 = require("../lib/output");
12
+ const git_js_1 = require("../lib/git.js");
11
13
  function readYamlFile(filePath) {
12
14
  const raw = (0, node_fs_1.readFileSync)(filePath, 'utf-8');
13
15
  const parsed = (0, yaml_1.parse)(raw);
@@ -16,6 +18,41 @@ function readYamlFile(filePath) {
16
18
  }
17
19
  return parsed;
18
20
  }
21
+ function resolveAgentsConfigPaths(repoRoot, manifest) {
22
+ const xEve = manifest['x-eve'] ||
23
+ manifest['x_eve'] ||
24
+ {};
25
+ const agentsBlock = xEve['agents'] || {};
26
+ const chatBlock = xEve['chat'] || {};
27
+ const manifestChat = manifest['chat'] || {};
28
+ const agentsPath = (0, node_path_1.resolve)(repoRoot, pickString(agentsBlock.config_path) ?? 'agents/agents.yaml');
29
+ const teamsPath = (0, node_path_1.resolve)(repoRoot, pickString(agentsBlock.teams_path) ?? 'agents/teams.yaml');
30
+ const chatPath = (0, node_path_1.resolve)(repoRoot, pickString(chatBlock.config_path) ?? pickString(manifestChat.config_path) ?? 'agents/chat.yaml');
31
+ return { agentsPath, teamsPath, chatPath };
32
+ }
33
+ function pickString(value) {
34
+ return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
35
+ }
36
+ function ensureFileExists(path, label) {
37
+ if (!(0, node_fs_1.existsSync)(path)) {
38
+ throw new Error(`Missing ${label} at ${path}. Update manifest config_path or add the file.`);
39
+ }
40
+ return path;
41
+ }
42
+ function isLocalApiUrl(apiUrl) {
43
+ try {
44
+ const url = new URL(apiUrl);
45
+ const host = url.hostname.toLowerCase();
46
+ return (host === 'localhost' ||
47
+ host === '127.0.0.1' ||
48
+ host === '0.0.0.0' ||
49
+ host.endsWith('.lvh.me') ||
50
+ host.endsWith('.localhost'));
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
19
56
  function loadAgentsConfig(repoRoot) {
20
57
  const eveDir = (0, node_path_1.join)(repoRoot, '.eve');
21
58
  const manifestPath = (0, node_path_1.join)(eveDir, 'manifest.yaml');
@@ -34,45 +71,126 @@ async function handleAgents(subcommand, positionals, flags, context) {
34
71
  const command = subcommand ?? 'config';
35
72
  const json = Boolean(flags.json);
36
73
  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 = harness_capabilities_js_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)`);
74
+ const repoRoot = (0, node_path_1.resolve)((0, args_1.getStringFlag)(flags, ['repo-dir', 'repo_dir', 'dir', 'path']) ?? process.cwd());
75
+ switch (command) {
76
+ case 'config': {
77
+ const result = loadAgentsConfig(repoRoot);
78
+ const response = {
79
+ repo_root: repoRoot,
80
+ source: result.source,
81
+ policy: result.policy,
82
+ };
83
+ if (result.manifest_defaults) {
84
+ response.manifest_defaults = result.manifest_defaults;
85
+ }
86
+ if (includeHarnesses) {
87
+ const harnesses = await (0, client_1.requestJson)(context, '/harnesses');
88
+ response.harnesses = harnesses.data;
89
+ response.capabilities = harness_capabilities_js_1.HARNESS_CAPABILITIES;
90
+ }
91
+ if (json) {
92
+ (0, output_1.outputJson)(response, json);
93
+ return;
94
+ }
95
+ console.log(`Agents config source: ${result.source.type}`);
96
+ if ('path' in result.source) {
97
+ console.log(`Path: ${result.source.path}`);
98
+ }
99
+ if (!result.policy) {
100
+ console.log('No policy found. Add x-eve.agents to .eve/manifest.yaml.');
101
+ }
102
+ else {
103
+ const profiles = result.policy.profiles || {};
104
+ const profileNames = Object.keys(profiles);
105
+ console.log(`Profiles: ${profileNames.length ? profileNames.join(', ') : 'none'}`);
106
+ }
107
+ if (includeHarnesses) {
108
+ const harnesses = response.harnesses;
109
+ if (harnesses?.length) {
110
+ const ready = harnesses.filter((h) => h.auth.available).length;
111
+ console.log(`Harnesses: ${harnesses.length} (${ready} ready)`);
112
+ }
113
+ }
114
+ return;
115
+ }
116
+ case 'sync': {
117
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
118
+ if (!projectId) {
119
+ throw new Error('Missing project id. Provide --project or set a profile default.');
120
+ }
121
+ const ref = (0, args_1.getStringFlag)(flags, ['ref']);
122
+ const local = (0, args_1.getBooleanFlag)(flags, ['local']) ?? false;
123
+ const allowDirty = (0, args_1.getBooleanFlag)(flags, ['allow-dirty', 'allow_dirty']) ?? false;
124
+ const forceNonlocal = (0, args_1.getBooleanFlag)(flags, ['force-nonlocal', 'force_nonlocal']) ?? false;
125
+ if (local && ref) {
126
+ throw new Error('Use either --local or --ref, not both.');
127
+ }
128
+ if (!local && !ref) {
129
+ throw new Error('Missing --ref. Use --ref <sha|branch> or --local for dev sync.');
130
+ }
131
+ if (local && !forceNonlocal && !isLocalApiUrl(context.apiUrl)) {
132
+ throw new Error(`--local sync is only allowed for local API URLs (localhost or *.lvh.me). ` +
133
+ `Current API: ${context.apiUrl}. Use --force-nonlocal to override.`);
134
+ }
135
+ const gitRoot = (0, git_js_1.getGitRoot)(repoRoot);
136
+ if (!gitRoot) {
137
+ throw new Error('Not a git repository. Run from the repo root or pass --repo-dir <path>.');
138
+ }
139
+ const dirty = (0, git_js_1.isGitDirty)(gitRoot);
140
+ if (dirty && !allowDirty) {
141
+ throw new Error('Working tree is dirty. Commit changes or pass --allow-dirty to sync anyway.');
142
+ }
143
+ const manifestPath = (0, node_path_1.join)(repoRoot, '.eve', 'manifest.yaml');
144
+ if (!(0, node_fs_1.existsSync)(manifestPath)) {
145
+ throw new Error(`Missing manifest at ${manifestPath}. Expected .eve/manifest.yaml.`);
146
+ }
147
+ 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');
152
+ let gitSha;
153
+ let branch;
154
+ let gitRef = ref ?? 'local';
155
+ if (ref) {
156
+ gitSha = await (0, git_js_1.resolveGitRef)(context, projectId, ref, repoRoot);
157
+ branch = (0, git_js_1.resolveGitBranch)(gitRoot, ref) ?? undefined;
158
+ }
159
+ else {
160
+ gitSha = (0, node_child_process_1.execSync)('git rev-parse HEAD', { cwd: gitRoot, encoding: 'utf-8' }).trim();
161
+ branch = (0, git_js_1.getGitBranch)(gitRoot) ?? undefined;
162
+ }
163
+ if (dirty) {
164
+ gitRef = `dirty:${gitRef}`;
165
+ }
166
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/agents/sync`, {
167
+ method: 'POST',
168
+ body: {
169
+ agents_yaml: agentsYaml,
170
+ teams_yaml: teamsYaml,
171
+ chat_yaml: chatYaml,
172
+ git_sha: gitSha,
173
+ branch,
174
+ git_ref: gitRef,
175
+ },
176
+ });
177
+ if (json) {
178
+ (0, output_1.outputJson)(response, json);
179
+ return;
180
+ }
181
+ 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}`);
185
+ if (gitSha)
186
+ console.log(` Git SHA: ${gitSha.substring(0, 8)}`);
187
+ if (branch)
188
+ console.log(` Branch: ${branch}`);
189
+ if (dirty)
190
+ console.log(' Warning: working tree dirty — marked non-deployable');
191
+ return;
76
192
  }
193
+ default:
194
+ throw new Error('Usage: eve agents <config|sync>');
77
195
  }
78
196
  }
@@ -59,6 +59,11 @@ async function handleAuth(subcommand, flags, context, credentials) {
59
59
  const email = (0, args_1.getStringFlag)(flags, ['email']) ?? process.env.EVE_AUTH_EMAIL ?? context.profile.default_email;
60
60
  const userId = (0, args_1.getStringFlag)(flags, ['user-id']);
61
61
  const password = (0, args_1.getStringFlag)(flags, ['password']) ?? process.env.EVE_AUTH_PASSWORD;
62
+ const ttlStr = (0, args_1.getStringFlag)(flags, ['ttl']);
63
+ const ttlDays = ttlStr ? parseInt(ttlStr, 10) : undefined;
64
+ if (ttlDays !== undefined && (isNaN(ttlDays) || ttlDays < 1 || ttlDays > 90)) {
65
+ throw new Error('--ttl must be between 1 and 90 days');
66
+ }
62
67
  const supabaseUrl = (0, args_1.getStringFlag)(flags, ['supabase-url']) ||
63
68
  process.env.EVE_SUPABASE_URL ||
64
69
  context.profile.supabase_url;
@@ -102,7 +107,7 @@ async function handleAuth(subcommand, flags, context, credentials) {
102
107
  const expiresAt = payload.expires_in
103
108
  ? Math.floor(Date.now() / 1000) + payload.expires_in
104
109
  : undefined;
105
- credentials.profiles[context.profileName] = {
110
+ credentials.tokens[context.authKey] = {
106
111
  access_token: payload.access_token,
107
112
  refresh_token: payload.refresh_token,
108
113
  expires_at: expiresAt,
@@ -116,7 +121,7 @@ async function handleAuth(subcommand, flags, context, credentials) {
116
121
  throw new Error('Usage: eve auth login --email <email> or --user-id <id>');
117
122
  }
118
123
  // Attempt SSH key login with GitHub key auto-discovery on failure
119
- const loginResult = await attemptSshLogin(context, credentials, flags, email, userId);
124
+ const loginResult = await attemptSshLogin(context, credentials, flags, email, userId, ttlDays);
120
125
  if (loginResult.success) {
121
126
  (0, output_1.outputJson)({ profile: context.profileName, token_type: loginResult.tokenType }, json, '✓ Logged in');
122
127
  return;
@@ -136,7 +141,7 @@ async function handleAuth(subcommand, flags, context, credentials) {
136
141
  }
137
142
  // Retry login after key registration
138
143
  console.log('\nRetrying login with registered keys...');
139
- const retryResult = await attemptSshLogin(context, credentials, flags, email, userId);
144
+ const retryResult = await attemptSshLogin(context, credentials, flags, email, userId, ttlDays);
140
145
  if (retryResult.success) {
141
146
  (0, output_1.outputJson)({ profile: context.profileName, token_type: retryResult.tokenType }, json, '✓ Logged in');
142
147
  return;
@@ -144,8 +149,14 @@ async function handleAuth(subcommand, flags, context, credentials) {
144
149
  throw new Error(retryResult.error ?? 'Auth verify failed after key registration');
145
150
  }
146
151
  case 'logout': {
147
- if (credentials.profiles[context.profileName]) {
152
+ const hadToken = Boolean(credentials.tokens[context.authKey] || credentials.profiles?.[context.profileName]);
153
+ if (credentials.tokens[context.authKey]) {
154
+ delete credentials.tokens[context.authKey];
155
+ }
156
+ if (credentials.profiles?.[context.profileName]) {
148
157
  delete credentials.profiles[context.profileName];
158
+ }
159
+ if (hadToken) {
149
160
  (0, config_1.saveCredentials)(credentials);
150
161
  }
151
162
  (0, output_1.outputJson)({ profile: context.profileName }, json, '✓ Logged out');
@@ -251,7 +262,7 @@ async function handleAuth(subcommand, flags, context, credentials) {
251
262
  if (!payload.access_token) {
252
263
  throw new Error('Bootstrap response missing access_token');
253
264
  }
254
- credentials.profiles[context.profileName] = {
265
+ credentials.tokens[context.authKey] = {
255
266
  access_token: payload.access_token,
256
267
  expires_at: payload.expires_at,
257
268
  token_type: payload.token_type,
@@ -262,7 +273,7 @@ async function handleAuth(subcommand, flags, context, credentials) {
262
273
  }
263
274
  case 'token': {
264
275
  // Print the current access token to stdout for use in scripts
265
- const tokenEntry = credentials.profiles[context.profileName];
276
+ const tokenEntry = credentials.tokens[context.authKey] || credentials.profiles?.[context.profileName];
266
277
  if (!tokenEntry || !tokenEntry.access_token) {
267
278
  console.error('No valid token found. Please login first with: eve auth login');
268
279
  process.exit(1);
@@ -691,7 +702,7 @@ function signNonceWithSsh(keyPath, nonce) {
691
702
  (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
692
703
  }
693
704
  }
694
- async function attemptSshLogin(context, credentials, flags, email, userId) {
705
+ async function attemptSshLogin(context, credentials, flags, email, userId, ttlDays) {
695
706
  const challengeResponse = await (0, client_1.requestRaw)(context, '/auth/challenge', {
696
707
  method: 'POST',
697
708
  body: {
@@ -723,7 +734,7 @@ async function attemptSshLogin(context, credentials, flags, email, userId) {
723
734
  }
724
735
  const verifyResponse = await (0, client_1.requestRaw)(context, '/auth/verify', {
725
736
  method: 'POST',
726
- body: { challenge_id: challenge.challenge_id, signature },
737
+ body: { challenge_id: challenge.challenge_id, signature, ...(ttlDays !== undefined && { ttl_days: ttlDays }) },
727
738
  });
728
739
  if (!verifyResponse.ok) {
729
740
  const message = typeof verifyResponse.data === 'string'
@@ -735,7 +746,7 @@ async function attemptSshLogin(context, credentials, flags, email, userId) {
735
746
  if (!payload.access_token) {
736
747
  return { success: false, error: 'Auth verify response missing access_token' };
737
748
  }
738
- credentials.profiles[context.profileName] = {
749
+ credentials.tokens[context.authKey] = {
739
750
  access_token: payload.access_token,
740
751
  expires_at: payload.expires_at,
741
752
  token_type: payload.token_type,
@@ -4,7 +4,7 @@ exports.handleBuild = handleBuild;
4
4
  const args_1 = require("../lib/args");
5
5
  const client_1 = require("../lib/client");
6
6
  const output_1 = require("../lib/output");
7
- const node_child_process_1 = require("node:child_process");
7
+ const git_js_1 = require("../lib/git.js");
8
8
  const ERROR_CODES = {
9
9
  auth_error: { code: 'auth_error', label: 'Authentication Error', hint: "Check GITHUB_TOKEN via 'eve secrets set'" },
10
10
  clone_error: { code: 'clone_error', label: 'Git Clone Error', hint: "Verify repo URL and access. Check 'eve secrets list'" },
@@ -63,23 +63,14 @@ async function handleCreate(flags, context, json) {
63
63
  const ref = (0, args_1.getStringFlag)(flags, ['ref']);
64
64
  const manifestHash = (0, args_1.getStringFlag)(flags, ['manifest-hash', 'manifest']);
65
65
  const services = (0, args_1.getStringFlag)(flags, ['services']);
66
+ const repoDir = (0, args_1.getStringFlag)(flags, ['repo-dir', 'repo_dir', 'dir']);
66
67
  if (!projectId || !ref || !manifestHash) {
67
- throw new Error('Usage: eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>]');
68
+ throw new Error('Usage: eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>] [--repo-dir <path>]');
68
69
  }
69
70
  // Resolve git ref to actual 40-char SHA
70
- let gitSha;
71
- try {
72
- gitSha = (0, node_child_process_1.execSync)(`git rev-parse ${ref}`, {
73
- encoding: 'utf-8',
74
- stdio: ['pipe', 'pipe', 'pipe'],
75
- }).trim();
76
- if (!json) {
77
- console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
78
- }
79
- }
80
- catch (error) {
81
- throw new Error(`Failed to resolve git ref '${ref}': ${error instanceof Error ? error.message : String(error)}\n` +
82
- 'Make sure you are in a git repository and the ref exists.');
71
+ const gitSha = await (0, git_js_1.resolveGitRef)(context, projectId, ref, repoDir);
72
+ if (!json && ref !== gitSha) {
73
+ console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
83
74
  }
84
75
  const body = { git_sha: gitSha, manifest_hash: manifestHash };
85
76
  if (services) {
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleChat = handleChat;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ async function handleChat(subcommand, _positionals, flags, context) {
8
+ const json = Boolean(flags.json);
9
+ switch (subcommand) {
10
+ case 'simulate': {
11
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
12
+ if (!projectId) {
13
+ throw new Error('Missing project id. Provide --project or set a profile default.');
14
+ }
15
+ const provider = (0, args_1.getStringFlag)(flags, ['provider']) ?? 'slack';
16
+ const teamId = (0, args_1.getStringFlag)(flags, ['team-id']);
17
+ const text = (0, args_1.getStringFlag)(flags, ['text']);
18
+ if (!teamId || !text) {
19
+ throw new Error('Usage: eve chat simulate --project <id> --team-id <team> --text <message>');
20
+ }
21
+ const channelId = (0, args_1.getStringFlag)(flags, ['channel-id']);
22
+ const userId = (0, args_1.getStringFlag)(flags, ['user-id']);
23
+ const threadKey = (0, args_1.getStringFlag)(flags, ['thread-key']);
24
+ const metadataFlag = (0, args_1.getStringFlag)(flags, ['metadata']);
25
+ let metadata;
26
+ if (metadataFlag) {
27
+ try {
28
+ const parsed = JSON.parse(metadataFlag);
29
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
30
+ metadata = parsed;
31
+ }
32
+ }
33
+ catch {
34
+ throw new Error('Invalid --metadata (must be JSON object)');
35
+ }
36
+ }
37
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/chat/simulate`, {
38
+ method: 'POST',
39
+ body: {
40
+ provider,
41
+ team_id: teamId,
42
+ channel_id: channelId,
43
+ user_id: userId,
44
+ text,
45
+ thread_key: threadKey,
46
+ metadata,
47
+ },
48
+ });
49
+ (0, output_1.outputJson)(response, json, `✓ Chat simulated (thread: ${response.thread_id})`);
50
+ return;
51
+ }
52
+ default:
53
+ throw new Error('Usage: eve chat <simulate>');
54
+ }
55
+ }
@@ -37,7 +37,7 @@ exports.handleEnv = handleEnv;
37
37
  const args_1 = require("../lib/args");
38
38
  const client_1 = require("../lib/client");
39
39
  const output_1 = require("../lib/output");
40
- const child_process_1 = require("child_process");
40
+ const git_js_1 = require("../lib/git.js");
41
41
  const readline = __importStar(require("node:readline/promises"));
42
42
  // ============================================================================
43
43
  // Main Handler
@@ -64,7 +64,7 @@ async function handleEnv(subcommand, positionals, flags, context) {
64
64
  ' list [project] - list environments for a project\n' +
65
65
  ' show <project> <name> - show details of an environment\n' +
66
66
  ' create <name> --type=<type> [options] - create an environment\n' +
67
- ' deploy <env> --ref <sha> [--direct] [--inputs <json>] - deploy to an environment\n' +
67
+ ' deploy <env> --ref <sha> [--direct] [--inputs <json>] [--repo-dir <path>] - deploy to an environment\n' +
68
68
  ' logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>] - get service logs\n' +
69
69
  ' diagnose <project> <env> - diagnose deployment health and events\n' +
70
70
  ' delete <name> [--project=<id>] [--force] - delete an environment');
@@ -194,27 +194,18 @@ async function handleDeploy(positionals, flags, context, json) {
194
194
  envName = flagName;
195
195
  }
196
196
  if (!projectId || !envName) {
197
- throw new Error('Usage: eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>] [--project=<id>]');
197
+ throw new Error('Usage: eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>] [--repo-dir <path>] [--project=<id>]');
198
198
  }
199
199
  // --ref flag is now required
200
200
  const ref = (0, args_1.getStringFlag)(flags, ['ref']);
201
201
  if (!ref) {
202
202
  throw new Error('Usage: eve env deploy <env> --ref <sha> [options]\n\nThe --ref flag is required (git SHA or commit reference)');
203
203
  }
204
+ const repoDir = (0, args_1.getStringFlag)(flags, ['repo-dir', 'repo_dir', 'dir']);
204
205
  // Resolve git ref to actual 40-char SHA
205
- let gitSha;
206
- try {
207
- gitSha = (0, child_process_1.execSync)(`git rev-parse ${ref}`, {
208
- encoding: 'utf-8',
209
- stdio: ['pipe', 'pipe', 'pipe'],
210
- }).trim();
211
- if (!json) {
212
- console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
213
- }
214
- }
215
- catch (error) {
216
- throw new Error(`Failed to resolve git ref '${ref}': ${error instanceof Error ? error.message : String(error)}\n` +
217
- 'Make sure you are in a git repository and the ref exists.');
206
+ const gitSha = await (0, git_js_1.resolveGitRef)(context, projectId, ref, repoDir);
207
+ if (!json && ref !== gitSha) {
208
+ console.log(`Resolved ref '${ref}' → ${gitSha.substring(0, 8)}...`);
218
209
  }
219
210
  if (!json) {
220
211
  console.log(`Deploying commit ${gitSha.substring(0, 8)} to ${envName}...`);
@@ -434,21 +425,6 @@ function buildQuery(params) {
434
425
  const query = search.toString();
435
426
  return query ? `?${query}` : '';
436
427
  }
437
- /**
438
- * Get current git SHA from working directory
439
- */
440
- function getGitSha() {
441
- try {
442
- const sha = (0, child_process_1.execSync)('git rev-parse HEAD', {
443
- encoding: 'utf8',
444
- stdio: ['pipe', 'pipe', 'pipe'],
445
- }).trim();
446
- return sha;
447
- }
448
- catch {
449
- return null;
450
- }
451
- }
452
428
  /**
453
429
  * Format environments as a human-readable table
454
430
  */
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleIntegrations = handleIntegrations;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ async function handleIntegrations(subcommand, positionals, flags, context) {
8
+ const json = Boolean(flags.json);
9
+ const orgId = (0, args_1.getStringFlag)(flags, ['org']) ?? context.orgId;
10
+ switch (subcommand) {
11
+ case 'list': {
12
+ if (!orgId) {
13
+ throw new Error('Usage: eve integrations list --org <org_id>');
14
+ }
15
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/integrations`);
16
+ (0, output_1.outputJson)(response, json);
17
+ return;
18
+ }
19
+ case 'slack': {
20
+ const action = positionals[0];
21
+ if (action !== 'connect') {
22
+ throw new Error('Usage: eve integrations slack connect --org <org_id> --team-id <team_id> [--token <token>]');
23
+ }
24
+ if (!orgId) {
25
+ throw new Error('Missing org id. Provide --org or set a profile default.');
26
+ }
27
+ const teamId = (0, args_1.getStringFlag)(flags, ['team-id']);
28
+ if (!teamId) {
29
+ throw new Error('Missing --team-id');
30
+ }
31
+ const token = (0, args_1.getStringFlag)(flags, ['token']);
32
+ const tokensJsonFlag = (0, args_1.getStringFlag)(flags, ['tokens-json']);
33
+ const status = (0, args_1.getStringFlag)(flags, ['status']);
34
+ let tokensJson;
35
+ if (tokensJsonFlag) {
36
+ try {
37
+ const parsed = JSON.parse(tokensJsonFlag);
38
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
39
+ tokensJson = parsed;
40
+ }
41
+ }
42
+ catch (error) {
43
+ throw new Error('Invalid --tokens-json (must be JSON object)');
44
+ }
45
+ }
46
+ if (!tokensJson && token) {
47
+ tokensJson = { access_token: token };
48
+ }
49
+ const body = { team_id: teamId };
50
+ if (tokensJson)
51
+ body.tokens_json = tokensJson;
52
+ if (status)
53
+ body.status = status;
54
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/integrations/slack/connect`, { method: 'POST', body });
55
+ (0, output_1.outputJson)(response, json, `✓ Slack integration connected: ${response.id}`);
56
+ return;
57
+ }
58
+ case 'test': {
59
+ const integrationId = positionals[0];
60
+ if (!integrationId) {
61
+ throw new Error('Usage: eve integrations test <integration_id>');
62
+ }
63
+ if (!orgId) {
64
+ throw new Error('Missing org id. Provide --org or set a profile default.');
65
+ }
66
+ const response = await (0, client_1.requestJson)(context, `/orgs/${orgId}/integrations/${integrationId}/test`, { method: 'POST' });
67
+ (0, output_1.outputJson)(response, json, response.ok ? '✓ Integration test ok' : 'Integration test failed');
68
+ return;
69
+ }
70
+ default:
71
+ throw new Error('Usage: eve integrations <list|slack|test>');
72
+ }
73
+ }