@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.
Files changed (122) hide show
  1. package/AGENTS-blurb.md +123 -0
  2. package/LICENSE +42 -0
  3. package/README.md +177 -0
  4. package/THIRD_PARTY_NOTICES.md +16 -0
  5. package/bin/keystroke.mjs +107 -0
  6. package/dist/_manifest-JSRE3H8k.mjs +385 -0
  7. package/dist/agent-bundle-package-DWV6B_5q-BtV7Xycc.mjs +2344 -0
  8. package/dist/agent-manifest-CDnbkR2f.mjs +245 -0
  9. package/dist/agents-CZJGxVqV.mjs +228 -0
  10. package/dist/api-keys-D2lgguuY.mjs +40 -0
  11. package/dist/auth-DN2VusyU.mjs +59 -0
  12. package/dist/auth.handler-CT1BQUvu.mjs +340 -0
  13. package/dist/browser-qwFrUH82.mjs +24 -0
  14. package/dist/build-agents-BmM_AsSd-BGi9wtzt.mjs +514 -0
  15. package/dist/build-metadata-BWS7uhd_-DR8gJjTX.mjs +1422 -0
  16. package/dist/build-progress-DgYKb4hB.mjs +183 -0
  17. package/dist/build-tasks-CdihpudT-D5r5HUHe.mjs +91 -0
  18. package/dist/build-workflows-CfxBnIWh-CdYPv8w2.mjs +370 -0
  19. package/dist/build.handler-4799CjWH.mjs +36 -0
  20. package/dist/chunk-CH6r78ws.mjs +37 -0
  21. package/dist/clear-cache.handler-B9tqSoSM.mjs +11 -0
  22. package/dist/clear.handler-BTIXXPTJ.mjs +42 -0
  23. package/dist/clear.handler-BydlX-zE.mjs +11 -0
  24. package/dist/commander-DfTVqQ-3.mjs +133 -0
  25. package/dist/concurrency-gXn9Rw8x-DNl2YtrS.mjs +20 -0
  26. package/dist/connect-BUXkeH0F.mjs +43 -0
  27. package/dist/connect.handler-CYel9cy6.mjs +430 -0
  28. package/dist/constants-CPpPdSNg.mjs +8 -0
  29. package/dist/context-T7HZuB97.mjs +138 -0
  30. package/dist/credential-env-map-CI8yWHVy.mjs +28 -0
  31. package/dist/credential-schema-mismatch-BKo5PjcQ.mjs +76 -0
  32. package/dist/credentials-CvmjU0lK.mjs +171 -0
  33. package/dist/credentials-OfVHOtG3.mjs +151216 -0
  34. package/dist/current-deployment-workflow-poHt27i3.mjs +94 -0
  35. package/dist/current.handler-B8zKzfPp.mjs +21 -0
  36. package/dist/delete.handler-bAu1iXVQ.mjs +17 -0
  37. package/dist/deploy-7Jjls436.mjs +26 -0
  38. package/dist/deploy-BOPIpRWm.mjs +74 -0
  39. package/dist/deploy-progress-BmGUNFKg.mjs +70 -0
  40. package/dist/deploy.handler-BAzgiNhd.mjs +370 -0
  41. package/dist/detect-env-access-CwkOYeYM-D_BCZqV6.mjs +209 -0
  42. package/dist/diff-utils-NEfcjqxt.mjs +185 -0
  43. package/dist/diff.handler-Du7SY8K4.mjs +47 -0
  44. package/dist/dist-BkJUoBiG.mjs +1116 -0
  45. package/dist/dist-CUK7yBM0.mjs +308 -0
  46. package/dist/env-91KwMKov.mjs +140 -0
  47. package/dist/env.handler-BAzBuMzQ.mjs +277 -0
  48. package/dist/error-boundary-VL-JLfIa.mjs +34 -0
  49. package/dist/file-metadata-D1vm-XY2.mjs +191 -0
  50. package/dist/get-intrinsic-zLxwtrLK.mjs +658 -0
  51. package/dist/import-module-CV84H5fZ-B_CBCmb4.mjs +1747 -0
  52. package/dist/init-DpMCotSK.mjs +45 -0
  53. package/dist/init.handler-CPRnif52.mjs +585 -0
  54. package/dist/inspect.handler-DT_cD036.mjs +146 -0
  55. package/dist/integration-catalog-Bt-L3GjF.mjs +104 -0
  56. package/dist/integrations-DlatPK4W.mjs +79 -0
  57. package/dist/keystroke.d.mts +3 -0
  58. package/dist/keystroke.mjs +707 -0
  59. package/dist/layout-CbMtQ2tm.mjs +67 -0
  60. package/dist/list-enrichment-y-cwizLr.mjs +189 -0
  61. package/dist/list.handler-BTWvCyjA.mjs +52 -0
  62. package/dist/list.handler-CWF_Dj15.mjs +24 -0
  63. package/dist/list.handler-CZ6G2x_G.mjs +75 -0
  64. package/dist/list.handler-DWaQkJaR.mjs +51 -0
  65. package/dist/list.handler-DqbFcBW7.mjs +180 -0
  66. package/dist/list.handler-lq3ZGAn4.mjs +104 -0
  67. package/dist/logs-BEg9L5l8.mjs +28 -0
  68. package/dist/logs.handler-6hoMBzqw.mjs +35 -0
  69. package/dist/logs.handler-BD_dXiL1.mjs +231 -0
  70. package/dist/metadata-layout-GUYIUo0i-_aG2zjue.mjs +5877 -0
  71. package/dist/normalize-path-CojS-CgQ-DLCOvnD1.mjs +20 -0
  72. package/dist/options-CeaTcFxP.mjs +43 -0
  73. package/dist/org-xLzBtt2_.mjs +41 -0
  74. package/dist/output-DM4b7KgY.mjs +72 -0
  75. package/dist/oxc-B3KI3rf_-n9d1hKNq.mjs +119 -0
  76. package/dist/paused.handler-BMFm9Cff.mjs +94 -0
  77. package/dist/project-config-D1qsQlO7.mjs +107 -0
  78. package/dist/projects-CHkRE9rS.mjs +1574 -0
  79. package/dist/projects-Cjb7sovS.mjs +30 -0
  80. package/dist/read-credential-keys-77a91T8M-KA0Iw0Z1.mjs +9 -0
  81. package/dist/register.handler-BPCdor1_.mjs +86 -0
  82. package/dist/requirements.handler-DPXdSks3.mjs +201 -0
  83. package/dist/resolve-project-DDJ29sCF.mjs +35 -0
  84. package/dist/rolldown-runtime-twds-ZHy-BWWzu8VG.mjs +15 -0
  85. package/dist/run-polling-CAgFRdK3.mjs +20 -0
  86. package/dist/runs-D9hNLb9A.mjs +259 -0
  87. package/dist/schedule-BXx3uXwr.mjs +1142 -0
  88. package/dist/schema-17qMfNyI.mjs +18 -0
  89. package/dist/schema-display-CgmeKigW.mjs +130 -0
  90. package/dist/schemas-CDib1RhE.mjs +125 -0
  91. package/dist/skills-sync.handler-DIy8GR16.mjs +34 -0
  92. package/dist/skills.command-CrjI2dN9.mjs +35 -0
  93. package/dist/skills.handler-Bz8bJKql.mjs +9 -0
  94. package/dist/source-analysis-Cj-ADyu--BJQcFPCG.mjs +144 -0
  95. package/dist/spinner-progress-DMVwgqO9.mjs +173 -0
  96. package/dist/src-C0X6u_Mw.mjs +1340 -0
  97. package/dist/src-eHwu-Gfw.mjs +369 -0
  98. package/dist/status.handler-BO4nwvWn.mjs +101 -0
  99. package/dist/switch.handler-D_9213Vf.mjs +51 -0
  100. package/dist/sync-BL_Mo5st.mjs +39 -0
  101. package/dist/sync-keystroke-agent-skills-Kx_H7UTd.mjs +70 -0
  102. package/dist/sync.handler-BUFPdzWz.mjs +82 -0
  103. package/dist/task-B2sZMaZu.mjs +8 -0
  104. package/dist/task-target-build-CBeCKbu2.mjs +432 -0
  105. package/dist/task-target-deploy-C5X-USeR.mjs +4 -0
  106. package/dist/task-target-deploy-CA6elFpF-BEr4gkol.mjs +271 -0
  107. package/dist/task-target-deploy-runner.d.mts +3 -0
  108. package/dist/task-target-deploy-runner.mjs +202 -0
  109. package/dist/test-BHTgR3UA.mjs +698 -0
  110. package/dist/test.handler-BcPQ8b74.mjs +13 -0
  111. package/dist/trigger-artifacts-DQPbQNqC-B4yeeFBY.mjs +239 -0
  112. package/dist/trigger-manifest-CY7brZeg.mjs +30 -0
  113. package/dist/try-deploy.handler-DqybNhXx.mjs +490 -0
  114. package/dist/upload-CkU--iDC.mjs +207 -0
  115. package/dist/upload.handler-DCtiznQp.mjs +441 -0
  116. package/dist/utils-CywxCDM7.mjs +14 -0
  117. package/dist/validate.handler-DOcTaJL0.mjs +280 -0
  118. package/dist/workflow-build-DBQaBfnn.mjs +1819 -0
  119. package/dist/workflow-bundler-BPiqVscj-X1PFFAuP.mjs +167 -0
  120. package/dist/workflows-g9z87AJJ.mjs +799 -0
  121. package/dist/writer-BG8poUm3-BbXlU2kI.mjs +426 -0
  122. package/package.json +87 -0
