@corelayer-ai/cli 0.5.0 → 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 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
 
@@ -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(config);
39
+ printJson(publicConfig);
39
40
  }
40
41
  else {
41
- process.stdout.write(`${JSON.stringify(config, null, 2)}\n`);
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 { readConfig, resolveToken, resolveApiUrl } from "../lib/config.js";
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),
@@ -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
  }
@@ -60,7 +55,7 @@ SUBCOMMANDS:
60
55
  get <id|slug> Show details for one issue
61
56
  summary Summarize issue health for a group
62
57
  diff Show issues new or changed since a timestamp
63
- close <id> Close an issue (write operation)
58
+ close <id|slug> Close an issue (write operation)
64
59
  bulk-close Close many issues in one request (write operation)
65
60
  reopen <id> Reopen a closed issue (write operation)
66
61
  delete <id> Delete an issue (write operation)
@@ -90,7 +85,7 @@ EXAMPLES:
90
85
  corelayer issues diff --group g_123 --since 2026-06-01T00:00:00Z
91
86
  corelayer issues diff --group g_123 --since 24hours
92
87
  corelayer issues diff --group g_123 --duration 2hours --limit 50
93
- corelayer issues close i_456 --feedback "fixed in deploy 2026-04-07"
88
+ corelayer issues close corelayer-prod-engine-20260602-1 --feedback "fixed in deploy 2026-04-07"
94
89
  corelayer issues bulk-close --group g_123 --last-seen-before-or-on 14days --feedback "Not seen in two weeks"
95
90
  corelayer issues delete i_456 --yes
