@keystrokehq/cli 0.1.102 → 0.1.103

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/dist/index.mjs CHANGED
@@ -5014,6 +5014,21 @@ function readLinkedProjectConfig(fromDir = process.cwd()) {
5014
5014
  return parseLinkedProjectConfig(readFileSync(join(root, KEYSTROKE_CONFIG$1), "utf8"));
5015
5015
  }
5016
5016
  //#endregion
5017
+ //#region src/errors/expected-cli-error.ts
5018
+ /**
5019
+ * User/project failures that are expected CLI exits (lint, typecheck, validation, etc.).
5020
+ * These should not become Error Tracking issues.
5021
+ */
5022
+ var ExpectedCliError = class extends Error {
5023
+ constructor(message) {
5024
+ super(message);
5025
+ this.name = "ExpectedCliError";
5026
+ }
5027
+ };
5028
+ function isExpectedCliError(error) {
5029
+ return error instanceof ExpectedCliError;
5030
+ }
5031
+ //#endregion
5017
5032
  //#region src/target-hints.ts
5018
5033
  function missingOrganizationRefMessage() {
5019
5034
  return "No organization selected. Pass `--organization <slug>` or set `organization` in keystroke.config.ts (via `keystroke projects link --project <slug>`).";
@@ -5022,10 +5037,10 @@ function missingProjectRefMessage() {
5022
5037
  return "No project selected. Pass `--project <slug>` or set `project` in keystroke.config.ts (via `keystroke projects link --project <slug>`).";
5023
5038
  }
5024
5039
  function missingOrganizationRefError() {
5025
- return new Error(missingOrganizationRefMessage());
5040
+ return new ExpectedCliError(missingOrganizationRefMessage());
5026
5041
  }
5027
5042
  function missingProjectRefError() {
5028
- return new Error(missingProjectRefMessage());
5043
+ return new ExpectedCliError(missingProjectRefMessage());
5029
5044
  }
5030
5045
  //#endregion
5031
5046
  //#region src/resolve-organization-target.ts
@@ -5052,7 +5067,7 @@ async function resolveActiveOrganization(config, fromDir = process.cwd()) {
5052
5067
  //#region src/project/resolve-project-root.ts
5053
5068
  function resolveProjectRoot(fromDir = process.cwd()) {
5054
5069
  const root = resolveKeystrokeConfigRoot(fromDir);
5055
- if (!root) throw new Error("Could not find keystroke project root (expected keystroke.config.ts)");
5070
+ if (!root) throw new ExpectedCliError("Could not find keystroke project root (expected keystroke.config.ts)");
5056
5071
  return root;
5057
5072
  }
5058
5073
  //#endregion
@@ -5198,14 +5213,14 @@ function getTelemetryContext() {
5198
5213
  */
5199
5214
  function isExpectedError(error) {
5200
5215
  if (!error || typeof error !== "object") return false;
5201
- const status = readStatus(error);
5202
- if (status !== void 0 && status >= 400 && status < 500) return true;
5216
+ const status = readStatus$1(error);
5217
+ if (status !== void 0 && (status === 0 || status >= 400 && status < 500)) return true;
5203
5218
  if ("name" in error && typeof error.name === "string") {
5204
5219
  if (error.name === "AbortError" || error.name === "CanceledError" || error.name === "CancelledError" || error.name === "RunTimeoutError") return true;
5205
5220
  }
5206
5221
  return false;
5207
5222
  }
5208
- function readStatus(error) {
5223
+ function readStatus$1(error) {
5209
5224
  if ("status" in error && typeof error.status === "number") return error.status;
5210
5225
  if ("statusCode" in error && typeof error.statusCode === "number") return error.statusCode;
5211
5226
  }
@@ -5339,6 +5354,39 @@ function sanitizeProperties(properties) {
5339
5354
  return Object.fromEntries(Object.entries(properties).filter((entry) => entry[1] !== void 0));
5340
5355
  }
5341
5356
  //#endregion
5357
+ //#region src/errors/should-report-cli-exception.ts
5358
+ function readStatus(error) {
5359
+ if (!error || typeof error !== "object") return;
5360
+ if ("status" in error && typeof error.status === "number") return error.status;
5361
+ if ("statusCode" in error && typeof error.statusCode === "number") return error.statusCode;
5362
+ }
5363
+ function isAuthCommand(context) {
5364
+ const operation = context.operation ?? "";
5365
+ const command = context.command ?? "";
5366
+ return operation.startsWith("cli.auth") || command.startsWith("auth");
5367
+ }
5368
+ function isDeployCommand(context) {
5369
+ const operation = context.operation ?? "";
5370
+ const command = context.command ?? "";
5371
+ return operation === "cli.deploy" || command === "deploy";
5372
+ }
5373
+ /**
5374
+ * CLI Error Tracking allowlist — report only:
5375
+ * - HTTP 5xx responses
5376
+ * - unexpected failures on auth / deploy paths
5377
+ *
5378
+ * Validation, lint/typecheck, bad flags, and local unreachable servers stay quiet.
5379
+ */
5380
+ function shouldReportCliException(error, context) {
5381
+ if (isExpectedCliError(error)) return false;
5382
+ const status = readStatus(error);
5383
+ if (status !== void 0) {
5384
+ if (status === 0 || status >= 400 && status < 500) return false;
5385
+ if (status >= 500) return true;
5386
+ }
5387
+ return isAuthCommand(context) || isDeployCommand(context);
5388
+ }
5389
+ //#endregion
5342
5390
  //#region src/telemetry/identity.ts
5343
5391
  function getCliTelemetryActorId() {
5344
5392
  return getOrCreateAnonymousDistinctId();
@@ -5409,7 +5457,7 @@ async function shutdownCliTelemetry() {
5409
5457
  enabled = false;
5410
5458
  }
5411
5459
  async function reportCliException(error, context) {
5412
- if (!enabled) return;
5460
+ if (!enabled || isExpectedCliError(error) || !shouldReportCliException(error, context)) return;
5413
5461
  await captureException({
5414
5462
  error,
5415
5463
  analyticsId: getCliTelemetryActorId(),
@@ -5604,7 +5652,7 @@ function parseJsonOption(value, flag) {
5604
5652
  try {
5605
5653
  return JSON.parse(value);
5606
5654
  } catch {
5607
- throw new Error(`Invalid ${flag} JSON`);
5655
+ throw new ExpectedCliError(`Invalid ${flag} JSON`);
5608
5656
  }
5609
5657
  }
5610
5658
  //#endregion
@@ -7116,8 +7164,7 @@ async function ensureSdkCurrent(root) {
7116
7164
  if (packages.length === 0) return;
7117
7165
  const outdated = await packagesNeedingUpdate(root, packages);
7118
7166
  if (outdated.length === 0) return;
7119
- const lines = outdated.map(formatOutdatedLine).join("\n");
7120
- throw new Error(`Outdated @keystrokehq packages:\n${lines}\n\nPlease run \`keystroke update\` before continuing.`);
7167
+ throw new ExpectedCliError(`Outdated @keystrokehq packages:\n${outdated.map(formatOutdatedLine).join("\n")}\n\nPlease run \`keystroke update\` before continuing.`);
7121
7168
  }
7122
7169
  //#endregion
7123
7170
  //#region src/commands/build.ts
@@ -7288,8 +7335,8 @@ async function buildDeployArchive(client, root, projectId, filter) {
7288
7335
  async function runDeploy(options) {
7289
7336
  const root = resolveProjectRoot(options.dir);
7290
7337
  await ensureSdkCurrent(root);
7291
- if (await runLint(root) !== 0) throw new Error("lint failed");
7292
- if (await runTypecheck(root) !== 0) throw new Error("typecheck failed");
7338
+ if (await runLint(root) !== 0) throw new ExpectedCliError("lint failed");
7339
+ if (await runTypecheck(root) !== 0) throw new ExpectedCliError("typecheck failed");
7293
7340
  const config = createCliConfig();
7294
7341
  await resolveActiveOrganization(config, root);
7295
7342
  const client = createCliPlatformClient(config);
@@ -7711,8 +7758,8 @@ function projectNameValidationError(name) {
7711
7758
  }
7712
7759
  function assertProjectName(name) {
7713
7760
  const trimmed = name.trim();
7714
- if (!trimmed) throw new Error("Project name is required");
7715
- if (!KEBAB_CASE.test(trimmed)) throw new Error(projectNameValidationError(trimmed));
7761
+ if (!trimmed) throw new ExpectedCliError("Project name is required");
7762
+ if (!KEBAB_CASE.test(trimmed)) throw new ExpectedCliError(projectNameValidationError(trimmed));
7716
7763
  return trimmed;
7717
7764
  }
7718
7765
  function projectNameFromDirectory(directory) {
@@ -7861,21 +7908,33 @@ function resolveSkillsBundleDir(fromModuleUrl = import.meta.url) {
7861
7908
  }
7862
7909
  //#endregion
7863
7910
  //#region src/skills/scaffold-agents-guide.ts
7864
- async function ensureSymlink(target, linkPath) {
7911
+ function isSymlinkUnsupported(error) {
7912
+ if (!error || typeof error !== "object" || !("code" in error)) return false;
7913
+ const code = error.code;
7914
+ return code === "EPERM" || code === "EACCES" || code === "ENOTSUP";
7915
+ }
7916
+ /** Prefer a symlink; on Windows without symlink privileges, write a regular file copy. */
7917
+ async function ensureClaudeMdLink(guidePath, linkPath, createLink = symlink) {
7865
7918
  await mkdir(dirname(linkPath), { recursive: true });
7866
7919
  await rm(linkPath, {
7867
7920
  recursive: true,
7868
7921
  force: true
7869
7922
  });
7870
- await symlink(target, linkPath, "file");
7923
+ const target = relative(dirname(linkPath), guidePath);
7924
+ try {
7925
+ await createLink(target, linkPath, "file");
7926
+ } catch (error) {
7927
+ if (!isSymlinkUnsupported(error)) throw error;
7928
+ await writeFile(linkPath, await readFile(guidePath, "utf8"));
7929
+ }
7871
7930
  }
7872
- /** Writes AGENTS.md from the CLI bundle and recreates the CLAUDE.md symlink. */
7931
+ /** Writes AGENTS.md from the CLI bundle and recreates the CLAUDE.md symlink (or copy). */
7873
7932
  async function scaffoldAgentsGuide(projectRoot, fromModuleUrl = import.meta.url) {
7874
7933
  const bundleDir = resolveSkillsBundleDir(fromModuleUrl);
7875
7934
  const guidePath = agentsMdPath(projectRoot);
7876
7935
  if ((await lstat(guidePath).catch(() => null))?.isSymbolicLink()) await rm(guidePath, { force: true });
7877
7936
  await writeFile(guidePath, stampAgentsGuideVersion(await readFile(join(bundleDir, "_AGENTS.md"), "utf8")));
7878
- await ensureSymlink(relative(dirname(join(projectRoot, "CLAUDE.md")), guidePath), join(projectRoot, "CLAUDE.md"));
7937
+ await ensureClaudeMdLink(guidePath, join(projectRoot, "CLAUDE.md"));
7879
7938
  }
7880
7939
  //#endregion
7881
7940
  //#region src/project/write-project-config.ts
@@ -7994,8 +8053,8 @@ async function runInit(options) {
7994
8053
  const playgroundRoot = resolvePlaygroundRoot(targetDir);
7995
8054
  if (!await directoryIsEmpty(targetDir)) {
7996
8055
  if (interactive) {
7997
- if (!await confirmTargetDir(targetDir)) throw new Error("Init cancelled");
7998
- } else if (!options.yes) throw new Error(`Directory "${targetDir}" is not empty. Pass --yes to merge or choose an empty directory.`);
8056
+ if (!await confirmTargetDir(targetDir)) throw new ExpectedCliError("Init cancelled");
8057
+ } else if (!options.yes) throw new ExpectedCliError(`Directory "${targetDir}" is not empty. Pass --yes to merge or choose an empty directory.`);
7999
8058
  }
8000
8059
  await copyTemplate(resolveTemplateDir(options.template ?? "hello-world"), targetDir, { projectName });
8001
8060
  await scaffoldEmptySrcDirs(targetDir);
@@ -9366,8 +9425,13 @@ async function syncAgentsGuide(command) {
9366
9425
  if (existsSync(legacySkillsDir) && lstatSync(legacySkillsDir).isSymbolicLink()) return;
9367
9426
  const currentVersion = readCliVersion();
9368
9427
  if (await readBundleVersion(projectRoot) === currentVersion) return;
9369
- await scaffoldAgentsGuide(projectRoot);
9370
- process.stderr.write(`Synced AGENTS.md to ${currentVersion}\n`);
9428
+ try {
9429
+ await scaffoldAgentsGuide(projectRoot);
9430
+ process.stderr.write(`Synced AGENTS.md to ${currentVersion}\n`);
9431
+ } catch (error) {
9432
+ const detail = error instanceof Error ? error.message : String(error);
9433
+ process.stderr.write(`Failed to sync AGENTS.md (${detail}); continuing.\n`);
9434
+ }
9371
9435
  }
9372
9436
  //#endregion
9373
9437
  //#region src/program.ts