@keystrokehq/cli 0.0.1
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/AGENTS-blurb.md +123 -0
- package/LICENSE +42 -0
- package/README.md +177 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/bin/keystroke.mjs +107 -0
- package/dist/_manifest-JSRE3H8k.mjs +385 -0
- package/dist/agent-bundle-package-DWV6B_5q-BtV7Xycc.mjs +2344 -0
- package/dist/agent-manifest-CDnbkR2f.mjs +245 -0
- package/dist/agents-CZJGxVqV.mjs +228 -0
- package/dist/api-keys-D2lgguuY.mjs +40 -0
- package/dist/auth-DN2VusyU.mjs +59 -0
- package/dist/auth.handler-CT1BQUvu.mjs +340 -0
- package/dist/browser-qwFrUH82.mjs +24 -0
- package/dist/build-agents-BmM_AsSd-BGi9wtzt.mjs +514 -0
- package/dist/build-metadata-BWS7uhd_-DR8gJjTX.mjs +1422 -0
- package/dist/build-progress-DgYKb4hB.mjs +183 -0
- package/dist/build-tasks-CdihpudT-D5r5HUHe.mjs +91 -0
- package/dist/build-workflows-CfxBnIWh-CdYPv8w2.mjs +370 -0
- package/dist/build.handler-4799CjWH.mjs +36 -0
- package/dist/chunk-CH6r78ws.mjs +37 -0
- package/dist/clear-cache.handler-B9tqSoSM.mjs +11 -0
- package/dist/clear.handler-BTIXXPTJ.mjs +42 -0
- package/dist/clear.handler-BydlX-zE.mjs +11 -0
- package/dist/commander-DfTVqQ-3.mjs +133 -0
- package/dist/concurrency-gXn9Rw8x-DNl2YtrS.mjs +20 -0
- package/dist/connect-BUXkeH0F.mjs +43 -0
- package/dist/connect.handler-CYel9cy6.mjs +430 -0
- package/dist/constants-CPpPdSNg.mjs +8 -0
- package/dist/context-T7HZuB97.mjs +138 -0
- package/dist/credential-env-map-CI8yWHVy.mjs +28 -0
- package/dist/credential-schema-mismatch-BKo5PjcQ.mjs +76 -0
- package/dist/credentials-CvmjU0lK.mjs +171 -0
- package/dist/credentials-OfVHOtG3.mjs +151216 -0
- package/dist/current-deployment-workflow-poHt27i3.mjs +94 -0
- package/dist/current.handler-B8zKzfPp.mjs +21 -0
- package/dist/delete.handler-bAu1iXVQ.mjs +17 -0
- package/dist/deploy-7Jjls436.mjs +26 -0
- package/dist/deploy-BOPIpRWm.mjs +74 -0
- package/dist/deploy-progress-BmGUNFKg.mjs +70 -0
- package/dist/deploy.handler-BAzgiNhd.mjs +370 -0
- package/dist/detect-env-access-CwkOYeYM-D_BCZqV6.mjs +209 -0
- package/dist/diff-utils-NEfcjqxt.mjs +185 -0
- package/dist/diff.handler-Du7SY8K4.mjs +47 -0
- package/dist/dist-BkJUoBiG.mjs +1116 -0
- package/dist/dist-CUK7yBM0.mjs +308 -0
- package/dist/env-91KwMKov.mjs +140 -0
- package/dist/env.handler-BAzBuMzQ.mjs +277 -0
- package/dist/error-boundary-VL-JLfIa.mjs +34 -0
- package/dist/file-metadata-D1vm-XY2.mjs +191 -0
- package/dist/get-intrinsic-zLxwtrLK.mjs +658 -0
- package/dist/import-module-CV84H5fZ-B_CBCmb4.mjs +1747 -0
- package/dist/init-DpMCotSK.mjs +45 -0
- package/dist/init.handler-CPRnif52.mjs +585 -0
- package/dist/inspect.handler-DT_cD036.mjs +146 -0
- package/dist/integration-catalog-Bt-L3GjF.mjs +104 -0
- package/dist/integrations-DlatPK4W.mjs +79 -0
- package/dist/keystroke.d.mts +3 -0
- package/dist/keystroke.mjs +707 -0
- package/dist/layout-CbMtQ2tm.mjs +67 -0
- package/dist/list-enrichment-y-cwizLr.mjs +189 -0
- package/dist/list.handler-BTWvCyjA.mjs +52 -0
- package/dist/list.handler-CWF_Dj15.mjs +24 -0
- package/dist/list.handler-CZ6G2x_G.mjs +75 -0
- package/dist/list.handler-DWaQkJaR.mjs +51 -0
- package/dist/list.handler-DqbFcBW7.mjs +180 -0
- package/dist/list.handler-lq3ZGAn4.mjs +104 -0
- package/dist/logs-BEg9L5l8.mjs +28 -0
- package/dist/logs.handler-6hoMBzqw.mjs +35 -0
- package/dist/logs.handler-BD_dXiL1.mjs +231 -0
- package/dist/metadata-layout-GUYIUo0i-_aG2zjue.mjs +5877 -0
- package/dist/normalize-path-CojS-CgQ-DLCOvnD1.mjs +20 -0
- package/dist/options-CeaTcFxP.mjs +43 -0
- package/dist/org-xLzBtt2_.mjs +41 -0
- package/dist/output-DM4b7KgY.mjs +72 -0
- package/dist/oxc-B3KI3rf_-n9d1hKNq.mjs +119 -0
- package/dist/paused.handler-BMFm9Cff.mjs +94 -0
- package/dist/project-config-D1qsQlO7.mjs +107 -0
- package/dist/projects-CHkRE9rS.mjs +1574 -0
- package/dist/projects-Cjb7sovS.mjs +30 -0
- package/dist/read-credential-keys-77a91T8M-KA0Iw0Z1.mjs +9 -0
- package/dist/register.handler-BPCdor1_.mjs +86 -0
- package/dist/requirements.handler-DPXdSks3.mjs +201 -0
- package/dist/resolve-project-DDJ29sCF.mjs +35 -0
- package/dist/rolldown-runtime-twds-ZHy-BWWzu8VG.mjs +15 -0
- package/dist/run-polling-CAgFRdK3.mjs +20 -0
- package/dist/runs-D9hNLb9A.mjs +259 -0
- package/dist/schedule-BXx3uXwr.mjs +1142 -0
- package/dist/schema-17qMfNyI.mjs +18 -0
- package/dist/schema-display-CgmeKigW.mjs +130 -0
- package/dist/schemas-CDib1RhE.mjs +125 -0
- package/dist/skills-sync.handler-DIy8GR16.mjs +34 -0
- package/dist/skills.command-CrjI2dN9.mjs +35 -0
- package/dist/skills.handler-Bz8bJKql.mjs +9 -0
- package/dist/source-analysis-Cj-ADyu--BJQcFPCG.mjs +144 -0
- package/dist/spinner-progress-DMVwgqO9.mjs +173 -0
- package/dist/src-C0X6u_Mw.mjs +1340 -0
- package/dist/src-eHwu-Gfw.mjs +369 -0
- package/dist/status.handler-BO4nwvWn.mjs +101 -0
- package/dist/switch.handler-D_9213Vf.mjs +51 -0
- package/dist/sync-BL_Mo5st.mjs +39 -0
- package/dist/sync-keystroke-agent-skills-Kx_H7UTd.mjs +70 -0
- package/dist/sync.handler-BUFPdzWz.mjs +82 -0
- package/dist/task-B2sZMaZu.mjs +8 -0
- package/dist/task-target-build-CBeCKbu2.mjs +432 -0
- package/dist/task-target-deploy-C5X-USeR.mjs +4 -0
- package/dist/task-target-deploy-CA6elFpF-BEr4gkol.mjs +271 -0
- package/dist/task-target-deploy-runner.d.mts +3 -0
- package/dist/task-target-deploy-runner.mjs +202 -0
- package/dist/test-BHTgR3UA.mjs +698 -0
- package/dist/test.handler-BcPQ8b74.mjs +13 -0
- package/dist/trigger-artifacts-DQPbQNqC-B4yeeFBY.mjs +239 -0
- package/dist/trigger-manifest-CY7brZeg.mjs +30 -0
- package/dist/try-deploy.handler-DqybNhXx.mjs +490 -0
- package/dist/upload-CkU--iDC.mjs +207 -0
- package/dist/upload.handler-DCtiznQp.mjs +441 -0
- package/dist/utils-CywxCDM7.mjs +14 -0
- package/dist/validate.handler-DOcTaJL0.mjs +280 -0
- package/dist/workflow-build-DBQaBfnn.mjs +1819 -0
- package/dist/workflow-bundler-BPiqVscj-X1PFFAuP.mjs +167 -0
- package/dist/workflows-g9z87AJJ.mjs +799 -0
- package/dist/writer-BG8poUm3-BbXlU2kI.mjs +426 -0
- package/package.json +87 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { h as toErrorMessage, m as isNetworkError, o as ANSI, s as style, t as ui } from "./keystroke.mjs";
|
|
4
|
+
import { i as writeJson } from "./output-DM4b7KgY.mjs";
|
|
5
|
+
import { i as requireClient } from "./context-T7HZuB97.mjs";
|
|
6
|
+
import { t as getIntegrationCatalog } from "./integration-catalog-Bt-L3GjF.mjs";
|
|
7
|
+
import Table from "cli-table3";
|
|
8
|
+
//#region src/commands/integrations/list.handler.ts
|
|
9
|
+
function summarizeConnections(entry, connections) {
|
|
10
|
+
const matches = connections.filter((c) => c.integrationPublicId === entry.publicId);
|
|
11
|
+
if (matches.length === 0) return {
|
|
12
|
+
total: 0,
|
|
13
|
+
defaultStatus: null
|
|
14
|
+
};
|
|
15
|
+
const preferred = matches.find((c) => c.isDefault) ?? matches[0];
|
|
16
|
+
return {
|
|
17
|
+
total: matches.length,
|
|
18
|
+
defaultStatus: preferred?.connectionStatus ?? null
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function formatStatusCell(summary) {
|
|
22
|
+
if (summary.total === 0) return style("—", ANSI.dim);
|
|
23
|
+
const { defaultStatus } = summary;
|
|
24
|
+
const statusLabel = defaultStatus === "connected" ? style("connected", ANSI.green) : defaultStatus === "broken" ? style("broken", ANSI.red) : defaultStatus === "needs_reconnect" ? style("needs reconnect", ANSI.yellow) : defaultStatus === "disconnected" ? style("disconnected", ANSI.dim) : defaultStatus === "pending" ? style("pending", ANSI.dim) : defaultStatus ?? style("(unknown)", ANSI.dim);
|
|
25
|
+
return summary.total > 1 ? `${statusLabel} (+${summary.total - 1} more)` : statusLabel;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Optionally fetches the caller's connections for join. Tolerates missing
|
|
29
|
+
* org context (the `/integrations` endpoint works without an org, but
|
|
30
|
+
* `/connections` requires one — so we skip the join gracefully rather than
|
|
31
|
+
* failing the whole command).
|
|
32
|
+
*/
|
|
33
|
+
async function loadConnectionsOrEmpty(ctx) {
|
|
34
|
+
if (!ctx.organizationId) return [];
|
|
35
|
+
try {
|
|
36
|
+
return (await requireClient(ctx).connections.list()).connections;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (isNetworkError(error)) throw error;
|
|
39
|
+
ui.warn(`Could not load connections for status join: ${toErrorMessage(error)}`);
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function handleIntegrationsList(options, ctx) {
|
|
44
|
+
let entries = [...(await getIntegrationCatalog(ctx)).integrations];
|
|
45
|
+
if (options.kind) entries = entries.filter((entry) => entry.connectionKind === options.kind);
|
|
46
|
+
if (options.search) {
|
|
47
|
+
const needle = options.search.trim().toLowerCase();
|
|
48
|
+
if (needle.length > 0) entries = entries.filter((entry) => {
|
|
49
|
+
return [
|
|
50
|
+
entry.publicId,
|
|
51
|
+
entry.name,
|
|
52
|
+
...entry.aliases
|
|
53
|
+
].join("\n").toLowerCase().includes(needle);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const connections = await loadConnectionsOrEmpty(ctx);
|
|
57
|
+
if (options.connected) {
|
|
58
|
+
const connectedIds = new Set(connections.map((c) => c.integrationPublicId).filter((id) => id !== null));
|
|
59
|
+
entries = entries.filter((entry) => connectedIds.has(entry.publicId));
|
|
60
|
+
}
|
|
61
|
+
if (ctx.jsonMode) {
|
|
62
|
+
writeJson(entries.map((entry) => ({
|
|
63
|
+
...entry,
|
|
64
|
+
connections: connections.filter((c) => c.integrationPublicId === entry.publicId)
|
|
65
|
+
})));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (entries.length === 0) {
|
|
69
|
+
const filters = [];
|
|
70
|
+
if (options.kind) filters.push(`kind=${options.kind}`);
|
|
71
|
+
if (options.search) filters.push(`search=${options.search}`);
|
|
72
|
+
if (options.connected) filters.push("connected=true");
|
|
73
|
+
const filterSuffix = filters.length > 0 ? ` matching ${filters.join(", ")}` : "";
|
|
74
|
+
ui.hint(`No integrations${filterSuffix}.`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const table = new Table({
|
|
78
|
+
head: [
|
|
79
|
+
"ID",
|
|
80
|
+
"Kind",
|
|
81
|
+
"Name",
|
|
82
|
+
"Status",
|
|
83
|
+
"Aliases"
|
|
84
|
+
],
|
|
85
|
+
style: { head: [] }
|
|
86
|
+
});
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const summary = summarizeConnections(entry, connections);
|
|
89
|
+
const otherAliases = entry.aliases.filter((alias) => alias !== entry.publicId);
|
|
90
|
+
table.push([
|
|
91
|
+
style(entry.publicId, `${ANSI.bold}${ANSI.cyan}`),
|
|
92
|
+
entry.connectionKind,
|
|
93
|
+
entry.name,
|
|
94
|
+
formatStatusCell(summary),
|
|
95
|
+
otherAliases.length > 0 ? otherAliases.join(", ") : style("—", ANSI.dim)
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
ui.text(table.toString());
|
|
99
|
+
const connectedCount = entries.reduce((acc, entry) => summarizeConnections(entry, connections).total > 0 ? acc + 1 : acc, 0);
|
|
100
|
+
ui.hint(`\n${entries.length} integration${entries.length === 1 ? "" : "s"}` + (connections.length > 0 ? ` · ${connectedCount} with active connections` : "") + (options.kind || options.search || options.connected ? " (filtered)" : ""));
|
|
101
|
+
if (connections.length === 0 && !ctx.organizationId) ui.hint("Sign in and select an organization to see per-integration connection status.");
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { handleIntegrationsList };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { t as createTypedCommand } from "./commander-DfTVqQ-3.mjs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/commands/logs/logs.command.ts
|
|
6
|
+
const LogsOptionsSchema = z.object({ count: z.coerce.number().int().min(1).max(1e3).default(100).describe("Number of log entries to display") });
|
|
7
|
+
const LOGS_OPTIONS_CONFIG = { count: {
|
|
8
|
+
flag: "--count <n>",
|
|
9
|
+
description: "Number of log entries to display (default: 100)"
|
|
10
|
+
} };
|
|
11
|
+
function createLogsCommand() {
|
|
12
|
+
return createTypedCommand({
|
|
13
|
+
name: "logs",
|
|
14
|
+
description: "Display recent CLI log entries",
|
|
15
|
+
schema: LogsOptionsSchema,
|
|
16
|
+
optionsConfig: LOGS_OPTIONS_CONFIG,
|
|
17
|
+
loadHandler: async () => (await import("./logs.handler-6hoMBzqw.mjs")).handleLogs,
|
|
18
|
+
subcommands: [createTypedCommand({
|
|
19
|
+
name: "clear",
|
|
20
|
+
description: "Delete the CLI log file",
|
|
21
|
+
schema: z.object({}),
|
|
22
|
+
optionsConfig: {},
|
|
23
|
+
loadHandler: async () => (await import("./clear.handler-BydlX-zE.mjs")).handleLogsClear
|
|
24
|
+
})]
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { createLogsCommand };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { a as originalConsole, o as ANSI, s as style, t as ui } from "./keystroke.mjs";
|
|
4
|
+
import { o as readLogFile } from "./dist-CUK7yBM0.mjs";
|
|
5
|
+
//#region src/commands/logs/logs.handler.ts
|
|
6
|
+
const LEVEL_STYLES = {
|
|
7
|
+
debug: ANSI.dim,
|
|
8
|
+
info: ANSI.cyan,
|
|
9
|
+
warn: ANSI.yellow,
|
|
10
|
+
error: ANSI.red
|
|
11
|
+
};
|
|
12
|
+
function formatEntry(line) {
|
|
13
|
+
let entry;
|
|
14
|
+
try {
|
|
15
|
+
entry = JSON.parse(line);
|
|
16
|
+
} catch {
|
|
17
|
+
return line;
|
|
18
|
+
}
|
|
19
|
+
const ts = style(entry.timestamp, ANSI.dim);
|
|
20
|
+
const levelStyled = style(`[${entry.level.toUpperCase()}]`, LEVEL_STYLES[entry.level] ?? "");
|
|
21
|
+
const meta = entry.meta !== void 0 ? ` ${style(JSON.stringify(entry.meta), ANSI.dim)}` : "";
|
|
22
|
+
return `${ts} ${levelStyled} ${entry.message}${meta}`;
|
|
23
|
+
}
|
|
24
|
+
async function handleLogs(options, _ctx) {
|
|
25
|
+
const lines = await readLogFile(options.count);
|
|
26
|
+
if (lines.length === 0) {
|
|
27
|
+
ui.hint("No log entries found.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const line of lines) originalConsole.info(formatEntry(line));
|
|
31
|
+
ui.br();
|
|
32
|
+
ui.hint(`Showing ${lines.length} log ${lines.length === 1 ? "entry" : "entries"}.`);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { handleLogs };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { D as throwReportedCliExit, h as toErrorMessage, o as ANSI, s as style, t as ui } from "./keystroke.mjs";
|
|
4
|
+
import { i as writeJson } from "./output-DM4b7KgY.mjs";
|
|
5
|
+
import { i as requireClient } from "./context-T7HZuB97.mjs";
|
|
6
|
+
import { n as sleep, t as TERMINAL_STATUSES } from "./run-polling-CAgFRdK3.mjs";
|
|
7
|
+
import Table from "cli-table3";
|
|
8
|
+
//#region src/commands/workflows/_shared/run-query.ts
|
|
9
|
+
async function listRunsByWorkflowRef(client, options) {
|
|
10
|
+
const byAuthoredId = await client.runs.listRuns({
|
|
11
|
+
authoredWorkflowId: options.workflowRef,
|
|
12
|
+
status: options.status,
|
|
13
|
+
limit: options.limit
|
|
14
|
+
});
|
|
15
|
+
if (byAuthoredId.runs.length > 0) return {
|
|
16
|
+
kind: "authoredId",
|
|
17
|
+
runs: byAuthoredId.runs
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
kind: "name",
|
|
21
|
+
runs: (await client.runs.listRuns({
|
|
22
|
+
workflowName: options.workflowRef,
|
|
23
|
+
status: options.status,
|
|
24
|
+
limit: options.limit
|
|
25
|
+
})).runs
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/commands/workflows/logs/run-log-display.ts
|
|
30
|
+
const LOG_LEVEL_STYLES = {
|
|
31
|
+
debug: ANSI.dim,
|
|
32
|
+
info: ANSI.cyan,
|
|
33
|
+
warn: ANSI.yellow,
|
|
34
|
+
error: ANSI.red
|
|
35
|
+
};
|
|
36
|
+
function formatTimestamp(timestamp) {
|
|
37
|
+
return (typeof timestamp === "string" ? new Date(timestamp) : timestamp).toISOString().replace("T", " ").replace("Z", "").substring(0, 23);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Formats a single run log entry as a terminal-ready string.
|
|
41
|
+
* When verbose is true, includes metadata JSON on a second line.
|
|
42
|
+
*/
|
|
43
|
+
function formatRunLogLine(log, verbose) {
|
|
44
|
+
const ts = style(formatTimestamp(log.timestamp), ANSI.dim);
|
|
45
|
+
const source = style(`[${log.source}]`, ANSI.dim);
|
|
46
|
+
const levelStyle = LOG_LEVEL_STYLES[log.level] ?? ANSI.reset;
|
|
47
|
+
const levelTag = style(`[${log.level.toUpperCase()}]`, levelStyle);
|
|
48
|
+
const stepHint = log.correlationId ? style(` <${log.correlationId}>`, ANSI.dim) : "";
|
|
49
|
+
const meta = verbose && log.metadata ? `\n ${style(JSON.stringify(log.metadata), ANSI.dim)}` : "";
|
|
50
|
+
return `${ts} ${source}${stepHint} ${levelTag} ${log.message}${meta}`;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/commands/workflows/logs/logs.handler.ts
|
|
54
|
+
function formatDuration(createdAt, completedAt) {
|
|
55
|
+
const start = new Date(createdAt).getTime();
|
|
56
|
+
const ms = (completedAt ? new Date(completedAt).getTime() : Date.now()) - start;
|
|
57
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
58
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
59
|
+
return `${Math.floor(ms / 6e4)}m ${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
60
|
+
}
|
|
61
|
+
function formatStatus(status) {
|
|
62
|
+
return style(status, {
|
|
63
|
+
completed: ANSI.green,
|
|
64
|
+
failed: ANSI.red,
|
|
65
|
+
running: ANSI.cyan,
|
|
66
|
+
pending: ANSI.dim,
|
|
67
|
+
cancelled: ANSI.dim
|
|
68
|
+
}[status] ?? ANSI.reset);
|
|
69
|
+
}
|
|
70
|
+
function resolveLogsMode(options) {
|
|
71
|
+
if (options.runId) return "run";
|
|
72
|
+
if (options.latest) return "latest";
|
|
73
|
+
return "list";
|
|
74
|
+
}
|
|
75
|
+
async function handleWorkflowsLogs(options, ctx) {
|
|
76
|
+
const client = requireClient(ctx);
|
|
77
|
+
const jsonMode = ctx.jsonMode;
|
|
78
|
+
const mode = resolveLogsMode(options);
|
|
79
|
+
if (mode === "run") {
|
|
80
|
+
await showRunLogs(client, options.runId, options, jsonMode);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (mode === "latest") {
|
|
84
|
+
await showLatestRunLogs(client, options, jsonMode);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
await listRecentRuns(client, options, jsonMode);
|
|
88
|
+
}
|
|
89
|
+
async function listRecentRuns(client, options, jsonMode) {
|
|
90
|
+
try {
|
|
91
|
+
const result = await listRunsByWorkflowRef(client, {
|
|
92
|
+
workflowRef: options.workflow,
|
|
93
|
+
status: options.status,
|
|
94
|
+
limit: options.limit
|
|
95
|
+
});
|
|
96
|
+
const { runs } = result;
|
|
97
|
+
if (jsonMode) {
|
|
98
|
+
writeJson(runs.map((run) => ({
|
|
99
|
+
id: run.id,
|
|
100
|
+
authoredWorkflowId: run.authoredWorkflowId,
|
|
101
|
+
workflowName: run.workflowName,
|
|
102
|
+
status: run.status,
|
|
103
|
+
createdAt: run.createdAt,
|
|
104
|
+
completedAt: run.completedAt ?? null,
|
|
105
|
+
duration: formatDuration(run.createdAt, run.completedAt)
|
|
106
|
+
})));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (runs.length === 0) {
|
|
110
|
+
ui.hint(`No runs found for workflow "${options.workflow}".`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const table = new Table({
|
|
114
|
+
head: [
|
|
115
|
+
"Run ID",
|
|
116
|
+
"Workflow ID",
|
|
117
|
+
"Workflow",
|
|
118
|
+
"Status",
|
|
119
|
+
"Created",
|
|
120
|
+
"Duration"
|
|
121
|
+
],
|
|
122
|
+
style: { head: [] }
|
|
123
|
+
});
|
|
124
|
+
for (const run of runs) table.push([
|
|
125
|
+
run.id,
|
|
126
|
+
run.authoredWorkflowId,
|
|
127
|
+
run.workflowName,
|
|
128
|
+
formatStatus(run.status),
|
|
129
|
+
new Date(run.createdAt).toLocaleString(),
|
|
130
|
+
formatDuration(run.createdAt, run.completedAt)
|
|
131
|
+
]);
|
|
132
|
+
ui.text(table.toString());
|
|
133
|
+
ui.hint(result.kind === "authoredId" ? `${runs.length} run(s) matched workflow id "${options.workflow}". Use --latest to view the newest run's logs, or --run-id <id> to pin a specific run.` : `${runs.length} run(s) matched workflow name "${options.workflow}". Use --latest to view the newest run's logs, or --run-id <id> to pin a specific run.`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const message = toErrorMessage(error);
|
|
136
|
+
ui.error(`Failed to list runs: ${message}`);
|
|
137
|
+
throwReportedCliExit(`Failed to list runs: ${message}`, { cause: error });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function showLatestRunLogs(client, options, jsonMode) {
|
|
141
|
+
try {
|
|
142
|
+
const result = await listRunsByWorkflowRef(client, {
|
|
143
|
+
workflowRef: options.workflow,
|
|
144
|
+
limit: 1
|
|
145
|
+
});
|
|
146
|
+
const latestRun = result.runs[0];
|
|
147
|
+
if (!latestRun) {
|
|
148
|
+
ui.hint(`No runs found for workflow "${options.workflow}".`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!jsonMode) ui.hint(result.kind === "authoredId" ? `Showing logs for the latest run of workflow id "${options.workflow}". Use --run-id ${latestRun.id} to pin this run.` : `Showing logs for the latest run of workflow name "${options.workflow}". Use --run-id ${latestRun.id} to pin this run.`);
|
|
152
|
+
await showRunLogs(client, latestRun.id, options, jsonMode);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const message = toErrorMessage(error);
|
|
155
|
+
ui.error(`Failed to resolve latest run: ${message}`);
|
|
156
|
+
throwReportedCliExit(`Failed to resolve latest run: ${message}`, { cause: error });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function showRunLogs(client, runId, options, jsonMode) {
|
|
160
|
+
const logFetchLimit = 200;
|
|
161
|
+
try {
|
|
162
|
+
const { run } = await client.runs.getRun(runId);
|
|
163
|
+
const { logs } = await client.runs.listLogs(runId, {
|
|
164
|
+
level: options.level,
|
|
165
|
+
limit: logFetchLimit,
|
|
166
|
+
offset: 0
|
|
167
|
+
});
|
|
168
|
+
if (jsonMode) {
|
|
169
|
+
const visibleLogs = options.tail ? logs.slice(-options.tail) : logs;
|
|
170
|
+
writeJson({
|
|
171
|
+
run: {
|
|
172
|
+
id: run.id,
|
|
173
|
+
authoredWorkflowId: run.authoredWorkflowId,
|
|
174
|
+
workflowName: run.workflowName,
|
|
175
|
+
status: run.status,
|
|
176
|
+
createdAt: run.createdAt,
|
|
177
|
+
completedAt: run.completedAt ?? null
|
|
178
|
+
},
|
|
179
|
+
logs: visibleLogs.map((log) => ({
|
|
180
|
+
id: log.id,
|
|
181
|
+
level: log.level,
|
|
182
|
+
message: log.message,
|
|
183
|
+
timestamp: log.timestamp,
|
|
184
|
+
metadata: log.metadata ?? null,
|
|
185
|
+
correlationId: log.correlationId ?? null
|
|
186
|
+
}))
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
ui.hint(`Run: ${run.id} | Workflow: ${run.workflowName} | Status: ${formatStatus(run.status)}`);
|
|
191
|
+
const seenIds = new Set(logs.map((log) => log.id));
|
|
192
|
+
const visibleLogs = options.tail ? logs.slice(-options.tail) : logs;
|
|
193
|
+
for (const log of visibleLogs) ui.text(formatRunLogLine({
|
|
194
|
+
...log,
|
|
195
|
+
metadata: log.metadata ?? void 0,
|
|
196
|
+
correlationId: log.correlationId ?? void 0
|
|
197
|
+
}, options.verbose));
|
|
198
|
+
if (!options.follow || TERMINAL_STATUSES.has(run.status)) {
|
|
199
|
+
if (visibleLogs.length === 0) ui.hint("No logs found for this run.");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
let currentStatus = run.status;
|
|
203
|
+
while (!TERMINAL_STATUSES.has(currentStatus)) {
|
|
204
|
+
await sleep(2e3);
|
|
205
|
+
try {
|
|
206
|
+
const { run: freshRun } = await client.runs.getRun(runId);
|
|
207
|
+
currentStatus = freshRun.status;
|
|
208
|
+
const { logs: newLogs } = await client.runs.listLogs(runId, {
|
|
209
|
+
level: options.level,
|
|
210
|
+
limit: logFetchLimit,
|
|
211
|
+
offset: 0
|
|
212
|
+
});
|
|
213
|
+
for (const log of newLogs) if (!seenIds.has(log.id)) {
|
|
214
|
+
seenIds.add(log.id);
|
|
215
|
+
ui.text(formatRunLogLine({
|
|
216
|
+
...log,
|
|
217
|
+
metadata: log.metadata ?? void 0,
|
|
218
|
+
correlationId: log.correlationId ?? void 0
|
|
219
|
+
}, options.verbose));
|
|
220
|
+
}
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
ui.hint(`Run ${runId} finished with status: ${formatStatus(currentStatus)}`);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const message = toErrorMessage(error);
|
|
226
|
+
ui.error(`Failed to get run logs: ${message}`);
|
|
227
|
+
throwReportedCliExit(`Failed to get run logs: ${message}`, { cause: error });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
export { handleWorkflowsLogs };
|