@corelayer-ai/cli 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/commands/anomalies.js +76 -0
- package/dist/commands/api-keys.js +1 -6
- package/dist/commands/config.js +3 -2
- package/dist/commands/integrations.js +3 -8
- package/dist/commands/issues.js +182 -193
- package/dist/commands/login.js +1 -6
- package/dist/commands/logout.js +21 -3
- package/dist/commands/preflight.js +65 -34
- package/dist/commands/schedules.js +51 -0
- package/dist/index.js +13 -3
- package/dist/lib/api-client.js +9 -0
- package/dist/lib/args.js +4 -0
- package/package.json +4 -2
- package/skill/SKILL.md +298 -108
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# CLI credential security
|
|
2
|
+
|
|
3
|
+
Browser login requires a user session and issues a key valid for 24 hours. API keys cannot authorize another CLI login. Sign in again when the key expires.
|
|
4
|
+
|
|
5
|
+
User-owned API keys require a current organization membership on every authentication. Their effective role cannot exceed either the key's stored role or the owner's current role. Removing a user blocks their keys once the membership change reaches Corelayer; demoting an admin immediately limits their keys after synchronization. This also applies to user-owned automation keys. Keys with no user owner retain their existing service-key behavior and require explicit revocation/expiry management.
|
|
6
|
+
|
|
7
|
+
`corelayer logout` revokes the credential saved in `~/.corelayer/config.json` at its saved API URL before clearing the file. A failed revocation leaves the credential available for retry. `corelayer logout --local-only` explicitly removes the local copy without revoking the remote credential, for offline cleanup or an already expired/revoked key. A key in `CORELAYER_API_KEY` is not revoked by logout; unset that variable to stop using it and revoke it through key management when needed. Revoking a shared saved API key also affects other consumers of that same key.
|
|
8
|
+
|
|
9
|
+
`corelayer config get` and its JSON output omit the token. Preflight invokes Git without a shell, resolves refs to commit IDs, and disables external diff/text-conversion helpers.
|
|
10
|
+
|
|
11
|
+
## Deploying the fix
|
|
12
|
+
|
|
13
|
+
Deploy the platform database service and API server changes before distributing the updated CLI: logout requires the new `cli-auth.logout` operation and `/api/v1/cli/logout` endpoint. No schema migration is required.
|
|
14
|
+
|
|
15
|
+
Existing CLI keys do not retroactively acquire the new 24-hour expiry. Revoke those old keys through key management and have users log in again. Live membership/role enforcement applies to existing user-owned keys once the server change is deployed. For incident response, explicitly revoke affected keys rather than relying only on local logout or identity-provider synchronization.
|
|
16
|
+
|
|
17
|
+
These changes address the audited defects. They do not introduce OAuth PKCE, granular read-only/resource scopes, OS-keychain storage, npm provenance, or enterprise SSO-policy configuration; those remain separate enterprise-readiness work.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CorelayerClient } from "../lib/api-client.js";
|
|
2
|
+
import { readFlag } from "../lib/args.js";
|
|
3
|
+
import { readConfig, resolveApiUrl, resolveToken } from "../lib/config.js";
|
|
4
|
+
import { fail, printJson, printTable } from "../lib/output.js";
|
|
5
|
+
function printHelp() {
|
|
6
|
+
process.stdout
|
|
7
|
+
.write(`corelayer anomalies - Inspect configured anomaly monitors
|
|
8
|
+
|
|
9
|
+
USAGE:
|
|
10
|
+
corelayer anomalies <subcommand> [options]
|
|
11
|
+
|
|
12
|
+
SUBCOMMANDS:
|
|
13
|
+
list List anomaly monitors for a group
|
|
14
|
+
get <configId> Get one anomaly monitor
|
|
15
|
+
|
|
16
|
+
OPTIONS:
|
|
17
|
+
--group <id> Group ID (or set default-group via 'corelayer config set')
|
|
18
|
+
--limit <n> Maximum results (default: 20, max: 100)
|
|
19
|
+
--offset <n> Results to skip (default: 0)
|
|
20
|
+
--json Output as JSON
|
|
21
|
+
|
|
22
|
+
EXAMPLES:
|
|
23
|
+
corelayer anomalies list --group g_123
|
|
24
|
+
corelayer anomalies list --json
|
|
25
|
+
corelayer anomalies get 550e8400-e29b-41d4-a716-446655440000
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
export async function runAnomalies(args, ctx) {
|
|
29
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
30
|
+
printHelp();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const token = resolveToken();
|
|
34
|
+
if (!token) {
|
|
35
|
+
fail("Not authenticated. Set CORELAYER_API_KEY or run: corelayer login");
|
|
36
|
+
}
|
|
37
|
+
const client = new CorelayerClient(resolveApiUrl(ctx.apiUrlOverride), token);
|
|
38
|
+
if (args[0] === "get") {
|
|
39
|
+
const configId = args[1];
|
|
40
|
+
if (!configId)
|
|
41
|
+
fail("Usage: corelayer anomalies get <configId>");
|
|
42
|
+
const config = await client.anomalies.get(configId);
|
|
43
|
+
if (ctx.json)
|
|
44
|
+
return printJson(config);
|
|
45
|
+
printTable(["ID", "METRIC", "RESOURCE ID", "FREQUENCY", "TIME COLUMN"], [
|
|
46
|
+
[
|
|
47
|
+
config.id,
|
|
48
|
+
config.metric_name || "-",
|
|
49
|
+
config.resource_id,
|
|
50
|
+
config.frequency || "-",
|
|
51
|
+
config.time_column,
|
|
52
|
+
],
|
|
53
|
+
]);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (args[0] !== "list") {
|
|
57
|
+
fail("Usage: corelayer anomalies <list|get> [options]");
|
|
58
|
+
}
|
|
59
|
+
const groupId = readFlag(args, "--group") || readConfig().defaults?.group;
|
|
60
|
+
if (!groupId) {
|
|
61
|
+
fail("Group ID required. Use --group <id> or: corelayer config set default-group <id>");
|
|
62
|
+
}
|
|
63
|
+
const limit = Math.min(Math.max(Number.parseInt(readFlag(args, "--limit") || "20", 10) || 20, 1), 100);
|
|
64
|
+
const offset = Math.max(Number.parseInt(readFlag(args, "--offset") || "0", 10) || 0, 0);
|
|
65
|
+
const result = await client.anomalies.listOffset(groupId, limit, offset);
|
|
66
|
+
if (ctx.json)
|
|
67
|
+
return printJson(result);
|
|
68
|
+
printTable(["ID", "METRIC", "RESOURCE ID", "FREQUENCY", "PARTITIONS"], result.configs.map((config) => [
|
|
69
|
+
config.id,
|
|
70
|
+
config.metric_name || "-",
|
|
71
|
+
config.resource_id,
|
|
72
|
+
config.frequency || "-",
|
|
73
|
+
config.partition_columns.join(", ") || "-",
|
|
74
|
+
]));
|
|
75
|
+
process.stdout.write(`Showing ${result.configs.length} of ${result.total} anomaly monitors\n`);
|
|
76
|
+
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { CorelayerClient } from "../lib/api-client.js";
|
|
2
|
+
import { readFlag } from "../lib/args.js";
|
|
2
3
|
import { resolveApiUrl, resolveToken } from "../lib/config.js";
|
|
3
4
|
import { fail, printJson } from "../lib/output.js";
|
|
4
|
-
function readFlag(args, flag) {
|
|
5
|
-
const index = args.indexOf(flag);
|
|
6
|
-
if (index === -1)
|
|
7
|
-
return undefined;
|
|
8
|
-
return args[index + 1];
|
|
9
|
-
}
|
|
10
5
|
function printHelp() {
|
|
11
6
|
process.stdout.write(`corelayer api-keys - Manage Corelayer API keys
|
|
12
7
|
|
package/dist/commands/config.js
CHANGED
|
@@ -34,11 +34,12 @@ export async function runConfig(args, ctx) {
|
|
|
34
34
|
const key = args[1];
|
|
35
35
|
const config = readConfig();
|
|
36
36
|
if (!key) {
|
|
37
|
+
const { token: _token, ...publicConfig } = config;
|
|
37
38
|
if (ctx.json) {
|
|
38
|
-
printJson(
|
|
39
|
+
printJson(publicConfig);
|
|
39
40
|
}
|
|
40
41
|
else {
|
|
41
|
-
process.stdout.write(`${JSON.stringify(
|
|
42
|
+
process.stdout.write(`${JSON.stringify(publicConfig, null, 2)}\n`);
|
|
42
43
|
}
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { CorelayerClient } from "../lib/api-client.js";
|
|
2
|
-
import {
|
|
2
|
+
import { readFlag } from "../lib/args.js";
|
|
3
|
+
import { readConfig, resolveApiUrl, resolveToken } from "../lib/config.js";
|
|
3
4
|
import { fail, printJson, printTable } from "../lib/output.js";
|
|
4
|
-
function readFlag(args, flag) {
|
|
5
|
-
const index = args.indexOf(flag);
|
|
6
|
-
if (index === -1)
|
|
7
|
-
return undefined;
|
|
8
|
-
return args[index + 1];
|
|
9
|
-
}
|
|
10
5
|
function printHelp() {
|
|
11
6
|
process.stdout.write(`corelayer integrations - List integrations for a group
|
|
12
7
|
|
|
@@ -48,7 +43,7 @@ export async function runIntegrations(args, ctx) {
|
|
|
48
43
|
printJson(result);
|
|
49
44
|
return;
|
|
50
45
|
}
|
|
51
|
-
const rows = result.accounts.map(account => [
|
|
46
|
+
const rows = result.accounts.map((account) => [
|
|
52
47
|
account.id,
|
|
53
48
|
account.provider_code,
|
|
54
49
|
String(account.resources?.length || 0),
|
package/dist/commands/issues.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { CorelayerClient } from "../lib/api-client.js";
|
|
4
|
+
import { readFlag } from "../lib/args.js";
|
|
4
5
|
import { readConfig, resolveApiUrl, resolveToken } from "../lib/config.js";
|
|
5
6
|
import { fail, formatDate, printJson, printTable } from "../lib/output.js";
|
|
6
|
-
function readFlag(args, flag) {
|
|
7
|
-
const index = args.indexOf(flag);
|
|
8
|
-
if (index === -1)
|
|
9
|
-
return undefined;
|
|
10
|
-
return args[index + 1];
|
|
11
|
-
}
|
|
12
7
|
function hasFlag(args, flag) {
|
|
13
8
|
return args.includes(flag);
|
|
14
9
|
}
|
|
@@ -112,208 +107,202 @@ function parseDuration(value) {
|
|
|
112
107
|
return Math.round(n * 24 * 60 * 60 * 1000);
|
|
113
108
|
return null;
|
|
114
109
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
function resolveGroupId(args, config) {
|
|
111
|
+
const groupId = readFlag(args, "--group") || config.defaults?.group;
|
|
112
|
+
if (!groupId) {
|
|
113
|
+
fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
|
|
119
114
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
return groupId;
|
|
116
|
+
}
|
|
117
|
+
async function runGetIssue(client, commandArgs, ctx) {
|
|
118
|
+
const issueIdentifier = commandArgs[0];
|
|
119
|
+
if (!issueIdentifier) {
|
|
120
|
+
fail("Usage: corelayer issues get <issueId|slug>");
|
|
125
121
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (sub === "list") {
|
|
130
|
-
const groupId = readFlag(args, "--group") || config.defaults?.group;
|
|
131
|
-
if (!groupId) {
|
|
132
|
-
fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
|
|
133
|
-
}
|
|
134
|
-
const result = await client.listIssues(groupId, {
|
|
135
|
-
status: readFlag(args, "--status"),
|
|
136
|
-
severity: readFlag(args, "--severity"),
|
|
137
|
-
limit: readFlag(args, "--limit"),
|
|
138
|
-
page: readFlag(args, "--page"),
|
|
139
|
-
});
|
|
140
|
-
if (ctx.json) {
|
|
141
|
-
printJson(result);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const rows = result.issues.map((issue) => [
|
|
145
|
-
issue.id,
|
|
146
|
-
issue.slug || "-",
|
|
147
|
-
issue.severity || "-",
|
|
148
|
-
issue.status || "-",
|
|
149
|
-
issue.title || "-",
|
|
150
|
-
formatDate(issue.last_seen_at || issue.lastSeenAt),
|
|
151
|
-
]);
|
|
152
|
-
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], rows);
|
|
153
|
-
if (!ctx.quiet) {
|
|
154
|
-
process.stdout.write(`Showing ${result.issues.length} of ${result.total} issues\n`);
|
|
155
|
-
}
|
|
122
|
+
const issue = await client.getIssue(issueIdentifier);
|
|
123
|
+
if (ctx.json) {
|
|
124
|
+
printJson(issue);
|
|
156
125
|
return;
|
|
157
126
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
process.stdout.write(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
process.stdout.write(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
127
|
+
process.stdout.write(`Issue ${issue.slug || issue.id}: ${issue.title || ""}\n`);
|
|
128
|
+
process.stdout.write(`Status: ${issue.status || "-"}\n`);
|
|
129
|
+
process.stdout.write(`Severity: ${issue.severity || "-"}\n`);
|
|
130
|
+
process.stdout.write(`Created: ${formatDate(issue.createdAt || issue.created_at)}\n`);
|
|
131
|
+
process.stdout.write(`Last seen: ${formatDate(issue.lastSeenAt || issue.last_seen_at)}\n`);
|
|
132
|
+
process.stdout.write(`Events: ${issue.event_count || 0}\n`);
|
|
133
|
+
if (issue.whatHappened) {
|
|
134
|
+
process.stdout.write(`\nWhat happened:\n${issue.whatHappened}\n`);
|
|
135
|
+
}
|
|
136
|
+
if (issue.rootCause) {
|
|
137
|
+
process.stdout.write(`\nRoot cause:\n${issue.rootCause}\n`);
|
|
138
|
+
}
|
|
139
|
+
if (issue.nextSteps) {
|
|
140
|
+
process.stdout.write(`\nNext steps:\n${issue.nextSteps}\n`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function issueRows(issues) {
|
|
144
|
+
return issues.map((issue) => [
|
|
145
|
+
issue.id,
|
|
146
|
+
issue.slug || "-",
|
|
147
|
+
issue.severity || "-",
|
|
148
|
+
issue.status || "-",
|
|
149
|
+
issue.title || "-",
|
|
150
|
+
formatDate(issue.last_seen_at || issue.lastSeenAt),
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
async function runListIssues({ args, client, config, ctx, }) {
|
|
154
|
+
const groupId = resolveGroupId(args, config);
|
|
155
|
+
const result = await client.listIssues(groupId, {
|
|
156
|
+
status: readFlag(args, "--status"),
|
|
157
|
+
severity: readFlag(args, "--severity"),
|
|
158
|
+
limit: readFlag(args, "--limit"),
|
|
159
|
+
page: readFlag(args, "--page"),
|
|
160
|
+
});
|
|
161
|
+
if (ctx.json) {
|
|
162
|
+
printJson(result);
|
|
183
163
|
return;
|
|
184
164
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
165
|
+
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], issueRows(result.issues));
|
|
166
|
+
if (!ctx.quiet) {
|
|
167
|
+
process.stdout.write(`Showing ${result.issues.length} of ${result.total} issues\n`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function runCloseIssue({ args, client, commandArgs, ctx, }) {
|
|
171
|
+
const issueIdentifier = commandArgs[0];
|
|
172
|
+
if (!issueIdentifier) {
|
|
173
|
+
fail('Usage: corelayer issues close <issueId|slug> [--feedback "..."]');
|
|
174
|
+
}
|
|
175
|
+
const feedback = readFlag(args, "--feedback");
|
|
176
|
+
const issue = await client.getIssue(issueIdentifier);
|
|
177
|
+
if (!issue.id) {
|
|
178
|
+
fail("Could not resolve issue identifier to an issue ID.");
|
|
179
|
+
}
|
|
180
|
+
await client.closeIssue(issue.id, feedback);
|
|
181
|
+
if (!ctx.quiet) {
|
|
182
|
+
process.stdout.write(`Issue ${issue.slug || issue.id} closed.\n`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function runBulkCloseIssues({ args, client, config, ctx, }) {
|
|
186
|
+
const groupId = resolveGroupId(args, config);
|
|
187
|
+
const lastSeenBefore = readFlag(args, "--last-seen-before");
|
|
188
|
+
const lastSeenBeforeOrOn = readFlag(args, "--last-seen-before-or-on");
|
|
189
|
+
if (!lastSeenBefore && !lastSeenBeforeOrOn) {
|
|
190
|
+
fail('Usage: corelayer issues bulk-close --group <groupId> (--last-seen-before <date|preset> | --last-seen-before-or-on <date|preset>) [--feedback "..."]');
|
|
191
|
+
}
|
|
192
|
+
const feedback = readFlag(args, "--feedback");
|
|
193
|
+
const result = await client.bulkIssueAction({
|
|
194
|
+
action: "close",
|
|
195
|
+
groupId,
|
|
196
|
+
feedback,
|
|
197
|
+
filters: {
|
|
198
|
+
status: readFlag(args, "--status") || "Open",
|
|
199
|
+
severity: readFlag(args, "--severity"),
|
|
200
|
+
lastSeenBefore,
|
|
201
|
+
lastSeenBeforeOrOn,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
if (ctx.json) {
|
|
205
|
+
printJson(result);
|
|
199
206
|
return;
|
|
200
207
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
208
|
+
process.stdout.write(`Bulk close complete. Closed ${result.affected} issues`);
|
|
209
|
+
if (result.skipped > 0) {
|
|
210
|
+
process.stdout.write(`, skipped ${result.skipped}`);
|
|
211
|
+
}
|
|
212
|
+
process.stdout.write(".\n");
|
|
213
|
+
}
|
|
214
|
+
async function runReopenIssue({ client, commandArgs, ctx, }) {
|
|
215
|
+
const issueId = commandArgs[0];
|
|
216
|
+
if (!issueId) {
|
|
217
|
+
fail("Usage: corelayer issues reopen <issueId>");
|
|
218
|
+
}
|
|
219
|
+
await client.reopenIssue(issueId);
|
|
220
|
+
if (!ctx.quiet) {
|
|
221
|
+
process.stdout.write(`Issue ${issueId} reopened.\n`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function runDeleteIssue({ args, client, commandArgs, ctx, }) {
|
|
225
|
+
const issueId = commandArgs[0];
|
|
226
|
+
if (!issueId) {
|
|
227
|
+
fail("Usage: corelayer issues delete <issueId> [--yes]");
|
|
228
|
+
}
|
|
229
|
+
const yes = hasFlag(args, "--yes");
|
|
230
|
+
if (!yes) {
|
|
231
|
+
const confirmed = await confirmDelete();
|
|
232
|
+
if (!confirmed) {
|
|
233
|
+
process.stdout.write("Aborted.\n");
|
|
225
234
|
return;
|
|
226
235
|
}
|
|
227
|
-
process.stdout.write(`Bulk close complete. Closed ${result.affected} issues`);
|
|
228
|
-
if (result.skipped > 0) {
|
|
229
|
-
process.stdout.write(`, skipped ${result.skipped}`);
|
|
230
|
-
}
|
|
231
|
-
process.stdout.write(".\n");
|
|
232
|
-
return;
|
|
233
236
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
fail("Usage: corelayer issues reopen <issueId>");
|
|
238
|
-
}
|
|
239
|
-
await client.reopenIssue(issueId);
|
|
240
|
-
if (!ctx.quiet) {
|
|
241
|
-
process.stdout.write(`Issue ${issueId} reopened.\n`);
|
|
242
|
-
}
|
|
243
|
-
return;
|
|
237
|
+
await client.deleteIssue(issueId);
|
|
238
|
+
if (!ctx.quiet) {
|
|
239
|
+
process.stdout.write(`Issue ${issueId} deleted.\n`);
|
|
244
240
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
await client.deleteIssue(issueId);
|
|
259
|
-
if (!ctx.quiet) {
|
|
260
|
-
process.stdout.write(`Issue ${issueId} deleted.\n`);
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
241
|
+
}
|
|
242
|
+
async function runIssueSummary({ args, client, config, }) {
|
|
243
|
+
const groupId = resolveGroupId(args, config);
|
|
244
|
+
const summary = await client.getIssueSummary(groupId);
|
|
245
|
+
printJson(summary);
|
|
246
|
+
}
|
|
247
|
+
function resolveDiffSince(args) {
|
|
248
|
+
const sinceFlag = readFlag(args, "--since");
|
|
249
|
+
const durationFlag = readFlag(args, "--duration");
|
|
250
|
+
if (!sinceFlag && !durationFlag) {
|
|
251
|
+
fail("One of --since <timestamp|preset> or --duration <duration> is required.\n" +
|
|
252
|
+
"Examples: --since 2026-06-01T00:00:00Z --since 24hours --duration 2hours");
|
|
263
253
|
}
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
254
|
+
if (!durationFlag) {
|
|
255
|
+
return sinceFlag;
|
|
256
|
+
}
|
|
257
|
+
const ms = parseDuration(durationFlag);
|
|
258
|
+
if (ms === null) {
|
|
259
|
+
fail(`Invalid --duration value: "${durationFlag}". Examples: 30m, 2hours, 4h, 1d, 7days`);
|
|
260
|
+
}
|
|
261
|
+
return new Date(Date.now() - ms).toISOString();
|
|
262
|
+
}
|
|
263
|
+
async function runDiffIssues({ args, client, config, ctx, }) {
|
|
264
|
+
const groupId = resolveGroupId(args, config);
|
|
265
|
+
const result = await client.diffIssues(groupId, resolveDiffSince(args), {
|
|
266
|
+
limit: readFlag(args, "--limit"),
|
|
267
|
+
page: readFlag(args, "--page"),
|
|
268
|
+
});
|
|
269
|
+
if (ctx.json) {
|
|
270
|
+
printJson(result);
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
else {
|
|
293
|
-
sinceValue = sinceFlag;
|
|
294
|
-
}
|
|
295
|
-
const result = await client.diffIssues(groupId, sinceValue, {
|
|
296
|
-
limit: readFlag(args, "--limit"),
|
|
297
|
-
page: readFlag(args, "--page"),
|
|
298
|
-
});
|
|
299
|
-
if (ctx.json) {
|
|
300
|
-
printJson(result);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
process.stdout.write(`DIFF SINCE ${result.since}\n`);
|
|
304
|
-
const rows = result.issues.map((issue) => [
|
|
305
|
-
issue.id,
|
|
306
|
-
issue.slug || "-",
|
|
307
|
-
issue.severity || "-",
|
|
308
|
-
issue.status || "-",
|
|
309
|
-
issue.title || "-",
|
|
310
|
-
formatDate(issue.last_seen_at || issue.lastSeenAt),
|
|
311
|
-
]);
|
|
312
|
-
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], rows);
|
|
313
|
-
if (!ctx.quiet) {
|
|
314
|
-
process.stdout.write(`${result.total} issue${result.total === 1 ? "" : "s"} changed since ${result.since}\n`);
|
|
315
|
-
}
|
|
273
|
+
process.stdout.write(`DIFF SINCE ${result.since}\n`);
|
|
274
|
+
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], issueRows(result.issues));
|
|
275
|
+
if (!ctx.quiet) {
|
|
276
|
+
process.stdout.write(`${result.total} issue${result.total === 1 ? "" : "s"} changed since ${result.since}\n`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const ISSUE_COMMAND_HANDLERS = {
|
|
280
|
+
"bulk-close": runBulkCloseIssues,
|
|
281
|
+
close: runCloseIssue,
|
|
282
|
+
delete: runDeleteIssue,
|
|
283
|
+
diff: runDiffIssues,
|
|
284
|
+
get: ({ client, commandArgs, ctx }) => runGetIssue(client, commandArgs, ctx),
|
|
285
|
+
list: runListIssues,
|
|
286
|
+
reopen: runReopenIssue,
|
|
287
|
+
summary: runIssueSummary,
|
|
288
|
+
};
|
|
289
|
+
export async function runIssues(args, ctx) {
|
|
290
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
291
|
+
printHelp();
|
|
316
292
|
return;
|
|
317
293
|
}
|
|
318
|
-
|
|
294
|
+
const sub = args[0];
|
|
295
|
+
const config = readConfig();
|
|
296
|
+
const token = resolveToken();
|
|
297
|
+
if (!token) {
|
|
298
|
+
fail("Not authenticated. Set CORELAYER_API_KEY or run: corelayer login");
|
|
299
|
+
}
|
|
300
|
+
const apiUrl = resolveApiUrl(ctx.apiUrlOverride);
|
|
301
|
+
const client = new CorelayerClient(apiUrl, token);
|
|
302
|
+
const commandArgs = removeFlags(args.slice(1));
|
|
303
|
+
const handler = ISSUE_COMMAND_HANDLERS[sub];
|
|
304
|
+
if (!handler) {
|
|
305
|
+
fail("Usage: corelayer issues <list|get|close|bulk-close|reopen|delete|summary|diff> [options]");
|
|
306
|
+
}
|
|
307
|
+
await handler({ args, client, commandArgs, config, ctx });
|
|
319
308
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
+
import { readFlag } from "../lib/args.js";
|
|
1
2
|
import { loginWithBrowser, loginWithCodeDirect } from "../lib/auth.js";
|
|
2
3
|
import { updateConfig } from "../lib/config.js";
|
|
3
4
|
import { fail } from "../lib/output.js";
|
|
4
|
-
function readFlag(args, flag) {
|
|
5
|
-
const index = args.indexOf(flag);
|
|
6
|
-
if (index === -1)
|
|
7
|
-
return undefined;
|
|
8
|
-
return args[index + 1];
|
|
9
|
-
}
|
|
10
5
|
function hasFlag(args, flag) {
|
|
11
6
|
return args.includes(flag);
|
|
12
7
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CorelayerClient } from "../lib/api-client.js";
|
|
2
|
+
import { clearToken, readConfig } from "../lib/config.js";
|
|
2
3
|
function printHelp() {
|
|
3
|
-
process.stdout
|
|
4
|
+
process.stdout
|
|
5
|
+
.write(`corelayer logout - Revoke the saved credential and clear it locally
|
|
4
6
|
|
|
5
7
|
USAGE:
|
|
6
8
|
corelayer logout
|
|
7
9
|
|
|
8
10
|
OPTIONS:
|
|
11
|
+
--local-only Clear the saved credential without server revocation
|
|
9
12
|
-h, --help Print help
|
|
10
13
|
`);
|
|
11
14
|
}
|
|
@@ -14,8 +17,23 @@ export async function runLogout(args, ctx) {
|
|
|
14
17
|
printHelp();
|
|
15
18
|
return;
|
|
16
19
|
}
|
|
20
|
+
const config = readConfig();
|
|
21
|
+
if (config.token && !args.includes("--local-only")) {
|
|
22
|
+
try {
|
|
23
|
+
await new CorelayerClient(config.apiUrl, config.token).logout();
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : "Request failed";
|
|
27
|
+
throw new Error(`Could not revoke the saved credential: ${message}. Local credentials were kept. Retry, or use --local-only to clear them without revocation.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
17
30
|
clearToken();
|
|
31
|
+
if (process.env.CORELAYER_API_KEY) {
|
|
32
|
+
process.stderr.write("CORELAYER_API_KEY is still set. Unset it to stop using that credential.\n");
|
|
33
|
+
}
|
|
18
34
|
if (!ctx.quiet) {
|
|
19
|
-
process.stdout.write("
|
|
35
|
+
process.stdout.write(args.includes("--local-only")
|
|
36
|
+
? "Local credentials cleared; the server credential was not revoked.\n"
|
|
37
|
+
: "Logged out.\n");
|
|
20
38
|
}
|
|
21
39
|
}
|