@epure-sh/cli 0.1.7 → 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 +47 -59
- package/bin/epure-cli.js +2 -357
- package/lib/api-catalog.json +470 -0
- package/lib/api.js +41 -0
- package/lib/auth.js +175 -0
- package/lib/cli.js +146 -0
- package/lib/mcp.js +87 -0
- package/lib/operations.js +83 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,80 +1,68 @@
|
|
|
1
1
|
# @epure-sh/cli
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
3
|
+
Manage [Epure](https://epure.sh) from a terminal or agent. Node.js 18+.
|
|
5
4
|
|
|
6
|
-
|
|
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
|
-
|
|
32
|
-
epure
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
epure
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
epure
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
epure triage ignore <ISSUE_UUID>
|
|
50
|
+
## Scripts and agents
|
|
63
51
|
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
epure
|
|
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
|
-
|
|
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.
|
|
63
|
+
|
|
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.
|
|
77
65
|
|
|
78
|
-
##
|
|
66
|
+
## Development
|
|
79
67
|
|
|
80
|
-
|
|
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,358 +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 fs = require('fs');
|
|
11
|
-
const { URL } = require('url');
|
|
12
|
-
|
|
13
|
-
const VERSION = '0.1.7';
|
|
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
|
-
Core Commands:
|
|
26
|
-
auth whoami Verify your active token and organization role
|
|
27
|
-
queue List unresolved production issue queue
|
|
28
|
-
context <issue_id> Export demangled stack trace and code lines for an issue
|
|
29
|
-
issue show <issue_id> View full metadata for a specific issue
|
|
30
|
-
triage resolve <issue_id> Mark an issue resolved (optional: --release <rel>)
|
|
31
|
-
triage ignore <issue_id> Ignore an issue from alerting
|
|
32
|
-
triage reopen <issue_id> Reopen a resolved or ignored issue
|
|
33
|
-
triage snooze <issue_id> Snooze an issue (optional: --mode hours|count)
|
|
34
|
-
alerts List recent alerting events (optional: --view unread)
|
|
35
|
-
api <METHOD> <PATH> Execute raw authenticated API call against workspace
|
|
36
|
-
|
|
37
|
-
Global Options:
|
|
38
|
-
--token, -t <token> Epure Personal Access Token (or EPURE_TOKEN env var)
|
|
39
|
-
--url, -u <url> Epure endpoint (default: https://epure.sh or EPURE_URL)
|
|
40
|
-
--output, -o <json|table> Output format (default: json)
|
|
41
|
-
--help, -h Display this help message
|
|
42
|
-
--version, -v Display CLI version
|
|
43
|
-
|
|
44
|
-
Examples:
|
|
45
|
-
export EPURE_TOKEN="epk_live_..."
|
|
46
|
-
npx @epure-sh/cli auth whoami
|
|
47
|
-
npx @epure-sh/cli --output table queue --limit 20
|
|
48
|
-
npx @epure-sh/cli context 550e8400-e29b-41d4-a716-446655440000 --format markdown
|
|
49
|
-
npx @epure-sh/cli triage resolve 550e8400-e29b-41d4-a716-446655440000 --release v1.2.0
|
|
50
|
-
`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
54
|
-
printHelp();
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
59
|
-
console.log(`@epure-sh/cli v${VERSION}`);
|
|
60
|
-
process.exit(0);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Parse top-level flags
|
|
64
|
-
let token = process.env.EPURE_TOKEN || '';
|
|
65
|
-
let baseUrl = process.env.EPURE_URL || 'https://epure.sh';
|
|
66
|
-
let outputFormat = 'json';
|
|
67
|
-
|
|
68
|
-
const filteredArgs = [];
|
|
69
|
-
for (let i = 0; i < args.length; i++) {
|
|
70
|
-
const arg = args[i];
|
|
71
|
-
if (arg === '--token' || arg === '-t') {
|
|
72
|
-
token = args[++i];
|
|
73
|
-
} else if (arg.startsWith('--token=')) {
|
|
74
|
-
token = arg.split('=')[1];
|
|
75
|
-
} else if (arg === '--url' || arg === '-u') {
|
|
76
|
-
baseUrl = args[++i];
|
|
77
|
-
} else if (arg.startsWith('--url=')) {
|
|
78
|
-
baseUrl = arg.split('=')[1];
|
|
79
|
-
} else if (arg === '--output' || arg === '-o') {
|
|
80
|
-
outputFormat = args[++i];
|
|
81
|
-
} else if (arg.startsWith('--output=')) {
|
|
82
|
-
outputFormat = arg.split('=')[1];
|
|
83
|
-
} else {
|
|
84
|
-
filteredArgs.push(arg);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
89
|
-
|
|
90
|
-
function request(method, path, body = null, headers = {}) {
|
|
91
|
-
return new Promise((resolve, reject) => {
|
|
92
|
-
if (!token && !path.startsWith('/api/v1/auth/')) {
|
|
93
|
-
return reject(new Error('EPURE_TOKEN is required. Pass --token <token> or export EPURE_TOKEN="epk_..."'));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const fullUrl = path.startsWith('http://') || path.startsWith('https://') ? path : `${baseUrl}${path}`;
|
|
97
|
-
const urlObj = new URL(fullUrl);
|
|
98
|
-
const isHttps = urlObj.protocol === 'https:';
|
|
99
|
-
const client = isHttps ? https : http;
|
|
100
|
-
|
|
101
|
-
const payload = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
|
|
102
|
-
|
|
103
|
-
const reqHeaders = {
|
|
104
|
-
'User-Agent': `@epure-sh/cli/${VERSION}`,
|
|
105
|
-
...headers,
|
|
106
|
-
};
|
|
107
|
-
if (token) {
|
|
108
|
-
reqHeaders['Authorization'] = `Bearer ${token}`;
|
|
109
|
-
}
|
|
110
|
-
if (payload && !reqHeaders['Content-Type']) {
|
|
111
|
-
reqHeaders['Content-Type'] = 'application/json';
|
|
112
|
-
reqHeaders['Content-Length'] = Buffer.byteLength(payload);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const req = client.request(
|
|
116
|
-
fullUrl,
|
|
117
|
-
{
|
|
118
|
-
method,
|
|
119
|
-
headers: reqHeaders,
|
|
120
|
-
},
|
|
121
|
-
(res) => {
|
|
122
|
-
let resBody = '';
|
|
123
|
-
res.on('data', (chunk) => {
|
|
124
|
-
resBody += chunk;
|
|
125
|
-
});
|
|
126
|
-
res.on('end', () => {
|
|
127
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
128
|
-
resolve({
|
|
129
|
-
status: res.statusCode,
|
|
130
|
-
headers: res.headers,
|
|
131
|
-
text: resBody,
|
|
132
|
-
json: () => {
|
|
133
|
-
try {
|
|
134
|
-
return JSON.parse(resBody);
|
|
135
|
-
} catch {
|
|
136
|
-
return resBody;
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
} else {
|
|
141
|
-
let errDetail = resBody;
|
|
142
|
-
try {
|
|
143
|
-
const parsed = JSON.parse(resBody);
|
|
144
|
-
errDetail = parsed.error || parsed.message || resBody;
|
|
145
|
-
} catch {}
|
|
146
|
-
reject(new Error(`HTTP ${res.statusCode}: ${errDetail}`));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
req.on('error', reject);
|
|
153
|
-
if (payload) {
|
|
154
|
-
req.write(payload);
|
|
155
|
-
}
|
|
156
|
-
req.end();
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function printOutput(data, tableRenderer = null) {
|
|
161
|
-
if (outputFormat === 'table' && tableRenderer) {
|
|
162
|
-
tableRenderer(data);
|
|
163
|
-
} else {
|
|
164
|
-
console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async function handleCommand() {
|
|
169
|
-
const cmd = filteredArgs[0];
|
|
170
|
-
const subCmd = filteredArgs[1];
|
|
171
|
-
|
|
172
|
-
switch (cmd) {
|
|
173
|
-
case 'auth': {
|
|
174
|
-
if (subCmd === 'whoami') {
|
|
175
|
-
const res = await request('GET', '/api/v1/agent/whoami');
|
|
176
|
-
printOutput(res.json(), (data) => {
|
|
177
|
-
console.log('\nAuthenticated Agent Identity:');
|
|
178
|
-
console.log(` User ID: ${data.user_id}`);
|
|
179
|
-
console.log(` Org ID: ${data.org_id}`);
|
|
180
|
-
console.log(` Email: ${data.email || 'N/A'}`);
|
|
181
|
-
console.log(` Role: ${data.role}`);
|
|
182
|
-
console.log(` Token Scope: ${(data.scopes || []).join(', ') || 'full'}\n`);
|
|
183
|
-
});
|
|
184
|
-
} else {
|
|
185
|
-
throw new Error(`Unknown auth command: ${subCmd}. Usage: epure auth whoami`);
|
|
186
|
-
}
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
case 'queue': {
|
|
191
|
-
let limit = 20;
|
|
192
|
-
let project = '';
|
|
193
|
-
let env = '';
|
|
194
|
-
let q = '';
|
|
195
|
-
|
|
196
|
-
for (let i = 1; i < filteredArgs.length; i++) {
|
|
197
|
-
const a = filteredArgs[i];
|
|
198
|
-
if (a === '--limit') limit = parseInt(filteredArgs[++i], 10);
|
|
199
|
-
else if (a === '--project') project = filteredArgs[++i];
|
|
200
|
-
else if (a === '--env') env = filteredArgs[++i];
|
|
201
|
-
else if (a === '--q') q = filteredArgs[++i];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let path = `/api/v1/agent/queue?limit=${limit}`;
|
|
205
|
-
if (project) path += `&project_id=${encodeURIComponent(project)}`;
|
|
206
|
-
if (env) path += `&environment=${encodeURIComponent(env)}`;
|
|
207
|
-
if (q) path += `&q=${encodeURIComponent(q)}`;
|
|
208
|
-
|
|
209
|
-
const res = await request('GET', path);
|
|
210
|
-
const data = res.json();
|
|
211
|
-
printOutput(data, (queue) => {
|
|
212
|
-
const issues = Array.isArray(queue) ? queue : queue.issues || [];
|
|
213
|
-
if (issues.length === 0) {
|
|
214
|
-
console.log('No unresolved issues in queue.');
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
console.log(`\nUnresolved Queue (${issues.length} issues):`);
|
|
218
|
-
console.log('─'.repeat(100));
|
|
219
|
-
console.log(
|
|
220
|
-
'ID'.padEnd(38) +
|
|
221
|
-
'LEVEL'.padEnd(10) +
|
|
222
|
-
'EVENTS'.padEnd(10) +
|
|
223
|
-
'USERS'.padEnd(10) +
|
|
224
|
-
'TITLE'
|
|
225
|
-
);
|
|
226
|
-
console.log('─'.repeat(100));
|
|
227
|
-
for (const item of issues) {
|
|
228
|
-
const id = (item.id || '').padEnd(38);
|
|
229
|
-
const lvl = (item.level || 'error').padEnd(10);
|
|
230
|
-
const evts = String(item.event_count || item.events || 1).padEnd(10);
|
|
231
|
-
const users = String(item.unique_user_count || item.users || 1).padEnd(10);
|
|
232
|
-
const title = (item.title || item.fingerprint || '').slice(0, 40);
|
|
233
|
-
console.log(`${id}${lvl}${evts}${users}${title}`);
|
|
234
|
-
}
|
|
235
|
-
console.log('─'.repeat(100) + '\n');
|
|
236
|
-
});
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
case 'context': {
|
|
241
|
-
const issueId = filteredArgs[1];
|
|
242
|
-
if (!issueId) {
|
|
243
|
-
throw new Error('Usage: epure context <issue_id> [--format markdown|json] [--out <file>]');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
let format = 'markdown';
|
|
247
|
-
let outFile = '';
|
|
248
|
-
for (let i = 2; i < filteredArgs.length; i++) {
|
|
249
|
-
const a = filteredArgs[i];
|
|
250
|
-
if (a === '--format') format = filteredArgs[++i];
|
|
251
|
-
else if (a === '--out') outFile = filteredArgs[++i];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const res = await request('GET', `/api/v1/agent/issues/${issueId}/context?format=${format}`);
|
|
255
|
-
const content = format === 'markdown' ? res.text : JSON.stringify(res.json(), null, 2);
|
|
256
|
-
|
|
257
|
-
if (outFile) {
|
|
258
|
-
fs.writeFileSync(outFile, content, 'utf8');
|
|
259
|
-
console.log(`Saved context for issue ${issueId} to ${outFile}`);
|
|
260
|
-
} else {
|
|
261
|
-
console.log(content);
|
|
262
|
-
}
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
case 'issue': {
|
|
267
|
-
if (subCmd === 'show') {
|
|
268
|
-
const issueId = filteredArgs[2];
|
|
269
|
-
if (!issueId) throw new Error('Usage: epure issue show <issue_id>');
|
|
270
|
-
const res = await request('GET', `/api/v1/agent/issues/${issueId}`);
|
|
271
|
-
printOutput(res.json());
|
|
272
|
-
} else {
|
|
273
|
-
throw new Error(`Unknown issue command: ${subCmd}. Usage: epure issue show <issue_id>`);
|
|
274
|
-
}
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
case 'triage': {
|
|
279
|
-
const action = subCmd;
|
|
280
|
-
const issueId = filteredArgs[2];
|
|
281
|
-
if (!issueId) {
|
|
282
|
-
throw new Error(`Usage: epure triage ${action || 'resolve|ignore|reopen|snooze'} <issue_id>`);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (action === 'resolve') {
|
|
286
|
-
let release = '';
|
|
287
|
-
for (let i = 3; i < filteredArgs.length; i++) {
|
|
288
|
-
if (filteredArgs[i] === '--release') release = filteredArgs[++i];
|
|
289
|
-
}
|
|
290
|
-
const body = { status: 'resolved' };
|
|
291
|
-
if (release) body.resolved_in_release = release;
|
|
292
|
-
const res = await request('PATCH', `/api/v1/issues/${issueId}`, body);
|
|
293
|
-
printOutput(res.json());
|
|
294
|
-
} else if (action === 'ignore') {
|
|
295
|
-
const res = await request('PATCH', `/api/v1/issues/${issueId}`, { status: 'ignored' });
|
|
296
|
-
printOutput(res.json());
|
|
297
|
-
} else if (action === 'reopen') {
|
|
298
|
-
const res = await request('PATCH', `/api/v1/issues/${issueId}`, { status: 'unresolved' });
|
|
299
|
-
printOutput(res.json());
|
|
300
|
-
} else if (action === 'snooze') {
|
|
301
|
-
let mode = 'hours';
|
|
302
|
-
for (let i = 3; i < filteredArgs.length; i++) {
|
|
303
|
-
if (filteredArgs[i] === '--mode') mode = filteredArgs[++i];
|
|
304
|
-
}
|
|
305
|
-
const res = await request('POST', `/api/v1/issues/${issueId}/snooze`, { mode });
|
|
306
|
-
printOutput(res.json());
|
|
307
|
-
} else {
|
|
308
|
-
throw new Error(`Unknown triage action: ${action}. Use: resolve, ignore, reopen, or snooze`);
|
|
309
|
-
}
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
case 'alerts': {
|
|
314
|
-
let view = 'unread';
|
|
315
|
-
let project = '';
|
|
316
|
-
let limit = 20;
|
|
317
|
-
|
|
318
|
-
for (let i = 1; i < filteredArgs.length; i++) {
|
|
319
|
-
const a = filteredArgs[i];
|
|
320
|
-
if (a === '--view') view = filteredArgs[++i];
|
|
321
|
-
else if (a === '--project') project = filteredArgs[++i];
|
|
322
|
-
else if (a === '--limit') limit = parseInt(filteredArgs[++i], 10);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
let path = `/api/v1/alerts?view=${view}&limit=${limit}`;
|
|
326
|
-
if (project) path += `&project_id=${encodeURIComponent(project)}`;
|
|
327
|
-
|
|
328
|
-
const res = await request('GET', path);
|
|
329
|
-
printOutput(res.json());
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
case 'api': {
|
|
334
|
-
const method = (filteredArgs[1] || 'GET').toUpperCase();
|
|
335
|
-
const apiPath = filteredArgs[2];
|
|
336
|
-
if (!apiPath) throw new Error('Usage: epure api <METHOD> <PATH> [--body <json>]');
|
|
337
|
-
|
|
338
|
-
let body = null;
|
|
339
|
-
for (let i = 3; i < filteredArgs.length; i++) {
|
|
340
|
-
if (filteredArgs[i] === '--body') body = filteredArgs[++i];
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const res = await request(method, apiPath, body);
|
|
344
|
-
printOutput(res.json());
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
default:
|
|
349
|
-
console.error(`Unknown command: ${cmd}`);
|
|
350
|
-
printHelp();
|
|
351
|
-
process.exit(1);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
handleCommand().catch((err) => {
|
|
356
|
-
console.error(`Error: ${err.message}`);
|
|
357
|
-
process.exit(1);
|
|
358
|
-
});
|
|
2
|
+
'use strict';
|
|
3
|
+
require('../lib/cli').run(process.argv.slice(2), { version: require('../package.json').version });
|