@prompd/cli 0.4.9 → 0.4.11

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 (60) hide show
  1. package/dist/commands/ask.d.ts +3 -0
  2. package/dist/commands/ask.d.ts.map +1 -0
  3. package/dist/commands/ask.js +121 -0
  4. package/dist/commands/ask.js.map +1 -0
  5. package/dist/commands/mcp.d.ts.map +1 -1
  6. package/dist/commands/mcp.js +192 -42
  7. package/dist/commands/mcp.js.map +1 -1
  8. package/dist/commands/run.d.ts.map +1 -1
  9. package/dist/commands/run.js +62 -4
  10. package/dist/commands/run.js.map +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/compiler/package-resolver.d.ts.map +1 -1
  15. package/dist/lib/compiler/package-resolver.js +5 -1
  16. package/dist/lib/compiler/package-resolver.js.map +1 -1
  17. package/dist/lib/executor.d.ts +16 -6
  18. package/dist/lib/executor.d.ts.map +1 -1
  19. package/dist/lib/executor.js +121 -245
  20. package/dist/lib/executor.js.map +1 -1
  21. package/dist/lib/index.d.ts +4 -2
  22. package/dist/lib/index.d.ts.map +1 -1
  23. package/dist/lib/index.js +21 -4
  24. package/dist/lib/index.js.map +1 -1
  25. package/dist/lib/mcp.d.ts +13 -2
  26. package/dist/lib/mcp.d.ts.map +1 -1
  27. package/dist/lib/mcp.js +447 -77
  28. package/dist/lib/mcp.js.map +1 -1
  29. package/dist/lib/nodeTypeRegistry.d.ts.map +1 -1
  30. package/dist/lib/nodeTypeRegistry.js +5 -1
  31. package/dist/lib/nodeTypeRegistry.js.map +1 -1
  32. package/dist/lib/providers/base.d.ts +92 -0
  33. package/dist/lib/providers/base.d.ts.map +1 -0
  34. package/dist/lib/providers/base.js +741 -0
  35. package/dist/lib/providers/base.js.map +1 -0
  36. package/dist/lib/providers/factory.d.ts +37 -0
  37. package/dist/lib/providers/factory.d.ts.map +1 -0
  38. package/dist/lib/providers/factory.js +82 -0
  39. package/dist/lib/providers/factory.js.map +1 -0
  40. package/dist/lib/providers/index.d.ts +12 -0
  41. package/dist/lib/providers/index.d.ts.map +1 -0
  42. package/dist/lib/providers/index.js +25 -0
  43. package/dist/lib/providers/index.js.map +1 -0
  44. package/dist/lib/providers/types.d.ts +129 -0
  45. package/dist/lib/providers/types.d.ts.map +1 -0
  46. package/dist/lib/providers/types.js +156 -0
  47. package/dist/lib/providers/types.js.map +1 -0
  48. package/dist/lib/workflowExecutor.d.ts +12 -1
  49. package/dist/lib/workflowExecutor.d.ts.map +1 -1
  50. package/dist/lib/workflowExecutor.js +771 -304
  51. package/dist/lib/workflowExecutor.js.map +1 -1
  52. package/dist/lib/workflowParser.d.ts.map +1 -1
  53. package/dist/lib/workflowParser.js +2 -0
  54. package/dist/lib/workflowParser.js.map +1 -1
  55. package/dist/lib/workflowTypes.d.ts +19 -1
  56. package/dist/lib/workflowTypes.d.ts.map +1 -1
  57. package/dist/lib/workflowTypes.js.map +1 -1
  58. package/dist/types/index.d.ts +3 -0
  59. package/dist/types/index.d.ts.map +1 -1
  60. package/package.json +6 -1
package/dist/lib/mcp.js CHANGED
@@ -34,13 +34,113 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.PrompdMCPServer = void 0;
37
+ // Import MCP SDK dynamically since it's an ES module.
38
+ // Use indirect import() to prevent TypeScript from converting to require() in CJS output.
39
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
37
40
  const fs = __importStar(require("fs-extra"));
