prinfer 0.2.1 → 0.4.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.
- package/README.md +63 -3
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp.cjs +234 -0
- package/dist/mcp.cjs.map +1 -0
- package/dist/mcp.d.cts +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +211 -0
- package/dist/mcp.js.map +1 -0
- package/dist/postinstall.cjs +90 -0
- package/dist/postinstall.cjs.map +1 -0
- package/dist/postinstall.d.cts +1 -0
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +67 -0
- package/dist/postinstall.js.map +1 -0
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@ npm install prinfer
|
|
|
14
14
|
# Basic usage
|
|
15
15
|
prinfer src/utils.ts myFunction
|
|
16
16
|
|
|
17
|
+
# Find variable at specific line
|
|
18
|
+
prinfer src/utils.ts:75 commandResult
|
|
19
|
+
|
|
17
20
|
# With custom tsconfig
|
|
18
21
|
prinfer src/utils.ts myFunction --project ./tsconfig.json
|
|
19
22
|
|
|
@@ -40,24 +43,32 @@ console.log(result.signature);
|
|
|
40
43
|
console.log(result.returnType);
|
|
41
44
|
// => "boolean"
|
|
42
45
|
|
|
46
|
+
// Find variable at specific line
|
|
47
|
+
const result2 = inferType("./src/utils.ts", "commandResult", { line: 75 });
|
|
48
|
+
console.log(result2.signature);
|
|
49
|
+
// => "Result<VaultAction[], CommandError>"
|
|
50
|
+
|
|
43
51
|
// With custom tsconfig
|
|
44
|
-
const
|
|
52
|
+
const result3 = inferType("./src/utils.ts", "myFunction", { project: "./tsconfig.json" });
|
|
45
53
|
```
|
|
46
54
|
|
|
47
55
|
### API Reference
|
|
48
56
|
|
|
49
|
-
#### `inferType(file, name,
|
|
57
|
+
#### `inferType(file, name, options?)`
|
|
50
58
|
|
|
51
59
|
Infer the type of a function or variable in a TypeScript file.
|
|
52
60
|
|
|
53
61
|
**Parameters:**
|
|
54
62
|
- `file` - Path to the TypeScript file
|
|
55
63
|
- `name` - Name of the function/variable to inspect
|
|
56
|
-
- `
|
|
64
|
+
- `options` - Optional object with:
|
|
65
|
+
- `line` - Line number to narrow search (1-based)
|
|
66
|
+
- `project` - Path to tsconfig.json
|
|
57
67
|
|
|
58
68
|
**Returns:** `InferredTypeResult`
|
|
59
69
|
- `signature` - The inferred type signature
|
|
60
70
|
- `returnType` - The return type (for functions)
|
|
71
|
+
- `line` - The line number where the symbol was found
|
|
61
72
|
|
|
62
73
|
#### Types
|
|
63
74
|
|
|
@@ -65,15 +76,64 @@ Infer the type of a function or variable in a TypeScript file.
|
|
|
65
76
|
interface Options {
|
|
66
77
|
file: string;
|
|
67
78
|
name: string;
|
|
79
|
+
line?: number;
|
|
68
80
|
project?: string;
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
interface InferredTypeResult {
|
|
72
84
|
signature: string;
|
|
73
85
|
returnType?: string;
|
|
86
|
+
line?: number;
|
|
74
87
|
}
|
|
75
88
|
```
|
|
76
89
|
|
|
90
|
+
## MCP Server (Claude Integration)
|
|
91
|
+
|
|
92
|
+
prinfer includes an MCP server for use with Claude Code or Claude Desktop.
|
|
93
|
+
|
|
94
|
+
### Setup for Claude Code
|
|
95
|
+
|
|
96
|
+
Add to your `~/.claude/claude_desktop_config.json`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"prinfer": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["prinfer-mcp"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Or if installed globally (`npm i -g prinfer`):
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"prinfer": {
|
|
115
|
+
"command": "prinfer-mcp"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Tool Usage
|
|
122
|
+
|
|
123
|
+
Once configured, Claude can use the `infer_type` tool:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
infer_type(file: "src/utils.ts", name: "myFunction")
|
|
127
|
+
infer_type(file: "src/utils.ts", name: "commandResult", line: 75)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Auto-installed Skill
|
|
131
|
+
|
|
132
|
+
When you install prinfer globally (`npm i -g prinfer`), a Claude skill is automatically added to `~/.claude/skills/prinfer.md`. This provides:
|
|
133
|
+
|
|
134
|
+
1. **Coding guideline** - Encourages Claude to prefer type inference over explicit annotations
|
|
135
|
+
2. **`/check-type` command** - Check types directly: `/check-type src/utils.ts:75 commandResult`
|
|
136
|
+
|
|
77
137
|
## Requirements
|
|
78
138
|
|
|
79
139
|
- Node.js >= 18.0.0
|
package/dist/cli.cjs
CHANGED
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findNodeByNameAndLine, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tfindNodeByNameAndLine,\n\tgetLineNumber,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param options - Optional: project path (string) or options object with line and project\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * // By name only\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n *\n * // By name and line number\n * const result2 = inferType(\"./src/utils.ts\", \"commandResult\", { line: 75 });\n * console.log(result2.signature);\n * // => \"Result<VaultAction[], CommandError>\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\toptions?: string | { line?: number; project?: string },\n): InferredTypeResult {\n\t// Normalize options for backward compatibility\n\tconst opts =\n\t\ttypeof options === \"string\" ? { project: options } : (options ?? {});\n\tconst { line, project } = opts;\n\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findNodeByNameAndLine(sourceFile, name, line);\n\tif (!node) {\n\t\tconst lineInfo = line !== undefined ? ` at line ${line}` : \"\";\n\t\tthrow new Error(\n\t\t\t`No symbol named \"${name}\"${lineInfo} found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node, sourceFile);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, {\n\t\tline: options.line,\n\t\tproject: options.project,\n\t});\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a node is any variable declaration with the given name\n */\nfunction isVariableNamed(node: ts.Node, name: string): boolean {\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if a node matches the given name (function-like or variable)\n */\nfunction isNamedNode(node: ts.Node, name: string): boolean {\n\treturn isFunctionLikeNamed(node, name) || isVariableNamed(node, name);\n}\n\n/**\n * Get the 1-based line number for a node\n */\nexport function getLineNumber(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n): number {\n\tconst { line } = sourceFile.getLineAndCharacterOfPosition(\n\t\tnode.getStart(sourceFile),\n\t);\n\treturn line + 1;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Find a node by name and optionally by line number\n */\nexport function findNodeByNameAndLine(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n\tline?: number,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isNamedNode(node, name)) {\n\t\t\tif (line !== undefined) {\n\t\t\t\tconst nodeLine = getLineNumber(sourceFile, node);\n\t\t\t\tif (nodeLine === line) {\n\t\t\t\t\tfound = node;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfound = node;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n\tsourceFile?: ts.SourceFile,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\tconst sf = sourceFile ?? node.getSourceFile();\n\tconst line = getLineNumber(sf, node);\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType, line };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags), line };\n}\n","#!/usr/bin/env bun\nimport { inferType } from \"./index.js\";\n\nconst HELP = `\nprinfer - TypeScript type inference inspection tool\n\nUsage:\n prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]\n\nArguments:\n file.ts Path to the TypeScript file\n :line Optional line number to narrow search (e.g., file.ts:75)\n name Name of the function/variable to inspect\n\nOptions:\n --project, -p Path to tsconfig.json (optional)\n --help, -h Show this help message\n\nExamples:\n prinfer src/utils.ts myFunction\n prinfer src/utils.ts:75 commandResult\n prinfer src/utils.ts myFunction --project ./tsconfig.json\n`.trim();\n\ninterface CliOptions {\n\tfile: string;\n\tname: string;\n\tline?: number;\n\tproject?: string;\n}\n\nfunction parseFileArg(arg: string): { file: string; line?: number } {\n\t// Match pattern: file.ts:123 or just file.ts\n\tconst match = arg.match(/^(.+):(\\d+)$/);\n\tif (match) {\n\t\treturn { file: match[1], line: Number.parseInt(match[2], 10) };\n\t}\n\treturn { file: arg };\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n\tconst args = argv.slice(2);\n\n\t// Check for help flag\n\tif (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n\t\tconsole.log(HELP);\n\t\treturn null;\n\t}\n\n\tconst fileArg = args[0];\n\tconst name = args[1];\n\n\tif (!fileArg || !name) {\n\t\tconsole.error(\n\t\t\t\"Error: Both <file> and <name> arguments are required.\\n\",\n\t\t);\n\t\tconsole.log(HELP);\n\t\tprocess.exit(1);\n\t}\n\n\tconst { file, line } = parseFileArg(fileArg);\n\n\t// Find project option\n\tlet project: string | undefined;\n\tconst projectIdx = args.findIndex((a) => a === \"--project\" || a === \"-p\");\n\tif (projectIdx >= 0) {\n\t\tproject = args[projectIdx + 1];\n\t\tif (!project) {\n\t\t\tconsole.error(\"Error: --project requires a path argument.\\n\");\n\t\t\tconsole.log(HELP);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treturn { file, name, line, project };\n}\n\nfunction main(): void {\n\tconst options = parseArgs(process.argv);\n\n\tif (!options) {\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tconst result = inferType(options.file, options.name, {\n\t\t\tline: options.line,\n\t\t\tproject: options.project,\n\t\t});\n\n\t\tconsole.log(result.signature);\n\t\tif (result.returnType) {\n\t\t\tconsole.log(\"returns:\", result.returnType);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error((error as Error).message);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"mappings":"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","names":["import_node_path","ts","path","ts","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findNodeByNameAndLine, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tfindNodeByNameAndLine,\n\tgetLineNumber,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param options - Optional: project path (string) or options object with line and project\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * // By name only\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n *\n * // By name and line number\n * const result2 = inferType(\"./src/utils.ts\", \"commandResult\", { line: 75 });\n * console.log(result2.signature);\n * // => \"Result<VaultAction[], CommandError>\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\toptions?: string | { line?: number; project?: string },\n): InferredTypeResult {\n\t// Normalize options for backward compatibility\n\tconst opts =\n\t\ttypeof options === \"string\" ? { project: options } : (options ?? {});\n\tconst { line, project } = opts;\n\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findNodeByNameAndLine(sourceFile, name, line);\n\tif (!node) {\n\t\tconst lineInfo = line !== undefined ? ` at line ${line}` : \"\";\n\t\tthrow new Error(\n\t\t\t`No symbol named \"${name}\"${lineInfo} found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node, sourceFile);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, {\n\t\tline: options.line,\n\t\tproject: options.project,\n\t});\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a node is any variable declaration with the given name\n */\nfunction isVariableNamed(node: ts.Node, name: string): boolean {\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if a node matches the given name (function-like or variable)\n */\nfunction isNamedNode(node: ts.Node, name: string): boolean {\n\treturn isFunctionLikeNamed(node, name) || isVariableNamed(node, name);\n}\n\n/**\n * Get the 1-based line number for a node\n */\nexport function getLineNumber(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n): number {\n\tconst { line } = sourceFile.getLineAndCharacterOfPosition(\n\t\tnode.getStart(sourceFile),\n\t);\n\treturn line + 1;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Find a node by name and optionally by line number\n */\nexport function findNodeByNameAndLine(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n\tline?: number,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isNamedNode(node, name)) {\n\t\t\tif (line !== undefined) {\n\t\t\t\tconst nodeLine = getLineNumber(sourceFile, node);\n\t\t\t\tif (nodeLine === line) {\n\t\t\t\t\tfound = node;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfound = node;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n\tsourceFile?: ts.SourceFile,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\tconst sf = sourceFile ?? node.getSourceFile();\n\tconst line = getLineNumber(sf, node);\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType, line };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags), line };\n}\n","#!/usr/bin/env node\nimport { inferType } from \"./index.js\";\n\nconst HELP = `\nprinfer - TypeScript type inference inspection tool\n\nUsage:\n prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]\n\nArguments:\n file.ts Path to the TypeScript file\n :line Optional line number to narrow search (e.g., file.ts:75)\n name Name of the function/variable to inspect\n\nOptions:\n --project, -p Path to tsconfig.json (optional)\n --help, -h Show this help message\n\nExamples:\n prinfer src/utils.ts myFunction\n prinfer src/utils.ts:75 commandResult\n prinfer src/utils.ts myFunction --project ./tsconfig.json\n`.trim();\n\ninterface CliOptions {\n\tfile: string;\n\tname: string;\n\tline?: number;\n\tproject?: string;\n}\n\nfunction parseFileArg(arg: string): { file: string; line?: number } {\n\t// Match pattern: file.ts:123 or just file.ts\n\tconst match = arg.match(/^(.+):(\\d+)$/);\n\tif (match) {\n\t\treturn { file: match[1], line: Number.parseInt(match[2], 10) };\n\t}\n\treturn { file: arg };\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n\tconst args = argv.slice(2);\n\n\t// Check for help flag\n\tif (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n\t\tconsole.log(HELP);\n\t\treturn null;\n\t}\n\n\tconst fileArg = args[0];\n\tconst name = args[1];\n\n\tif (!fileArg || !name) {\n\t\tconsole.error(\n\t\t\t\"Error: Both <file> and <name> arguments are required.\\n\",\n\t\t);\n\t\tconsole.log(HELP);\n\t\tprocess.exit(1);\n\t}\n\n\tconst { file, line } = parseFileArg(fileArg);\n\n\t// Find project option\n\tlet project: string | undefined;\n\tconst projectIdx = args.findIndex((a) => a === \"--project\" || a === \"-p\");\n\tif (projectIdx >= 0) {\n\t\tproject = args[projectIdx + 1];\n\t\tif (!project) {\n\t\t\tconsole.error(\"Error: --project requires a path argument.\\n\");\n\t\t\tconsole.log(HELP);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treturn { file, name, line, project };\n}\n\nfunction main(): void {\n\tconst options = parseArgs(process.argv);\n\n\tif (!options) {\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tconst result = inferType(options.file, options.name, {\n\t\t\tline: options.line,\n\t\t\tproject: options.project,\n\t\t});\n\n\t\tconsole.log(result.signature);\n\t\tif (result.returnType) {\n\t\t\tconsole.log(\"returns:\", result.returnType);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error((error as Error).message);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"mappings":"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","names":["import_node_path","ts","path","ts","path","fs"]}
|
package/dist/cli.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
CHANGED
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findNodeByNameAndLine, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tfindNodeByNameAndLine,\n\tgetLineNumber,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param options - Optional: project path (string) or options object with line and project\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * // By name only\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n *\n * // By name and line number\n * const result2 = inferType(\"./src/utils.ts\", \"commandResult\", { line: 75 });\n * console.log(result2.signature);\n * // => \"Result<VaultAction[], CommandError>\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\toptions?: string | { line?: number; project?: string },\n): InferredTypeResult {\n\t// Normalize options for backward compatibility\n\tconst opts =\n\t\ttypeof options === \"string\" ? { project: options } : (options ?? {});\n\tconst { line, project } = opts;\n\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findNodeByNameAndLine(sourceFile, name, line);\n\tif (!node) {\n\t\tconst lineInfo = line !== undefined ? ` at line ${line}` : \"\";\n\t\tthrow new Error(\n\t\t\t`No symbol named \"${name}\"${lineInfo} found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node, sourceFile);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, {\n\t\tline: options.line,\n\t\tproject: options.project,\n\t});\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a node is any variable declaration with the given name\n */\nfunction isVariableNamed(node: ts.Node, name: string): boolean {\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if a node matches the given name (function-like or variable)\n */\nfunction isNamedNode(node: ts.Node, name: string): boolean {\n\treturn isFunctionLikeNamed(node, name) || isVariableNamed(node, name);\n}\n\n/**\n * Get the 1-based line number for a node\n */\nexport function getLineNumber(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n): number {\n\tconst { line } = sourceFile.getLineAndCharacterOfPosition(\n\t\tnode.getStart(sourceFile),\n\t);\n\treturn line + 1;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Find a node by name and optionally by line number\n */\nexport function findNodeByNameAndLine(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n\tline?: number,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isNamedNode(node, name)) {\n\t\t\tif (line !== undefined) {\n\t\t\t\tconst nodeLine = getLineNumber(sourceFile, node);\n\t\t\t\tif (nodeLine === line) {\n\t\t\t\t\tfound = node;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfound = node;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n\tsourceFile?: ts.SourceFile,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\tconst sf = sourceFile ?? node.getSourceFile();\n\tconst line = getLineNumber(sf, node);\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType, line };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags), line };\n}\n","#!/usr/bin/env bun\nimport { inferType } from \"./index.js\";\n\nconst HELP = `\nprinfer - TypeScript type inference inspection tool\n\nUsage:\n prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]\n\nArguments:\n file.ts Path to the TypeScript file\n :line Optional line number to narrow search (e.g., file.ts:75)\n name Name of the function/variable to inspect\n\nOptions:\n --project, -p Path to tsconfig.json (optional)\n --help, -h Show this help message\n\nExamples:\n prinfer src/utils.ts myFunction\n prinfer src/utils.ts:75 commandResult\n prinfer src/utils.ts myFunction --project ./tsconfig.json\n`.trim();\n\ninterface CliOptions {\n\tfile: string;\n\tname: string;\n\tline?: number;\n\tproject?: string;\n}\n\nfunction parseFileArg(arg: string): { file: string; line?: number } {\n\t// Match pattern: file.ts:123 or just file.ts\n\tconst match = arg.match(/^(.+):(\\d+)$/);\n\tif (match) {\n\t\treturn { file: match[1], line: Number.parseInt(match[2], 10) };\n\t}\n\treturn { file: arg };\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n\tconst args = argv.slice(2);\n\n\t// Check for help flag\n\tif (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n\t\tconsole.log(HELP);\n\t\treturn null;\n\t}\n\n\tconst fileArg = args[0];\n\tconst name = args[1];\n\n\tif (!fileArg || !name) {\n\t\tconsole.error(\n\t\t\t\"Error: Both <file> and <name> arguments are required.\\n\",\n\t\t);\n\t\tconsole.log(HELP);\n\t\tprocess.exit(1);\n\t}\n\n\tconst { file, line } = parseFileArg(fileArg);\n\n\t// Find project option\n\tlet project: string | undefined;\n\tconst projectIdx = args.findIndex((a) => a === \"--project\" || a === \"-p\");\n\tif (projectIdx >= 0) {\n\t\tproject = args[projectIdx + 1];\n\t\tif (!project) {\n\t\t\tconsole.error(\"Error: --project requires a path argument.\\n\");\n\t\t\tconsole.log(HELP);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treturn { file, name, line, project };\n}\n\nfunction main(): void {\n\tconst options = parseArgs(process.argv);\n\n\tif (!options) {\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tconst result = inferType(options.file, options.name, {\n\t\t\tline: options.line,\n\t\t\tproject: options.project,\n\t\t});\n\n\t\tconsole.log(result.signature);\n\t\tif (result.returnType) {\n\t\t\tconsole.log(\"returns:\", result.returnType);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error((error as Error).message);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"mappings":"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","names":["path","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findNodeByNameAndLine, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tfindNodeByNameAndLine,\n\tgetLineNumber,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param options - Optional: project path (string) or options object with line and project\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * // By name only\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n *\n * // By name and line number\n * const result2 = inferType(\"./src/utils.ts\", \"commandResult\", { line: 75 });\n * console.log(result2.signature);\n * // => \"Result<VaultAction[], CommandError>\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\toptions?: string | { line?: number; project?: string },\n): InferredTypeResult {\n\t// Normalize options for backward compatibility\n\tconst opts =\n\t\ttypeof options === \"string\" ? { project: options } : (options ?? {});\n\tconst { line, project } = opts;\n\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findNodeByNameAndLine(sourceFile, name, line);\n\tif (!node) {\n\t\tconst lineInfo = line !== undefined ? ` at line ${line}` : \"\";\n\t\tthrow new Error(\n\t\t\t`No symbol named \"${name}\"${lineInfo} found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node, sourceFile);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, {\n\t\tline: options.line,\n\t\tproject: options.project,\n\t});\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a node is any variable declaration with the given name\n */\nfunction isVariableNamed(node: ts.Node, name: string): boolean {\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if a node matches the given name (function-like or variable)\n */\nfunction isNamedNode(node: ts.Node, name: string): boolean {\n\treturn isFunctionLikeNamed(node, name) || isVariableNamed(node, name);\n}\n\n/**\n * Get the 1-based line number for a node\n */\nexport function getLineNumber(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n): number {\n\tconst { line } = sourceFile.getLineAndCharacterOfPosition(\n\t\tnode.getStart(sourceFile),\n\t);\n\treturn line + 1;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Find a node by name and optionally by line number\n */\nexport function findNodeByNameAndLine(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n\tline?: number,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isNamedNode(node, name)) {\n\t\t\tif (line !== undefined) {\n\t\t\t\tconst nodeLine = getLineNumber(sourceFile, node);\n\t\t\t\tif (nodeLine === line) {\n\t\t\t\t\tfound = node;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfound = node;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n\tsourceFile?: ts.SourceFile,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\tconst sf = sourceFile ?? node.getSourceFile();\n\tconst line = getLineNumber(sf, node);\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType, line };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags), line };\n}\n","#!/usr/bin/env node\nimport { inferType } from \"./index.js\";\n\nconst HELP = `\nprinfer - TypeScript type inference inspection tool\n\nUsage:\n prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]\n\nArguments:\n file.ts Path to the TypeScript file\n :line Optional line number to narrow search (e.g., file.ts:75)\n name Name of the function/variable to inspect\n\nOptions:\n --project, -p Path to tsconfig.json (optional)\n --help, -h Show this help message\n\nExamples:\n prinfer src/utils.ts myFunction\n prinfer src/utils.ts:75 commandResult\n prinfer src/utils.ts myFunction --project ./tsconfig.json\n`.trim();\n\ninterface CliOptions {\n\tfile: string;\n\tname: string;\n\tline?: number;\n\tproject?: string;\n}\n\nfunction parseFileArg(arg: string): { file: string; line?: number } {\n\t// Match pattern: file.ts:123 or just file.ts\n\tconst match = arg.match(/^(.+):(\\d+)$/);\n\tif (match) {\n\t\treturn { file: match[1], line: Number.parseInt(match[2], 10) };\n\t}\n\treturn { file: arg };\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n\tconst args = argv.slice(2);\n\n\t// Check for help flag\n\tif (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n\t\tconsole.log(HELP);\n\t\treturn null;\n\t}\n\n\tconst fileArg = args[0];\n\tconst name = args[1];\n\n\tif (!fileArg || !name) {\n\t\tconsole.error(\n\t\t\t\"Error: Both <file> and <name> arguments are required.\\n\",\n\t\t);\n\t\tconsole.log(HELP);\n\t\tprocess.exit(1);\n\t}\n\n\tconst { file, line } = parseFileArg(fileArg);\n\n\t// Find project option\n\tlet project: string | undefined;\n\tconst projectIdx = args.findIndex((a) => a === \"--project\" || a === \"-p\");\n\tif (projectIdx >= 0) {\n\t\tproject = args[projectIdx + 1];\n\t\tif (!project) {\n\t\t\tconsole.error(\"Error: --project requires a path argument.\\n\");\n\t\t\tconsole.log(HELP);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treturn { file, name, line, project };\n}\n\nfunction main(): void {\n\tconst options = parseArgs(process.argv);\n\n\tif (!options) {\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tconst result = inferType(options.file, options.name, {\n\t\t\tline: options.line,\n\t\t\tproject: options.project,\n\t\t});\n\n\t\tconsole.log(result.signature);\n\t\tif (result.returnType) {\n\t\t\tconsole.log(\"returns:\", result.returnType);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error((error as Error).message);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"mappings":"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","names":["path","path"]}
|
package/dist/mcp.cjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/mcp.ts
|
|
27
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
28
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
29
|
+
var import_zod = require("zod");
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
33
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
34
|
+
|
|
35
|
+
// src/core.ts
|
|
36
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
37
|
+
var import_typescript = __toESM(require("typescript"), 1);
|
|
38
|
+
function findNearestTsconfig(startDir) {
|
|
39
|
+
return import_typescript.default.findConfigFile(startDir, import_typescript.default.sys.fileExists, "tsconfig.json") ?? void 0;
|
|
40
|
+
}
|
|
41
|
+
function loadProgram(entryFileAbs, project) {
|
|
42
|
+
const fileDir = import_node_path.default.dirname(entryFileAbs);
|
|
43
|
+
const tsconfigPath = project ? import_node_path.default.resolve(process.cwd(), project) : findNearestTsconfig(fileDir);
|
|
44
|
+
if (!tsconfigPath) {
|
|
45
|
+
return import_typescript.default.createProgram([entryFileAbs], {
|
|
46
|
+
target: import_typescript.default.ScriptTarget.ES2022,
|
|
47
|
+
module: import_typescript.default.ModuleKind.ESNext,
|
|
48
|
+
strict: true,
|
|
49
|
+
allowJs: true,
|
|
50
|
+
checkJs: false,
|
|
51
|
+
moduleResolution: import_typescript.default.ModuleResolutionKind.Bundler,
|
|
52
|
+
skipLibCheck: true
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const cfg = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
|
|
56
|
+
if (cfg.error) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
import_typescript.default.flattenDiagnosticMessageText(cfg.error.messageText, "\n")
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const parsed = import_typescript.default.parseJsonConfigFileContent(
|
|
62
|
+
cfg.config,
|
|
63
|
+
import_typescript.default.sys,
|
|
64
|
+
import_node_path.default.dirname(tsconfigPath)
|
|
65
|
+
);
|
|
66
|
+
return import_typescript.default.createProgram({
|
|
67
|
+
rootNames: parsed.fileNames,
|
|
68
|
+
options: parsed.options
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function isArrowOrFnExpr(n) {
|
|
72
|
+
return !!n && (import_typescript.default.isArrowFunction(n) || import_typescript.default.isFunctionExpression(n));
|
|
73
|
+
}
|
|
74
|
+
function nodeNameText(n) {
|
|
75
|
+
if (!n) return void 0;
|
|
76
|
+
if (import_typescript.default.isIdentifier(n)) return n.text;
|
|
77
|
+
if (import_typescript.default.isStringLiteral(n)) return n.text;
|
|
78
|
+
if (import_typescript.default.isNumericLiteral(n)) return n.text;
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
function isFunctionLikeNamed(node, name) {
|
|
82
|
+
if (import_typescript.default.isFunctionDeclaration(node) && node.name?.text === name) return true;
|
|
83
|
+
if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name) && node.name.text === name) {
|
|
84
|
+
return isArrowOrFnExpr(node.initializer);
|
|
85
|
+
}
|
|
86
|
+
if ((import_typescript.default.isMethodDeclaration(node) || import_typescript.default.isMethodSignature(node)) && nodeNameText(node.name) === name) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (import_typescript.default.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
|
|
90
|
+
return isArrowOrFnExpr(node.initializer);
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function isVariableNamed(node, name) {
|
|
95
|
+
if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name) && node.name.text === name) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
function isNamedNode(node, name) {
|
|
101
|
+
return isFunctionLikeNamed(node, name) || isVariableNamed(node, name);
|
|
102
|
+
}
|
|
103
|
+
function getLineNumber(sourceFile, node) {
|
|
104
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(
|
|
105
|
+
node.getStart(sourceFile)
|
|
106
|
+
);
|
|
107
|
+
return line + 1;
|
|
108
|
+
}
|
|
109
|
+
function findNodeByNameAndLine(sourceFile, name, line) {
|
|
110
|
+
let found;
|
|
111
|
+
const visit = (node) => {
|
|
112
|
+
if (found) return;
|
|
113
|
+
if (isNamedNode(node, name)) {
|
|
114
|
+
if (line !== void 0) {
|
|
115
|
+
const nodeLine = getLineNumber(sourceFile, node);
|
|
116
|
+
if (nodeLine === line) {
|
|
117
|
+
found = node;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
found = node;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
import_typescript.default.forEachChild(node, visit);
|
|
126
|
+
};
|
|
127
|
+
visit(sourceFile);
|
|
128
|
+
return found;
|
|
129
|
+
}
|
|
130
|
+
function getTypeInfo(program, node, sourceFile) {
|
|
131
|
+
const checker = program.getTypeChecker();
|
|
132
|
+
const sf = sourceFile ?? node.getSourceFile();
|
|
133
|
+
const line = getLineNumber(sf, node);
|
|
134
|
+
let sig;
|
|
135
|
+
if (import_typescript.default.isFunctionDeclaration(node) || import_typescript.default.isMethodDeclaration(node)) {
|
|
136
|
+
sig = checker.getSignatureFromDeclaration(node) ?? void 0;
|
|
137
|
+
} else if (import_typescript.default.isVariableDeclaration(node)) {
|
|
138
|
+
const init = node.initializer;
|
|
139
|
+
if (isArrowOrFnExpr(init))
|
|
140
|
+
sig = checker.getSignatureFromDeclaration(init) ?? void 0;
|
|
141
|
+
} else if (import_typescript.default.isPropertyAssignment(node)) {
|
|
142
|
+
const init = node.initializer;
|
|
143
|
+
if (isArrowOrFnExpr(init))
|
|
144
|
+
sig = checker.getSignatureFromDeclaration(init) ?? void 0;
|
|
145
|
+
} else if (import_typescript.default.isMethodSignature(node)) {
|
|
146
|
+
sig = checker.getSignatureFromDeclaration(node) ?? void 0;
|
|
147
|
+
}
|
|
148
|
+
const flags = import_typescript.default.TypeFormatFlags.NoTruncation;
|
|
149
|
+
if (sig) {
|
|
150
|
+
const signature = checker.signatureToString(sig, void 0, flags);
|
|
151
|
+
const ret = checker.getReturnTypeOfSignature(sig);
|
|
152
|
+
const returnType = checker.typeToString(ret, void 0, flags);
|
|
153
|
+
return { signature, returnType, line };
|
|
154
|
+
}
|
|
155
|
+
const nodeWithName = node;
|
|
156
|
+
let nameNode = node;
|
|
157
|
+
if (nodeWithName.name && import_typescript.default.isIdentifier(nodeWithName.name)) {
|
|
158
|
+
nameNode = nodeWithName.name;
|
|
159
|
+
}
|
|
160
|
+
const t = checker.getTypeAtLocation(nameNode);
|
|
161
|
+
return { signature: checker.typeToString(t, void 0, flags), line };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/index.ts
|
|
165
|
+
function inferType(file, name, options) {
|
|
166
|
+
const opts = typeof options === "string" ? { project: options } : options ?? {};
|
|
167
|
+
const { line, project } = opts;
|
|
168
|
+
const entryFileAbs = import_node_path2.default.resolve(process.cwd(), file);
|
|
169
|
+
if (!import_node_fs.default.existsSync(entryFileAbs)) {
|
|
170
|
+
throw new Error(`File not found: ${entryFileAbs}`);
|
|
171
|
+
}
|
|
172
|
+
const program = loadProgram(entryFileAbs, project);
|
|
173
|
+
const sourceFile = program.getSourceFile(entryFileAbs);
|
|
174
|
+
if (!sourceFile) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const node = findNodeByNameAndLine(sourceFile, name, line);
|
|
180
|
+
if (!node) {
|
|
181
|
+
const lineInfo = line !== void 0 ? ` at line ${line}` : "";
|
|
182
|
+
throw new Error(
|
|
183
|
+
`No symbol named "${name}"${lineInfo} found in ${entryFileAbs}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return getTypeInfo(program, node, sourceFile);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/mcp.ts
|
|
190
|
+
var server = new import_mcp.McpServer({
|
|
191
|
+
name: "prinfer",
|
|
192
|
+
version: "0.2.1"
|
|
193
|
+
});
|
|
194
|
+
server.tool(
|
|
195
|
+
"infer_type",
|
|
196
|
+
"Infer the TypeScript type of a function or variable in a file. Returns the type signature and optionally the return type for functions.",
|
|
197
|
+
{
|
|
198
|
+
file: import_zod.z.string().describe("Path to the TypeScript file"),
|
|
199
|
+
name: import_zod.z.string().describe("Name of the function or variable to inspect"),
|
|
200
|
+
line: import_zod.z.number().optional().describe("Optional line number to narrow search (1-based)"),
|
|
201
|
+
project: import_zod.z.string().optional().describe("Optional path to tsconfig.json")
|
|
202
|
+
},
|
|
203
|
+
async ({ file, name, line, project }) => {
|
|
204
|
+
try {
|
|
205
|
+
const result = inferType(file, name, { line, project });
|
|
206
|
+
let text = `Type: ${result.signature}`;
|
|
207
|
+
if (result.returnType) {
|
|
208
|
+
text += `
|
|
209
|
+
Returns: ${result.returnType}`;
|
|
210
|
+
}
|
|
211
|
+
if (result.line) {
|
|
212
|
+
text += `
|
|
213
|
+
Line: ${result.line}`;
|
|
214
|
+
}
|
|
215
|
+
return { content: [{ type: "text", text }] };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: "text",
|
|
221
|
+
text: `Error: ${error.message}`
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
isError: true
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
async function main() {
|
|
230
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
231
|
+
await server.connect(transport);
|
|
232
|
+
}
|
|
233
|
+
main().catch(console.error);
|
|
234
|
+
//# sourceMappingURL=mcp.cjs.map
|
package/dist/mcp.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/index.ts","../src/core.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { inferType } from \"./index.js\";\n\nconst server = new McpServer({\n\tname: \"prinfer\",\n\tversion: \"0.2.1\",\n});\n\nserver.tool(\n\t\"infer_type\",\n\t\"Infer the TypeScript type of a function or variable in a file. Returns the type signature and optionally the return type for functions.\",\n\t{\n\t\tfile: z.string().describe(\"Path to the TypeScript file\"),\n\t\tname: z\n\t\t\t.string()\n\t\t\t.describe(\"Name of the function or variable to inspect\"),\n\t\tline: z\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe(\"Optional line number to narrow search (1-based)\"),\n\t\tproject: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\"Optional path to tsconfig.json\"),\n\t},\n\tasync ({ file, name, line, project }) => {\n\t\ttry {\n\t\t\tconst result = inferType(file, name, { line, project });\n\t\t\tlet text = `Type: ${result.signature}`;\n\t\t\tif (result.returnType) {\n\t\t\t\ttext += `\\nReturns: ${result.returnType}`;\n\t\t\t}\n\t\t\tif (result.line) {\n\t\t\t\ttext += `\\nLine: ${result.line}`;\n\t\t\t}\n\t\t\treturn { content: [{ type: \"text\", text }] };\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Error: ${(error as Error).message}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\t},\n);\n\nasync function main() {\n\tconst transport = new StdioServerTransport();\n\tawait server.connect(transport);\n}\n\nmain().catch(console.error);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findNodeByNameAndLine, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tfindNodeByNameAndLine,\n\tgetLineNumber,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param options - Optional: project path (string) or options object with line and project\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * // By name only\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n *\n * // By name and line number\n * const result2 = inferType(\"./src/utils.ts\", \"commandResult\", { line: 75 });\n * console.log(result2.signature);\n * // => \"Result<VaultAction[], CommandError>\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\toptions?: string | { line?: number; project?: string },\n): InferredTypeResult {\n\t// Normalize options for backward compatibility\n\tconst opts =\n\t\ttypeof options === \"string\" ? { project: options } : (options ?? {});\n\tconst { line, project } = opts;\n\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findNodeByNameAndLine(sourceFile, name, line);\n\tif (!node) {\n\t\tconst lineInfo = line !== undefined ? ` at line ${line}` : \"\";\n\t\tthrow new Error(\n\t\t\t`No symbol named \"${name}\"${lineInfo} found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node, sourceFile);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, {\n\t\tline: options.line,\n\t\tproject: options.project,\n\t});\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a node is any variable declaration with the given name\n */\nfunction isVariableNamed(node: ts.Node, name: string): boolean {\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if a node matches the given name (function-like or variable)\n */\nfunction isNamedNode(node: ts.Node, name: string): boolean {\n\treturn isFunctionLikeNamed(node, name) || isVariableNamed(node, name);\n}\n\n/**\n * Get the 1-based line number for a node\n */\nexport function getLineNumber(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n): number {\n\tconst { line } = sourceFile.getLineAndCharacterOfPosition(\n\t\tnode.getStart(sourceFile),\n\t);\n\treturn line + 1;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Find a node by name and optionally by line number\n */\nexport function findNodeByNameAndLine(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n\tline?: number,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isNamedNode(node, name)) {\n\t\t\tif (line !== undefined) {\n\t\t\t\tconst nodeLine = getLineNumber(sourceFile, node);\n\t\t\t\tif (nodeLine === line) {\n\t\t\t\t\tfound = node;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfound = node;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n\tsourceFile?: ts.SourceFile,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\tconst sf = sourceFile ?? node.getSourceFile();\n\tconst line = getLineNumber(sf, node);\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType, line };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags), line };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,iBAA0B;AAC1B,mBAAqC;AACrC,iBAAkB;;;ACHlB,qBAAe;AACf,IAAAA,oBAAiB;;;ACDjB,uBAAiB;AACjB,wBAAe;AAMR,SAAS,oBAAoB,UAAsC;AACzE,SACC,kBAAAC,QAAG,eAAe,UAAU,kBAAAA,QAAG,IAAI,YAAY,eAAe,KAC9D;AAEF;AAKO,SAAS,YACf,cACA,SACa;AACb,QAAM,UAAU,iBAAAC,QAAK,QAAQ,YAAY;AACzC,QAAM,eAAe,UAClB,iBAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO,IACnC,oBAAoB,OAAO;AAE9B,MAAI,CAAC,cAAc;AAElB,WAAO,kBAAAD,QAAG,cAAc,CAAC,YAAY,GAAG;AAAA,MACvC,QAAQ,kBAAAA,QAAG,aAAa;AAAA,MACxB,QAAQ,kBAAAA,QAAG,WAAW;AAAA,MACtB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,kBAAkB,kBAAAA,QAAG,qBAAqB;AAAA,MAC1C,cAAc;AAAA,IACf,CAAC;AAAA,EACF;AAEA,QAAM,MAAM,kBAAAA,QAAG,eAAe,cAAc,kBAAAA,QAAG,IAAI,QAAQ;AAC3D,MAAI,IAAI,OAAO;AACd,UAAM,IAAI;AAAA,MACT,kBAAAA,QAAG,6BAA6B,IAAI,MAAM,aAAa,IAAI;AAAA,IAC5D;AAAA,EACD;AAEA,QAAM,SAAS,kBAAAA,QAAG;AAAA,IACjB,IAAI;AAAA,IACJ,kBAAAA,QAAG;AAAA,IACH,iBAAAC,QAAK,QAAQ,YAAY;AAAA,EAC1B;AACA,SAAO,kBAAAD,QAAG,cAAc;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,EACjB,CAAC;AACF;AAEA,SAAS,gBACR,GACgD;AAChD,SAAO,CAAC,CAAC,MAAM,kBAAAA,QAAG,gBAAgB,CAAC,KAAK,kBAAAA,QAAG,qBAAqB,CAAC;AAClE;AAEA,SAAS,aACR,GACqB;AACrB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,kBAAAA,QAAG,aAAa,CAAC,EAAG,QAAO,EAAE;AACjC,MAAI,kBAAAA,QAAG,gBAAgB,CAAC,EAAG,QAAO,EAAE;AACpC,MAAI,kBAAAA,QAAG,iBAAiB,CAAC,EAAG,QAAO,EAAE;AACrC,SAAO;AACR;AAEA,SAAS,oBAAoB,MAAe,MAAuB;AAElE,MAAI,kBAAAA,QAAG,sBAAsB,IAAI,KAAK,KAAK,MAAM,SAAS,KAAM,QAAO;AAGvE,MACC,kBAAAA,QAAG,sBAAsB,IAAI,KAC7B,kBAAAA,QAAG,aAAa,KAAK,IAAI,KACzB,KAAK,KAAK,SAAS,MAClB;AACD,WAAO,gBAAgB,KAAK,WAAW;AAAA,EACxC;AAGA,OACE,kBAAAA,QAAG,oBAAoB,IAAI,KAAK,kBAAAA,QAAG,kBAAkB,IAAI,MAC1D,aAAa,KAAK,IAAI,MAAM,MAC3B;AACD,WAAO;AAAA,EACR;AAIA,MAAI,kBAAAA,QAAG,qBAAqB,IAAI,KAAK,aAAa,KAAK,IAAI,MAAM,MAAM;AACtE,WAAO,gBAAgB,KAAK,WAAW;AAAA,EACxC;AAEA,SAAO;AACR;AAKA,SAAS,gBAAgB,MAAe,MAAuB;AAC9D,MACC,kBAAAA,QAAG,sBAAsB,IAAI,KAC7B,kBAAAA,QAAG,aAAa,KAAK,IAAI,KACzB,KAAK,KAAK,SAAS,MAClB;AACD,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAKA,SAAS,YAAY,MAAe,MAAuB;AAC1D,SAAO,oBAAoB,MAAM,IAAI,KAAK,gBAAgB,MAAM,IAAI;AACrE;AAKO,SAAS,cACf,YACA,MACS;AACT,QAAM,EAAE,KAAK,IAAI,WAAW;AAAA,IAC3B,KAAK,SAAS,UAAU;AAAA,EACzB;AACA,SAAO,OAAO;AACf;AA2BO,SAAS,sBACf,YACA,MACA,MACsB;AACtB,MAAI;AAEJ,QAAM,QAAQ,CAAC,SAAkB;AAChC,QAAI,MAAO;AACX,QAAI,YAAY,MAAM,IAAI,GAAG;AAC5B,UAAI,SAAS,QAAW;AACvB,cAAM,WAAW,cAAc,YAAY,IAAI;AAC/C,YAAI,aAAa,MAAM;AACtB,kBAAQ;AACR;AAAA,QACD;AAAA,MACD,OAAO;AACN,gBAAQ;AACR;AAAA,MACD;AAAA,IACD;AACA,sBAAAE,QAAG,aAAa,MAAM,KAAK;AAAA,EAC5B;AAEA,QAAM,UAAU;AAChB,SAAO;AACR;AAKO,SAAS,YACf,SACA,MACA,YACqB;AACrB,QAAM,UAAU,QAAQ,eAAe;AACvC,QAAM,KAAK,cAAc,KAAK,cAAc;AAC5C,QAAM,OAAO,cAAc,IAAI,IAAI;AAEnC,MAAI;AAGJ,MAAI,kBAAAA,QAAG,sBAAsB,IAAI,KAAK,kBAAAA,QAAG,oBAAoB,IAAI,GAAG;AACnE,UAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACpD,WAAW,kBAAAA,QAAG,sBAAsB,IAAI,GAAG;AAC1C,UAAM,OAAO,KAAK;AAClB,QAAI,gBAAgB,IAAI;AACvB,YAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACrD,WAAW,kBAAAA,QAAG,qBAAqB,IAAI,GAAG;AACzC,UAAM,OAAO,KAAK;AAClB,QAAI,gBAAgB,IAAI;AACvB,YAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACrD,WAAW,kBAAAA,QAAG,kBAAkB,IAAI,GAAG;AAEtC,UAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACpD;AAEA,QAAM,QAAQ,kBAAAA,QAAG,gBAAgB;AAEjC,MAAI,KAAK;AACR,UAAM,YAAY,QAAQ,kBAAkB,KAAK,QAAW,KAAK;AACjE,UAAM,MAAM,QAAQ,yBAAyB,GAAG;AAChD,UAAM,aAAa,QAAQ,aAAa,KAAK,QAAW,KAAK;AAC7D,WAAO,EAAE,WAAW,YAAY,KAAK;AAAA,EACtC;AAGA,QAAM,eAAe;AACrB,MAAI,WAAoB;AACxB,MAAI,aAAa,QAAQ,kBAAAA,QAAG,aAAa,aAAa,IAAI,GAAG;AAC5D,eAAW,aAAa;AAAA,EACzB;AAEA,QAAM,IAAI,QAAQ,kBAAkB,QAAQ;AAC5C,SAAO,EAAE,WAAW,QAAQ,aAAa,GAAG,QAAW,KAAK,GAAG,KAAK;AACrE;;;ADpMO,SAAS,UACf,MACA,MACA,SACqB;AAErB,QAAM,OACL,OAAO,YAAY,WAAW,EAAE,SAAS,QAAQ,IAAK,WAAW,CAAC;AACnE,QAAM,EAAE,MAAM,QAAQ,IAAI;AAE1B,QAAM,eAAe,kBAAAC,QAAK,QAAQ,QAAQ,IAAI,GAAG,IAAI;AAErD,MAAI,CAAC,eAAAC,QAAG,WAAW,YAAY,GAAG;AACjC,UAAM,IAAI,MAAM,mBAAmB,YAAY,EAAE;AAAA,EAClD;AAEA,QAAM,UAAU,YAAY,cAAc,OAAO;AACjD,QAAM,aAAa,QAAQ,cAAc,YAAY;AAErD,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI;AAAA,MACT,iFAAiF,YAAY;AAAA,IAC9F;AAAA,EACD;AAEA,QAAM,OAAO,sBAAsB,YAAY,MAAM,IAAI;AACzD,MAAI,CAAC,MAAM;AACV,UAAM,WAAW,SAAS,SAAY,YAAY,IAAI,KAAK;AAC3D,UAAM,IAAI;AAAA,MACT,oBAAoB,IAAI,IAAI,QAAQ,aAAa,YAAY;AAAA,IAC9D;AAAA,EACD;AAEA,SAAO,YAAY,SAAS,MAAM,UAAU;AAC7C;;;ADtEA,IAAM,SAAS,IAAI,qBAAU;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AACV,CAAC;AAED,OAAO;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,IACC,MAAM,aAAE,OAAO,EAAE,SAAS,6BAA6B;AAAA,IACvD,MAAM,aACJ,OAAO,EACP,SAAS,6CAA6C;AAAA,IACxD,MAAM,aACJ,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD;AAAA,IAC5D,SAAS,aACP,OAAO,EACP,SAAS,EACT,SAAS,gCAAgC;AAAA,EAC5C;AAAA,EACA,OAAO,EAAE,MAAM,MAAM,MAAM,QAAQ,MAAM;AACxC,QAAI;AACH,YAAM,SAAS,UAAU,MAAM,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtD,UAAI,OAAO,SAAS,OAAO,SAAS;AACpC,UAAI,OAAO,YAAY;AACtB,gBAAQ;AAAA,WAAc,OAAO,UAAU;AAAA,MACxC;AACA,UAAI,OAAO,MAAM;AAChB,gBAAQ;AAAA,QAAW,OAAO,IAAI;AAAA,MAC/B;AACA,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE;AAAA,IAC5C,SAAS,OAAO;AACf,aAAO;AAAA,QACN,SAAS;AAAA,UACR;AAAA,YACC,MAAM;AAAA,YACN,MAAM,UAAW,MAAgB,OAAO;AAAA,UACzC;AAAA,QACD;AAAA,QACA,SAAS;AAAA,MACV;AAAA,IACD;AAAA,EACD;AACD;AAEA,eAAe,OAAO;AACrB,QAAM,YAAY,IAAI,kCAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC/B;AAEA,KAAK,EAAE,MAAM,QAAQ,KAAK;","names":["import_node_path","ts","path","ts","path","fs"]}
|
package/dist/mcp.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
|
|
12
|
+
// src/core.ts
|
|
13
|
+
import path from "path";
|
|
14
|
+
import ts from "typescript";
|
|
15
|
+
function findNearestTsconfig(startDir) {
|
|
16
|
+
return ts.findConfigFile(startDir, ts.sys.fileExists, "tsconfig.json") ?? void 0;
|
|
17
|
+
}
|
|
18
|
+
function loadProgram(entryFileAbs, project) {
|
|
19
|
+
const fileDir = path.dirname(entryFileAbs);
|
|
20
|
+
const tsconfigPath = project ? path.resolve(process.cwd(), project) : findNearestTsconfig(fileDir);
|
|
21
|
+
if (!tsconfigPath) {
|
|
22
|
+
return ts.createProgram([entryFileAbs], {
|
|
23
|
+
target: ts.ScriptTarget.ES2022,
|
|
24
|
+
module: ts.ModuleKind.ESNext,
|
|
25
|
+
strict: true,
|
|
26
|
+
allowJs: true,
|
|
27
|
+
checkJs: false,
|
|
28
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
29
|
+
skipLibCheck: true
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
33
|
+
if (cfg.error) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
ts.flattenDiagnosticMessageText(cfg.error.messageText, "\n")
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
39
|
+
cfg.config,
|
|
40
|
+
ts.sys,
|
|
41
|
+
path.dirname(tsconfigPath)
|
|
42
|
+
);
|
|
43
|
+
return ts.createProgram({
|
|
44
|
+
rootNames: parsed.fileNames,
|
|
45
|
+
options: parsed.options
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function isArrowOrFnExpr(n) {
|
|
49
|
+
return !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));
|
|
50
|
+
}
|
|
51
|
+
function nodeNameText(n) {
|
|
52
|
+
if (!n) return void 0;
|
|
53
|
+
if (ts.isIdentifier(n)) return n.text;
|
|
54
|
+
if (ts.isStringLiteral(n)) return n.text;
|
|
55
|
+
if (ts.isNumericLiteral(n)) return n.text;
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
function isFunctionLikeNamed(node, name) {
|
|
59
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;
|
|
60
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === name) {
|
|
61
|
+
return isArrowOrFnExpr(node.initializer);
|
|
62
|
+
}
|
|
63
|
+
if ((ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) && nodeNameText(node.name) === name) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
|
|
67
|
+
return isArrowOrFnExpr(node.initializer);
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function isVariableNamed(node, name) {
|
|
72
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === name) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function isNamedNode(node, name) {
|
|
78
|
+
return isFunctionLikeNamed(node, name) || isVariableNamed(node, name);
|
|
79
|
+
}
|
|
80
|
+
function getLineNumber(sourceFile, node) {
|
|
81
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(
|
|
82
|
+
node.getStart(sourceFile)
|
|
83
|
+
);
|
|
84
|
+
return line + 1;
|
|
85
|
+
}
|
|
86
|
+
function findNodeByNameAndLine(sourceFile, name, line) {
|
|
87
|
+
let found;
|
|
88
|
+
const visit = (node) => {
|
|
89
|
+
if (found) return;
|
|
90
|
+
if (isNamedNode(node, name)) {
|
|
91
|
+
if (line !== void 0) {
|
|
92
|
+
const nodeLine = getLineNumber(sourceFile, node);
|
|
93
|
+
if (nodeLine === line) {
|
|
94
|
+
found = node;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
found = node;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
ts.forEachChild(node, visit);
|
|
103
|
+
};
|
|
104
|
+
visit(sourceFile);
|
|
105
|
+
return found;
|
|
106
|
+
}
|
|
107
|
+
function getTypeInfo(program, node, sourceFile) {
|
|
108
|
+
const checker = program.getTypeChecker();
|
|
109
|
+
const sf = sourceFile ?? node.getSourceFile();
|
|
110
|
+
const line = getLineNumber(sf, node);
|
|
111
|
+
let sig;
|
|
112
|
+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
|
|
113
|
+
sig = checker.getSignatureFromDeclaration(node) ?? void 0;
|
|
114
|
+
} else if (ts.isVariableDeclaration(node)) {
|
|
115
|
+
const init = node.initializer;
|
|
116
|
+
if (isArrowOrFnExpr(init))
|
|
117
|
+
sig = checker.getSignatureFromDeclaration(init) ?? void 0;
|
|
118
|
+
} else if (ts.isPropertyAssignment(node)) {
|
|
119
|
+
const init = node.initializer;
|
|
120
|
+
if (isArrowOrFnExpr(init))
|
|
121
|
+
sig = checker.getSignatureFromDeclaration(init) ?? void 0;
|
|
122
|
+
} else if (ts.isMethodSignature(node)) {
|
|
123
|
+
sig = checker.getSignatureFromDeclaration(node) ?? void 0;
|
|
124
|
+
}
|
|
125
|
+
const flags = ts.TypeFormatFlags.NoTruncation;
|
|
126
|
+
if (sig) {
|
|
127
|
+
const signature = checker.signatureToString(sig, void 0, flags);
|
|
128
|
+
const ret = checker.getReturnTypeOfSignature(sig);
|
|
129
|
+
const returnType = checker.typeToString(ret, void 0, flags);
|
|
130
|
+
return { signature, returnType, line };
|
|
131
|
+
}
|
|
132
|
+
const nodeWithName = node;
|
|
133
|
+
let nameNode = node;
|
|
134
|
+
if (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {
|
|
135
|
+
nameNode = nodeWithName.name;
|
|
136
|
+
}
|
|
137
|
+
const t = checker.getTypeAtLocation(nameNode);
|
|
138
|
+
return { signature: checker.typeToString(t, void 0, flags), line };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/index.ts
|
|
142
|
+
function inferType(file, name, options) {
|
|
143
|
+
const opts = typeof options === "string" ? { project: options } : options ?? {};
|
|
144
|
+
const { line, project } = opts;
|
|
145
|
+
const entryFileAbs = path2.resolve(process.cwd(), file);
|
|
146
|
+
if (!fs.existsSync(entryFileAbs)) {
|
|
147
|
+
throw new Error(`File not found: ${entryFileAbs}`);
|
|
148
|
+
}
|
|
149
|
+
const program = loadProgram(entryFileAbs, project);
|
|
150
|
+
const sourceFile = program.getSourceFile(entryFileAbs);
|
|
151
|
+
if (!sourceFile) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const node = findNodeByNameAndLine(sourceFile, name, line);
|
|
157
|
+
if (!node) {
|
|
158
|
+
const lineInfo = line !== void 0 ? ` at line ${line}` : "";
|
|
159
|
+
throw new Error(
|
|
160
|
+
`No symbol named "${name}"${lineInfo} found in ${entryFileAbs}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return getTypeInfo(program, node, sourceFile);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/mcp.ts
|
|
167
|
+
var server = new McpServer({
|
|
168
|
+
name: "prinfer",
|
|
169
|
+
version: "0.2.1"
|
|
170
|
+
});
|
|
171
|
+
server.tool(
|
|
172
|
+
"infer_type",
|
|
173
|
+
"Infer the TypeScript type of a function or variable in a file. Returns the type signature and optionally the return type for functions.",
|
|
174
|
+
{
|
|
175
|
+
file: z.string().describe("Path to the TypeScript file"),
|
|
176
|
+
name: z.string().describe("Name of the function or variable to inspect"),
|
|
177
|
+
line: z.number().optional().describe("Optional line number to narrow search (1-based)"),
|
|
178
|
+
project: z.string().optional().describe("Optional path to tsconfig.json")
|
|
179
|
+
},
|
|
180
|
+
async ({ file, name, line, project }) => {
|
|
181
|
+
try {
|
|
182
|
+
const result = inferType(file, name, { line, project });
|
|
183
|
+
let text = `Type: ${result.signature}`;
|
|
184
|
+
if (result.returnType) {
|
|
185
|
+
text += `
|
|
186
|
+
Returns: ${result.returnType}`;
|
|
187
|
+
}
|
|
188
|
+
if (result.line) {
|
|
189
|
+
text += `
|
|
190
|
+
Line: ${result.line}`;
|
|
191
|
+
}
|
|
192
|
+
return { content: [{ type: "text", text }] };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return {
|
|
195
|
+
content: [
|
|
196
|
+
{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: `Error: ${error.message}`
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
isError: true
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
async function main() {
|
|
207
|
+
const transport = new StdioServerTransport();
|
|
208
|
+
await server.connect(transport);
|
|
209
|
+
}
|
|
210
|
+
main().catch(console.error);
|
|
211
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/index.ts","../src/core.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { inferType } from \"./index.js\";\n\nconst server = new McpServer({\n\tname: \"prinfer\",\n\tversion: \"0.2.1\",\n});\n\nserver.tool(\n\t\"infer_type\",\n\t\"Infer the TypeScript type of a function or variable in a file. Returns the type signature and optionally the return type for functions.\",\n\t{\n\t\tfile: z.string().describe(\"Path to the TypeScript file\"),\n\t\tname: z\n\t\t\t.string()\n\t\t\t.describe(\"Name of the function or variable to inspect\"),\n\t\tline: z\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe(\"Optional line number to narrow search (1-based)\"),\n\t\tproject: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\"Optional path to tsconfig.json\"),\n\t},\n\tasync ({ file, name, line, project }) => {\n\t\ttry {\n\t\t\tconst result = inferType(file, name, { line, project });\n\t\t\tlet text = `Type: ${result.signature}`;\n\t\t\tif (result.returnType) {\n\t\t\t\ttext += `\\nReturns: ${result.returnType}`;\n\t\t\t}\n\t\t\tif (result.line) {\n\t\t\t\ttext += `\\nLine: ${result.line}`;\n\t\t\t}\n\t\t\treturn { content: [{ type: \"text\", text }] };\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Error: ${(error as Error).message}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\t},\n);\n\nasync function main() {\n\tconst transport = new StdioServerTransport();\n\tawait server.connect(transport);\n}\n\nmain().catch(console.error);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findNodeByNameAndLine, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tfindNodeByNameAndLine,\n\tgetLineNumber,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param options - Optional: project path (string) or options object with line and project\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * // By name only\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n *\n * // By name and line number\n * const result2 = inferType(\"./src/utils.ts\", \"commandResult\", { line: 75 });\n * console.log(result2.signature);\n * // => \"Result<VaultAction[], CommandError>\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\toptions?: string | { line?: number; project?: string },\n): InferredTypeResult {\n\t// Normalize options for backward compatibility\n\tconst opts =\n\t\ttypeof options === \"string\" ? { project: options } : (options ?? {});\n\tconst { line, project } = opts;\n\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findNodeByNameAndLine(sourceFile, name, line);\n\tif (!node) {\n\t\tconst lineInfo = line !== undefined ? ` at line ${line}` : \"\";\n\t\tthrow new Error(\n\t\t\t`No symbol named \"${name}\"${lineInfo} found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node, sourceFile);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, {\n\t\tline: options.line,\n\t\tproject: options.project,\n\t});\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a node is any variable declaration with the given name\n */\nfunction isVariableNamed(node: ts.Node, name: string): boolean {\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if a node matches the given name (function-like or variable)\n */\nfunction isNamedNode(node: ts.Node, name: string): boolean {\n\treturn isFunctionLikeNamed(node, name) || isVariableNamed(node, name);\n}\n\n/**\n * Get the 1-based line number for a node\n */\nexport function getLineNumber(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n): number {\n\tconst { line } = sourceFile.getLineAndCharacterOfPosition(\n\t\tnode.getStart(sourceFile),\n\t);\n\treturn line + 1;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Find a node by name and optionally by line number\n */\nexport function findNodeByNameAndLine(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n\tline?: number,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isNamedNode(node, name)) {\n\t\t\tif (line !== undefined) {\n\t\t\t\tconst nodeLine = getLineNumber(sourceFile, node);\n\t\t\t\tif (nodeLine === line) {\n\t\t\t\t\tfound = node;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfound = node;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n\tsourceFile?: ts.SourceFile,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\tconst sf = sourceFile ?? node.getSourceFile();\n\tconst line = getLineNumber(sf, node);\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType, line };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags), line };\n}\n"],"mappings":"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","names":["path","path"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/postinstall.ts
|
|
27
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
28
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
29
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
30
|
+
var SKILL_CONTENT = `# TypeScript Type Inference (prinfer)
|
|
31
|
+
|
|
32
|
+
## Coding Guideline
|
|
33
|
+
|
|
34
|
+
When writing TypeScript code, prefer relying on type inference over explicit type annotations:
|
|
35
|
+
|
|
36
|
+
- Let TypeScript infer return types for functions when the implementation makes them obvious
|
|
37
|
+
- Let TypeScript infer variable types from their initializers
|
|
38
|
+
- Only add explicit types when:
|
|
39
|
+
- The inferred type is too wide (e.g., \`string\` instead of a literal type)
|
|
40
|
+
- The type serves as documentation for complex structures
|
|
41
|
+
- You're defining a public API contract
|
|
42
|
+
|
|
43
|
+
Use the \`prinfer\` MCP tool (\`infer_type\`) to verify what TypeScript infers before adding explicit types.
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### /check-type
|
|
48
|
+
|
|
49
|
+
Check the inferred type of a TypeScript symbol.
|
|
50
|
+
|
|
51
|
+
Usage: \`/check-type <file>:<line> <name>\` or \`/check-type <file> <name>\`
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
- \`/check-type src/utils.ts:75 commandResult\`
|
|
55
|
+
- \`/check-type src/utils.ts myFunction\`
|
|
56
|
+
|
|
57
|
+
<command-name>check-type</command-name>
|
|
58
|
+
|
|
59
|
+
Use the \`infer_type\` MCP tool to check the type:
|
|
60
|
+
1. Parse the arguments to extract file, optional line number, and symbol name
|
|
61
|
+
2. Call \`infer_type(file, name, line?)\`
|
|
62
|
+
3. Report the inferred signature and return type
|
|
63
|
+
`;
|
|
64
|
+
function main() {
|
|
65
|
+
const homeDir = import_node_os.default.homedir();
|
|
66
|
+
const skillsDir = import_node_path.default.join(homeDir, ".claude", "skills");
|
|
67
|
+
const skillFile = import_node_path.default.join(skillsDir, "prinfer.md");
|
|
68
|
+
const claudeDir = import_node_path.default.join(homeDir, ".claude");
|
|
69
|
+
if (!import_node_fs.default.existsSync(claudeDir)) {
|
|
70
|
+
console.log(
|
|
71
|
+
"~/.claude directory not found. Skipping skill installation."
|
|
72
|
+
);
|
|
73
|
+
console.log("To manually install, create ~/.claude/skills/prinfer.md");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!import_node_fs.default.existsSync(skillsDir)) {
|
|
77
|
+
import_node_fs.default.mkdirSync(skillsDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
if (import_node_fs.default.existsSync(skillFile)) {
|
|
80
|
+
console.log(
|
|
81
|
+
"prinfer skill already installed at ~/.claude/skills/prinfer.md"
|
|
82
|
+
);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
import_node_fs.default.writeFileSync(skillFile, SKILL_CONTENT);
|
|
86
|
+
console.log("Installed prinfer skill to ~/.claude/skills/prinfer.md");
|
|
87
|
+
console.log("You can now use /check-type to verify TypeScript types!");
|
|
88
|
+
}
|
|
89
|
+
main();
|
|
90
|
+
//# sourceMappingURL=postinstall.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/postinstall.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nconst SKILL_CONTENT = `# TypeScript Type Inference (prinfer)\n\n## Coding Guideline\n\nWhen writing TypeScript code, prefer relying on type inference over explicit type annotations:\n\n- Let TypeScript infer return types for functions when the implementation makes them obvious\n- Let TypeScript infer variable types from their initializers\n- Only add explicit types when:\n - The inferred type is too wide (e.g., \\`string\\` instead of a literal type)\n - The type serves as documentation for complex structures\n - You're defining a public API contract\n\nUse the \\`prinfer\\` MCP tool (\\`infer_type\\`) to verify what TypeScript infers before adding explicit types.\n\n## Commands\n\n### /check-type\n\nCheck the inferred type of a TypeScript symbol.\n\nUsage: \\`/check-type <file>:<line> <name>\\` or \\`/check-type <file> <name>\\`\n\nExamples:\n- \\`/check-type src/utils.ts:75 commandResult\\`\n- \\`/check-type src/utils.ts myFunction\\`\n\n<command-name>check-type</command-name>\n\nUse the \\`infer_type\\` MCP tool to check the type:\n1. Parse the arguments to extract file, optional line number, and symbol name\n2. Call \\`infer_type(file, name, line?)\\`\n3. Report the inferred signature and return type\n`;\n\nfunction main() {\n\tconst homeDir = os.homedir();\n\tconst skillsDir = path.join(homeDir, \".claude\", \"skills\");\n\tconst skillFile = path.join(skillsDir, \"prinfer.md\");\n\n\t// Check if ~/.claude exists\n\tconst claudeDir = path.join(homeDir, \".claude\");\n\tif (!fs.existsSync(claudeDir)) {\n\t\tconsole.log(\n\t\t\t\"~/.claude directory not found. Skipping skill installation.\",\n\t\t);\n\t\tconsole.log(\"To manually install, create ~/.claude/skills/prinfer.md\");\n\t\treturn;\n\t}\n\n\t// Create skills directory if it doesn't exist\n\tif (!fs.existsSync(skillsDir)) {\n\t\tfs.mkdirSync(skillsDir, { recursive: true });\n\t}\n\n\t// Check if skill already exists\n\tif (fs.existsSync(skillFile)) {\n\t\tconsole.log(\n\t\t\t\"prinfer skill already installed at ~/.claude/skills/prinfer.md\",\n\t\t);\n\t\treturn;\n\t}\n\n\t// Write the skill file\n\tfs.writeFileSync(skillFile, SKILL_CONTENT);\n\tconsole.log(\"Installed prinfer skill to ~/.claude/skills/prinfer.md\");\n\tconsole.log(\"You can now use /check-type to verify TypeScript types!\");\n}\n\nmain();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,qBAAe;AACf,qBAAe;AACf,uBAAiB;AAEjB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCtB,SAAS,OAAO;AACf,QAAM,UAAU,eAAAA,QAAG,QAAQ;AAC3B,QAAM,YAAY,iBAAAC,QAAK,KAAK,SAAS,WAAW,QAAQ;AACxD,QAAM,YAAY,iBAAAA,QAAK,KAAK,WAAW,YAAY;AAGnD,QAAM,YAAY,iBAAAA,QAAK,KAAK,SAAS,SAAS;AAC9C,MAAI,CAAC,eAAAC,QAAG,WAAW,SAAS,GAAG;AAC9B,YAAQ;AAAA,MACP;AAAA,IACD;AACA,YAAQ,IAAI,yDAAyD;AACrE;AAAA,EACD;AAGA,MAAI,CAAC,eAAAA,QAAG,WAAW,SAAS,GAAG;AAC9B,mBAAAA,QAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC5C;AAGA,MAAI,eAAAA,QAAG,WAAW,SAAS,GAAG;AAC7B,YAAQ;AAAA,MACP;AAAA,IACD;AACA;AAAA,EACD;AAGA,iBAAAA,QAAG,cAAc,WAAW,aAAa;AACzC,UAAQ,IAAI,wDAAwD;AACpE,UAAQ,IAAI,yDAAyD;AACtE;AAEA,KAAK;","names":["os","path","fs"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/postinstall.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var SKILL_CONTENT = `# TypeScript Type Inference (prinfer)
|
|
8
|
+
|
|
9
|
+
## Coding Guideline
|
|
10
|
+
|
|
11
|
+
When writing TypeScript code, prefer relying on type inference over explicit type annotations:
|
|
12
|
+
|
|
13
|
+
- Let TypeScript infer return types for functions when the implementation makes them obvious
|
|
14
|
+
- Let TypeScript infer variable types from their initializers
|
|
15
|
+
- Only add explicit types when:
|
|
16
|
+
- The inferred type is too wide (e.g., \`string\` instead of a literal type)
|
|
17
|
+
- The type serves as documentation for complex structures
|
|
18
|
+
- You're defining a public API contract
|
|
19
|
+
|
|
20
|
+
Use the \`prinfer\` MCP tool (\`infer_type\`) to verify what TypeScript infers before adding explicit types.
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
### /check-type
|
|
25
|
+
|
|
26
|
+
Check the inferred type of a TypeScript symbol.
|
|
27
|
+
|
|
28
|
+
Usage: \`/check-type <file>:<line> <name>\` or \`/check-type <file> <name>\`
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
- \`/check-type src/utils.ts:75 commandResult\`
|
|
32
|
+
- \`/check-type src/utils.ts myFunction\`
|
|
33
|
+
|
|
34
|
+
<command-name>check-type</command-name>
|
|
35
|
+
|
|
36
|
+
Use the \`infer_type\` MCP tool to check the type:
|
|
37
|
+
1. Parse the arguments to extract file, optional line number, and symbol name
|
|
38
|
+
2. Call \`infer_type(file, name, line?)\`
|
|
39
|
+
3. Report the inferred signature and return type
|
|
40
|
+
`;
|
|
41
|
+
function main() {
|
|
42
|
+
const homeDir = os.homedir();
|
|
43
|
+
const skillsDir = path.join(homeDir, ".claude", "skills");
|
|
44
|
+
const skillFile = path.join(skillsDir, "prinfer.md");
|
|
45
|
+
const claudeDir = path.join(homeDir, ".claude");
|
|
46
|
+
if (!fs.existsSync(claudeDir)) {
|
|
47
|
+
console.log(
|
|
48
|
+
"~/.claude directory not found. Skipping skill installation."
|
|
49
|
+
);
|
|
50
|
+
console.log("To manually install, create ~/.claude/skills/prinfer.md");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!fs.existsSync(skillsDir)) {
|
|
54
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
if (fs.existsSync(skillFile)) {
|
|
57
|
+
console.log(
|
|
58
|
+
"prinfer skill already installed at ~/.claude/skills/prinfer.md"
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(skillFile, SKILL_CONTENT);
|
|
63
|
+
console.log("Installed prinfer skill to ~/.claude/skills/prinfer.md");
|
|
64
|
+
console.log("You can now use /check-type to verify TypeScript types!");
|
|
65
|
+
}
|
|
66
|
+
main();
|
|
67
|
+
//# sourceMappingURL=postinstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/postinstall.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nconst SKILL_CONTENT = `# TypeScript Type Inference (prinfer)\n\n## Coding Guideline\n\nWhen writing TypeScript code, prefer relying on type inference over explicit type annotations:\n\n- Let TypeScript infer return types for functions when the implementation makes them obvious\n- Let TypeScript infer variable types from their initializers\n- Only add explicit types when:\n - The inferred type is too wide (e.g., \\`string\\` instead of a literal type)\n - The type serves as documentation for complex structures\n - You're defining a public API contract\n\nUse the \\`prinfer\\` MCP tool (\\`infer_type\\`) to verify what TypeScript infers before adding explicit types.\n\n## Commands\n\n### /check-type\n\nCheck the inferred type of a TypeScript symbol.\n\nUsage: \\`/check-type <file>:<line> <name>\\` or \\`/check-type <file> <name>\\`\n\nExamples:\n- \\`/check-type src/utils.ts:75 commandResult\\`\n- \\`/check-type src/utils.ts myFunction\\`\n\n<command-name>check-type</command-name>\n\nUse the \\`infer_type\\` MCP tool to check the type:\n1. Parse the arguments to extract file, optional line number, and symbol name\n2. Call \\`infer_type(file, name, line?)\\`\n3. Report the inferred signature and return type\n`;\n\nfunction main() {\n\tconst homeDir = os.homedir();\n\tconst skillsDir = path.join(homeDir, \".claude\", \"skills\");\n\tconst skillFile = path.join(skillsDir, \"prinfer.md\");\n\n\t// Check if ~/.claude exists\n\tconst claudeDir = path.join(homeDir, \".claude\");\n\tif (!fs.existsSync(claudeDir)) {\n\t\tconsole.log(\n\t\t\t\"~/.claude directory not found. Skipping skill installation.\",\n\t\t);\n\t\tconsole.log(\"To manually install, create ~/.claude/skills/prinfer.md\");\n\t\treturn;\n\t}\n\n\t// Create skills directory if it doesn't exist\n\tif (!fs.existsSync(skillsDir)) {\n\t\tfs.mkdirSync(skillsDir, { recursive: true });\n\t}\n\n\t// Check if skill already exists\n\tif (fs.existsSync(skillFile)) {\n\t\tconsole.log(\n\t\t\t\"prinfer skill already installed at ~/.claude/skills/prinfer.md\",\n\t\t);\n\t\treturn;\n\t}\n\n\t// Write the skill file\n\tfs.writeFileSync(skillFile, SKILL_CONTENT);\n\tconsole.log(\"Installed prinfer skill to ~/.claude/skills/prinfer.md\");\n\tconsole.log(\"You can now use /check-type to verify TypeScript types!\");\n}\n\nmain();\n"],"mappings":";;;AACA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCtB,SAAS,OAAO;AACf,QAAM,UAAU,GAAG,QAAQ;AAC3B,QAAM,YAAY,KAAK,KAAK,SAAS,WAAW,QAAQ;AACxD,QAAM,YAAY,KAAK,KAAK,WAAW,YAAY;AAGnD,QAAM,YAAY,KAAK,KAAK,SAAS,SAAS;AAC9C,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC9B,YAAQ;AAAA,MACP;AAAA,IACD;AACA,YAAQ,IAAI,yDAAyD;AACrE;AAAA,EACD;AAGA,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC9B,OAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC5C;AAGA,MAAI,GAAG,WAAW,SAAS,GAAG;AAC7B,YAAQ;AAAA,MACP;AAAA,IACD;AACA;AAAA,EACD;AAGA,KAAG,cAAc,WAAW,aAAa;AACzC,UAAQ,IAAI,wDAAwD;AACpE,UAAQ,IAAI,yDAAyD;AACtE;AAEA,KAAK;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prinfer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "TypeScript type inference inspection tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
-
"prinfer": "./dist/cli.js"
|
|
22
|
+
"prinfer": "./dist/cli.js",
|
|
23
|
+
"prinfer-mcp": "./dist/mcp.js"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"dist"
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"version": "changeset version",
|
|
33
34
|
"release": "bun run build && changeset publish",
|
|
34
35
|
"prepublishOnly": "bun run ci",
|
|
35
|
-
"prepare": "simple-git-hooks"
|
|
36
|
+
"prepare": "simple-git-hooks",
|
|
37
|
+
"postinstall": "node ./dist/postinstall.js || true"
|
|
36
38
|
},
|
|
37
39
|
"simple-git-hooks": {
|
|
38
40
|
"pre-commit": "bunx biome check ."
|
|
@@ -62,5 +64,9 @@
|
|
|
62
64
|
},
|
|
63
65
|
"engines": {
|
|
64
66
|
"bun": ">=1.0.0"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
70
|
+
"zod": "^4.3.6"
|
|
65
71
|
}
|
|
66
72
|
}
|