@keystrokehq/cli 0.0.137 → 0.0.139

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.
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env node
2
+ import { _ as string, c as _function, d as custom, f as discriminatedUnion, h as object, i as walkTypeScriptFiles, l as array, n as discoverModuleFileEntries, o as ZodType, p as literal, r as entryIdFromFile, v as union, x as toJSONSchema } from "./discovery-DV7FkTRA-BFmtZG9u.mjs";
3
+ import { X as PromptInputSchema, Z as PromptResponseSchema, et as ROUTE_MANIFEST_REL_PATH, wt as normalizeCredentialList } from "./dist-B27Z3YpE.mjs";
4
+ import { dirname, join, relative, sep } from "node:path";
5
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
6
+ import { pathToFileURL } from "node:url";
7
+ //#region ../../packages/manifest/dist/index.mjs
8
+ function isManifestAgent(value) {
9
+ if (typeof value !== "object" || value === null) return false;
10
+ const agent = value;
11
+ return typeof agent.slug === "string" && agent.slug.trim().length > 0 && typeof agent.buildRuntime === "function" && typeof agent.model === "string" && typeof agent.systemPrompt === "string";
12
+ }
13
+ function validateManifestAgent(value, filePath) {
14
+ if (!isManifestAgent(value)) throw new Error(`${filePath} must default-export defineAgent(...)`);
15
+ return value;
16
+ }
17
+ function normalizeWebhookEndpoint(endpoint) {
18
+ return endpoint.replace(/^\/+|\/+$/g, "").replace(/^triggers\/?/, "");
19
+ }
20
+ function agentRouteFromKey(key) {
21
+ return `/agents/${key}`;
22
+ }
23
+ function agentSessionsListPath(route) {
24
+ return `${route}/sessions`;
25
+ }
26
+ function agentSessionDetailPath(route) {
27
+ return `${route}/sessions/:sessionId`;
28
+ }
29
+ function workflowRouteFromKey(key) {
30
+ return `/workflows/${key}`;
31
+ }
32
+ function workflowRunsListPath(route) {
33
+ return `${route}/runs`;
34
+ }
35
+ function workflowRunDetailPath(route) {
36
+ return `${route}/runs/:runId`;
37
+ }
38
+ function triggerRunsListPath(attachmentKey) {
39
+ return `/triggers/${attachmentKey}/runs`;
40
+ }
41
+ function triggerRunDetailPath(attachmentKey) {
42
+ return `/triggers/${attachmentKey}/runs/:runId`;
43
+ }
44
+ function pollRouteFromKey(attachmentKey) {
45
+ return `/triggers/${attachmentKey}/poll`;
46
+ }
47
+ function pollGroupRouteFromId(pollId) {
48
+ return `/triggers/polls/${pollId}/run`;
49
+ }
50
+ function webhookRouteFromEndpoint(endpoint) {
51
+ const normalized = normalizeWebhookEndpoint(endpoint);
52
+ if (!normalized) throw new Error("Webhook endpoint must not be empty");
53
+ return `/triggers/${normalized}`;
54
+ }
55
+ async function discoverAgentEntries(agentsDir) {
56
+ const files = await discoverModuleFileEntries(agentsDir, {
57
+ nestedEntry: "agent",
58
+ duplicateLabel: "agent module file"
59
+ });
60
+ const entries = [];
61
+ for (const { filePath, moduleFile } of files) {
62
+ const agent = await importAgentDefinition(filePath);
63
+ entries.push({
64
+ key: agent.slug,
65
+ route: agentRouteFromKey(agent.slug),
66
+ filePath,
67
+ moduleFile
68
+ });
69
+ }
70
+ return entries;
71
+ }
72
+ async function importAgentDefinition(filePath) {
73
+ return validateManifestAgent((await import(pathToFileURL(filePath).href)).default, filePath);
74
+ }
75
+ const ACTION = Symbol.for("keystroke.action");
76
+ function isManifestAction(value) {
77
+ if (typeof value !== "object" || value === null) return false;
78
+ return ACTION in value && value[ACTION] === true;
79
+ }
80
+ function getManifestActionCredentialRequirements(action) {
81
+ return Array.isArray(action.credentials) ? action.credentials : void 0;
82
+ }
83
+ function countAgentCredentials(agent) {
84
+ const keys = /* @__PURE__ */ new Set();
85
+ for (const tool of agent.tools ?? []) {
86
+ const record = tool;
87
+ const requirements = isManifestAction(tool) ? getManifestActionCredentialRequirements(tool) : "credentials" in record && Array.isArray(record.credentials) ? record.credentials : void 0;
88
+ if (!requirements?.length) continue;
89
+ for (const requirement of normalizeCredentialList(requirements)) keys.add(requirement.key);
90
+ }
91
+ return keys.size;
92
+ }
93
+ const SKIP_DIRS = new Set([".git", "node_modules"]);
94
+ function toPosix$1(path) {
95
+ return path.split(sep).join("/");
96
+ }
97
+ function parseSkillFrontmatter(raw) {
98
+ const match = raw.match(/^---\s*\n([\s\S]*?)\n---/);
99
+ if (!match) return {};
100
+ const out = {};
101
+ for (const line of match[1]?.split("\n") ?? []) {
102
+ const trimmed = line.trim();
103
+ if (!trimmed || trimmed.startsWith("#")) continue;
104
+ const colon = trimmed.indexOf(":");
105
+ if (colon < 0) continue;
106
+ const key = trimmed.slice(0, colon).trim();
107
+ let value = trimmed.slice(colon + 1).trim();
108
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
109
+ if (key === "name") out.name = value;
110
+ else if (key === "description") out.description = value;
111
+ }
112
+ return out;
113
+ }
114
+ function walkSkillFiles(root, dir, out) {
115
+ for (const name of readdirSync(dir).sort()) {
116
+ const absolute = join(dir, name);
117
+ const stats = statSync(absolute);
118
+ if (stats.isDirectory()) {
119
+ if (SKIP_DIRS.has(name)) continue;
120
+ walkSkillFiles(root, absolute, out);
121
+ continue;
122
+ }
123
+ if (!stats.isFile() || name !== "SKILL.md") continue;
124
+ const moduleFile = toPosix$1(relative(root, absolute));
125
+ const slug = toPosix$1(relative(join(root, "src", "skills"), absolute)).replace(/\/?SKILL\.md$/, "");
126
+ if (!slug) continue;
127
+ const frontmatter = parseSkillFrontmatter(readFileSync(absolute, "utf8"));
128
+ out.push({
129
+ slug,
130
+ name: frontmatter.name,
131
+ description: frontmatter.description,
132
+ moduleFile
133
+ });
134
+ }
135
+ }
136
+ /** Discover skill metadata from src/skills SKILL.md files for the route manifest. */
137
+ function discoverSkillManifestEntries(projectRoot) {
138
+ const skillsDir = join(projectRoot, "src", "skills");
139
+ if (!statSync(skillsDir, { throwIfNoEntry: false })?.isDirectory()) return [];
140
+ const entries = [];
141
+ walkSkillFiles(projectRoot, skillsDir, entries);
142
+ return entries;
143
+ }
144
+ function schemaToJson(schema) {
145
+ return toJSONSchema(schema, { target: "openapi-3.0" });
146
+ }
147
+ function collectIntegrationKeys(integrations) {
148
+ return integrations.map((integration) => integration.key);
149
+ }
150
+ function serializeRouteManifest(manifest) {
151
+ return manifest.map((entry) => {
152
+ switch (entry.kind) {
153
+ case "health": return entry;
154
+ case "agent": return {
155
+ kind: entry.kind,
156
+ method: entry.method,
157
+ path: entry.path,
158
+ agentSlug: entry.agentSlug,
159
+ moduleFile: entry.moduleFile,
160
+ name: entry.name,
161
+ description: entry.description,
162
+ model: entry.model,
163
+ systemPrompt: entry.systemPrompt,
164
+ toolCount: entry.toolCount,
165
+ credentialCount: entry.credentialCount,
166
+ requestSchema: schemaToJson(entry.request),
167
+ responseSchema: schemaToJson(entry.response)
168
+ };
169
+ case "workflow": return {
170
+ kind: entry.kind,
171
+ method: entry.method,
172
+ path: entry.path,
173
+ workflowSlug: entry.workflowSlug,
174
+ workflowName: entry.workflowName,
175
+ description: entry.description,
176
+ subscribable: entry.subscribable,
177
+ moduleFile: entry.moduleFile,
178
+ requestSchema: schemaToJson(entry.request),
179
+ responseSchema: schemaToJson(entry.response)
180
+ };
181
+ case "trigger-webhook": return {
182
+ kind: entry.kind,
183
+ method: entry.method,
184
+ path: entry.path,
185
+ attachmentIds: entry.attachmentIds,
186
+ moduleFile: entry.moduleFile,
187
+ requestSchema: schemaToJson(entry.request),
188
+ attachmentSchemas: Object.fromEntries(Object.entries(entry.attachmentSchemas).map(([attachmentKey, schemas]) => [attachmentKey, {
189
+ requestSchema: schemaToJson(schemas.request),
190
+ ...schemas.filter ? { filterSchema: schemaToJson(schemas.filter) } : {}
191
+ }])),
192
+ responseSchema: schemaToJson(entry.response)
193
+ };
194
+ case "trigger-poll": return {
195
+ kind: entry.kind,
196
+ method: entry.method,
197
+ path: entry.path,
198
+ attachmentId: entry.attachmentId,
199
+ moduleFile: entry.moduleFile,
200
+ schedule: entry.schedule,
201
+ responseSchema: schemaToJson(entry.response)
202
+ };
203
+ case "trigger-poll-group": return {
204
+ kind: entry.kind,
205
+ method: entry.method,
206
+ path: entry.path,
207
+ pollId: entry.pollId,
208
+ attachmentIds: entry.attachmentIds,
209
+ moduleFile: entry.moduleFile,
210
+ schedule: entry.schedule,
211
+ responseSchema: schemaToJson(entry.response)
212
+ };
213
+ case "plugin": return {
214
+ kind: entry.kind,
215
+ method: entry.method,
216
+ path: entry.path,
217
+ plugin: entry.plugin
218
+ };
219
+ default: return entry;
220
+ }
221
+ });
222
+ }
223
+ function toStoredRouteManifest(manifest, options) {
224
+ return {
225
+ version: 1,
226
+ entries: serializeRouteManifest(manifest),
227
+ skills: options?.skills ?? [],
228
+ integrations: options?.integrations
229
+ };
230
+ }
231
+ function buildStoredRouteManifestFromContext(ctx) {
232
+ const integrations = ctx.options?.integrations ?? ctx.options?.plugins?.map((plugin) => ({
233
+ key: plugin.name,
234
+ mount: plugin.register
235
+ })) ?? [];
236
+ return toStoredRouteManifest(ctx.manifest, {
237
+ integrations: collectIntegrationKeys(integrations),
238
+ skills: ctx.skills ?? (ctx.projectRoot ? discoverSkillManifestEntries(ctx.projectRoot) : void 0) ?? []
239
+ });
240
+ }
241
+ function persistStoredRouteManifest(projectRoot, manifest) {
242
+ const path = join(projectRoot, ROUTE_MANIFEST_REL_PATH);
243
+ mkdirSync(dirname(path), { recursive: true });
244
+ writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
245
+ }
246
+ const WORKFLOW = Symbol.for("keystroke.workflow");
247
+ function isManifestWorkflow(value) {
248
+ if (typeof value !== "object" || value === null) return false;
249
+ if (!(WORKFLOW in value) || value[WORKFLOW] !== true) return false;
250
+ const workflow = value;
251
+ return typeof workflow.slug === "string" && workflow.slug.trim().length > 0 && workflow.input instanceof Object && "safeParse" in workflow.input && workflow.output instanceof Object && "safeParse" in workflow.output;
252
+ }
253
+ function validateManifestWorkflow(value, filePath) {
254
+ if (!isManifestWorkflow(value)) throw new Error(`${filePath} must default-export defineWorkflow(...)`);
255
+ return value;
256
+ }
257
+ const TRIGGER_ATTACHMENT = Symbol.for("keystroke.triggerAttachment");
258
+ const zodSchema = custom((value) => value instanceof ZodType, "must be a Zod schema");
259
+ const cronScheduleSchema = string().trim().min(1, "cron schedule must be a non-empty string");
260
+ const workflowSchema = custom((value) => isManifestWorkflow(value), "must be defineWorkflow(...)");
261
+ const agentSchema = custom((value) => typeof value === "object" && value !== null && typeof value.slug === "string" && value.slug.trim().length > 0 && typeof value.prompt === "function", "must be defineAgent(...)");
262
+ const promptSchema = union([string(), _function()]);
263
+ const baseSourceShape = {
264
+ key: string().trim().min(1),
265
+ attach: _function()
266
+ };
267
+ const triggerSourceSchema = discriminatedUnion("kind", [
268
+ object({
269
+ kind: literal("webhook"),
270
+ ...baseSourceShape,
271
+ endpoint: string().min(1),
272
+ request: zodSchema,
273
+ filter: zodSchema.optional(),
274
+ passes: _function()
275
+ }),
276
+ object({
277
+ kind: literal("cron"),
278
+ ...baseSourceShape,
279
+ schedule: cronScheduleSchema
280
+ }),
281
+ object({
282
+ kind: literal("poll"),
283
+ ...baseSourceShape,
284
+ id: string().optional(),
285
+ schedule: cronScheduleSchema,
286
+ run: _function(),
287
+ filters: array(_function()),
288
+ passes: _function()
289
+ })
290
+ ]);
291
+ const triggerAttachmentCoreSchema = discriminatedUnion("target", [object({
292
+ key: string().trim().min(1),
293
+ source: triggerSourceSchema,
294
+ target: literal("workflow"),
295
+ workflow: workflowSchema,
296
+ transform: _function().optional()
297
+ }), object({
298
+ key: string().trim().min(1),
299
+ source: triggerSourceSchema,
300
+ target: literal("agent"),
301
+ agent: agentSchema,
302
+ prompt: promptSchema
303
+ })]);
304
+ function isManifestTriggerAttachment(value) {
305
+ if (typeof value !== "object" || value === null) return false;
306
+ if (!(TRIGGER_ATTACHMENT in value) || value[TRIGGER_ATTACHMENT] !== true) return false;
307
+ return triggerAttachmentCoreSchema.safeParse(value).success;
308
+ }
309
+ function validateManifestTriggerAttachment(value, filePath) {
310
+ if (!isManifestTriggerAttachment(value)) throw new Error(`${filePath} must default-export a trigger attachment from .attach({ workflow }) or .attach({ agent, prompt })`);
311
+ return value;
312
+ }
313
+ function shouldDiscoverTriggerFile(triggersDir, filePath) {
314
+ return !relative(triggersDir, filePath).split(sep).includes("sources");
315
+ }
316
+ async function discoverTriggerAttachments(triggersDir) {
317
+ const files = await discoverModuleFileEntries(triggersDir, {
318
+ nestedEntry: "trigger",
319
+ duplicateLabel: "trigger module file",
320
+ shouldDiscoverFile: (filePath) => shouldDiscoverTriggerFile(triggersDir, filePath)
321
+ });
322
+ const attachments = [];
323
+ for (const { filePath, moduleFile } of files) {
324
+ const attachment = await importTriggerAttachment(filePath);
325
+ attachments.push({
326
+ key: attachment.key,
327
+ filePath,
328
+ moduleFile,
329
+ attachment
330
+ });
331
+ }
332
+ return attachments;
333
+ }
334
+ function validateImportedTriggerAttachment(def, filePath) {
335
+ return validateManifestTriggerAttachment(def, filePath);
336
+ }
337
+ async function importTriggerAttachment(filePath) {
338
+ return validateImportedTriggerAttachment((await import(pathToFileURL(filePath).href)).default, filePath);
339
+ }
340
+ function pollGroupId(discovered) {
341
+ const source = discovered.attachment.source;
342
+ if (source.kind !== "poll") throw new Error(`Attachment "${discovered.key}" is not a poll trigger`);
343
+ return source.id ?? source.key;
344
+ }
345
+ function buildPollGroups(attachments) {
346
+ const byId = /* @__PURE__ */ new Map();
347
+ for (const discovered of attachments) {
348
+ if (discovered.attachment.source.kind !== "poll") continue;
349
+ const id = pollGroupId(discovered);
350
+ const group = byId.get(id) ?? [];
351
+ group.push(discovered);
352
+ byId.set(id, group);
353
+ }
354
+ return [...byId.entries()].map(([id, groupAttachments]) => ({
355
+ id,
356
+ attachments: groupAttachments
357
+ }));
358
+ }
359
+ function buildWebhookBindingsByRoute(attachments, handlerOptionsFor) {
360
+ const webhookBindingsByRoute = /* @__PURE__ */ new Map();
361
+ for (const discovered of attachments) {
362
+ const source = discovered.attachment.source;
363
+ if (source.kind !== "webhook") continue;
364
+ const route = webhookRouteFromEndpoint(source.endpoint);
365
+ const bindings = webhookBindingsByRoute.get(route) ?? [];
366
+ bindings.push({
367
+ discovered,
368
+ options: handlerOptionsFor(discovered.key)
369
+ });
370
+ webhookBindingsByRoute.set(route, bindings);
371
+ }
372
+ return webhookBindingsByRoute;
373
+ }
374
+ function webhookSchemaEntriesFromBindings(bindings) {
375
+ return bindings.flatMap((binding) => {
376
+ const source = binding.discovered.attachment.source;
377
+ if (source.kind !== "webhook") return [];
378
+ return [{
379
+ attachmentKey: binding.discovered.key,
380
+ request: source.request,
381
+ ...source.filter ? { filter: source.filter } : {}
382
+ }];
383
+ });
384
+ }
385
+ function webhookMatchSchemaForBindings(bindings) {
386
+ const schemas = bindings.flatMap((binding) => {
387
+ const source = binding.discovered.attachment.source;
388
+ return source.kind === "webhook" ? [source.request] : [];
389
+ });
390
+ if (schemas.length === 0) throw new Error("Webhook bindings require at least one webhook source");
391
+ if (schemas.length === 1) return schemas[0];
392
+ return union(schemas);
393
+ }
394
+ function webhookManifestAttachmentSchemasFromBindings(bindings) {
395
+ return Object.fromEntries(webhookSchemaEntriesFromBindings(bindings).map((entry) => [entry.attachmentKey, {
396
+ request: entry.request,
397
+ ...entry.filter ? { filter: entry.filter } : {}
398
+ }]));
399
+ }
400
+ async function discoverWorkflowEntries(workflowsDir) {
401
+ const files = await discoverModuleFileEntries(workflowsDir, {
402
+ nestedEntry: "workflow",
403
+ duplicateLabel: "workflow module file"
404
+ });
405
+ const entries = [];
406
+ for (const { filePath, moduleFile } of files) {
407
+ const definition = await importWorkflowDefinition(filePath);
408
+ entries.push({
409
+ key: definition.slug,
410
+ route: workflowRouteFromKey(definition.slug),
411
+ filePath,
412
+ moduleFile
413
+ });
414
+ }
415
+ return entries;
416
+ }
417
+ function validateImportedWorkflowDefinition(def, filePath) {
418
+ return validateManifestWorkflow(def, filePath);
419
+ }
420
+ async function importWorkflowDefinition(filePath) {
421
+ return validateImportedWorkflowDefinition((await import(pathToFileURL(filePath).href)).default, filePath);
422
+ }
423
+ async function discoverWorkflows(workflowsDir) {
424
+ const entries = await discoverWorkflowEntries(workflowsDir);
425
+ const workflows = [];
426
+ for (const entry of entries) {
427
+ const definition = await importWorkflowDefinition(entry.filePath);
428
+ workflows.push({
429
+ key: definition.slug,
430
+ route: workflowRouteFromKey(definition.slug),
431
+ filePath: entry.filePath,
432
+ definition
433
+ });
434
+ }
435
+ return workflows;
436
+ }
437
+ function resolveDistModuleDirs(projectRoot) {
438
+ const distBase = join(projectRoot, "dist");
439
+ if (!existsSync(distBase)) throw new Error(`Build output missing at ${distBase}. Run keystroke build before emitting the route manifest.`);
440
+ return {
441
+ agentsDir: join(distBase, "agents"),
442
+ workflowsDir: join(distBase, "workflows"),
443
+ triggersDir: join(distBase, "triggers")
444
+ };
445
+ }
446
+ function toPosix(path) {
447
+ return path.split(sep).join("/");
448
+ }
449
+ /**
450
+ * Resolve manifest moduleFile values to project-root-relative source paths.
451
+ *
452
+ * Discovery runs over compiled `dist/` modules, so the raw moduleFile is a
453
+ * dist-relative `.mjs` path. Dist entries are named by their source entry id
454
+ * (`team/escalation.mjs` ← `src/agents/team/escalation/agent.ts`), so walking
455
+ * the matching source dir by the same id rules recovers the source path. Falls
456
+ * back to the dist-relative value when no source file matches (e.g. building
457
+ * a dist-only artifact with no `src/`).
458
+ */
459
+ var SourceModuleFileResolver = class {
460
+ projectRoot;
461
+ mapsByKind = /* @__PURE__ */ new Map();
462
+ constructor(projectRoot) {
463
+ this.projectRoot = projectRoot;
464
+ }
465
+ async sourceMapFor(kindDir, nestedEntry) {
466
+ const existing = this.mapsByKind.get(kindDir);
467
+ if (existing) return existing;
468
+ const map = /* @__PURE__ */ new Map();
469
+ const sourceDir = join(this.projectRoot, "src", kindDir);
470
+ for (const filePath of await walkTypeScriptFiles(sourceDir)) {
471
+ const id = entryIdFromFile(sourceDir, filePath, { nestedEntry });
472
+ if (id) map.set(id, toPosix(relative(this.projectRoot, filePath)));
473
+ }
474
+ this.mapsByKind.set(kindDir, map);
475
+ return map;
476
+ }
477
+ async resolve(kindDir, nestedEntry, distDir, distFilePath) {
478
+ const fallback = toPosix(relative(distDir, distFilePath));
479
+ const id = entryIdFromFile(distDir, distFilePath, { nestedEntry });
480
+ if (!id) return fallback;
481
+ return (await this.sourceMapFor(kindDir, nestedEntry)).get(id) ?? fallback;
482
+ }
483
+ };
484
+ /** Build a stored route manifest from compiled dist/ modules without starting a server. */
485
+ async function buildStoredRouteManifestForProject(projectRoot) {
486
+ const previousRoot = process.env.KEYSTROKE_ROOT;
487
+ process.env.KEYSTROKE_ROOT = projectRoot;
488
+ try {
489
+ const dirs = resolveDistModuleDirs(projectRoot);
490
+ const sourcePaths = new SourceModuleFileResolver(projectRoot);
491
+ const manifest = [{
492
+ kind: "health",
493
+ method: "GET",
494
+ path: "/health"
495
+ }];
496
+ const agentEntries = await discoverAgentEntries(dirs.agentsDir);
497
+ for (const entry of agentEntries) {
498
+ const agent = await importAgentDefinition(entry.filePath);
499
+ const moduleFile = await sourcePaths.resolve("agents", "agent", dirs.agentsDir, entry.filePath);
500
+ manifest.push({
501
+ kind: "agent",
502
+ method: "POST",
503
+ path: entry.route,
504
+ agentSlug: agent.slug,
505
+ moduleFile,
506
+ name: agent.name,
507
+ description: agent.description,
508
+ model: agent.model,
509
+ systemPrompt: agent.systemPrompt,
510
+ toolCount: agent.tools?.length ?? 0,
511
+ credentialCount: countAgentCredentials(agent),
512
+ request: PromptInputSchema,
513
+ response: PromptResponseSchema
514
+ });
515
+ manifest.push({
516
+ kind: "agent-sessions-list",
517
+ method: "GET",
518
+ path: agentSessionsListPath(entry.route),
519
+ agentSlug: entry.key,
520
+ moduleFile
521
+ });
522
+ manifest.push({
523
+ kind: "agent-session-detail",
524
+ method: "GET",
525
+ path: agentSessionDetailPath(entry.route),
526
+ agentSlug: entry.key,
527
+ moduleFile
528
+ });
529
+ }
530
+ const workflows = await discoverWorkflows(dirs.workflowsDir);
531
+ for (const workflow of workflows) {
532
+ const route = workflowRouteFromKey(workflow.key);
533
+ const moduleFile = await sourcePaths.resolve("workflows", "workflow", dirs.workflowsDir, workflow.filePath);
534
+ manifest.push({
535
+ kind: "workflow",
536
+ method: "POST",
537
+ path: route,
538
+ workflowSlug: workflow.definition.slug,
539
+ workflowName: workflow.definition.name,
540
+ description: workflow.definition.description,
541
+ subscribable: workflow.definition.subscription?.mode === "subscribable",
542
+ moduleFile,
543
+ request: workflow.definition.input,
544
+ response: workflow.definition.output
545
+ });
546
+ manifest.push({
547
+ kind: "workflow-runs-list",
548
+ method: "GET",
549
+ path: workflowRunsListPath(route),
550
+ workflowSlug: workflow.definition.slug,
551
+ workflowName: workflow.definition.name,
552
+ moduleFile
553
+ });
554
+ manifest.push({
555
+ kind: "workflow-run-detail",
556
+ method: "GET",
557
+ path: workflowRunDetailPath(route),
558
+ workflowSlug: workflow.definition.slug,
559
+ workflowName: workflow.definition.name,
560
+ moduleFile
561
+ });
562
+ }
563
+ const attachments = await discoverTriggerAttachments(dirs.triggersDir);
564
+ const discoveredByKey = new Map(attachments.map((attachment) => [attachment.key, attachment]));
565
+ const pollGroups = buildPollGroups(attachments);
566
+ for (const discovered of discoveredByKey.values()) {
567
+ const source = discovered.attachment.source;
568
+ const moduleFile = await sourcePaths.resolve("triggers", "trigger", dirs.triggersDir, discovered.filePath);
569
+ if (source.kind === "cron") {
570
+ manifest.push({
571
+ kind: "cron-schedule",
572
+ attachmentId: discovered.key,
573
+ moduleFile,
574
+ schedule: source.schedule
575
+ });
576
+ continue;
577
+ }
578
+ if (source.kind === "poll") {
579
+ const route = pollRouteFromKey(source.key);
580
+ manifest.push({
581
+ kind: "trigger-poll",
582
+ method: "POST",
583
+ path: route,
584
+ attachmentId: discovered.key,
585
+ moduleFile,
586
+ schedule: source.schedule,
587
+ response: PromptResponseSchema
588
+ });
589
+ manifest.push({
590
+ kind: "trigger-runs-list",
591
+ method: "GET",
592
+ path: triggerRunsListPath(discovered.key),
593
+ attachmentId: discovered.key,
594
+ moduleFile
595
+ });
596
+ manifest.push({
597
+ kind: "trigger-run-detail",
598
+ method: "GET",
599
+ path: triggerRunDetailPath(discovered.key),
600
+ attachmentId: discovered.key,
601
+ moduleFile
602
+ });
603
+ continue;
604
+ }
605
+ if (source.kind === "webhook") {
606
+ const route = webhookRouteFromEndpoint(source.endpoint);
607
+ const bindings = buildWebhookBindingsByRoute(discoveredByKey.values(), () => ({
608
+ execution: { attachmentKey: discovered.key },
609
+ attachmentKey: discovered.key
610
+ })).get(route) ?? [{
611
+ discovered,
612
+ options: { attachmentKey: discovered.key }
613
+ }];
614
+ manifest.push({
615
+ kind: "trigger-webhook",
616
+ method: "POST",
617
+ path: route,
618
+ attachmentIds: bindings.map(({ discovered: row }) => row.key),
619
+ moduleFile,
620
+ request: webhookMatchSchemaForBindings(bindings),
621
+ attachmentSchemas: webhookManifestAttachmentSchemasFromBindings(bindings),
622
+ response: PromptResponseSchema
623
+ });
624
+ for (const { discovered: row } of bindings) {
625
+ const rowModuleFile = await sourcePaths.resolve("triggers", "trigger", dirs.triggersDir, row.filePath);
626
+ manifest.push({
627
+ kind: "trigger-runs-list",
628
+ method: "GET",
629
+ path: triggerRunsListPath(row.key),
630
+ attachmentId: row.key,
631
+ moduleFile: rowModuleFile
632
+ });
633
+ manifest.push({
634
+ kind: "trigger-run-detail",
635
+ method: "GET",
636
+ path: triggerRunDetailPath(row.key),
637
+ attachmentId: row.key,
638
+ moduleFile: rowModuleFile
639
+ });
640
+ }
641
+ }
642
+ }
643
+ for (const group of pollGroups) {
644
+ if (group.attachments.length <= 1) continue;
645
+ const first = group.attachments[0];
646
+ const source = first.attachment.source;
647
+ manifest.push({
648
+ kind: "trigger-poll-group",
649
+ method: "POST",
650
+ path: pollGroupRouteFromId(group.id),
651
+ pollId: group.id,
652
+ attachmentIds: group.attachments.map((attachment) => attachment.key),
653
+ moduleFile: await sourcePaths.resolve("triggers", "trigger", dirs.triggersDir, first.filePath),
654
+ schedule: source.kind === "poll" ? source.schedule : "",
655
+ response: PromptResponseSchema
656
+ });
657
+ }
658
+ return buildStoredRouteManifestFromContext({
659
+ manifest,
660
+ options: {},
661
+ projectRoot,
662
+ skills: discoverSkillManifestEntries(projectRoot)
663
+ });
664
+ } finally {
665
+ if (previousRoot === void 0) delete process.env.KEYSTROKE_ROOT;
666
+ else process.env.KEYSTROKE_ROOT = previousRoot;
667
+ }
668
+ }
669
+ /** Write `dist/.keystroke/route-manifest.json` for the project. */
670
+ async function emitStoredRouteManifestForProject(projectRoot) {
671
+ persistStoredRouteManifest(projectRoot, await buildStoredRouteManifestForProject(projectRoot));
672
+ }
673
+ //#endregion
674
+ export { emitStoredRouteManifestForProject };
675
+
676
+ //# sourceMappingURL=dist-Efdscyea.mjs.map