@corelayer-ai/cli 0.5.1 → 0.5.3
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 +3 -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/issue-tickets.js +163 -0
- package/dist/commands/issues.js +196 -192
- package/dist/commands/login.js +1 -6
- package/dist/commands/logout.js +21 -3
- package/dist/commands/preflight.js +69 -34
- package/dist/commands/schedules.js +51 -0
- package/dist/index.js +13 -3
- package/dist/lib/api-client.js +22 -4
- package/dist/lib/args.js +4 -0
- package/package.json +5 -3
- package/skill/SKILL.md +298 -108
- package/skill/references/ecs-monitor-recovery.md +67 -0
- package/skill/references/preflight.md +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Corelayer CLI
|
|
2
|
+
|
|
3
|
+
Source and packaging for the `@corelayer-ai/cli` command-line client. Provides authentication, production issue investigation and actions, organization memory queries, schedules, preflight checks, and MCP integration. The `skill/` folder contains the agent instructions distributed with the CLI.
|
|
@@ -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),
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFlag } from "../lib/args.js";
|
|
3
|
+
import { fail, printJson } from "../lib/output.js";
|
|
4
|
+
function ticketInput(args, config) {
|
|
5
|
+
const provider = readFlag(args, "--provider");
|
|
6
|
+
if (provider !== "jira" && provider !== "linear")
|
|
7
|
+
fail("--provider jira|linear is required.");
|
|
8
|
+
const groupId = readFlag(args, "--group") || config.defaults?.group;
|
|
9
|
+
if (!groupId)
|
|
10
|
+
fail("--group is required (or configure default-group).");
|
|
11
|
+
const input = { groupId, provider };
|
|
12
|
+
for (const [flag, key] of [
|
|
13
|
+
["--account", "accountId"],
|
|
14
|
+
["--project", "projectId"],
|
|
15
|
+
["--team", "teamId"],
|
|
16
|
+
["--type", "issueTypeId"],
|
|
17
|
+
["--query", "query"],
|
|
18
|
+
["--ticket", "ticketId"],
|
|
19
|
+
]) {
|
|
20
|
+
const value = readFlag(args, flag);
|
|
21
|
+
if (value)
|
|
22
|
+
input[key] = value;
|
|
23
|
+
}
|
|
24
|
+
const revision = readFlag(args, "--revision");
|
|
25
|
+
if (revision) {
|
|
26
|
+
const value = Number(revision);
|
|
27
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
28
|
+
fail("--revision must be a positive integer from ticket destinations.");
|
|
29
|
+
input.expectedRevision = value;
|
|
30
|
+
}
|
|
31
|
+
const fields = readFlag(args, "--fields");
|
|
32
|
+
if (fields) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(fields);
|
|
35
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object")
|
|
36
|
+
fail("--fields must be a JSON object.");
|
|
37
|
+
input.fields = parsed;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
fail("--fields must be a JSON object.");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return input;
|
|
44
|
+
}
|
|
45
|
+
function ticketIdentity(args, mutating) {
|
|
46
|
+
const suppliedId = readFlag(args, "--request-id");
|
|
47
|
+
const requestId = suppliedId || (mutating ? randomUUID() : undefined);
|
|
48
|
+
if ((mutating || suppliedId !== undefined) &&
|
|
49
|
+
(!requestId ||
|
|
50
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(requestId)))
|
|
51
|
+
fail("A valid --request-id UUID is required.");
|
|
52
|
+
return requestId;
|
|
53
|
+
}
|
|
54
|
+
function mutationInput(action, input, requestId) {
|
|
55
|
+
if (!input.accountId)
|
|
56
|
+
fail("--account is required; use ticket destinations to discover accounts.");
|
|
57
|
+
if (action === "link" && !input.ticketId)
|
|
58
|
+
fail("--ticket is required for link.");
|
|
59
|
+
input.requestId = requestId;
|
|
60
|
+
input.intent =
|
|
61
|
+
action === "create"
|
|
62
|
+
? "create_new"
|
|
63
|
+
: action === "link"
|
|
64
|
+
? "link_existing"
|
|
65
|
+
: "find_or_create";
|
|
66
|
+
process.stderr.write(`Ticket request: ${requestId}\n`);
|
|
67
|
+
}
|
|
68
|
+
function interruptedRequest(error) {
|
|
69
|
+
return (error instanceof Error &&
|
|
70
|
+
(/timed out|fetch failed|socket|network/i.test(error.message) ||
|
|
71
|
+
("status" in error &&
|
|
72
|
+
typeof error.status === "number" &&
|
|
73
|
+
error.status >= 500)));
|
|
74
|
+
}
|
|
75
|
+
function resultExitCode(status) {
|
|
76
|
+
const codes = {
|
|
77
|
+
needs_input: 2,
|
|
78
|
+
pending: 3,
|
|
79
|
+
in_progress: 3,
|
|
80
|
+
recovery_required: 4,
|
|
81
|
+
blocked: 4,
|
|
82
|
+
stale_request: 2,
|
|
83
|
+
failed: 1,
|
|
84
|
+
error: 1,
|
|
85
|
+
};
|
|
86
|
+
return typeof status === "string" ? codes[status] || 0 : 0;
|
|
87
|
+
}
|
|
88
|
+
function shellArgument(value) {
|
|
89
|
+
return `'${String(value).replaceAll("'", "'\"'\"'")}'`;
|
|
90
|
+
}
|
|
91
|
+
function resumeFlags(input, requestId) {
|
|
92
|
+
const flags = [
|
|
93
|
+
["groupId", "--group"],
|
|
94
|
+
["provider", "--provider"],
|
|
95
|
+
["accountId", "--account"],
|
|
96
|
+
["projectId", "--project"],
|
|
97
|
+
["teamId", "--team"],
|
|
98
|
+
["issueTypeId", "--type"],
|
|
99
|
+
["expectedRevision", "--revision"],
|
|
100
|
+
];
|
|
101
|
+
const values = flags
|
|
102
|
+
.filter(([key]) => input[key] !== undefined)
|
|
103
|
+
.map(([key, flag]) => `${flag} ${shellArgument(input[key])}`);
|
|
104
|
+
if (input.fields)
|
|
105
|
+
values.push(`--fields ${shellArgument(JSON.stringify(input.fields))}`);
|
|
106
|
+
values.push(`--request-id ${shellArgument(requestId)}`);
|
|
107
|
+
return values.join(" ");
|
|
108
|
+
}
|
|
109
|
+
function humanTicketResult(result, issueId, input, requestId) {
|
|
110
|
+
const lines = [
|
|
111
|
+
String(result.message || result.status || JSON.stringify(result, null, 2)),
|
|
112
|
+
];
|
|
113
|
+
const candidates = Array.isArray(result.candidates)
|
|
114
|
+
? result.candidates.filter((candidate) => candidate !== null && typeof candidate === "object")
|
|
115
|
+
: [];
|
|
116
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
117
|
+
lines.push(`${index + 1}. ${candidate.identifier || candidate.id}: ${candidate.title || ""}${candidate.evidence ? ` — ${candidate.evidence}` : ""}`);
|
|
118
|
+
if (candidate.url)
|
|
119
|
+
lines.push(` ${candidate.url}`);
|
|
120
|
+
}
|
|
121
|
+
const resumeId = typeof result.requestId === "string" ? result.requestId : requestId;
|
|
122
|
+
if (result.status === "needs_input" && resumeId) {
|
|
123
|
+
const flags = resumeFlags(input, resumeId);
|
|
124
|
+
const firstTicket = candidates[0]?.id;
|
|
125
|
+
if (firstTicket)
|
|
126
|
+
lines.push(`Link an existing ticket: corelayer issues ticket link ${shellArgument(issueId)} ${flags} --ticket ${shellArgument(firstTicket)}`);
|
|
127
|
+
lines.push(`Create a new ticket: corelayer issues ticket create ${shellArgument(issueId)} ${flags}`);
|
|
128
|
+
if (result.requirements)
|
|
129
|
+
lines.push(`Required ticket details: ${JSON.stringify(result.requirements, null, 2)}`);
|
|
130
|
+
}
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
export async function runIssueTickets(args, client, config, ctx) {
|
|
134
|
+
const [action, issueId] = args;
|
|
135
|
+
if (!issueId ||
|
|
136
|
+
!["destinations", "search", "ensure", "create", "link", "status"].includes(action))
|
|
137
|
+
fail("Usage: corelayer issues ticket <destinations|search|ensure|create|link|status> <id|slug> --provider jira|linear --group <id>");
|
|
138
|
+
const input = ticketInput(args, config);
|
|
139
|
+
const mutating = ["ensure", "create", "link"].includes(action);
|
|
140
|
+
const requestId = ticketIdentity(args, mutating);
|
|
141
|
+
if (mutating)
|
|
142
|
+
mutationInput(action, input, requestId);
|
|
143
|
+
const suffixes = {
|
|
144
|
+
destinations: "/destinations",
|
|
145
|
+
search: "/search",
|
|
146
|
+
status: `/requests/${encodeURIComponent(requestId || "current")}`,
|
|
147
|
+
};
|
|
148
|
+
let result;
|
|
149
|
+
try {
|
|
150
|
+
result = await client.ticketRequest(mutating || action === "search" ? "POST" : "GET", issueId, suffixes[action] || "", input);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!mutating || !interruptedRequest(error))
|
|
154
|
+
throw error;
|
|
155
|
+
process.stderr.write("Response interrupted; checking the same ticket request.\n");
|
|
156
|
+
result = await client.ticketRequest("GET", issueId, `/requests/${encodeURIComponent(requestId)}`, { groupId: input.groupId, provider: input.provider });
|
|
157
|
+
}
|
|
158
|
+
if (ctx.json)
|
|
159
|
+
printJson(result);
|
|
160
|
+
else
|
|
161
|
+
process.stdout.write(`${humanTicketResult(result, issueId, input, requestId)}\n`);
|
|
162
|
+
process.exitCode = resultExitCode(result.status);
|
|
163
|
+
}
|