@keystrokehq/cli 0.0.164 → 0.0.166

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 (38) hide show
  1. package/README.md +11 -0
  2. package/dist/dist-BMPry9tj.mjs +577 -0
  3. package/dist/dist-BMPry9tj.mjs.map +1 -0
  4. package/dist/dist-CegYAHE0.mjs +3 -0
  5. package/dist/{schemas-Bq8SXmZk.mjs → dist-D_0UmqFb.mjs} +2113 -6
  6. package/dist/dist-D_0UmqFb.mjs.map +1 -0
  7. package/dist/{dist-CgV0MxBq.mjs → dist-WoxxHzM6.mjs} +123 -25
  8. package/dist/dist-WoxxHzM6.mjs.map +1 -0
  9. package/dist/index.mjs +147 -37
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{maybe-auto-update-CIcDJRrg.mjs → maybe-auto-update-B03C4E8i.mjs} +2 -2
  12. package/dist/{maybe-auto-update-CIcDJRrg.mjs.map → maybe-auto-update-B03C4E8i.mjs.map} +1 -1
  13. package/dist/skills-bundle/_AGENTS.md +7 -3
  14. package/dist/skills-bundle/skills/keystroke-actions/SKILL.md +1 -1
  15. package/dist/skills-bundle/skills/keystroke-actions/references/catalog-and-imports.md +2 -3
  16. package/dist/skills-bundle/skills/keystroke-agents/references/tools-mcp-codemode.md +1 -1
  17. package/dist/skills-bundle/skills/keystroke-cli/SKILL.md +4 -2
  18. package/dist/skills-bundle/skills/keystroke-deploy/SKILL.md +92 -0
  19. package/dist/skills-bundle/skills/keystroke-deploy/references/build-and-full-deploy.md +28 -0
  20. package/dist/skills-bundle/skills/keystroke-deploy/references/filtered-deploy.md +49 -0
  21. package/dist/skills-bundle/skills/keystroke-deploy/references/wip-ignore.md +32 -0
  22. package/dist/skills-bundle/skills/keystroke-gateways/SKILL.md +5 -6
  23. package/dist/skills-bundle/skills/keystroke-gateways/references/slack-setup.md +0 -11
  24. package/dist/templates/hello-world/README.md +1 -1
  25. package/dist/templates/hello-world/keystroke.config.ts +1 -5
  26. package/dist/{version-DdbxgFWa.mjs → version-FrEOAEU7.mjs} +2 -2
  27. package/dist/{version-DdbxgFWa.mjs.map → version-FrEOAEU7.mjs.map} +1 -1
  28. package/package.json +1 -1
  29. package/dist/discovery-DV7FkTRA-JYiC_9cY.mjs +0 -71
  30. package/dist/discovery-DV7FkTRA-JYiC_9cY.mjs.map +0 -1
  31. package/dist/dist-CgV0MxBq.mjs.map +0 -1
  32. package/dist/dist-DtqmE5R_.mjs +0 -2113
  33. package/dist/dist-DtqmE5R_.mjs.map +0 -1
  34. package/dist/dist-sXdjEZpP.mjs +0 -293
  35. package/dist/dist-sXdjEZpP.mjs.map +0 -1
  36. package/dist/schemas-Bq8SXmZk.mjs.map +0 -1
  37. package/dist/walk-project-171B4cvO-DKtupJ6Z.mjs +0 -102
  38. package/dist/walk-project-171B4cvO-DKtupJ6Z.mjs.map +0 -1
package/README.md CHANGED
@@ -106,6 +106,17 @@ keystroke build
106
106
  keystroke build --dir ./my-app