38
41
  const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
39
43
  const glob = __importStar(require("glob"));
40
44
  const parser_1 = require("./parser");
41
45
  const executor_1 = require("./executor");
46
+ const compiler_1 = require("./compiler");
47
+ const config_1 = require("./config");
48
+ const registry_1 = require("./registry");
42
49
  const workflow_1 = require("./workflow");
43
50
  const security_1 = require("./security");
51
+ /**
52
+ * Definitions for built-in MCP tools that wrap CLI operations.
53
+ * These are always available regardless of what .prmd files are registered.
54
+ */
55
+ const BUILTIN_TOOLS = [
56
+ {
57
+ name: 'prompd_compile',
58
+ description: 'Compile a .prmd file or package reference using the 6-stage compilation pipeline. Returns the rendered prompt with parameters substituted and templates processed.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ source: { type: 'string', description: 'Path to .prmd file or package reference (@namespace/package@version)' },
63
+ format: { type: 'string', description: 'Output format: markdown | provider-json:openai | provider-json:anthropic', default: 'markdown' },
64
+ parameters: { type: 'object', description: 'Key-value parameters to substitute in the prompt', additionalProperties: true },
65
+ },
66
+ required: ['source'],
67
+ },
68
+ },
69
+ {
70
+ name: 'prompd_run',
71
+ description: 'Execute a .prmd prompt file with an LLM provider and return the response. Requires a configured API key for the chosen provider.',
72
+ inputSchema: {
73
+ type: 'object',
74
+ properties: {
75
+ file: { type: 'string', description: 'Path to the .prmd file to execute' },
76
+ provider: { type: 'string', description: 'LLM provider: openai, anthropic, ollama' },
77
+ model: { type: 'string', description: 'Model name (e.g., gpt-4, claude-3-sonnet)' },
78
+ parameters: { type: 'object', description: 'Key-value parameters for the prompt', additionalProperties: true },
79
+ },
80
+ required: ['file'],
81
+ },
82
+ },
83
+ {
84
+ name: 'prompd_search',
85
+ description: 'Search the Prompd package registry for available packages matching a query.',
86
+ inputSchema: {
87
+ type: 'object',
88
+ properties: {
89
+ query: { type: 'string', description: 'Search query string' },
90
+ limit: { type: 'number', description: 'Maximum number of results (default: 20)' },
91
+ registry: { type: 'string', description: 'Specific registry name to search (optional)' },
92
+ },
93
+ required: ['query'],
94
+ },
95
+ },
96
+ {
97
+ name: 'prompd_show',
98
+ description: 'Show the structure, metadata, and parameters of a .prmd file. Useful for understanding what a prompt expects before calling it.',
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ file: { type: 'string', description: 'Path to the .prmd file to inspect' },
103
+ },
104
+ required: ['file'],
105
+ },
106
+ },
107
+ {
108
+ name: 'prompd_list',
109
+ description: 'List available .prmd files in a directory (defaults to current directory). Returns file names, descriptions, and parameters.',
110
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {
113
+ directory: { type: 'string', description: 'Directory to search for .prmd files (default: current directory)' },
114
+ detailed: { type: 'boolean', description: 'Include full details for each file' },
115
+ },
116
+ required: [],
117
+ },
118
+ },
119
+ {
120
+ name: 'prompd_explain',
121
+ description: 'Get detailed information about a .prmd file, .pdpkg package, or registry package (@namespace/name). Shows metadata, parameters, sections, file info, and available versions.',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ target: { type: 'string', description: '.prmd file path, .pdpkg package path, or registry package (@namespace/name)' },
126
+ detailed: { type: 'boolean', description: 'Show extended details' },
127
+ sections: { type: 'boolean', description: 'Show section content previews (for .prmd files)' },
128
+ },
129
+ required: ['target'],
130
+ },
131
+ },
132
+ {
133
+ name: 'prompd_validate',
134
+ description: 'Validate a .prmd file for syntax errors, structural issues, and best practice violations. Returns errors, warnings, and info messages.',
135
+ inputSchema: {
136
+ type: 'object',
137
+ properties: {
138
+ file: { type: 'string', description: 'Path to the .prmd file to validate' },
139
+ },
140
+ required: ['file'],
141
+ },
142
+ },
143
+ ];
44
144
  class PrompdMCPServer {
45
145
  constructor(config) {
46
146
  this.config = config;
@@ -56,7 +156,7 @@ class PrompdMCPServer {
56
156
  }
57
157
  async initializeMCP() {
58
158
  // Dynamic import of MCP SDK
59
- const serverModule = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/index.js')));
159
+ const serverModule = await dynamicImport('@modelcontextprotocol/sdk/server/index.js');
60
160
  // Initialize MCP server
61
161
  this.server = new serverModule.Server({
62
162
  name: this.config.name || 'prompd-mcp-server',
@@ -70,19 +170,29 @@ class PrompdMCPServer {
70
170
  }
71
171
  async setupHandlers() {
72
172
  // Dynamic import for schemas
73
- const typesModule = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
173
+ const typesModule = await dynamicImport('@modelcontextprotocol/sdk/types.js');
74
174
  // List available tools
75
175
  this.server.setRequestHandler(typesModule.ListToolsRequestSchema, async () => {
76
176
  const tools = [];
77
- // Add prompt tools
78
- for (const [name, prompd] of this.registeredTools.entries()) {
177
+ // Add built-in CLI tools first
178
+ for (const builtin of BUILTIN_TOOLS) {
179
+ tools.push({
180
+ name: builtin.name,
181
+ description: builtin.description,
182
+ inputSchema: builtin.inputSchema,
183
+ });
184
+ }
185
+ // Add registered prompt tools
186
+ for (const [name, entry] of this.registeredTools.entries()) {
187
+ const desc = entry.prompd.metadata.description || `Execute ${name} prompt`;
188
+ const templateNote = this.config.execute ? '' : ' Returns a fully rendered prompt for the LLM to follow as instructions.';
79
189
  tools.push({
80
190
  name,
81
- description: prompd.metadata.description || `Execute ${name} prompt`,
82
- inputSchema: this.createInputSchema(prompd)
191
+ description: desc + templateNote,
192
+ inputSchema: this.createInputSchema(entry.prompd)
83
193
  });
84
194
  }
85
- // Add workflow tools
195
+ // Add registered workflow tools
86
196
  for (const [name, workflow] of this.registeredWorkflows.entries()) {
87
197
  tools.push({
88
198
  name,
@@ -102,7 +212,17 @@ class PrompdMCPServer {
102
212
  throw new Error(`Security validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
103
213
  }
104
214
  const { name, arguments: args } = request.params;
105
- // Sanitize and validate tool name
215
+ // Security: Check request size
216
+ const requestSize = JSON.stringify(args).length;
217
+ if (this.config.maxRequestSize && requestSize > this.config.maxRequestSize) {
218
+ throw new Error('Request too large');
219
+ }
220
+ // Check if this is a built-in tool
221
+ const builtinResult = await this.handleBuiltinTool(name, args || {});
222
+ if (builtinResult !== null) {
223
+ return builtinResult;
224
+ }
225
+ // Sanitize and validate tool name for registered tools
106
226
  let sanitizedName;
107
227
  try {
108
228
  sanitizedName = security_1.SecurityManager.sanitizeToolName(name);
@@ -120,11 +240,6 @@ class PrompdMCPServer {
120
240
  if (this.config.allowedTools && !this.config.allowedTools.includes(sanitizedName)) {
121
241
  throw new Error(`Tool '${sanitizedName}' not allowed`);
122
242
  }
123
- // Security: Check request size
124
- const requestSize = JSON.stringify(args).length;
125
- if (this.config.maxRequestSize && requestSize > this.config.maxRequestSize) {
126
- throw new Error('Request too large');
127
- }
128
243
  try {
129
244
  if (isWorkflow) {
130
245
  // Execute workflow
@@ -154,11 +269,11 @@ class PrompdMCPServer {
154
269
  }
155
270
  else {
156
271
  // Execute prompt
157
- const prompd = this.registeredTools.get(sanitizedName);
158
- this.validateParameters(prompd, args || {});
272
+ const entry = this.registeredTools.get(sanitizedName);
273
+ this.validateParameters(entry.prompd, args || {});
159
274
  if (this.config.execute && this.executor) {
160
- // Execute with LLM and return response
161
- const result = await this.executeWithLLM(prompd, args || {});
275
+ // Execute with LLM using original file path (compiler resolves includes/inheritance)
276
+ const result = await this.executeWithLLM(entry.filePath, args || {});
162
277
  return {
163
278
  content: [
164
279
  {
@@ -169,13 +284,13 @@ class PrompdMCPServer {
169
284
  };
170
285
  }
171
286
  else {
172
- // Return rendered prompt template
173
- const renderedPrompt = this.renderPrompt(prompd, args || {});
287
+ // Compile with full pipeline (Nunjucks, includes, inheritance)
288
+ const renderedPrompt = await this.compilePrompt(entry.filePath, args || {});
174
289
  return {
175
290
  content: [
176
291
  {
177
292
  type: 'text',
178
- text: renderedPrompt
293
+ text: `[INSTRUCTIONS: Use the following rendered prompt as your guide to complete the user's request.]\n\n${renderedPrompt}`
179
294
  }
180
295
  ]
181
296
  };
@@ -187,6 +302,297 @@ class PrompdMCPServer {
187
302
  }
188
303
  });
189
304
  }
305
+ /**
306
+ * Handle built-in CLI tool calls. Returns null if the tool name doesn't match a built-in.
307
+ */
308
+ async handleBuiltinTool(name, args) {
309
+ switch (name) {
310
+ case 'prompd_compile':
311
+ return this.handleCompile(args);
312
+ case 'prompd_run':
313
+ return this.handleRun(args);
314
+ case 'prompd_search':
315
+ return this.handleSearch(args);
316
+ case 'prompd_show':
317
+ return this.handleShow(args);
318
+ case 'prompd_list':
319
+ return this.handleList(args);
320
+ case 'prompd_explain':
321
+ return this.handleExplain(args);
322
+ case 'prompd_validate':
323
+ return this.handleValidate(args);
324
+ default:
325
+ return null;
326
+ }
327
+ }
328
+ textResult(text) {
329
+ return { content: [{ type: 'text', text }] };
330
+ }
331
+ async handleCompile(args) {
332
+ const source = args.source;
333
+ if (!source)
334
+ throw new Error('source is required');
335
+ const compiler = new compiler_1.PrompdCompiler();
336
+ const result = await compiler.compile(source, {
337
+ outputFormat: args.format || 'markdown',
338
+ parameters: args.parameters || {},
339
+ });
340
+ return this.textResult(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
341
+ }
342
+ async handleRun(args) {
343
+ const file = args.file;
344
+ if (!file)
345
+ throw new Error('file is required');
346
+ const configManager = config_1.ConfigManager.getInstance();
347
+ const config = await configManager.loadConfig();
348
+ const provider = args.provider || this.config.provider || configManager.getDefaultProvider(config);
349
+ const model = args.model || this.config.model || configManager.getDefaultModel(provider, config);
350
+ const executor = new executor_1.PrompdExecutor();
351
+ const response = await executor.execute(file, {
352
+ provider,
353
+ model,
354
+ apiKey: this.config.apiKey,
355
+ params: args.parameters || {},
356
+ });
357
+ const responseText = response.response || response.content || 'No response received';
358
+ const result = { response: responseText, provider, model };
359
+ if (response.usage)
360
+ result.usage = response.usage;
361
+ return this.textResult(JSON.stringify(result, null, 2));
362
+ }
363
+ async handleSearch(args) {
364
+ const query = args.query;
365
+ if (!query)
366
+ throw new Error('query is required');
367
+ const client = new registry_1.RegistryClient(args.registry);
368
+ const results = await client.search({
369
+ query,
370
+ limit: args.limit || 20,
371
+ });
372
+ if (results.packages.length === 0) {
373
+ return this.textResult(`No packages found for: ${query}`);
374
+ }
375
+ const lines = [`Found ${results.packages.length} package(s) for "${query}":\n`];
376
+ for (const pkg of results.packages) {
377
+ lines.push(`${pkg.name} v${pkg.version}`);
378
+ lines.push(` ${pkg.description || 'No description'}`);
379
+ if (pkg.author)
380
+ lines.push(` Author: ${pkg.author}`);
381
+ lines.push('');
382
+ }
383
+ return this.textResult(lines.join('\n'));
384
+ }
385
+ async handleShow(args) {
386
+ const file = args.file;
387
+ if (!file)
388
+ throw new Error('file is required');
389
+ const parser = new parser_1.PrompdParser();
390
+ const prompd = await parser.parseFile(file);
391
+ const metadata = prompd.metadata;
392
+ const lines = [];
393
+ lines.push(`=== ${metadata.name || 'Unnamed'} ===`);
394
+ if (metadata.version)
395
+ lines.push(`Version: ${metadata.version}`);
396
+ if (metadata.description)
397
+ lines.push(`\nDescription:\n ${metadata.description}\n`);
398
+ const allParams = [...(metadata.parameters || []), ...(metadata.variables || [])];
399
+ if (allParams.length > 0) {
400
+ lines.push('Parameters:');
401
+ for (const param of allParams) {
402
+ const required = param.required ? ' (required)' : '';
403
+ lines.push(` - ${param.name} (${param.type})${required}`);
404
+ if (param.description)
405
+ lines.push(` ${param.description}`);
406
+ if (param.default !== undefined)
407
+ lines.push(` Default: ${JSON.stringify(param.default)}`);
408
+ if (param.pattern)
409
+ lines.push(` Pattern: ${param.pattern}`);
410
+ if (param.enum)
411
+ lines.push(` Enum: ${param.enum.join(', ')}`);
412
+ }
413
+ }
414
+ const sectionKeys = Object.keys(prompd.sections);
415
+ if (sectionKeys.length > 0) {
416
+ lines.push('\nSections:');
417
+ for (const key of sectionKeys)
418
+ lines.push(` - #${key}`);
419
+ }
420
+ return this.textResult(lines.join('\n'));
421
+ }
422
+ async handleList(args) {
423
+ const detailed = args.detailed;
424
+ // When no directory specified, search both global and local cache dirs
425
+ const explicitDir = args.directory;
426
+ const searchDirs = [];
427
+ if (explicitDir) {
428
+ searchDirs.push(path.resolve(explicitDir));
429
+ }
430
+ else {
431
+ const globalCache = path.join(os.homedir(), '.prompd', 'cache');
432
+ const localCache = path.join(process.cwd(), '.prompd', 'cache');
433
+ if (fs.existsSync(globalCache))
434
+ searchDirs.push(globalCache);
435
+ if (fs.existsSync(localCache) && localCache !== globalCache)
436
+ searchDirs.push(localCache);
437
+ // Also search cwd if it has .prmd files directly
438
+ searchDirs.push(process.cwd());
439
+ }
440
+ const files = [];
441
+ const seen = new Set();
442
+ for (const dir of searchDirs) {
443
+ const pattern = path.join(dir, '**/*.prmd').replace(/\\/g, '/');
444
+ for (const f of glob.sync(pattern)) {
445
+ const normalized = path.resolve(f);
446
+ if (!seen.has(normalized)) {
447
+ seen.add(normalized);
448
+ files.push(f);
449
+ }
450
+ }
451
+ }
452
+ if (files.length === 0) {
453
+ const searchedLabel = explicitDir ? path.resolve(explicitDir) : searchDirs.join(', ');
454
+ return this.textResult(`No .prmd files found in ${searchedLabel}`);
455
+ }
456
+ const parser = new parser_1.PrompdParser();
457
+ const searchedLabel = explicitDir ? path.resolve(explicitDir) : searchDirs.join(', ');
458
+ const lines = [`Found ${files.length} .prmd file(s) in ${searchedLabel}:\n`];
459
+ for (const file of files) {
460
+ try {
461
+ const prompd = await parser.parseFile(file);
462
+ const metadata = prompd.metadata;
463
+ const name = metadata.name || path.basename(file, '.prmd');
464
+ if (detailed) {
465
+ lines.push(`${name}`);
466
+ lines.push(` File: ${file}`);
467
+ if (metadata.description)
468
+ lines.push(` Description: ${metadata.description}`);
469
+ if (metadata.version)
470
+ lines.push(` Version: ${metadata.version}`);
471
+ if (metadata.parameters && metadata.parameters.length > 0) {
472
+ lines.push(` Parameters: ${metadata.parameters.map(p => p.name).join(', ')}`);
473
+ }
474
+ lines.push('');
475
+ }
476
+ else {
477
+ const desc = metadata.description
478
+ ? (metadata.description.length > 50 ? metadata.description.substring(0, 47) + '...' : metadata.description)
479
+ : '';
480
+ lines.push(` ${name} - ${desc}`);
481
+ }
482
+ }
483
+ catch {
484
+ lines.push(` ${path.basename(file, '.prmd')} - (parse error)`);
485
+ }
486
+ }
487
+ return this.textResult(lines.join('\n'));
488
+ }
489
+ async handleExplain(args) {
490
+ const target = args.target;
491
+ if (!target)
492
+ throw new Error('target is required');
493
+ const lines = [];
494
+ if (target.endsWith('.prmd')) {
495
+ // Explain .prmd file
496
+ if (!await fs.pathExists(target))
497
+ throw new Error(`File not found: ${target}`);
498
+ const parser = new parser_1.PrompdParser();
499
+ const prompd = await parser.parseFile(target);
500
+ const stats = await fs.stat(target);
501
+ lines.push(`File: ${path.basename(target)}`);
502
+ lines.push(`Path: ${path.resolve(target)}\n`);
503
+ lines.push('Metadata:');
504
+ lines.push(` ID: ${prompd.metadata.id}`);
505
+ if (prompd.metadata.name)
506
+ lines.push(` Name: ${prompd.metadata.name}`);
507
+ if (prompd.metadata.version)
508
+ lines.push(` Version: ${prompd.metadata.version}`);
509
+ if (prompd.metadata.description)
510
+ lines.push(` Description: ${prompd.metadata.description}`);
511
+ lines.push('');
512
+ const params = prompd.metadata.parameters || [];
513
+ if (params.length > 0) {
514
+ lines.push('Parameters:');
515
+ for (const param of params) {
516
+ const required = param.required ? 'required' : 'optional';
517
+ const defaultVal = param.default !== undefined ? ` (default: ${JSON.stringify(param.default)})` : '';
518
+ lines.push(` - ${param.name} (${param.type}) ${required}${defaultVal}`);
519
+ if (param.description)
520
+ lines.push(` ${param.description}`);
521
+ }
522
+ lines.push('');
523
+ }
524
+ if (args.sections && Object.keys(prompd.sections).length > 0) {
525
+ lines.push('Sections:');
526
+ for (const section of Object.keys(prompd.sections))
527
+ lines.push(` - ${section}`);
528
+ lines.push('');
529
+ }
530
+ lines.push('File Info:');
531
+ lines.push(` Size: ${stats.size} bytes`);
532
+ lines.push(` Modified: ${stats.mtime.toISOString()}`);
533
+ }
534
+ else if (target.startsWith('@') && target.includes('/')) {
535
+ // Explain registry package
536
+ const client = new registry_1.RegistryClient();
537
+ const info = await client.getPackageInfo(target);
538
+ const versions = await client.getPackageVersions(target);
539
+ lines.push(`Package: ${target}\n`);
540
+ if (info.description)
541
+ lines.push(`${info.description}\n`);
542
+ lines.push('Package Info:');
543
+ lines.push(` Name: ${info.name}`);
544
+ if (info.version)
545
+ lines.push(` Latest Version: ${info.version}`);
546
+ if (info.author)
547
+ lines.push(` Author: ${info.author}`);
548
+ lines.push('');
549
+ if (versions && versions.length > 0) {
550
+ const sorted = versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
551
+ const display = args.detailed ? sorted : sorted.slice(0, 10);
552
+ lines.push('Available Versions:');
553
+ display.forEach((v, i) => {
554
+ lines.push(` ${i === 0 ? '(latest) ' : ''}${v}`);
555
+ });
556
+ if (!args.detailed && sorted.length > 10) {
557
+ lines.push(` ... and ${sorted.length - 10} more`);
558
+ }
559
+ }
560
+ }
561
+ else {
562
+ throw new Error('Target must be a .prmd file or registry package (@namespace/name)');
563
+ }
564
+ return this.textResult(lines.join('\n'));
565
+ }
566
+ async handleValidate(args) {
567
+ const file = args.file;
568
+ if (!file)
569
+ throw new Error('file is required');
570
+ const parser = new parser_1.PrompdParser();
571
+ const issues = await parser.validateFile(file);
572
+ if (issues.length === 0) {
573
+ return this.textResult(`${file} is valid - no issues found.`);
574
+ }
575
+ const errors = issues.filter(i => i.level === 'error');
576
+ const warnings = issues.filter(i => i.level === 'warning');
577
+ const info = issues.filter(i => i.level === 'info');
578
+ const lines = [];
579
+ if (errors.length > 0) {
580
+ lines.push(`ERRORS (${errors.length}):`);
581
+ for (const issue of errors)
582
+ lines.push(` - ${issue.message}`);
583
+ }
584
+ if (warnings.length > 0) {
585
+ lines.push(`WARNINGS (${warnings.length}):`);
586
+ for (const issue of warnings)
587
+ lines.push(` - ${issue.message}`);
588
+ }
589
+ if (info.length > 0) {
590
+ lines.push(`INFO (${info.length}):`);
591
+ for (const issue of info)
592
+ lines.push(` - ${issue.message}`);
593
+ }
594
+ return this.textResult(lines.join('\n'));
595
+ }
190
596
  createInputSchema(prompd) {
191
597
  const properties = {};
192
598
  const required = [];
@@ -295,64 +701,28 @@ class PrompdMCPServer {
295
701
  default: return true;
296
702
  }
297
703
  }
298
- renderPrompt(prompd, args) {
299
- let rendered = prompd.content;
300
- // Apply defaults for missing parameters
301
- const allParams = [
302
- ...(prompd.metadata.parameters || []),
303
- ...(prompd.metadata.variables || [])
304
- ];
305
- const finalArgs = { ...args };
306
- for (const param of allParams) {
307
- if (finalArgs[param.name] === undefined && param.default !== undefined) {
308
- finalArgs[param.name] = param.default;
309
- }
310
- }
311
- // Simple parameter substitution
312
- for (const [key, value] of Object.entries(finalArgs)) {
313
- const placeholder = `{${key}}`;
314
- rendered = rendered.replace(new RegExp(placeholder, 'g'), String(value));
315
- }
316
- return rendered;
704
+ async compilePrompt(filePath, args) {
705
+ const compiler = new compiler_1.PrompdCompiler();
706
+ const result = await compiler.compile(filePath, {
707
+ outputFormat: 'markdown',
708
+ parameters: args,
709
+ });
710
+ return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
317
711
  }
318
- async executeWithLLM(prompd, args) {
712
+ async executeWithLLM(filePath, args) {
319
713
  if (!this.executor) {
320
714
  throw new Error('Executor not initialized');
321
715
  }
322
- // Create secure temporary file for execution
323
- const tempFile = security_1.SecurityManager.createSecureTempPath('prompd-exec', '.prmd');
324
- const prompdContent = this.reconstructPrompdFile(prompd);
325
- // Create secure execution context
326
- const { tempDir, cleanup } = await security_1.SecurityManager.createSecureExecutionContext();
327
- try {
328
- // Ensure temp directory exists
329
- await fs.ensureDir(path.dirname(tempFile));
330
- // Write with restrictive permissions
331
- await fs.writeFile(tempFile, prompdContent, { mode: 0o600 });
332
- const executeOptions = {
333
- provider: this.config.provider || 'openai',
334
- model: this.config.model || 'gpt-4',
335
- apiKey: this.config.apiKey,
336
- params: security_1.SecurityManager.sanitizeParameters(args)
337
- };
338
- const result = await this.executor.execute(tempFile, executeOptions);
339
- return result;
340
- }
341
- finally {
342
- // Clean up temp files and directory
343
- try {
344
- await fs.remove(tempFile);
345
- await cleanup();
346
- }
347
- catch (error) {
348
- console.warn('Failed to cleanup temporary files:', error);
349
- }
350
- }
351
- }
352
- reconstructPrompdFile(prompd) {
353
- const yaml = require('yaml');
354
- const frontmatter = yaml.stringify(prompd.metadata);
355
- return `---\n${frontmatter}---\n\n${prompd.content}`;
716
+ const executeOptions = {
717
+ provider: this.config.provider || 'openai',
718
+ model: this.config.model || 'gpt-4',
719
+ apiKey: this.config.apiKey,
720
+ params: security_1.SecurityManager.sanitizeParameters(args)
721
+ };
722
+ // Execute directly from the original file — the executor runs
723
+ // the full compiler pipeline internally, preserving relative paths
724
+ // for {% include %}, inherits:, context:, etc.
725
+ return await this.executor.execute(filePath, executeOptions);
356
726
  }
357
727
  async registerTool(name, prompdFile) {
358
728
  try {
@@ -362,7 +732,7 @@ class PrompdMCPServer {
362
732
  // Validate file size
363
733
  await security_1.SecurityManager.validateFileSize(validatedPath);
364
734
  const prompd = await this.parser.parseFile(validatedPath);
365
- this.registeredTools.set(sanitizedName, prompd);
735
+ this.registeredTools.set(sanitizedName, { prompd, filePath: validatedPath });
366
736
  }
367
737
  catch (error) {
368
738
  throw new Error(`Failed to register tool '${name}': ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -376,7 +746,7 @@ class PrompdMCPServer {
376
746
  try {
377
747
  const prompd = await this.parser.parseFile(file);
378
748
  const toolName = prompd.metadata.name || path.basename(file, '.prmd');
379
- this.registeredTools.set(toolName, prompd);
749
+ this.registeredTools.set(toolName, { prompd, filePath: file });
380
750
  }
381
751
  catch (error) {
382
752
  console.warn(`Skipping invalid prompd file ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -400,7 +770,7 @@ class PrompdMCPServer {
400
770
  // Initialize MCP components first
401
771
  await this.initializeMCP();
402
772
  // Dynamic import for transport
403
- const stdioModule = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
773
+ const stdioModule = await dynamicImport('@modelcontextprotocol/sdk/server/stdio.js');
404
774
  const transport = new stdioModule.StdioServerTransport();
405
775
  await this.server.connect(transport);
406
776
  }