@pikku/inspector 0.11.1 → 0.12.0

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 (189) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +327 -59
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +153 -44
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.d.ts +3 -0
  23. package/dist/add/add-rpc-invocations.js +65 -25
  24. package/dist/add/add-schedule.js +11 -5
  25. package/dist/add/add-secret.d.ts +3 -0
  26. package/dist/add/add-secret.js +82 -0
  27. package/dist/add/add-trigger.d.ts +2 -0
  28. package/dist/add/add-trigger.js +87 -0
  29. package/dist/add/add-variable.d.ts +1 -0
  30. package/dist/add/add-variable.js +8 -0
  31. package/dist/add/add-workflow-graph.d.ts +7 -0
  32. package/dist/add/add-workflow-graph.js +396 -0
  33. package/dist/add/add-workflow.js +124 -26
  34. package/dist/error-codes.d.ts +16 -1
  35. package/dist/error-codes.js +21 -1
  36. package/dist/index.d.ts +9 -5
  37. package/dist/index.js +5 -2
  38. package/dist/inspector.d.ts +1 -1
  39. package/dist/inspector.js +106 -13
  40. package/dist/schema-generator.d.ts +1 -0
  41. package/dist/schema-generator.js +1 -0
  42. package/dist/types-map.js +10 -1
  43. package/dist/types.d.ts +180 -30
  44. package/dist/utils/compute-required-schemas.d.ts +4 -0
  45. package/dist/utils/compute-required-schemas.js +41 -0
  46. package/dist/utils/contract-hashes.d.ts +35 -0
  47. package/dist/utils/contract-hashes.js +202 -0
  48. package/dist/utils/custom-types-generator.d.ts +9 -0
  49. package/dist/utils/custom-types-generator.js +71 -0
  50. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  51. package/dist/utils/detect-schema-vendor.js +76 -0
  52. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  53. package/dist/utils/ensure-function-metadata.js +220 -6
  54. package/dist/utils/extract-function-name.d.ts +5 -16
  55. package/dist/utils/extract-function-name.js +93 -298
  56. package/dist/utils/extract-services.d.ts +2 -1
  57. package/dist/utils/extract-services.js +25 -1
  58. package/dist/utils/filter-inspector-state.js +107 -23
  59. package/dist/utils/get-property-value.d.ts +8 -2
  60. package/dist/utils/get-property-value.js +33 -4
  61. package/dist/utils/hash.d.ts +2 -0
  62. package/dist/utils/hash.js +23 -0
  63. package/dist/utils/middleware.d.ts +7 -30
  64. package/dist/utils/middleware.js +80 -66
  65. package/dist/utils/permissions.d.ts +2 -2
  66. package/dist/utils/permissions.js +10 -10
  67. package/dist/utils/post-process.d.ts +9 -10
  68. package/dist/utils/post-process.js +231 -24
  69. package/dist/utils/resolve-external-package.d.ts +12 -0
  70. package/dist/utils/resolve-external-package.js +34 -0
  71. package/dist/utils/resolve-function-types.d.ts +6 -0
  72. package/dist/utils/resolve-function-types.js +29 -0
  73. package/dist/utils/resolve-identifier.d.ts +10 -0
  74. package/dist/utils/resolve-identifier.js +36 -0
  75. package/dist/utils/resolve-versions.d.ts +2 -0
  76. package/dist/utils/resolve-versions.js +78 -0
  77. package/dist/utils/schema-generator.d.ts +9 -0
  78. package/dist/utils/schema-generator.js +209 -0
  79. package/dist/utils/serialize-inspector-state.d.ts +73 -13
  80. package/dist/utils/serialize-inspector-state.js +102 -6
  81. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  82. package/dist/utils/serialize-mcp-json.js +99 -0
  83. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  84. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  85. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  86. package/dist/utils/serialize-openapi-json.js +151 -0
  87. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  88. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  89. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  90. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
  91. package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
  92. package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
  93. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  94. package/dist/utils/workflow/dsl/index.js +7 -0
  95. package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
  96. package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
  97. package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
  98. package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
  99. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  100. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
  101. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  102. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  103. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  104. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  105. package/dist/utils/workflow/graph/index.d.ts +8 -0
  106. package/dist/utils/workflow/graph/index.js +8 -0
  107. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
  108. package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
  109. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
  110. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  111. package/dist/visit.js +13 -2
  112. package/package.json +26 -4
  113. package/src/add/add-ai-agent.ts +468 -0
  114. package/src/add/add-channel.ts +82 -79
  115. package/src/add/add-cli.ts +49 -20
  116. package/src/add/add-file-with-factory.ts +2 -0
  117. package/src/add/add-functions.ts +429 -71
  118. package/src/add/add-http-route.ts +246 -65
  119. package/src/add/add-http-routes.ts +228 -0
  120. package/src/add/add-keyed-wiring.ts +151 -0
  121. package/src/add/add-mcp-prompt.ts +26 -15
  122. package/src/add/add-mcp-resource.ts +27 -15
  123. package/src/add/add-middleware.ts +482 -80
  124. package/src/add/add-permission.ts +199 -40
  125. package/src/add/add-queue-worker.ts +24 -19
  126. package/src/add/add-rpc-invocations.ts +78 -31
  127. package/src/add/add-schedule.ts +16 -11
  128. package/src/add/add-secret.ts +140 -0
  129. package/src/add/add-trigger.ts +154 -0
  130. package/src/add/add-variable.ts +9 -0
  131. package/src/add/add-workflow-graph.ts +522 -0
  132. package/src/add/add-workflow.ts +117 -30
  133. package/src/error-codes.ts +26 -1
  134. package/src/index.ts +27 -8
  135. package/src/inspector.ts +145 -17
  136. package/src/schema-generator.ts +1 -0
  137. package/src/types-map.ts +12 -1
  138. package/src/types.ts +192 -51
  139. package/src/utils/compute-required-schemas.ts +49 -0
  140. package/src/utils/contract-hashes.test.ts +528 -0
  141. package/src/utils/contract-hashes.ts +290 -0
  142. package/src/utils/custom-types-generator.ts +88 -0
  143. package/src/utils/detect-schema-vendor.ts +90 -0
  144. package/src/utils/ensure-function-metadata.ts +324 -7
  145. package/src/utils/extract-function-name.ts +108 -358
  146. package/src/utils/extract-services.ts +35 -2
  147. package/src/utils/filter-inspector-state.test.ts +34 -20
  148. package/src/utils/filter-inspector-state.ts +140 -31
  149. package/src/utils/get-property-value.ts +50 -5
  150. package/src/utils/hash.ts +26 -0
  151. package/src/utils/middleware.test.ts +204 -0
  152. package/src/utils/middleware.ts +129 -67
  153. package/src/utils/permissions.test.ts +35 -12
  154. package/src/utils/permissions.ts +10 -10
  155. package/src/utils/post-process.ts +283 -43
  156. package/src/utils/resolve-external-package.ts +42 -0
  157. package/src/utils/resolve-function-types.ts +42 -0
  158. package/src/utils/resolve-identifier.ts +46 -0
  159. package/src/utils/resolve-versions.test.ts +249 -0
  160. package/src/utils/resolve-versions.ts +105 -0
  161. package/src/utils/schema-generator.ts +329 -0
  162. package/src/utils/serialize-inspector-state.ts +181 -20
  163. package/src/utils/serialize-mcp-json.ts +145 -0
  164. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  165. package/src/utils/serialize-openapi-json.ts +277 -0
  166. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  167. package/src/utils/test-data/inspector-state.json +69 -66
  168. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
  169. package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
  170. package/src/utils/workflow/dsl/index.ts +11 -0
  171. package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
  172. package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
  173. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
  174. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  175. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  176. package/src/utils/workflow/graph/index.ts +11 -0
  177. package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
  178. package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
  179. package/src/visit.ts +14 -2
  180. package/tsconfig.tsbuildinfo +1 -1
  181. package/dist/add/add-mcp-tool.d.ts +0 -2
  182. package/dist/add/add-mcp-tool.js +0 -81
  183. package/dist/utils/extract-service-metadata.d.ts +0 -19
  184. package/dist/utils/extract-service-metadata.js +0 -244
  185. package/dist/utils/write-service-metadata.d.ts +0 -13
  186. package/dist/utils/write-service-metadata.js +0 -37
  187. package/src/add/add-mcp-tool.ts +0 -141
  188. package/src/utils/extract-service-metadata.ts +0 -353
  189. package/src/utils/write-service-metadata.ts +0 -51
