@crossdelta/platform-sdk 0.19.0 → 0.19.3

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 (46) hide show
  1. package/README.md +27 -5
  2. package/bin/chunk-634PL24Z.mjs +20 -0
  3. package/bin/cli.mjs +604 -0
  4. package/bin/config-CKQHYOF4.mjs +2 -0
  5. package/bin/docs/generators/code-style.md +79 -0
  6. package/bin/docs/generators/natural-language.md +117 -0
  7. package/bin/docs/generators/service.md +129 -60
  8. package/bin/templates/hono-microservice/Dockerfile.hbs +3 -1
  9. package/bin/templates/hono-microservice/src/config/env.ts.hbs +3 -0
  10. package/bin/templates/nest-microservice/Dockerfile.hbs +6 -2
  11. package/bin/templates/nest-microservice/src/config/env.ts.hbs +17 -0
  12. package/bin/templates/nest-microservice/src/main.ts.hbs +2 -1
  13. package/bin/templates/workspace/.github/actions/prepare-build-context/action.yml +58 -6
  14. package/bin/templates/workspace/.github/workflows/build-and-deploy.yml.hbs +25 -3
  15. package/bin/templates/workspace/.github/workflows/publish-packages.yml +6 -8
  16. package/bin/templates/workspace/biome.json.hbs +4 -1
  17. package/bin/templates/workspace/infra/package.json.hbs +2 -2
  18. package/bin/templates/workspace/package.json.hbs +1 -0
  19. package/bin/templates/workspace/packages/contracts/README.md.hbs +5 -5
  20. package/bin/templates/workspace/packages/contracts/package.json.hbs +15 -6
  21. package/bin/templates/workspace/packages/contracts/src/index.ts +1 -1
  22. package/bin/templates/workspace/packages/contracts/tsconfig.json.hbs +6 -1
  23. package/bin/templates/workspace/turbo.json +8 -11
  24. package/bin/templates/workspace/turbo.json.hbs +6 -5
  25. package/dist/facade.d.mts +840 -0
  26. package/dist/facade.d.ts +840 -0
  27. package/dist/facade.js +2294 -0
  28. package/dist/facade.js.map +1 -0
  29. package/dist/facade.mjs +2221 -0
  30. package/dist/facade.mjs.map +1 -0
  31. package/dist/plugin-types-DQOv97Zh.d.mts +180 -0
  32. package/dist/plugin-types-DQOv97Zh.d.ts +180 -0
  33. package/dist/plugin-types.d.mts +1 -0
  34. package/dist/plugin-types.d.ts +1 -0
  35. package/dist/plugin-types.js +19 -0
  36. package/dist/plugin-types.js.map +1 -0
  37. package/dist/plugin-types.mjs +1 -0
  38. package/dist/plugin-types.mjs.map +1 -0
  39. package/dist/plugin.d.mts +31 -0
  40. package/dist/plugin.d.ts +31 -0
  41. package/dist/plugin.js +105 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/dist/plugin.mjs +75 -0
  44. package/dist/plugin.mjs.map +1 -0
  45. package/package.json +118 -99
  46. package/bin/cli.js +0 -540