96
91
  `);
@@ -112,204 +107,202 @@ function parseDuration(value) {
112
107
  return Math.round(n * 24 * 60 * 60 * 1000);
113
108
  return null;
114
109
  }
115
- export async function runIssues(args, ctx) {
116
- if (args.includes("--help") || args.includes("-h")) {
117
- printHelp();
118
- return;
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
- const sub = args[0];
121
- const config = readConfig();
122
- const token = resolveToken();
123
- if (!token) {
124
- fail("Not authenticated. Set CORELAYER_API_KEY or run: corelayer login");
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 apiUrl = resolveApiUrl(ctx.apiUrlOverride);
127
- const client = new CorelayerClient(apiUrl, token);
128
- const commandArgs = removeFlags(args.slice(1));
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
- if (sub === "get") {
159
- const issueIdentifier = commandArgs[0];
160
- if (!issueIdentifier) {
161
- fail("Usage: corelayer issues get <issueId|slug>");
162
- }
163
- const issue = await client.getIssue(issueIdentifier);
164
- if (ctx.json) {
165
- printJson(issue);
166
- return;
167
- }
168
- process.stdout.write(`Issue ${issue.slug || issue.id}: ${issue.title || ""}\n`);
169
- process.stdout.write(`Status: ${issue.status || "-"}\n`);
170
- process.stdout.write(`Severity: ${issue.severity || "-"}\n`);
171
- process.stdout.write(`Created: ${formatDate(issue.createdAt || issue.created_at)}\n`);
172
- process.stdout.write(`Last seen: ${formatDate(issue.lastSeenAt || issue.last_seen_at)}\n`);
173
- process.stdout.write(`Events: ${issue.event_count || 0}\n`);
174
- if (issue.whatHappened) {
175
- process.stdout.write(`\nWhat happened:\n${issue.whatHappened}\n`);
176
- }
177
- if (issue.rootCause) {
178
- process.stdout.write(`\nRoot cause:\n${issue.rootCause}\n`);
179
- }
180
- if (issue.nextSteps) {
181
- process.stdout.write(`\nNext steps:\n${issue.nextSteps}\n`);
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
- if (sub === "close") {
186
- const issueId = commandArgs[0];
187
- if (!issueId) {
188
- fail('Usage: corelayer issues close <issueId> [--feedback "..."]');
189
- }
190
- const feedback = readFlag(args, "--feedback");
191
- await client.closeIssue(issueId, feedback);
192
- if (!ctx.quiet) {
193
- process.stdout.write(`Issue ${issueId} closed.\n`);
194
- }
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);
195
206
  return;
196
207
  }
197
- if (sub === "bulk-close") {
198
- const groupId = readFlag(args, "--group") || config.defaults?.group;
199
- if (!groupId) {
200
- fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
201
- }
202
- const lastSeenBefore = readFlag(args, "--last-seen-before");
203
- const lastSeenBeforeOrOn = readFlag(args, "--last-seen-before-or-on");
204
- if (!lastSeenBefore && !lastSeenBeforeOrOn) {
205
- fail('Usage: corelayer issues bulk-close --group <groupId> (--last-seen-before <date|preset> | --last-seen-before-or-on <date|preset>) [--feedback "..."]');
206
- }
207
- const feedback = readFlag(args, "--feedback");
208
- const result = await client.bulkIssueAction({
209
- action: "close",
210
- groupId,
211
- feedback,
212
- filters: {
213
- status: readFlag(args, "--status") || "Open",
214
- severity: readFlag(args, "--severity"),
215
- lastSeenBefore,
216
- lastSeenBeforeOrOn,
217
- },
218
- });
219
- if (ctx.json) {
220
- printJson(result);
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");
221
234
  return;
222
235
  }
223
- process.stdout.write(`Bulk close complete. Closed ${result.affected} issues`);
224
- if (result.skipped > 0) {
225
- process.stdout.write(`, skipped ${result.skipped}`);
226
- }
227
- process.stdout.write(".\n");
228
- return;
229
236
  }
230
- if (sub === "reopen") {
231
- const issueId = commandArgs[0];
232
- if (!issueId) {
233
- fail("Usage: corelayer issues reopen <issueId>");
234
- }
235
- await client.reopenIssue(issueId);
236
- if (!ctx.quiet) {
237
- process.stdout.write(`Issue ${issueId} reopened.\n`);
238
- }
239
- return;
237
+ await client.deleteIssue(issueId);
238
+ if (!ctx.quiet) {
239
+ process.stdout.write(`Issue ${issueId} deleted.\n`);
240
240
  }
241
- if (sub === "delete") {
242
- const issueId = commandArgs[0];
243
- if (!issueId) {
244
- fail("Usage: corelayer issues delete <issueId> [--yes]");
245
- }
246
- const yes = hasFlag(args, "--yes");
247
- if (!yes) {
248
- const confirmed = await confirmDelete();
249
- if (!confirmed) {
250
- process.stdout.write("Aborted.\n");
251
- return;
252
- }
253
- }
254
- await client.deleteIssue(issueId);
255
- if (!ctx.quiet) {
256
- process.stdout.write(`Issue ${issueId} deleted.\n`);
257
- }
258
- 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");
259
253
  }
260
- if (sub === "summary") {
261
- const groupId = readFlag(args, "--group") || config.defaults?.group;
262
- if (!groupId) {
263
- fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
264
- }
265
- const summary = await client.getIssueSummary(groupId);
266
- printJson(summary);
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);
267
271
  return;
268
272
  }
269
- if (sub === "diff") {
270
- const groupId = readFlag(args, "--group") || config.defaults?.group;
271
- if (!groupId) {
272
- fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
273
- }
274
- const sinceFlag = readFlag(args, "--since");
275
- const durationFlag = readFlag(args, "--duration");
276
- if (!sinceFlag && !durationFlag) {
277
- fail("One of --since <timestamp|preset> or --duration <duration> is required.\n" +
278
- "Examples: --since 2026-06-01T00:00:00Z --since 24hours --duration 2hours");
279
- }
280
- let sinceValue;
281
- if (durationFlag) {
282
- const ms = parseDuration(durationFlag);
283
- if (ms === null) {
284
- fail(`Invalid --duration value: "${durationFlag}". Examples: 30m, 2hours, 4h, 1d, 7days`);
285
- }
286
- sinceValue = new Date(Date.now() - ms).toISOString();
287
- }
288
- else {
289
- sinceValue = sinceFlag;
290
- }
291
- const result = await client.diffIssues(groupId, sinceValue, {
292
- limit: readFlag(args, "--limit"),
293
- page: readFlag(args, "--page"),
294
- });
295
- if (ctx.json) {
296
- printJson(result);
297
- return;
298
- }
299
- process.stdout.write(`DIFF SINCE ${result.since}\n`);
300
- const rows = result.issues.map((issue) => [
301
- issue.id,
302
- issue.slug || "-",
303
- issue.severity || "-",
304
- issue.status || "-",
305
- issue.title || "-",
306
- formatDate(issue.last_seen_at || issue.lastSeenAt),
307
- ]);
308
- printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], rows);
309
- if (!ctx.quiet) {
310
- process.stdout.write(`${result.total} issue${result.total === 1 ? "" : "s"} changed since ${result.since}\n`);
311
- }
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();
312
292
  return;
313
293
  }
314
- fail("Usage: corelayer issues <list|get|close|bulk-close|reopen|delete|summary|diff> [options]");
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 });
315
308
  }
@@ -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
  }
@@ -1,11 +1,14 @@
1
- import { clearToken } from "../lib/config.js";
1
+ import { CorelayerClient } from "../lib/api-client.js";
2
+ import { clearToken, readConfig } from "../lib/config.js";
2
3
  function printHelp() {
3
- process.stdout.write(`corelayer logout - Sign out and clear local credentials
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("Logged out.\n");
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
  }