@pikku/inspector 0.11.0 → 0.11.2

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 (109) hide show
  1. package/CHANGELOG.md +32 -2
  2. package/dist/add/add-channel.js +11 -10
  3. package/dist/add/add-file-with-factory.js +10 -10
  4. package/dist/add/add-forge-credential.d.ts +8 -0
  5. package/dist/add/add-forge-credential.js +77 -0
  6. package/dist/add/add-forge-node.d.ts +7 -0
  7. package/dist/add/add-forge-node.js +77 -0
  8. package/dist/add/add-functions.js +158 -51
  9. package/dist/add/add-http-route.js +28 -4
  10. package/dist/add/add-mcp-prompt.js +6 -5
  11. package/dist/add/add-mcp-resource.js +6 -5
  12. package/dist/add/add-mcp-tool.js +6 -5
  13. package/dist/add/add-middleware.js +1 -1
  14. package/dist/add/add-permission.js +1 -1
  15. package/dist/add/add-queue-worker.js +6 -5
  16. package/dist/add/add-rpc-invocations.d.ts +3 -0
  17. package/dist/add/add-rpc-invocations.js +51 -25
  18. package/dist/add/add-schedule.js +5 -4
  19. package/dist/add/add-workflow-graph.d.ts +6 -0
  20. package/dist/add/add-workflow-graph.js +659 -0
  21. package/dist/add/add-workflow.d.ts +1 -1
  22. package/dist/add/add-workflow.js +191 -69
  23. package/dist/error-codes.d.ts +3 -0
  24. package/dist/error-codes.js +3 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +3 -0
  27. package/dist/inspector.js +29 -9
  28. package/dist/types.d.ts +47 -8
  29. package/dist/utils/extract-function-name.js +7 -7
  30. package/dist/utils/extract-function-node.d.ts +10 -0
  31. package/dist/utils/extract-function-node.js +38 -0
  32. package/dist/utils/extract-node-value.d.ts +8 -0
  33. package/dist/utils/extract-node-value.js +24 -0
  34. package/dist/utils/extract-service-metadata.d.ts +19 -0
  35. package/dist/utils/extract-service-metadata.js +244 -0
  36. package/dist/utils/get-files-and-methods.d.ts +3 -3
  37. package/dist/utils/get-files-and-methods.js +3 -3
  38. package/dist/utils/get-property-value.d.ts +14 -6
  39. package/dist/utils/get-property-value.js +55 -43
  40. package/dist/utils/post-process.d.ts +9 -0
  41. package/dist/utils/post-process.js +30 -3
  42. package/dist/utils/serialize-inspector-state.d.ts +42 -6
  43. package/dist/utils/serialize-inspector-state.js +36 -10
  44. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  45. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
  46. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +17 -0
  47. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +1284 -0
  48. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  49. package/dist/utils/workflow/dsl/index.js +7 -0
  50. package/dist/utils/workflow/dsl/patterns.d.ts +60 -0
  51. package/dist/utils/workflow/dsl/patterns.js +218 -0
  52. package/dist/utils/workflow/dsl/validation.d.ts +30 -0
  53. package/dist/utils/workflow/dsl/validation.js +142 -0
  54. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  55. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
  56. package/dist/utils/workflow/graph/index.d.ts +6 -0
  57. package/dist/utils/workflow/graph/index.js +6 -0
  58. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
  59. package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
  60. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
  61. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  62. package/dist/utils/write-service-metadata.d.ts +13 -0
  63. package/dist/utils/write-service-metadata.js +37 -0
  64. package/dist/visit.js +8 -2
  65. package/package.json +16 -4
  66. package/src/add/add-channel.ts +37 -17
  67. package/src/add/add-file-with-factory.ts +10 -10
  68. package/src/add/add-forge-credential.ts +119 -0
  69. package/src/add/add-forge-node.ts +132 -0
  70. package/src/add/add-functions.ts +199 -69
  71. package/src/add/add-http-route.ts +34 -5
  72. package/src/add/add-mcp-prompt.ts +11 -7
  73. package/src/add/add-mcp-resource.ts +11 -7
  74. package/src/add/add-mcp-tool.ts +11 -7
  75. package/src/add/add-middleware.ts +1 -1
  76. package/src/add/add-permission.ts +1 -1
  77. package/src/add/add-queue-worker.ts +11 -12
  78. package/src/add/add-rpc-invocations.ts +61 -31
  79. package/src/add/add-schedule.ts +10 -5
  80. package/src/add/add-workflow-graph.ts +864 -0
  81. package/src/add/add-workflow.ts +212 -116
  82. package/src/error-codes.ts +3 -0
  83. package/src/index.ts +12 -0
  84. package/src/inspector.ts +36 -10
  85. package/src/types.ts +43 -9
  86. package/src/utils/extract-function-name.ts +7 -7
  87. package/src/utils/extract-function-node.ts +58 -0
  88. package/src/utils/extract-node-value.ts +31 -0
  89. package/src/utils/extract-service-metadata.ts +353 -0
  90. package/src/utils/filter-inspector-state.test.ts +3 -3
  91. package/src/utils/filter-utils.test.ts +45 -51
  92. package/src/utils/get-files-and-methods.ts +11 -11
  93. package/src/utils/get-property-value.ts +67 -53
  94. package/src/utils/permissions.test.ts +3 -3
  95. package/src/utils/post-process.ts +56 -3
  96. package/src/utils/serialize-inspector-state.ts +67 -19
  97. package/src/utils/test-data/inspector-state.json +9 -9
  98. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
  99. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +1608 -0
  100. package/src/utils/workflow/dsl/index.ts +11 -0
  101. package/src/utils/workflow/dsl/patterns.ts +279 -0
  102. package/src/utils/workflow/dsl/validation.ts +180 -0
  103. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
  104. package/src/utils/workflow/graph/index.ts +6 -0
  105. package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
  106. package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
  107. package/src/utils/write-service-metadata.ts +51 -0
  108. package/src/visit.ts +9 -3
  109. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,10 @@
