@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
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript'
2
- import { FunctionServicesMeta } from '@pikku/core'
2
+ import { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core'
3
3
 
4
4
  /**
5
5
  * Extract services from a function's first parameter destructuring pattern
@@ -26,10 +26,43 @@ export function extractServicesFromFunction(
26
26
  services.services.push(original)
27
27
  }
28
28
  }
29
- } else if (ts.isIdentifier(firstParam.name)) {
29
+ } else if (
30
+ ts.isIdentifier(firstParam.name) &&
31
+ !firstParam.name.text.startsWith('_')
32
+ ) {
30
33
  services.optimized = false
31
34
  }
32
35
  }
33
36
 
34
37
  return services
35
38
  }
39
+
40
+ export function extractUsedWires(
41
+ handlerNode: ts.FunctionExpression | ts.ArrowFunction,
42
+ paramIndex: number
43
+ ): FunctionWiresMeta {
44
+ const param = handlerNode.parameters[paramIndex]
45
+ if (param && ts.isObjectBindingPattern(param.name)) {
46
+ const wires: string[] = []
47
+ for (const elem of param.name.elements) {
48
+ const propertyName =
49
+ elem.propertyName && ts.isIdentifier(elem.propertyName)
50
+ ? elem.propertyName.text
51
+ : ts.isIdentifier(elem.name)
52
+ ? elem.name.text
53
+ : undefined
54
+ if (propertyName) {
55
+ wires.push(propertyName)
56
+ }
57
+ }
58
+ return { optimized: true, wires }
59
+ }
60
+ if (
61
+ param &&
62
+ ts.isIdentifier(param.name) &&
63
+ !param.name.text.startsWith('_')
64
+ ) {
65
+ return { optimized: false, wires: [] }
66
+ }
67
+ return { optimized: true, wires: [] }
68
+ }
@@ -46,7 +46,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
46
46
  meta: {
47
47
  get: {
48
48
  '/api/users': {
49
- pikkuFuncName: 'getUsers',
49
+ pikkuFuncId: 'getUsers',
50
50
  route: '/api/users',
51
51
  method: 'GET',
52
52
  tags: ['api', 'public'],
@@ -54,7 +54,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
54
54
  permissions: [],
55
55
  },
56
56
  '/admin/settings': {
57
- pikkuFuncName: 'getAdminSettings',
57
+ pikkuFuncId: 'getAdminSettings',
58
58
  route: '/admin/settings',
59
59
  method: 'GET',
60
60
  tags: ['admin'],
@@ -64,7 +64,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
64
64
  },
65
65
  post: {
66
66
  '/api/users': {
67
- pikkuFuncName: 'createUser',
67
+ pikkuFuncId: 'createUser',
68
68
  route: '/api/users',
69
69
  method: 'POST',
70
70
  tags: ['api'],
@@ -152,13 +152,13 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
152
152
  channels: {
153
153
  meta: {
154
154
  'chat-channel': {
155
- pikkuFuncName: 'handleChatMessage',
155
+ pikkuFuncId: 'handleChatMessage',
156
156
  tags: ['realtime', 'public'],
157
157
  middleware: [],
158
158
  permissions: [],
159
159
  },
160
160
  'admin-channel': {
161
- pikkuFuncName: 'handleAdminMessage',
161
+ pikkuFuncId: 'handleAdminMessage',
162
162
  tags: ['realtime', 'admin'],
163
163
  middleware: [{ type: 'wire', name: 'authMiddleware' }],
164
164
  permissions: [],
@@ -169,13 +169,13 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
169
169
  scheduledTasks: {
170
170
  meta: {
171
171
  'daily-report': {
172
- pikkuFuncName: 'dailyReport',
172
+ pikkuFuncId: 'dailyReport',
173
173
  schedule: '0 0 * * *',
174
174
  tags: ['cron', 'reports'],
175
175
  middleware: [],
176
176
  },
177
177
  'hourly-cleanup': {
178
- pikkuFuncName: 'hourlyCleanup',
178
+ pikkuFuncId: 'hourlyCleanup',
179
179
  schedule: '0 * * * *',
180
180
  tags: ['cron', 'maintenance'],
181
181
  middleware: [],
@@ -186,20 +186,27 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
186
186
  queueWorkers: {
187
187
  meta: {
188
188
  'email-worker': {
189
- pikkuFuncName: 'sendEmailWorker',
190
- queueName: 'email-queue',
189
+ pikkuFuncId: 'sendEmailWorker',
190
+ name: 'email-queue',
191
191
  tags: ['queue', 'email'],
192
192
  middleware: [],
193
193
  },
194
194
  'notification-worker': {
195
- pikkuFuncName: 'sendNotificationWorker',
196
- queueName: 'notification-queue',
195
+ pikkuFuncId: 'sendNotificationWorker',
196
+ name: 'notification-queue',
197
197
  tags: ['queue', 'notifications'],
198
198
  middleware: [],
199
199
  },
200
200
  },
201
201
  files: new Set(['/test/project/src/workers/email.ts']),
202
202
  },
203
+ workflows: {
204
+ meta: {},
205
+ files: new Map(),
206
+ graphMeta: {},
207
+ graphFiles: new Map(),
208
+ invokedWorkflows: new Set(),
209
+ },
203
210
  rpc: {
204
211
  internalMeta: {},
205
212
  internalFiles: new Map(),
@@ -212,7 +219,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
212
219
  'search-tool': {
213
220
  name: 'search-tool',
214
221
  description: 'Search tool',
215
- pikkuFuncName: 'mcpSearchTool',
222
+ pikkuFuncId: 'mcpSearchTool',
216
223
  tags: ['mcp', 'search'],
217
224
  middleware: [],
218
225
  permissions: [],
@@ -220,7 +227,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
220
227
  'analyze-tool': {
221
228
  name: 'analyze-tool',
222
229
  description: 'Analyze tool',
223
- pikkuFuncName: 'mcpAnalyzeTool',
230
+ pikkuFuncId: 'mcpAnalyzeTool',
224
231
  tags: ['mcp', 'analytics'],
225
232
  middleware: [],
226
233
  permissions: [],
@@ -231,7 +238,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
231
238
  title: 'Docs Resource',
232
239
  description: 'Documentation resource',
233
240
  uri: 'docs://resource',
234
- pikkuFuncName: 'mcpDocsResource',
241
+ pikkuFuncId: 'mcpDocsResource',
235
242
  tags: ['mcp', 'docs'],
236
243
  middleware: [],
237
244
  permissions: [],
@@ -241,7 +248,7 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
241
248
  'help-prompt': {
242
249
  name: 'help-prompt',
243
250
  description: 'Help prompt',
244
- pikkuFuncName: 'mcpHelpPrompt',
251
+ pikkuFuncId: 'mcpHelpPrompt',
245
252
  tags: ['mcp', 'help'],
246
253
  middleware: [],
247
254
  permissions: [],
@@ -255,14 +262,14 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
255
262
  'my-cli': {
256
263
  commands: {
257
264
  build: {
258
- pikkuFuncName: 'cliCommand',
265
+ pikkuFuncId: 'cliCommand',
259
266
  tags: ['cli', 'build'],
260
267
  middleware: [],
261
268
  positionals: [],
262
269
  options: {},
263
270
  } as any,
264
271
  test: {
265
- pikkuFuncName: 'cliTestCommand',
272
+ pikkuFuncId: 'cliTestCommand',
266
273
  tags: ['cli', 'test'],
267
274
  middleware: [],
268
275
  positionals: [],
@@ -275,11 +282,18 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
275
282
  files: new Set(['/test/project/src/cli/commands.ts']),
276
283
  },
277
284
  middleware: {
278
- meta: {},
285
+ definitions: {},
286
+ instances: {},
287
+ tagMiddleware: new Map(),
288
+ },
289
+ channelMiddleware: {
290
+ definitions: {},
291
+ instances: {},
279
292
  tagMiddleware: new Map(),
280
293
  },
281
294
  permissions: {
282
- meta: {},
295
+ definitions: {},
296
+ instances: {},
283
297
  tagPermissions: new Map(),
284
298
  },
285
299
  serviceAggregation: {
@@ -413,7 +427,7 @@ describe('filterInspectorState', () => {
413
427
 
414
428
  assert.equal(Object.keys(result.http.meta.get).length, 1)
415
429
  assert.ok(result.http.meta.get['/api/users'])
416
- assert.equal(result.http.meta.get['/api/users'].pikkuFuncName, 'getUsers')
430
+ assert.equal(result.http.meta.get['/api/users'].pikkuFuncId, 'getUsers')
417
431
  })
418
432
 
419
433
  test('should filter HTTP routes by name wildcard', () => {
@@ -1,7 +1,10 @@
1
1
  import { InspectorState, InspectorFilters, InspectorLogger } from '../types.js'
2
- import { PikkuWiringTypes } from '@pikku/core'
2
+ import { PikkuWiringTypes, parseVersionedId } from '@pikku/core'
3
3
  import { aggregateRequiredServices } from './post-process.js'
4
4
 
5
+ // Module-level Set to track warned groups across multiple filter calls
6
+ const globalWarnedGroups = new Set<string>()
7
+
5
8
  /**
6
9
  * Match a value against a pattern with wildcard support
7
10
  * Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
@@ -39,8 +42,10 @@ function matchesFilters(
39
42
  filePath?: string
40
43
  httpRoute?: string
41
44
  httpMethod?: string
45
+ groupBasePath?: string
42
46
  },
43
- logger: InspectorLogger
47
+ logger: InspectorLogger,
48
+ warnedGroups?: Set<string>
44
49
  ): boolean {
45
50
  // If no filters, allow everything
46
51
  if (Object.keys(filters).length === 0) return true
@@ -87,10 +92,13 @@ function matchesFilters(
87
92
  }
88
93
  }
89
94
 
90
- // Check name filter
95
+ // Check name filter (match against both full ID and base name for versioned functions)
91
96
  if (filters.names && filters.names.length > 0) {
92
- const nameMatches = filters.names.some((pattern) =>
93
- matchesWildcard(meta.name, pattern)
97
+ const { baseName } = parseVersionedId(meta.name)
98
+ const nameMatches = filters.names.some(
99
+ (pattern) =>
100
+ matchesWildcard(meta.name, pattern) ||
101
+ (baseName !== meta.name && matchesWildcard(baseName, pattern))
94
102
  )
95
103
  if (!nameMatches) {
96
104
  logger.debug(`⒡ Filtered by name: ${meta.type}:${meta.name}`)
@@ -107,6 +115,23 @@ function matchesFilters(
107
115
  logger.debug(`⒡ Filtered by HTTP route: ${meta.httpRoute}`)
108
116
  return false
109
117
  }
118
+
119
+ // If route is part of a wireHTTPRoutes group, check if filter is at group level
120
+ if (meta.groupBasePath && warnedGroups) {
121
+ const groupMatches = filters.httpRoutes.some(
122
+ (pattern) =>
123
+ matchesWildcard(meta.groupBasePath!, pattern) ||
124
+ matchesWildcard(meta.groupBasePath! + '/*', pattern)
125
+ )
126
+ if (!groupMatches && !warnedGroups.has(meta.groupBasePath)) {
127
+ warnedGroups.add(meta.groupBasePath)
128
+ logger.warn(
129
+ `Filtering within wireHTTPRoutes group is not yet supported. ` +
130
+ `Route '${meta.httpRoute}' is part of group '${meta.groupBasePath}'. ` +
131
+ `Use '--httpRoutes=${meta.groupBasePath}/*' to filter the entire group.`
132
+ )
133
+ }
134
+ }
110
135
  }
111
136
 
112
137
  // Check HTTP method filter
@@ -181,6 +206,11 @@ export function filterInspectorState(
181
206
  meta: JSON.parse(JSON.stringify(state.channels.meta)),
182
207
  files: new Set<string>(), // Will be repopulated with filtered files
183
208
  },
209
+ triggers: {
210
+ ...state.triggers,
211
+ meta: JSON.parse(JSON.stringify(state.triggers?.meta ?? {})),
212
+ files: new Set<string>(),
213
+ },
184
214
  scheduledTasks: {
185
215
  ...state.scheduledTasks,
186
216
  meta: JSON.parse(JSON.stringify(state.scheduledTasks.meta)),
@@ -198,7 +228,12 @@ export function filterInspectorState(
198
228
  JSON.stringify(state.mcpEndpoints.resourcesMeta)
199
229
  ),
200
230
  promptsMeta: JSON.parse(JSON.stringify(state.mcpEndpoints.promptsMeta)),
201
- files: new Set<string>(), // Will be repopulated with filtered files
231
+ files: new Set<string>(),
232
+ },
233
+ agents: {
234
+ ...state.agents,
235
+ agentsMeta: JSON.parse(JSON.stringify(state.agents?.agentsMeta ?? {})),
236
+ files: new Map(),
202
237
  },
203
238
  cli: {
204
239
  ...state.cli,
@@ -214,31 +249,31 @@ export function filterInspectorState(
214
249
  const routeMeta = routes[route]
215
250
 
216
251
  // Get function file path for directory filtering
217
- const funcFile = filteredState.functions.files.get(
218
- routeMeta.pikkuFuncName
219
- )
252
+ const funcFile = filteredState.functions.files.get(routeMeta.pikkuFuncId)
220
253
  const filePath = funcFile?.path
221
254
 
222
255
  const matches = matchesFilters(
223
256
  filters,
224
257
  {
225
258
  type: 'http' as PikkuWiringTypes,
226
- name: routeMeta.pikkuFuncName, // Use function name, not route
259
+ name: routeMeta.pikkuFuncId, // Use function name, not route
227
260
  tags: routeMeta.tags,
228
261
  filePath,
229
262
  httpRoute: routeMeta.route,
230
263
  httpMethod: routeMeta.method,
264
+ groupBasePath: routeMeta.groupBasePath,
231
265
  },
232
- logger
266
+ logger,
267
+ globalWarnedGroups
233
268
  )
234
269
 
235
270
  if (!matches) {
236
271
  delete routes[route]
237
272
  } else {
238
273
  // Track used functions/middleware/permissions
239
- if (routeMeta.pikkuFuncName) {
274
+ if (routeMeta.pikkuFuncId) {
240
275
  filteredState.serviceAggregation.usedFunctions.add(
241
- routeMeta.pikkuFuncName
276
+ routeMeta.pikkuFuncId
242
277
  )
243
278
  }
244
279
  extractWireNames(routeMeta.middleware).forEach((name: string) =>
@@ -275,9 +310,9 @@ export function filterInspectorState(
275
310
  if (!matches) {
276
311
  delete filteredState.channels.meta[name]
277
312
  } else {
278
- if (channelMeta.pikkuFuncName) {
313
+ if (channelMeta.pikkuFuncId) {
279
314
  filteredState.serviceAggregation.usedFunctions.add(
280
- channelMeta.pikkuFuncName
315
+ channelMeta.pikkuFuncId
281
316
  )
282
317
  }
283
318
  extractWireNames(channelMeta.middleware).forEach((name: string) =>
@@ -294,6 +329,35 @@ export function filterInspectorState(
294
329
  filteredState.channels.files = new Set(state.channels.files)
295
330
  }
296
331
 
332
+ // Filter triggers
333
+ for (const name of Object.keys(filteredState.triggers.meta)) {
334
+ const triggerMeta = filteredState.triggers.meta[name]
335
+ const matches = matchesFilters(
336
+ filters,
337
+ {
338
+ type: 'trigger' as PikkuWiringTypes,
339
+ name,
340
+ tags: triggerMeta.tags,
341
+ },
342
+ logger
343
+ )
344
+
345
+ if (!matches) {
346
+ delete filteredState.triggers.meta[name]
347
+ } else {
348
+ if (triggerMeta.pikkuFuncId) {
349
+ filteredState.serviceAggregation.usedFunctions.add(
350
+ triggerMeta.pikkuFuncId
351
+ )
352
+ }
353
+ }
354
+ }
355
+
356
+ // Repopulate triggers.files if any triggers remain
357
+ if (Object.keys(filteredState.triggers.meta).length > 0) {
358
+ filteredState.triggers.files = new Set(state.triggers.files)
359
+ }
360
+
297
361
  // Filter scheduled tasks
298
362
  for (const name of Object.keys(filteredState.scheduledTasks.meta)) {
299
363
  const taskMeta = filteredState.scheduledTasks.meta[name]
@@ -310,10 +374,8 @@ export function filterInspectorState(
310
374
  if (!matches) {
311
375
  delete filteredState.scheduledTasks.meta[name]
312
376
  } else {
313
- if (taskMeta.pikkuFuncName) {
314
- filteredState.serviceAggregation.usedFunctions.add(
315
- taskMeta.pikkuFuncName
316
- )
377
+ if (taskMeta.pikkuFuncId) {
378
+ filteredState.serviceAggregation.usedFunctions.add(taskMeta.pikkuFuncId)
317
379
  }
318
380
  extractWireNames(taskMeta.middleware).forEach((name: string) =>
319
381
  filteredState.serviceAggregation.usedMiddleware.add(name)
@@ -342,9 +404,9 @@ export function filterInspectorState(
342
404
  if (!matches) {
343
405
  delete filteredState.queueWorkers.meta[name]
344
406
  } else {
345
- if (workerMeta.pikkuFuncName) {
407
+ if (workerMeta.pikkuFuncId) {
346
408
  filteredState.serviceAggregation.usedFunctions.add(
347
- workerMeta.pikkuFuncName
409
+ workerMeta.pikkuFuncId
348
410
  )
349
411
  }
350
412
  extractWireNames(workerMeta.middleware).forEach((name: string) =>
@@ -374,10 +436,8 @@ export function filterInspectorState(
374
436
  if (!matches) {
375
437
  delete filteredState.mcpEndpoints.toolsMeta[name]
376
438
  } else {
377
- if (toolMeta.pikkuFuncName) {
378
- filteredState.serviceAggregation.usedFunctions.add(
379
- toolMeta.pikkuFuncName
380
- )
439
+ if (toolMeta.pikkuFuncId) {
440
+ filteredState.serviceAggregation.usedFunctions.add(toolMeta.pikkuFuncId)
381
441
  }
382
442
  extractWireNames(toolMeta.middleware).forEach((name: string) =>
383
443
  filteredState.serviceAggregation.usedMiddleware.add(name)
@@ -404,9 +464,9 @@ export function filterInspectorState(
404
464
  if (!matches) {
405
465
  delete filteredState.mcpEndpoints.resourcesMeta[name]
406
466
  } else {
407
- if (resourceMeta.pikkuFuncName) {
467
+ if (resourceMeta.pikkuFuncId) {
408
468
  filteredState.serviceAggregation.usedFunctions.add(
409
- resourceMeta.pikkuFuncName
469
+ resourceMeta.pikkuFuncId
410
470
  )
411
471
  }
412
472
  extractWireNames(resourceMeta.middleware).forEach((name: string) =>
@@ -434,9 +494,9 @@ export function filterInspectorState(
434
494
  if (!matches) {
435
495
  delete filteredState.mcpEndpoints.promptsMeta[name]
436
496
  } else {
437
- if (promptMeta.pikkuFuncName) {
497
+ if (promptMeta.pikkuFuncId) {
438
498
  filteredState.serviceAggregation.usedFunctions.add(
439
- promptMeta.pikkuFuncName
499
+ promptMeta.pikkuFuncId
440
500
  )
441
501
  }
442
502
  extractWireNames(promptMeta.middleware).forEach((name: string) =>
@@ -457,6 +517,40 @@ export function filterInspectorState(
457
517
  filteredState.mcpEndpoints.files = new Set(state.mcpEndpoints.files)
458
518
  }
459
519
 
520
+ // Filter AI agents
521
+ for (const name of Object.keys(filteredState.agents.agentsMeta)) {
522
+ const agentMeta = filteredState.agents.agentsMeta[name]
523
+ const matches = matchesFilters(
524
+ filters,
525
+ {
526
+ type: 'agent' as PikkuWiringTypes,
527
+ name,
528
+ tags: agentMeta.tags,
529
+ },
530
+ logger
531
+ )
532
+
533
+ if (!matches) {
534
+ delete filteredState.agents.agentsMeta[name]
535
+ } else {
536
+ if (agentMeta.pikkuFuncId) {
537
+ filteredState.serviceAggregation.usedFunctions.add(
538
+ agentMeta.pikkuFuncId
539
+ )
540
+ }
541
+ extractWireNames(agentMeta.middleware).forEach((name: string) =>
542
+ filteredState.serviceAggregation.usedMiddleware.add(name)
543
+ )
544
+ extractWireNames(agentMeta.permissions).forEach((name: string) =>
545
+ filteredState.serviceAggregation.usedPermissions.add(name)
546
+ )
547
+ }
548
+ }
549
+
550
+ if (Object.keys(filteredState.agents.agentsMeta).length > 0) {
551
+ filteredState.agents.files = new Map(state.agents.files)
552
+ }
553
+
460
554
  // Filter CLI programs (note: CLI filtering might be more complex with nested commands)
461
555
  const referencedRenderers = new Set<string>()
462
556
 
@@ -479,9 +573,9 @@ export function filterInspectorState(
479
573
  if (!matches) {
480
574
  delete programMeta.commands[commandName]
481
575
  } else {
482
- if (commandMeta.pikkuFuncName) {
576
+ if (commandMeta.pikkuFuncId) {
483
577
  filteredState.serviceAggregation.usedFunctions.add(
484
- commandMeta.pikkuFuncName
578
+ commandMeta.pikkuFuncId
485
579
  )
486
580
  }
487
581
  extractWireNames(commandMeta.middleware).forEach((name: string) =>
@@ -517,6 +611,21 @@ export function filterInspectorState(
517
611
  filteredState.cli.files = new Set(state.cli.files)
518
612
  }
519
613
 
614
+ // Post-filter version expansion: include all versions of matched functions
615
+ const includedBaseNames = new Set<string>()
616
+ for (const funcId of filteredState.serviceAggregation.usedFunctions) {
617
+ const { baseName } = parseVersionedId(funcId)
618
+ includedBaseNames.add(baseName)
619
+ }
620
+ if (includedBaseNames.size > 0) {
621
+ for (const funcId of Object.keys(state.functions.meta)) {
622
+ const { baseName } = parseVersionedId(funcId)
623
+ if (includedBaseNames.has(baseName)) {
624
+ filteredState.serviceAggregation.usedFunctions.add(funcId)
625
+ }
626
+ }
627
+ }
628
+
520
629
  // Recalculate requiredServices based on filtered functions/middleware/permissions
521
630
  // Need to cast to InspectorState temporarily for aggregateRequiredServices
522
631
  const stateForAggregation = filteredState as InspectorState
@@ -1,10 +1,36 @@
1
1
  import * as ts from 'typescript'
2
2
  import { ErrorCode } from '../error-codes.js'
3
3
 
4
+ /**
5
+ * Extracts an array of strings from an object property.
6
+ */
7
+ export const getArrayPropertyValue = (
8
+ obj: ts.ObjectLiteralExpression,
9
+ propertyName: string
10
+ ): string[] | null => {
11
+ const property = obj.properties.find(
12
+ (p) =>
13
+ ts.isPropertyAssignment(p) &&
14
+ ts.isIdentifier(p.name) &&
15
+ p.name.text === propertyName
16
+ )
17
+
18
+ if (property && ts.isPropertyAssignment(property)) {
19
+ const initializer = property.initializer
20
+ if (ts.isArrayLiteralExpression(initializer)) {
21
+ return initializer.elements
22
+ .filter(ts.isStringLiteral)
23
+ .map((element) => element.text)
24
+ }
25
+ }
26
+
27
+ return null
28
+ }
29
+
4
30
  export const getPropertyValue = (
5
31
  obj: ts.ObjectLiteralExpression,
6
32
  propertyName: string
7
- ): string | string[] | null | boolean => {
33
+ ): string | string[] | number | null | boolean => {
8
34
  const property = obj.properties.find(
9
35
  (p) =>
10
36
  ts.isPropertyAssignment(p) &&
@@ -41,7 +67,13 @@ export const getPropertyValue = (
41
67
  return false
42
68
  }
43
69
 
44
- // Handle string literals for other properties
70
+ if (ts.isNumericLiteral(initializer)) {
71
+ if (propertyName === 'name' || propertyName === 'schedule') {
72
+ return initializer.text
73
+ }
74
+ return Number(initializer.text)
75
+ }
76
+
45
77
  if (
46
78
  ts.isStringLiteral(initializer) ||
47
79
  ts.isNoSubstitutionTemplateLiteral(initializer)
@@ -49,7 +81,6 @@ export const getPropertyValue = (
49
81
  return initializer.text
50
82
  }
51
83
 
52
- // Handle other initializer types if necessary
53
84
  return initializer.getText()
54
85
  }
55
86
 
@@ -57,7 +88,7 @@ export const getPropertyValue = (
57
88
  }
58
89
 
59
90
  /**
60
- * Extracts common wire metadata (tags, summary, description, errors) directly from an object
91
+ * Extracts common wire metadata (title, tags, summary, description, errors) directly from an object
61
92
  * @param obj - The TypeScript object literal expression to extract metadata from
62
93
  * @param wiringType - The type of wiring (e.g., 'HTTP route', 'Channel', 'Queue worker')
63
94
  * @param wiringName - The name/identifier of the wiring (e.g., route path, channel name)
@@ -70,12 +101,16 @@ export const getCommonWireMetaData = (
70
101
  wiringName: string | null,
71
102
  logger?: { critical: (code: ErrorCode, message: string) => void }
72
103
  ): {
104
+ disabled?: true
105
+ title?: string
73
106
  tags?: string[]
74
107
  summary?: string
75
108
  description?: string
76
109
  errors?: string[]
77
110
  } => {
78
111
  const metadata: {
112
+ disabled?: true
113
+ title?: string
79
114
  tags?: string[]
80
115
  summary?: string
81
116
  description?: string
@@ -86,7 +121,17 @@ export const getCommonWireMetaData = (
86
121
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
87
122
  const propName = prop.name.text
88
123
 
89
- if (propName === 'summary' && ts.isStringLiteral(prop.initializer)) {
124
+ if (
125
+ propName === 'disabled' &&
126
+ prop.initializer.kind === ts.SyntaxKind.TrueKeyword
127
+ ) {
128
+ metadata.disabled = true
129
+ } else if (propName === 'title' && ts.isStringLiteral(prop.initializer)) {
130
+ metadata.title = prop.initializer.text
131
+ } else if (
132
+ propName === 'summary' &&
133
+ ts.isStringLiteral(prop.initializer)
134
+ ) {
90
135
  metadata.summary = prop.initializer.text
91
136
  } else if (
92
137
  propName === 'description' &&
@@ -0,0 +1,26 @@
1
+ import { createHash } from 'crypto'
2
+
3
+ export function canonicalJSON(obj: unknown): string {
4
+ return JSON.stringify(sortDeep(obj))
5
+ }
6
+
7
+ function sortDeep(value: unknown): unknown {
8
+ if (value === null || value === undefined) {
9
+ return value
10
+ }
11
+ if (Array.isArray(value)) {
12
+ return value.map(sortDeep)
13
+ }
14
+ if (typeof value === 'object') {
15
+ const sorted: Record<string, unknown> = {}
16
+ for (const key of Object.keys(value as Record<string, unknown>).sort()) {
17
+ sorted[key] = sortDeep((value as Record<string, unknown>)[key])
18
+ }
19
+ return sorted
20
+ }
21
+ return value
22
+ }
23
+
24
+ export function hashString(input: string, length: number = 16): string {
25
+ return createHash('sha256').update(input).digest('hex').slice(0, length)
26
+ }