@@ -0,0 +1,707 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command, CommanderError, Option } from "commander";
4
+ import * as fs from "node:fs";
5
+ import * as fsPromises from "node:fs/promises";
6
+ import * as os from "node:os";
7
+ import * as path$1 from "node:path";
8
+ import { z } from "zod";
9
+ import { log } from "@clack/prompts";
10
+ //#region package.json
11
+ var version = "0.0.1";
12
+ //#endregion
13
+ //#region src/command-registry.ts
14
+ const ROOT_OPTIONS_WITH_VALUES = new Set([
15
+ "--api-key",
16
+ "--base-url",
17
+ "--org"
18
+ ]);
19
+ const ROOT_VERSION_FLAGS = new Set(["-V", "--version"]);
20
+ const lazyCommandDefinitions = [
21
+ {
22
+ name: "agents",
23
+ loadCommand: async () => (await import("./agents-CZJGxVqV.mjs")).createAgentsCommand()
24
+ },
25
+ {
26
+ name: "api-keys",
27
+ loadCommand: async () => (await import("./api-keys-D2lgguuY.mjs")).createApiKeysCommand()
28
+ },
29
+ {
30
+ name: "auth",
31
+ loadCommand: async () => (await import("./auth-DN2VusyU.mjs")).createAuthCommand()
32
+ },
33
+ {
34
+ name: "connect",
35
+ loadCommand: async () => (await import("./connect-BUXkeH0F.mjs")).createConnectCommand()
36
+ },
37
+ {
38
+ name: "credentials",
39
+ loadCommand: async () => (await import("./credentials-CvmjU0lK.mjs")).createCredentialsCommand(),
40
+ copyInheritedSettings: true
41
+ },
42
+ {
43
+ name: "org",
44
+ loadCommand: async () => (await import("./org-xLzBtt2_.mjs")).createOrgCommand()
45
+ },
46
+ {
47
+ name: "deploy",
48
+ loadCommand: async () => (await import("./deploy-BOPIpRWm.mjs")).createDeployCommand()
49
+ },
50
+ {
51
+ name: "init",
52
+ loadCommand: async () => (await import("./init-DpMCotSK.mjs")).createInitCommand()
53
+ },
54
+ {
55
+ name: "integrations",
56
+ loadCommand: async () => (await import("./integrations-DlatPK4W.mjs")).createIntegrationsCommand()
57
+ },
58
+ {
59
+ name: "logs",
60
+ loadCommand: async () => (await import("./logs-BEg9L5l8.mjs")).createLogsCommand()
61
+ },
62
+ {
63
+ name: "projects",
64
+ loadCommand: async () => (await import("./projects-Cjb7sovS.mjs")).createProjectsCommand()
65
+ },
66
+ {
67
+ name: "runs",
68
+ loadCommand: async () => (await import("./runs-D9hNLb9A.mjs")).createRunsCommand()
69
+ },
70
+ {
71
+ name: "skills",
72
+ loadCommand: async () => (await import("./skills.command-CrjI2dN9.mjs")).createSkillsCommand()
73
+ },
74
+ {
75
+ name: "sync",
76
+ loadCommand: async () => (await import("./sync-BL_Mo5st.mjs")).createSyncCommand()
77
+ },
78
+ {
79
+ name: "test",
80
+ loadCommand: async () => (await import("./test-BHTgR3UA.mjs")).createTestCommand()
81
+ },
82
+ {
83
+ name: "workflows",
84
+ loadCommand: async () => (await import("./workflows-g9z87AJJ.mjs")).createWorkflowsCommand()
85
+ }
86
+ ];
87
+ function selectCommandRegistration(argv, commandNames = new Set(lazyCommandDefinitions.map((definition) => definition.name))) {
88
+ const firstCommand = readFirstNonOption(argv, 0);
89
+ if (!firstCommand) return argv.some((arg) => ROOT_VERSION_FLAGS.has(arg)) ? { kind: "none" } : { kind: "all" };
90
+ if (firstCommand.value === "help") {
91
+ const helpTarget = readFirstNonOption(argv, firstCommand.index + 1);
92
+ return helpTarget && commandNames.has(helpTarget.value) ? {
93
+ kind: "selected",
94
+ name: helpTarget.value
95
+ } : { kind: "all" };
96
+ }
97
+ return commandNames.has(firstCommand.value) ? {
98
+ kind: "selected",
99
+ name: firstCommand.value
100
+ } : { kind: "all" };
101
+ }
102
+ async function registerCommandsForArgv(program, argv, definitions = lazyCommandDefinitions) {
103
+ const selection = selectCommandRegistration(argv, new Set(definitions.map((definition) => definition.name)));
104
+ const selectedDefinitions = selection.kind === "none" ? [] : selection.kind === "all" ? definitions : definitions.filter((definition) => definition.name === selection.name);
105
+ for (const definition of selectedDefinitions) {
106
+ const command = await definition.loadCommand();
107
+ if (definition.copyInheritedSettings) command.copyInheritedSettings(program);
108
+ program.addCommand(command);
109
+ }
110
+ }
111
+ function readFirstNonOption(argv, startIndex) {
112
+ for (let index = startIndex; index < argv.length; index += 1) {
113
+ const arg = argv[index];
114
+ if (!arg) continue;
115
+ if (arg === "--") return null;
116
+ if (arg.startsWith("--")) {
117
+ const [flag] = arg.split("=", 1);
118
+ if (flag && ROOT_OPTIONS_WITH_VALUES.has(flag) && !arg.includes("=")) index += 1;
119
+ continue;
120
+ }
121
+ if (arg.startsWith("-")) continue;
122
+ return {
123
+ value: arg,
124
+ index
125
+ };
126
+ }
127
+ return null;
128
+ }
129
+ //#endregion
130
+ //#region src/lib/cli-errors.ts
131
+ var CliExitError = class extends Error {
132
+ exitCode;
133
+ reported;
134
+ constructor(message, options = {}) {
135
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
136
+ this.name = "CliExitError";
137
+ this.exitCode = options.exitCode ?? 1;
138
+ this.reported = options.reported ?? false;
139
+ }
140
+ markReported() {
141
+ this.reported = true;
142
+ return this;
143
+ }
144
+ };
145
+ function throwReportedCliExit(message, options = {}) {
146
+ throw new CliExitError(message, {
147
+ ...options,
148
+ reported: true
149
+ });
150
+ }
151
+ var InputValidationError = class extends CliExitError {
152
+ constructor(message, options = {}) {
153
+ super(message, options);
154
+ this.name = "InputValidationError";
155
+ }
156
+ };
157
+ var WorkflowResolutionError = class extends CliExitError {
158
+ constructor(message, options = {}) {
159
+ super(message, options);
160
+ this.name = "WorkflowResolutionError";
161
+ }
162
+ };
163
+ var ProjectNotFoundError = class extends CliExitError {
164
+ constructor(message, options = {}) {
165
+ super(message, options);
166
+ this.name = "ProjectNotFoundError";
167
+ }
168
+ };
169
+ var AuthenticationError = class extends CliExitError {
170
+ constructor(message, options = {}) {
171
+ super(message, options);
172
+ this.name = "AuthenticationError";
173
+ }
174
+ };
175
+ //#endregion
176
+ //#region src/commands/auth/auth.constants.ts
177
+ const CLI_AUTH_COMMAND = "keystroke auth";
178
+ const CALLBACK_LOOPBACK_HOST = "127.0.0.1";
179
+ const CALLBACK_PATH = "/callback";
180
+ const CALLBACK_LOOPBACK_ORIGIN = `http://${CALLBACK_LOOPBACK_HOST}`;
181
+ const AUTH_TIMEOUT_SECONDS = {
182
+ min: 30,
183
+ max: 900,
184
+ default: 180
185
+ };
186
+ const AUTH_URL_PATH = "/auth/device";
187
+ //#endregion
188
+ //#region src/lib/error-utils.ts
189
+ /** Extract a human-readable message from any error value. */
190
+ function toErrorMessage(error) {
191
+ if (error instanceof Error) return error.message;
192
+ return String(error);
193
+ }
194
+ /**
195
+ * Builds a combined string from error.message + error.cause.message + error.cause.code
196
+ * for broad pattern matching against network/auth keywords.
197
+ */
198
+ function getFullErrorMessage(error) {
199
+ const parts = [toErrorMessage(error)];
200
+ if (error instanceof Error && error.cause) {
201
+ const c = error.cause;
202
+ if (typeof c?.message === "string") parts.push(c.message);
203
+ if (typeof c?.code === "string") parts.push(c.code);
204
+ }
205
+ return parts.join(" ").toLowerCase();
206
+ }
207
+ const NETWORK_ERROR_PATTERNS = [
208
+ "network",
209
+ "fetch",
210
+ "econnrefused",
211
+ "econnreset",
212
+ "enotfound",
213
+ "etimedout",
214
+ "timeout",
215
+ "timed out",
216
+ "connection refused",
217
+ "unable to reach",
218
+ "unable to connect",
219
+ "getaddrinfo",
220
+ "failed to fetch",
221
+ "connection reset",
222
+ "is the computer"
223
+ ];
224
+ const AUTH_ERROR_PATTERNS = [
225
+ "401",
226
+ "unauthorized",
227
+ "403",
228
+ "forbidden",
229
+ "expired",
230
+ "invalid api key",
231
+ "invalid token",
232
+ "invalid credentials",
233
+ "invalid authentication"
234
+ ];
235
+ /** True when the error looks like a network/connection failure rather than an API response. */
236
+ function isNetworkError(error) {
237
+ const status = getHttpStatus(error);
238
+ if (status !== null) {
239
+ if (status === 502 || status === 503 || status === 504) return true;
240
+ return false;
241
+ }
242
+ const msg = getFullErrorMessage(error);
243
+ return NETWORK_ERROR_PATTERNS.some((p) => msg.includes(p));
244
+ }
245
+ /** True when the error indicates an authentication/authorization failure (401, 403, expired key, etc.). */
246
+ function isAuthError(error) {
247
+ const status = getHttpStatus(error);
248
+ if (status !== null) return status === 401 || status === 403;
249
+ const msg = toErrorMessage(error).toLowerCase();
250
+ return AUTH_ERROR_PATTERNS.some((p) => msg.includes(p));
251
+ }
252
+ /** The hint message shown when auth fails. */
253
+ const AUTH_HINT = `Run \`${CLI_AUTH_COMMAND}\` to authenticate.`;
254
+ /** The hint message shown when auth needs re-authentication. */
255
+ const REAUTH_HINT = `Run \`${CLI_AUTH_COMMAND}\` to re-authenticate.`;
256
+ function isHttpError(error) {
257
+ if (typeof error !== "object" || error === null || !("response" in error)) return false;
258
+ const resp = error.response;
259
+ if (typeof resp !== "object" || resp === null) return false;
260
+ return typeof resp.status === "number";
261
+ }
262
+ function getHttpStatus(error) {
263
+ if (!isHttpError(error)) return null;
264
+ return error.response.status;
265
+ }
266
+ async function getApiErrorCode(error) {
267
+ if (!isHttpError(error)) return null;
268
+ if (typeof error.response.clone !== "function") return null;
269
+ try {
270
+ const body = await error.response.clone().json();
271
+ return typeof body.errorCode === "string" ? body.errorCode : null;
272
+ } catch {
273
+ return null;
274
+ }
275
+ }
276
+ //#endregion
277
+ //#region src/lib/tty.ts
278
+ function isTTY() {
279
+ return process.stdout.isTTY === true;
280
+ }
281
+ //#endregion
282
+ //#region src/lib/terminal.ts
283
+ const ANSI = {
284
+ reset: "\x1B[0m",
285
+ bold: "\x1B[1m",
286
+ dim: "\x1B[2m",
287
+ red: "\x1B[31m",
288
+ cyan: "\x1B[36m",
289
+ green: "\x1B[32m",
290
+ yellow: "\x1B[33m"
291
+ };
292
+ function style(text, color) {
293
+ if (!isTTY()) return text;
294
+ return `${color}${text}${ANSI.reset}`;
295
+ }
296
+ //#endregion
297
+ //#region src/lib/logger.ts
298
+ /**
299
+ * Original console methods, saved before any patching.
300
+ * Used by ui.ts to always write to the real terminal regardless of capture.
301
+ */
302
+ const originalConsole = {
303
+ log: console.log.bind(console),
304
+ info: console.info.bind(console),
305
+ warn: console.warn.bind(console),
306
+ error: console.error.bind(console),
307
+ debug: console.debug.bind(console)
308
+ };
309
+ let stream = null;
310
+ let debugMode = false;
311
+ /** When true, do not open or write the log file (avoids races after {@link logger.close}). */
312
+ let loggingClosed = false;
313
+ const KEYSTROKE_CREDENTIALS_DIR = ".keystroke";
314
+ const KEYSTROKE_LOGS_DIR = "logs";
315
+ const KEYSTROKE_LOG_FILE = "cli.jsonl";
316
+ const MAX_LOG_LINES = 25e4;
317
+ const MAX_PRUNE_READ_BYTES = 32 * 1024 * 1024;
318
+ const DEBUG_PREFIXES = {
319
+ debug: "[DEBUG]",
320
+ info: "[INFO]",
321
+ warn: "[WARN]",
322
+ error: "[ERROR]"
323
+ };
324
+ const LEVEL_MAP = {
325
+ log: "info",
326
+ info: "info",
327
+ warn: "warn",
328
+ error: "error",
329
+ debug: "debug"
330
+ };
331
+ function ensureStream() {
332
+ if (loggingClosed) return null;
333
+ if (!stream) {
334
+ const logFilePath = getLogFilePath();
335
+ fs.mkdirSync(path$1.dirname(logFilePath), { recursive: true });
336
+ stream = fs.createWriteStream(logFilePath, { flags: "a" });
337
+ stream.on("error", () => {});
338
+ }
339
+ return stream;
340
+ }
341
+ function getLogFilePath(homeDir = os.homedir()) {
342
+ return path$1.join(homeDir, KEYSTROKE_CREDENTIALS_DIR, KEYSTROKE_LOGS_DIR, KEYSTROKE_LOG_FILE);
343
+ }
344
+ async function readTailForPrune(filePath) {
345
+ const handle = await fsPromises.open(filePath, "r");
346
+ try {
347
+ const stats = await handle.stat();
348
+ const bytesToRead = Math.min(stats.size, MAX_PRUNE_READ_BYTES);
349
+ if (bytesToRead <= 0) return "";
350
+ const start = Math.max(0, stats.size - bytesToRead);
351
+ const buffer = Buffer.alloc(bytesToRead);
352
+ const { bytesRead } = await handle.read(buffer, 0, bytesToRead, start);
353
+ let content = buffer.subarray(0, bytesRead).toString("utf8");
354
+ if (start > 0) {
355
+ const firstNewline = content.indexOf("\n");
356
+ if (firstNewline === -1) return "";
357
+ content = content.slice(firstNewline + 1);
358
+ }
359
+ return content;
360
+ } finally {
361
+ await handle.close();
362
+ }
363
+ }
364
+ async function pruneLogFile() {
365
+ const filePath = getLogFilePath();
366
+ let content;
367
+ try {
368
+ content = await readTailForPrune(filePath);
369
+ } catch {
370
+ return;
371
+ }
372
+ const lines = content.split("\n");
373
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
374
+ if (lines.length <= MAX_LOG_LINES) return;
375
+ const kept = lines.slice(lines.length - MAX_LOG_LINES);
376
+ await fsPromises.writeFile(filePath, `${kept.join("\n")}\n`);
377
+ }
378
+ function formatTimestamp() {
379
+ const now = /* @__PURE__ */ new Date();
380
+ return `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}.${String(now.getMilliseconds()).padStart(3, "0")}`;
381
+ }
382
+ function write(level, message, meta) {
383
+ if (loggingClosed) return;
384
+ const entry = {
385
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
386
+ level,
387
+ message,
388
+ ...meta !== void 0 ? { meta } : {}
389
+ };
390
+ const s = ensureStream();
391
+ if (s && !s.destroyed && s.writable) try {
392
+ s.write(`${JSON.stringify(entry)}\n`);
393
+ } catch {}
394
+ if (debugMode) {
395
+ const ts = formatTimestamp();
396
+ const prefix = DEBUG_PREFIXES[level];
397
+ const formatted = meta ? `${ts} ${prefix} ${message} ${JSON.stringify(meta)}` : `${ts} ${prefix} ${message}`;
398
+ originalConsole.info(style(formatted, ANSI.dim));
399
+ }
400
+ }
401
+ /**
402
+ * Patches global console methods to capture output from third-party
403
+ * and workspace packages. Captured output is always written to the
404
+ * log file and only forwarded to the real console when --debug is active.
405
+ */
406
+ function installConsoleCapture() {
407
+ for (const method of [
408
+ "log",
409
+ "info",
410
+ "warn",
411
+ "error",
412
+ "debug"
413
+ ]) {
414
+ const level = LEVEL_MAP[method] ?? "info";
415
+ const original = originalConsole[method];
416
+ console[method] = (...args) => {
417
+ write(level, `[captured] ${args.map(String).join(" ")}`);
418
+ if (debugMode) original(...args);
419
+ };
420
+ }
421
+ }
422
+ /**
423
+ * Initialize the logger: install console capture and prune the log file.
424
+ * Call once at CLI startup.
425
+ */
426
+ function initLogger() {
427
+ installConsoleCapture();
428
+ pruneLogFile().catch((error) => {
429
+ if (process.env.KEYSTROKE_DEBUG_LOGGER === "1") {
430
+ const message = error instanceof Error ? error.message : String(error);
431
+ originalConsole.warn(`[logger] Failed to prune CLI log file: ${message}`);
432
+ }
433
+ });
434
+ }
435
+ const logger = {
436
+ debug: (msg, meta) => write("debug", msg, meta),
437
+ info: (msg, meta) => write("info", msg, meta),
438
+ warn: (msg, meta) => write("warn", msg, meta),
439
+ error: (msg, meta) => write("error", msg, meta),
440
+ /** Enable or disable debug mode (shows all log entries in the console). */
441
+ setDebug(enabled) {
442
+ debugMode = enabled;
443
+ },
444
+ /** Stop accepting log writes and tear down the log file stream. */
445
+ close() {
446
+ loggingClosed = true;
447
+ if (stream) {
448
+ stream.end();
449
+ stream = null;
450
+ }
451
+ }
452
+ };
453
+ //#endregion
454
+ //#region src/lib/cli-telemetry.ts
455
+ const TELEMETRY_TIMEOUT_MS = 2500;
456
+ let state = null;
457
+ let lastFailureError;
458
+ function isTruthyEnv(v) {
459
+ return v === "1" || v?.toLowerCase() === "true";
460
+ }
461
+ /**
462
+ * Opt-in CLI telemetry. In CI, only `KEYSTROKE_TELEMETRY=1` enables (not `true`).
463
+ */
464
+ function isCliTelemetryEnabled() {
465
+ const procEnv = process.env;
466
+ const raw = procEnv.KEYSTROKE_TELEMETRY;
467
+ if (!isTruthyEnv(raw)) return false;
468
+ if ((procEnv.CI === "true" || procEnv.CI === "1") && raw !== "1") return false;
469
+ return true;
470
+ }
471
+ /** Exported for unit tests — stable dotted command id (e.g. `keystroke.workflows.deploy`). */
472
+ function cliTelemetryCommandPath(cmd) {
473
+ const parts = [];
474
+ let current = cmd;
475
+ while (current) {
476
+ const name = current.name();
477
+ if (name) parts.unshift(name);
478
+ current = current.parent ?? null;
479
+ }
480
+ const path = parts.join(".");
481
+ return path.length > 0 ? path : "keystroke";
482
+ }
483
+ function registerCliTelemetryHooks(program) {
484
+ program.hook("preAction", (_thisCommand, actionCommand) => {
485
+ beginCliTelemetryRun(cliTelemetryCommandPath(actionCommand));
486
+ });
487
+ }
488
+ function captureCliTelemetryFailure(error) {
489
+ lastFailureError = error;
490
+ }
491
+ function beginCliTelemetryRun(command) {
492
+ state = {
493
+ startMs: Date.now(),
494
+ command
495
+ };
496
+ lastFailureError = void 0;
497
+ }
498
+ function captureCliTelemetryResolvedContext(ctx) {
499
+ if (!state) return;
500
+ state.apiKey = ctx.apiKey;
501
+ state.baseUrl = ctx.baseUrl;
502
+ state.organizationId = ctx.organizationId;
503
+ }
504
+ function nodeRuntimeLabel() {
505
+ if (process.versions.bun) return `bun/${process.versions.bun}`;
506
+ return `node/${process.version}`;
507
+ }
508
+ /** Maps thrown values to coarse telemetry error families (no raw messages in payload). */
509
+ function cliTelemetryErrorFamilyFromError(error) {
510
+ if (error instanceof AuthenticationError) return "auth";
511
+ const candidate = error instanceof CliExitError && error.cause !== void 0 ? error.cause : error;
512
+ if (candidate instanceof AuthenticationError) return "auth";
513
+ if (isNetworkError(candidate)) return "network";
514
+ if (isAuthError(candidate)) return "auth";
515
+ const msg = toErrorMessage(candidate).toLowerCase();
516
+ if (candidate instanceof CliExitError && (msg.includes("cancel") || msg.includes("abort"))) return "user_abort";
517
+ if (msg.includes("not found") || msg.includes("404")) return "not_found";
518
+ if (msg.includes("invalid") || msg.includes("validation") || msg.includes("zod")) return "validation";
519
+ if (candidate !== void 0) return "unknown";
520
+ }
521
+ async function sendTelemetryEvent(body, ctx) {
522
+ const url = `${ctx.baseUrl.replace(/\/$/, "")}/api/v1/cli/telemetry`;
523
+ const headers = {
524
+ "Content-Type": "application/json",
525
+ Authorization: `Bearer ${ctx.apiKey}`
526
+ };
527
+ if (ctx.organizationId && /^[0-9a-f-]{36}$/i.test(ctx.organizationId)) headers["X-Organization-Id"] = ctx.organizationId;
528
+ const controller = new AbortController();
529
+ const timeout = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
530
+ try {
531
+ const res = await fetch(url, {
532
+ method: "POST",
533
+ headers,
534
+ body: JSON.stringify(body),
535
+ signal: controller.signal
536
+ });
537
+ if (!res.ok) logger.debug("CLI telemetry request failed", { status: res.status });
538
+ } catch (error) {
539
+ logger.debug("CLI telemetry send error", { error: toErrorMessage(error) });
540
+ } finally {
541
+ clearTimeout(timeout);
542
+ }
543
+ }
544
+ async function flushCliTelemetryForExit(exitCode) {
545
+ if (!isCliTelemetryEnabled() || !state) return;
546
+ const { apiKey, baseUrl, organizationId } = state;
547
+ if (!apiKey || !baseUrl) return;
548
+ const durationMs = Math.max(0, Date.now() - state.startMs);
549
+ const payload = {
550
+ event: "cli.command.completed",
551
+ cliVersion: version,
552
+ command: state.command,
553
+ exitCode,
554
+ durationMs,
555
+ os: process.platform,
556
+ arch: process.arch,
557
+ nodeRuntime: nodeRuntimeLabel()
558
+ };
559
+ if (exitCode !== 0) {
560
+ const family = cliTelemetryErrorFamilyFromError(lastFailureError);
561
+ if (family) payload.errorFamily = family;
562
+ }
563
+ await sendTelemetryEvent(payload, {
564
+ baseUrl,
565
+ apiKey,
566
+ organizationId
567
+ });
568
+ }
569
+ z.object({
570
+ apiKey: z.string().optional(),
571
+ baseUrl: z.string().optional(),
572
+ debug: z.boolean().optional(),
573
+ org: z.string().optional()
574
+ });
575
+ const GLOBAL_OPTIONS_CONFIG = {
576
+ apiKey: {
577
+ flag: "--api-key <key>",
578
+ description: "Keystroke API Key (overrides KEYSTROKE_API_KEY env)"
579
+ },
580
+ baseUrl: {
581
+ flag: "--base-url <url>",
582
+ description: "Keystroke API URL (overrides SERVER_URL env)"
583
+ },
584
+ debug: {
585
+ flag: "--debug",
586
+ description: "Show all diagnostic logs in the console"
587
+ },
588
+ org: {
589
+ flag: "--org <id>",
590
+ description: "Organization ID (overrides KEYSTROKE_ORG_ID and stored credentials)"
591
+ }
592
+ };
593
+ /**
594
+ * Creates fresh hidden Option instances for a subcommand.
595
+ *
596
+ * With `enablePositionalOptions` on the root program, Commander only parses
597
+ * options declared on the command they appear after. This factory lets every
598
+ * subcommand accept global flags at any position without showing them in
599
+ * per-command --help output.
600
+ *
601
+ * Returns new instances each call to avoid shared mutable state.
602
+ */
603
+ function createHiddenGlobalOptions() {
604
+ return Object.values(GLOBAL_OPTIONS_CONFIG).map((cfg) => new Option(cfg.flag).hideHelp());
605
+ }
606
+ //#endregion
607
+ //#region src/lib/ui.ts
608
+ const ui = {
609
+ /** Blank line separator */
610
+ br: () => {
611
+ originalConsole.info("");
612
+ logger.info("");
613
+ },
614
+ /** Primary informational text — raw, no prefix (used for tables, structured output) */
615
+ text: (msg) => {
616
+ originalConsole.info(msg);
617
+ logger.info(msg);
618
+ },
619
+ /** Section header */
620
+ header: (msg) => {
621
+ log.info(msg);
622
+ logger.info(msg);
623
+ },
624
+ /** Success message */
625
+ success: (msg) => {
626
+ log.success(msg);
627
+ logger.info(msg);
628
+ },
629
+ /** Warning message */
630
+ warn: (msg) => {
631
+ log.warn(msg);
632
+ logger.warn(msg);
633
+ },
634
+ /** Error message */
635
+ error: (msg) => {
636
+ log.error(msg);
637
+ logger.error(msg);
638
+ },
639
+ /** Secondary/hint text */
640
+ hint: (msg) => {
641
+ log.message(msg);
642
+ logger.info(msg);
643
+ }
644
+ };
645
+ //#endregion
646
+ //#region src/index.ts
647
+ /**
648
+ * Keystroke CLI Entry Point
649
+ */
650
+ function getRunMode() {
651
+ const url = import.meta.url;
652
+ return url.includes("/dist/") || url.endsWith(".mjs") ? "built" : "source";
653
+ }
654
+ async function flushWritableStream(stream) {
655
+ if (stream.destroyed || !stream.writable) return;
656
+ await new Promise((resolve) => {
657
+ stream.write("", () => resolve());
658
+ });
659
+ }
660
+ async function allowBufferedOutputToFlush() {
661
+ await Promise.all([flushWritableStream(process.stdout), flushWritableStream(process.stderr)]);
662
+ await new Promise((resolve) => setTimeout(resolve, 0));
663
+ }
664
+ function shouldLogCliStartup(argv) {
665
+ return argv[0] !== "logs";
666
+ }
667
+ function resolveCliExitCode(error) {
668
+ if (error instanceof CommanderError) return error.exitCode;
669
+ if (error instanceof CliExitError) return error.exitCode;
670
+ return 1;
671
+ }
672
+ async function runCli() {
673
+ initLogger();
674
+ if (process.argv.includes("--debug")) logger.setDebug(true);
675
+ if (shouldLogCliStartup(process.argv.slice(2))) logger.info("CLI started", {
676
+ argv: process.argv.slice(2),
677
+ runMode: getRunMode()
678
+ });
679
+ const program = new Command().name("keystroke").description("Keystroke CLI - Headless workflow management").version(version).enablePositionalOptions();
680
+ program.exitOverride();
681
+ for (const cfg of Object.values(GLOBAL_OPTIONS_CONFIG)) program.option(cfg.flag, cfg.description);
682
+ await registerCommandsForArgv(program, process.argv.slice(2));
683
+ registerCliTelemetryHooks(program);
684
+ await program.parseAsync(process.argv);
685
+ }
686
+ async function main() {
687
+ let exitCode = 0;
688
+ let failure;
689
+ try {
690
+ await runCli();
691
+ } catch (error) {
692
+ exitCode = resolveCliExitCode(error);
693
+ failure = error;
694
+ if (!(error instanceof CliExitError) && !(error instanceof CommanderError)) ui.error(`Error: ${toErrorMessage(error)}`);
695
+ }
696
+ if (failure) captureCliTelemetryFailure(failure);
697
+ await flushCliTelemetryForExit(exitCode);
698
+ await allowBufferedOutputToFlush();
699
+ await new Promise((resolve) => {
700
+ setImmediate(resolve);
701
+ });
702
+ logger.close();
703
+ process.exit(exitCode);
704
+ }
705
+ main();
706
+ //#endregion
707
+ export { CliExitError as C, throwReportedCliExit as D, WorkflowResolutionError as E, AuthenticationError as S, ProjectNotFoundError as T, AUTH_URL_PATH as _, originalConsole as a, CALLBACK_PATH as b, isTTY as c, getApiErrorCode as d, getHttpStatus as f, AUTH_TIMEOUT_SECONDS as g, toErrorMessage as h, logger as i, AUTH_HINT as l, isNetworkError as m, createHiddenGlobalOptions as n, ANSI as o, isAuthError as p, captureCliTelemetryResolvedContext as r, style as s, ui as t, REAUTH_HINT as u, CALLBACK_LOOPBACK_HOST as v, InputValidationError as w, CLI_AUTH_COMMAND as x, CALLBACK_LOOPBACK_ORIGIN as y };