@eve-horizon/cli 0.0.1

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 ADDED
@@ -0,0 +1,275 @@
1
+ # Eve Horizon CLI
2
+
3
+ Published CLI (npx) for interacting with the Eve Horizon API. Dev/ops tooling lives in `./bin/eh`.
4
+
5
+ ```bash
6
+ npx @eve/cli --help
7
+ ```
8
+
9
+ ## Philosophy
10
+
11
+ 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
+
13
+ See [API Philosophy](../../docs/system/api-philosophy.md) and [OpenAPI](../../docs/system/openapi.md).
14
+
15
+ ## Profiles (defaults + credentials)
16
+
17
+ Profiles store API URL, default org/project IDs, default harness, and Supabase auth config.
18
+
19
+ ```bash
20
+ # Create or update a profile
21
+ eve profile set local \
22
+ --api-url http://localhost:4701 \
23
+ --org org_defaulttestorg \
24
+ --project proj_xxx
25
+
26
+ # Set default harness (with optional variant)
27
+ eve profile set --harness mclaude
28
+ eve profile set --harness mclaude:fast # harness with variant
29
+
30
+ # Set active profile
31
+ eve profile use local
32
+
33
+ # Show active profile
34
+ eve profile show
35
+ ```
36
+
37
+ Profile harness defaults are used when scheduling jobs.
38
+ The `--harness` flag accepts `harness` or `harness:variant` format everywhere.
39
+ Priority: `--harness` flag > job hints > profile default > system default.
40
+
41
+ ### Supabase config (cloud auth)
42
+
43
+ Store Supabase settings in a profile (recommended for cloud stacks):
44
+
45
+ ```bash
46
+ eve profile set prod \
47
+ --api-url https://api.eve-horizon.example.com \
48
+ --supabase-url https://your-project.supabase.co \
49
+ --supabase-anon-key <anon-key>
50
+ ```
51
+
52
+ ## Auth
53
+
54
+ Auth is **optional** per stack. When the server has `EVE_AUTH_ENABLED=false`, login is not required.
55
+ Supabase settings can be stored in a profile or provided via `EVE_SUPABASE_URL` and
56
+ `EVE_SUPABASE_ANON_KEY`.
57
+
58
+ The CLI will refresh access tokens automatically if a refresh token is available.
59
+
60
+ ```bash
61
+ # Login (cloud)
62
+ eve auth login --email you@example.com --password '...' \
63
+ --profile prod
64
+
65
+ # Status / whoami
66
+ eve auth status
67
+ eve auth whoami
68
+
69
+ # Logout
70
+ eve auth logout
71
+ ```
72
+
73
+ ## Commands
74
+
75
+ ### Organizations
76
+
77
+ ```bash
78
+ eve org ensure "My Org"
79
+ eve org list
80
+ eve org get org_xxx
81
+ eve org update org_xxx --name "New Name"
82
+ ```
83
+
84
+ ### Projects
85
+
86
+ ```bash
87
+ eve project ensure --name my-project --slug MyProj --repo-url https://github.com/org/repo
88
+ eve project list
89
+ eve project get proj_xxx
90
+ ```
91
+
92
+ ### Jobs
93
+
94
+ Jobs follow a phase-based lifecycle: `idea` → `backlog` → `ready` → `active` → `review` → `done`
95
+
96
+ Jobs created without a `--phase` flag default to `ready`, making them immediately schedulable.
97
+
98
+ ```bash
99
+ # Create a job (defaults to ready phase)
100
+ eve job create --description "Fix the login bug in auth.ts"
101
+ eve job create --description "Add dark mode" --priority 1 --harness mclaude
102
+
103
+ # List and filter jobs
104
+ eve job list --phase ready
105
+ eve job ready # Schedulable jobs (ready, not blocked)
106
+ eve job blocked # Jobs waiting on dependencies
107
+
108
+ # View job details
109
+ eve job show MyProj-abc123
110
+ eve job tree MyProj-abc123 # Hierarchical view
111
+
112
+ # Update jobs
113
+ eve job update MyProj-abc123 --phase active --assignee agent-1
114
+ eve job close MyProj-abc123 --reason "Work completed"
115
+ eve job cancel MyProj-abc123 --reason "No longer needed"
116
+
117
+ # Dependencies
118
+ eve job dep add MyProj-abc123 MyProj-def456 # abc123 depends on def456
119
+ eve job dep list MyProj-abc123
120
+
121
+ # Claim/release workflow (typically used by scheduler/agents)
122
+ eve job claim MyProj-abc123 --harness mclaude
123
+ eve job release MyProj-abc123 --reason "Need more info"
124
+ eve job attempts MyProj-abc123
125
+
126
+ # Job execution and monitoring
127
+ eve job logs MyProj-abc123
128
+ eve job result MyProj-abc123 --format full # Get job results (text|json|full)
129
+ eve job result MyProj-abc123 --attempt 2 # Get results from specific attempt
130
+ eve job wait MyProj-abc123 --timeout 300 # Wait for job completion
131
+ eve job wait MyProj-abc123 --quiet --json # Wait quietly, JSON output
132
+ eve job follow MyProj-abc123 # Stream logs in real-time via SSE
133
+ eve job follow MyProj-abc123 --raw # Stream raw JSON lines
134
+ eve job follow MyProj-abc123 --no-result # Stream logs without final result
135
+
136
+ # Review workflow
137
+ eve job submit MyProj-abc123 --summary "Implemented fix"
138
+ eve job approve MyProj-abc123 --comment "LGTM"
139
+ eve job reject MyProj-abc123 --reason "Missing tests"
140
+ ```
141
+
142
+ #### Job Results
143
+
144
+ Fetch and display completed job results:
145
+
146
+ ```bash
147
+ # Show full job result (default format)
148
+ eve job result MyProj-abc123
149
+
150
+ # Get results in different formats
151
+ eve job result MyProj-abc123 --format text # Plain text output only
152
+ eve job result MyProj-abc123 --format json # Full JSON structure
153
+ eve job result MyProj-abc123 --format full # Formatted with metadata (default)
154
+
155
+ # Get results from a specific attempt
156
+ eve job result MyProj-abc123 --attempt 2
157
+ ```
158
+
159
+ #### Waiting for Job Completion
160
+
161
+ Block until a job completes, with optional timeout:
162
+
163
+ ```bash
164
+ # Wait for job to complete (default timeout: 300s)
165
+ eve job wait MyProj-abc123
166
+
167
+ # Custom timeout (max 300s enforced by API)
168
+ eve job wait MyProj-abc123 --timeout 120
169
+
170
+ # Quiet mode (no progress output)
171
+ eve job wait MyProj-abc123 --quiet
172
+
173
+ # JSON output format
174
+ eve job wait MyProj-abc123 --json
175
+ ```
176
+
177
+ Exit codes:
178
+ - `0`: Job completed successfully
179
+ - `1`: Job failed
180
+ - `124`: Timeout reached
181
+ - `125`: Job was cancelled
182
+
183
+ #### Real-time Log Streaming
184
+
185
+ Stream job logs in real-time using Server-Sent Events (SSE):
186
+
187
+ ```bash
188
+ # Stream logs with timestamps and formatted output
189
+ eve job follow MyProj-abc123
190
+
191
+ # Stream raw JSON lines (for parsing/filtering)
192
+ eve job follow MyProj-abc123 --raw
193
+
194
+ # Stream logs without printing final result
195
+ eve job follow MyProj-abc123 --no-result
196
+ ```
197
+
198
+ The `follow` command connects to the job's SSE endpoint and displays logs as they are generated. It shows:
199
+ - Timestamps for each log entry
200
+ - Tool names and actions
201
+
202
+ ### Secrets
203
+
204
+ Secrets can be stored at user/org/project scope. Values are never returned in plaintext; `show` returns a masked value.
205
+
206
+ ```bash
207
+ # Project secret
208
+ eve secrets set GITHUB_TOKEN ghp_xxx --project proj_xxx --type github_token
209
+ eve secrets list --project proj_xxx
210
+ eve secrets show GITHUB_TOKEN --project proj_xxx
211
+ eve secrets delete GITHUB_TOKEN --project proj_xxx
212
+
213
+ # Import host secrets from .env
214
+ eve secrets import --project proj_xxx --file .env
215
+ ```
216
+ - Formatted, human-readable output
217
+
218
+ Use `--raw` for programmatic consumption or when piping to other tools.
219
+
220
+ #### Scheduling Hints
221
+
222
+ Jobs can include hints for the scheduler:
223
+
224
+ ```bash
225
+ eve job create --description "Heavy computation" \
226
+ --harness mclaude:fast \
227
+ --worker-type gpu \
228
+ --permission auto_edit \
229
+ --timeout 7200
230
+ ```
231
+
232
+ #### Creating Sub-Jobs (for agents)
233
+
234
+ Agents can create and optionally claim sub-jobs inline:
235
+
236
+ ```bash
237
+ # Create sub-job under parent
238
+ eve job create --parent MyProj-abc123 --description "Implement feature X"
239
+
240
+ # Create and immediately claim (for inline execution)
241
+ eve job create --parent $EVE_JOB_ID --description "Sub-task" --claim
242
+ ```
243
+
244
+ Environment variables for agent context:
245
+ - `EVE_PROJECT_ID` - Current project
246
+ - `EVE_JOB_ID` - Current job being executed
247
+ - `EVE_ATTEMPT_ID` - Current attempt UUID
248
+ - `EVE_AGENT_ID` - Agent identifier
249
+
250
+ ### Harnesses
251
+
252
+ ```bash
253
+ eve harness list # List available harnesses
254
+ eve harness get mclaude # Show harness details and auth status
255
+ ```
256
+
257
+ ## Pagination
258
+
259
+ List endpoints accept `--limit` and `--offset`. Default limit is **10**, newest first.
260
+
261
+ ```bash
262
+ eve job list --limit 10 --offset 20
263
+ ```
264
+
265
+ ## Dev CLI
266
+
267
+ Local dev/ops tooling:
268
+
269
+ ```bash
270
+ ./bin/eh --help
271
+ ./bin/eh dev start
272
+ ./bin/eh k8s start # Default runtime
273
+ ./bin/eh docker start # Quick dev loop
274
+ ./bin/eh db migrate
275
+ ```
package/bin/eve.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleApi = handleApi;
4
+ exports.handleSpec = handleSpec;
5
+ exports.fetchApiSpec = fetchApiSpec;
6
+ const args_1 = require("../lib/args");
7
+ const client_1 = require("../lib/client");
8
+ const output_1 = require("../lib/output");
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const node_child_process_1 = require("node:child_process");
12
+ async function handleApi(subcommand, positionals, flags, context) {
13
+ const jsonOutput = Boolean(flags.json);
14
+ switch (subcommand) {
15
+ case 'list':
16
+ return handleList(positionals, flags, context, jsonOutput);
17
+ case 'show':
18
+ return handleShow(positionals, flags, context, jsonOutput);
19
+ case 'spec':
20
+ return handleSpec(positionals, flags, context, jsonOutput);
21
+ case 'refresh':
22
+ return handleRefresh(positionals, flags, context, jsonOutput);
23
+ case 'examples':
24
+ return handleExamples(positionals, flags, context, jsonOutput);
25
+ case 'call':
26
+ return handleCall(positionals, flags, context);
27
+ default:
28
+ throw new Error('Usage: eve api <list|show|spec|refresh|examples|call>\n' +
29
+ ' list [project] - list API sources\n' +
30
+ ' show <name> [project] - show a single API source\n' +
31
+ ' spec <name> [project] - show cached API spec\n' +
32
+ ' refresh <name> [project] - refresh cached API spec\n' +
33
+ ' examples <name> [project] - print curl examples from spec\n' +
34
+ ' call <name> <method> <path> [options] - call an API with Eve auth');
35
+ }
36
+ }
37
+ async function handleList(positionals, flags, context, jsonOutput) {
38
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
39
+ if (!projectId) {
40
+ throw new Error('Usage: eve api list [project] [--project=<id>] [--env=<name>]');
41
+ }
42
+ const query = buildQuery({
43
+ env: (0, args_1.getStringFlag)(flags, ['env']),
44
+ });
45
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis${query}`);
46
+ if (jsonOutput) {
47
+ (0, output_1.outputJson)(response, jsonOutput);
48
+ return;
49
+ }
50
+ if (!response.data || response.data.length === 0) {
51
+ console.log('No API sources found.');
52
+ return;
53
+ }
54
+ formatApiSourcesTable(response.data);
55
+ }
56
+ async function handleShow(positionals, flags, context, jsonOutput) {
57
+ const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
58
+ const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
59
+ const env = (0, args_1.getStringFlag)(flags, ['env']);
60
+ if (!name || !projectId) {
61
+ throw new Error('Usage: eve api show <name> [project] [--project=<id>] [--env=<name>]');
62
+ }
63
+ const query = buildQuery({ env });
64
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}${query}`);
65
+ (0, output_1.outputJson)(response, jsonOutput);
66
+ }
67
+ async function handleSpec(positionals, flags, context, jsonOutput) {
68
+ const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
69
+ const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
70
+ const env = (0, args_1.getStringFlag)(flags, ['env']);
71
+ if (!name || !projectId) {
72
+ throw new Error('Usage: eve api spec <name> [project] [--project=<id>] [--env=<name>]');
73
+ }
74
+ const spec = await fetchApiSpec(context, projectId, name, env);
75
+ if (typeof spec.data === 'string') {
76
+ (0, output_1.outputJson)(spec.data, jsonOutput, spec.raw);
77
+ return;
78
+ }
79
+ (0, output_1.outputJson)(spec.data, jsonOutput);
80
+ }
81
+ async function handleRefresh(positionals, flags, context, jsonOutput) {
82
+ const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
83
+ const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
84
+ const env = (0, args_1.getStringFlag)(flags, ['env']);
85
+ if (!name || !projectId) {
86
+ throw new Error('Usage: eve api refresh <name> [project] [--project=<id>] [--env=<name>]');
87
+ }
88
+ const query = buildQuery({ env });
89
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}/refresh${query}`, {
90
+ method: 'POST',
91
+ });
92
+ (0, output_1.outputJson)(response, jsonOutput);
93
+ }
94
+ async function handleExamples(positionals, flags, context, jsonOutput) {
95
+ const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
96
+ const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
97
+ const env = (0, args_1.getStringFlag)(flags, ['env']);
98
+ if (!name || !projectId) {
99
+ throw new Error('Usage: eve api examples <name> [project] [--project=<id>] [--env=<name>]');
100
+ }
101
+ const query = buildQuery({ env });
102
+ const api = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}${query}`);
103
+ const spec = await fetchApiSpec(context, projectId, name, env);
104
+ if (jsonOutput) {
105
+ (0, output_1.outputJson)({ api, spec: spec.data }, jsonOutput);
106
+ return;
107
+ }
108
+ if (!spec.data || typeof spec.data !== 'object') {
109
+ throw new Error('API spec is not JSON; examples require a JSON OpenAPI spec.');
110
+ }
111
+ const examples = buildCurlExamples(api, spec.data);
112
+ if (examples.length === 0) {
113
+ console.log('No endpoints found in spec.');
114
+ return;
115
+ }
116
+ console.log(`API examples for ${api.name} (${api.type})`);
117
+ console.log('');
118
+ examples.forEach((example) => {
119
+ console.log(example);
120
+ });
121
+ }
122
+ async function handleCall(positionals, flags, context) {
123
+ const name = positionals[0];
124
+ const methodInput = positionals[1];
125
+ const pathInput = positionals[2];
126
+ if (!name || !methodInput || !pathInput) {
127
+ throw new Error('Usage: eve api call <name> <method> <path> [--json <payload>] [--jq <expr>]');
128
+ }
129
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
130
+ const env = (0, args_1.getStringFlag)(flags, ['env']);
131
+ if (!projectId) {
132
+ throw new Error('Missing project id. Provide --project or set a profile default.');
133
+ }
134
+ const query = buildQuery({ env });
135
+ const api = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}${query}`);
136
+ const jqExpr = (0, args_1.getStringFlag)(flags, ['jq']);
137
+ const printCurl = Boolean(flags['print-curl']);
138
+ const jsonPayloadFlag = flags.json;
139
+ if (jsonPayloadFlag === true) {
140
+ throw new Error('Usage: --json <payload|@file>');
141
+ }
142
+ const graphqlFlag = flags.graphql;
143
+ const graphqlQuery = graphqlFlag === true ? undefined : (0, args_1.getStringFlag)(flags, ['graphql']);
144
+ if (graphqlFlag === true && !graphqlQuery) {
145
+ throw new Error('Usage: --graphql <query|@file>');
146
+ }
147
+ if (graphqlQuery && jsonPayloadFlag) {
148
+ throw new Error('Use --variables for GraphQL variables; --json is for JSON bodies.');
149
+ }
150
+ const variablesFlag = (0, args_1.getStringFlag)(flags, ['variables']);
151
+ const variables = variablesFlag ? resolveJsonInput(variablesFlag) : undefined;
152
+ const jsonBody = typeof jsonPayloadFlag === 'string' ? resolveJsonInput(jsonPayloadFlag) : undefined;
153
+ const graphqlBody = graphqlQuery
154
+ ? {
155
+ query: resolveTextInput(graphqlQuery),
156
+ ...(variables ? { variables } : {}),
157
+ }
158
+ : undefined;
159
+ const body = graphqlBody ?? jsonBody;
160
+ const method = methodInput.toUpperCase();
161
+ const path = resolveApiPath(api.base_url, pathInput);
162
+ const tokenOverride = (0, args_1.getStringFlag)(flags, ['token']);
163
+ const authToken = tokenOverride ?? process.env.EVE_JOB_TOKEN ?? context.token;
164
+ if (printCurl) {
165
+ const curl = buildCurlCommand({
166
+ method,
167
+ url: path,
168
+ authToken,
169
+ jsonBody: body,
170
+ authHint: tokenOverride ? 'override' : (process.env.EVE_JOB_TOKEN ? 'job' : 'user'),
171
+ });
172
+ console.log(curl);
173
+ return;
174
+ }
175
+ const response = await fetch(path, {
176
+ method,
177
+ headers: buildApiHeaders(authToken, body),
178
+ body: body ? JSON.stringify(body) : undefined,
179
+ });
180
+ const text = await response.text();
181
+ if (!response.ok) {
182
+ throw new Error(`HTTP ${response.status}: ${text}`);
183
+ }
184
+ const { data, jsonText } = parseResponse(text);
185
+ if (jqExpr) {
186
+ if (!jsonText) {
187
+ throw new Error('Cannot apply --jq to a non-JSON response.');
188
+ }
189
+ const output = runJq(jqExpr, jsonText);
190
+ process.stdout.write(output);
191
+ return;
192
+ }
193
+ if (data !== undefined) {
194
+ console.log(JSON.stringify(data, null, 2));
195
+ return;
196
+ }
197
+ process.stdout.write(text);
198
+ }
199
+ function buildQuery(params) {
200
+ const query = Object.entries(params)
201
+ .filter(([, value]) => value !== undefined)
202
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
203
+ .join('&');
204
+ return query ? `?${query}` : '';
205
+ }
206
+ function formatApiSourcesTable(apis) {
207
+ const nameWidth = Math.max(4, ...apis.map((api) => api.name.length));
208
+ const typeWidth = Math.max(4, ...apis.map((api) => api.type.length));
209
+ console.log(`${'NAME'.padEnd(nameWidth)} ${'TYPE'.padEnd(typeWidth)} BASE URL`);
210
+ console.log(`${'-'.repeat(nameWidth)} ${'-'.repeat(typeWidth)} --------`);
211
+ apis.forEach((api) => {
212
+ console.log(`${api.name.padEnd(nameWidth)} ${api.type.padEnd(typeWidth)} ${api.base_url}`);
213
+ });
214
+ }
215
+ async function fetchApiSpec(context, projectId, name, env) {
216
+ const query = buildQuery({ env });
217
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}/spec${query}`);
218
+ if (response && typeof response === 'object' && 'schema' in response) {
219
+ const schema = response.schema;
220
+ if (typeof schema === 'string') {
221
+ return { data: schema.trim(), raw: schema };
222
+ }
223
+ return { data: schema, raw: JSON.stringify(schema) };
224
+ }
225
+ if (typeof response === 'string') {
226
+ return { data: response.trim(), raw: response };
227
+ }
228
+ return { data: response, raw: JSON.stringify(response) };
229
+ }
230
+ function buildCurlExamples(api, spec) {
231
+ const paths = spec.paths ?? {};
232
+ const examples = [];
233
+ Object.entries(paths).forEach(([path, methods]) => {
234
+ Object.entries(methods).forEach(([method, operation]) => {
235
+ const lower = method.toLowerCase();
236
+ if (!['get', 'post', 'put', 'patch', 'delete'].includes(lower)) {
237
+ return;
238
+ }
239
+ const requestBody = operation.requestBody?.content?.['application/json'];
240
+ const examplePayload = extractExamplePayload(requestBody);
241
+ const needsBody = ['post', 'put', 'patch'].includes(lower);
242
+ const curl = buildCurlCommand({
243
+ method: lower.toUpperCase(),
244
+ url: resolveApiPath(api.base_url, path),
245
+ authToken: api.auth_mode === 'eve' ? '$EVE_JOB_TOKEN' : undefined,
246
+ jsonBody: needsBody ? (examplePayload ?? {}) : undefined,
247
+ authHint: 'job',
248
+ });
249
+ const summary = operation.summary ?? operation.description;
250
+ if (summary) {
251
+ examples.push(`# ${summary}`);
252
+ }
253
+ examples.push(curl);
254
+ examples.push('');
255
+ });
256
+ });
257
+ return examples;
258
+ }
259
+ function extractExamplePayload(requestBody) {
260
+ if (!requestBody)
261
+ return undefined;
262
+ if (requestBody.example !== undefined)
263
+ return requestBody.example;
264
+ const examples = requestBody.examples ? Object.values(requestBody.examples) : [];
265
+ if (examples.length === 0)
266
+ return undefined;
267
+ return examples[0]?.value;
268
+ }
269
+ function resolveApiPath(baseUrl, path) {
270
+ if (path.startsWith('http://') || path.startsWith('https://')) {
271
+ return path;
272
+ }
273
+ if (!baseUrl) {
274
+ return path;
275
+ }
276
+ const trimmedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
277
+ const trimmedPath = path.startsWith('/') ? path : `/${path}`;
278
+ return `${trimmedBase}${trimmedPath}`;
279
+ }
280
+ function buildApiHeaders(authToken, body) {
281
+ const headers = {};
282
+ if (authToken) {
283
+ headers.Authorization = `Bearer ${authToken}`;
284
+ }
285
+ if (body !== undefined) {
286
+ headers['Content-Type'] = 'application/json';
287
+ }
288
+ return headers;
289
+ }
290
+ function resolveJsonInput(value) {
291
+ const text = resolveTextInput(value);
292
+ try {
293
+ return JSON.parse(text);
294
+ }
295
+ catch (error) {
296
+ throw new Error(`Invalid JSON payload: ${error.message}`);
297
+ }
298
+ }
299
+ function resolveTextInput(value) {
300
+ if (value.startsWith('@')) {
301
+ const filePath = (0, node_path_1.resolve)(value.slice(1));
302
+ return (0, node_fs_1.readFileSync)(filePath, 'utf-8');
303
+ }
304
+ if (value.trim().startsWith('{') || value.trim().startsWith('[')) {
305
+ return value;
306
+ }
307
+ if ((0, node_fs_1.existsSync)(value)) {
308
+ return (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(value), 'utf-8');
309
+ }
310
+ return value;
311
+ }
312
+ function parseResponse(text) {
313
+ if (!text)
314
+ return { data: undefined, jsonText: undefined };
315
+ try {
316
+ const data = JSON.parse(text);
317
+ return { data, jsonText: JSON.stringify(data) };
318
+ }
319
+ catch {
320
+ return { data: undefined, jsonText: undefined };
321
+ }
322
+ }
323
+ function runJq(expression, jsonText) {
324
+ const result = (0, node_child_process_1.spawnSync)('jq', [expression], {
325
+ input: jsonText,
326
+ encoding: 'utf-8',
327
+ });
328
+ if (result.error) {
329
+ if (result.error.code === 'ENOENT') {
330
+ throw new Error('jq is not installed. Install jq to use --jq output filtering.');
331
+ }
332
+ throw result.error;
333
+ }
334
+ if (result.status !== 0) {
335
+ throw new Error(result.stderr || 'jq failed to process the response.');
336
+ }
337
+ return result.stdout ?? '';
338
+ }
339
+ function buildCurlCommand(options) {
340
+ const parts = ['curl', '-sS', '-X', options.method, `'${options.url}'`];
341
+ if (options.authToken) {
342
+ const tokenValue = options.authHint === 'job' ? '$EVE_JOB_TOKEN' : options.authToken;
343
+ parts.push(`-H 'Authorization: Bearer ${tokenValue}'`);
344
+ }
345
+ if (options.jsonBody !== undefined) {
346
+ parts.push("-H 'Content-Type: application/json'");
347
+ parts.push(`-d '${JSON.stringify(options.jsonBody)}'`);
348
+ }
349
+ return parts.join(' ');
350
+ }