@keystrokehq/cli 0.0.31 → 0.0.32

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.
@@ -9,6 +9,7 @@ const InitOptionsSchema = z.object({
9
9
  name: z.string().optional().describe("Project name (skips prompt; default: directory basename)"),
10
10
  description: z.string().optional().describe("Project description for the API (skips prompt when creating a new project)"),
11
11
  scaffold: z.boolean().default(true).describe("Scaffold project files (package.json, vitest.config.ts, etc.)"),
12
+ install: z.boolean().default(true).describe("Install scaffold dependencies after writing project files"),
12
13
  agent: z.array(z.string()).optional().describe("Agent target to install Keystroke skills for. Repeat for multiple agents; '*' selects all."),
13
14
  method: z.enum(SKILL_INSTALL_METHODS).optional().describe("Skill install method for agent-specific targets")
14
15
  });
@@ -29,6 +30,10 @@ const INIT_OPTIONS_CONFIG = {
29
30
  flag: "--no-scaffold",
30
31
  description: "Skip scaffolding project files (package.json, vitest.config.ts, etc.)"
31
32
  },
33
+ install: {
34
+ flag: "--no-install",
35
+ description: "Skip installing scaffold dependencies after writing project files"
36
+ },
32
37
  agent: {
33
38
  flag: "--agent <agent>",
34
39
  description: "Install Keystroke skills for an agent target. Repeat for multiple agents; '*' selects all.",
@@ -45,7 +50,7 @@ function createInitCommand() {
45
50
  description: "Initialize a Keystroke project (creates keystroke.config.ts)",
46
51
  schema: InitOptionsSchema,
47
52
  optionsConfig: INIT_OPTIONS_CONFIG,
48
- loadHandler: async () => (await import("./init.handler-CzlmkNXi.mjs")).handleInit
53
+ loadHandler: async () => (await import("./init.handler-B3T4J6u_.mjs")).handleInit
49
54
  });
50
55
  }
51
56
  //#endregion
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { D as CliExitError, a as ui, i as fetchLatestNpmPackageVersion } from "./keystroke.mjs";
3
+ import { D as CliExitError, a as ui, i as fetchLatestNpmPackageVersion, j as throwReportedCliExit } from "./keystroke.mjs";
4
4
  import { i as projects } from "./dist-Dw7gCE7y.mjs";
5
5
  import { a as writeProjectConfig, i as readProjectConfig, r as getProjectConfigPath } from "./project-config-DudGRFPO.mjs";
6
6
  import { i as requireClient } from "./context-sgKhRc5v.mjs";
7
- import { i as UnknownSkillAgentError, n as resolveSkillInstallChoices, r as installKeystrokeAgentSkills, t as summarizeSkillInstall } from "./skill-installer-D6j9IA3Z.mjs";
7
+ import { a as runProjectInstall, i as resolvePackageManagerResolution, t as buildProjectInstallCommand } from "./package-manager-CvY4IW7X.mjs";
8
+ import { i as UnknownSkillAgentError, n as resolveSkillInstallChoices, r as installKeystrokeAgentSkills, t as summarizeSkillInstall } from "./skill-installer-DG8kTaQR.mjs";
8
9
  import { createRequire } from "node:module";
9
10
  import path from "node:path";
10
11
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
@@ -14,6 +15,7 @@ import { cancel, isCancel, text } from "@clack/prompts";
14
15
  const WORKFLOW_CORE_PACKAGE_NAME = "@keystrokehq/core";
15
16
  const CONFIG_PACKAGE_NAME = "@keystrokehq/config";
16
17
  const RUNTIME_PACKAGE_NAME = "@keystrokehq/runtime";
18
+ const TESTING_PACKAGE_NAME = "@keystrokehq/testing";
17
19
  const FALLBACK_SCAFFOLD_VERSION_RANGE = "latest";
18
20
  const DEFAULT_TIMEOUT_MS = 750;
