@pikku/inspector 0.7.7 → 0.8.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.
@@ -0,0 +1,112 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { PikkuEventTypes } from '@pikku/core'
4
+ import { InspectorFilters, InspectorState } from './types.js'
5
+ import {
6
+ extractFunctionName,
7
+ getPropertyAssignmentInitializer,
8
+ matchesFilters,
9
+ } from './utils.js'
10
+
11
+ export const addMCPResource = (
12
+ node: ts.Node,
13
+ checker: ts.TypeChecker,
14
+ state: InspectorState,
15
+ filters: InspectorFilters
16
+ ) => {
17
+ if (!ts.isCallExpression(node)) {
18
+ return
19
+ }
20
+
21
+ const args = node.arguments
22
+ const firstArg = args[0]
23
+ const expression = node.expression
24
+
25
+ // Check if the call is to addMCPResource
26
+ if (!ts.isIdentifier(expression) || expression.text !== 'addMCPResource') {
27
+ return
28
+ }
29
+
30
+ if (!firstArg) {
31
+ return
32
+ }
33
+
34
+ if (ts.isObjectLiteralExpression(firstArg)) {
35
+ const obj = firstArg
36
+
37
+ const uriValue = getPropertyValue(obj, 'uri') as string | null
38
+ const titleValue = getPropertyValue(obj, 'title') as string | null
39
+ const descriptionValue = getPropertyValue(obj, 'description') as
40
+ | string
41
+ | null
42
+ const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
43
+ const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
44
+
45
+ const funcInitializer = getPropertyAssignmentInitializer(
46
+ obj,
47
+ 'func',
48
+ true,
49
+ checker
50
+ )
51
+ if (!funcInitializer) {
52
+ console.error(
53
+ `• No valid 'func' property for MCP resource '${uriValue}'.`
54
+ )
55
+ return
56
+ }
57
+
58
+ const pikkuFuncName = extractFunctionName(
59
+ funcInitializer,
60
+ checker
61
+ ).pikkuFuncName
62
+
63
+ if (!uriValue) {
64
+ console.error(`• MCP resource is missing the required 'uri' property.`)
65
+ return
66
+ }
67
+
68
+ if (!titleValue) {
69
+ console.error(
70
+ `• MCP resource '${uriValue}' is missing the required 'title' property.`
71
+ )
72
+ return
73
+ }
74
+
75
+ if (!descriptionValue) {
76
+ console.error(`• MCP resource '${uriValue}' is missing a description.`)
77
+ return
78
+ }
79
+
80
+ if (
81
+ !matchesFilters(
82
+ filters,
83
+ { tags },
84
+ { type: PikkuEventTypes.mcp, name: uriValue }
85
+ )
86
+ ) {
87
+ return
88
+ }
89
+
90
+ // lookup existing function metadata
91
+ const fnMeta = state.functions.meta[pikkuFuncName]
92
+ if (!fnMeta) {
93
+ console.error(`• No function metadata found for '${pikkuFuncName}'.`)
94
+ return
95
+ }
96
+ const inputSchema = fnMeta.inputs?.[0] || null
97
+ const outputSchema = fnMeta.outputs?.[0] || null
98
+
99
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName)
100
+
101
+ state.mcpEndpoints.resourcesMeta[uriValue] = {
102
+ pikkuFuncName,
103
+ uri: uriValue,
104
+ title: titleValue,
105
+ description: descriptionValue,
106
+ ...(streamingValue !== null && { streaming: streamingValue }),
107
+ tags,
108
+ inputSchema,
109
+ outputSchema,
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,103 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { PikkuEventTypes } from '@pikku/core'
4
+ import { InspectorFilters, InspectorState } from './types.js'
5
+ import {
6
+ extractFunctionName,
7
+ getPropertyAssignmentInitializer,
8
+ matchesFilters,
9
+ } from './utils.js'
10
+
11
+ export const addMCPTool = (
12
+ node: ts.Node,
13
+ checker: ts.TypeChecker,
14
+ state: InspectorState,
15
+ filters: InspectorFilters
16
+ ) => {
17
+ if (!ts.isCallExpression(node)) {
18
+ return
19
+ }
20
+
21
+ const args = node.arguments
22
+ const firstArg = args[0]
23
+ const expression = node.expression
24
+
25
+ // Check if the call is to addMCPTool
26
+ if (!ts.isIdentifier(expression) || expression.text !== 'addMCPTool') {
27
+ return
28
+ }
29
+
30
+ if (!firstArg) {
31
+ return
32
+ }
33
+
34
+ if (ts.isObjectLiteralExpression(firstArg)) {
35
+ const obj = firstArg
36
+
37
+ const nameValue = getPropertyValue(obj, 'name') as string | null
38
+ const titleValue = getPropertyValue(obj, 'title') as string | null
39
+ const descriptionValue = getPropertyValue(obj, 'description') as
40
+ | string
41
+ | null
42
+ const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
43
+ const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
44
+
45
+ const funcInitializer = getPropertyAssignmentInitializer(
46
+ obj,
47
+ 'func',
48
+ true,
49
+ checker
50
+ )
51
+ if (!funcInitializer) {
52
+ console.error(`• No valid 'func' property for MCP tool '${nameValue}'.`)
53
+ return
54
+ }
55
+
56
+ const pikkuFuncName = extractFunctionName(
57
+ funcInitializer,
58
+ checker
59
+ ).pikkuFuncName
60
+
61
+ if (!nameValue) {
62
+ console.error(`• MCP tool is missing the required 'name' property.`)
63
+ return
64
+ }
65
+
66
+ if (!descriptionValue) {
67
+ console.error(`• MCP tool '${nameValue}' is missing a description.`)
68
+ return
69
+ }
70
+
71
+ if (
72
+ !matchesFilters(
73
+ filters,
74
+ { tags },
75
+ { type: PikkuEventTypes.mcp, name: nameValue }
76
+ )
77
+ ) {
78
+ return
79
+ }
80
+
81
+ // lookup existing function metadata
82
+ const fnMeta = state.functions.meta[pikkuFuncName]
83
+ if (!fnMeta) {
84
+ console.error(`• No function metadata found for '${pikkuFuncName}'.`)
85
+ return
86
+ }
87
+ const inputSchema = fnMeta.inputs?.[0] || null
88
+ const outputSchema = fnMeta.outputs?.[0] || null
89
+
90
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName)
91
+
92
+ state.mcpEndpoints.toolsMeta[nameValue] = {
93
+ pikkuFuncName,
94
+ name: nameValue,
95
+ title: titleValue || undefined,
96
+ description: descriptionValue,
97
+ ...(streamingValue !== null && { streaming: streamingValue }),
98
+ tags,
99
+ inputSchema,
100
+ outputSchema,
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,88 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { APIDocs, PikkuEventTypes } from '@pikku/core'
4
+ import { InspectorFilters, InspectorState } from './types.js'
5
+ import {
6
+ extractFunctionName,
7
+ getPropertyAssignmentInitializer,
8
+ matchesFilters,
9
+ } from './utils.js'
10
+
11
+ export const addQueueWorker = (
12
+ node: ts.Node,
13
+ checker: ts.TypeChecker,
14
+ state: InspectorState,
15
+ filters: InspectorFilters
16
+ ) => {
17
+ if (!ts.isCallExpression(node)) {
18
+ return
19
+ }
20
+
21
+ const args = node.arguments
22
+ const firstArg = args[0]
23
+ const expression = node.expression
24
+
25
+ // Check if the call is to addQueueWorker
26
+ if (!ts.isIdentifier(expression) || expression.text !== 'addQueueWorker') {
27
+ return
28
+ }
29
+
30
+ if (!firstArg) {
31
+ return
32
+ }
33
+
34
+ if (ts.isObjectLiteralExpression(firstArg)) {
35
+ const obj = firstArg
36
+
37
+ const queueName = getPropertyValue(obj, 'queueName') as string | null
38
+ const docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
39
+ const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
40
+
41
+ // --- find the referenced function ---
42
+ const funcInitializer = getPropertyAssignmentInitializer(
43
+ obj,
44
+ 'func',
45
+ true,
46
+ checker
47
+ )
48
+ if (!funcInitializer) {
49
+ console.error(
50
+ `• No valid 'func' property for queue processor '${queueName}'.`
51
+ )
52
+ return
53
+ }
54
+
55
+ const pikkuFuncName = extractFunctionName(
56
+ funcInitializer,
57
+ checker
58
+ ).pikkuFuncName
59
+
60
+ if (!queueName) {
61
+ console.error(
62
+ `• No 'queueName' provided for queue processor function '${pikkuFuncName}'.`
63
+ )
64
+ return
65
+ }
66
+
67
+ if (
68
+ !matchesFilters(
69
+ filters,
70
+ { tags },
71
+ { type: PikkuEventTypes.queue, name: queueName }
72
+ )
73
+ ) {
74
+ console.info(
75
+ `• Skipping queue processor '${pikkuFuncName}' for queue '${queueName}' due to filter mismatch.`
76
+ )
77
+ return
78
+ }
79
+
80
+ state.queueWorkers.files.add(node.getSourceFile().fileName)
81
+ state.queueWorkers.meta[queueName] = {
82
+ pikkuFuncName,
83
+ queueName,
84
+ docs,
85
+ tags,
86
+ }
87
+ }
88
+ }
@@ -1,8 +1,12 @@
1
1
  import * as ts from 'typescript'
2
2
  import { getPropertyValue } from './get-property-value.js'
3
- import { APIDocs } from '@pikku/core'
3
+ import { APIDocs, PikkuEventTypes } from '@pikku/core'
4
4
  import { InspectorFilters, InspectorState } from './types.js'
5
- import { extractFunctionName, matchesFilters } from './utils.js'
5
+ import {
6
+ extractFunctionName,
7
+ getPropertyAssignmentInitializer,
8
+ matchesFilters,
9
+ } from './utils.js'
6
10
 
7
11
  export const addSchedule = (
8
12
  node: ts.Node,
@@ -35,22 +39,21 @@ export const addSchedule = (
35
39
  const docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
36
40
  const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
37
41
 
38
- // --- find the referenced function ---
39
- const funcProp = obj.properties.find(
40
- (p) =>
41
- ts.isPropertyAssignment(p) &&
42
- ts.isIdentifier(p.name) &&
43
- p.name.text === 'func'
44
- ) as ts.PropertyAssignment | undefined
45
-
46
- if (!funcProp || !ts.isIdentifier(funcProp.initializer)) {
42
+ const funcInitializer = getPropertyAssignmentInitializer(
43
+ obj,
44
+ 'func',
45
+ true,
46
+ checker
47
+ )
48
+ if (!funcInitializer) {
47
49
  console.error(
48
50
  `• No valid 'func' property for scheduled task '${nameValue}'.`
49
51
  )
50
52
  return
51
53
  }
54
+
52
55
  const pikkuFuncName = extractFunctionName(
53
- funcProp.initializer,
56
+ funcInitializer,
54
57
  checker
55
58
  ).pikkuFuncName
56
59
 
@@ -59,7 +62,11 @@ export const addSchedule = (
59
62
  }
60
63
 
61
64
  if (
62
- !matchesFilters(filters, { tags }, { type: 'schedule', name: nameValue })
65
+ !matchesFilters(
66
+ filters,
67
+ { tags },
68
+ { type: PikkuEventTypes.scheduled, name: nameValue }
69
+ )
63
70
  ) {
64
71
  return
65
72
  }
package/src/inspector.ts CHANGED
@@ -49,9 +49,19 @@ export const inspect = (
49
49
  meta: {},
50
50
  files: new Set(),
51
51
  },
52
+ queueWorkers: {
53
+ meta: {},
54
+ files: new Set(),
55
+ },
52
56
  rpc: {
53
57
  meta: {},
54
58
  },
59
+ mcpEndpoints: {
60
+ resourcesMeta: {},
61
+ toolsMeta: {},
62
+ promptsMeta: {},
63
+ files: new Set(),
64
+ },
55
65
  }
56
66
 
57
67
  // First sweep: add all functions
package/src/types.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { ChannelsMeta } from '@pikku/core/channel'
2
2
  import { HTTPRoutesMeta } from '@pikku/core/http'
3
3
  import { ScheduledTasksMeta } from '@pikku/core/scheduler'
4
+ import { queueWorkersMeta } from '@pikku/core/queue'
5
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core'
4
6
  import { TypesMap } from './types-map.js'
5
7
  import { FunctionsMeta } from '@pikku/core'
6
- import { RPCMeta } from '../../core/src/rpc/rpc-types.js'
8
+ import { RPCMeta } from '@pikku/core/rpc'
7
9
 
8
10
  export type PathToNameAndType = Map<
9
11
  string,
@@ -53,7 +55,17 @@ export interface InspectorState {
53
55
  meta: ScheduledTasksMeta
54
56
  files: Set<string>
55
57
  }
58
+ queueWorkers: {
59
+ meta: queueWorkersMeta
60
+ files: Set<string>
61
+ }
56
62
  rpc: {
57
63
  meta: Record<string, RPCMeta>
58
64
  }
65
+ mcpEndpoints: {
66
+ resourcesMeta: MCPResourceMeta
67
+ toolsMeta: MCPToolMeta
68
+ promptsMeta: MCPPromptMeta
69
+ files: Set<string>
70
+ }
59
71
  }
package/src/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as ts from 'typescript'
2
2
  import { InspectorFilters } from './types.js'
3
+ import { PikkuEventTypes } from '@pikku/core'
3
4
 
4
5
  type ExtractedFunctionName = {
5
6
  pikkuFuncName: string
@@ -843,7 +844,10 @@ export function getPropertyAssignmentInitializer(
843
844
  export const matchesFilters = (
844
845
  filters: InspectorFilters,
845
846
  params: { tags?: string[] },
846
- meta: { type: 'schedule' | 'http' | 'channel'; name: string }
847
+ meta: {
848
+ type: PikkuEventTypes
849
+ name: string
850
+ }
847
851
  ) => {
848
852
  if (Object.keys(filters).length === 0 || filters.tags?.length === 0) {
849
853
  return true
package/src/visit.ts CHANGED
@@ -3,6 +3,10 @@ import { addFileWithFactory } from './add-file-with-factory.js'
3
3
  import { addFileExtendsCoreType } from './add-file-extends-core-type.js'
4
4
  import { addHTTPRoute } from './add-http-route.js'
5
5
  import { addSchedule } from './add-schedule.js'
6
+ import { addQueueWorker } from './add-queue-worker.js'
7
+ import { addMCPResource } from './add-mcp-resource.js'
8
+ import { addMCPTool } from './add-mcp-tool.js'
9
+ import { addMCPPrompt } from './add-mcp-prompt.js'
6
10
  import { InspectorFilters, InspectorState } from './types.js'
7
11
  import { addFunctions } from './add-functions.js'
8
12
  import { addChannel } from './add-channel.js'
@@ -62,6 +66,10 @@ export const visitRoutes = (
62
66
  ) => {
63
67
  addHTTPRoute(node, checker, state, filters)
64
68
  addSchedule(node, checker, state, filters)
69
+ addQueueWorker(node, checker, state, filters)
65
70
  addChannel(node, checker, state, filters)
71
+ addMCPResource(node, checker, state, filters)
72
+ addMCPTool(node, checker, state, filters)
73
+ addMCPPrompt(node, checker, state, filters)
66
74
  ts.forEachChild(node, (child) => visitRoutes(checker, child, state, filters))
67
75
  }
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "extends": "../tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "rootDir": "./src",
5
- "module": "Node16",
5
+ "module": "Node18",
6
6
  "outDir": "dist",
7
7
  "target": "esnext",
8
8
  "declaration": true,