@specverse/engines 4.1.5 → 4.1.6

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 (120) hide show
  1. package/dist/libs/instance-factories/applications/templates/generic/backend-env-generator.js +22 -0
  2. package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +66 -0
  3. package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +54 -0
  4. package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +290 -0
  5. package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +530 -0
  6. package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +437 -0
  7. package/dist/libs/instance-factories/applications/templates/react/api-types-generator.js +146 -0
  8. package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +73 -0
  9. package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +18 -0
  10. package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +99 -0
  11. package/dist/libs/instance-factories/applications/templates/react/gitignore-generator.js +35 -0
  12. package/dist/libs/instance-factories/applications/templates/react/index-css-generator.js +9 -0
  13. package/dist/libs/instance-factories/applications/templates/react/index-html-generator.js +23 -0
  14. package/dist/libs/instance-factories/applications/templates/react/main-tsx-generator.js +29 -0
  15. package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +49 -0
  16. package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +156 -0
  17. package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +935 -0
  18. package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +143 -0
  19. package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +101 -0
  20. package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +50 -0
  21. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +646 -0
  22. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +65 -0
  23. package/dist/libs/instance-factories/applications/templates/react/tsconfig-generator.js +28 -0
  24. package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +132 -0
  25. package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +143 -0
  26. package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +143 -0
  27. package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +355 -0
  28. package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +91 -0
  29. package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +79 -0
  30. package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +42 -0
  31. package/dist/libs/instance-factories/cli/templates/commander/cli-bin-wrapper-generator.js +11 -0
  32. package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +111 -0
  33. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +928 -0
  34. package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +83 -0
  35. package/dist/libs/instance-factories/communication/templates/eventemitter/publisher-generator.js +91 -0
  36. package/dist/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.js +86 -0
  37. package/dist/libs/instance-factories/controllers/templates/fastify/meta-routes-generator.js +93 -0
  38. package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +280 -0
  39. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +125 -0
  40. package/dist/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.js +25 -0
  41. package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +371 -0
  42. package/dist/libs/instance-factories/orms/templates/prisma/services-generator.js +266 -0
  43. package/dist/libs/instance-factories/scaffolding/templates/generic/env-example-generator.js +51 -0
  44. package/dist/libs/instance-factories/scaffolding/templates/generic/env-generator.js +61 -0
  45. package/dist/libs/instance-factories/scaffolding/templates/generic/gitignore-generator.js +59 -0
  46. package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +126 -0
  47. package/dist/libs/instance-factories/scaffolding/templates/generic/readme-generator.js +159 -0
  48. package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +56 -0
  49. package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-react-generator.js +37 -0
  50. package/dist/libs/instance-factories/sdks/templates/python/sdk-generator.js +29 -0
  51. package/dist/libs/instance-factories/sdks/templates/typescript/sdk-generator.js +28 -0
  52. package/dist/libs/instance-factories/services/templates/memory/generate-interpreter.js +14 -0
  53. package/dist/libs/instance-factories/services/templates/memory/step-conventions-memory.js +415 -0
  54. package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +177 -0
  55. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +413 -0
  56. package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +243 -0
  57. package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +264 -0
  58. package/dist/libs/instance-factories/services/templates/shared-patterns.js +24 -0
  59. package/dist/libs/instance-factories/shared/path-resolver.js +59 -0
  60. package/dist/libs/instance-factories/storage/templates/mongodb/config-generator.js +13 -0
  61. package/dist/libs/instance-factories/storage/templates/mongodb/docker-generator.js +16 -0
  62. package/dist/libs/instance-factories/storage/templates/postgresql/config-generator.js +45 -0
  63. package/dist/libs/instance-factories/storage/templates/postgresql/docker-generator.js +46 -0
  64. package/dist/libs/instance-factories/storage/templates/redis/config-generator.js +14 -0
  65. package/dist/libs/instance-factories/storage/templates/redis/docker-generator.js +16 -0
  66. package/dist/libs/instance-factories/test-generation.js +145 -0
  67. package/dist/libs/instance-factories/testing/templates/vitest/tests-generator.js +30 -0
  68. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +149 -0
  69. package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +232 -0
  70. package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +49 -0
  71. package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +18 -0
  72. package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
  73. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +97 -0
  74. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +64 -0
  75. package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +182 -0
  76. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +1210 -0
  77. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +172 -0
  78. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +240 -0
  79. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +147 -0
  80. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +281 -0
  81. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +409 -0
  82. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +414 -0
  83. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +467 -0
  84. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +135 -0
  85. package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
  86. package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +965 -0
  87. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +238 -0
  88. package/dist/libs/instance-factories/validation/templates/zod/validation-generator.js +25 -0
  89. package/dist/libs/instance-factories/views/index.js +48 -0
  90. package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +742 -0
  91. package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +824 -0
  92. package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +719 -0
  93. package/dist/libs/instance-factories/views/templates/react/app-generator.js +45 -0
  94. package/dist/libs/instance-factories/views/templates/react/components-generator.js +779 -0
  95. package/dist/libs/instance-factories/views/templates/react/forms-generator.js +285 -0
  96. package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +46 -0
  97. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +111 -0
  98. package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +9 -0
  99. package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +23 -0
  100. package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +21 -0
  101. package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +299 -0
  102. package/dist/libs/instance-factories/views/templates/react/router-generator.js +136 -0
  103. package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +107 -0
  104. package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +179 -0
  105. package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +7 -0
  106. package/dist/libs/instance-factories/views/templates/react/types-generator.js +56 -0
  107. package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +27 -0
  108. package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +29 -0
  109. package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +261 -0
  110. package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +34 -0
  111. package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +800 -0
  112. package/dist/libs/instance-factories/views/templates/shared/base-generator.js +305 -0
  113. package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +517 -0
  114. package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
  115. package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +445 -0
  116. package/dist/libs/instance-factories/views/templates/shared/index.js +80 -0
  117. package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +210 -0
  118. package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +492 -0
  119. package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +321 -0
  120. package/package.json +3 -2