@@ -5,12 +5,19 @@ import {
5
5
  getCommonWireMetaData,
6
6
  } from '../utils/get-property-value.js'
7
7
  import { pathToRegexp } from 'path-to-regexp'
8
- import { extractFunctionName } from '../utils/extract-function-name.js'
8
+ import {
9
+ extractFunctionName,
10
+ makeContextBasedId,
11
+ } from '../utils/extract-function-name.js'
9
12
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
10
13
  import type { ChannelMessageMeta, ChannelMeta } from '@pikku/core/channel'
11
14
  import type { InspectorState, AddWiring } from '../types.js'
12
- import { resolveMiddleware } from '../utils/middleware.js'
15
+ import {
16
+ resolveMiddleware,
17
+ resolveChannelMiddleware,
18
+ } from '../utils/middleware.js'
13
19
  import { extractWireNames } from '../utils/post-process.js'
20
+ import { resolveIdentifier } from '../utils/resolve-identifier.js'
14
21
 
15
22
  /**
16
23
  * Safely get the "initializer" expression of a property-like AST node:
@@ -61,31 +68,31 @@ function getHandlerNameFromExpression(
61
68
  ts.isFunctionExpression(decl.initializer)
62
69
  ) {
63
70
  // Extract function name from the declaration's initializer
64
- const { pikkuFuncName } = extractFunctionName(
71
+ const { pikkuFuncId } = extractFunctionName(
65
72
  decl.initializer,
66
73
  checker,
67
74
  rootDir
68
75
  )
69
- return pikkuFuncName
76
+ return pikkuFuncId
70
77
  }
71
78
  }
72
79
  // For function declarations, use directly
73
80
  else if (ts.isFunctionDeclaration(decl)) {
74
- const { pikkuFuncName } = extractFunctionName(decl, checker, rootDir)
75
- return pikkuFuncName
81
+ const { pikkuFuncId } = extractFunctionName(decl, checker, rootDir)
82
+ return pikkuFuncId
76
83
  }
77
84
  }
78
85
  }
79
86
 
80
87
  // Fallback: try to extract directly from the identifier
81
- const { pikkuFuncName } = extractFunctionName(expr, checker, rootDir)
82
- return pikkuFuncName
88
+ const { pikkuFuncId } = extractFunctionName(expr, checker, rootDir)
89
+ return pikkuFuncId
83
90
  }
84
91
 
85
92
  // Handle call expressions
86
93
  if (ts.isCallExpression(expr)) {
87
- const { pikkuFuncName } = extractFunctionName(expr, checker, rootDir)
88
- return pikkuFuncName
94
+ const { pikkuFuncId } = extractFunctionName(expr, checker, rootDir)
95
+ return pikkuFuncId
89
96
  }
90
97
 
91
98
  // Handle object literals with 'func' property
@@ -125,8 +132,20 @@ export function addMessagesRoutes(
125
132
  return result
126
133
 
127
134
  for (const chanElem of onMsgRouteProp.properties) {
128
- const chanInit = getInitializerOf(chanElem)
129
- if (!chanInit || !ts.isObjectLiteralExpression(chanInit)) continue
135
+ let chanInit = getInitializerOf(chanElem)
136
+ if (!chanInit) continue
137
+
138
+ // If the value is an identifier, resolve it (handles defineChannelRoutes)
139
+ if (ts.isIdentifier(chanInit)) {
140
+ const resolved = resolveIdentifier(chanInit, checker, [
141
+ 'defineChannelRoutes',
142
+ ])
143
+ if (resolved && ts.isObjectLiteralExpression(resolved)) {
144
+ chanInit = resolved
145
+ }
146
+ }
147
+
148
+ if (!ts.isObjectLiteralExpression(chanInit)) continue
130
149
 
131
150
  const channelKey = chanElem.name!.getText()
132
151
  result[channelKey] = {}
@@ -193,12 +212,12 @@ export function addMessagesRoutes(
193
212
  ts.isFunctionExpression(importDecl.initializer) ||
194
213
  ts.isCallExpression(importDecl.initializer)
195
214
  ) {
196
- const { pikkuFuncName } = extractFunctionName(
215
+ const { pikkuFuncId } = extractFunctionName(
197
216
  importDecl.initializer,
198
217
  checker,
199
218
  state.rootDir
200
219
  )
201
- const handlerName = pikkuFuncName
220
+ const handlerName = pikkuFuncId
202
221
 
203
222
  // Look up in the registry
204
223
  const fnMeta = state.functions.meta[handlerName]
@@ -217,7 +236,7 @@ export function addMessagesRoutes(
217
236
  : undefined
218
237
 
219
238
  result[channelKey]![routeKey] = {
220
- pikkuFuncName: handlerName,
239
+ pikkuFuncId: handlerName,
221
240
  middleware: routeMiddleware,
222
241
  }
223
242
  continue
@@ -225,12 +244,12 @@ export function addMessagesRoutes(
225
244
  }
226
245
  } else if (ts.isFunctionDeclaration(importDecl)) {
227
246
  // Extract from the function declaration
228
- const { pikkuFuncName } = extractFunctionName(
247
+ const { pikkuFuncId } = extractFunctionName(
229
248
  importDecl,
230
249
  checker,
231
250
  state.rootDir
232
251
  )
233
- const handlerName = pikkuFuncName
252
+ const handlerName = pikkuFuncId
234
253
 
235
254
  // Look up in the registry
236
255
  const fnMeta = state.functions.meta[handlerName]
@@ -249,7 +268,7 @@ export function addMessagesRoutes(
249
268
  : undefined
250
269
 
251
270
  result[channelKey]![routeKey] = {
252
- pikkuFuncName: handlerName,
271
+ pikkuFuncId: handlerName,
253
272
  middleware: routeMiddleware,
254
273
  }
255
274
  continue
@@ -275,12 +294,12 @@ export function addMessagesRoutes(
275
294
  ts.isVariableDeclaration(exportDecl) &&
276
295
  exportDecl.initializer
277
296
  ) {
278
- const { pikkuFuncName } = extractFunctionName(
297
+ const { pikkuFuncId } = extractFunctionName(
279
298
  exportDecl.initializer,
280
299
  checker,
281
300
  state.rootDir
282
301
  )
283
- const handlerName = pikkuFuncName
302
+ const handlerName = pikkuFuncId
284
303
 
285
304
  const fnMeta = state.functions.meta[handlerName]
286
305
  if (fnMeta) {
@@ -300,18 +319,18 @@ export function addMessagesRoutes(
300
319
  : undefined
301
320
 
302
321
  result[channelKey]![routeKey] = {
303
- pikkuFuncName: handlerName,
322
+ pikkuFuncId: handlerName,
304
323
  middleware: routeMiddleware,
305
324
  }
306
325
  continue
307
326
  }
308
327
  } else if (ts.isFunctionDeclaration(exportDecl)) {
309
- const { pikkuFuncName } = extractFunctionName(
328
+ const { pikkuFuncId } = extractFunctionName(
310
329
  exportDecl,
311
330
  checker,
312
331
  state.rootDir
313
332
  )
314
- const handlerName = pikkuFuncName
333
+ const handlerName = pikkuFuncId
315
334
 
316
335
  const fnMeta = state.functions.meta[handlerName]
317
336
  if (fnMeta) {
@@ -331,7 +350,7 @@ export function addMessagesRoutes(
331
350
  : undefined
332
351
 
333
352
  result[channelKey]![routeKey] = {
334
- pikkuFuncName: handlerName,
353
+ pikkuFuncId: handlerName,
335
354
  middleware: routeMiddleware,
336
355
  }
337
356
  continue
@@ -365,7 +384,7 @@ export function addMessagesRoutes(
365
384
  continue
366
385
  }
367
386
  result[channelKey]![routeKey] = {
368
- pikkuFuncName: possibleMatch,
387
+ pikkuFuncId: possibleMatch,
369
388
  }
370
389
  continue
371
390
  }
@@ -392,12 +411,12 @@ export function addMessagesRoutes(
392
411
  // If we found the actual function, extract its name
393
412
  if (actualFunction) {
394
413
  // Extract the function name directly from the actual function
395
- const { pikkuFuncName } = extractFunctionName(
414
+ const { pikkuFuncId } = extractFunctionName(
396
415
  actualFunction,
397
416
  checker,
398
417
  state.rootDir
399
418
  )
400
- const handlerName = pikkuFuncName
419
+ const handlerName = pikkuFuncId
401
420
 
402
421
  // Now use this handlerName to look up in the registry
403
422
  const fnMeta = state.functions.meta[handlerName]
@@ -417,7 +436,7 @@ export function addMessagesRoutes(
417
436
  : undefined
418
437
 
419
438
  result[channelKey]![routeKey] = {
420
- pikkuFuncName: handlerName,
439
+ pikkuFuncId: handlerName,
421
440
  middleware: routeMiddleware,
422
441
  }
423
442
  continue // Skip the normal processing below
@@ -459,7 +478,7 @@ export function addMessagesRoutes(
459
478
  : undefined
460
479
 
461
480
  result[channelKey]![routeKey] = {
462
- pikkuFuncName: handlerName,
481
+ pikkuFuncId: handlerName,
463
482
  middleware: routeMiddleware,
464
483
  }
465
484
  }
@@ -501,12 +520,11 @@ export const addChannel: AddWiring = (
501
520
  .map((k) => k.name)
502
521
  : []
503
522
 
504
- const { tags, summary, description, errors } = getCommonWireMetaData(
505
- obj,
506
- 'Channel',
507
- name,
508
- logger
509
- )
523
+ const { disabled, tags, summary, description, errors } =
524
+ getCommonWireMetaData(obj, 'Channel', name, logger)
525
+
526
+ if (disabled) return
527
+
510
528
  const query = getPropertyValue(obj, 'query') as string[] | []
511
529
 
512
530
  const connect = getPropertyAssignmentInitializer(
@@ -532,21 +550,20 @@ export const addChannel: AddWiring = (
532
550
  )
533
551
 
534
552
  if (onMsgProp) {
535
- const { pikkuFuncName } = extractFunctionName(
536
- onMsgProp,
537
- checker,
538
- state.rootDir
539
- )
540
- const fnMeta = state.functions.meta[pikkuFuncName]
553
+ const extracted = extractFunctionName(onMsgProp, checker, state.rootDir)
554
+ const msgFuncId = extracted.pikkuFuncId.startsWith('__temp_')
555
+ ? makeContextBasedId('channel', name, 'message')
556
+ : extracted.pikkuFuncId
557
+ const fnMeta = state.functions.meta[msgFuncId]
541
558
  if (!fnMeta) {
542
559
  logger.critical(
543
560
  ErrorCode.FUNCTION_METADATA_NOT_FOUND,
544
- `No function metadata found for onMessage handler '${pikkuFuncName}'`
561
+ `No function metadata found for onMessage handler '${msgFuncId}'`
545
562
  )
546
563
  return
547
564
  }
548
565
  message = {
549
- pikkuFuncName,
566
+ pikkuFuncId: msgFuncId,
550
567
  }
551
568
  }
552
569
 
@@ -555,40 +572,45 @@ export const addChannel: AddWiring = (
555
572
 
556
573
  // --- resolve middleware ---
557
574
  const middleware = resolveMiddleware(state, obj, tags, checker)
575
+ const channelMiddleware = resolveChannelMiddleware(state, obj, tags, checker)
558
576
 
559
577
  // --- track used functions/middleware for service aggregation ---
560
578
  // Track connect/disconnect/message handlers
579
+ let connectFuncId: string | undefined
561
580
  if (connect) {
562
- const connectFuncName = extractFunctionName(
563
- connect,
564
- checker,
565
- state.rootDir
566
- ).pikkuFuncName
567
- state.serviceAggregation.usedFunctions.add(connectFuncName)
581
+ const extracted = extractFunctionName(connect, checker, state.rootDir)
582
+ connectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
583
+ ? makeContextBasedId('channel', name, 'connect')
584
+ : extracted.pikkuFuncId
585
+ state.serviceAggregation.usedFunctions.add(connectFuncId)
568
586
  }
587
+
588
+ let disconnectFuncId: string | undefined
569
589
  if (disconnect) {
570
- const disconnectFuncName = extractFunctionName(
590
+ const extracted = extractFunctionName(
571
591
  disconnect as any,
572
592
  checker,
573
593
  state.rootDir
574
- ).pikkuFuncName
575
- state.serviceAggregation.usedFunctions.add(disconnectFuncName)
594
+ )
595
+ disconnectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
596
+ ? makeContextBasedId('channel', name, 'disconnect')
597
+ : extracted.pikkuFuncId
598
+ state.serviceAggregation.usedFunctions.add(disconnectFuncId)
576
599
  }
600
+
577
601
  if (message) {
578
- state.serviceAggregation.usedFunctions.add(message.pikkuFuncName)
602
+ state.serviceAggregation.usedFunctions.add(message.pikkuFuncId)
579
603
  }
580
- // Track message wiring handlers
604
+
581
605
  for (const channelHandlers of Object.values(messageWirings)) {
582
606
  for (const handler of Object.values(channelHandlers)) {
583
- state.serviceAggregation.usedFunctions.add(handler.pikkuFuncName)
607
+ state.serviceAggregation.usedFunctions.add(handler.pikkuFuncId)
584
608
  }
585
609
  }
586
- // Track middleware
587
610
  extractWireNames(middleware).forEach((name) =>
588
611
  state.serviceAggregation.usedMiddleware.add(name)
589
612
  )
590
613
 
591
- // record into state
592
614
  state.channels.files.add(node.getSourceFile().fileName)
593
615
  state.channels.meta[name] = {
594
616
  name,
@@ -596,28 +618,8 @@ export const addChannel: AddWiring = (
596
618
  input: null,
597
619
  params: params.length ? params : undefined,
598
620
  query: query?.length ? query : undefined,
599
- // inputTypes: getInputTypes(
600
- // state.channels.metaInputTypes,
601
- // 'get',
602
- // null, // TODO
603
- // query,
604
- // params
605
- // ),
606
- connect: connect
607
- ? {
608
- pikkuFuncName: extractFunctionName(connect, checker, state.rootDir)
609
- .pikkuFuncName,
610
- }
611
- : null,
612
- disconnect: disconnect
613
- ? {
614
- pikkuFuncName: extractFunctionName(
615
- disconnect as any,
616
- checker,
617
- state.rootDir
618
- ).pikkuFuncName,
619
- }
620
- : null,
621
+ connect: connectFuncId ? { pikkuFuncId: connectFuncId } : null,
622
+ disconnect: disconnectFuncId ? { pikkuFuncId: disconnectFuncId } : null,
621
623
  message,
622
624
  messageWirings,
623
625
  summary,
@@ -625,5 +627,6 @@ export const addChannel: AddWiring = (
625
627
  errors,
626
628
  tags: tags ?? undefined,
627
629
  middleware,
630
+ channelMiddleware,
628
631
  }
629
632
  }
@@ -10,6 +10,7 @@ import { extractFunctionName } from '../utils/extract-function-name.js'
10
10
  import { resolveMiddleware } from '../utils/middleware.js'
11
11
  import { extractWireNames } from '../utils/post-process.js'
12
12
  import { getPropertyValue } from '../utils/get-property-value.js'
13
+ import { resolveIdentifier } from '../utils/resolve-identifier.js'
13
14
 
14
15
  // Track if we've warned about missing Config type to avoid duplicate warnings
15
16
  const configTypeWarningShown = new Set<string>()
@@ -106,6 +107,10 @@ function processCLIConfig(
106
107
  return null
107
108
  }
108
109
 
110
+ if (getPropertyValue(node, 'disabled') === true) {
111
+ return null
112
+ }
113
+
109
114
  // Second pass: process other properties with program tags available
110
115
  for (const prop of node.properties) {
111
116
  if (!ts.isPropertyAssignment(prop)) continue
@@ -152,7 +157,7 @@ function processCLIConfig(
152
157
  prop.initializer,
153
158
  typeChecker,
154
159
  inspectorState.rootDir
155
- ).pikkuFuncName
160
+ ).pikkuFuncId
156
161
  break
157
162
  }
158
163
  }
@@ -177,6 +182,30 @@ function processCommands(
177
182
  let defaultCommandName: string | null = null
178
183
 
179
184
  for (const prop of node.properties) {
185
+ // Handle spread assignments: { ...externalCommands }
186
+ if (ts.isSpreadAssignment(prop)) {
187
+ let spreadTarget: ts.Node | undefined = prop.expression
188
+ if (ts.isIdentifier(prop.expression)) {
189
+ spreadTarget = resolveIdentifier(prop.expression, typeChecker, [
190
+ 'defineCLICommands',
191
+ ])
192
+ }
193
+ if (spreadTarget && ts.isObjectLiteralExpression(spreadTarget)) {
194
+ const spreadCommands = processCommands(
195
+ logger,
196
+ spreadTarget,
197
+ sourceFile,
198
+ typeChecker,
199
+ programName,
200
+ inspectorState,
201
+ options,
202
+ programTags
203
+ )
204
+ Object.assign(commands, spreadCommands)
205
+ }
206
+ continue
207
+ }
208
+
180
209
  if (!ts.isPropertyAssignment(prop)) continue
181
210
 
182
211
  const commandName = getPropertyName(prop)
@@ -243,11 +272,11 @@ function processCommand(
243
272
  ts.isFunctionExpression(node)
244
273
  ) {
245
274
  return {
246
- pikkuFuncName: extractFunctionName(
275
+ pikkuFuncId: extractFunctionName(
247
276
  node,
248
277
  typeChecker,
249
278
  inspectorState.rootDir
250
- ).pikkuFuncName,
279
+ ).pikkuFuncId,
251
280
  positionals: [],
252
281
  options: {},
253
282
  }
@@ -285,13 +314,13 @@ function processCommand(
285
314
  }
286
315
 
287
316
  const meta: CLICommandMeta = {
288
- pikkuFuncName: '',
317
+ pikkuFuncId: '',
289
318
  positionals: [],
290
319
  options: {},
291
320
  }
292
321
 
293
- // First pass: extract pikkuFuncName and tags so we can use them when processing options/middleware
294
- let pikkuFuncName: string | undefined
322
+ // First pass: extract pikkuFuncId and tags so we can use them when processing options/middleware
323
+ let pikkuFuncId: string | undefined
295
324
  let optionsNode: ts.ObjectLiteralExpression | undefined
296
325
  let tags: string[] | undefined
297
326
 
@@ -302,12 +331,12 @@ function processCommand(
302
331
  const propName = prop.name.text
303
332
 
304
333
  if (propName === 'func') {
305
- pikkuFuncName = extractFunctionName(
334
+ pikkuFuncId = extractFunctionName(
306
335
  prop.initializer,
307
336
  typeChecker,
308
337
  inspectorState.rootDir
309
- ).pikkuFuncName
310
- meta.pikkuFuncName = pikkuFuncName
338
+ ).pikkuFuncId
339
+ meta.pikkuFuncId = pikkuFuncId
311
340
  } else if (
312
341
  propName === 'options' &&
313
342
  ts.isObjectLiteralExpression(prop.initializer)
@@ -367,11 +396,11 @@ function processCommand(
367
396
  prop.initializer,
368
397
  typeChecker,
369
398
  inspectorState.rootDir
370
- ).pikkuFuncName
399
+ ).pikkuFuncId
371
400
  break
372
401
 
373
402
  case 'options':
374
- // Process with pikkuFuncName from first pass
403
+ // Process with pikkuFuncId from first pass
375
404
  if (optionsNode) {
376
405
  meta.options = processOptions(
377
406
  logger,
@@ -379,7 +408,7 @@ function processCommand(
379
408
  typeChecker,
380
409
  inspectorState,
381
410
  options,
382
- pikkuFuncName
411
+ pikkuFuncId
383
412
  )
384
413
  }
385
414
  break
@@ -425,7 +454,7 @@ function processCommand(
425
454
  }
426
455
 
427
456
  // --- track used functions/middleware for service aggregation ---
428
- inspectorState.serviceAggregation.usedFunctions.add(meta.pikkuFuncName)
457
+ inspectorState.serviceAggregation.usedFunctions.add(meta.pikkuFuncId)
429
458
  extractWireNames(meta.middleware).forEach((name) =>
430
459
  inspectorState.serviceAggregation.usedMiddleware.add(name)
431
460
  )
@@ -443,7 +472,7 @@ function processOptions(
443
472
  typeChecker: TypeChecker,
444
473
  inspectorState: InspectorState,
445
474
  inspectorOptions: InspectorOptions,
446
- pikkuFuncName?: string
475
+ pikkuFuncId?: string
447
476
  ): Record<string, any> {
448
477
  const options: Record<string, any> = {}
449
478
 
@@ -506,10 +535,10 @@ function processOptions(
506
535
  }
507
536
 
508
537
  // Extract enum values from the function input type if available
509
- // Get the input type if we have a pikkuFuncName
538
+ // Get the input type if we have a pikkuFuncId
510
539
  let inputTypes: ts.Type[] | undefined
511
- if (pikkuFuncName) {
512
- inputTypes = inspectorState.typesLookup.get(pikkuFuncName)
540
+ if (pikkuFuncId) {
541
+ inputTypes = inspectorState.typesLookup.get(pikkuFuncId)
513
542
  }
514
543
 
515
544
  let derivedChoices: string[] | null = null
@@ -772,7 +801,7 @@ export const addCLIRenderers: AddWiring = (
772
801
  if (args.length === 0) return
773
802
 
774
803
  // Extract renderer name
775
- const { pikkuFuncName, exportedName } = extractFunctionName(
804
+ const { pikkuFuncId, exportedName } = extractFunctionName(
776
805
  node,
777
806
  typeChecker,
778
807
  inspectorState.rootDir
@@ -808,8 +837,8 @@ export const addCLIRenderers: AddWiring = (
808
837
  }
809
838
 
810
839
  // Store renderer metadata
811
- inspectorState.cli.meta.renderers[pikkuFuncName] = {
812
- name: pikkuFuncName,
840
+ inspectorState.cli.meta.renderers[pikkuFuncId] = {
841
+ name: pikkuFuncId,
813
842
  exportedName: exportedName ?? undefined,
814
843
  services,
815
844
  filePath,
@@ -5,7 +5,9 @@ import { extractServicesFromFunction } from '../utils/extract-services.js'
5
5
  // Mapping of wrapper function names to their corresponding types
6
6
  const wrapperFunctionMap: Record<string, string> = {
7
7
  pikkuConfig: 'CreateConfig',
8
+ pikkuExternalConfig: 'CreateConfig',
8
9
  pikkuServices: 'CreateSingletonServices',
10
+ pikkuExternalServices: 'CreateSingletonServices',
9
11
  pikkuWireServices: 'CreateWireServices',
10
12
  }
11
13