19
21
  /**
@@ -41,6 +43,13 @@ async function runtimeScaffoldVersionRange(options = {}) {
41
43
  readLocalVersion: options.readLocalRuntimeVersion ?? readLocalRuntimeVersion
42
44
  });
43
45
  }
46
+ async function testingScaffoldVersionRange(options = {}) {
47
+ return packageScaffoldVersionRange({
48
+ packageName: TESTING_PACKAGE_NAME,
49
+ timeoutMs: options.timeoutMs,
50
+ readLocalVersion: options.readLocalTestingVersion ?? readLocalTestingVersion
51
+ });
52
+ }
44
53
  async function packageScaffoldVersionRange(options) {
45
54
  const latestVersion = await fetchLatestNpmPackageVersion({
46
55
  packageName: options.packageName,
@@ -75,6 +84,14 @@ function readLocalRuntimeVersion() {
75
84
  } catch {}
76
85
  return null;
77
86
  }
87
+ function readLocalTestingVersion() {
88
+ try {
89
+ const raw = readFileSync(createRequire(import.meta.url).resolve(`${TESTING_PACKAGE_NAME}/package.json`), "utf-8");
90
+ const version = JSON.parse(raw).version?.trim();
91
+ if (version) return version;
92
+ } catch {}
93
+ return null;
94
+ }
78
95
  //#endregion
79
96
  //#region src/commands/init/templates/biome-config.ts
80
97
  /**
@@ -206,6 +223,8 @@ describe('hello workflow', () => {
206
223
  }
207
224
  //#endregion
208
225
  //#region src/commands/init/templates/package-json.ts
226
+ /** Matches the Keystroke monorepo; Corepack uses this for `pnpm install`. */
227
+ const SCAFFOLD_PNPM_PACKAGE_MANAGER = "pnpm@11.1.1";
209
228
  /**
210
229
  * Template for generating a package.json for a new Keystroke project.
211
230
  */