@@ -0,0 +1,2221 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
6
+ // cli/src/config/constants.ts
7
+ var CLI_VERSION, LOCK_FILES;
8
+ var init_constants = __esm({
9
+ "cli/src/config/constants.ts"() {
10
+ "use strict";
11
+ CLI_VERSION = process.env.npm_package_version || "0.0.0";
12
+ LOCK_FILES = [
13
+ "bun.lock",
14
+ "bun.lockb",
15
+ "package-lock.json",
16
+ "yarn.lock",
17
+ "pnpm-lock.yaml"
18
+ ];
19
+ }
20
+ });
21
+
22
+ // cli/src/utils/ascii-chart.ts
23
+ import chalk from "chalk";
24
+ var init_ascii_chart = __esm({
25
+ "cli/src/utils/ascii-chart.ts"() {
26
+ "use strict";
27
+ }
28
+ });
29
+
30
+ // cli/src/core/errors/types.ts
31
+ var init_types = __esm({
32
+ "cli/src/core/errors/types.ts"() {
33
+ "use strict";
34
+ }
35
+ });
36
+
37
+ // cli/src/core/errors/cli-error.ts
38
+ var init_cli_error = __esm({
39
+ "cli/src/core/errors/cli-error.ts"() {
40
+ "use strict";
41
+ init_types();
42
+ }
43
+ });
44
+
45
+ // cli/src/core/errors/handler.ts
46
+ import chalk2 from "chalk";
47
+ var init_handler = __esm({
48
+ "cli/src/core/errors/handler.ts"() {
49
+ "use strict";
50
+ init_cli_error();
51
+ init_types();
52
+ }
53
+ });
54
+
55
+ // cli/src/core/errors/index.ts
56
+ var init_errors = __esm({
57
+ "cli/src/core/errors/index.ts"() {
58
+ "use strict";
59
+ init_cli_error();
60
+ init_handler();
61
+ init_types();
62
+ }
63
+ });
64
+
65
+ // cli/src/core/filesystem/watcher.ts
66
+ import { existsSync, unlinkSync, writeFileSync } from "fs";
67
+ import { join } from "path";
68
+ import chokidar from "chokidar";
69
+ var init_watcher = __esm({
70
+ "cli/src/core/filesystem/watcher.ts"() {
71
+ "use strict";
72
+ init_utils();
73
+ init_config();
74
+ }
75
+ });
76
+
77
+ // cli/src/utils/create-workflow-command.ts
78
+ import chalk3 from "chalk";
79
+ import { Command } from "commander";
80
+ import { Listr } from "listr2";
81
+ var init_create_workflow_command = __esm({
82
+ "cli/src/utils/create-workflow-command.ts"() {
83
+ "use strict";
84
+ init_errors();
85
+ init_watcher();
86
+ init_config();
87
+ }
88
+ });
89
+
90
+ // cli/src/utils/debounce.ts
91
+ var init_debounce = __esm({
92
+ "cli/src/utils/debounce.ts"() {
93
+ "use strict";
94
+ }
95
+ });
96
+
97
+ // cli/src/utils/deep.ts
98
+ var init_deep = __esm({
99
+ "cli/src/utils/deep.ts"() {
100
+ "use strict";
101
+ }
102
+ });
103
+
104
+ // cli/src/utils/process.ts
105
+ import { execaSync } from "execa";
106
+ var init_process = __esm({
107
+ "cli/src/utils/process.ts"() {
108
+ "use strict";
109
+ }
110
+ });
111
+
112
+ // cli/src/utils/environment.ts
113
+ var init_environment = __esm({
114
+ "cli/src/utils/environment.ts"() {
115
+ "use strict";
116
+ init_process();
117
+ }
118
+ });
119
+
120
+ // cli/src/utils/fs.ts
121
+ import { readdirSync } from "fs";
122
+ import { dirname, isAbsolute, join as join2, normalize, resolve, sep } from "path";
123
+ import { fileURLToPath } from "url";
124
+ import { packageUpSync } from "package-up";
125
+ import { rimraf } from "rimraf";
126
+ function getCliRoot() {
127
+ const packageJsonPath = packageUpSync({ cwd: dirname(getModuleFilename()) });
128
+ if (!packageJsonPath) {
129
+ throw new Error("Could not find package.json");
130
+ }
131
+ return dirname(packageJsonPath);
132
+ }
133
+ var getModuleFilename;
134
+ var init_fs = __esm({
135
+ "cli/src/utils/fs.ts"() {
136
+ "use strict";
137
+ init_errors();
138
+ init_config();
139
+ getModuleFilename = () => {
140
+ if (typeof import.meta?.url === "string") {
141
+ return fileURLToPath(import.meta.url);
142
+ }
143
+ return process.cwd();
144
+ };
145
+ }
146
+ });
147
+
148
+ // cli/src/utils/logger.ts
149
+ import chalk4 from "chalk";
150
+ var init_logger = __esm({
151
+ "cli/src/utils/logger.ts"() {
152
+ "use strict";
153
+ }
154
+ });
155
+
156
+ // cli/src/utils/merge-contexts.ts
157
+ var init_merge_contexts = __esm({
158
+ "cli/src/utils/merge-contexts.ts"() {
159
+ "use strict";
160
+ }
161
+ });
162
+
163
+ // cli/src/utils/tasks.ts
164
+ import chalk5 from "chalk";
165
+ var init_tasks = __esm({
166
+ "cli/src/utils/tasks.ts"() {
167
+ "use strict";
168
+ }
169
+ });
170
+
171
+ // cli/src/utils/index.ts
172
+ var init_utils = __esm({
173
+ "cli/src/utils/index.ts"() {
174
+ "use strict";
175
+ init_ascii_chart();
176
+ init_create_workflow_command();
177
+ init_debounce();
178
+ init_deep();
179
+ init_environment();
180
+ init_fs();
181
+ init_logger();
182
+ init_merge_contexts();
183
+ init_process();
184
+ init_tasks();
185
+ }
186
+ });
187
+
188
+ // cli/src/core/packages/config.ts
189
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
190
+ import { join as join3, resolve as resolve2 } from "path";
191
+ var readJsonSync, readJSONSync, DEFAULT_WORKSPACE_PATHS, parseJsonFile, extractPathValue, getDirName, getParentDir, hasWorkspaceIndicators, isWorkspaceRoot, findWorkspaceRootDir, findWorkspaceRoot, getWorkspacePackageJson, getWorkspacePathsConfig, getContractsConfig, getRootPackageScope, readPackageConfig, _pkgJson, getPkgJson, pkgJson, discoverAvailableServices;
192
+ var init_config = __esm({
193
+ "cli/src/core/packages/config.ts"() {
194
+ "use strict";
195
+ init_constants();
196
+ init_utils();
197
+ readJsonSync = (path) => JSON.parse(readFileSync(path, "utf-8"));
198
+ readJSONSync = readJsonSync;
199
+ DEFAULT_WORKSPACE_PATHS = {
200
+ services: "services",
201
+ apps: "apps",
202
+ packages: "packages",
203
+ contracts: "packages/contracts"
204
+ };
205
+ parseJsonFile = (path) => {
206
+ if (!existsSync2(path)) return null;
207
+ try {
208
+ return JSON.parse(readFileSync(path, "utf-8"));
209
+ } catch {
210
+ return null;
211
+ }
212
+ };
213
+ extractPathValue = (pathConfig, defaultValue) => {
214
+ if (typeof pathConfig === "string") return pathConfig;
215
+ if (typeof pathConfig === "object" && pathConfig !== null && "path" in pathConfig) {
216
+ return pathConfig.path;
217
+ }
218
+ return defaultValue;
219
+ };
220
+ getDirName = (path) => path.split("/").pop() || "workspace";
221
+ getParentDir = (dir) => {
222
+ const parent = dir.slice(0, dir.lastIndexOf("/"));
223
+ return parent === dir ? null : parent;
224
+ };
225
+ hasWorkspaceIndicators = (pkg, dir) => {
226
+ if (!pkg.workspaces) return false;
227
+ if (pkg.pf) return true;
228
+ if (existsSync2(join3(dir, "turbo.json"))) return true;
229
+ const hasLockFile = LOCK_FILES.some((f) => existsSync2(join3(dir, f)));
230
+ const hasInfraDir = existsSync2(join3(dir, "infra"));
231
+ return hasLockFile && hasInfraDir;
232
+ };
233
+ isWorkspaceRoot = (dir) => {
234
+ const pkg = parseJsonFile(join3(dir, "package.json"));
235
+ return pkg ? hasWorkspaceIndicators(pkg, dir) : false;
236
+ };
237
+ findWorkspaceRootDir = (startDir) => {
238
+ let dir = startDir;
239
+ while (dir) {
240
+ if (isWorkspaceRoot(dir)) return dir;
241
+ dir = getParentDir(dir);
242
+ }
243
+ return null;
244
+ };
245
+ findWorkspaceRoot = () => {
246
+ const root = findWorkspaceRootDir(process.cwd());
247
+ if (!root) {
248
+ throw new Error(
249
+ `
250
+ \x1B[31m\u2716\x1B[0m Not in a workspace directory
251
+
252
+ Current directory: ${process.cwd()}
253
+
254
+ This command must be run from within a workspace created with \x1B[36mpf new workspace\x1B[0m
255
+
256
+ To create a new workspace, run:
257
+
258
+ \x1B[36mpf new workspace my-platform\x1B[0m
259
+ `
260
+ );
261
+ }
262
+ return root;
263
+ };
264
+ getWorkspacePackageJson = (workspaceRoot) => {
265
+ const root = workspaceRoot ?? findWorkspaceRoot();
266
+ return parseJsonFile(join3(root, "package.json"));
267
+ };
268
+ getWorkspacePathsConfig = (workspaceRoot) => {
269
+ const pkg = getWorkspacePackageJson(workspaceRoot);
270
+ if (!pkg?.pf?.paths) return DEFAULT_WORKSPACE_PATHS;
271
+ const { paths } = pkg.pf;
272
+ return {
273
+ services: extractPathValue(paths.services, DEFAULT_WORKSPACE_PATHS.services),
274
+ apps: extractPathValue(paths.apps, DEFAULT_WORKSPACE_PATHS.apps),
275
+ packages: extractPathValue(paths.packages, DEFAULT_WORKSPACE_PATHS.packages),
276
+ contracts: extractPathValue(paths.contracts, DEFAULT_WORKSPACE_PATHS.contracts)
277
+ };
278
+ };
279
+ getContractsConfig = (workspaceRoot) => {
280
+ const root = workspaceRoot ?? findWorkspaceRoot();
281
+ const paths = getWorkspacePathsConfig(root);
282
+ const contractsPath = join3(root, paths.contracts);
283
+ const contractsPkg = parseJsonFile(join3(contractsPath, "package.json"));
284
+ const packageName = contractsPkg?.name ?? `${getRootPackageScope()}/contracts`;
285
+ return {
286
+ packagePath: contractsPath,
287
+ eventsPath: join3(contractsPath, "src", "events"),
288
+ indexPath: join3(contractsPath, "src", "index.ts"),
289
+ relativePath: paths.contracts,
290
+ packageName
291
+ };
292
+ };
293
+ getRootPackageScope = () => {
294
+ const rootPath = findWorkspaceRoot();
295
+ const pkg = parseJsonFile(join3(rootPath, "package.json"));
296
+ const fallbackScope = `@${getDirName(rootPath)}`;
297
+ if (!pkg?.name) return fallbackScope;
298
+ if (pkg.name.startsWith("@")) {
299
+ return pkg.name.split("/")[0];
300
+ }
301
+ return `@${pkg.name}`;
302
+ };
303
+ readPackageConfig = (relativePath) => {
304
+ const root = getCliRoot();
305
+ return readJSONSync(resolve2(root, relativePath));
306
+ };
307
+ _pkgJson = null;
308
+ getPkgJson = () => {
309
+ if (!_pkgJson) {
310
+ _pkgJson = readPackageConfig("package.json");
311
+ }
312
+ return _pkgJson;
313
+ };
314
+ pkgJson = new Proxy({}, {
315
+ get: (_, prop) => getPkgJson()[prop]
316
+ });
317
+ discoverAvailableServices = (workspaceRoot) => {
318
+ const root = workspaceRoot ?? findWorkspaceRoot();
319
+ const paths = getWorkspacePathsConfig(root);
320
+ const servicesDir = join3(root, paths.services);
321
+ if (!existsSync2(servicesDir)) return [];
322
+ try {
323
+ const entries = readdirSync2(servicesDir, { withFileTypes: true });
324
+ return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => `${paths.services}/${entry.name}`).sort();
325
+ } catch {
326
+ return [];
327
+ }
328
+ };
329
+ }
330
+ });
331
+
332
+ // cli/src/core/facade/index.ts
333
+ import { readdir } from "fs/promises";
334
+ import { dirname as dirname5, join as join13 } from "path";
335
+ import { fileURLToPath as fileURLToPath3 } from "url";
336
+ import { runFlow as flowcoreRunFlow } from "@crossdelta/flowcore";
337
+
338
+ // cli/src/core/plugins/loader.ts
339
+ var defaultLogger = {
340
+ debug: () => {
341
+ },
342
+ info: console.log,
343
+ warn: console.warn,
344
+ error: console.error
345
+ };
346
+ var isObject = (val) => val !== null && typeof val === "object";
347
+ var isNonEmptyString = (val) => typeof val === "string" && val.length > 0;
348
+ var isString = (val) => typeof val === "string";
349
+ var isFunction = (val) => typeof val === "function";
350
+ var validateCommand = (cmd, index) => {
351
+ if (!isObject(cmd)) return [`commands[${index}]: must be an object`];
352
+ return [
353
+ !isNonEmptyString(cmd.name) && `commands[${index}]: missing or invalid 'name'`,
354
+ !isString(cmd.description) && `commands[${index}]: missing or invalid 'description'`,
355
+ !Array.isArray(cmd.args) && `commands[${index}]: missing 'args' array`,
356
+ !Array.isArray(cmd.options) && `commands[${index}]: missing 'options' array`,
357
+ !isFunction(cmd.run) && `commands[${index}]: missing 'run' function`
358
+ ].filter((e) => e !== false);
359
+ };
360
+ var validatePluginStrict = (obj) => {
361
+ if (!isObject(obj)) return ["Plugin must be an object"];
362
+ const baseErrors = [
363
+ !isNonEmptyString(obj.name) && "Missing or invalid 'name'",
364
+ !isString(obj.version) && "Missing or invalid 'version'",
365
+ !Array.isArray(obj.commands) && "Missing 'commands' array",
366
+ !Array.isArray(obj.flows) && "Missing 'flows' array",
367
+ !isFunction(obj.setup) && "Missing 'setup' function"
368
+ ].filter((e) => e !== false);
369
+ const commandErrors = Array.isArray(obj.commands) ? obj.commands.flatMap((cmd, i) => validateCommand(cmd, i)) : [];
370
+ return [...baseErrors, ...commandErrors];
371
+ };
372
+ var createPluginContext = (workspace, logger = defaultLogger) => ({
373
+ workspace,
374
+ logger
375
+ });
376
+ var loadPluginFromModule = async (moduleName, context) => {
377
+ const { logger } = context;
378
+ logger.debug(`Loading plugin from module: ${moduleName}`);
379
+ const mod = await import(moduleName);
380
+ const createPlugin = mod.createPfPlugin || mod.default?.createPfPlugin;
381
+ if (!isFunction(createPlugin)) {
382
+ throw new Error(`Module ${moduleName} does not export createPfPlugin`);
383
+ }
384
+ const plugin = createPlugin({
385
+ contractsPath: context.workspace.contracts.path,
386
+ contractsPackage: context.workspace.contracts.packageName,
387
+ availableServices: context.workspace.availableServices
388
+ });
389
+ const errors = validatePluginStrict(plugin);
390
+ if (errors.length > 0) {
391
+ throw new Error(`Plugin from ${moduleName} does not conform to PfPlugin interface:
392
+ - ${errors.join("\n - ")}`);
393
+ }
394
+ const validatedPlugin = plugin;
395
+ logger.debug(`Loaded plugin: ${validatedPlugin.name} v${validatedPlugin.version}`);
396
+ await validatedPlugin.setup(context);
397
+ return {
398
+ plugin: validatedPlugin,
399
+ source: moduleName
400
+ };
401
+ };
402
+
403
+ // cli/src/core/facade/index.ts
404
+ import { deriveEventNames as deriveEventNames4 } from "@crossdelta/cloudevents";
405
+
406
+ // cli/src/core/capabilities/config.ts
407
+ import { z } from "zod";
408
+ var ProviderMetaSchema = z.object({
409
+ dependencies: z.record(z.string(), z.string()).default({}),
410
+ envVars: z.array(
411
+ z.object({
412
+ key: z.string(),
413
+ description: z.string(),
414
+ required: z.boolean().default(true)
415
+ })
416
+ ).default([]),
417
+ /** Custom adapter filename (e.g., 'push-notification.adapter.ts') */
418
+ adapterFile: z.string().optional()
419
+ });
420
+ var NotifierDefaultsSchema = z.object({
421
+ push: z.string().optional(),
422
+ email: z.string().optional(),
423
+ slack: z.string().optional(),
424
+ sms: z.string().optional()
425
+ }).optional();
426
+ var NotifierConfigSchema = z.object({
427
+ defaults: NotifierDefaultsSchema,
428
+ providers: z.object({
429
+ push: z.record(z.string(), ProviderMetaSchema).optional(),
430
+ email: z.record(z.string(), ProviderMetaSchema).optional(),
431
+ slack: z.record(z.string(), ProviderMetaSchema).optional(),
432
+ sms: z.record(z.string(), ProviderMetaSchema).optional()
433
+ }).optional()
434
+ });
435
+ var CapabilitiesConfigSchema = z.object({
436
+ notifier: NotifierConfigSchema.optional()
437
+ });
438
+ var getRegisteredProviders = (workspaceConfig) => ({
439
+ push: Object.keys(workspaceConfig?.notifier?.providers?.push ?? {}),
440
+ email: Object.keys(workspaceConfig?.notifier?.providers?.email ?? {}),
441
+ slack: Object.keys(workspaceConfig?.notifier?.providers?.slack ?? {}),
442
+ sms: Object.keys(workspaceConfig?.notifier?.providers?.sms ?? {})
443
+ });
444
+ var getDefaultProvider = (channel, workspaceConfig) => workspaceConfig?.notifier?.defaults?.[channel];
445
+ var lookupProvider = (channel, provider, workspaceConfig) => {
446
+ const meta = workspaceConfig?.notifier?.providers?.[channel]?.[provider];
447
+ if (meta) {
448
+ return { found: true, provider, meta, source: "workspace" };
449
+ }
450
+ const availableProviders = Object.keys(workspaceConfig?.notifier?.providers?.[channel] ?? {});
451
+ const configSnippet = generateConfigSnippet(channel, provider);
452
+ return {
453
+ found: false,
454
+ channel,
455
+ provider,
456
+ availableProviders,
457
+ configSnippet
458
+ };
459
+ };
460
+ var normalizeProviderName = (name) => name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
461
+ var extractProviderFromPrompt = (prompt) => {
462
+ const patterns = [
463
+ /(?:via|using|with|über|per)\s+([\w\s-]+?)(?:\s+(?:for|to|and|,|\.|$))/i,
464
+ /(?:via|using|with|über|per)\s+([\w-]+)/i
465
+ ];
466
+ for (const pattern of patterns) {
467
+ const match = prompt.match(pattern);
468
+ if (match?.[1]) {
469
+ return match[1].trim();
470
+ }
471
+ }
472
+ return void 0;
473
+ };
474
+ var generateConfigSnippet = (channel, provider) => {
475
+ const snippet = {
476
+ pf: {
477
+ capabilities: {
478
+ notifier: {
479
+ providers: {
480
+ [channel]: {
481
+ [provider]: {
482
+ dependencies: { "your-package": "^1.0.0" },
483
+ envVars: [{ key: "YOUR_API_KEY", description: "API key for provider", required: true }]
484
+ }
485
+ }
486
+ }
487
+ }
488
+ }
489
+ }
490
+ };
491
+ return JSON.stringify(snippet, null, 2);
492
+ };
493
+ var resolveProvider = (channel, explicitProvider, workspaceConfig) => {
494
+ if (explicitProvider) {
495
+ return { resolved: true, provider: explicitProvider, source: "explicit" };
496
+ }
497
+ const defaultProvider = getDefaultProvider(channel, workspaceConfig);
498
+ if (defaultProvider) {
499
+ return { resolved: true, provider: defaultProvider, source: "default" };
500
+ }
501
+ const availableProviders = Object.keys(workspaceConfig?.notifier?.providers?.[channel] ?? {});
502
+ return {
503
+ resolved: false,
504
+ needsElicitation: true,
505
+ channel,
506
+ availableProviders
507
+ };
508
+ };
509
+ var loadCapabilitiesConfig = (workspacePkg) => {
510
+ if (!workspacePkg) return void 0;
511
+ const pfConfig = workspacePkg.pf;
512
+ if (!pfConfig?.capabilities) return void 0;
513
+ const result = CapabilitiesConfigSchema.safeParse(pfConfig.capabilities);
514
+ return result.success ? result.data : void 0;
515
+ };
516
+ var loadCapabilitiesConfigFromWorkspace = (getWorkspacePackageJson2) => {
517
+ const pkg = getWorkspacePackageJson2();
518
+ return loadCapabilitiesConfig(pkg);
519
+ };
520
+
521
+ // cli/src/core/capabilities/lowering.ts
522
+ var lowerSpecToCapabilities = (spec, serviceDir) => {
523
+ const metadata = {
524
+ files: [],
525
+ dependencies: {},
526
+ envVars: [],
527
+ postCommands: []
528
+ };
529
+ for (const trigger of spec.triggers) {
530
+ if (trigger.type === "event") {
531
+ metadata.postCommands.push(`pf cloudevents add ${trigger.eventType} --service ${serviceDir} --fields "id:string"`);
532
+ }
533
+ }
534
+ for (const action of spec.actions) {
535
+ if (action.type === "notify" && action.provider) {
536
+ metadata.files.push({
537
+ path: `${serviceDir}/src/adapters/${action.channel}.adapter.ts`,
538
+ intent: `${action.channel} adapter using ${action.provider}`,
539
+ layer: "adapter",
540
+ kind: "notifier"
541
+ });
542
+ metadata.files.push({
543
+ path: `${serviceDir}/src/use-cases/send-${action.channel}.use-case.ts`,
544
+ intent: `send ${action.channel} notification`,
545
+ layer: "useCase"
546
+ });
547
+ }
548
+ }
549
+ return metadata;
550
+ };
551
+
552
+ // cli/src/core/capabilities/types.ts
553
+ import { z as z2 } from "zod";
554
+ var EventTriggerSchema = z2.object({
555
+ type: z2.literal("event"),
556
+ eventType: z2.string(),
557
+ stream: z2.string().optional()
558
+ });
559
+ var HttpTriggerSchema = z2.object({
560
+ type: z2.literal("http"),
561
+ method: z2.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional(),
562
+ path: z2.string().optional()
563
+ });
564
+ var TriggerSchema = z2.discriminatedUnion("type", [EventTriggerSchema, HttpTriggerSchema]);
565
+ var EmitEventActionSchema = z2.object({
566
+ type: z2.literal("emit.event"),
567
+ eventType: z2.string()
568
+ });
569
+ var NotifyActionSchema = z2.object({
570
+ type: z2.literal("notify"),
571
+ channel: z2.enum(["push", "email", "slack", "sms"]),
572
+ /** Must be explicitly provided by user or elicited - NO defaults */
573
+ provider: z2.string().optional()
574
+ });
575
+ var CallHttpActionSchema = z2.object({
576
+ type: z2.literal("call.http"),
577
+ name: z2.string().optional(),
578
+ baseUrl: z2.string().optional()
579
+ });
580
+ var PersistActionSchema = z2.object({
581
+ type: z2.literal("persist"),
582
+ store: z2.enum(["db", "kv", "cache"]),
583
+ entity: z2.string().optional()
584
+ });
585
+ var ActionSchema = z2.discriminatedUnion("type", [
586
+ EmitEventActionSchema,
587
+ NotifyActionSchema,
588
+ CallHttpActionSchema,
589
+ PersistActionSchema
590
+ ]);
591
+ var ServiceSpecSchema = z2.object({
592
+ serviceName: z2.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/),
593
+ framework: z2.enum(["hono", "nest"]).default("hono"),
594
+ triggers: z2.array(TriggerSchema).min(1),
595
+ actions: z2.array(ActionSchema).default([])
596
+ });
597
+
598
+ // cli/src/core/capabilities/parser.ts
599
+ var PROVIDER_KEYWORDS = {
600
+ "pusher beams": { channel: "push", provider: "pusher-beams" },
601
+ "pusher-beams": { channel: "push", provider: "pusher-beams" },
602
+ pusherbeams: { channel: "push", provider: "pusher-beams" },
603
+ firebase: { channel: "push", provider: "firebase" },
604
+ onesignal: { channel: "push", provider: "onesignal" },
605
+ expo: { channel: "push", provider: "expo" },
606
+ resend: { channel: "email", provider: "resend" },
607
+ sendgrid: { channel: "email", provider: "sendgrid" },
608
+ ses: { channel: "email", provider: "ses" },
609
+ postmark: { channel: "email", provider: "postmark" },
610
+ "slack-webhook": { channel: "slack", provider: "slack-webhook" },
611
+ "slack webhook": { channel: "slack", provider: "slack-webhook" },
612
+ "slack-api": { channel: "slack", provider: "slack-api" },
613
+ twilio: { channel: "sms", provider: "twilio" },
614
+ vonage: { channel: "sms", provider: "vonage" }
615
+ };
616
+ var detectProvider = (prompt, channel, workspaceConfig) => {
617
+ const lowerPrompt = prompt.toLowerCase();
618
+ for (const [keyword, info] of Object.entries(PROVIDER_KEYWORDS)) {
619
+ if (info.channel === channel && lowerPrompt.includes(keyword)) {
620
+ return info.provider;
621
+ }
622
+ }
623
+ const registeredProviders = getRegisteredProviders(workspaceConfig);
624
+ const channelProviders = registeredProviders[channel] ?? [];
625
+ for (const provider of channelProviders) {
626
+ const patterns = [provider, provider.replace("-", " "), provider.replace("-", "")];
627
+ if (patterns.some((p) => lowerPrompt.includes(p.toLowerCase()))) {
628
+ return provider;
629
+ }
630
+ }
631
+ const rawProvider = extractProviderFromPrompt(prompt);
632
+ if (rawProvider) {
633
+ return normalizeProviderName(rawProvider);
634
+ }
635
+ return void 0;
636
+ };
637
+ var inferServiceName = (prompt) => {
638
+ const patterns = [
639
+ // "create a notifications service" / "build order-processor service"
640
+ /(?:create|build|make|erstelle)\s+(?:a\s+)?(?:an?\s+)?([a-z][a-z0-9-]*)\s+service/i,
641
+ // "notifications service that listens" / "order-processor service which handles"
642
+ /([a-z][a-z0-9-]*)\s+service\s+(?:that|which|to|consumes|listens|handles)/i,
643
+ // "service called notifications" / "service named order-processor"
644
+ /service\s+(?:called|named)\s+([a-z][a-z0-9-]*)/i,
645
+ // "a notifications service" / "an order-processor service" (at start)
646
+ /^(?:a|an)\s+([a-z][a-z0-9-]*)\s+service/i,
647
+ // Simple: "notifications service" at start of prompt
648
+ /^([a-z][a-z0-9-]*)\s+service\b/i
649
+ ];
650
+ for (const pattern of patterns) {
651
+ const match = prompt.match(pattern);
652
+ if (match?.[1]) {
653
+ return match[1].toLowerCase().replace(/\s+/g, "-");
654
+ }
655
+ }
656
+ return void 0;
657
+ };
658
+ var inferTriggers = (prompt) => {
659
+ const triggers = [];
660
+ const eventPatterns = [
661
+ /(?:listens?\s+(?:to|for)|consumes?|handles?|reacts?\s+to|when)\s+([a-z][a-z0-9.]*)\s+events?/gi,
662
+ /on\s+([a-z][a-z0-9.]*)\s+event/gi
663
+ ];
664
+ for (const pattern of eventPatterns) {
665
+ for (const match of prompt.matchAll(pattern)) {
666
+ const eventType = match[1].toLowerCase();
667
+ if (!triggers.some((t) => t.type === "event" && t.eventType === eventType)) {
668
+ triggers.push({ type: "event", eventType });
669
+ }
670
+ }
671
+ }
672
+ const directEventPattern = /\b([a-z][a-z0-9]*\.[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)?)\b/gi;
673
+ for (const match of prompt.matchAll(directEventPattern)) {
674
+ const eventType = match[1].toLowerCase();
675
+ if (!eventType.includes(".json") && !eventType.includes(".ts") && !eventType.includes(".js")) {
676
+ if (!triggers.some((t) => t.type === "event" && t.eventType === eventType)) {
677
+ triggers.push({ type: "event", eventType });
678
+ }
679
+ }
680
+ }
681
+ return triggers;
682
+ };
683
+ var inferActions = (prompt, workspaceConfig) => {
684
+ const actions = [];
685
+ if (/push\s+notification|send\s+push|notify.*push/i.test(prompt)) {
686
+ const provider = detectProvider(prompt, "push", workspaceConfig);
687
+ actions.push({ type: "notify", channel: "push", provider });
688
+ }
689
+ if (/send\s+email|email\s+notification/i.test(prompt)) {
690
+ const provider = detectProvider(prompt, "email", workspaceConfig);
691
+ actions.push({ type: "notify", channel: "email", provider });
692
+ }
693
+ if (/slack\s+(?:message|notification)/i.test(prompt)) {
694
+ const provider = detectProvider(prompt, "slack", workspaceConfig);
695
+ actions.push({ type: "notify", channel: "slack", provider });
696
+ }
697
+ if (/sms|text\s+message/i.test(prompt)) {
698
+ const provider = detectProvider(prompt, "sms", workspaceConfig);
699
+ actions.push({ type: "notify", channel: "sms", provider });
700
+ }
701
+ const emitMatch = prompt.match(/emit(?:s)?\s+([a-z][a-z0-9.]*)\s+event/i);
702
+ if (emitMatch) {
703
+ actions.push({ type: "emit.event", eventType: emitMatch[1].toLowerCase() });
704
+ }
705
+ return actions;
706
+ };
707
+ var findMissingFields = (raw, workspaceConfig) => {
708
+ const missing = [];
709
+ if (!raw.serviceName) {
710
+ missing.push({
711
+ path: "serviceName",
712
+ prompt: 'What should the service be named? (kebab-case, e.g., "order-notifications")',
713
+ type: "string",
714
+ required: true
715
+ });
716
+ }
717
+ if (!raw.triggers || raw.triggers.length === 0) {
718
+ missing.push({
719
+ path: "triggers[0].eventType",
720
+ prompt: 'What event should trigger this service? (e.g., "order.created")',
721
+ type: "string",
722
+ required: true
723
+ });
724
+ }
725
+ raw.actions?.forEach((action, index) => {
726
+ if (action.type === "notify" && !action.provider) {
727
+ const channel = action.channel;
728
+ const defaultProvider = getDefaultProvider(channel, workspaceConfig);
729
+ if (!defaultProvider) {
730
+ const registeredProviders = getRegisteredProviders(workspaceConfig);
731
+ const availableProviders = registeredProviders[channel] ?? [];
732
+ missing.push({
733
+ path: `actions[${index}].provider`,
734
+ prompt: availableProviders.length > 0 ? `Which provider for ${channel} notifications?` : `Which provider for ${channel} notifications? (No providers registered - add to package.json pf.capabilities)`,
735
+ type: "enum",
736
+ options: availableProviders,
737
+ required: true
738
+ });
739
+ }
740
+ }
741
+ if (action.type === "call.http" && !action.baseUrl) {
742
+ missing.push({
743
+ path: `actions[${index}].baseUrl`,
744
+ prompt: "What is the base URL for the HTTP call?",
745
+ type: "string",
746
+ required: true
747
+ });
748
+ }
749
+ });
750
+ return missing;
751
+ };
752
+ var applyWorkspaceDefaults = (raw, workspaceConfig) => {
753
+ if (!workspaceConfig || !raw.actions) return raw;
754
+ const updatedActions = raw.actions.map((action) => {
755
+ if (action.type === "notify" && !action.provider) {
756
+ const channel = action.channel;
757
+ const defaultProvider = getDefaultProvider(channel, workspaceConfig);
758
+ if (defaultProvider) {
759
+ return { ...action, provider: defaultProvider };
760
+ }
761
+ }
762
+ return action;
763
+ });
764
+ return { ...raw, actions: updatedActions };
765
+ };
766
+ var validateSpec = (raw, workspaceConfig) => {
767
+ const withDefaults = applyWorkspaceDefaults(raw, workspaceConfig);
768
+ const missingFields = findMissingFields(withDefaults, workspaceConfig);
769
+ if (missingFields.length > 0) {
770
+ return {
771
+ complete: false,
772
+ fields: missingFields,
773
+ partialSpec: withDefaults
774
+ };
775
+ }
776
+ const result = ServiceSpecSchema.safeParse(withDefaults);
777
+ if (!result.success) {
778
+ const fields = result.error.issues.map((issue) => ({
779
+ path: issue.path.join("."),
780
+ prompt: issue.message,
781
+ type: "string",
782
+ required: true
783
+ }));
784
+ return { complete: false, fields, partialSpec: withDefaults };
785
+ }
786
+ return { complete: true, spec: result.data };
787
+ };
788
+ var parseServicePrompt = async (prompt, options) => {
789
+ const opts = options && "extract" in options ? { interpreter: options } : options ?? {};
790
+ const { interpreter, workspaceConfig } = opts;
791
+ let raw;
792
+ if (interpreter) {
793
+ raw = await interpreter.extract(prompt);
794
+ } else {
795
+ raw = {
796
+ serviceName: inferServiceName(prompt),
797
+ framework: /nest(?:js)?/i.test(prompt) ? "nest" : "hono",
798
+ triggers: inferTriggers(prompt),
799
+ actions: inferActions(prompt, workspaceConfig)
800
+ };
801
+ }
802
+ return validateSpec(raw, workspaceConfig);
803
+ };
804
+
805
+ // cli/src/core/effects/changes.ts
806
+ var groupBy = (items, key) => {
807
+ return items.reduce((acc, item) => {
808
+ const group = String(item[key]);
809
+ const existing = acc[group];
810
+ acc[group] = existing ? [...existing, item] : [item];
811
+ return acc;
812
+ }, {});
813
+ };
814
+ var formatChange = (change, index) => {
815
+ const lines = [
816
+ `${index + 1}. ${change.type.toUpperCase()}: ${change.path}`,
817
+ ` ${change.description}`
818
+ ];
819
+ if (change.preview) {
820
+ const previewLines = change.preview.split("\n").map((line) => ` ${line}`);
821
+ lines.push(" Preview:", " ```", ...previewLines, " ```");
822
+ }
823
+ lines.push("");
824
+ return lines;
825
+ };
826
+ var formatFileList = (icon, label, items) => {
827
+ if (!items?.length) return [];
828
+ return [
829
+ `${icon} ${label}:`,
830
+ ...items.map((c) => ` \u2022 ${c.path} - ${c.description}`),
831
+ ""
832
+ ];
833
+ };
834
+ var aggregateChanges = (results) => {
835
+ return results.filter((r) => r.success && r.changes).flatMap((r) => r.changes ?? []);
836
+ };
837
+ var formatChangeSummary = (changes) => {
838
+ if (changes.length === 0) {
839
+ return "\u2713 No changes";
840
+ }
841
+ const groups = groupBy(changes, "type");
842
+ const summary = [
843
+ "\u{1F4DD} Changes Summary:",
844
+ `${groups.create?.length || 0} files to create`,
845
+ `${groups.update?.length || 0} files to update`,
846
+ `${groups.delete?.length || 0} files to delete`,
847
+ "",
848
+ ...formatFileList("\u{1F4C4}", "Create", groups.create),
849
+ ...formatFileList("\u270F\uFE0F ", "Update", groups.update),
850
+ ...formatFileList("\u{1F5D1}\uFE0F ", "Delete", groups.delete)
851
+ ];
852
+ return summary.join("\n");
853
+ };
854
+ var formatChangeDetails = (changes) => {
855
+ if (changes.length === 0) {
856
+ return "\u2713 No changes";
857
+ }
858
+ const header = ["\u{1F4DD} Detailed Changes:", ""];
859
+ const details = changes.flatMap((change, index) => formatChange(change, index));
860
+ return [...header, ...details].join("\n");
861
+ };
862
+
863
+ // cli/src/core/events/index-manager.ts
864
+ init_config();
865
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
866
+ import { dirname as dirname2, join as join4 } from "path";
867
+ import { deriveEventNames } from "@crossdelta/cloudevents";
868
+ var addExportToDomainIndex = (domainIndexPath, eventFile) => {
869
+ const exportLine = `export * from './${eventFile}'`;
870
+ if (!existsSync3(domainIndexPath)) {
871
+ const domainDir = dirname2(domainIndexPath);
872
+ if (!existsSync3(domainDir)) {
873
+ mkdirSync(domainDir, { recursive: true });
874
+ }
875
+ const header = `/**
876
+ * ${dirname2(domainIndexPath).split("/").pop()} Domain Events
877
+ */
878
+
879
+ `;
880
+ writeFileSync3(domainIndexPath, `${header + exportLine}
881
+ `, "utf-8");
882
+ return true;
883
+ }
884
+ const content = readFileSync2(domainIndexPath, "utf-8");
885
+ if (content.includes(`'./${eventFile}'`)) return false;
886
+ const updatedContent = `${content.trimEnd()}
887
+ ${exportLine}
888
+ `;
889
+ writeFileSync3(domainIndexPath, updatedContent, "utf-8");
890
+ return true;
891
+ };
892
+ var addDomainToEventsIndex = (eventsIndexPath, domain) => {
893
+ const exportLine = `export * from './${domain}'`;
894
+ if (!existsSync3(eventsIndexPath)) {
895
+ const header = `/**
896
+ * Event Contracts Index
897
+ *
898
+ * Re-exports all event contracts for convenient importing.
899
+ */
900
+
901
+ `;
902
+ writeFileSync3(eventsIndexPath, header + exportLine + "\n", "utf-8");
903
+ return true;
904
+ }
905
+ const content = readFileSync2(eventsIndexPath, "utf-8");
906
+ const lines = content.split("\n");
907
+ if (lines.some((line) => line.trim() === exportLine)) {
908
+ return false;
909
+ }
910
+ const lastExportIndex = lines.findLastIndex((line) => line.startsWith("export"));
911
+ if (lastExportIndex >= 0) {
912
+ lines.splice(lastExportIndex + 1, 0, exportLine);
913
+ } else {
914
+ lines.push(exportLine);
915
+ }
916
+ writeFileSync3(eventsIndexPath, lines.join("\n"), "utf-8");
917
+ return true;
918
+ };
919
+ var enableMainExports = (mainIndexPath) => {
920
+ if (!existsSync3(mainIndexPath)) return false;
921
+ const content = readFileSync2(mainIndexPath, "utf-8");
922
+ const eventsExport = "export * from './events'";
923
+ const hasUncommentedExport = content.split("\n").some(
924
+ (line) => line.trim() === eventsExport || line.trim().startsWith(eventsExport)
925
+ );
926
+ if (hasUncommentedExport) return false;
927
+ let updated = content;
928
+ updated = updated.replace(/\/\/\s*export \* from '\.\/events'/, eventsExport);
929
+ updated = updated.replace(/\/\/\s*export \* from '\.\/stream-policies'/, "export * from './stream-policies'");
930
+ if (/export \* from '\.\/events\/\w+\/\w+'/.test(updated)) {
931
+ updated = updated.replace(/export \* from '\.\/events\/\w+\/\w+'\n?/g, "");
932
+ }
933
+ if (!updated.includes(eventsExport)) {
934
+ updated = `${updated.trimEnd()}
935
+ ${eventsExport}
936
+ `;
937
+ }
938
+ if (updated !== content) {
939
+ writeFileSync3(mainIndexPath, updated, "utf-8");
940
+ return true;
941
+ }
942
+ return false;
943
+ };
944
+ var addExportToIndex = (eventType, workspaceRoot) => {
945
+ const { packagePath } = getContractsConfig(workspaceRoot);
946
+ const { domain, action } = deriveEventNames(eventType);
947
+ const eventsDir = join4(packagePath, "src", "events");
948
+ if (!existsSync3(eventsDir)) return false;
949
+ const domainDir = join4(eventsDir, domain);
950
+ const domainIndexPath = join4(domainDir, "index.ts");
951
+ const eventsIndexPath = join4(eventsDir, "index.ts");
952
+ const mainIndexPath = join4(packagePath, "src", "index.ts");
953
+ const singularDomain = eventType.split(".")[0];
954
+ const legacyDomainDir = join4(eventsDir, singularDomain);
955
+ if (singularDomain !== domain && existsSync3(legacyDomainDir)) {
956
+ console.warn(`\u26A0\uFE0F Warning: Legacy folder '${singularDomain}/' found. Please migrate to '${domain}/' to avoid conflicts.`);
957
+ console.warn(` Run: mv ${legacyDomainDir} ${domainDir}`);
958
+ }
959
+ let updated = false;
960
+ if (addExportToDomainIndex(domainIndexPath, action)) {
961
+ updated = true;
962
+ }
963
+ if (addDomainToEventsIndex(eventsIndexPath, domain)) {
964
+ updated = true;
965
+ }
966
+ if (enableMainExports(mainIndexPath)) {
967
+ updated = true;
968
+ }
969
+ return updated;
970
+ };
971
+
972
+ // cli/src/core/effects/stream.handler.ts
973
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
974
+ import { join as join5 } from "path";
975
+ import { getStreamName } from "@crossdelta/cloudevents";
976
+ var serviceConsumesStream = (servicePath, streamName) => {
977
+ const indexPath = join5(servicePath, "src", "index.ts");
978
+ if (!existsSync4(indexPath)) return false;
979
+ const content = readFileSync3(indexPath, "utf-8");
980
+ const namespace = streamName.toLowerCase();
981
+ const streamsArrayPattern = new RegExp(`streams:\\s*\\[[^\\]]*['"]${streamName}['"]`);
982
+ if (streamsArrayPattern.test(content)) return true;
983
+ if (content.includes(`stream: '${streamName}'`)) return true;
984
+ const subjectPattern = new RegExp(`subjects:\\s*\\[.*['"]${namespace}\\.[*>]`);
985
+ return subjectPattern.test(content);
986
+ };
987
+ var addCloudEventsImport = (content) => {
988
+ if (content.includes("@crossdelta/cloudevents")) return content;
989
+ const cloudEventsImport = `import { consumeJetStreams } from '@crossdelta/cloudevents'
990
+ `;
991
+ const telemetryMatch = content.match(/import\s+['"]@crossdelta\/telemetry['"]\s*\n/);
992
+ if (telemetryMatch) {
993
+ const insertPos = (telemetryMatch.index ?? 0) + telemetryMatch[0].length;
994
+ return `${content.slice(0, insertPos)}
995
+ ${cloudEventsImport}${content.slice(insertPos)}`;
996
+ }
997
+ const firstImportMatch = content.match(/import\s+.*\n/);
998
+ if (firstImportMatch) {
999
+ const insertPos = (firstImportMatch.index ?? 0) + firstImportMatch[0].length;
1000
+ return content.slice(0, insertPos) + cloudEventsImport + content.slice(insertPos);
1001
+ }
1002
+ return content;
1003
+ };
1004
+ var generateStreamCode = (streamName, serviceName) => `
1005
+
1006
+ // Consume from ${streamName} stream
1007
+ consumeJetStreams({
1008
+ streams: ['${streamName}'],
1009
+ consumer: '${serviceName}-service',
1010
+ discover: './src/events/**/*.handler.ts',
1011
+ })`;
1012
+ var addStreamToService = (servicePath, eventType) => {
1013
+ const streamName = getStreamName(eventType);
1014
+ const indexPath = join5(servicePath, "src", "index.ts");
1015
+ const mainPath = join5(servicePath, "src", "main.ts");
1016
+ if (!existsSync4(indexPath) && existsSync4(mainPath)) {
1017
+ return { added: false, streamName };
1018
+ }
1019
+ if (!existsSync4(indexPath)) {
1020
+ return { added: false, streamName, warning: "Entry point not found (index.ts or main.ts)" };
1021
+ }
1022
+ if (serviceConsumesStream(servicePath, streamName)) {
1023
+ return { added: false, streamName };
1024
+ }
1025
+ let content = readFileSync3(indexPath, "utf-8");
1026
+ const serviceName = servicePath.split("/").pop() || "unknown";
1027
+ if (!content.includes("consumeJetStreams")) {
1028
+ content = addCloudEventsImport(content);
1029
+ }
1030
+ const streamCode = generateStreamCode(streamName, serviceName);
1031
+ const updatedContent = `${content.trimEnd() + streamCode}
1032
+ `;
1033
+ writeFileSync4(indexPath, updatedContent, "utf-8");
1034
+ return { added: true, streamName };
1035
+ };
1036
+
1037
+ // cli/src/core/effects/handlers.ts
1038
+ var handleStreamWired = (effect, context, options) => {
1039
+ const { stream, servicePath } = effect;
1040
+ const absolutePath = servicePath.startsWith("/") ? servicePath : `${context.workspace.workspaceRoot}/${servicePath}`;
1041
+ if (options?.dryRun) {
1042
+ return {
1043
+ success: true,
1044
+ message: `Would wire stream ${stream} to ${servicePath}`,
1045
+ changes: [
1046
+ {
1047
+ type: "update",
1048
+ path: absolutePath,
1049
+ description: `Wire ${stream} stream to service`,
1050
+ preview: `consumeJetStreams({ streams: ['${stream.toUpperCase()}'], ... })`
1051
+ }
1052
+ ]
1053
+ };
1054
+ }
1055
+ const result = addStreamToService(absolutePath, `${stream.toLowerCase()}.event`);
1056
+ if (result.warning) {
1057
+ context.logger.warn(result.warning);
1058
+ return { success: false, message: result.warning };
1059
+ }
1060
+ if (result.added) {
1061
+ context.logger.info(`Wired stream ${result.streamName} to service`);
1062
+ return { success: true, message: `Wired stream ${result.streamName}` };
1063
+ }
1064
+ context.logger.debug(`Stream ${result.streamName} already wired`);
1065
+ return { success: true, message: `Stream ${result.streamName} already configured` };
1066
+ };
1067
+ var handleContractCreated = (effect, context, options) => {
1068
+ const { path, eventType } = effect;
1069
+ if (options?.dryRun) {
1070
+ const preview = `
1071
+ import { createContract } from '@crossdelta/cloudevents'
1072
+ import { z } from 'zod'
1073
+
1074
+ export const ${toPascalCase(eventType)}Contract = createContract({
1075
+ type: '${eventType}',
1076
+ schema: z.object({ ... }),
1077
+ })
1078
+ `.trim();
1079
+ return {
1080
+ success: true,
1081
+ message: `Would create contract ${eventType}`,
1082
+ changes: [
1083
+ {
1084
+ type: "create",
1085
+ path,
1086
+ description: `Create event contract for ${eventType}`,
1087
+ preview: preview.slice(0, 500)
1088
+ }
1089
+ ]
1090
+ };
1091
+ }
1092
+ const updated = addExportToIndex(eventType, context.workspace.workspaceRoot);
1093
+ if (updated) {
1094
+ context.logger.debug(`Added export for ${eventType} to contracts index`);
1095
+ }
1096
+ context.logger.debug(`Contract created: ${eventType} at ${path}`);
1097
+ return {
1098
+ success: true,
1099
+ message: updated ? `Contract ${eventType} created with exports` : `Contract ${eventType} created`
1100
+ };
1101
+ };
1102
+ var toPascalCase = (eventType) => {
1103
+ return eventType.split(".").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1104
+ };
1105
+ var handleHandlerCreated = (effect, context, options) => {
1106
+ const { path, eventType, servicePath } = effect;
1107
+ if (options?.dryRun) {
1108
+ const preview = `
1109
+ import { handleEvent } from '@crossdelta/cloudevents'
1110
+ import { ${toPascalCase(eventType)}Contract } from '@your-scope/contracts'
1111
+
1112
+ export default handleEvent(${toPascalCase(eventType)}Contract, async (data) => {
1113
+ console.log('Processing ${eventType}:', data)
1114
+ // TODO: Implement handler logic
1115
+ })
1116
+ `.trim();
1117
+ return {
1118
+ success: true,
1119
+ message: `Would create handler for ${eventType}`,
1120
+ changes: [
1121
+ {
1122
+ type: "create",
1123
+ path,
1124
+ description: `Create event handler for ${eventType}`,
1125
+ preview: preview.slice(0, 500)
1126
+ }
1127
+ ]
1128
+ };
1129
+ }
1130
+ context.logger.debug(`Handler created: ${eventType} at ${path} (service: ${servicePath})`);
1131
+ return { success: true };
1132
+ };
1133
+ var effectHandlers = {
1134
+ "stream.wired": handleStreamWired,
1135
+ "contract.created": handleContractCreated,
1136
+ "handler.created": handleHandlerCreated
1137
+ };
1138
+ var applyEffects = (effects, context, options) => {
1139
+ return effects.map((effect) => {
1140
+ const handler = effectHandlers[effect.kind];
1141
+ if (!handler) {
1142
+ context.logger.warn(`Unknown effect kind: ${effect.kind}`);
1143
+ return { success: false, message: `Unknown effect kind: ${effect.kind}` };
1144
+ }
1145
+ return handler(effect, context, options);
1146
+ });
1147
+ };
1148
+
1149
+ // cli/src/core/flows/service-generation.flow.ts
1150
+ import { join as join12 } from "path";
1151
+ import { deriveEventNames as deriveEventNames3 } from "@crossdelta/cloudevents";
1152
+
1153
+ // cli/src/core/operations/types.ts
1154
+ var ok = (operation, summary, options = {}) => ({
1155
+ ok: true,
1156
+ operation,
1157
+ summary,
1158
+ artifacts: options.artifacts ?? [],
1159
+ changes: options.changes ?? [],
1160
+ diagnostics: options.diagnostics ?? [],
1161
+ next: options.next,
1162
+ data: options.data
1163
+ });
1164
+ var err = (operation, error, options = {}) => ({
1165
+ ok: false,
1166
+ operation,
1167
+ summary: `Failed: ${error}`,
1168
+ artifacts: [],
1169
+ changes: [],
1170
+ diagnostics: options.diagnostics ?? [{ level: "error", message: error }],
1171
+ data: options.data
1172
+ });
1173
+
1174
+ // cli/src/core/flows/service-generation.flow.ts
1175
+ init_config();
1176
+
1177
+ // cli/src/commands/create/hono-microservice/templates.ts
1178
+ import { join as join8 } from "path";
1179
+
1180
+ // cli/src/core/esm.ts
1181
+ import { dirname as dirname3 } from "path";
1182
+ import { fileURLToPath as fileURLToPath2 } from "url";
1183
+ var getDirname = (importMetaUrl) => dirname3(fileURLToPath2(importMetaUrl));
1184
+
1185
+ // cli/src/core/filesystem/index.ts
1186
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
1187
+ import { dirname as dirname4, join as join6 } from "path";
1188
+ init_watcher();
1189
+ var getDefaultStartDir = () => {
1190
+ if (typeof import.meta?.url === "string") {
1191
+ return getDirname(import.meta.url);
1192
+ }
1193
+ if (typeof __dirname === "string") return __dirname;
1194
+ return process.cwd();
1195
+ };
1196
+ var findPackageRoot = (startDir) => {
1197
+ const dir = startDir ?? getDefaultStartDir();
1198
+ const hasPackageJson = (d) => existsSync5(join6(d, "package.json"));
1199
+ const parent = dirname4(dir);
1200
+ if (hasPackageJson(dir)) return dir;
1201
+ if (dir === "/") throw new Error("Could not find package.json (package root)");
1202
+ return findPackageRoot(parent);
1203
+ };
1204
+ var findDirectory = (searchPaths, errorMessage) => {
1205
+ const found = searchPaths.find(existsSync5);
1206
+ if (!found) {
1207
+ throw new Error(
1208
+ errorMessage ?? `Directory not found. Searched in: ${searchPaths.join(", ")}`
1209
+ );
1210
+ }
1211
+ return found;
1212
+ };
1213
+
1214
+ // cli/src/core/templates/renderer.ts
1215
+ import { readFileSync as readFileSync4 } from "fs";
1216
+ import { join as join7 } from "path";
1217
+ import Handlebars from "handlebars";
1218
+ var findTemplatesDir = (searchPaths, errorMessage) => {
1219
+ return findDirectory(
1220
+ searchPaths,
1221
+ errorMessage ?? `Templates directory not found. Searched in: ${searchPaths.join(", ")}`
1222
+ );
1223
+ };
1224
+ var renderTemplate = (templatePath, data = {}) => {
1225
+ const templateContent = readFileSync4(templatePath, "utf-8");
1226
+ const template = Handlebars.compile(templateContent);
1227
+ return template(data);
1228
+ };
1229
+ var createTemplateRenderer = (templatesDir) => (templateName, data = {}) => {
1230
+ const templatePath = join7(templatesDir, templateName);
1231
+ return renderTemplate(templatePath, data);
1232
+ };
1233
+
1234
+ // cli/src/core/templates/strings.ts
1235
+ var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
1236
+ var toDisplayName = (name) => name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1237
+
1238
+ // cli/src/commands/create/hono-microservice/templates.ts
1239
+ var getModuleDir = () => {
1240
+ if (typeof import.meta?.url === "string") {
1241
+ return getDirname(import.meta.url);
1242
+ }
1243
+ if (typeof __dirname === "string") return __dirname;
1244
+ return process.cwd();
1245
+ };
1246
+ var getTemplatesDir = () => {
1247
+ const moduleDir = getModuleDir();
1248
+ const packageRoot = findPackageRoot();
1249
+ return findTemplatesDir(
1250
+ [
1251
+ join8(moduleDir, "templates", "hono-microservice"),
1252
+ join8(moduleDir, "..", "hono-microservice", "templates"),
1253
+ join8(packageRoot, "bin", "templates", "hono-microservice")
1254
+ // Global install fallback
1255
+ ],
1256
+ "Hono templates directory not found."
1257
+ );
1258
+ };
1259
+ var renderTemplate2 = (templateName, data = {}) => createTemplateRenderer(getTemplatesDir())(templateName, data);
1260
+ function generateHonoTsConfig() {
1261
+ return renderTemplate2("tsconfig.json.hbs");
1262
+ }
1263
+ function generateHonoDockerfile(bunVersion = "1.2.23") {
1264
+ return renderTemplate2("Dockerfile.hbs", { bunVersion });
1265
+ }
1266
+ function generateHonoBiomeJson() {
1267
+ return renderTemplate2("biome.json.hbs");
1268
+ }
1269
+ function generateHonoEnvTs(serviceName, envVars = []) {
1270
+ return renderTemplate2("src/config/env.ts.hbs", {
1271
+ envKey: toEnvKey(serviceName),
1272
+ envVars
1273
+ });
1274
+ }
1275
+
1276
+ // cli/src/core/templates/hono-bun.template.ts
1277
+ var generateImports = (hasEvents) => {
1278
+ const imports = [
1279
+ "import './config/env'",
1280
+ "import '@crossdelta/telemetry'",
1281
+ ""
1282
+ ];
1283
+ if (hasEvents) {
1284
+ imports.push("import { consumeJetStreams } from '@crossdelta/cloudevents'");
1285
+ }
1286
+ imports.push("import { Hono } from 'hono'");
1287
+ return imports;
1288
+ };
1289
+ var generateServerSetup = (envKey) => [
1290
+ `const port = Number(process.env.${envKey}_PORT) || 8080`,
1291
+ "const app = new Hono()",
1292
+ "",
1293
+ "app.get('/health', (c) => {",
1294
+ " return c.json({ status: 'ok' })",
1295
+ "})"
1296
+ ];
1297
+ var generateJetStreamConsumer = (serviceName, streams) => [
1298
+ "",
1299
+ "// Start NATS JetStream consumer",
1300
+ "consumeJetStreams({",
1301
+ ` streams: [${streams.map((s) => `'${s}'`).join(", ")}],`,
1302
+ ` consumer: '${serviceName}',`,
1303
+ " discover: './src/events/**/*.handler.ts',",
1304
+ "})"
1305
+ ];
1306
+ var generateBunServe = () => [
1307
+ "",
1308
+ "Bun.serve({",
1309
+ " port,",
1310
+ " fetch: app.fetch,",
1311
+ "})",
1312
+ "",
1313
+ "console.log(`\u{1F680} Service ready at http://localhost:${port}`)"
1314
+ ];
1315
+ var generateIndexTs = (vars) => {
1316
+ const envKey = toEnvKey(vars.serviceName);
1317
+ const parts = [
1318
+ ...generateImports(vars.hasEvents),
1319
+ "",
1320
+ ...generateServerSetup(envKey),
1321
+ ...vars.hasEvents ? generateJetStreamConsumer(vars.serviceName, vars.streams) : [],
1322
+ ...generateBunServe()
1323
+ ];
1324
+ return `${parts.join("\n")}
1325
+ `;
1326
+ };
1327
+ var generatePackageJson = (vars) => {
1328
+ const dependencies = {
1329
+ hono: "^4.6.14",
1330
+ "@crossdelta/telemetry": "workspace:*",
1331
+ zod: "^4.1.0",
1332
+ // Always include contracts - user may add events later via "pf cloudevents add"
1333
+ [`${vars.workspaceScope}/contracts`]: "workspace:*"
1334
+ };
1335
+ if (vars.hasEvents) {
1336
+ dependencies["@crossdelta/cloudevents"] = "workspace:*";
1337
+ }
1338
+ const pkg = {
1339
+ name: vars.packageName,
1340
+ version: "0.0.1",
1341
+ type: "module",
1342
+ scripts: {
1343
+ "start:dev": "bun --watch src/index.ts",
1344
+ start: "bun src/index.ts",
1345
+ test: "bun test"
1346
+ },
1347
+ dependencies,
1348
+ devDependencies: {
1349
+ "@types/bun": "^1.2.23"
1350
+ }
1351
+ };
1352
+ return JSON.stringify(pkg, null, 2);
1353
+ };
1354
+ var generateReadme = (vars) => {
1355
+ return `# ${vars.serviceName}
1356
+
1357
+ ## Development
1358
+
1359
+ \`\`\`bash
1360
+ bun install
1361
+ bun dev
1362
+ \`\`\`
1363
+
1364
+ Service runs on port: \`${vars.port}\`
1365
+
1366
+ ## Environment Variables
1367
+
1368
+ - \`${vars.envVarName}_PORT\`: Service port (default: ${vars.port})
1369
+ ${vars.hasEvents ? `
1370
+ ## Events
1371
+
1372
+ This service consumes from:
1373
+ ${vars.streams.map((s) => `- ${s}`).join("\n")}` : ""}
1374
+ `;
1375
+ };
1376
+ var generateEventsGitkeep = () => "";
1377
+ var HonoBunTemplate = {
1378
+ name: "hono-bun",
1379
+ runtime: "bun",
1380
+ framework: "hono",
1381
+ files: [
1382
+ {
1383
+ path: "src/index.ts",
1384
+ content: generateIndexTs
1385
+ },
1386
+ {
1387
+ path: "src/config/env.ts",
1388
+ content: (vars) => generateHonoEnvTs(vars.serviceName, vars.envVars)
1389
+ },
1390
+ {
1391
+ path: "package.json",
1392
+ content: generatePackageJson
1393
+ },
1394
+ {
1395
+ path: "tsconfig.json",
1396
+ content: () => generateHonoTsConfig()
1397
+ },
1398
+ {
1399
+ path: "Dockerfile",
1400
+ content: () => generateHonoDockerfile()
1401
+ },
1402
+ {
1403
+ path: "biome.json",
1404
+ content: () => generateHonoBiomeJson()
1405
+ },
1406
+ {
1407
+ path: "README.md",
1408
+ content: generateReadme
1409
+ },
1410
+ {
1411
+ path: "src/events/.gitkeep",
1412
+ content: generateEventsGitkeep,
1413
+ skip: (vars) => !vars.hasEvents
1414
+ }
1415
+ ]
1416
+ };
1417
+
1418
+ // cli/src/commands/create/nest-microservice/templates.ts
1419
+ import { join as join9 } from "path";
1420
+ var getModuleDir2 = () => {
1421
+ if (typeof import.meta?.url === "string") {
1422
+ return getDirname(import.meta.url);
1423
+ }
1424
+ if (typeof __dirname === "string") return __dirname;
1425
+ return process.cwd();
1426
+ };
1427
+ var getTemplatesDir2 = () => {
1428
+ const moduleDir = getModuleDir2();
1429
+ const packageRoot = findPackageRoot();
1430
+ return findTemplatesDir(
1431
+ [
1432
+ join9(moduleDir, "templates", "nest-microservice"),
1433
+ join9(moduleDir, "..", "nest-microservice", "templates"),
1434
+ join9(packageRoot, "bin", "templates", "nest-microservice")
1435
+ // Global install fallback
1436
+ ],
1437
+ "NestJS templates directory not found."
1438
+ );
1439
+ };
1440
+ var renderTemplate3 = (templateName, data = {}) => createTemplateRenderer(getTemplatesDir2())(templateName, data);
1441
+ function generateNestMainTs(serviceName, defaultPort = 3e3) {
1442
+ return renderTemplate3("src/main.ts.hbs", {
1443
+ serviceName,
1444
+ envKey: toEnvKey(serviceName),
1445
+ displayName: toDisplayName(serviceName),
1446
+ defaultPort
1447
+ });
1448
+ }
1449
+ function generateAppContext() {
1450
+ return renderTemplate3("src/app.context.ts.hbs", {});
1451
+ }
1452
+ function generateEventsModule() {
1453
+ return renderTemplate3("src/events/events.module.ts.hbs", {});
1454
+ }
1455
+ function generateEventsService(serviceName) {
1456
+ return renderTemplate3("src/events/events.service.ts.hbs", { serviceName });
1457
+ }
1458
+ function generateNestDockerfile(nodeVersion = "24", bunVersion = "1.2.23") {
1459
+ return renderTemplate3("Dockerfile.hbs", { nodeVersion, bunVersion });
1460
+ }
1461
+ function generateNestBiomeJson() {
1462
+ return renderTemplate3("biome.json.hbs", {});
1463
+ }
1464
+ function generateNestEnvTs(serviceName, envVars = []) {
1465
+ return renderTemplate3("src/config/env.ts.hbs", {
1466
+ envKey: toEnvKey(serviceName),
1467
+ envVars
1468
+ });
1469
+ }
1470
+
1471
+ // cli/src/core/templates/nest.template.ts
1472
+ var generatePackageJson2 = (vars) => {
1473
+ const dependencies = {
1474
+ "@nestjs/common": "^11.0.0",
1475
+ "@nestjs/core": "^11.0.0",
1476
+ "@nestjs/platform-express": "^11.0.0",
1477
+ "@crossdelta/telemetry": "workspace:*",
1478
+ "reflect-metadata": "^0.2.0",
1479
+ "rxjs": "^7.8.0",
1480
+ "zod": "^4.1.0",
1481
+ // Always include contracts - user may add events later via "pf cloudevents add"
1482
+ [`${vars.workspaceScope}/contracts`]: "workspace:*"
1483
+ };
1484
+ if (vars.hasEvents) {
1485
+ dependencies["@crossdelta/cloudevents"] = "workspace:*";
1486
+ }
1487
+ const pkg = {
1488
+ name: vars.packageName,
1489
+ version: "0.0.1",
1490
+ scripts: {
1491
+ "start:dev": "bun --watch src/main.ts",
1492
+ start: "node dist/main.js",
1493
+ build: "tsc",
1494
+ test: "bun test"
1495
+ },
1496
+ dependencies,
1497
+ devDependencies: {
1498
+ "@nestjs/cli": "^11.0.0",
1499
+ "@types/node": "^24.0.0",
1500
+ typescript: "^5.7.0"
1501
+ }
1502
+ };
1503
+ return JSON.stringify(pkg, null, 2);
1504
+ };
1505
+ var generateTsConfig = () => {
1506
+ const config = {
1507
+ extends: "../../../../tsconfig.json",
1508
+ compilerOptions: {
1509
+ outDir: "./dist",
1510
+ rootDir: "./src",
1511
+ experimentalDecorators: true,
1512
+ emitDecoratorMetadata: true
1513
+ },
1514
+ include: ["src/**/*"],
1515
+ exclude: ["node_modules", "dist", "**/*.test.ts"]
1516
+ };
1517
+ return JSON.stringify(config, null, 2);
1518
+ };
1519
+ var generateAppModule = (vars) => {
1520
+ const imports = ["Module", "HttpModule"];
1521
+ const moduleImports = ["HttpModule"];
1522
+ if (vars.hasEvents) {
1523
+ imports.push("OnModuleInit");
1524
+ moduleImports.push("EventsModule");
1525
+ }
1526
+ return `import { ${imports.join(", ")} } from '@nestjs/common'
1527
+ ${vars.hasEvents ? "import { EventsModule } from './events/events.module'" : ""}
1528
+
1529
+ @Module({
1530
+ imports: [${moduleImports.join(", ")}],
1531
+ controllers: [],
1532
+ providers: [],
1533
+ })
1534
+ export class AppModule${vars.hasEvents ? " implements OnModuleInit" : ""} {
1535
+ ${vars.hasEvents ? "async onModuleInit() {\n // Events module initializes NATS consumer\n }" : ""}
1536
+ }
1537
+ `;
1538
+ };
1539
+ var generateAppController = () => {
1540
+ return `import { Controller, Get } from '@nestjs/common'
1541
+
1542
+ @Controller()
1543
+ export class AppController {
1544
+ @Get('health')
1545
+ health() {
1546
+ return { status: 'ok' }
1547
+ }
1548
+ }
1549
+ `;
1550
+ };
1551
+ var generateReadme2 = (vars) => {
1552
+ return `# ${vars.serviceName}
1553
+
1554
+ ## Development
1555
+
1556
+ \`\`\`bash
1557
+ bun install
1558
+ bun dev
1559
+ \`\`\`
1560
+
1561
+ Service runs on port: \`${vars.port}\`
1562
+
1563
+ ## Environment Variables
1564
+
1565
+ - \`${vars.envVarName}_PORT\`: Service port (default: ${vars.port})
1566
+ ${vars.hasEvents ? `
1567
+ ## Events
1568
+
1569
+ This service consumes from:
1570
+ ${vars.streams.map((s) => `- ${s}`).join("\n")}` : ""}
1571
+ `;
1572
+ };
1573
+ var generateEventsGitkeep2 = () => "";
1574
+ var NestTemplate = {
1575
+ name: "nest",
1576
+ runtime: "node",
1577
+ framework: "nest",
1578
+ files: [
1579
+ {
1580
+ path: "src/config/env.ts",
1581
+ content: (vars) => generateNestEnvTs(vars.serviceName, vars.envVars)
1582
+ },
1583
+ {
1584
+ path: "src/main.ts",
1585
+ content: (vars) => generateNestMainTs(vars.serviceName, vars.port)
1586
+ },
1587
+ {
1588
+ path: "src/app.module.ts",
1589
+ content: generateAppModule
1590
+ },
1591
+ {
1592
+ path: "src/app.controller.ts",
1593
+ content: () => generateAppController()
1594
+ },
1595
+ {
1596
+ path: "src/app.context.ts",
1597
+ content: () => generateAppContext()
1598
+ },
1599
+ {
1600
+ path: "package.json",
1601
+ content: generatePackageJson2
1602
+ },
1603
+ {
1604
+ path: "tsconfig.json",
1605
+ content: () => generateTsConfig()
1606
+ },
1607
+ {
1608
+ path: "Dockerfile",
1609
+ content: () => generateNestDockerfile()
1610
+ },
1611
+ {
1612
+ path: "biome.json",
1613
+ content: () => generateNestBiomeJson()
1614
+ },
1615
+ {
1616
+ path: "README.md",
1617
+ content: generateReadme2
1618
+ },
1619
+ {
1620
+ path: "src/events/events.module.ts",
1621
+ content: () => generateEventsModule(),
1622
+ skip: (vars) => !vars.hasEvents
1623
+ },
1624
+ {
1625
+ path: "src/events/events.service.ts",
1626
+ content: (vars) => generateEventsService(vars.serviceName),
1627
+ skip: (vars) => !vars.hasEvents
1628
+ },
1629
+ {
1630
+ path: "src/events/handlers/.gitkeep",
1631
+ content: generateEventsGitkeep2,
1632
+ skip: (vars) => !vars.hasEvents
1633
+ }
1634
+ ]
1635
+ };
1636
+
1637
+ // cli/src/core/templates/index.ts
1638
+ var templates = [HonoBunTemplate, NestTemplate];
1639
+ var selectTemplate = (type) => {
1640
+ const normalized = normalizeTemplateType(type);
1641
+ const template = templates.find((t) => t.name === normalized);
1642
+ if (!template) {
1643
+ throw new Error(`Template '${type}' not found. Available: ${templates.map((t) => t.name).join(", ")}`);
1644
+ }
1645
+ return template;
1646
+ };
1647
+ var normalizeTemplateType = (type) => {
1648
+ const aliases = {
1649
+ "hono-micro": "hono-bun",
1650
+ "nest-micro": "nest"
1651
+ };
1652
+ return aliases[type] || type;
1653
+ };
1654
+
1655
+ // cli/src/core/flows/effect-builder.ts
1656
+ import { join as join10 } from "path";
1657
+ import { deriveEventNames as deriveEventNames2 } from "@crossdelta/cloudevents";
1658
+
1659
+ // cli/src/core/policies/service-layout.ts
1660
+ var SERVICE_LAYOUT_POLICY_VERSION = 1;
1661
+ var defaultCodingConstraints = {
1662
+ appliesTo: ["src/domain", "src/use-cases"],
1663
+ forbiddenPatterns: [
1664
+ {
1665
+ pattern: "console\\.(log|error|warn|info|debug)\\s*\\(",
1666
+ description: "console.* is forbidden in business logic (use structured logging)",
1667
+ code: "CONSOLE_IN_DOMAIN"
1668
+ },
1669
+ {
1670
+ pattern: "process\\.env\\b",
1671
+ description: "process.env is forbidden in business logic (use dependency injection)",
1672
+ code: "PROCESS_ENV_IN_DOMAIN"
1673
+ },
1674
+ {
1675
+ pattern: "\\bfetch\\s*\\(",
1676
+ description: "fetch() is forbidden in business logic (use adapters/services)",
1677
+ code: "FETCH_IN_DOMAIN"
1678
+ }
1679
+ ]
1680
+ };
1681
+ var serviceLayoutPolicy = {
1682
+ hono: {
1683
+ businessLogicDir: "use-cases",
1684
+ eventsDir: "events",
1685
+ filePattern: "{name}.use-case.ts",
1686
+ suffix: "use-case",
1687
+ handlerDelegatesToBusinessLogic: true,
1688
+ pathGuards: {
1689
+ forbidDirs: ["src/services"]
1690
+ },
1691
+ codingConstraints: defaultCodingConstraints
1692
+ },
1693
+ nestjs: {
1694
+ businessLogicDir: "",
1695
+ eventsDir: "events",
1696
+ filePattern: "{name}.service.ts",
1697
+ suffix: "service",
1698
+ handlerDelegatesToBusinessLogic: false,
1699
+ pathGuards: {
1700
+ forbidDirs: []
1701
+ },
1702
+ codingConstraints: {
1703
+ appliesTo: ["src/domain"],
1704
+ forbiddenPatterns: defaultCodingConstraints.forbiddenPatterns
1705
+ }
1706
+ }
1707
+ };
1708
+ var toCamelCase = (name) => {
1709
+ return name.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
1710
+ };
1711
+ var calculateImportPath = (businessLogicDir, fileNameWithoutExt) => {
1712
+ return `../${businessLogicDir}/${fileNameWithoutExt}`;
1713
+ };
1714
+ var resolveServiceLayout = (framework, name) => {
1715
+ const layout = serviceLayoutPolicy[framework];
1716
+ const fileName = layout.filePattern.replace("{name}", name);
1717
+ const fileNameWithoutExt = fileName.replace(/\.ts$/, "");
1718
+ const dirPath = layout.businessLogicDir ? `src/${layout.businessLogicDir}` : "src";
1719
+ return {
1720
+ filePath: dirPath ? `${dirPath}/${fileName}` : `src/${fileName}`,
1721
+ dirPath,
1722
+ fileName,
1723
+ functionName: toCamelCase(name),
1724
+ delegateFromHandler: layout.handlerDelegatesToBusinessLogic,
1725
+ importFromEvents: layout.businessLogicDir ? calculateImportPath(layout.businessLogicDir, fileNameWithoutExt) : `./${fileNameWithoutExt}`
1726
+ };
1727
+ };
1728
+ var resolveEventLayout = (framework, eventName) => {
1729
+ const layout = serviceLayoutPolicy[framework];
1730
+ const dirPath = `src/${layout.eventsDir}`;
1731
+ const fileName = `${eventName}.handler.ts`;
1732
+ return {
1733
+ filePath: `${dirPath}/${fileName}`,
1734
+ dirPath,
1735
+ fileName
1736
+ };
1737
+ };
1738
+ var deriveUseCaseName = (eventType) => {
1739
+ const normalized = eventType.replace(/\./g, "-");
1740
+ return `handle-${normalized}`;
1741
+ };
1742
+ var normalizePath = (filePath) => {
1743
+ let normalized = filePath.replace(/\\/g, "/");
1744
+ normalized = normalized.replace(/^\.\//, "").replace(/^\//, "");
1745
+ const segments = normalized.split("/");
1746
+ const resolved = [];
1747
+ for (const segment of segments) {
1748
+ if (segment === "..") {
1749
+ resolved.pop();
1750
+ } else if (segment !== "." && segment !== "") {
1751
+ resolved.push(segment);
1752
+ }
1753
+ }
1754
+ return resolved.join("/");
1755
+ };
1756
+ var matchesForbiddenDir = (filePath, forbidDirs) => {
1757
+ const normalizedPath = normalizePath(filePath);
1758
+ for (const dir of forbidDirs) {
1759
+ const normalizedDir = normalizePath(dir);
1760
+ if (normalizedPath.startsWith(`${normalizedDir}/`) || normalizedPath === normalizedDir) {
1761
+ return dir;
1762
+ }
1763
+ }
1764
+ return null;
1765
+ };
1766
+ var isInConstrainedDir = (filePath, appliesTo) => {
1767
+ const normalizedPath = normalizePath(filePath);
1768
+ return appliesTo.some((dir) => {
1769
+ const normalizedDir = normalizePath(dir);
1770
+ return normalizedPath.startsWith(`${normalizedDir}/`) || normalizedPath === normalizedDir;
1771
+ });
1772
+ };
1773
+ var findPatternViolations = (content, patterns, filePath) => {
1774
+ const violations = [];
1775
+ const lines = content.split("\n");
1776
+ for (const { pattern, description, code } of patterns) {
1777
+ const regex = new RegExp(pattern, "g");
1778
+ for (let i = 0; i < lines.length; i++) {
1779
+ if (regex.test(lines[i])) {
1780
+ violations.push({
1781
+ code,
1782
+ message: description,
1783
+ path: filePath,
1784
+ line: i + 1
1785
+ });
1786
+ }
1787
+ regex.lastIndex = 0;
1788
+ }
1789
+ }
1790
+ return violations;
1791
+ };
1792
+ var validateFile = (file, layout) => {
1793
+ const violations = [];
1794
+ const forbiddenDir = matchesForbiddenDir(file.path, layout.pathGuards.forbidDirs);
1795
+ if (forbiddenDir) {
1796
+ violations.push({
1797
+ code: "FORBIDDEN_DIR",
1798
+ message: `Path '${file.path}' is in forbidden directory '${forbiddenDir}'`,
1799
+ path: file.path
1800
+ });
1801
+ }
1802
+ if (isInConstrainedDir(file.path, layout.codingConstraints.appliesTo)) {
1803
+ const patternViolations = findPatternViolations(
1804
+ file.content,
1805
+ layout.codingConstraints.forbiddenPatterns,
1806
+ file.path
1807
+ );
1808
+ violations.push(...patternViolations);
1809
+ }
1810
+ return violations;
1811
+ };
1812
+ var validateGeneratedFiles = (framework, files) => {
1813
+ const layout = serviceLayoutPolicy[framework];
1814
+ const violations = [];
1815
+ for (const file of files) {
1816
+ violations.push(...validateFile(file, layout));
1817
+ }
1818
+ return {
1819
+ valid: violations.length === 0,
1820
+ violations
1821
+ };
1822
+ };
1823
+ var getFrameworkPolicy = (framework) => serviceLayoutPolicy[framework];
1824
+
1825
+ // cli/src/core/flows/effect-builder.ts
1826
+ var buildFileEffects = (servicePath, files) => {
1827
+ return files.map((file) => ({
1828
+ kind: "file:write",
1829
+ path: join10(servicePath, file.path),
1830
+ content: file.content
1831
+ }));
1832
+ };
1833
+ var buildInfraEffect = (serviceName, port, template) => ({
1834
+ kind: "infra:add",
1835
+ serviceName,
1836
+ port,
1837
+ framework: template.framework,
1838
+ runtime: template.runtime
1839
+ });
1840
+ var buildEnvEffect = (envVarName, port) => ({
1841
+ kind: "env:add",
1842
+ key: `${envVarName}_PORT`,
1843
+ value: port.toString()
1844
+ });
1845
+ var generateUseCaseContent = (eventType, workspaceScope) => {
1846
+ const { typeName } = deriveEventNames2(eventType);
1847
+ const useCaseName = deriveUseCaseName(eventType);
1848
+ const layout = resolveServiceLayout("hono", useCaseName);
1849
+ return `import type { ${typeName} } from '${workspaceScope}/contracts'
1850
+
1851
+ /**
1852
+ * Handle ${eventType} event
1853
+ *
1854
+ * Business logic for processing the event.
1855
+ * Keep this pure where possible - side effects via returned data or adapters.
1856
+ */
1857
+ export const ${layout.functionName} = async (data: ${typeName}): Promise<void> => {
1858
+ // TODO: Implement business logic
1859
+ // Example: validate data, transform, call external services
1860
+ }
1861
+ `;
1862
+ };
1863
+ var generateEventHandlerContent = (eventType, workspaceScope, framework) => {
1864
+ const { contractName, typeName } = deriveEventNames2(eventType);
1865
+ const useCaseName = deriveUseCaseName(eventType);
1866
+ const layout = resolveServiceLayout(framework, useCaseName);
1867
+ if (layout.delegateFromHandler) {
1868
+ return `import { handleEvent } from '@crossdelta/cloudevents'
1869
+ import { ${contractName}, type ${typeName} } from '${workspaceScope}/contracts'
1870
+ import { ${layout.functionName} } from '${layout.importFromEvents}'
1871
+
1872
+ export default handleEvent(${contractName}, async (data: ${typeName}) => {
1873
+ console.log('\u{1F4E6} [${eventType}] Event received:', data)
1874
+ await ${layout.functionName}(data)
1875
+ })
1876
+ `;
1877
+ }
1878
+ return `import { handleEvent } from '@crossdelta/cloudevents'
1879
+ import { ${contractName}, type ${typeName} } from '${workspaceScope}/contracts'
1880
+
1881
+ export default handleEvent(${contractName}, async (data: ${typeName}) => {
1882
+ console.log('\u{1F4E6} [${eventType}] Event received:', data)
1883
+ // TODO: Inject and call service
1884
+ })
1885
+ `;
1886
+ };
1887
+ var buildUseCaseEffects = (servicePath, vars, framework) => {
1888
+ if (!vars.hasEvents || vars.events.length === 0) {
1889
+ return [];
1890
+ }
1891
+ const layout = resolveServiceLayout(framework, "stub");
1892
+ if (!layout.delegateFromHandler) {
1893
+ return [];
1894
+ }
1895
+ return vars.events.map((eventType) => {
1896
+ const useCaseName = deriveUseCaseName(eventType);
1897
+ const resolved = resolveServiceLayout(framework, useCaseName);
1898
+ return {
1899
+ kind: "file:write",
1900
+ path: join10(servicePath, resolved.filePath),
1901
+ content: generateUseCaseContent(eventType, vars.workspaceScope)
1902
+ };
1903
+ });
1904
+ };
1905
+ var buildEventHandlerEffects = (servicePath, vars, framework) => {
1906
+ if (!vars.hasEvents || vars.events.length === 0) {
1907
+ return [];
1908
+ }
1909
+ return vars.events.map((eventType) => {
1910
+ const { kebab } = deriveEventNames2(eventType);
1911
+ const eventLayout = resolveEventLayout(framework, kebab);
1912
+ return {
1913
+ kind: "file:write",
1914
+ path: join10(servicePath, eventLayout.filePath),
1915
+ content: generateEventHandlerContent(eventType, vars.workspaceScope, framework)
1916
+ };
1917
+ });
1918
+ };
1919
+ var mapFramework = (templateFramework) => {
1920
+ if (templateFramework === "hono") return "hono";
1921
+ if (templateFramework === "nest") return "nestjs";
1922
+ return "hono";
1923
+ };
1924
+ var buildServiceEffects = (servicePath, files, vars, template) => {
1925
+ const framework = mapFramework(template.framework);
1926
+ const fileEffects = buildFileEffects(servicePath, files);
1927
+ const useCaseEffects = buildUseCaseEffects(servicePath, vars, framework);
1928
+ const eventHandlerEffects = buildEventHandlerEffects(servicePath, vars, framework);
1929
+ const infraEffect = buildInfraEffect(vars.serviceName, vars.port, template);
1930
+ const envEffect = buildEnvEffect(vars.envVarName, vars.port);
1931
+ return [...fileEffects, ...useCaseEffects, ...eventHandlerEffects, infraEffect, envEffect];
1932
+ };
1933
+
1934
+ // cli/src/core/flows/port-allocation.ts
1935
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
1936
+ import { join as join11 } from "path";
1937
+ var INTERNAL_PORT_START = 4001;
1938
+ var extractPort = (content) => {
1939
+ const portsMatch = content.match(/ports\(\)\.(?:http|https|grpc|primary)\((\d+)\)/);
1940
+ if (portsMatch) {
1941
+ return Number.parseInt(portsMatch[1], 10);
1942
+ }
1943
+ const containerMatch = content.match(/containerPort:\s*(\d+)/);
1944
+ if (containerMatch) {
1945
+ return Number.parseInt(containerMatch[1], 10);
1946
+ }
1947
+ return null;
1948
+ };
1949
+ var scanUsedPorts = (workspaceRoot) => {
1950
+ try {
1951
+ const infraServicesPath = join11(workspaceRoot, "infra", "services");
1952
+ if (!existsSync6(infraServicesPath)) {
1953
+ return /* @__PURE__ */ new Set();
1954
+ }
1955
+ const files = readdirSync4(infraServicesPath).filter((f) => f.endsWith(".ts"));
1956
+ return files.reduce((ports, file) => {
1957
+ const content = readFileSync5(join11(infraServicesPath, file), "utf-8");
1958
+ const port = extractPort(content);
1959
+ if (port !== null) {
1960
+ ports.add(port);
1961
+ }
1962
+ return ports;
1963
+ }, /* @__PURE__ */ new Set());
1964
+ } catch (error) {
1965
+ console.warn("Failed to scan ports:", error);
1966
+ return /* @__PURE__ */ new Set();
1967
+ }
1968
+ };
1969
+ var allocateNextPort = (usedPorts, startPort) => {
1970
+ return usedPorts.has(startPort) ? allocateNextPort(usedPorts, startPort + 1) : startPort;
1971
+ };
1972
+
1973
+ // cli/src/core/flows/service-generation.flow.ts
1974
+ var validateServiceName = (name) => {
1975
+ if (!name || name.trim().length === 0) {
1976
+ throw new Error("Service name cannot be empty");
1977
+ }
1978
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
1979
+ throw new Error(`Service name '${name}' must be kebab-case (lowercase, alphanumeric, hyphens only)`);
1980
+ }
1981
+ };
1982
+ var resolveServicePath = (name, customPath, workspaceRoot) => {
1983
+ if (customPath) {
1984
+ if (customPath.startsWith("/")) {
1985
+ return customPath;
1986
+ }
1987
+ if (customPath.startsWith(".")) {
1988
+ return join12(workspaceRoot, customPath);
1989
+ }
1990
+ if (customPath.includes("/")) {
1991
+ return join12(workspaceRoot, customPath);
1992
+ }
1993
+ }
1994
+ const paths = getWorkspacePathsConfig(workspaceRoot);
1995
+ return join12(workspaceRoot, paths.services, name);
1996
+ };
1997
+ var deriveStreamsFromEvents = (events) => [
1998
+ ...new Set(events.map((e) => deriveEventNames3(e).streamName))
1999
+ ];
2000
+ var buildTemplateVars = (inputs, workspaceRoot, servicePath, port) => {
2001
+ const contractsConfig = getContractsConfig(workspaceRoot);
2002
+ const workspaceScope = contractsConfig.packageName.split("/")[0];
2003
+ const events = inputs.events ?? [];
2004
+ const hasEvents = events.length > 0 || inputs.hasEvents || false;
2005
+ const streams = events.length > 0 ? deriveStreamsFromEvents(events) : inputs.streams ?? [];
2006
+ return {
2007
+ serviceName: inputs.name,
2008
+ packageName: `${workspaceScope}/${inputs.name}`,
2009
+ servicePath: servicePath.replace(`${workspaceRoot}/`, ""),
2010
+ port,
2011
+ envVarName: toEnvKey(inputs.name),
2012
+ hasEvents,
2013
+ streams,
2014
+ events,
2015
+ workspaceScope,
2016
+ envVars: inputs.envVars
2017
+ };
2018
+ };
2019
+ var generateFiles = (template, vars) => {
2020
+ return template.files.filter((f) => !f.skip || !f.skip(vars)).map((f) => ({
2021
+ path: f.path,
2022
+ content: f.content(vars)
2023
+ }));
2024
+ };
2025
+ var buildArtifacts = (files, serviceName, servicePath) => {
2026
+ const fileArtifacts = files.map((f) => ({
2027
+ type: "file",
2028
+ path: join12(servicePath, f.path),
2029
+ description: `Service file: ${f.path}`
2030
+ }));
2031
+ return [
2032
+ ...fileArtifacts,
2033
+ {
2034
+ type: "config",
2035
+ path: `infra/services/${serviceName}.ts`,
2036
+ description: "Infrastructure configuration"
2037
+ },
2038
+ {
2039
+ type: "env",
2040
+ path: ".env.local",
2041
+ description: "Environment variables"
2042
+ }
2043
+ ];
2044
+ };
2045
+ var buildNextActions = (servicePath) => {
2046
+ return [
2047
+ {
2048
+ command: `cd ${servicePath} && bun dev`,
2049
+ description: "Start this service"
2050
+ },
2051
+ {
2052
+ command: "pf dev",
2053
+ description: "Start all services"
2054
+ }
2055
+ ];
2056
+ };
2057
+ var planServiceGeneration = (inputs) => {
2058
+ validateServiceName(inputs.name);
2059
+ const workspaceRoot = inputs.workspaceRoot ?? findWorkspaceRoot();
2060
+ const servicePath = resolveServicePath(inputs.name, inputs.path, workspaceRoot);
2061
+ const port = inputs.port ?? allocateNextPort(scanUsedPorts(workspaceRoot), INTERNAL_PORT_START);
2062
+ const vars = buildTemplateVars(inputs, workspaceRoot, servicePath, port);
2063
+ const template = selectTemplate(inputs.type);
2064
+ const files = generateFiles(template, vars);
2065
+ const effects = buildServiceEffects(servicePath, files, vars, template);
2066
+ const artifacts = buildArtifacts(files, inputs.name, servicePath);
2067
+ const next = buildNextActions(servicePath);
2068
+ return ok("service.generate", `Generated ${inputs.name} service (port ${port})`, {
2069
+ artifacts,
2070
+ changes: effects,
2071
+ next,
2072
+ data: {
2073
+ serviceName: inputs.name,
2074
+ servicePath,
2075
+ port,
2076
+ vars
2077
+ }
2078
+ });
2079
+ };
2080
+
2081
+ // cli/src/core/facade/index.ts
2082
+ init_config();
2083
+
2084
+ // cli/src/core/plugins/types.ts
2085
+ var isElicitInputEffect = (effect) => effect.kind === "elicit:input";
2086
+
2087
+ // cli/src/core/facade/context.ts
2088
+ init_config();
2089
+ var createContextFromWorkspace = async (workspaceRoot, logger) => {
2090
+ const root = workspaceRoot ?? findWorkspaceRoot();
2091
+ const contractsConfig = getContractsConfig(root);
2092
+ const availableServices = discoverAvailableServices(root);
2093
+ const workspace = {
2094
+ workspaceRoot: root,
2095
+ contracts: {
2096
+ path: contractsConfig.packagePath,
2097
+ packageName: contractsConfig.packageName
2098
+ },
2099
+ availableServices
2100
+ };
2101
+ return createPluginContext(workspace, logger);
2102
+ };
2103
+ var createContext = createPluginContext;
2104
+
2105
+ // cli/src/core/facade/tools.ts
2106
+ var argToProperty = (arg) => [
2107
+ arg.name,
2108
+ { type: "string", description: arg.description }
2109
+ ];
2110
+ var parseOptionName = (flags) => {
2111
+ const match = flags.match(/--([a-zA-Z0-9-]+)/);
2112
+ return match ? match[1] : null;
2113
+ };
2114
+ var optionToProperty = (opt) => {
2115
+ const name = parseOptionName(opt.flags);
2116
+ if (!name) return null;
2117
+ return [
2118
+ name,
2119
+ {
2120
+ type: "string",
2121
+ description: opt.description,
2122
+ ...opt.default !== void 0 && { default: opt.default }
2123
+ }
2124
+ ];
2125
+ };
2126
+ var buildInputSchema = (command) => {
2127
+ const argProperties = Object.fromEntries(command.args.map(argToProperty));
2128
+ const optionProperties = Object.fromEntries(
2129
+ command.options.map(optionToProperty).filter((entry) => entry !== null)
2130
+ );
2131
+ const required = command.args.filter((arg) => arg.required !== false).map((arg) => arg.name);
2132
+ return {
2133
+ type: "object",
2134
+ properties: { ...argProperties, ...optionProperties },
2135
+ ...required.length > 0 && { required }
2136
+ };
2137
+ };
2138
+ var buildToolName = (pluginName, commandName) => `${pluginName}.${commandName}`;
2139
+ var commandToToolSpec = (plugin, command) => ({
2140
+ name: buildToolName(plugin.name, command.name),
2141
+ description: command.description,
2142
+ inputSchema: buildInputSchema(command)
2143
+ });
2144
+ var commandToExecutableToolSpec = (plugin, command) => ({
2145
+ ...commandToToolSpec(plugin, command),
2146
+ execute: async (args) => {
2147
+ const result = await command.run(args, {});
2148
+ return {
2149
+ ok: true,
2150
+ operation: buildToolName(plugin.name, command.name),
2151
+ summary: formatOutput(result.output) ?? `Executed ${buildToolName(plugin.name, command.name)}`,
2152
+ effects: result.effects ?? [],
2153
+ output: result.output
2154
+ };
2155
+ }
2156
+ });
2157
+ var formatOutput = (output) => {
2158
+ if (!output) return void 0;
2159
+ return Array.isArray(output) ? output.join("\n") : output;
2160
+ };
2161
+ var collectToolSpecs = (plugins) => plugins.flatMap(({ plugin }) => plugin.commands.map((cmd) => commandToToolSpec(plugin, cmd)));
2162
+ var collectExecutableToolSpecs = (plugins, _context) => plugins.flatMap(({ plugin }) => plugin.commands.map((cmd) => commandToExecutableToolSpec(plugin, cmd)));
2163
+
2164
+ // cli/src/core/facade/index.ts
2165
+ var loadPlugins = async (moduleNames, context) => Promise.all(moduleNames.map((moduleName) => loadPluginFromModule(moduleName, context)));
2166
+ var runFlow = async (plugin, flowName, options) => {
2167
+ const flow = plugin.flows.find((f) => f.name === flowName);
2168
+ if (!flow) {
2169
+ throw new Error(`Flow '${flowName}' not found in plugin '${plugin.name}'`);
2170
+ }
2171
+ const steps = await flow.getSteps(options.initialContext);
2172
+ return flowcoreRunFlow(steps, options);
2173
+ };
2174
+ var getGeneratorDocsDir = () => {
2175
+ if (typeof import.meta?.url === "string") {
2176
+ const __filename = fileURLToPath3(import.meta.url);
2177
+ const __dirname2 = dirname5(__filename);
2178
+ return join13(__dirname2, "../bin/docs/generators");
2179
+ }
2180
+ if (typeof __dirname !== "undefined") {
2181
+ return join13(__dirname, "../bin/docs/generators");
2182
+ }
2183
+ throw new Error("Cannot determine docs directory: neither import.meta.url nor __dirname available");
2184
+ };
2185
+ var listGeneratorDocs = async () => {
2186
+ const docsDir = getGeneratorDocsDir();
2187
+ const files = await readdir(docsDir);
2188
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
2189
+ };
2190
+ export {
2191
+ SERVICE_LAYOUT_POLICY_VERSION,
2192
+ aggregateChanges,
2193
+ applyEffects,
2194
+ collectExecutableToolSpecs,
2195
+ collectToolSpecs,
2196
+ createContext,
2197
+ createContextFromWorkspace,
2198
+ err as createErrResult,
2199
+ ok as createOkResult,
2200
+ deriveEventNames4 as deriveEventNames,
2201
+ findWorkspaceRoot,
2202
+ formatChangeDetails,
2203
+ formatChangeSummary,
2204
+ getFrameworkPolicy,
2205
+ getGeneratorDocsDir,
2206
+ getRegisteredProviders,
2207
+ isElicitInputEffect,
2208
+ listGeneratorDocs,
2209
+ loadCapabilitiesConfig,
2210
+ loadCapabilitiesConfigFromWorkspace,
2211
+ loadPlugins,
2212
+ lookupProvider,
2213
+ lowerSpecToCapabilities,
2214
+ parseServicePrompt,
2215
+ planServiceGeneration,
2216
+ resolveProvider,
2217
+ runFlow,
2218
+ serviceLayoutPolicy,
2219
+ validateGeneratedFiles
2220
+ };
2221
+ //# sourceMappingURL=facade.mjs.map