107
107
  ```
108
108
 
109
+ ### `deploy`
110
+
111
+ Build, upload, and promote a project artifact on the platform. Requires `keystroke auth login` and `--project <slug>`.
112
+
113
+ ```bash
114
+ keystroke deploy
115
+ keystroke deploy --filter workflows/ping --filter agents/world
116
+ ```
117
+
118
+ `--filter` matches exact build entry keys (`agents/foo`, `workflows/bar`, …). Repeat the flag for multiple modules. Targeted deploy downloads the active artifact, overlays rebuilt module files, merges `route-manifest.json`, and uploads a full tarball — the first deploy for a project must be a full deploy without `--filter`. Config and bundled assets are carried forward from the active artifact; skill/asset drift is not validated in v1.
119
+
109
120
  ### `dev`
110
121
 
111
122
  Watch `src/`, rebuild `dist/` on change, and restart the API. **Not true hot reload** — each change reruns tsdown and boots a fresh server process so discovery picks up new agents/workflows/triggers.
@@ -0,0 +1,577 @@
1
+ #!/usr/bin/env node
2
+ import { cn as object, in as array, ln as string } from "./dist-D_0UmqFb.mjs";
3
+ import { L as entryIdFromFile, R as readKeystrokeIgnoreDirective, a as buildStoredRouteManifestForProject, z as shouldSkipKeystrokeModuleFile } from "./dist-WoxxHzM6.mjs";
4
+ import { isBuiltin } from "node:module";
5
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
6
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+ import "node:fs/promises";
9
+ import { createHash } from "node:crypto";
10
+ import { build, mergeConfig } from "tsdown";
11
+ //#region ../../packages/build/dist/walk-project-eZ95LOUW.mjs
12
+ function toPosix$1(path) {
13
+ return path.split(sep).join("/");
14
+ }
15
+ function projectRelativePath(root, filePath) {
16
+ return toPosix$1(relative(root, normalizeBuildFilePath(filePath)));
17
+ }
18
+ /** Normalize paths so /var and /private/var compare equal on macOS. */
19
+ function normalizeBuildFilePath(filePath) {
20
+ const absolute = resolve(filePath);
21
+ try {
22
+ return realpathSync.native(absolute);
23
+ } catch {
24
+ return absolute;
25
+ }
26
+ }
27
+ function normalizeModuleId(id, root) {
28
+ if (id.startsWith("\0")) return null;
29
+ const normalized = normalizeBuildFilePath(id.startsWith("file://") ? fileURLToPath(id) : id);
30
+ if (!normalized.startsWith(normalizeBuildFilePath(root))) return null;
31
+ return normalized;
32
+ }
33
+ function formatImportGuardError(root, ignoredPath, importer) {
34
+ const ignored = projectRelativePath(root, ignoredPath);
35
+ if (importer) return `Cannot import "${ignored}" (@keystroke ignore) from "${projectRelativePath(root, importer)}". Remove the directive or stop importing it from a shipped module.`;
36
+ return `Cannot import "${ignored}" (@keystroke ignore). Remove the directive or stop importing it from a shipped module.`;
37
+ }
38
+ /** Throw when a built module imports a `@keystroke ignore` source file. */
39
+ function keystrokeIgnoreGuardPlugin(options) {
40
+ const root = normalizeBuildFilePath(options.root);
41
+ const ignoredFiles = new Set(options.ignoredFiles.map((filePath) => normalizeBuildFilePath(filePath)));
42
+ function assertNotIgnored(filePath, importer) {
43
+ const normalized = normalizeBuildFilePath(filePath);
44
+ if (!ignoredFiles.has(normalized)) return;
45
+ throw new Error(formatImportGuardError(root, normalized, importer));
46
+ }
47
+ return {
48
+ name: "keystroke-ignore-guard",
49
+ async resolveId(source, importer, resolveOptions) {
50
+ if (resolveOptions?.isEntry || !importer) return null;
51
+ const resolved = await this.resolve(source, importer, {
52
+ ...resolveOptions,
53
+ skipSelf: true
54
+ });
55
+ if (!resolved) return null;
56
+ assertNotIgnored(typeof resolved === "string" ? resolved : resolved.id, importer);
57
+ return null;
58
+ },
59
+ load(id) {
60
+ const normalized = normalizeModuleId(id, root);
61
+ if (!normalized) return null;
62
+ assertNotIgnored(normalized);
63
+ return null;
64
+ }
65
+ };
66
+ }
67
+ const IGNORED_DIRS = new Set([
68
+ "node_modules",
69
+ "dist",
70
+ ".git",
71
+ ".turbo",
72
+ "build",
73
+ "coverage",
74
+ ".keystroke",
75
+ ".cache",
76
+ "tmp"
77
+ ]);
78
+ const IGNORED_ENV_FILE = /\.env/;
79
+ const IGNORED_LOG_FILE = /\.log$/;
80
+ function isIgnoredFile(name) {
81
+ return IGNORED_ENV_FILE.test(name) || IGNORED_LOG_FILE.test(name);
82
+ }
83
+ function toPosix$2(path) {
84
+ return path.split(sep).join("/");
85
+ }
86
+ function isProbablyBinary(buffer) {
87
+ const sampleLength = Math.min(buffer.length, 8e3);
88
+ for (let index = 0; index < sampleLength; index += 1) if (buffer[index] === 0) return true;
89
+ return false;
90
+ }
91
+ function classifyBuildEntry(root, srcRoot, filePath) {
92
+ const relativePath = toPosix$2(relative(root, filePath));
93
+ const agentsPrefix = `${toPosix$2(relative(root, join(srcRoot, "agents")))}/`;
94
+ const workflowsPrefix = `${toPosix$2(relative(root, join(srcRoot, "workflows")))}/`;
95
+ const triggersPrefix = `${toPosix$2(relative(root, join(srcRoot, "triggers")))}/`;
96
+ if (relativePath.startsWith(agentsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
97
+ const id = entryIdFromFile(join(srcRoot, "agents"), filePath, { nestedEntry: "agent" });
98
+ return id ? {
99
+ entryKey: `agents/${id}`,
100
+ entryPath: filePath
101
+ } : null;
102
+ }
103
+ if (relativePath.startsWith(workflowsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
104
+ const id = entryIdFromFile(join(srcRoot, "workflows"), filePath, { nestedEntry: "workflow" });
105
+ return id ? {
106
+ entryKey: `workflows/${id}`,
107
+ entryPath: filePath
108
+ } : null;
109
+ }
110
+ if (relativePath.startsWith(triggersPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
111
+ const id = entryIdFromFile(join(srcRoot, "triggers"), filePath, { nestedEntry: "trigger" });
112
+ return id ? {
113
+ entryKey: `triggers/${id}`,
114
+ entryPath: filePath
115
+ } : null;
116
+ }
117
+ return null;
118
+ }
119
+ function shouldSkipIgnoredModule(filePath, phase) {
120
+ return shouldSkipKeystrokeModuleFile(readKeystrokeIgnoreDirective(filePath), phase);
121
+ }
122
+ function isSrcTypeScriptFile(srcRoot, filePath) {
123
+ const relativePath = toPosix$2(relative(srcRoot, filePath));
124
+ if (relativePath.startsWith("..")) return false;
125
+ return /\.(ts|mts)$/.test(relativePath);
126
+ }
127
+ function walkTree(root, dir, srcRoot, phase, collectSources, sourceFiles, buildEntries, ignoredFiles, totals) {
128
+ for (const name of readdirSync(dir).sort()) {
129
+ if (collectSources && (sourceFiles.length >= 2e3 || totals.bytes >= 8388608)) return;
130
+ const absolute = join(dir, name);
131
+ const stats = statSync(absolute);
132
+ if (stats.isDirectory()) {
133
+ if (IGNORED_DIRS.has(name)) continue;
134
+ walkTree(root, absolute, srcRoot, phase, collectSources, sourceFiles, buildEntries, ignoredFiles, totals);
135
+ continue;
136
+ }
137
+ if (!stats.isFile()) continue;
138
+ const isUnderSrc = isSrcTypeScriptFile(srcRoot, absolute);
139
+ const skipIgnored = isUnderSrc ? shouldSkipIgnoredModule(absolute, phase) : false;
140
+ if (isUnderSrc && skipIgnored) ignoredFiles.push(normalizeBuildFilePath(absolute));
141
+ const buildEntry = classifyBuildEntry(root, srcRoot, absolute);
142
+ if (buildEntry && !skipIgnored) buildEntries[buildEntry.entryKey] = buildEntry.entryPath;
143
+ if (!collectSources) continue;
144
+ if (stats.size > 262144 || isIgnoredFile(name)) continue;
145
+ const buffer = readFileSync(absolute);
146
+ if (isProbablyBinary(buffer)) continue;
147
+ totals.bytes += buffer.byteLength;
148
+ sourceFiles.push({
149
+ path: toPosix$2(relative(root, absolute)),
150
+ contents: buffer.toString("utf8"),
151
+ hash: createHash("sha256").update(buffer).digest("hex")
152
+ });
153
+ }
154
+ }
155
+ /**
156
+ * One full-tree walk of the project: classifies tsdown entries and optionally
157
+ * collects deploy source files (path + contents + hash) under snapshot limits.
158
+ */
159
+ function walkProject(root, srcDirOrOptions = "src", maybeOptions) {
160
+ const srcDir = typeof srcDirOrOptions === "string" ? srcDirOrOptions : srcDirOrOptions.srcDir ?? "src";
161
+ const options = typeof srcDirOrOptions === "string" ? maybeOptions ?? {} : srcDirOrOptions;
162
+ const collectSources = options.collectSources ?? false;
163
+ const phase = options.phase ?? "build";
164
+ const sourceFiles = [];
165
+ const buildEntries = {};
166
+ const ignoredFiles = [];
167
+ walkTree(root, root, join(root, srcDir), phase, collectSources, sourceFiles, buildEntries, ignoredFiles, { bytes: 0 });
168
+ return {
169
+ sourceFiles,
170
+ buildEntries,
171
+ ignoredFiles
172
+ };
173
+ }
174
+ //#endregion
175
+ //#region ../../packages/sandbox/dist/files-B0H9_dP4.mjs
176
+ const SandboxFileContentSchema = object({
177
+ path: string(),
178
+ file: string()
179
+ });
180
+ /** Destination path under `/workspace`. For `dir`, used as a prefix. */
181
+ const SandboxFileSchema = object({
182
+ path: string(),
183
+ /** Single file body — typically from a build-time import (`import doc from "./doc.md"`). */
184
+ file: string().optional(),
185
+ /** Directory contents — each path is relative to `path`. */
186
+ dir: array(SandboxFileContentSchema).optional()
187
+ });
188
+ object({
189
+ key: string().optional(),
190
+ files: array(SandboxFileSchema).optional()
191
+ });
192
+ const SKIP_DIRS$1 = new Set([".git", "node_modules"]);
193
+ /** Recursively read a host file or directory into sandbox-relative `{ path, file }` entries. */
194
+ function packDirFromDisk(rootPath) {
195
+ const stat = statSync(rootPath);
196
+ if (stat.isFile()) return [{
197
+ path: basename(rootPath),
198
+ file: readFileSync(rootPath, "utf8")
199
+ }];
200
+ if (!stat.isDirectory()) throw new Error(`Expected a file or directory at ${rootPath}`);
201
+ const files = [];
202
+ walkDir(rootPath, rootPath, files);
203
+ return files;
204
+ }
205
+ function walkDir(rootPath, currentPath, files) {
206
+ for (const name of readdirSync(currentPath)) {
207
+ if (SKIP_DIRS$1.has(name)) continue;
208
+ const entryPath = join(currentPath, name);
209
+ const stat = statSync(entryPath);
210
+ if (stat.isDirectory()) {
211
+ walkDir(rootPath, entryPath, files);
212
+ continue;
213
+ }
214
+ if (!stat.isFile()) continue;
215
+ files.push({
216
+ path: relative(rootPath, entryPath),
217
+ file: readFileSync(entryPath, "utf8")
218
+ });
219
+ }
220
+ }
221
+ const SKIP_DIRS = new Set([".git", "node_modules"]);
222
+ /** Pack every subdir under `src/skills/` and `src/files/` into an asset manifest. */
223
+ function packAssetDirs(appRoot, srcDir = "src") {
224
+ return {
225
+ skills: packAssetRoot(join(appRoot, srcDir, "skills")),
226
+ files: packAssetRoot(join(appRoot, srcDir, "files"))
227
+ };
228
+ }
229
+ function packAssetRoot(rootPath) {
230
+ if (!statSync(rootPath, { throwIfNoEntry: false })?.isDirectory()) return {};
231
+ const manifest = {};
232
+ for (const name of readdirSync(rootPath)) {
233
+ if (SKIP_DIRS.has(name)) continue;
234
+ const entryPath = join(rootPath, name);
235
+ if (!statSync(entryPath).isDirectory()) continue;
236
+ manifest[name] = packDirFromDisk(entryPath);
237
+ }
238
+ return manifest;
239
+ }
240
+ //#endregion
241
+ //#region ../../packages/tsdown-config/deps.js
242
+ /** Native addons kept external — resolved from the CLI runtime at app start. */
243
+ const NATIVE_RUNTIME_DEPS = [
244
+ "pg",
245
+ "better-sqlite3",
246
+ "@parcel/watcher"
247
+ ];
248
+ /** Kept external so .d.ts emission does not inline internal/inferred types. */
249
+ const LIBRARY_EXTERNAL_DEPS = [
250
+ "better-auth",
251
+ /^@better-auth\//,
252
+ /^better-auth\//
253
+ ];
254
+ /** Kept external when bundling published entry points (cli, keystroke, platform). */
255
+ const BUNDLED_ENTRY_EXTERNAL_DEPS = [...[
256
+ ...NATIVE_RUNTIME_DEPS,
257
+ "dockerode",
258
+ "ssh2",
259
+ "cpu-features",
260
+ "microsandbox",
261
+ /^@aws-sdk\//,
262
+ /^@napi-rs\//,
263
+ "hono",
264
+ /^@hono\//,
265
+ "undici"
266
+ ], ...LIBRARY_EXTERNAL_DEPS];
267
+ /** Bundle into CLI — device login uses Better Auth client only. */
268
+ const BETTER_AUTH_CLIENT_BUNDLE_PATTERNS = [/^better-auth\/client$/, /^better-auth\/client\//];
269
+ function isNativeRuntimeDep(id) {
270
+ return NATIVE_RUNTIME_DEPS.some((dep) => id === dep || id.startsWith(`${dep}/`));
271
+ }
272
+ function packageName(id) {
273
+ if (id.startsWith("@")) {
274
+ const [scope, name] = id.split("/");
275
+ return name ? `${scope}/${name}` : id;
276
+ }
277
+ return id.split("/")[0] ?? id;
278
+ }
279
+ function isRuntimeExternal(id) {
280
+ const name = packageName(id);
281
+ if (isNativeRuntimeDep(name)) return true;
282
+ if (name.startsWith("@keystrokehq/")) return true;
283
+ if (name === "better-auth" || name.startsWith("@better-auth/")) return true;
284
+ return false;
285
+ }
286
+ /** Library packages resolve npm deps from node_modules at runtime. */
287
+ function libraryBuildDepsConfig() {
288
+ return {
289
+ alwaysBundle: (_id) => null,
290
+ neverBundle: [
291
+ ...NATIVE_RUNTIME_DEPS,
292
+ /^@keystrokehq\//,
293
+ ...LIBRARY_EXTERNAL_DEPS
294
+ ],
295
+ onlyBundle: false
296
+ };
297
+ }
298
+ /** Bundle npm deps into dist; resolve @keystrokehq/* and natives from the runtime. */
299
+ function userAppBuildDepsConfig() {
300
+ return {
301
+ alwaysBundle: (id, _importer) => {
302
+ if (id.startsWith(".") || id.startsWith("/") || isBuiltin(id)) return null;
303
+ return isRuntimeExternal(id) ? null : true;
304
+ },
305
+ neverBundle: [
306
+ ...NATIVE_RUNTIME_DEPS,
307
+ /^@keystrokehq\//,
308
+ ...LIBRARY_EXTERNAL_DEPS
309
+ ],
310
+ onlyBundle: false
311
+ };
312
+ }
313
+ /**
314
+ * Published entry packages (cli, keystroke, platform): declare bundled @keystrokehq/*
315
+ * in dependencies (changesets release graph ignores devDependencies). Inlining is
316
+ * driven by alwaysBundle below, not by dep kind. Runtime npm deps stay external.
317
+ */
318
+ function bundledEntryDepsConfig() {
319
+ return {
320
+ alwaysBundle: [/^@keystrokehq\//],
321
+ neverBundle: [...BUNDLED_ENTRY_EXTERNAL_DEPS],
322
+ onlyBundle: false
323
+ };
324
+ }
325
+ [...BETTER_AUTH_CLIENT_BUNDLE_PATTERNS];
326
+ //#endregion
327
+ //#region ../../packages/tsdown-config/index.js
328
+ const baseTsdownConfig = {
329
+ format: ["esm", "cjs"],
330
+ dts: true,
331
+ clean: true,
332
+ sourcemap: true,
333
+ target: "node20",
334
+ deps: libraryBuildDepsConfig()
335
+ };
336
+ bundledEntryDepsConfig();
337
+ //#endregion
338
+ //#region ../../packages/build/dist/index.mjs
339
+ function packageRoot(nodeModules, scope, name) {
340
+ const direct = scope ? join(nodeModules, scope, name) : join(nodeModules, name);
341
+ if (existsSync(join(direct, "package.json"))) return direct;
342
+ const pnpmDir = join(nodeModules, ".pnpm");
343
+ if (!existsSync(pnpmDir)) return null;
344
+ for (const entry of readdirSync(pnpmDir)) {
345
+ const candidate = scope ? join(pnpmDir, entry, "node_modules", scope, name) : join(pnpmDir, entry, "node_modules", name);
346
+ if (existsSync(join(candidate, "package.json"))) return candidate;
347
+ }
348
+ return null;
349
+ }
350
+ /** Resolve a file shipped with `@keystrokehq/build` inside the CLI runtime. */
351
+ function resolveRuntimeBuildArtifact(runtimeNodeModules, relativePath) {
352
+ const pkgRoot = packageRoot(runtimeNodeModules, "@keystrokehq", "build");
353
+ if (!pkgRoot) throw new Error("Keystroke runtime is missing @keystrokehq/build");
354
+ const artifact = join(pkgRoot, relativePath);
355
+ if (!existsSync(artifact)) throw new Error(`Keystroke runtime artifact not found: ${relativePath}`);
356
+ return artifact;
357
+ }
358
+ /** Resolve exact build-entry keys for a filtered deploy build. */
359
+ function resolveBuildFilter(filter, buildEntries) {
360
+ if (filter.length === 0) throw new Error("At least one --filter entry is required");
361
+ const matched = {};
362
+ for (const key of [...new Set(filter)]) {
363
+ const entryPath = buildEntries[key];
364
+ if (!entryPath) throw new Error(`Unknown build entry "${key}". Use keys like agents/foo or workflows/bar from walkProject.`);
365
+ matched[key] = entryPath;
366
+ }
367
+ return matched;
368
+ }
369
+ function toPosix(path) {
370
+ return path.replace(/\\/g, "/");
371
+ }
372
+ function distRelativePath(entryKey) {
373
+ return `${entryKey}.mjs`;
374
+ }
375
+ async function buildSingleEntry(root, entryKey, entryPath, ignoredFiles) {
376
+ mkdirSync(join(root, ".keystroke"), { recursive: true });
377
+ const scratchRoot = mkdtempSync(join(root, ".keystroke", "filter-build-"));
378
+ const outDir = join(scratchRoot, "dist");
379
+ const relativePath = distRelativePath(entryKey);
380
+ await build(mergeConfig(baseTsdownConfig, {
381
+ entry: { [entryKey]: entryPath },
382
+ format: ["esm"],
383
+ outDir,
384
+ clean: true,
385
+ dts: false,
386
+ loader: { ".md": "text" },
387
+ deps: userAppBuildDepsConfig(),
388
+ plugins: [keystrokeIgnoreGuardPlugin({
389
+ root,
390
+ ignoredFiles
391
+ })]
392
+ }));
393
+ return {
394
+ scratchRoot,
395
+ relativePath
396
+ };
397
+ }
398
+ async function manifestEntriesForBuiltModule(root, entryPath, relativePath, contents, sourceMap) {
399
+ const moduleFile = toPosix(relative(root, entryPath));
400
+ const distPath = join(root, "dist", relativePath);
401
+ const distMapPath = `${distPath}.map`;
402
+ const previousDist = existsSync(distPath) ? readFileSync(distPath) : void 0;
403
+ const hadMap = existsSync(distMapPath);
404
+ const previousMap = hadMap ? readFileSync(distMapPath) : void 0;
405
+ mkdirSync(dirname(distPath), { recursive: true });
406
+ writeFileSync(distPath, contents);
407
+ if (sourceMap) writeFileSync(distMapPath, sourceMap);
408
+ try {
409
+ return (await buildStoredRouteManifestForProject(root, { reloadModules: true })).entries.filter((entry) => "moduleFile" in entry && entry.moduleFile === moduleFile);
410
+ } finally {
411
+ if (previousDist) writeFileSync(distPath, previousDist);
412
+ else rmSync(distPath, { force: true });
413
+ if (previousMap) writeFileSync(distMapPath, previousMap);
414
+ else if (sourceMap || hadMap) rmSync(distMapPath, { force: true });
415
+ }
416
+ }
417
+ /** Build selected modules in isolation for a filtered deploy merge. */
418
+ async function buildFilteredApp(options) {
419
+ const root = options.root ?? process.cwd();
420
+ const srcDir = options.srcDir ?? "src";
421
+ const phase = options.phase ?? "deploy";
422
+ const collectSources = options.collectSources ?? false;
423
+ const previous = process.cwd();
424
+ const walked = walkProject(root, {
425
+ srcDir,
426
+ collectSources,
427
+ phase
428
+ });
429
+ const matched = resolveBuildFilter(options.filter, walked.buildEntries);
430
+ const files = [];
431
+ const manifestEntries = [];
432
+ try {
433
+ process.chdir(root);
434
+ for (const [entryKey, entryPath] of Object.entries(matched)) {
435
+ const { scratchRoot, relativePath } = await buildSingleEntry(root, entryKey, entryPath, walked.ignoredFiles);
436
+ try {
437
+ const builtPath = join(scratchRoot, "dist", relativePath);
438
+ const mapPath = `${builtPath}.map`;
439
+ const contents = readFileSync(builtPath);
440
+ const sourceMap = existsSync(mapPath) ? readFileSync(mapPath) : void 0;
441
+ files.push({
442
+ entryKey,
443
+ relativePath,
444
+ contents,
445
+ sourceMap
446
+ });
447
+ manifestEntries.push(...await manifestEntriesForBuiltModule(root, entryPath, relativePath, contents, sourceMap));
448
+ } finally {
449
+ rmSync(scratchRoot, {
450
+ recursive: true,
451
+ force: true
452
+ });
453
+ }
454
+ }
455
+ } finally {
456
+ process.chdir(previous);
457
+ }
458
+ return {
459
+ files,
460
+ manifestEntries,
461
+ sourceFiles: walked.sourceFiles
462
+ };
463
+ }
464
+ const ASSETS_MODULE = "@keystrokehq/assets";
465
+ const VIRTUAL_PREFIX = `\0${ASSETS_MODULE}:`;
466
+ function renderAssetModule(manifest) {
467
+ return `export default ${JSON.stringify(manifest)};\n`;
468
+ }
469
+ /** Rolldown plugin: pack src/skills + src/files and expose `@keystrokehq/assets`. */
470
+ function agentAssetsPlugin(options) {
471
+ const srcDir = options.srcDir ?? "src";
472
+ let manifest = null;
473
+ return {
474
+ name: "keystroke-assets",
475
+ buildStart() {
476
+ manifest = packAssetDirs(options.root, srcDir);
477
+ },
478
+ resolveId(source) {
479
+ if (source !== ASSETS_MODULE) return null;
480
+ return `${VIRTUAL_PREFIX}manifest`;
481
+ },
482
+ load(id) {
483
+ if (id !== `${VIRTUAL_PREFIX}manifest` || !manifest) return null;
484
+ return renderAssetModule(manifest);
485
+ },
486
+ buildEnd() {
487
+ if (!manifest) return;
488
+ const outDir = join(options.root, options.outDir ?? "dist", ".keystroke");
489
+ mkdirSync(outDir, { recursive: true });
490
+ writeFileSync(join(outDir, "assets.mjs"), renderAssetModule(manifest));
491
+ }
492
+ };
493
+ }
494
+ /** Full tsdown config for user keystroke apps (config + discovered modules only). */
495
+ function createAppBuildConfig(options = {}, walked) {
496
+ const root = options.root ?? process.cwd();
497
+ const srcDir = options.srcDir ?? "src";
498
+ const outDir = options.outDir ?? "dist";
499
+ const configEntry = existsSync(join(root, "keystroke.config.ts")) ? join(root, "keystroke.config.ts") : void 0;
500
+ const walkedResult = walked ?? walkProject(root, {
501
+ srcDir,
502
+ phase: options.phase
503
+ });
504
+ const discovered = walkedResult.buildEntries;
505
+ const ignoredFiles = walkedResult.ignoredFiles;
506
+ const entries = {
507
+ ...configEntry ? { config: configEntry } : {},
508
+ ...discovered
509
+ };
510
+ if (Object.keys(entries).length === 0) throw new Error("Nothing to build — add keystroke.config.ts or modules under src/");
511
+ return mergeConfig(baseTsdownConfig, {
512
+ entry: entries,
513
+ format: ["esm"],
514
+ outDir: join(root, outDir),
515
+ clean: options.clean ?? true,
516
+ dts: false,
517
+ loader: { ".md": "text" },
518
+ deps: userAppBuildDepsConfig(),
519
+ plugins: [keystrokeIgnoreGuardPlugin({
520
+ root,
521
+ ignoredFiles
522
+ }), agentAssetsPlugin({
523
+ root,
524
+ srcDir,
525
+ outDir
526
+ })]
527
+ });
528
+ }
529
+ /** Build user agents, workflows, triggers, and config to `dist/`. */
530
+ async function buildApp(options = {}) {
531
+ const root = options.root ?? process.cwd();
532
+ const srcDir = options.srcDir ?? "src";
533
+ const emitManifest = options.emitManifest ?? true;
534
+ const collectSources = options.collectSources ?? false;
535
+ const phase = options.phase ?? "build";
536
+ const previous = process.cwd();
537
+ const walked = walkProject(root, {
538
+ srcDir,
539
+ collectSources,
540
+ phase
541
+ });
542
+ try {
543
+ process.chdir(root);
544
+ await build(createAppBuildConfig({
545
+ ...options,
546
+ root
547
+ }, {
548
+ buildEntries: walked.buildEntries,
549
+ ignoredFiles: walked.ignoredFiles
550
+ }));
551
+ if (emitManifest) {
552
+ const { emitStoredRouteManifestForProject } = await import("./dist-CegYAHE0.mjs");
553
+ await emitStoredRouteManifestForProject(root);
554
+ }
555
+ } finally {
556
+ process.chdir(previous);
557
+ }
558
+ return { sourceFiles: walked.sourceFiles };
559
+ }
560
+ /**
561
+ * Watch `src/` and rebuild `dist/` on change.
562
+ *
563
+ * NOT true hot reload — each change runs a full tsdown rebuild. Callers should
564
+ * restart the API server in `onRebuild` so discovery picks up new/changed modules.
565
+ */
566
+ async function watchApp(options = {}) {
567
+ const { runWatchApp } = await import("./watch-app-DTIeKrbl-CSpsNzVG.mjs");
568
+ await runWatchApp((watchOptions) => buildApp({
569
+ ...watchOptions,
570
+ emitManifest: false,
571
+ collectSources: false
572
+ }), options);
573
+ }
574
+ //#endregion
575
+ export { buildApp, buildFilteredApp, resolveRuntimeBuildArtifact, watchApp };
576
+
577
+ //# sourceMappingURL=dist-BMPry9tj.mjs.map