@epure-sh/cli 0.1.8 → 0.2.0

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
@@ -1,90 +1,68 @@
1
1
  # @epure-sh/cli
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@epure-sh/cli.svg)](https://www.npmjs.com/package/@epure-sh/cli)
4
- [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
3
+ Manage [Epure](https://epure.sh) from a terminal or agent. Node.js 18+.
5
4
 
6
- > Official Command-Line Interface for [Epure](https://epure.sh) — error triage, issue queues, demangled stack traces, and Agent API inspection.
5
+ ## Start
7
6
 
8
- ---
9
-
10
- ## ⚡ Zero-Install Quickstart (via `npx`)
11
-
12
- Run directly without installing:
13
-
14
- ```bash
15
- export EPURE_TOKEN="epk_live_..."
16
-
17
- # Verify token & organization role
18
- npx @epure-sh/cli auth whoami
19
-
20
- # Inspect unresolved issues
21
- npx @epure-sh/cli --output table queue --limit 20
22
- ```
23
-
24
- ---
25
-
26
- ## 📦 Global Installation
27
-
28
- ```bash
7
+ ```sh
29
8
  npm install -g @epure-sh/cli
30
-
31
- # Now you can use the 'epure' command directly:
32
- epure auth whoami
33
- epure queue
9
+ epure auth login
10
+ epure projects list
11
+ epure issues list --limit 20
34
12
  ```
35
13
 
36
- ---
14
+ Sign in through your normal Epure browser account and approve the workspace and scopes. CLI and MCP share this OAuth session. There are **no pasted credentials, `--token` options or token environment variables**. Existing entries without an OAuth refresh credential and saved client identity require sign-in again.
37
15
 
38
- ## 🛠️ Common Commands
16
+ On SSH/headless machines, run `epure auth login --device --no-browser` and approve the displayed URL/code from another device. Approval expires; commands never silently open a browser. For least privilege, use `--scope "read:agent"` or `--scope "read:agent write:triage"`; setup/admin actions need `write:admin` plus the appropriate workspace role. Defaults request all three scopes, shown in browser consent.
39
17
 
40
- ### 1. View Unresolved Queue
41
- ```bash
42
- epure queue --limit 25 --output table
18
+ ```sh
19
+ epure auth status # local session metadata, never credentials
20
+ epure auth whoami # verify session with server
21
+ epure auth logout # revoke grant and remove local session
22
+ epure auth login --url https://errors.example.com
23
+ epure auth instances
24
+ epure auth use https://app.epure.sh
43
25
  ```
44
26
 
45
- ### 2. Export Crash Context for Coding Agents
46
- Export demangled stack traces and source lines to markdown:
47
- ```bash
48
- epure context <ISSUE_UUID> --format markdown
49
- # Or save directly to a file:
50
- epure context <ISSUE_UUID> --format markdown --out bug-context.md
27
+ Self-hosted origins require HTTPS; loopback HTTP is supported for development. `--url`/`EPURE_URL` selects an instance. Credentials live in `$XDG_CONFIG_HOME/epure/credentials.json` (default `~/.config/epure/credentials.json`), mode 0600, with atomic writes and serialized refresh rotation. Logout affects both local clients. If remote revocation is unavailable, logout reports that clearly; revoke the grant in Settings → Connected apps. The server needs the 0.2 client revocation update for remote logout.
28
+
29
+ ## Everyday work
30
+
31
+ ```sh
32
+ epure projects create --name "Checkout"
33
+ epure issues list --project-id PROJECT_ID --q 'status:unresolved' --limit 20
34
+ epure issues context ISSUE_ID
35
+ epure issues resolve ISSUE_ID --resolved-in-release v2.1
36
+ epure issues snooze ISSUE_ID --mode 4h
37
+ epure rules create --project-id PROJECT_ID --name "New errors" --kind issue_created
38
+ epure channels create --project-id PROJECT_ID --provider webhook --label Ops --config '{"url":"https://hooks.example.com/epure"}'
39
+ epure channels test CHANNEL_ID
40
+ epure monitors create --project-id PROJECT_ID --slug backup --schedule '0 * * * *' --timezone UTC
41
+ epure monitors update MONITOR_ID --paused true
42
+ epure monitors check-in MONITOR_ID --status ok --duration-ms 850
43
+ epure projects delete PROJECT_ID --yes
51
44
  ```
52
45
 
53
- ### 3. Triage & Resolve Issues
54
- ```bash
55
- # Mark resolved with release tag
56
- epure triage resolve <ISSUE_UUID> --release v1.2.0
46
+ Run `epure RESOURCE --help`, `epure RESOURCE ACTION --help`, or `epure schema RESOURCE.ACTION` for supported flags and exact schemas. Resources include projects, issues, rules, channels, monitors, alerts, releases, members, invitations, webhooks, privacy and connected apps. `login`, `logout`, `queue`, `context`, `issue show` and `triage resolve|ignore|reopen|snooze` remain aliases.
57
47
 
58
- # Snooze an issue for 24 hours
59
- epure triage snooze <ISSUE_UUID> --mode hours
48
+ Mutations accept individual flags or `--data '{...}'`, `--data @request.json`, and `--data -` for stdin. Unknown fields and invalid types fail before requests. Destructive actions require `--yes`, including scripts; no hidden prompts.
60
49
 
61
- # Ignore issue from alerting
62
- epure triage ignore <ISSUE_UUID>
50
+ ## Scripts and agents
63
51
 
64
- # Reopen issue
65
- epure triage reopen <ISSUE_UUID>
66
- ```
52
+ Terminal output defaults to a table; redirected output defaults to JSON. Use `--json`, `--output json|table|ndjson`, and `--fields id,name` (including dotted paths). Diagnostics go to stderr, so stdout remains parseable. No automatic write retries after network failures.
67
53
 
68
- ### 4. Self-Hosted Instances
69
- Point to your own Epure deployment:
70
- ```bash
71
- export EPURE_URL="https://errors.mycompany.com"
72
- export EPURE_TOKEN="epk_..."
73
- epure queue
54
+ ```sh
55
+ epure projects list --json --fields id,name
56
+ epure schema monitors.create
57
+ epure api-catalog --search noise-policy --limit 10
58
+ epure api GET /api/v1/projects/PROJECT_ID/noise-policy
59
+ epure api PUT /api/v1/projects/PROJECT_ID/noise-policy --data @policy.json
74
60
  ```
75
61
 
76
- ---
77
-
78
- ## 📄 License
79
-
80
- Apache-2.0 © Epure
81
-
82
- ## Full resource API access (0.1.8)
83
-
84
- CLI: `epure api-catalog`, then `epure api GET /api/v1/projects`. Mutations accept JSON, `@body.json`, or `-` for stdin. Multipart uploads: `epure api POST /api/v1/projects/PROJECT/releases/VERSION/files '{}' --file file=app.js.map`.
62
+ The catalog has paginated REST routes and typed named actions. Advanced `api METHOD PATH --query JSON --data JSON` covers remaining supported product routes. Browser authentication, manual credential management, simulations and internal endpoints are excluded. Server scope and role checks always apply.
85
63
 
86
- MCP: `epure_api_catalog` lists actions; `epure_api` accepts method/path/query/body and multipart files (field, filename, base64, contentType). REST tools and existing queue/context/resolve tools work without the optional hosted MCP endpoint.
64
+ Exit codes: **0** success, **1** request/runtime failure, **2** usage/confirmation/cancellation, **4** sign-in required, **5** forbidden. Errors are JSON on stderr with a stable `error.code` and actionable message. Sign-in explicitly before unattended jobs; no interactive approval is attempted by a resource command.
87
65
 
88
- Use a PAT: read:agent for reads, write:triage for triage, write:admin plus an appropriate organization role for project/channel/rule/monitor/release/team management. Credential changes and OAuth consent remain session/login operations. No permission bypass. Application response text is untrusted data. Default cloud endpoint: https://app.epure.sh.
66
+ ## Development
89
67
 
90
- For full management consent at login: `EPURE_SCOPES="read:agent write:triage write:admin" epure login` (or `epure-mcp login`). Existing credentials need reauthentication or a suitably scoped PAT.
68
+ See [research and design](https://github.com/epure-sh/epure/blob/main/docs/cli-mcp-redesign.md). Both npm packages ship identical shared runtime modules, enforced by regression tests. The Cargo `epure-cli` binary embeds and launches this same runtime, and also requires Node.js 18+.
package/bin/epure-cli.js CHANGED
@@ -1,517 +1,3 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * @epure-sh/cli - Official Command-Line Interface for Epure
5
- * Triage errors, inspect issue queues, demangle stack traces, and manage workspaces.
6
- */
7
-
8
- const http = require('http');
9
- const https = require('https');
10
- const { URL } = require('url');
11
- const auth = require('../lib/auth');
12
-
13
- const VERSION = '0.1.8';
14
-
15
- const args = process.argv.slice(2);
16
-
17
- function printHelp() {
18
- console.log(`
19
- @epure-sh/cli - Epure Command-Line Interface (v${VERSION})
20
-
21
- Usage:
22
- npx @epure-sh/cli <command> [options]
23
- epure <command> [options]
24
-
25
- Authentication:
26
- login Authenticate via browser OAuth (loopback or device code)
27
- logout Remove saved credentials for current or specified instance
28
- use <url> Switch active Epure instance
29
- instances List all configured Epure instances
30
- auth whoami Verify your active token and organization role
31
-
32
- Core Commands:
33
- queue List unresolved production issue queue
34
- context <issue_id> Export demangled stack trace and code lines for an issue
35
- issue show <issue_id> View full metadata for a specific issue
36
- triage resolve <issue_id> Mark an issue resolved (optional: --release <rel>)
37
- triage ignore <issue_id> Ignore an issue from alerting
38
- triage reopen <issue_id> Reopen a resolved or ignored issue
39
- alerts List recent alerting events (optional: --view unread)
40
- api-catalog List available product API actions
41
- api <METHOD> <PATH> [JSON|@file|-] Execute any authorized API action
42
-
43
- Admin / Outbound Sales:
44
- admin create-canary Pre-provision an isolated Epure Cloud workspace with live DSN & Magic Link
45
- Flags: --domain <d>, --email <e>, [--name <n>], [--error <err>]
46
-
47
- Global Options:
48
- --token, -t <token> Override Epure Personal Access Token
49
- --url, -u <url> Override Epure endpoint (default: current instance or https://app.epure.sh)
50
- --device Force RFC 8628 Device Code flow (for SSH/headless)
51
- --output, -o <json|table> Output format (default: json)
52
- --help, -h Display this help message
53
- --version, -v Display CLI version
54
-
55
- Examples:
56
- epure login
57
- epure login --url http://localhost:8080
58
- epure use https://app.epure.sh
59
- epure queue --limit 20
60
- epure context 550e8400-e29b-41d4-a716-446655440000 --format markdown
61
- epure triage resolve 550e8400-e29b-41d4-a716-446655440000 --release v1.2.0
62
- `);
63
- }
64
-
65
- if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
66
- printHelp();
67
- process.exit(0);
68
- }
69
-
70
- if (args.includes('--version') || args.includes('-v')) {
71
- console.log(`@epure-sh/cli v${VERSION}`);
72
- process.exit(0);
73
- }
74
-
75
- // Parse top-level flags
76
- let explicitToken = process.env.EPURE_TOKEN || '';
77
- let explicitUrl = process.env.EPURE_URL || '';
78
- let forceDevice = false;
79
- let outputFormat = 'json';
80
-
81
- const filteredArgs = [];
82
- for (let i = 0; i < args.length; i++) {
83
- const arg = args[i];
84
- if (arg === '--token' || arg === '-t') {
85
- explicitToken = args[++i];
86
- } else if (arg.startsWith('--token=')) {
87
- explicitToken = arg.split('=')[1];
88
- } else if (arg === '--url' || arg === '-u') {
89
- explicitUrl = args[++i];
90
- } else if (arg.startsWith('--url=')) {
91
- explicitUrl = arg.split('=')[1];
92
- } else if (arg === '--device' || arg === '--no-browser') {
93
- forceDevice = true;
94
- } else if (arg === '--output' || arg === '-o') {
95
- outputFormat = args[++i];
96
- } else if (arg.startsWith('--output=')) {
97
- outputFormat = arg.split('=')[1];
98
- } else {
99
- filteredArgs.push(arg);
100
- }
101
- }
102
-
103
- let activeToken = explicitToken;
104
- let baseUrl = explicitUrl;
105
-
106
- async function resolveAuthentication() {
107
- if (explicitToken) {
108
- baseUrl = auth.normalizeUrl(explicitUrl || 'https://app.epure.sh');
109
- return explicitToken;
110
- }
111
-
112
- const cred = auth.getActiveCredentials(explicitUrl);
113
- if (cred) {
114
- baseUrl = cred.url;
115
- try {
116
- activeToken = await auth.ensureValidToken(cred);
117
- return activeToken;
118
- } catch {
119
- activeToken = cred.access_token;
120
- return activeToken;
121
- }
122
- }
123
-
124
- baseUrl = auth.normalizeUrl(explicitUrl || 'https://app.epure.sh');
125
- return '';
126
- }
127
-
128
- function request(method, path, body = null, headers = {}) {
129
- return new Promise((resolve, reject) => {
130
- if (!activeToken && !path.startsWith('/api/v1/auth/')) {
131
- return reject(
132
- new Error(
133
- 'Authentication required. Run "epure login" or pass --token <token>'
134
- )
135
- );
136
- }
137
-
138
- const fullUrl =
139
- path.startsWith('http://') || path.startsWith('https://')
140
- ? path
141
- : `${baseUrl}${path}`;
142
- const urlObj = new URL(fullUrl);
143
- const isHttps = urlObj.protocol === 'https:';
144
- const client = isHttps ? https : http;
145
-
146
- const payload = body
147
- ? typeof body === 'string'
148
- ? body
149
- : JSON.stringify(body)
150
- : null;
151
-
152
- const reqHeaders = {
153
- 'User-Agent': `@epure-sh/cli/${VERSION}`,
154
- ...headers,
155
- };
156
- if (activeToken) {
157
- reqHeaders['Authorization'] = `Bearer ${activeToken}`;
158
- }
159
- if (payload && !reqHeaders['Content-Type']) {
160
- reqHeaders['Content-Type'] = 'application/json';
161
- reqHeaders['Content-Length'] = Buffer.byteLength(payload);
162
- }
163
-
164
- const req = client.request(
165
- fullUrl,
166
- {
167
- method,
168
- headers: reqHeaders,
169
- },
170
- (res) => {
171
- let resBody = '';
172
- res.on('data', (chunk) => {
173
- resBody += chunk;
174
- });
175
- res.on('end', () => {
176
- if (res.statusCode >= 200 && res.statusCode < 300) {
177
- resolve({
178
- status: res.statusCode,
179
- headers: res.headers,
180
- text: resBody,
181
- json: () => {
182
- try {
183
- return JSON.parse(resBody);
184
- } catch {
185
- return resBody;
186
- }
187
- },
188
- });
189
- } else {
190
- let errDetail = resBody;
191
- try {
192
- const parsed = JSON.parse(resBody);
193
- errDetail = parsed.error_description || parsed.error || parsed.message || resBody;
194
- } catch {}
195
- reject(new Error(`HTTP ${res.statusCode}: ${errDetail}`));
196
- }
197
- });
198
- }
199
- );
200
-
201
- req.on('error', reject);
202
- if (payload) {
203
- req.write(payload);
204
- }
205
- req.end();
206
- });
207
- }
208
-
209
- function printOutput(data, tableRenderer = null) {
210
- if (outputFormat === 'table' && tableRenderer) {
211
- tableRenderer(data);
212
- } else {
213
- console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
214
- }
215
- }
216
-
217
- async function handleCommand() {
218
- const cmd = filteredArgs[0];
219
- const subCmd = filteredArgs[1];
220
-
221
- if (cmd === 'login') {
222
- const res = await auth.login({
223
- url: explicitUrl,
224
- device: forceDevice,
225
- clientId: 'epure-cli',
226
- });
227
- console.log(`\n✓ Successfully authenticated with ${res.url}!`);
228
- console.log(` Organization: ${res.org_id}`);
229
- console.log(` Credentials saved to ${auth.getCredentialsPath()}\n`);
230
- return;
231
- }
232
-
233
- if (cmd === 'logout') {
234
- const removed = auth.removeInstance(explicitUrl);
235
- console.log(`✓ Logged out of ${removed}. Credentials removed.\n`);
236
- return;
237
- }
238
-
239
- if (cmd === 'use') {
240
- const target = filteredArgs[1];
241
- if (!target) {
242
- throw new Error('Usage: epure use <url>');
243
- }
244
- const switched = auth.switchInstance(target);
245
- console.log(`✓ Active instance switched to: ${switched}\n`);
246
- return;
247
- }
248
-
249
- if (cmd === 'instances') {
250
- const info = auth.listInstances();
251
- console.log('\nConfigured Epure Instances:');
252
- if (info.instances.length === 0) {
253
- console.log(' (no instances configured. Run "epure login")\n');
254
- return;
255
- }
256
- for (const inst of info.instances) {
257
- const active = inst === info.current_instance ? '* ' : ' ';
258
- console.log(`${active}${inst}`);
259
- }
260
- console.log('');
261
- return;
262
- }
263
-
264
- // Admin / Canary Provisioning command
265
- if (cmd === 'admin') {
266
- let domain = '';
267
- let email = '';
268
- let name = '';
269
- let error = '';
270
- let secret = process.env.EPURE_INTERNAL_SECRET || explicitToken || 'epure_dev_internal_secret';
271
-
272
- for (let i = 1; i < filteredArgs.length; i++) {
273
- const a = filteredArgs[i];
274
- if (a === '--domain') domain = filteredArgs[++i];
275
- else if (a === '--email') email = filteredArgs[++i];
276
- else if (a === '--name') name = filteredArgs[++i];
277
- else if (a === '--error') error = filteredArgs[++i];
278
- else if (a === '--secret') secret = filteredArgs[++i];
279
- }
280
-
281
- if (subCmd === 'create-canary') {
282
- if (!domain) {
283
- throw new Error('Usage: epure admin create-canary --domain <domain> --email <email> [--name <name>] [--error <error>]');
284
- }
285
-
286
- if (secret) {
287
- activeToken = secret;
288
- }
289
- if (!baseUrl || baseUrl === 'https://app.epure.sh') {
290
- baseUrl = process.env.EPURE_CLOUD_URL || explicitUrl || 'https://cloud.epure.sh';
291
- }
292
- if (!activeToken) {
293
- await resolveAuthentication();
294
- }
295
-
296
- const body = {
297
- domain,
298
- email: email || undefined,
299
- app_name: name || domain.replace(/\./g, '-'),
300
- seed_error: error || undefined,
301
- };
302
-
303
- const res = await request('POST', '/api/internal/canary', body);
304
- const data = res.json();
305
-
306
- printOutput(data, (info) => {
307
- console.log('\n⚡ Epure Cloud Canary Provisioned Successfully!');
308
- console.log('───────────────────────────────────────────────────────');
309
- console.log(` Domain: ${info.domain || domain}`);
310
- if (email) console.log(` Prospect Email: ${email}`);
311
- console.log(` Live DSN: ${info.dsn}`);
312
- console.log(` 1-Click Magic: ${info.magic_link}`);
313
- console.log(` MCP Command: ${info.mcp_cmd}`);
314
- console.log(' Validity: 14 Days (Zero password/signup prompt)');
315
- console.log('───────────────────────────────────────────────────────');
316
- console.log(' Seeded Exceptions:');
317
- console.log(` 1. [Primary] ${error || 'TypeError: Cannot read properties of undefined (reading auth)'}`);
318
- console.log(' 2. [Timeout] DatabaseError: connection pool exhausted (timeout after 5000ms)');
319
- console.log(' 3. [Webhook] UnhandledPromiseRejection: failed to process webhook payment payload\n');
320
- });
321
- return;
322
- } else {
323
- throw new Error(`Unknown admin command: ${subCmd}. Usage: epure admin create-canary`);
324
- }
325
- }
326
-
327
- if (cmd === 'api-catalog') { printOutput(require('../lib/api-catalog.json')); return; }
328
-
329
- // All other commands require authentication
330
- await resolveAuthentication();
331
- if (!activeToken) {
332
- if (process.stdin.isTTY) {
333
- console.log('No active session found. Initiating login...\n');
334
- await auth.login({
335
- url: explicitUrl,
336
- device: forceDevice,
337
- clientId: 'epure-cli',
338
- });
339
- await resolveAuthentication();
340
- } else {
341
- throw new Error(
342
- 'Authentication required. Run "epure login" or set EPURE_TOKEN.'
343
- );
344
- }
345
- }
346
-
347
- switch (cmd) {
348
- case 'auth': {
349
- if (subCmd === 'whoami') {
350
- const res = await request('GET', '/api/v1/agent/whoami');
351
- printOutput(res.json(), (data) => {
352
- console.log('\nAuthenticated Agent Identity:');
353
- console.log(` Endpoint: ${baseUrl}`);
354
- console.log(` User ID: ${data.user_id}`);
355
- console.log(` Org ID: ${data.org_id}`);
356
- console.log(` Email: ${data.email || 'N/A'}`);
357
- console.log(` Role: ${data.role}`);
358
- console.log(` Token Scope: ${(data.scopes || []).join(', ') || 'full'}\n`);
359
- });
360
- } else {
361
- throw new Error(`Unknown auth command: ${subCmd}. Usage: epure auth whoami`);
362
- }
363
- break;
364
- }
365
-
366
- case 'queue': {
367
- let limit = 20;
368
- let project = '';
369
- let env = '';
370
- let q = '';
371
-
372
- for (let i = 1; i < filteredArgs.length; i++) {
373
- const a = filteredArgs[i];
374
- if (a === '--limit') limit = parseInt(filteredArgs[++i], 10);
375
- else if (a === '--project') project = filteredArgs[++i];
376
- else if (a === '--env') env = filteredArgs[++i];
377
- else if (a === '--query' || a === '-q') q = filteredArgs[++i];
378
- }
379
-
380
- const params = new URLSearchParams({ limit: limit.toString() });
381
- if (project) params.set('project_id', project);
382
- if (env) params.set('environment', env);
383
- if (q) params.set('query', q);
384
-
385
- const res = await request('GET', `/api/v1/agent/queue?${params.toString()}`);
386
- const data = res.json();
387
-
388
- printOutput(data, (body) => {
389
- const issues = body.issues || [];
390
- console.log(`\nUnresolved Production Queue (${issues.length} issues):\n`);
391
- if (issues.length === 0) {
392
- console.log(' All clear! No unresolved issues.');
393
- return;
394
- }
395
- for (const iss of issues) {
396
- const count = String(iss.event_count || 1).padStart(5, ' ');
397
- const title = iss.title || 'Unknown error';
398
- console.log(` [${count} ev] ${iss.id} - ${title}`);
399
- if (iss.culprit) {
400
- console.log(` Culprit: ${iss.culprit}`);
401
- }
402
- }
403
- console.log('');
404
- });
405
- break;
406
- }
407
-
408
- case 'context': {
409
- const issueId = filteredArgs[1];
410
- if (!issueId) {
411
- throw new Error('Usage: epure context <issue_id> [--format markdown|json]');
412
- }
413
-
414
- let format = 'markdown';
415
- for (let i = 2; i < filteredArgs.length; i++) {
416
- if (filteredArgs[i] === '--format') format = filteredArgs[++i];
417
- }
418
-
419
- const params = new URLSearchParams({ issue_id: issueId, format });
420
- const res = await request('GET', `/api/v1/agent/issues/${encodeURIComponent(issueId)}/context?${params.toString()}`);
421
-
422
- if (format === 'markdown') {
423
- console.log(res.text);
424
- } else {
425
- printOutput(res.json());
426
- }
427
- break;
428
- }
429
-
430
- case 'issue': {
431
- if (subCmd === 'show') {
432
- const issueId = filteredArgs[2];
433
- if (!issueId) {
434
- throw new Error('Usage: epure issue show <issue_id>');
435
- }
436
- const res = await request('GET', `/api/v1/agent/issues/${issueId}`);
437
- printOutput(res.json());
438
- } else {
439
- throw new Error(`Unknown issue command: ${subCmd}. Usage: epure issue show <issue_id>`);
440
- }
441
- break;
442
- }
443
-
444
- case 'triage': {
445
- const action = subCmd;
446
- const issueId = filteredArgs[2];
447
-
448
- if (!action || !issueId) {
449
- throw new Error(
450
- 'Usage: epure triage <resolve|ignore|reopen|snooze> <issue_id> [flags]'
451
- );
452
- }
453
-
454
- let payload = { action };
455
-
456
- for (let i = 3; i < filteredArgs.length; i++) {
457
- const a = filteredArgs[i];
458
- if (a === '--release') payload.release = filteredArgs[++i];
459
- else if (a === '--commit') payload.commit_sha = filteredArgs[++i];
460
- else if (a === '--mode') payload.snooze_mode = filteredArgs[++i];
461
- else if (a === '--hours') payload.snooze_hours = parseInt(filteredArgs[++i], 10);
462
- else if (a === '--count') payload.snooze_count = parseInt(filteredArgs[++i], 10);
463
- }
464
-
465
- const res = await request('POST', `/api/v1/agent/issues/${issueId}/triage`, payload);
466
- printOutput(res.json(), (data) => {
467
- console.log(`\n✓ Issue ${issueId} triaged: ${data.status || action}\n`);
468
- });
469
- break;
470
- }
471
-
472
- case 'alerts': {
473
- let view = '';
474
- for (let i = 1; i < filteredArgs.length; i++) {
475
- if (filteredArgs[i] === '--view') view = filteredArgs[++i];
476
- }
477
- const params = new URLSearchParams();
478
- if (view) params.set('view', view);
479
-
480
- const res = await request('GET', `/api/v1/agent/alerts?${params.toString()}`);
481
- printOutput(res.json());
482
- break;
483
- }
484
-
485
- case 'api-catalog': { printOutput(require('../lib/api-catalog.json')); break; }
486
-
487
- case 'api': {
488
- const method = (filteredArgs[1] || 'GET').toUpperCase();
489
- const path = filteredArgs[2];
490
- if (!path) {
491
- throw new Error('Usage: epure api <METHOD> <PATH> [JSON_BODY]');
492
- }
493
- const input = filteredArgs[3]?.startsWith('--') ? null : filteredArgs[3];
494
- const rawBody = input === '-' ? require('fs').readFileSync(0,'utf8') : input?.startsWith('@') ? require('fs').readFileSync(input.slice(1),'utf8') : input || null;
495
- if (!path.startsWith('/api/v1/')) throw new Error('Path must start with /api/v1/');
496
- const files=[];
497
- for(let i=3;i<filteredArgs.length;i++)if(filteredArgs[i]==='--file'){
498
- const spec=filteredArgs[++i];if(!spec?.includes('='))throw new Error('--file expects field=filename');
499
- const split=spec.indexOf('=');const filename=spec.slice(split+1);
500
- files.push({field:spec.slice(0,split),filename:require('path').basename(filename),base64:require('fs').readFileSync(filename).toString('base64')});
501
- }
502
- const result=await require('../lib/api').apiRequest(baseUrl,activeToken,{method,path,body:rawBody?JSON.parse(rawBody):undefined,...(files.length?{files}:{})});
503
- printOutput(result.data);
504
- break;
505
- }
506
-
507
- default:
508
- console.error(`Unknown command: ${cmd}\n`);
509
- printHelp();
510
- process.exit(1);
511
- }
512
- }
513
-
514
- handleCommand().catch((err) => {
515
- console.error(`\nError: ${err.message}\n`);
516
- process.exit(1);
517
- });
2
+ 'use strict';
3
+ require('../lib/cli').run(process.argv.slice(2), { version: require('../package.json').version });