@@ -0,0 +1,928 @@
1
+ function generateCommand(context) {
2
+ const { command } = context;
3
+ if (!command) {
4
+ throw new Error("Command is required in template context");
5
+ }
6
+ const name = command.name;
7
+ const description = command.description || "";
8
+ const args = command.arguments || {};
9
+ const flags = command.flags || {};
10
+ const exitCodes = command.exitCodes || {};
11
+ const subcommands = command.subcommands || {};
12
+ const serviceRef = command.serviceRef;
13
+ const positionalArgs = Object.entries(args).filter(([_, arg]) => arg.positional).sort((a, b) => (a[1].position || 0) - (b[1].position || 0)).map(([argName, arg]) => {
14
+ const required = arg.required;
15
+ return required ? `<${argName}>` : `[${argName}]`;
16
+ }).join(" ");
17
+ const commandStr = positionalArgs ? `${name} ${positionalArgs}` : name;
18
+ const optionDefs = Object.entries(flags).map(([flagName, flag]) => {
19
+ const alias = flag.alias ? `${flag.alias}, ` : "";
20
+ const flagType = flag.type?.toLowerCase();
21
+ const valuePart = flagType === "boolean" ? "" : ` <${flagName.replace(/^--/, "")}>`;
22
+ const defaultVal = flag.default !== void 0 ? `, ${JSON.stringify(flag.default)}` : "";
23
+ const desc = flag.description || `${flagName} option`;
24
+ return ` .option('${alias}${flagName}${valuePart}', '${desc}'${defaultVal})`;
25
+ });
26
+ const optionTypes = Object.entries(flags).map(([flagName, flag]) => {
27
+ const tsType = mapFlagTypeToTS(flag.type);
28
+ const key = flagName.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
29
+ return ` ${key}${flag.required ? "" : "?"}: ${tsType};`;
30
+ });
31
+ const argTypes = Object.entries(args).filter(([_, arg]) => arg.positional).map(([argName, arg]) => {
32
+ const tsType = mapArgTypeToTS(arg.type);
33
+ return `${argName}: ${tsType}`;
34
+ });
35
+ const actionParams = argTypes.length > 0 ? argTypes.join(", ") + ", options: CommandOptions" : "options: CommandOptions";
36
+ const exitCodeComments = Object.entries(exitCodes).length > 0 ? Object.entries(exitCodes).map(
37
+ ([code, meaning]) => ` // ${code}: ${meaning}`
38
+ ).join("\n") : "";
39
+ const hasSubcommands = Object.keys(subcommands).length > 0;
40
+ const subcommandRegistrations = hasSubcommands ? generateSubcommandRegistrations(name, subcommands) : "";
41
+ const serviceImport = serviceRef ? `import { ${serviceRef} } from '../services/${serviceRef}.js';` : "";
42
+ const allImportSets = [];
43
+ if (ENGINE_HANDLERS[name]?.imports) allImportSets.push(ENGINE_HANDLERS[name].imports);
44
+ if (hasSubcommands) {
45
+ for (const subName of Object.keys(subcommands)) {
46
+ const key = `${name}.${subName}`;
47
+ if (ENGINE_HANDLERS[key]?.imports) allImportSets.push(ENGINE_HANDLERS[key].imports);
48
+ }
49
+ }
50
+ const engineImports = deduplicateImports(allImportSets);
51
+ return `/**
52
+ * ${name} command
53
+ * ${description}
54
+ * Generated from SpecVerse specification
55
+ */
56
+
57
+ import { Command } from 'commander';
58
+ ${serviceImport}
59
+ ${engineImports}
60
+
61
+ interface CommandOptions {
62
+ ${optionTypes.length > 0 ? optionTypes.join("\n") : " [key: string]: any;"}
63
+ }
64
+
65
+ ${exitCodeComments ? `/**
66
+ * Exit codes:
67
+ ${exitCodeComments}
68
+ */` : ""}
69
+
70
+ /**
71
+ * Register the ${name} command on the program.
72
+ */
73
+ export function register${capitalize(name)}Command(program: Command): void {
74
+ ${hasSubcommands ? generateCommandWithSubcommands(name, description, subcommands, optionDefs) : generateLeafCommand(name, description, commandStr, optionDefs, actionParams, serviceRef, exitCodes)}
75
+ }
76
+ ${subcommandRegistrations}
77
+ `;
78
+ }
79
+ const ENGINE_HANDLERS = {
80
+ validate: {
81
+ imports: `import { readFileSync, existsSync } from 'fs';
82
+ import { EngineRegistry } from '@specverse/entities';
83
+ import type { ParserEngine } from '@specverse/types';`,
84
+ handler: `if (!existsSync(file)) {
85
+ console.error('File not found:', file);
86
+ process.exit(1);
87
+ }
88
+
89
+ // Discover and initialize parser engine
90
+ const registry = new EngineRegistry();
91
+ await registry.discover();
92
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
93
+ if (!parser) {
94
+ console.error('No parser engine found. Install @specverse/engines.');
95
+ process.exit(1);
96
+ }
97
+ await parser.initialize();
98
+
99
+ const content = readFileSync(file, 'utf8');
100
+ const result = parser.parseContent(content, file);
101
+
102
+ if (result.errors.length > 0) {
103
+ console.error('Validation failed');
104
+ result.errors.forEach((e: string) => console.error(' ', e));
105
+ if (result.warnings && result.warnings.length > 0) {
106
+ console.warn('Warnings:');
107
+ result.warnings.forEach((w: string) => console.warn(' ', w));
108
+ }
109
+ process.exit(1);
110
+ }
111
+
112
+ if (options.json) {
113
+ console.log(JSON.stringify({ valid: true, warnings: result.warnings }, null, 2));
114
+ } else {
115
+ console.log('Validation successful');
116
+ if (result.warnings && result.warnings.length > 0) {
117
+ console.warn('Warnings:');
118
+ result.warnings.forEach((w: string) => console.warn(' ', w));
119
+ }
120
+ }`
121
+ },
122
+ infer: {
123
+ imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
124
+ import { EngineRegistry } from '@specverse/entities';
125
+ import type { ParserEngine, InferenceEngine } from '@specverse/types';`,
126
+ handler: `if (!existsSync(file)) {
127
+ console.error('File not found:', file);
128
+ process.exit(1);
129
+ }
130
+
131
+ console.log('Running inference on', file, '...');
132
+
133
+ // Discover engines
134
+ const registry = new EngineRegistry();
135
+ await registry.discover();
136
+
137
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
138
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
139
+ await parser.initialize();
140
+
141
+ const inferEngine = registry.getEngineForCapability('infer') as InferenceEngine;
142
+ if (!inferEngine) { console.error('No inference engine found.'); process.exit(1); }
143
+ await inferEngine.initialize({ options: { verbose: options.verbose } });
144
+
145
+ const content = readFileSync(file, 'utf8');
146
+ const parseResult = parser.parseContent(content, file);
147
+
148
+ if (parseResult.errors.length > 0) {
149
+ console.error('Cannot infer from invalid specification:');
150
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
151
+ process.exit(1);
152
+ }
153
+
154
+ const ast = parseResult.ast!;
155
+ const inferResult = await inferEngine.infer(ast, {
156
+ generateControllers: true,
157
+ generateServices: true,
158
+ generateEvents: true,
159
+ generateViews: true,
160
+ generateDeployment: options.deployment || false,
161
+ verbose: options.verbose || false,
162
+ });
163
+
164
+ const outputFile = options.output || file.replace(/\\.specly$/, '-inferred.specly');
165
+ writeFileSync(outputFile, inferResult.yaml, 'utf8');
166
+ console.log('Inferred specification written to:', outputFile);`
167
+ },
168
+ realize: {
169
+ imports: `import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
170
+ import { resolve, join } from 'path';
171
+ import { EngineRegistry } from '@specverse/entities';
172
+ import type { ParserEngine, InferenceEngine, RealizeEngine } from '@specverse/types';`,
173
+ handler: `if (!existsSync(file)) {
174
+ console.error('File not found:', file);
175
+ process.exit(1);
176
+ }
177
+
178
+ // Discover engines
179
+ const registry = new EngineRegistry();
180
+ await registry.discover();
181
+
182
+ // Parse \u2014 let the parser engine handle its own schema
183
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
184
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
185
+ await parser.initialize();
186
+
187
+ const content = readFileSync(file, 'utf8');
188
+ const parseResult = parser.parseContent(content, file);
189
+ if (parseResult.errors.length > 0) {
190
+ console.error('Invalid spec:');
191
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
192
+ process.exit(1);
193
+ }
194
+
195
+ // Infer \u2014 let the inference engine handle its own rules
196
+ const inferEngine = registry.getEngineForCapability('infer') as InferenceEngine;
197
+ if (!inferEngine) { console.error('No inference engine found.'); process.exit(1); }
198
+ await inferEngine.initialize();
199
+ const inferResult = await inferEngine.infer(parseResult.ast!, {
200
+ generateControllers: true, generateServices: true,
201
+ generateEvents: true, generateViews: true,
202
+ });
203
+
204
+ // Load inferred YAML and flatten component data for realize
205
+ const yaml = await import('js-yaml');
206
+ const inferredYaml = yaml.load(inferResult.yaml) as any;
207
+ // Inference output: { components: { Name: { models, controllers, ... } } }
208
+ // realizeAll expects: { models: {...}, controllers: {...}, ... } (flat component data)
209
+ const componentName = Object.keys(inferredYaml?.components || {})[0];
210
+ const componentData = componentName ? inferredYaml.components[componentName] : {};
211
+
212
+ // Merge original spec's services/events/views that inference didn't generate
213
+ // (inference generates from models; explicit services in the spec are preserved here)
214
+ const origComponent = parseResult.ast!.components?.[0] || {};
215
+ for (const section of ['services', 'events', 'views']) {
216
+ const origData = (origComponent as any)[section];
217
+ if (origData && (!componentData[section] || JSON.stringify(componentData[section]) === '{}')) {
218
+ // Convert array form (from parser) to object form (for realize)
219
+ if (Array.isArray(origData)) {
220
+ componentData[section] = {};
221
+ for (const item of origData) {
222
+ componentData[section][item.name] = item;
223
+ }
224
+ } else {
225
+ componentData[section] = origData;
226
+ }
227
+ }
228
+ }
229
+
230
+ // Inject key as name into entity maps, and expand convention-format attributes
231
+ // Iterate all object-valued sections (not hardcoded \u2014 covers any entity type)
232
+ const entitySections = Object.keys(componentData).filter(k =>
233
+ componentData[k] && typeof componentData[k] === 'object' && !Array.isArray(componentData[k])
234
+ && !['version', 'description', 'name', 'componentName', 'commonDefinitions'].includes(k)
235
+ );
236
+ for (const section of entitySections) {
237
+ if (componentData[section] && typeof componentData[section] === 'object' && !Array.isArray(componentData[section])) {
238
+ for (const [key, value] of Object.entries(componentData[section])) {
239
+ if (value && typeof value === 'object') {
240
+ (value as any).name = key;
241
+ // Expand convention-format attributes: { attrName: "Type modifiers" } \u2192 [{ name, type, ... }]
242
+ if ((value as any).attributes && typeof (value as any).attributes === 'object' && !Array.isArray((value as any).attributes)) {
243
+ (value as any).attributes = Object.entries((value as any).attributes).map(([attrName, attrDef]: [string, any]) => {
244
+ if (typeof attrDef === 'string') {
245
+ const parts = attrDef.split(' ');
246
+ return { name: attrName, type: parts[0], required: parts.includes('required'), unique: parts.includes('unique'), auto: parts.find((p: string) => p.startsWith('auto='))?.split('=')[1] };
247
+ }
248
+ return { name: attrName, ...(typeof attrDef === 'object' ? attrDef : {}) };
249
+ });
250
+ }
251
+ // Expand convention-format relationships: { relName: "type Target modifiers" } \u2192 array
252
+ if ((value as any).relationships && typeof (value as any).relationships === 'object' && !Array.isArray((value as any).relationships)) {
253
+ (value as any).relationships = Object.entries((value as any).relationships).map(([relName, relDef]: [string, any]) => {
254
+ if (typeof relDef === 'object') return { name: relName, ...relDef };
255
+ if (typeof relDef === 'string') {
256
+ const parts = relDef.split(' ');
257
+ return { name: relName, type: parts[0], target: parts[1], cascade: parts.includes('cascade') };
258
+ }
259
+ return { name: relName };
260
+ });
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ const inferredSpec = { ...componentData, componentName, components: inferredYaml?.components || {} };
267
+
268
+ // Realize \u2014 let the realize engine handle its own library
269
+ const manifestPath = options.manifest || resolve(process.cwd(), 'manifests/implementation.yaml');
270
+ if (!existsSync(manifestPath)) {
271
+ console.error('Manifest not found:', manifestPath);
272
+ process.exit(1);
273
+ }
274
+
275
+ // Runtime mode (default): swap ReactApp \u2192 ReactAppRuntime in manifest
276
+ let effectiveManifestPath = manifestPath;
277
+ if (!options.static) {
278
+ const manifestContent = readFileSync(manifestPath, 'utf8');
279
+ if (/instanceFactory:\\s*["']?ReactApp["']?\\s*$/m.test(manifestContent)) {
280
+ const { tmpdir } = await import('os');
281
+ const runtimeManifest = manifestContent.replace(
282
+ /instanceFactory:\\s*["']?ReactApp["']?\\s*$/gm,
283
+ 'instanceFactory: "ReactAppRuntime"'
284
+ );
285
+ effectiveManifestPath = join(tmpdir(), 'specverse-runtime-manifest.yaml');
286
+ writeFileSync(effectiveManifestPath, runtimeManifest, 'utf8');
287
+ console.log(' Using runtime mode (slim frontend with @specverse/runtime)');
288
+ }
289
+ } else {
290
+ console.log(' Using static mode (full frontend generation)');
291
+ }
292
+
293
+ const realizeEngine = registry.getEngineForCapability('realize') as RealizeEngine;
294
+ if (!realizeEngine) { console.error('No realize engine found.'); process.exit(1); }
295
+ await realizeEngine.initialize({ manifestPath: effectiveManifestPath, workingDir: process.cwd() });
296
+
297
+ const outputDir = options.output || resolve(process.cwd(), 'generated/code');
298
+ console.log('Realizing ' + type + ' from ' + file + '...');
299
+ await (realizeEngine as any).realizeAll(inferredSpec, outputDir);
300
+
301
+ // Write dev.specly for runtime mode
302
+ if (!options.static && inferResult.devYaml) {
303
+ const frontendDir = join(outputDir, 'frontend', 'src');
304
+ mkdirSync(frontendDir, { recursive: true });
305
+ const devSpeclyPath = join(frontendDir, 'dev.specly');
306
+ writeFileSync(devSpeclyPath, inferResult.devYaml, 'utf8');
307
+ console.log(' Wrote dev.specly \u2192', devSpeclyPath);
308
+ }`
309
+ },
310
+ init: {
311
+ imports: `import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
312
+ import { resolve, join, dirname } from 'path';
313
+ import { fileURLToPath } from 'url';`,
314
+ handler: `const __fn = fileURLToPath(import.meta.url);
315
+ const __dn = dirname(__fn);
316
+
317
+ // Resolve templates from package.json "specverse.templateDir" config
318
+ const templatesDir = await (async () => {
319
+ const { createRequire } = await import('module');
320
+ const require = createRequire(import.meta.url);
321
+
322
+ // Find our own package.json by walking up
323
+ let dir = __dn;
324
+ while (dir !== dirname(dir)) {
325
+ const pkgPath = join(dir, 'package.json');
326
+ if (existsSync(pkgPath)) {
327
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
328
+ if (pkg.specverse?.templateDir) {
329
+ const candidate = resolve(dir, pkg.specverse.templateDir);
330
+ if (existsSync(candidate)) return candidate;
331
+ }
332
+ }
333
+ dir = dirname(dir);
334
+ }
335
+
336
+ // Fallback: engine-realize assets
337
+ try {
338
+ const realizePkg = dirname(require.resolve('@specverse/engine-realize/package.json'));
339
+ const candidate = join(realizePkg, 'assets', 'templates');
340
+ if (existsSync(candidate)) return candidate;
341
+ } catch { /* not installed */ }
342
+
343
+ return null;
344
+ })();
345
+
346
+ if (!templatesDir) {
347
+ console.error('Templates not found. Set "specverse.templateDir" in package.json or install @specverse/engine-realize.');
348
+ process.exit(1);
349
+ }
350
+
351
+ if (options.list) {
352
+ const templates = readdirSync(templatesDir).filter(
353
+ d => statSync(join(templatesDir!, d)).isDirectory()
354
+ );
355
+ console.log('Available templates:');
356
+ templates.forEach(t => console.log(' ' + t));
357
+ return;
358
+ }
359
+
360
+ const projectName = name || 'my-project';
361
+ const templateName = options.template || 'default';
362
+ const templateDir = join(templatesDir, templateName);
363
+
364
+ if (!existsSync(templateDir)) {
365
+ console.error('Template not found: ' + templateName);
366
+ console.error('Available: ' + readdirSync(templatesDir).filter(
367
+ d => statSync(join(templatesDir!, d)).isDirectory()
368
+ ).join(', '));
369
+ process.exit(1);
370
+ }
371
+
372
+ const destDir = resolve(process.cwd(), projectName);
373
+ if (existsSync(destDir)) {
374
+ console.error('Directory already exists: ' + destDir);
375
+ process.exit(1);
376
+ }
377
+
378
+ // Copy template with variable substitution
379
+ const kebab = projectName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
380
+ const component = projectName.charAt(0).toUpperCase() + projectName.slice(1);
381
+ const vars: Record<string, string> = {
382
+ '{{PROJECT_NAME}}': projectName,
383
+ '{{projectName}}': projectName,
384
+ '{{projectNameKebab}}': kebab,
385
+ '{{componentName}}': component,
386
+ '{{DB_USER}}': 'postgres',
387
+ '{{DB_PASSWORD}}': 'postgres',
388
+ };
389
+
390
+ function copyDir(src: string, dest: string) {
391
+ mkdirSync(dest, { recursive: true });
392
+ for (const item of readdirSync(src)) {
393
+ const srcPath = join(src, item);
394
+ let destName = item;
395
+ if (item === 'gitignore') destName = '.gitignore';
396
+ if (item === 'dot.env.example') destName = '.env.example';
397
+ const destPath = join(dest, destName);
398
+
399
+ if (statSync(srcPath).isDirectory()) {
400
+ copyDir(srcPath, destPath);
401
+ } else {
402
+ let content = readFileSync(srcPath, 'utf8');
403
+ for (const [key, val] of Object.entries(vars)) {
404
+ content = content.split(key).join(val);
405
+ }
406
+ writeFileSync(destPath, content);
407
+ }
408
+ }
409
+ }
410
+
411
+ copyDir(templateDir, destDir);
412
+ console.log('Project created: ' + destDir);
413
+ console.log('Template: ' + templateName);
414
+ console.log('');
415
+ console.log('Next steps:');
416
+ console.log(' cd ' + projectName);
417
+ console.log(' npm run setup # validate, generate code, install deps, setup db');
418
+ console.log(' npm run dev:backend # start backend (terminal 1)');
419
+ console.log(' npm run dev:frontend # start frontend (terminal 2)');`
420
+ },
421
+ // === gen subcommands ===
422
+ "gen.diagrams": {
423
+ imports: `import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
424
+ import { resolve, dirname, basename, join } from 'path';
425
+ import { fileURLToPath } from 'url';
426
+ import { EngineRegistry } from '@specverse/entities';
427
+ import type { ParserEngine } from '@specverse/types';`,
428
+ handler: `const registry = new EngineRegistry();
429
+ await registry.discover();
430
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
431
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
432
+ await parser.initialize();
433
+
434
+ const content = readFileSync(file, 'utf8');
435
+ const parseResult = parser.parseContent(content, file);
436
+ if (parseResult.errors.length > 0) {
437
+ console.error('Invalid spec:');
438
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
439
+ process.exit(1);
440
+ }
441
+
442
+ const gen = registry.getEngineForCapability('generate-diagrams') as any;
443
+ if (!gen) { console.error('No generators engine found.'); process.exit(1); }
444
+ await gen.initialize();
445
+
446
+ const diagrams = await gen.generateDiagrams(parseResult.ast!, { type: options.type || 'all' });
447
+ const outputDir = options.output || basename(file, '.specly') + '-diagrams';
448
+ if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
449
+ for (const [diagramType, diagramContent] of diagrams.entries()) {
450
+ writeFileSync(join(outputDir, diagramType + '.mmd'), diagramContent);
451
+ console.log(' ' + diagramType);
452
+ }
453
+ console.log('Generated ' + diagrams.size + ' diagrams in: ' + outputDir);`
454
+ },
455
+ "gen.docs": {
456
+ imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
457
+ import { resolve, dirname, basename } from 'path';
458
+ import { fileURLToPath } from 'url';
459
+ import { EngineRegistry } from '@specverse/entities';
460
+ import type { ParserEngine } from '@specverse/types';`,
461
+ handler: `const registry = new EngineRegistry();
462
+ await registry.discover();
463
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
464
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
465
+ await parser.initialize();
466
+
467
+ const content = readFileSync(file, 'utf8');
468
+ const parseResult = parser.parseContent(content, file);
469
+ if (parseResult.errors.length > 0) {
470
+ console.error('Invalid spec:');
471
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
472
+ process.exit(1);
473
+ }
474
+
475
+ const gen = registry.getEngineForCapability('generate-docs') as any;
476
+ if (!gen) { console.error('No generators engine found.'); process.exit(1); }
477
+ await gen.initialize();
478
+
479
+ const docs = await gen.generateDocs(parseResult.ast!, { format: options.format || 'markdown' });
480
+ const ext = options.format === 'html' ? '.html' : '.md';
481
+ const outputFile = options.output || basename(file, '.specly') + '-docs' + ext;
482
+ writeFileSync(outputFile, docs);
483
+ console.log('Documentation generated: ' + outputFile);`
484
+ },
485
+ "gen.uml": {
486
+ imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
487
+ import { resolve, dirname, basename } from 'path';
488
+ import { fileURLToPath } from 'url';
489
+ import { EngineRegistry } from '@specverse/entities';
490
+ import type { ParserEngine } from '@specverse/types';`,
491
+ handler: `const registry = new EngineRegistry();
492
+ await registry.discover();
493
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
494
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
495
+ await parser.initialize();
496
+
497
+ const content = readFileSync(file, 'utf8');
498
+ const parseResult = parser.parseContent(content, file);
499
+ if (parseResult.errors.length > 0) {
500
+ console.error('Invalid spec:');
501
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
502
+ process.exit(1);
503
+ }
504
+
505
+ const gen = registry.getEngineForCapability('generate-uml') as any;
506
+ if (!gen) { console.error('No generators engine found.'); process.exit(1); }
507
+ await gen.initialize();
508
+
509
+ const uml = await gen.generateUML(parseResult.ast!, { type: options.type || 'all' });
510
+ const outputFile = basename(file, '.specly') + '-uml.puml';
511
+ writeFileSync(outputFile, uml);
512
+ console.log('UML generated: ' + outputFile);`
513
+ },
514
+ // === dev subcommands ===
515
+ "dev.format": {
516
+ imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
517
+ import { resolve, dirname } from 'path';
518
+ import { fileURLToPath } from 'url';
519
+ import { EngineRegistry } from '@specverse/entities';
520
+ import type { ParserEngine } from '@specverse/types';`,
521
+ handler: `const registry = new EngineRegistry();
522
+ await registry.discover();
523
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
524
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
525
+ await parser.initialize();
526
+
527
+ const content = readFileSync(file, 'utf8');
528
+ const result = parser.parseContent(content, file);
529
+ if (result.errors.length > 0) {
530
+ console.error('Cannot format invalid spec:');
531
+ result.errors.forEach((e: string) => console.error(' ', e));
532
+ process.exit(1);
533
+ }
534
+ const yaml = await import('js-yaml');
535
+ const formatted = yaml.dump(yaml.load(content), { lineWidth: 120, noRefs: true });
536
+ if (options.write) {
537
+ writeFileSync(file, formatted);
538
+ console.log('Formatted and saved: ' + file);
539
+ } else {
540
+ console.log(formatted);
541
+ }`
542
+ },
543
+ "dev.watch": {
544
+ imports: `import { readFileSync, existsSync, watch } from 'fs';
545
+ import { resolve, dirname } from 'path';
546
+ import { fileURLToPath } from 'url';
547
+ import { EngineRegistry } from '@specverse/entities';
548
+ import type { ParserEngine } from '@specverse/types';`,
549
+ handler: `const registry = new EngineRegistry();
550
+ await registry.discover();
551
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
552
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
553
+ await parser.initialize();
554
+
555
+ console.log('Watching ' + file + ' for changes...');
556
+ const doValidate = () => {
557
+ try {
558
+ const c = readFileSync(file, 'utf8');
559
+ const r = parser.parseContent(c, file);
560
+ if (r.errors.length > 0) {
561
+ console.log('[' + new Date().toLocaleTimeString() + '] FAILED');
562
+ r.errors.forEach((e: string) => console.error(' ', e));
563
+ } else {
564
+ console.log('[' + new Date().toLocaleTimeString() + '] Valid');
565
+ }
566
+ } catch (e: any) { console.error('Watch error:', e.message); }
567
+ };
568
+ doValidate();
569
+ watch(file, doValidate);
570
+ await new Promise(() => {});`
571
+ },
572
+ "dev.quick": {
573
+ imports: `import { readFileSync, existsSync } from 'fs';
574
+ import { resolve, dirname } from 'path';
575
+ import { fileURLToPath } from 'url';
576
+ import { EngineRegistry } from '@specverse/entities';
577
+ import type { ParserEngine } from '@specverse/types';`,
578
+ handler: `const registry = new EngineRegistry();
579
+ await registry.discover();
580
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
581
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
582
+ await parser.initialize();
583
+
584
+ const content = readFileSync(file, 'utf8');
585
+ const result = parser.parseContent(content, file);
586
+ if (result.errors.length > 0) {
587
+ console.error('Quick check: FAILED');
588
+ result.errors.forEach((e: string) => console.error(' ', e));
589
+ process.exit(1);
590
+ }
591
+ console.log('Quick check: OK');`
592
+ },
593
+ // === cache command (leaf, not subcommand) ===
594
+ cache: {
595
+ imports: `import { EngineRegistry } from '@specverse/entities';
596
+ import type { ParserEngine } from '@specverse/types';`,
597
+ handler: `const registry = new EngineRegistry();
598
+ await registry.discover();
599
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
600
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
601
+ await parser.initialize();
602
+
603
+ // Access ImportResolver cache via parser
604
+ const resolverModule = await import('@specverse/engines/parser');
605
+ const resolver = new resolverModule.ImportResolver({ basePath: process.cwd() });
606
+
607
+ const cacheDir = (resolver as any).getCacheDir ? (resolver as any).getCacheDir() : null;
608
+ if (options.stats) {
609
+ console.log('Cache directory:', cacheDir || 'default');
610
+ } else if (options.list) {
611
+ if (cacheDir) {
612
+ const fs = await import('fs');
613
+ if (fs.existsSync(cacheDir)) {
614
+ const items = fs.readdirSync(cacheDir);
615
+ if (items.length === 0) { console.log('Cache is empty'); }
616
+ else { items.forEach((item: string) => console.log(' ', item)); }
617
+ } else { console.log('Cache directory does not exist'); }
618
+ } else { console.log('No cache directory configured'); }
619
+ } else if (options.clear) {
620
+ if (resolver.clearCache) { resolver.clearCache(); }
621
+ console.log('Cache cleared');
622
+ } else {
623
+ console.log('Use --stats, --list, or --clear');
624
+ }`
625
+ },
626
+ // === ai subcommands ===
627
+ "ai.docs": {
628
+ imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
629
+ import { resolve, dirname, basename } from 'path';
630
+ import { fileURLToPath } from 'url';
631
+ import { EngineRegistry } from '@specverse/entities';
632
+ import type { ParserEngine } from '@specverse/types';`,
633
+ handler: `const registry = new EngineRegistry();
634
+ await registry.discover();
635
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
636
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
637
+ await parser.initialize();
638
+
639
+ const content = readFileSync(file, 'utf8');
640
+ const parseResult = parser.parseContent(content, file);
641
+ if (parseResult.errors.length > 0) {
642
+ console.error('Invalid spec:');
643
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
644
+ process.exit(1);
645
+ }
646
+
647
+ const aiEngine = registry.getEngineForCapability('ai-prompts') as any;
648
+ if (!aiEngine) {
649
+ console.error('AI engine not available. Install @specverse/engines.');
650
+ process.exit(1);
651
+ }
652
+ await aiEngine.initialize({ provider: options.provider });
653
+ const prompt = await aiEngine.generatePrompt(parseResult.ast!, { type: 'docs' });
654
+ const outputFile = options.output || basename(file, '.specly') + '-ai-docs.md';
655
+ writeFileSync(outputFile, prompt);
656
+ console.log('AI documentation prompt generated: ' + outputFile);`
657
+ },
658
+ "ai.suggest": {
659
+ imports: `import { readFileSync, existsSync } from 'fs';
660
+ import { resolve, dirname } from 'path';
661
+ import { fileURLToPath } from 'url';
662
+ import { EngineRegistry } from '@specverse/entities';
663
+ import type { ParserEngine } from '@specverse/types';`,
664
+ handler: `const registry = new EngineRegistry();
665
+ await registry.discover();
666
+ const parser = registry.getEngineForCapability('parse') as ParserEngine;
667
+ if (!parser) { console.error('No parser engine found.'); process.exit(1); }
668
+ await parser.initialize();
669
+
670
+ const content = readFileSync(file, 'utf8');
671
+ const parseResult = parser.parseContent(content, file);
672
+ if (parseResult.errors.length > 0) {
673
+ console.error('Invalid spec:');
674
+ parseResult.errors.forEach((e: string) => console.error(' ', e));
675
+ process.exit(1);
676
+ }
677
+
678
+ const aiEngine = registry.getEngineForCapability('ai-suggestions') as any;
679
+ if (!aiEngine) {
680
+ console.error('AI engine not available. Install @specverse/engines.');
681
+ process.exit(1);
682
+ }
683
+ await aiEngine.initialize();
684
+ const suggestions = await aiEngine.suggest(parseResult.ast!);
685
+ if (suggestions.length === 0) {
686
+ console.log('No suggestions \u2014 spec looks good!');
687
+ } else {
688
+ const warnings = suggestions.filter((s: any) => s.severity === 'warning');
689
+ const improvements = suggestions.filter((s: any) => s.severity === 'improvement');
690
+ const info = suggestions.filter((s: any) => s.severity === 'info');
691
+ if (warnings.length > 0) {
692
+ console.log('\\nWarnings:');
693
+ warnings.forEach((s: any) => console.log(' [' + s.target + '] ' + s.description));
694
+ }
695
+ if (improvements.length > 0) {
696
+ console.log('\\nSuggested improvements:');
697
+ improvements.forEach((s: any) => console.log(' [' + s.target + '] ' + s.description));
698
+ }
699
+ if (info.length > 0) {
700
+ console.log('\\nInfo:');
701
+ info.forEach((s: any) => console.log(' [' + s.target + '] ' + s.description));
702
+ }
703
+ console.log('\\n' + suggestions.length + ' suggestion(s): ' + warnings.length + ' warning, ' + improvements.length + ' improvement, ' + info.length + ' info');
704
+ }`
705
+ },
706
+ "ai.template": {
707
+ imports: `import { writeFileSync } from 'fs';
708
+ import { EngineRegistry } from '@specverse/entities';`,
709
+ handler: `const registry = new EngineRegistry();
710
+ await registry.discover();
711
+ const aiEngine = registry.getEngineForCapability('ai-templates') as any;
712
+ if (!aiEngine) {
713
+ console.error('AI engine not available. Install @specverse/engines.');
714
+ process.exit(1);
715
+ }
716
+ await aiEngine.initialize();
717
+ const template = await aiEngine.template(operation, { config: options.config });
718
+ if (options.output) {
719
+ writeFileSync(options.output, template);
720
+ console.log('Template written to: ' + options.output);
721
+ } else {
722
+ console.log(template);
723
+ }`
724
+ },
725
+ // === session subcommands ===
726
+ "session.create": {
727
+ imports: `import { EngineRegistry } from '@specverse/entities';`,
728
+ handler: `const registry = new EngineRegistry();
729
+ await registry.discover();
730
+ const aiEngine = registry.getEngineForCapability('ai-prompts') as any;
731
+ if (!aiEngine) { console.error('AI engine not available.'); process.exit(1); }
732
+ await aiEngine.initialize();
733
+
734
+ const { SessionManager } = await import('@specverse/engines/ai');
735
+ const manager = new SessionManager();
736
+ const session = await manager.create({ name: options.name, pver: options.pver });
737
+
738
+ if (options.json) {
739
+ console.log(JSON.stringify(session, null, 2));
740
+ } else {
741
+ console.log('Session created: ' + session.sessionId);
742
+ console.log('Status: ' + session.status);
743
+ if (session.name) console.log('Name: ' + session.name);
744
+ console.log('Prompt version: ' + session.pver);
745
+ }`
746
+ },
747
+ "session.list": {
748
+ imports: ``,
749
+ handler: `const { SessionManager } = await import('@specverse/engines/ai');
750
+ const manager = new SessionManager();
751
+ const sessions = await manager.list({ all: options.all });
752
+
753
+ if (options.json) {
754
+ console.log(JSON.stringify(sessions, null, 2));
755
+ } else if (sessions.length === 0) {
756
+ console.log('No sessions found. Create one with: specverse session create');
757
+ } else {
758
+ console.log('Sessions (' + sessions.length + '):');
759
+ for (const s of sessions) {
760
+ console.log(' ' + (s.status === 'active' ? '*' : ' ') + ' ' + s.sessionId + (s.name ? ' (' + s.name + ')' : '') + ' \u2014 ' + s.status + ', ' + s.jobsProcessed + ' jobs');
761
+ }
762
+ }`
763
+ },
764
+ "session.delete": {
765
+ imports: ``,
766
+ handler: `const { SessionManager } = await import('@specverse/engines/ai');
767
+ const manager = new SessionManager();
768
+ await manager.delete(sessionId, { force: options.force });
769
+ console.log('Session deleted: ' + sessionId);`
770
+ },
771
+ "session.submit": {
772
+ imports: ``,
773
+ handler: `const { SessionManager } = await import('@specverse/engines/ai');
774
+ const manager = new SessionManager();
775
+ const job = await manager.submit(sessionId, requirements, {
776
+ jobId: options.jobId,
777
+ outputPath: options.output,
778
+ operation: options.operation,
779
+ });
780
+ console.log('Job submitted: ' + job.jobId);
781
+ console.log('Status: ' + job.status);
782
+ if (options.output) console.log('Output: ' + options.output);`
783
+ },
784
+ "session.status": {
785
+ imports: ``,
786
+ handler: `const { SessionManager } = await import('@specverse/engines/ai');
787
+ const manager = new SessionManager();
788
+ const status = await manager.status(id);
789
+ if (options.json) {
790
+ console.log(JSON.stringify(status, null, 2));
791
+ } else {
792
+ console.log('ID: ' + (status.sessionId || status.jobId));
793
+ console.log('Status: ' + status.status);
794
+ if (status.created) console.log('Created: ' + new Date(status.created).toLocaleString());
795
+ if (status.jobsProcessed !== undefined) console.log('Jobs: ' + status.jobsProcessed);
796
+ }`
797
+ },
798
+ "session.process": {
799
+ imports: ``,
800
+ handler: `const { SessionManager } = await import('@specverse/engines/ai');
801
+ const manager = new SessionManager();
802
+ console.log('Processing job: ' + jobId);
803
+ await manager.process(jobId);
804
+ console.log('Job processed successfully');`
805
+ }
806
+ };
807
+ function generateLeafCommand(name, description, commandStr, optionDefs, actionParams, serviceRef, exitCodes) {
808
+ const engineHandler = ENGINE_HANDLERS[name];
809
+ const handler = engineHandler ? engineHandler.handler : serviceRef ? `const service = new ${serviceRef}();
810
+ const result = await service.execute(${actionParams.includes(":") ? "{ " + actionParams.split(",").map((p) => p.trim().split(":")[0].trim()).join(", ") + ", ...options }" : "options"});
811
+ console.log(result);` : `console.log('Executing ${name}...');
812
+ // TODO: Wire to service`;
813
+ return `const cmd = program
814
+ .command('${commandStr}')
815
+ .description('${description}')
816
+ ${optionDefs.join("\n")}
817
+ .action(async (${actionParams}) => {
818
+ try {
819
+ ${handler}
820
+ } catch (error: any) {
821
+ console.error('Error:', error.message);
822
+ process.exit(${Object.keys(exitCodes).find((k) => k !== "0") || "1"});
823
+ }
824
+ });`;
825
+ }
826
+ function generateCommandWithSubcommands(name, description, subcommands, optionDefs) {
827
+ const subcmdRegistrations = Object.entries(subcommands).map(([subName, subDef]) => {
828
+ const subDesc = subDef.description || "";
829
+ const subArgs = subDef.arguments || {};
830
+ const subFlags = subDef.flags || {};
831
+ const positionalStr = Object.entries(subArgs).filter(([_, a]) => a.positional).map(([n, a]) => a.required ? `<${n}>` : `[${n}]`).join(" ");
832
+ const subCmdStr = positionalStr ? `${subName} ${positionalStr}` : subName;
833
+ const subOptionDefs = Object.entries(subFlags).map(([flagName, flag]) => {
834
+ const alias = flag.alias ? `${flag.alias}, ` : "";
835
+ const flagType = flag.type?.toLowerCase();
836
+ const valuePart = flagType === "boolean" ? "" : ` <${flagName.replace(/^--/, "")}>`;
837
+ const defaultVal = flag.default !== void 0 ? `, ${JSON.stringify(flag.default)}` : "";
838
+ return ` .option('${alias}${flagName}${valuePart}', '${flag.description || flagName}'${defaultVal})`;
839
+ });
840
+ const handlerKey = `${name}.${subName}`;
841
+ const engineHandler = ENGINE_HANDLERS[handlerKey];
842
+ const subArgTypes = Object.entries(subArgs).filter(([_, a]) => a.positional).map(([n, a]) => `${n}: ${mapArgTypeToTS(a.type)}`);
843
+ const subActionParams = subArgTypes.length > 0 ? subArgTypes.join(", ") + ", options: any" : "options: any";
844
+ const handler = engineHandler ? engineHandler.handler : `console.log('${name} ${subName}: not yet implemented via engine');`;
845
+ return `
846
+ cmd
847
+ .command('${subCmdStr}')
848
+ .description('${subDesc}')
849
+ ${subOptionDefs.join("\n")}
850
+ .action(async (${subActionParams}) => {
851
+ try {
852
+ ${handler}
853
+ } catch (error: any) {
854
+ console.error('Error:', error.message);
855
+ process.exit(1);
856
+ }
857
+ });`;
858
+ });
859
+ return `const cmd = program
860
+ .command('${name}')
861
+ .description('${description}');
862
+ ${subcmdRegistrations.join("\n")}`;
863
+ }
864
+ function generateSubcommandRegistrations(_parentName, _subcommands) {
865
+ return "";
866
+ }
867
+ function mapFlagTypeToTS(type) {
868
+ if (!type) return "string";
869
+ const lower = type.toLowerCase();
870
+ if (lower === "boolean") return "boolean";
871
+ if (lower === "number" || lower === "integer") return "number";
872
+ return "string";
873
+ }
874
+ function mapArgTypeToTS(type) {
875
+ if (!type) return "string";
876
+ const lower = type.toLowerCase();
877
+ if (lower === "filepath" || lower === "string") return "string";
878
+ if (lower === "number" || lower === "integer") return "number";
879
+ if (lower === "boolean") return "boolean";
880
+ return "string";
881
+ }
882
+ function capitalize(str) {
883
+ return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
884
+ }
885
+ function deduplicateImports(importBlocks) {
886
+ const seen = /* @__PURE__ */ new Map();
887
+ const typeImports = /* @__PURE__ */ new Map();
888
+ const rawLines = /* @__PURE__ */ new Set();
889
+ for (const block of importBlocks) {
890
+ for (const line of block.split("\n").map((l) => l.trim()).filter((l) => l)) {
891
+ const namedMatch = line.match(/^import\s+\{\s*(.+?)\s*\}\s+from\s+'(.+?)';?$/);
892
+ if (namedMatch) {
893
+ const names = namedMatch[1].split(",").map((n) => n.trim());
894
+ const mod = namedMatch[2];
895
+ if (!seen.has(mod)) seen.set(mod, /* @__PURE__ */ new Set());
896
+ names.forEach((n) => seen.get(mod).add(n));
897
+ continue;
898
+ }
899
+ const typeMatch = line.match(/^import\s+type\s+\{\s*(.+?)\s*\}\s+from\s+'(.+?)';?$/);
900
+ if (typeMatch) {
901
+ const names = typeMatch[1].split(",").map((n) => n.trim());
902
+ const mod = typeMatch[2];
903
+ if (!typeImports.has(mod)) typeImports.set(mod, /* @__PURE__ */ new Set());
904
+ names.forEach((n) => typeImports.get(mod).add(n));
905
+ continue;
906
+ }
907
+ rawLines.add(line);
908
+ }
909
+ }
910
+ const result = [];
911
+ for (const [mod, names] of seen.entries()) {
912
+ result.push(`import { ${[...names].join(", ")} } from '${mod}';`);
913
+ }
914
+ for (const [mod, names] of typeImports.entries()) {
915
+ const regularNames = seen.get(mod) || /* @__PURE__ */ new Set();
916
+ const typeOnly = [...names].filter((n) => !regularNames.has(n));
917
+ if (typeOnly.length > 0) {
918
+ result.push(`import type { ${typeOnly.join(", ")} } from '${mod}';`);
919
+ }
920
+ }
921
+ for (const line of rawLines) {
922
+ result.push(line);
923
+ }
924
+ return result.join("\n");
925
+ }
926
+ export {
927
+ generateCommand as default
928
+ };