1
1
  import * as ts from 'typescript';
2
2
  import { extractFunctionName } from '../utils/extract-function-name.js';
3
- import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
4
- import { getPropertyValue } from '../utils/get-property-value.js';
3
+ import { extractFunctionNode } from '../utils/extract-function-node.js';
4
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
5
5
  import { resolveMiddleware } from '../utils/middleware.js';
6
+ import { resolvePermissions } from '../utils/permissions.js';
7
+ import { ErrorCode } from '../error-codes.js';
6
8
  const isValidVariableName = (name) => {
7
9
  const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
8
10
  return regex.test(name);
@@ -109,7 +111,7 @@ const resolveUnionTypes = (checker, type) => {
109
111
  // Check if it's a union type AND not part of an intersection
110
112
  if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
111
113
  for (const t of type.types) {
112
- const name = nullifyTypes(checker.typeToString(t));
114
+ const name = nullifyTypes(checker.typeToString(t, undefined, ts.TypeFormatFlags.NoTruncation));
113
115
  if (name) {
114
116
  types.push(t);
115
117
  names.push(name);
@@ -117,7 +119,7 @@ const resolveUnionTypes = (checker, type) => {
117
119
  }
118
120
  }
119
121
  else {
120
- const name = nullifyTypes(checker.typeToString(type));
122
+ const name = nullifyTypes(checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation));
121
123
  if (name) {
122
124
  types.push(type);
123
125
  names.push(name);
@@ -231,44 +233,95 @@ export const addFunctions = (logger, node, checker, state) => {
231
233
  if (args.length === 0)
232
234
  return;
233
235
  const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
236
+ let title;
234
237
  let tags;
238
+ let summary;
239
+ let description;
240
+ let errors;
235
241
  let expose;
236
242
  let internal;
237
- let docs;
238
243
  let objectNode;
239
- // determine the actual handler expression:
240
- // either the `func` prop or the first argument directly
241
- let handlerNode = args[0];
242
- let isDirectFunction = true; // Default to direct function format
243
- if (ts.isObjectLiteralExpression(handlerNode)) {
244
- isDirectFunction = false; // This is object format with func property
245
- objectNode = handlerNode;
246
- tags = getPropertyValue(handlerNode, 'tags') || undefined;
247
- expose = getPropertyValue(handlerNode, 'expose');
248
- internal = getPropertyValue(handlerNode, 'internal');
249
- docs = getPropertyValue(handlerNode, 'docs');
250
- const fnProp = getPropertyAssignmentInitializer(handlerNode, 'func', true, checker);
251
- if (!fnProp ||
252
- (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
253
- logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`);
254
- // Create stub metadata to prevent "function not found" errors in wirings
255
- state.functions.meta[pikkuFuncName] = {
256
- pikkuFuncName,
257
- name,
258
- services: { optimized: false, services: [] },
259
- inputSchemaName: null,
260
- outputSchemaName: null,
261
- inputs: [],
262
- outputs: [],
263
- middleware: undefined,
264
- };
265
- return;
244
+ // Extract the function node using shared utility
245
+ const firstArg = args[0];
246
+ const { funcNode: handlerNode, resolvedFunc, isDirectFunction, } = extractFunctionNode(firstArg, checker);
247
+ // Variables to hold zod schema references if provided
248
+ let inputZodSchemaRef = null;
249
+ let outputZodSchemaRef = null;
250
+ // Helper to resolve schema identifier to its actual source file
251
+ const resolveSchemaSourceFile = (identifier) => {
252
+ const symbol = checker.getSymbolAtLocation(identifier);
253
+ if (!symbol)
254
+ return null;
255
+ const decl = symbol.valueDeclaration || symbol.declarations?.[0];
256
+ if (!decl)
257
+ return null;
258
+ // If it's an import specifier, resolve the aliased symbol to get the actual source
259
+ if (ts.isImportSpecifier(decl)) {
260
+ const aliasedSymbol = checker.getAliasedSymbol(symbol);
261
+ if (aliasedSymbol) {
262
+ const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
263
+ if (aliasedDecl) {
264
+ return {
265
+ variableName: identifier.text,
266
+ sourceFile: aliasedDecl.getSourceFile().fileName,
267
+ };
268
+ }
269
+ }
270
+ }
271
+ // Not an import - use the current source file
272
+ return {
273
+ variableName: identifier.text,
274
+ sourceFile: decl.getSourceFile().fileName,
275
+ };
276
+ };
277
+ // Extract config properties if using object form
278
+ if (ts.isObjectLiteralExpression(firstArg)) {
279
+ objectNode = firstArg;
280
+ const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger);
281
+ title = metadata.title;
282
+ tags = metadata.tags;
283
+ summary = metadata.summary;
284
+ description = metadata.description;
285
+ errors = metadata.errors;
286
+ expose = getPropertyValue(firstArg, 'expose');
287
+ internal = getPropertyValue(firstArg, 'internal');
288
+ // Extract zod schema variable names from input/output properties
289
+ for (const prop of firstArg.properties) {
290
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
291
+ const propName = prop.name.text;
292
+ if (propName === 'input' || propName === 'output') {
293
+ if (ts.isIdentifier(prop.initializer)) {
294
+ // Good - it's a variable reference, resolve its actual source file
295
+ const ref = resolveSchemaSourceFile(prop.initializer);
296
+ if (ref) {
297
+ if (propName === 'input') {
298
+ inputZodSchemaRef = ref;
299
+ }
300
+ else {
301
+ outputZodSchemaRef = ref;
302
+ }
303
+ }
304
+ }
305
+ else if (ts.isCallExpression(prop.initializer)) {
306
+ // Bad - it's an inline expression
307
+ const schemaName = `${name.charAt(0).toUpperCase() + name.slice(1)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`;
308
+ logger.critical(ErrorCode.INLINE_ZOD_SCHEMA, `Inline Zod schemas are not supported for '${propName}' in '${name}'.\n` +
309
+ ` Extract to an exported variable:\n` +
310
+ ` export const ${schemaName} = ${prop.initializer.getText()}\n` +
311
+ ` Then use: ${propName}: ${schemaName}`);
312
+ }
313
+ }
314
+ }
266
315
  }
267
- handlerNode = fnProp;
268
316
  }
269
- if (!ts.isArrowFunction(handlerNode) &&
270
- !ts.isFunctionExpression(handlerNode)) {
271
- logger.error(`• Handler for ${name} is not a function.`);
317
+ // Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
318
+ const handler = resolvedFunc &&
319
+ (ts.isArrowFunction(resolvedFunc) || ts.isFunctionExpression(resolvedFunc))
320
+ ? resolvedFunc
321
+ : handlerNode;
322
+ // Validate that we got a valid function
323
+ if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler)) {
324
+ logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`);
272
325
  // Create stub metadata to prevent "function not found" errors in wirings
273
326
  state.functions.meta[pikkuFuncName] = {
274
327
  pikkuFuncName,
@@ -286,7 +339,7 @@ export const addFunctions = (logger, node, checker, state) => {
286
339
  optimized: true,
287
340
  services: [],
288
341
  };
289
- const firstParam = handlerNode.parameters[0];
342
+ const firstParam = handler.parameters[0];
290
343
  if (firstParam) {
291
344
  if (ts.isObjectBindingPattern(firstParam.name)) {
292
345
  for (const elem of firstParam.name.elements) {
@@ -304,24 +357,64 @@ export const addFunctions = (logger, node, checker, state) => {
304
357
  services.optimized = false;
305
358
  }
306
359
  }
360
+ // --- Extract used wires from third parameter ---
361
+ const usedWires = [];
362
+ const thirdParam = handler.parameters[2];
363
+ if (thirdParam && ts.isObjectBindingPattern(thirdParam.name)) {
364
+ for (const elem of thirdParam.name.elements) {
365
+ const propertyName = elem.propertyName && ts.isIdentifier(elem.propertyName)
366
+ ? elem.propertyName.text
367
+ : ts.isIdentifier(elem.name)
368
+ ? elem.name.text
369
+ : undefined;
370
+ if (propertyName) {
371
+ usedWires.push(propertyName);
372
+ }
373
+ }
374
+ }
307
375
  // --- Generics → ts.Type[], unwrapped from Promise ---
308
376
  const genericTypes = (typeArguments ?? [])
309
377
  .map((tn) => checker.getTypeFromTypeNode(tn))
310
378
  .map((t) => unwrapPromise(checker, t));
379
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
311
380
  // --- Input Extraction ---
312
- let { names: inputNames, types: inputTypes } = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
313
- // if (inputTypes.length === 0) {
314
- // logger.debug(
315
- // `\x1b[31m• Unknown input type for '${name}', assuming void.\x1b[0m`
316
- // )
317
- // }
381
+ let inputNames = [];
382
+ let inputTypes = [];
383
+ if (inputZodSchemaRef) {
384
+ const schemaName = `${capitalizedName}Input`;
385
+ inputNames = [schemaName];
386
+ state.zodLookup.set(schemaName, inputZodSchemaRef);
387
+ state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
388
+ }
389
+ else if (genericTypes.length >= 1 && genericTypes[0]) {
390
+ // Fall back to extracting from generic type arguments
391
+ const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
392
+ inputNames = result.names;
393
+ inputTypes = result.types;
394
+ }
395
+ else {
396
+ // Fall back to extracting from the function's second parameter type
397
+ const secondParam = handler.parameters[1];
398
+ if (secondParam) {
399
+ const paramType = checker.getTypeAtLocation(secondParam);
400
+ const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', pikkuFuncName, paramType);
401
+ inputNames = result.names;
402
+ inputTypes = result.types;
403
+ }
404
+ }
318
405
  // --- Output Extraction ---
319
406
  let outputNames = [];
320
- if (genericTypes.length >= 2) {
407
+ if (outputZodSchemaRef) {
408
+ const schemaName = `${capitalizedName}Output`;
409
+ outputNames = [schemaName];
410
+ state.zodLookup.set(schemaName, outputZodSchemaRef);
411
+ state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
412
+ }
413
+ else if (genericTypes.length >= 2) {
321
414
  outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', name, genericTypes[1]).names;
322
415
  }
323
416
  else {
324
- const sig = checker.getSignatureFromDeclaration(handlerNode);
417
+ const sig = checker.getSignatureFromDeclaration(handler);
325
418
  if (sig) {
326
419
  const rawRet = checker.getReturnTypeOfSignature(sig);
327
420
  const unwrapped = unwrapPromise(checker, rawRet);
@@ -331,29 +424,38 @@ export const addFunctions = (logger, node, checker, state) => {
331
424
  if (inputNames.length > 1) {
332
425
  logger.warn('More than one input type detected, only the first one will be used as a schema.');
333
426
  }
427
+ // Store the input type for later use
428
+ if (inputTypes.length > 0) {
429
+ state.typesLookup.set(pikkuFuncName, inputTypes);
430
+ }
334
431
  // --- resolve middleware ---
335
432
  const middleware = objectNode
336
433
  ? resolveMiddleware(state, objectNode, tags, checker)
337
434
  : undefined;
435
+ // --- resolve permissions ---
436
+ const permissions = objectNode
437
+ ? resolvePermissions(state, objectNode, tags, checker)
438
+ : undefined;
338
439
  state.functions.meta[pikkuFuncName] = {
339
440
  pikkuFuncName,
340
441
  name,
341
442
  services,
443
+ usedWires: usedWires.length > 0 ? usedWires : undefined,
342
444
  inputSchemaName: inputNames[0] ?? null,
343
445
  outputSchemaName: outputNames[0] ?? null,
344
446
  inputs: inputNames.filter((n) => n !== 'void') ?? null,
345
447
  outputs: outputNames.filter((n) => n !== 'void') ?? null,
346
448
  expose: expose || undefined,
347
449
  internal: internal || undefined,
450
+ title,
348
451
  tags: tags || undefined,
349
- docs: docs || undefined,
350
- isDirectFunction,
452
+ summary,
453
+ description,
454
+ errors,
351
455
  middleware,
456
+ permissions,
457
+ isDirectFunction,
352
458
  };
353
- // Store the input type for later use
354
- if (inputTypes.length > 0) {
355
- state.typesLookup.set(pikkuFuncName, inputTypes);
356
- }
357
459
  // Store function file location for wiring generation
358
460
  if (exportedName) {
359
461
  state.functions.files.set(pikkuFuncName, {
@@ -361,6 +463,11 @@ export const addFunctions = (logger, node, checker, state) => {
361
463
  exportedName,
362
464
  });
363
465
  }
466
+ // Workflow functions don't get registered as RPC functions,
467
+ // they are their own type handled by add-workdflow
468
+ if (expression.text.includes('Workflow')) {
469
+ return;
470
+ }
364
471
  if (exportedName || explicitName) {
365
472
  if (!exportedName) {
366
473
  logger.error(`• Function with explicit name '${name}' is not exported, this is not allowed.`);
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
4
  import { extractFunctionName } from '../utils/extract-function-name.js';
5
5
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
@@ -47,9 +47,30 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
47
47
  const keys = pathToRegexp(route).keys;
48
48
  const params = keys.filter((k) => k.type === 'param').map((k) => k.name);
49
49
  const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
50
- const docs = getPropertyValue(obj, 'docs') || undefined;
51
- const tags = getPropertyTags(obj, 'HTTP route', route, logger);
50
+ const { title, tags, summary, description, errors } = getCommonWireMetaData(obj, 'HTTP route', route, logger);
52
51
  const query = getPropertyValue(obj, 'query') || [];
52
+ // Check if this is a workflow trigger (workflow: true)
53
+ const isWorkflowTrigger = getPropertyValue(obj, 'workflow') === true;
54
+ if (isWorkflowTrigger) {
55
+ // Workflow triggers don't need func - they're handled by workflow-utils
56
+ // Just record the route for HTTP meta but skip function processing
57
+ state.http.files.add(node.getSourceFile().fileName);
58
+ state.http.meta[method][route] = {
59
+ pikkuFuncName: '', // No function - workflow handles it
60
+ route,
61
+ method: method,
62
+ params: params.length > 0 ? params : undefined,
63
+ query: query.length > 0 ? query : undefined,
64
+ inputTypes: undefined,
65
+ title,
66
+ summary,
67
+ description,
68
+ errors,
69
+ tags,
70
+ workflow: true,
71
+ };
72
+ return;
73
+ }
53
74
  // --- find the referenced function name first for filtering ---
54
75
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
55
76
  if (!funcInitializer) {
@@ -85,7 +106,10 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
85
106
  params: params.length > 0 ? params : undefined,
86
107
  query: query.length > 0 ? query : undefined,
87
108
  inputTypes,
88
- docs,
109
+ title,
110
+ summary,
111
+ description,
112
+ errors,
89
113
  tags,
90
114
  middleware,
91
115
  permissions,
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractWireNames } from '../utils/post-process.js';
4
4
  import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
5
5
  import { extractFunctionName } from '../utils/extract-function-name.js';
@@ -24,8 +24,7 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
24
24
  if (ts.isObjectLiteralExpression(firstArg)) {
25
25
  const obj = firstArg;
26
26
  const nameValue = getPropertyValue(obj, 'name');
27
- const descriptionValue = getPropertyValue(obj, 'description');
28
- const tags = getPropertyTags(obj, 'MCP prompt', nameValue, logger);
27
+ const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP prompt', nameValue, logger);
29
28
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
30
29
  if (!funcInitializer) {
31
30
  logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP prompt '${nameValue}'.`);
@@ -38,7 +37,7 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
38
37
  logger.critical(ErrorCode.MISSING_NAME, "MCP prompt is missing the required 'name' property.");
39
38
  return;
40
39
  }
41
- if (!descriptionValue) {
40
+ if (!description) {
42
41
  logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP prompt '${nameValue}' is missing a description.`);
43
42
  return;
44
43
  }
@@ -62,7 +61,9 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
62
61
  state.mcpEndpoints.promptsMeta[nameValue] = {
63
62
  pikkuFuncName,
64
63
  name: nameValue,
65
- description: descriptionValue,
64
+ description,
65
+ summary,
66
+ errors,
66
67
  tags,
67
68
  inputSchema,
68
69
  outputSchema,
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractWireNames } from '../utils/post-process.js';
4
4
  import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
5
5
  import { extractFunctionName } from '../utils/extract-function-name.js';
@@ -25,9 +25,8 @@ export const addMCPResource = (logger, node, checker, state, options) => {
25
25
  const obj = firstArg;
26
26
  const uriValue = getPropertyValue(obj, 'uri');
27
27
  const titleValue = getPropertyValue(obj, 'title');
28
- const descriptionValue = getPropertyValue(obj, 'description');
28
+ const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP resource', uriValue, logger);
29
29
  const streamingValue = getPropertyValue(obj, 'streaming');
30
- const tags = getPropertyTags(obj, 'MCP resource', uriValue, logger);
31
30
  if (streamingValue === true) {
32
31
  logger.warn(`MCP resource '${uriValue}' has streaming enabled, but streaming is not yet supported.`);
33
32
  }
@@ -47,7 +46,7 @@ export const addMCPResource = (logger, node, checker, state, options) => {
47
46
  logger.critical(ErrorCode.MISSING_TITLE, `MCP resource '${uriValue}' is missing the required 'title' property.`);
48
47
  return;
49
48
  }
50
- if (!descriptionValue) {
49
+ if (!description) {
51
50
  logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP resource '${uriValue}' is missing a description.`);
52
51
  return;
53
52
  }
@@ -72,7 +71,9 @@ export const addMCPResource = (logger, node, checker, state, options) => {
72
71
  pikkuFuncName,
73
72
  uri: uriValue,
74
73
  title: titleValue,
75
- description: descriptionValue,
74
+ description,
75
+ summary,
76
+ errors,
76
77
  ...(streamingValue !== null && { streaming: streamingValue }),
77
78
  tags,
78
79
  inputSchema,
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractWireNames } from '../utils/post-process.js';
4
4
  import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
5
5
  import { extractFunctionName } from '../utils/extract-function-name.js';
@@ -25,9 +25,8 @@ export const addMCPTool = (logger, node, checker, state, options) => {
25
25
  const obj = firstArg;
26
26
  const nameValue = getPropertyValue(obj, 'name');
27
27
  const titleValue = getPropertyValue(obj, 'title');
28
- const descriptionValue = getPropertyValue(obj, 'description');
28
+ const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP tool', nameValue, logger);
29
29
  const streamingValue = getPropertyValue(obj, 'streaming');
30
- const tags = getPropertyTags(obj, 'MCP tool', nameValue, logger);
31
30
  if (streamingValue === true) {
32
31
  logger.warn(`MCP tool '${nameValue}' has streaming enabled, but streaming is not yet supported.`);
33
32
  }
@@ -43,7 +42,7 @@ export const addMCPTool = (logger, node, checker, state, options) => {
43
42
  logger.critical(ErrorCode.MISSING_NAME, "MCP tool is missing the required 'name' property.");
44
43
  return;
45
44
  }
46
- if (!descriptionValue) {
45
+ if (!description) {
47
46
  logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP tool '${nameValue}' is missing a description.`);
48
47
  return;
49
48
  }
@@ -68,7 +67,9 @@ export const addMCPTool = (logger, node, checker, state, options) => {
68
67
  pikkuFuncName,
69
68
  name: nameValue,
70
69
  title: titleValue || undefined,
71
- description: descriptionValue,
70
+ description,
71
+ summary,
72
+ errors,
72
73
  ...(streamingValue !== null && { streaming: streamingValue }),
73
74
  tags,
74
75
  inputSchema,
@@ -92,7 +92,7 @@ export const addMiddleware = (logger, node, checker, state) => {
92
92
  }
93
93
  else {
94
94
  // No pikkuMiddleware wrapper found - extract from factory's return value directly
95
- // Factory pattern: (config) => (services, interaction, next) => { ... }
95
+ // Factory pattern: (config) => (services, wire, next) => { ... }
96
96
  if (ts.isArrowFunction(factoryNode) ||
97
97
  ts.isFunctionExpression(factoryNode)) {
98
98
  const factoryBody = factoryNode.body;
@@ -92,7 +92,7 @@ export const addPermission = (logger, node, checker, state) => {
92
92
  }
93
93
  else {
94
94
  // No pikkuPermission wrapper found - extract from factory's return value directly
95
- // Factory pattern: (config) => (services, data, session) => { ... }
95
+ // Factory pattern: (config) => (services, data, wire) => { ... }
96
96
  if (ts.isArrowFunction(factoryNode) ||
97
97
  ts.isFunctionExpression(factoryNode)) {
98
98
  const factoryBody = factoryNode.body;
@@ -1,11 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractFunctionName } from '../utils/extract-function-name.js';
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
5
  import { resolveMiddleware } from '../utils/middleware.js';
6
6
  import { extractWireNames } from '../utils/post-process.js';
7
7
  import { ErrorCode } from '../error-codes.js';
8
- export const addQueueWorker = (logger, node, checker, state, options) => {
8
+ export const addQueueWorker = (logger, node, checker, state) => {
9
9
  if (!ts.isCallExpression(node)) {
10
10
  return;
11
11
  }
@@ -22,8 +22,7 @@ export const addQueueWorker = (logger, node, checker, state, options) => {
22
22
  if (ts.isObjectLiteralExpression(firstArg)) {
23
23
  const obj = firstArg;
24
24
  const queueName = getPropertyValue(obj, 'queueName');
25
- const docs = getPropertyValue(obj, 'docs') || undefined;
26
- const tags = getPropertyTags(obj, 'Queue worker', queueName, logger);
25
+ const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'Queue worker', queueName, logger);
27
26
  // --- find the referenced function ---
28
27
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
29
28
  if (!funcInitializer) {
@@ -44,7 +43,9 @@ export const addQueueWorker = (logger, node, checker, state, options) => {
44
43
  state.queueWorkers.meta[queueName] = {
45
44
  pikkuFuncName,
46
45
  queueName,
47
- docs,
46
+ summary,
47
+ description,
48
+ errors,
48
49
  tags,
49
50
  middleware,
50
51
  };
@@ -2,5 +2,8 @@ import * as ts from 'typescript';
2
2
  import { InspectorState, InspectorLogger } from '../types.js';
3
3
  /**
4
4
  * Scan for rpc.invoke() calls to track which functions are actually being invoked
5
+ * Also detects external package usage via:
6
+ * - Namespaced calls: rpc.invoke('namespace:function')
7
+ * - External helper: external('namespace:function')
5
8
  */
6
9
  export declare function addRPCInvocations(node: ts.Node, state: InspectorState, logger: InspectorLogger): void;
@@ -1,35 +1,61 @@
1
1
  import * as ts from 'typescript';
2
+ /**
3
+ * Helper to extract namespace from a namespaced function reference like 'ext:hello'
4
+ */
5
+ function extractNamespace(functionRef) {
6
+ const colonIndex = functionRef.indexOf(':');
7
+ if (colonIndex !== -1) {
8
+ return functionRef.substring(0, colonIndex);
9
+ }
10
+ return null;
11
+ }
2
12
  /**
3
13
  * Scan for rpc.invoke() calls to track which functions are actually being invoked
14
+ * Also detects external package usage via:
15
+ * - Namespaced calls: rpc.invoke('namespace:function')
16
+ * - External helper: external('namespace:function')
4
17
  */
5
18
  export function addRPCInvocations(node, state, logger) {
6
- // Look for property access expressions: rpc.invoke
7
- if (ts.isPropertyAccessExpression(node)) {
8
- const { expression, name } = node;
9
- // Check if this is accessing 'invoke' property
10
- if (name.text === 'invoke') {
11
- // Check if the object is 'rpc' (or a variable containing rpc)
12
- if (ts.isIdentifier(expression) && expression.text === 'rpc') {
13
- // This is rpc.invoke - now we need to find the parent call expression
14
- const parent = node.parent;
15
- if (ts.isCallExpression(parent) && parent.expression === node) {
16
- // This is rpc.invoke('function-name')
17
- const [firstArg] = parent.arguments;
18
- if (firstArg) {
19
- // Extract the function name from string literal
20
- if (ts.isStringLiteral(firstArg)) {
21
- const functionName = firstArg.text;
22
- logger.debug(`• Found RPC invocation: ${functionName}`);
23
- state.rpc.invokedFunctions.add(functionName);
24
- }
25
- // Handle template literals like `function-${name}`
26
- else if (ts.isTemplateExpression(firstArg) ||
27
- ts.isNoSubstitutionTemplateLiteral(firstArg)) {
28
- logger.warn(`• Found dynamic RPC invocation: ${firstArg.getText()}`);
29
- logger.warn(`\tYou can only use string literals for RPC function names, with ' or " and not \``);
30
- }
19
+ // Look for call expressions: external('ext:hello') or rpc.invoke('...')
20
+ if (ts.isCallExpression(node)) {
21
+ const { expression, arguments: args } = node;
22
+ // Check for external('namespace:function') calls
23
+ if (ts.isIdentifier(expression) && expression.text === 'external') {
24
+ const [firstArg] = args;
25
+ if (firstArg && ts.isStringLiteral(firstArg)) {
26
+ const functionRef = firstArg.text;
27
+ logger.debug(`• Found external() call: ${functionRef}`);
28
+ state.rpc.invokedFunctions.add(functionRef);
29
+ const namespace = extractNamespace(functionRef);
30
+ if (namespace) {
31
+ logger.debug(` → External package detected: ${namespace}`);
32
+ state.rpc.usedExternalPackages.add(namespace);
33
+ }
34
+ }
35
+ }
36
+ // Check for rpc.invoke('...') calls
37
+ if (ts.isPropertyAccessExpression(expression) &&
38
+ expression.name.text === 'invoke' &&
39
+ ts.isIdentifier(expression.expression) &&
40
+ expression.expression.text === 'rpc') {
41
+ const [firstArg] = args;
42
+ if (firstArg) {
43
+ if (ts.isStringLiteral(firstArg)) {
44
+ const functionRef = firstArg.text;
45
+ logger.debug(`• Found RPC invocation: ${functionRef}`);
46
+ state.rpc.invokedFunctions.add(functionRef);
47
+ const namespace = extractNamespace(functionRef);
48
+ if (namespace) {
49
+ logger.debug(` → External package detected: ${namespace}`);
50
+ state.rpc.usedExternalPackages.add(namespace);
31
51
  }
32
52
  }
53
+ // Handle template literals like `function-${name}`
54
+ else if (ts.isTemplateExpression(firstArg) ||
55
+ ts.isNoSubstitutionTemplateLiteral(firstArg)) {
56
+ logger.warn(`• Found dynamic RPC invocation: ${firstArg.getText()}`);
57
+ logger.warn(`\tYou can only use string literals for RPC function names, with ' or " and not \``);
58
+ }
33
59
  }
34
60
  }
35
61
  }
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractFunctionName } from '../utils/extract-function-name.js';
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
5
  import { resolveMiddleware } from '../utils/middleware.js';
@@ -23,8 +23,7 @@ export const addSchedule = (logger, node, checker, state, options) => {
23
23
  const obj = firstArg;
24
24
  const nameValue = getPropertyValue(obj, 'name');
25
25
  const scheduleValue = getPropertyValue(obj, 'schedule');
26
- const docs = getPropertyValue(obj, 'docs') || undefined;
27
- const tags = getPropertyTags(obj, 'Scheduler', nameValue, logger);
26
+ const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'Scheduler', nameValue, logger);
28
27
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
29
28
  if (!funcInitializer) {
30
29
  logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for scheduled task '${nameValue}'.`);
@@ -44,7 +43,9 @@ export const addSchedule = (logger, node, checker, state, options) => {
44
43
  pikkuFuncName,
45
44
  name: nameValue,
46
45
  schedule: scheduleValue,
47
- docs,
46
+ summary,
47
+ description,
48
+ errors,
48
49
  tags,
49
50
  middleware,
50
51
  };