@@ -214,6 +233,7 @@ function createPackageJsonContent(projectName, options) {
214
233
  name: projectName,
215
234
  private: true,
216
235
  type: "module",
236
+ ...options.packageManagerField ? { packageManager: options.packageManagerField } : {},
217
237
  scripts: {
218
238
  test: "vitest run",
219
239
  typecheck: "tsc --noEmit",
@@ -227,7 +247,7 @@ function createPackageJsonContent(projectName, options) {
227
247
  devDependencies: {
228
248
  "@biomejs/biome": "2.4.13",
229
249
  "@keystrokehq/runtime": options.runtimeVersionRange,
230
- "@keystrokehq/testing": options.workflowCoreVersionRange,
250
+ "@keystrokehq/testing": options.testingVersionRange,
231
251
  vitest: "^4.0.18",
232
252
  typescript: "^5.9.3"
233
253
  }
@@ -329,7 +349,7 @@ async function promptProjectDescription(options) {
329
349
  const t = typeof answer === "string" ? answer.trim() : "";
330
350
  return t.length > 0 ? t : void 0;
331
351
  }
332
- async function ensureScaffoldPackageJson(targetDir, projectName, workflowCoreRange, configRange, runtimeRange) {
352
+ async function ensureScaffoldPackageJson(targetDir, projectName, workflowCoreRange, configRange, runtimeRange, testingRange, packageManagerField) {
333
353
  const pkgPath = path.join(targetDir, "package.json");
334
354
  try {
335
355
  const raw = await readFile(pkgPath, "utf-8");
@@ -340,7 +360,7 @@ async function ensureScaffoldPackageJson(targetDir, projectName, workflowCoreRan
340
360
  pkg.dependencies.zod = pkg.dependencies.zod ?? "^4.3.6";
341
361
  pkg.devDependencies = pkg.devDependencies ?? {};
342
362
  pkg.devDependencies["@keystrokehq/runtime"] = runtimeRange;
343
- pkg.devDependencies["@keystrokehq/testing"] = workflowCoreRange;
363
+ pkg.devDependencies["@keystrokehq/testing"] = testingRange;
344
364
  pkg.devDependencies.vitest = pkg.devDependencies.vitest ?? "^4.0.18";
345
365
  pkg.devDependencies.typescript = pkg.devDependencies.typescript ?? "^5.9.3";
346
366
  if (!pkg.name) pkg.name = projectName;
@@ -349,7 +369,9 @@ async function ensureScaffoldPackageJson(targetDir, projectName, workflowCoreRan
349
369
  } catch {
350
370
  await writeFile(pkgPath, `${createPackageJsonContent(projectName, {
351
371
  configVersionRange: configRange,
372
+ packageManagerField,
352
373
  runtimeVersionRange: runtimeRange,
374
+ testingVersionRange: testingRange,
353
375
  workflowCoreVersionRange: workflowCoreRange
354
376
  })}\n`, "utf-8");
355
377
  return "created";
@@ -427,12 +449,13 @@ async function handleInit(options, ctx) {
427
449
  await projects.track(targetDir, { name: projectName });
428
450
  await installAgentSkillsForInit(targetDir, options);
429
451
  if (options.scaffold) {
430
- const [workflowCoreVersionRange, configVersionRange, runtimeVersionRange] = await Promise.all([
452
+ const [workflowCoreVersionRange, configVersionRange, runtimeVersionRange, testingVersionRange] = await Promise.all([
431
453
  workflowCoreScaffoldVersionRange(),
432
454
  configScaffoldVersionRange(),
433
- runtimeScaffoldVersionRange()
455
+ runtimeScaffoldVersionRange(),
456
+ testingScaffoldVersionRange()
434
457
  ]);
435
- await scaffoldProject(targetDir, projectName, workflowCoreVersionRange, configVersionRange, runtimeVersionRange);
458
+ await scaffoldProject(targetDir, projectName, workflowCoreVersionRange, configVersionRange, runtimeVersionRange, testingVersionRange, options.install !== false);
436
459
  }
437
460
  }
438
461
  /** Write a file only if it does not already exist. Returns true if written. */
@@ -445,9 +468,11 @@ async function writeIfMissing(filePath, content) {
445
468
  return true;
446
469
  }
447
470
  }
448
- async function scaffoldProject(targetDir, projectName, workflowCoreVersionRange, configVersionRange, runtimeVersionRange) {
449
- const pkgAction = await ensureScaffoldPackageJson(targetDir, projectName, workflowCoreVersionRange, configVersionRange, runtimeVersionRange);
450
- ui.success(pkgAction === "created" ? `Created package.json (@keystrokehq/core ${workflowCoreVersionRange}, @keystrokehq/config ${configVersionRange}, @keystrokehq/runtime ${runtimeVersionRange})` : `Updated package.json (Keystroke deps -> core ${workflowCoreVersionRange}, config ${configVersionRange}, runtime ${runtimeVersionRange})`);
471
+ async function scaffoldProject(targetDir, projectName, workflowCoreVersionRange, configVersionRange, runtimeVersionRange, testingVersionRange, shouldInstall) {
472
+ const packageManagerResolution = await resolvePackageManagerResolution({ cwd: targetDir });
473
+ const { packageManager } = packageManagerResolution;
474
+ const pkgAction = await ensureScaffoldPackageJson(targetDir, projectName, workflowCoreVersionRange, configVersionRange, runtimeVersionRange, testingVersionRange, packageManager === "pnpm" && packageManagerResolution.source === "preferred" ? SCAFFOLD_PNPM_PACKAGE_MANAGER : void 0);
475
+ ui.success(pkgAction === "created" ? `Created package.json (@keystrokehq/core ${workflowCoreVersionRange}, @keystrokehq/config ${configVersionRange}, @keystrokehq/runtime ${runtimeVersionRange}, @keystrokehq/testing ${testingVersionRange})` : `Updated package.json (Keystroke deps -> core ${workflowCoreVersionRange}, config ${configVersionRange}, runtime ${runtimeVersionRange}, testing ${testingVersionRange})`);
451
476
  const files = [
452
477
  {
453
478
  rel: "vitest.config.ts",
@@ -498,7 +523,25 @@ async function scaffoldProject(targetDir, projectName, workflowCoreVersionRange,
498
523
  }
499
524
  if (created.length > 0) ui.success(`Scaffolded ${created.length} file(s): ${created.join(", ")}`);
500
525
  else ui.hint("Scaffold files already exist — skipped (package.json was still updated).");
501
- ui.hint("Run `pnpm install` to install dependencies, then `pnpm test` to run tests (or use your lockfile's package manager).");
526
+ if (shouldInstall) await installScaffoldDependencies(targetDir, packageManager);
527
+ else {
528
+ const installCommand = buildProjectInstallCommand(packageManager);
529
+ const renderedCommand = [installCommand.command, ...installCommand.args].join(" ");
530
+ ui.hint(`Skipped dependency install. Run \`${renderedCommand}\` when ready.`);
531
+ }
532
+ }
533
+ async function installScaffoldDependencies(targetDir, packageManager) {
534
+ const installCommand = buildProjectInstallCommand(packageManager);
535
+ const renderedCommand = [installCommand.command, ...installCommand.args].join(" ");
536
+ ui.header(`Installing dependencies with ${packageManager}...`);
537
+ ui.hint(renderedCommand);
538
+ const exitCode = await runProjectInstall({
539
+ cwd: targetDir,
540
+ packageManager
541
+ });
542
+ if (exitCode !== 0) throwReportedCliExit(`Dependency install failed with exit code ${exitCode}.`);
543
+ ui.success("Dependencies installed.");
544
+ ui.hint(`Run \`${packageManager} test\` to run the scaffolded workflow tests.`);
502
545
  }
503
546
  //#endregion
504
547
  export { handleInit };
@@ -158,14 +158,14 @@ function createIntegrationsCommand() {
158
158
  description: "List Keystroke integrations available to your organization",
159
159
  schema: IntegrationsListOptionsSchema,
160
160
  optionsConfig: INTEGRATIONS_LIST_OPTIONS_CONFIG,
161
- loadHandler: async () => (await import("./list.handler-Jk_vK66s.mjs")).handleIntegrationsList,
161
+ loadHandler: async () => (await import("./list.handler-CMRQKH4b.mjs")).handleIntegrationsList,
162
162
  subcommands: [
163
163
  createTypedCommand({
164
164
  name: "list",
165
165
  description: "List Keystroke integrations available to your organization",
166
166
  schema: IntegrationsListOptionsSchema,
167
167
  optionsConfig: INTEGRATIONS_LIST_OPTIONS_CONFIG,
168
- loadHandler: async () => (await import("./list.handler-Jk_vK66s.mjs")).handleIntegrationsList
168
+ loadHandler: async () => (await import("./list.handler-CMRQKH4b.mjs")).handleIntegrationsList
169
169
  }),
170
170
  createTypedCommand({
171
171
  name: "show",
@@ -177,7 +177,7 @@ function createIntegrationsCommand() {
177
177
  description: "Integration id, e.g. aws-s3",
178
178
  key: "id"
179
179
  },
180
- loadHandler: async () => (await import("./show.handler-C_VDYU91.mjs")).handleIntegrationShow
180
+ loadHandler: async () => (await import("./show.handler-nkK6Erbb.mjs")).handleIntegrationShow
181
181
  }),
182
182
  createTypedCommand({
183
183
  name: "register",
@@ -185,7 +185,7 @@ function createIntegrationsCommand() {
185
185
  schema: IntegrationsRegisterOptionsSchema,
186
186
  optionsConfig: INTEGRATIONS_REGISTER_OPTIONS_CONFIG,
187
187
  handler: async (opts, ctx) => {
188
- const { handleIntegrationsRegister } = await import("./register.handler-CePNU3sP.mjs");
188
+ const { handleIntegrationsRegister } = await import("./register.handler-CKMZ2WmD.mjs");
189
189
  await handleIntegrationsRegister({
190
190
  integrationId: opts.integrationId,
191
191
  clientAppCredentialSetId: opts.clientApp,
@@ -521,7 +521,7 @@ const logger = {
521
521
  };
522
522
  //#endregion
523
523
  //#region package.json
524
- var version = "0.0.31";
524
+ var version = "0.0.32";
525
525
  //#endregion
526
526
  //#region src/command-registry.ts
527
527
  const ROOT_OPTIONS_WITH_VALUES$1 = new Set([
@@ -566,11 +566,11 @@ const lazyCommandDefinitions = [
566
566
  },
567
567
  {
568
568
  name: "init",
569
- loadCommand: async () => (await import("./init-DX08T87c.mjs")).createInitCommand()
569
+ loadCommand: async () => (await import("./init-BOCDwqKR.mjs")).createInitCommand()
570
570
  },
571
571
  {
572
572
  name: "integrations",
573
- loadCommand: async () => (await import("./integrations-MEExmqcg.mjs")).createIntegrationsCommand()
573
+ loadCommand: async () => (await import("./integrations-m7_tb3GV.mjs")).createIntegrationsCommand()
574
574
  },
575
575
  {
576
576
  name: "invites",
@@ -586,7 +586,7 @@ const lazyCommandDefinitions = [
586
586
  },
587
587
  {
588
588
  name: "operations",
589
- loadCommand: async () => (await import("./operations-AWMLs6mE.mjs")).createOperationsCommand()
589
+ loadCommand: async () => (await import("./operations-CF2nUiBs.mjs")).createOperationsCommand()
590
590
  },
591
591
  {
592
592
  name: "projects",
@@ -598,11 +598,11 @@ const lazyCommandDefinitions = [
598
598
  },
599
599
  {
600
600
  name: "search",
601
- loadCommand: async () => (await import("./search-BEfy2fG9.mjs")).createSearchCommand()
601
+ loadCommand: async () => (await import("./search-CA0J5NOY.mjs")).createSearchCommand()
602
602
  },
603
603
  {
604
604
  name: "skills",
605
- loadCommand: async () => (await import("./skills.command-0-E8mcYE.mjs")).createSkillsCommand()
605
+ loadCommand: async () => (await import("./skills.command-COYd3k4Z.mjs")).createSkillsCommand()
606
606
  },
607
607
  {
608
608
  name: "sync",
@@ -3,8 +3,8 @@
3
3
  import { a as ui } from "./keystroke.mjs";
4
4
  import { i as writeJson } from "./output-BWcVRt-T.mjs";
5
5
  import { i as requireClient } from "./context-sgKhRc5v.mjs";
6
- import { t as detectPackageManager } from "./package-manager-DT1EhOkS.mjs";
7
- import { t as renderOperation } from "./render-operation-Bc7Wu1sP.mjs";
6
+ import { n as detectPackageManager } from "./package-manager-CvY4IW7X.mjs";
7
+ import { t as renderOperation } from "./render-operation-DWbMwhfc.mjs";
8
8
  //#region src/commands/operations/list.handler.ts
9
9
  async function handleOperationsList(options, ctx) {
10
10
  const result = await requireClient(ctx).operations.list({
@@ -56,13 +56,13 @@ function createOperationsCommand() {
56
56
  description: "Browse and inspect operations across all integrations",
57
57
  schema: OperationsListOptionsSchema,
58
58
  optionsConfig: OPERATIONS_LIST_OPTIONS_CONFIG,
59
- loadHandler: async () => (await import("./list.handler-BKfGLkFu.mjs")).handleOperationsList,
59
+ loadHandler: async () => (await import("./list.handler-DbYUk6ko.mjs")).handleOperationsList,
60
60
  subcommands: [createTypedCommand({
61
61
  name: "list",
62
62
  description: "List operations, optionally scoped to one integration",
63
63
  schema: OperationsListOptionsSchema,
64
64
  optionsConfig: OPERATIONS_LIST_OPTIONS_CONFIG,
65
- loadHandler: async () => (await import("./list.handler-BKfGLkFu.mjs")).handleOperationsList
65
+ loadHandler: async () => (await import("./list.handler-DbYUk6ko.mjs")).handleOperationsList
66
66
  }), createTypedCommand({
67
67
  name: "show",
68
68
  description: "Show full details for one operation, including input/output schemas",
@@ -73,7 +73,7 @@ function createOperationsCommand() {
73
73
  description: "Operation id, e.g. aws-s3.put-object",
74
74
  key: "id"
75
75
  },
76
- loadHandler: async () => (await import("./show.handler-Wmv0tkxx.mjs")).handleOperationShow
76
+ loadHandler: async () => (await import("./show.handler-CwwnCmbp.mjs")).handleOperationShow
77
77
  })]
78
78
  });
79
79
  }
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { y as toErrorMessage } from "./keystroke.mjs";
4
+ import { dirname, join } from "node:path";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { spawn } from "node:child_process";
7
+ //#region src/lib/package-manager.ts
8
+ const PackageManagerValues = [
9
+ "npm",
10
+ "pnpm",
11
+ "yarn",
12
+ "bun"
13
+ ];
14
+ function parsePackageManagerField(value) {
15
+ if (typeof value !== "string" || value.length === 0) return null;
16
+ const name = value.split("@")[0]?.trim();
17
+ if (name && PackageManagerValues.includes(name)) return name;
18
+ return null;
19
+ }
20
+ function detectFromLockfiles(directory) {
21
+ if (existsSync(join(directory, "bun.lockb")) || existsSync(join(directory, "bun.lock"))) return "bun";
22
+ if (existsSync(join(directory, "pnpm-lock.yaml"))) return "pnpm";
23
+ if (existsSync(join(directory, "yarn.lock"))) return "yarn";
24
+ if (existsSync(join(directory, "package-lock.json")) || existsSync(join(directory, "npm-shrinkwrap.json"))) return "npm";
25
+ return null;
26
+ }
27
+ function readPackageManagerField(directory) {
28
+ const packageJsonPath = join(directory, "package.json");
29
+ if (!existsSync(packageJsonPath)) return null;
30
+ try {
31
+ return parsePackageManagerField(JSON.parse(readFileSync(packageJsonPath, "utf8")).packageManager);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function walkProjectDirectories(startDir) {
37
+ const directories = [];
38
+ let directory = startDir;
39
+ while (true) {
40
+ directories.push(directory);
41
+ const parent = dirname(directory);
42
+ if (parent === directory) break;
43
+ directory = parent;
44
+ }
45
+ return directories;
46
+ }
47
+ function hasPackageManagerSignal(cwd) {
48
+ for (const directory of walkProjectDirectories(cwd)) {
49
+ if (detectFromLockfiles(directory)) return true;
50
+ if (readPackageManagerField(directory)) return true;
51
+ }
52
+ return false;
53
+ }
54
+ /**
55
+ * Infers the package manager for the current working directory by walking up
56
+ * from `cwd` and preferring lockfiles (monorepo roots) over nested package.json
57
+ * files without lockfiles.
58
+ */
59
+ function detectPackageManager(options = {}) {
60
+ let packageManagerFromField = null;
61
+ for (const directory of walkProjectDirectories(options.cwd ?? process.cwd())) {
62
+ const fromLockfiles = detectFromLockfiles(directory);
63
+ if (fromLockfiles) return fromLockfiles;
64
+ const fromField = readPackageManagerField(directory);
65
+ if (fromField && !packageManagerFromField) packageManagerFromField = fromField;
66
+ }
67
+ return packageManagerFromField ?? "npm";
68
+ }
69
+ async function resolvePackageManagerResolution(options = {}) {
70
+ const cwd = options.cwd ?? process.cwd();
71
+ if (hasPackageManagerSignal(cwd)) return {
72
+ packageManager: detectPackageManager({ cwd }),
73
+ source: "project"
74
+ };
75
+ if (await (options.isPackageManagerAvailable ?? isPackageManagerOnPath)("pnpm")) return {
76
+ packageManager: "pnpm",
77
+ source: "preferred"
78
+ };
79
+ return {
80
+ packageManager: "npm",
81
+ source: "fallback"
82
+ };
83
+ }
84
+ function formatPackageInstallCommand(packageName, options = {}) {
85
+ const packageManager = options.packageManager ?? detectPackageManager({ cwd: options.cwd });
86
+ if (packageManager === "npm") return `npm install ${packageName}`;
87
+ if (packageManager === "pnpm") return `pnpm add ${packageName}`;
88
+ if (packageManager === "yarn") return `yarn add ${packageName}`;
89
+ return `bun add ${packageName}`;
90
+ }
91
+ function buildProjectInstallCommand(packageManager) {
92
+ if (packageManager === "pnpm") return {
93
+ command: "pnpm",
94
+ args: ["install"]
95
+ };
96
+ if (packageManager === "yarn") return {
97
+ command: "yarn",
98
+ args: ["install"]
99
+ };
100
+ if (packageManager === "bun") return {
101
+ command: "bun",
102
+ args: ["install"]
103
+ };
104
+ return {
105
+ command: "npm",
106
+ args: ["install"]
107
+ };
108
+ }
109
+ function isPackageManagerOnPath(packageManager) {
110
+ return new Promise((resolve) => {
111
+ const child = spawn(packageManager, ["--version"], {
112
+ stdio: "ignore",
113
+ shell: process.platform === "win32"
114
+ });
115
+ child.on("error", () => resolve(false));
116
+ child.on("close", (code) => resolve(code === 0));
117
+ });
118
+ }
119
+ async function runProjectInstall(options) {
120
+ const installCommand = buildProjectInstallCommand(options.packageManager);
121
+ return (options.runCommand ?? runPackageManagerCommand)(installCommand, { cwd: options.cwd });
122
+ }
123
+ function runPackageManagerCommand({ command, args }, options) {
124
+ return new Promise((resolve, reject) => {
125
+ const child = spawn(command, args, {
126
+ cwd: options.cwd,
127
+ stdio: "inherit",
128
+ shell: process.platform === "win32"
129
+ });
130
+ child.on("error", (error) => {
131
+ reject(new Error(`Failed to start ${command}: ${toErrorMessage(error)}`, { cause: error }));
132
+ });
133
+ child.on("close", (code) => resolve(code ?? 1));
134
+ });
135
+ }
136
+ //#endregion
137
+ export { runProjectInstall as a, resolvePackageManagerResolution as i, detectPackageManager as n, formatPackageInstallCommand as r, buildProjectInstallCommand as t };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { a as ui } from "./keystroke.mjs";
4
- import { n as formatPackageInstallCommand } from "./package-manager-DT1EhOkS.mjs";
4
+ import { r as formatPackageInstallCommand } from "./package-manager-CvY4IW7X.mjs";
5
5
  //#region src/lib/render-operation.ts
6
6
  function isFull(op) {
7
7
  return "inputSchemaJson" in op;
@@ -46,7 +46,7 @@ function createSearchCommand() {
46
46
  description: "Search query (matches name and related fields per --type)",
47
47
  key: "query"
48
48
  },
49
- loadHandler: async () => (await import("./search.handler-V7ObLGjN.mjs")).handleSearch
49
+ loadHandler: async () => (await import("./search.handler-BVDsYZlJ.mjs")).handleSearch
50
50
  });
51
51
  }
52
52
  //#endregion
@@ -4,8 +4,8 @@ import { a as ui } from "./keystroke.mjs";
4
4
  import { i as writeJson } from "./output-BWcVRt-T.mjs";
5
5
  import { i as requireClient } from "./context-sgKhRc5v.mjs";
6
6
  import { t as renderCredential } from "./render-credential-Bn15FEUC.mjs";
7
- import { n as formatPackageInstallCommand, t as detectPackageManager } from "./package-manager-DT1EhOkS.mjs";
8
- import { t as renderOperation } from "./render-operation-Bc7Wu1sP.mjs";
7
+ import { n as detectPackageManager, r as formatPackageInstallCommand } from "./package-manager-CvY4IW7X.mjs";
8
+ import { t as renderOperation } from "./render-operation-DWbMwhfc.mjs";
9
9
  //#region src/lib/render-integration.ts
10
10
  function renderIntegration(i, { full = false, packageManager } = {}) {
11
11
  ui.br();
@@ -4,8 +4,8 @@ import { a as ui } from "./keystroke.mjs";
4
4
  import { i as writeJson } from "./output-BWcVRt-T.mjs";
5
5
  import { i as requireClient } from "./context-sgKhRc5v.mjs";
6
6
  import { i as renderJsonSchema } from "./schema-display-FvI8QjOQ.mjs";
7
- import { t as detectPackageManager } from "./package-manager-DT1EhOkS.mjs";
8
- import { t as renderOperation } from "./render-operation-Bc7Wu1sP.mjs";
7
+ import { n as detectPackageManager } from "./package-manager-CvY4IW7X.mjs";
8
+ import { t as renderOperation } from "./render-operation-DWbMwhfc.mjs";
9
9
  //#region src/commands/operations/show.handler.ts
10
10
  async function handleOperationShow(options, ctx) {
11
11
  const { operation } = await requireClient(ctx).operations.get(options.id);
@@ -3,7 +3,7 @@
3
3
  import { a as ui } from "./keystroke.mjs";
4
4
  import { i as writeJson } from "./output-BWcVRt-T.mjs";
5
5
  import { i as requireClient } from "./context-sgKhRc5v.mjs";
6
- import { n as formatPackageInstallCommand, t as detectPackageManager } from "./package-manager-DT1EhOkS.mjs";
6
+ import { n as detectPackageManager, r as formatPackageInstallCommand } from "./package-manager-CvY4IW7X.mjs";
7
7
  //#region src/commands/integrations/show.handler.ts
8
8
  async function handleIntegrationShow(options, ctx) {
9
9
  const client = requireClient(ctx);
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { D as CliExitError, a as ui } from "./keystroke.mjs";
4
4
  import { i as writeJson, r as isJsonMode } from "./output-BWcVRt-T.mjs";
5
- import { i as UnknownSkillAgentError, n as resolveSkillInstallChoices, r as installKeystrokeAgentSkills, t as summarizeSkillInstall } from "./skill-installer-D6j9IA3Z.mjs";
5
+ import { i as UnknownSkillAgentError, n as resolveSkillInstallChoices, r as installKeystrokeAgentSkills, t as summarizeSkillInstall } from "./skill-installer-DG8kTaQR.mjs";
6
6
  import path from "node:path";
7
7
  //#region src/commands/skills/skills-sync.handler.ts
8
8
  async function handleSkillsSync(options, _ctx) {
@@ -38,7 +38,7 @@ function createSkillsCommand() {
38
38
  description: "Install bundled Keystroke skills into selected agent skill directories",
39
39
  schema: SkillsCommandOptionsSchema,
40
40
  optionsConfig: SKILLS_OPTIONS_CONFIG,
41
- loadHandler: async () => (await import("./skills-sync.handler-BAATdT6N.mjs")).handleSkillsSync
41
+ loadHandler: async () => (await import("./skills-sync.handler-yRmi3OgP.mjs")).handleSkillsSync
42
42
  })]
43
43
  });
44
44
  cmd.enablePositionalOptions();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keystrokehq/cli",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "private": false,
5
5
  "description": "Command-line interface for creating, managing, and deploying Keystroke automations.",
6
6
  "type": "module",
@@ -38,12 +38,12 @@
38
38
  "tsdown": "0.21.10",
39
39
  "typescript": "^5.9.3",
40
40
  "vitest": "^4.1.5",
41
- "@keystroke/local-memory": "0.0.2",
42
41
  "@keystroke/env-utils": "0.0.0",
43
- "@keystrokehq/config": "0.0.2",
42
+ "@keystroke/local-memory": "0.0.2",
44
43
  "@keystroke/shared-types": "0.0.11",
45
- "@keystroke/test-utils": "0.0.6",
44
+ "@keystrokehq/config": "0.0.2",
46
45
  "@keystroke/typescript-config": "0.0.0",
46
+ "@keystroke/test-utils": "0.0.6",
47
47
  "@keystroke/utils": "0.0.0",
48
48
  "@keystroke/workflow-builder": "0.0.15",
49
49
  "@keystrokehq/core": "0.0.10",
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { dirname, join } from "node:path";
4
- import { existsSync, readFileSync } from "node:fs";
5
- //#region src/lib/package-manager.ts
6
- const PackageManagerValues = [
7
- "npm",
8
- "pnpm",
9
- "yarn",
10
- "bun"
11
- ];
12
- function parsePackageManagerField(value) {
13
- if (typeof value !== "string" || value.length === 0) return null;
14
- const name = value.split("@")[0]?.trim();
15
- if (name && PackageManagerValues.includes(name)) return name;
16
- return null;
17
- }
18
- function detectFromLockfiles(directory) {
19
- if (existsSync(join(directory, "bun.lockb")) || existsSync(join(directory, "bun.lock"))) return "bun";
20
- if (existsSync(join(directory, "pnpm-lock.yaml"))) return "pnpm";
21
- if (existsSync(join(directory, "yarn.lock"))) return "yarn";
22
- if (existsSync(join(directory, "package-lock.json")) || existsSync(join(directory, "npm-shrinkwrap.json"))) return "npm";
23
- return null;
24
- }
25
- function readPackageManagerField(directory) {
26
- const packageJsonPath = join(directory, "package.json");
27
- if (!existsSync(packageJsonPath)) return null;
28
- try {
29
- return parsePackageManagerField(JSON.parse(readFileSync(packageJsonPath, "utf8")).packageManager);
30
- } catch {
31
- return null;
32
- }
33
- }
34
- /**
35
- * Infers the package manager for the current working directory by walking up
36
- * from `cwd` and preferring lockfiles (monorepo roots) over nested package.json
37
- * files without lockfiles.
38
- */
39
- function detectPackageManager(options = {}) {
40
- let directory = options.cwd ?? process.cwd();
41
- let packageManagerFromField = null;
42
- while (true) {
43
- const fromLockfiles = detectFromLockfiles(directory);
44
- if (fromLockfiles) return fromLockfiles;
45
- const fromField = readPackageManagerField(directory);
46
- if (fromField && !packageManagerFromField) packageManagerFromField = fromField;
47
- const parent = dirname(directory);
48
- if (parent === directory) break;
49
- directory = parent;
50
- }
51
- return packageManagerFromField ?? "npm";
52
- }
53
- function formatPackageInstallCommand(packageName, options = {}) {
54
- const packageManager = options.packageManager ?? detectPackageManager({ cwd: options.cwd });
55
- if (packageManager === "npm") return `npm install ${packageName}`;
56
- if (packageManager === "pnpm") return `pnpm add ${packageName}`;
57
- if (packageManager === "yarn") return `yarn add ${packageName}`;
58
- return `bun add ${packageName}`;
59
- }
60
- //#endregion
61
- export { formatPackageInstallCommand as n, detectPackageManager as t };