prinfer 0.4.1 → 0.5.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 CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="printfer-logo.webp" alt="prinfer logo" width="400">
3
+ </p>
4
+
1
5
  # prinfer
2
6
 
3
7
  **Typehints for your AI agent.**
@@ -7,6 +11,7 @@ prinfer gives AI coding assistants the ability to inspect TypeScript's inferred
7
11
  ## Why?
8
12
 
9
13
  AI agents write TypeScript, but they can't see what the compiler infers. This leads to:
14
+
10
15
  - Unnecessary explicit type annotations everywhere
11
16
  - Verbose code that fights against TypeScript's design
12
17
  - Missed opportunities to leverage type inference
@@ -19,9 +24,17 @@ prinfer solves this by exposing TypeScript's type inference to your agent via MC
19
24
  npm i -g prinfer
20
25
  ```
21
26
 
22
- That's it. On install, prinfer automatically:
23
- 1. Adds itself as an MCP tool for Claude
24
- 2. Installs a skill that teaches Claude to prefer type inference
27
+ or
28
+
29
+ ```bash
30
+ bun add -g prinfer
31
+ ```
32
+
33
+ On install, prinfer automatically configures itself for Claude Code. If auto-setup doesn't run (common with global installs), run:
34
+
35
+ ```bash
36
+ prinfer setup
37
+ ```
25
38
 
26
39
  ## What Gets Installed
27
40
 
@@ -34,18 +47,21 @@ infer_type(file: "src/utils.ts", name: "myFunction")
34
47
  infer_type(file: "src/utils.ts", name: "commandResult", line: 75)
35
48
  ```
36
49
 
37
- ### Claude Skill (`~/.claude/skills/prinfer.md`)
50
+ ### Claude Skill (`~/.claude/skills/prefer-infer.md`)
38
51
 
39
52
  A coding guideline that encourages your agent to:
53
+
40
54
  - Rely on type inference instead of explicit annotations
41
55
  - Use prinfer to verify types before adding redundant hints
42
56
  - Write idiomatic TypeScript
43
57
 
44
58
  Plus a `/check-type` command for quick lookups.
45
59
 
46
- ## MCP Setup
60
+ ## Manual Setup
47
61
 
48
- If the auto-setup didn't work, add to `~/.claude/claude_desktop_config.json`:
62
+ If `prinfer setup` doesn't work, configure manually:
63
+
64
+ **1. Add MCP server** to `~/.claude/claude_desktop_config.json`:
49
65
 
50
66
  ```json
51
67
  {
@@ -57,6 +73,8 @@ If the auto-setup didn't work, add to `~/.claude/claude_desktop_config.json`:
57
73
  }
58
74
  ```
59
75
 
76
+ **2. Create skill file** at `~/.claude/skills/prefer-infer.md` with content from [prefer-infer.md](https://github.com/clockblocker/prinfer/blob/master/src/postinstall.ts#L10-L42)
77
+
60
78
  ## CLI Usage
61
79
 
62
80
  prinfer also works as a standalone CLI:
@@ -67,6 +85,7 @@ prinfer src/utils.ts:75 commandResult
67
85
  ```
68
86
 
69
87
  Output:
88
+
70
89
  ```
71
90
  (x: number, y: string) => boolean
72
91
  returns: boolean
package/dist/cli.cjs CHANGED
@@ -23,6 +23,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
+ // src/cli.ts
27
+ var import_node_fs2 = __toESM(require("fs"), 1);
28
+ var import_node_os = __toESM(require("os"), 1);
29
+ var import_node_path3 = __toESM(require("path"), 1);
30
+
26
31
  // src/index.ts
27
32
  var import_node_fs = __toESM(require("fs"), 1);
28
33
  var import_node_path2 = __toESM(require("path"), 1);
@@ -187,6 +192,10 @@ prinfer - TypeScript type inference inspection tool
187
192
 
188
193
  Usage:
189
194
  prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]
195
+ prinfer setup
196
+
197
+ Commands:
198
+ setup Install MCP server and skill for Claude Code
190
199
 
191
200
  Arguments:
192
201
  file.ts Path to the TypeScript file
@@ -200,8 +209,124 @@ Options:
200
209
  Examples:
201
210
  prinfer src/utils.ts myFunction
202
211
  prinfer src/utils.ts:75 commandResult
203
- prinfer src/utils.ts myFunction --project ./tsconfig.json
212
+ prinfer setup
213
+ `.trim();
214
+ var MANUAL_SETUP = `
215
+ Manual setup instructions:
216
+
217
+ 1. Add MCP server to ~/.claude/claude_desktop_config.json:
218
+
219
+ {
220
+ "mcpServers": {
221
+ "prinfer": {
222
+ "command": "prinfer-mcp"
223
+ }
224
+ }
225
+ }
226
+
227
+ 2. Create skill file at ~/.claude/skills/prefer-infer.md:
228
+
229
+ See https://github.com/clockblocker/prinfer for skill content.
204
230
  `.trim();
231
+ var SKILL_CONTENT = `# TypeScript Type Inference (prinfer)
232
+
233
+ ## Coding Guideline
234
+
235
+ When writing TypeScript code, prefer relying on type inference over explicit type annotations:
236
+
237
+ - Let TypeScript infer return types for functions when the implementation makes them obvious
238
+ - Let TypeScript infer variable types from their initializers
239
+ - Only add explicit types when:
240
+ - The inferred type is too wide (e.g., \`string\` instead of a literal type)
241
+ - The type serves as documentation for complex structures
242
+ - You're defining a public API contract
243
+
244
+ Use the \`prinfer\` MCP tool (\`infer_type\`) to verify what TypeScript infers before adding explicit types.
245
+
246
+ ## Commands
247
+
248
+ ### /check-type
249
+
250
+ Check the inferred type of a TypeScript symbol.
251
+
252
+ Usage: \`/check-type <file>:<line> <name>\` or \`/check-type <file> <name>\`
253
+
254
+ Examples:
255
+ - \`/check-type src/utils.ts:75 commandResult\`
256
+ - \`/check-type src/utils.ts myFunction\`
257
+
258
+ <command-name>check-type</command-name>
259
+
260
+ Use the \`infer_type\` MCP tool to check the type:
261
+ 1. Parse the arguments to extract file, optional line number, and symbol name
262
+ 2. Call \`infer_type(file, name, line?)\`
263
+ 3. Report the inferred signature and return type
264
+ `;
265
+ function runSetup() {
266
+ const homeDir = import_node_os.default.homedir();
267
+ const claudeDir = import_node_path3.default.join(homeDir, ".claude");
268
+ if (!import_node_fs2.default.existsSync(claudeDir)) {
269
+ console.error("Error: ~/.claude directory not found.");
270
+ console.error("Make sure Claude Code is installed first.\n");
271
+ console.error(MANUAL_SETUP);
272
+ process.exit(1);
273
+ }
274
+ let mcpOk = false;
275
+ let skillOk = false;
276
+ const configFile = import_node_path3.default.join(claudeDir, "claude_desktop_config.json");
277
+ try {
278
+ let config = {};
279
+ if (import_node_fs2.default.existsSync(configFile)) {
280
+ config = JSON.parse(import_node_fs2.default.readFileSync(configFile, "utf-8"));
281
+ }
282
+ if (config.mcpServers?.prinfer) {
283
+ console.log("[ok] MCP server already configured");
284
+ mcpOk = true;
285
+ } else {
286
+ config.mcpServers = config.mcpServers || {};
287
+ config.mcpServers.prinfer = { command: "prinfer-mcp" };
288
+ import_node_fs2.default.writeFileSync(
289
+ configFile,
290
+ `${JSON.stringify(config, null, 2)}
291
+ `
292
+ );
293
+ console.log("[ok] Added MCP server to claude_desktop_config.json");
294
+ mcpOk = true;
295
+ }
296
+ } catch (err) {
297
+ console.error(
298
+ `[error] Failed to configure MCP server: ${err.message}`
299
+ );
300
+ }
301
+ const skillsDir = import_node_path3.default.join(claudeDir, "skills");
302
+ const skillFile = import_node_path3.default.join(skillsDir, "prefer-infer.md");
303
+ try {
304
+ if (!import_node_fs2.default.existsSync(skillsDir)) {
305
+ import_node_fs2.default.mkdirSync(skillsDir, { recursive: true });
306
+ }
307
+ if (import_node_fs2.default.existsSync(skillFile)) {
308
+ console.log("[ok] Skill already installed");
309
+ skillOk = true;
310
+ } else {
311
+ import_node_fs2.default.writeFileSync(skillFile, SKILL_CONTENT);
312
+ console.log(
313
+ "[ok] Installed skill to ~/.claude/skills/prefer-infer.md"
314
+ );
315
+ skillOk = true;
316
+ }
317
+ } catch (err) {
318
+ console.error(
319
+ `[error] Failed to install skill: ${err.message}`
320
+ );
321
+ }
322
+ if (mcpOk && skillOk) {
323
+ console.log("\nSetup complete! Restart Claude Code to use prinfer.");
324
+ } else {
325
+ console.error("\nSome steps failed. Manual setup:\n");
326
+ console.error(MANUAL_SETUP);
327
+ process.exit(1);
328
+ }
329
+ }
205
330
  function parseFileArg(arg) {
206
331
  const match = arg.match(/^(.+):(\d+)$/);
207
332
  if (match) {
@@ -215,6 +340,10 @@ function parseArgs(argv) {
215
340
  console.log(HELP);
216
341
  return null;
217
342
  }
343
+ if (args[0] === "setup") {
344
+ runSetup();
345
+ return null;
346
+ }
218
347
  const fileArg = args[0];
219
348
  const name = args[1];
220
349
  if (!fileArg || !name) {
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 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"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/index.ts","../src/core.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\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 prinfer setup\n\nCommands:\n setup Install MCP server and skill for Claude Code\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 setup\n`.trim();\n\nconst MANUAL_SETUP = `\nManual setup instructions:\n\n1. Add MCP server to ~/.claude/claude_desktop_config.json:\n\n {\n \"mcpServers\": {\n \"prinfer\": {\n \"command\": \"prinfer-mcp\"\n }\n }\n }\n\n2. Create skill file at ~/.claude/skills/prefer-infer.md:\n\n See https://github.com/clockblocker/prinfer for skill content.\n`.trim();\n\ninterface McpConfig {\n\tmcpServers?: Record<string, { command: string; args?: string[] }>;\n}\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 runSetup(): void {\n\tconst homeDir = os.homedir();\n\tconst claudeDir = path.join(homeDir, \".claude\");\n\n\tif (!fs.existsSync(claudeDir)) {\n\t\tconsole.error(\"Error: ~/.claude directory not found.\");\n\t\tconsole.error(\"Make sure Claude Code is installed first.\\n\");\n\t\tconsole.error(MANUAL_SETUP);\n\t\tprocess.exit(1);\n\t}\n\n\tlet mcpOk = false;\n\tlet skillOk = false;\n\n\t// Install MCP server\n\tconst configFile = path.join(claudeDir, \"claude_desktop_config.json\");\n\ttry {\n\t\tlet config: McpConfig = {};\n\t\tif (fs.existsSync(configFile)) {\n\t\t\tconfig = JSON.parse(fs.readFileSync(configFile, \"utf-8\"));\n\t\t}\n\n\t\tif (config.mcpServers?.prinfer) {\n\t\t\tconsole.log(\"[ok] MCP server already configured\");\n\t\t\tmcpOk = true;\n\t\t} else {\n\t\t\tconfig.mcpServers = config.mcpServers || {};\n\t\t\tconfig.mcpServers.prinfer = { command: \"prinfer-mcp\" };\n\t\t\tfs.writeFileSync(\n\t\t\t\tconfigFile,\n\t\t\t\t`${JSON.stringify(config, null, 2)}\\n`,\n\t\t\t);\n\t\t\tconsole.log(\"[ok] Added MCP server to claude_desktop_config.json\");\n\t\t\tmcpOk = true;\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[error] Failed to configure MCP server: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\t// Install skill\n\tconst skillsDir = path.join(claudeDir, \"skills\");\n\tconst skillFile = path.join(skillsDir, \"prefer-infer.md\");\n\ttry {\n\t\tif (!fs.existsSync(skillsDir)) {\n\t\t\tfs.mkdirSync(skillsDir, { recursive: true });\n\t\t}\n\n\t\tif (fs.existsSync(skillFile)) {\n\t\t\tconsole.log(\"[ok] Skill already installed\");\n\t\t\tskillOk = true;\n\t\t} else {\n\t\t\tfs.writeFileSync(skillFile, SKILL_CONTENT);\n\t\t\tconsole.log(\n\t\t\t\t\"[ok] Installed skill to ~/.claude/skills/prefer-infer.md\",\n\t\t\t);\n\t\t\tskillOk = true;\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[error] Failed to install skill: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\tif (mcpOk && skillOk) {\n\t\tconsole.log(\"\\nSetup complete! Restart Claude Code to use prinfer.\");\n\t} else {\n\t\tconsole.error(\"\\nSome steps failed. Manual setup:\\n\");\n\t\tconsole.error(MANUAL_SETUP);\n\t\tprocess.exit(1);\n\t}\n}\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\t// Check for setup command\n\tif (args[0] === \"setup\") {\n\t\trunSetup();\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","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":["import_node_fs","import_node_path","import_node_path","ts","path","ts","path","fs","os","path","fs"]}
package/dist/cli.js CHANGED
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli.ts
4
+ import fs2 from "fs";
5
+ import os from "os";
6
+ import path3 from "path";
7
+
3
8
  // src/index.ts
4
9
  import fs from "fs";
5
10
  import path2 from "path";
@@ -164,6 +169,10 @@ prinfer - TypeScript type inference inspection tool
164
169
 
165
170
  Usage:
166
171
  prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]
172
+ prinfer setup
173
+
174
+ Commands:
175
+ setup Install MCP server and skill for Claude Code
167
176
 
168
177
  Arguments:
169
178
  file.ts Path to the TypeScript file
@@ -177,8 +186,124 @@ Options:
177
186
  Examples:
178
187
  prinfer src/utils.ts myFunction
179
188
  prinfer src/utils.ts:75 commandResult
180
- prinfer src/utils.ts myFunction --project ./tsconfig.json
189
+ prinfer setup
190
+ `.trim();
191
+ var MANUAL_SETUP = `
192
+ Manual setup instructions:
193
+
194
+ 1. Add MCP server to ~/.claude/claude_desktop_config.json:
195
+
196
+ {
197
+ "mcpServers": {
198
+ "prinfer": {
199
+ "command": "prinfer-mcp"
200
+ }
201
+ }
202
+ }
203
+
204
+ 2. Create skill file at ~/.claude/skills/prefer-infer.md:
205
+
206
+ See https://github.com/clockblocker/prinfer for skill content.
181
207
  `.trim();
208
+ var SKILL_CONTENT = `# TypeScript Type Inference (prinfer)
209
+
210
+ ## Coding Guideline
211
+
212
+ When writing TypeScript code, prefer relying on type inference over explicit type annotations:
213
+
214
+ - Let TypeScript infer return types for functions when the implementation makes them obvious
215
+ - Let TypeScript infer variable types from their initializers
216
+ - Only add explicit types when:
217
+ - The inferred type is too wide (e.g., \`string\` instead of a literal type)
218
+ - The type serves as documentation for complex structures
219
+ - You're defining a public API contract
220
+
221
+ Use the \`prinfer\` MCP tool (\`infer_type\`) to verify what TypeScript infers before adding explicit types.
222
+
223
+ ## Commands
224
+
225
+ ### /check-type
226
+
227
+ Check the inferred type of a TypeScript symbol.
228
+
229
+ Usage: \`/check-type <file>:<line> <name>\` or \`/check-type <file> <name>\`
230
+
231
+ Examples:
232
+ - \`/check-type src/utils.ts:75 commandResult\`
233
+ - \`/check-type src/utils.ts myFunction\`
234
+
235
+ <command-name>check-type</command-name>
236
+
237
+ Use the \`infer_type\` MCP tool to check the type:
238
+ 1. Parse the arguments to extract file, optional line number, and symbol name
239
+ 2. Call \`infer_type(file, name, line?)\`
240
+ 3. Report the inferred signature and return type
241
+ `;
242
+ function runSetup() {
243
+ const homeDir = os.homedir();
244
+ const claudeDir = path3.join(homeDir, ".claude");
245
+ if (!fs2.existsSync(claudeDir)) {
246
+ console.error("Error: ~/.claude directory not found.");
247
+ console.error("Make sure Claude Code is installed first.\n");
248
+ console.error(MANUAL_SETUP);
249
+ process.exit(1);
250
+ }
251
+ let mcpOk = false;
252
+ let skillOk = false;
253
+ const configFile = path3.join(claudeDir, "claude_desktop_config.json");
254
+ try {
255
+ let config = {};
256
+ if (fs2.existsSync(configFile)) {
257
+ config = JSON.parse(fs2.readFileSync(configFile, "utf-8"));
258
+ }
259
+ if (config.mcpServers?.prinfer) {
260
+ console.log("[ok] MCP server already configured");
261
+ mcpOk = true;
262
+ } else {
263
+ config.mcpServers = config.mcpServers || {};
264
+ config.mcpServers.prinfer = { command: "prinfer-mcp" };
265
+ fs2.writeFileSync(
266
+ configFile,
267
+ `${JSON.stringify(config, null, 2)}
268
+ `
269
+ );
270
+ console.log("[ok] Added MCP server to claude_desktop_config.json");
271
+ mcpOk = true;
272
+ }
273
+ } catch (err) {
274
+ console.error(
275
+ `[error] Failed to configure MCP server: ${err.message}`
276
+ );
277
+ }
278
+ const skillsDir = path3.join(claudeDir, "skills");
279
+ const skillFile = path3.join(skillsDir, "prefer-infer.md");
280
+ try {
281
+ if (!fs2.existsSync(skillsDir)) {
282
+ fs2.mkdirSync(skillsDir, { recursive: true });
283
+ }
284
+ if (fs2.existsSync(skillFile)) {
285
+ console.log("[ok] Skill already installed");
286
+ skillOk = true;
287
+ } else {
288
+ fs2.writeFileSync(skillFile, SKILL_CONTENT);
289
+ console.log(
290
+ "[ok] Installed skill to ~/.claude/skills/prefer-infer.md"
291
+ );
292
+ skillOk = true;
293
+ }
294
+ } catch (err) {
295
+ console.error(
296
+ `[error] Failed to install skill: ${err.message}`
297
+ );
298
+ }
299
+ if (mcpOk && skillOk) {
300
+ console.log("\nSetup complete! Restart Claude Code to use prinfer.");
301
+ } else {
302
+ console.error("\nSome steps failed. Manual setup:\n");
303
+ console.error(MANUAL_SETUP);
304
+ process.exit(1);
305
+ }
306
+ }
182
307
  function parseFileArg(arg) {
183
308
  const match = arg.match(/^(.+):(\d+)$/);
184
309
  if (match) {
@@ -192,6 +317,10 @@ function parseArgs(argv) {
192
317
  console.log(HELP);
193
318
  return null;
194
319
  }
320
+ if (args[0] === "setup") {
321
+ runSetup();
322
+ return null;
323
+ }
195
324
  const fileArg = args[0];
196
325
  const name = args[1];
197
326
  if (!fileArg || !name) {
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 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":";;;AAAA,OAAO,QAAQ;AACf,OAAOA,WAAU;;;ACDjB,OAAO,UAAU;AACjB,OAAO,QAAQ;AAMR,SAAS,oBAAoB,UAAsC;AACzE,SACC,GAAG,eAAe,UAAU,GAAG,IAAI,YAAY,eAAe,KAC9D;AAEF;AAKO,SAAS,YACf,cACA,SACa;AACb,QAAM,UAAU,KAAK,QAAQ,YAAY;AACzC,QAAM,eAAe,UAClB,KAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO,IACnC,oBAAoB,OAAO;AAE9B,MAAI,CAAC,cAAc;AAElB,WAAO,GAAG,cAAc,CAAC,YAAY,GAAG;AAAA,MACvC,QAAQ,GAAG,aAAa;AAAA,MACxB,QAAQ,GAAG,WAAW;AAAA,MACtB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,kBAAkB,GAAG,qBAAqB;AAAA,MAC1C,cAAc;AAAA,IACf,CAAC;AAAA,EACF;AAEA,QAAM,MAAM,GAAG,eAAe,cAAc,GAAG,IAAI,QAAQ;AAC3D,MAAI,IAAI,OAAO;AACd,UAAM,IAAI;AAAA,MACT,GAAG,6BAA6B,IAAI,MAAM,aAAa,IAAI;AAAA,IAC5D;AAAA,EACD;AAEA,QAAM,SAAS,GAAG;AAAA,IACjB,IAAI;AAAA,IACJ,GAAG;AAAA,IACH,KAAK,QAAQ,YAAY;AAAA,EAC1B;AACA,SAAO,GAAG,cAAc;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,EACjB,CAAC;AACF;AAEA,SAAS,gBACR,GACgD;AAChD,SAAO,CAAC,CAAC,MAAM,GAAG,gBAAgB,CAAC,KAAK,GAAG,qBAAqB,CAAC;AAClE;AAEA,SAAS,aACR,GACqB;AACrB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,GAAG,aAAa,CAAC,EAAG,QAAO,EAAE;AACjC,MAAI,GAAG,gBAAgB,CAAC,EAAG,QAAO,EAAE;AACpC,MAAI,GAAG,iBAAiB,CAAC,EAAG,QAAO,EAAE;AACrC,SAAO;AACR;AAEA,SAAS,oBAAoB,MAAe,MAAuB;AAElE,MAAI,GAAG,sBAAsB,IAAI,KAAK,KAAK,MAAM,SAAS,KAAM,QAAO;AAGvE,MACC,GAAG,sBAAsB,IAAI,KAC7B,GAAG,aAAa,KAAK,IAAI,KACzB,KAAK,KAAK,SAAS,MAClB;AACD,WAAO,gBAAgB,KAAK,WAAW;AAAA,EACxC;AAGA,OACE,GAAG,oBAAoB,IAAI,KAAK,GAAG,kBAAkB,IAAI,MAC1D,aAAa,KAAK,IAAI,MAAM,MAC3B;AACD,WAAO;AAAA,EACR;AAIA,MAAI,GAAG,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,GAAG,sBAAsB,IAAI,KAC7B,GAAG,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,OAAG,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,GAAG,sBAAsB,IAAI,KAAK,GAAG,oBAAoB,IAAI,GAAG;AACnE,UAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACpD,WAAW,GAAG,sBAAsB,IAAI,GAAG;AAC1C,UAAM,OAAO,KAAK;AAClB,QAAI,gBAAgB,IAAI;AACvB,YAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACrD,WAAW,GAAG,qBAAqB,IAAI,GAAG;AACzC,UAAM,OAAO,KAAK;AAClB,QAAI,gBAAgB,IAAI;AACvB,YAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACrD,WAAW,GAAG,kBAAkB,IAAI,GAAG;AAEtC,UAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACpD;AAEA,QAAM,QAAQ,GAAG,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,GAAG,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,eAAeC,MAAK,QAAQ,QAAQ,IAAI,GAAG,IAAI;AAErD,MAAI,CAAC,GAAG,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;;;AEzEA,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBX,KAAK;AASP,SAAS,aAAa,KAA8C;AAEnE,QAAM,QAAQ,IAAI,MAAM,cAAc;AACtC,MAAI,OAAO;AACV,WAAO,EAAE,MAAM,MAAM,CAAC,GAAG,MAAM,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,EAAE;AAAA,EAC9D;AACA,SAAO,EAAE,MAAM,IAAI;AACpB;AAEA,SAAS,UAAU,MAAmC;AACrD,QAAM,OAAO,KAAK,MAAM,CAAC;AAGzB,MAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,GAAG;AACxE,YAAQ,IAAI,IAAI;AAChB,WAAO;AAAA,EACR;AAEA,QAAM,UAAU,KAAK,CAAC;AACtB,QAAM,OAAO,KAAK,CAAC;AAEnB,MAAI,CAAC,WAAW,CAAC,MAAM;AACtB,YAAQ;AAAA,MACP;AAAA,IACD;AACA,YAAQ,IAAI,IAAI;AAChB,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,EAAE,MAAM,KAAK,IAAI,aAAa,OAAO;AAG3C,MAAI;AACJ,QAAM,aAAa,KAAK,UAAU,CAAC,MAAM,MAAM,eAAe,MAAM,IAAI;AACxE,MAAI,cAAc,GAAG;AACpB,cAAU,KAAK,aAAa,CAAC;AAC7B,QAAI,CAAC,SAAS;AACb,cAAQ,MAAM,8CAA8C;AAC5D,cAAQ,IAAI,IAAI;AAChB,cAAQ,KAAK,CAAC;AAAA,IACf;AAAA,EACD;AAEA,SAAO,EAAE,MAAM,MAAM,MAAM,QAAQ;AACpC;AAEA,SAAS,OAAa;AACrB,QAAM,UAAU,UAAU,QAAQ,IAAI;AAEtC,MAAI,CAAC,SAAS;AACb,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,MAAI;AACH,UAAM,SAAS,UAAU,QAAQ,MAAM,QAAQ,MAAM;AAAA,MACpD,MAAM,QAAQ;AAAA,MACd,SAAS,QAAQ;AAAA,IAClB,CAAC;AAED,YAAQ,IAAI,OAAO,SAAS;AAC5B,QAAI,OAAO,YAAY;AACtB,cAAQ,IAAI,YAAY,OAAO,UAAU;AAAA,IAC1C;AAAA,EACD,SAAS,OAAO;AACf,YAAQ,MAAO,MAAgB,OAAO;AACtC,YAAQ,KAAK,CAAC;AAAA,EACf;AACD;AAEA,KAAK;","names":["path","path"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/index.ts","../src/core.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\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 prinfer setup\n\nCommands:\n setup Install MCP server and skill for Claude Code\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 setup\n`.trim();\n\nconst MANUAL_SETUP = `\nManual setup instructions:\n\n1. Add MCP server to ~/.claude/claude_desktop_config.json:\n\n {\n \"mcpServers\": {\n \"prinfer\": {\n \"command\": \"prinfer-mcp\"\n }\n }\n }\n\n2. Create skill file at ~/.claude/skills/prefer-infer.md:\n\n See https://github.com/clockblocker/prinfer for skill content.\n`.trim();\n\ninterface McpConfig {\n\tmcpServers?: Record<string, { command: string; args?: string[] }>;\n}\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 runSetup(): void {\n\tconst homeDir = os.homedir();\n\tconst claudeDir = path.join(homeDir, \".claude\");\n\n\tif (!fs.existsSync(claudeDir)) {\n\t\tconsole.error(\"Error: ~/.claude directory not found.\");\n\t\tconsole.error(\"Make sure Claude Code is installed first.\\n\");\n\t\tconsole.error(MANUAL_SETUP);\n\t\tprocess.exit(1);\n\t}\n\n\tlet mcpOk = false;\n\tlet skillOk = false;\n\n\t// Install MCP server\n\tconst configFile = path.join(claudeDir, \"claude_desktop_config.json\");\n\ttry {\n\t\tlet config: McpConfig = {};\n\t\tif (fs.existsSync(configFile)) {\n\t\t\tconfig = JSON.parse(fs.readFileSync(configFile, \"utf-8\"));\n\t\t}\n\n\t\tif (config.mcpServers?.prinfer) {\n\t\t\tconsole.log(\"[ok] MCP server already configured\");\n\t\t\tmcpOk = true;\n\t\t} else {\n\t\t\tconfig.mcpServers = config.mcpServers || {};\n\t\t\tconfig.mcpServers.prinfer = { command: \"prinfer-mcp\" };\n\t\t\tfs.writeFileSync(\n\t\t\t\tconfigFile,\n\t\t\t\t`${JSON.stringify(config, null, 2)}\\n`,\n\t\t\t);\n\t\t\tconsole.log(\"[ok] Added MCP server to claude_desktop_config.json\");\n\t\t\tmcpOk = true;\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[error] Failed to configure MCP server: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\t// Install skill\n\tconst skillsDir = path.join(claudeDir, \"skills\");\n\tconst skillFile = path.join(skillsDir, \"prefer-infer.md\");\n\ttry {\n\t\tif (!fs.existsSync(skillsDir)) {\n\t\t\tfs.mkdirSync(skillsDir, { recursive: true });\n\t\t}\n\n\t\tif (fs.existsSync(skillFile)) {\n\t\t\tconsole.log(\"[ok] Skill already installed\");\n\t\t\tskillOk = true;\n\t\t} else {\n\t\t\tfs.writeFileSync(skillFile, SKILL_CONTENT);\n\t\t\tconsole.log(\n\t\t\t\t\"[ok] Installed skill to ~/.claude/skills/prefer-infer.md\",\n\t\t\t);\n\t\t\tskillOk = true;\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[error] Failed to install skill: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\tif (mcpOk && skillOk) {\n\t\tconsole.log(\"\\nSetup complete! Restart Claude Code to use prinfer.\");\n\t} else {\n\t\tconsole.error(\"\\nSome steps failed. Manual setup:\\n\");\n\t\tconsole.error(MANUAL_SETUP);\n\t\tprocess.exit(1);\n\t}\n}\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\t// Check for setup command\n\tif (args[0] === \"setup\") {\n\t\trunSetup();\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","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":["fs","path","path","path","path","fs"]}
package/dist/mcp.cjs CHANGED
@@ -187,6 +187,37 @@ function inferType(file, name, options) {
187
187
  }
188
188
 
189
189
  // src/mcp.ts
190
+ var HELP = `
191
+ prinfer-mcp - MCP server for TypeScript type inference
192
+
193
+ This is an MCP (Model Context Protocol) server. It's designed to be run
194
+ by Claude Code, not directly from the command line.
195
+
196
+ Setup:
197
+ Run 'prinfer setup' to configure Claude Code automatically.
198
+
199
+ Manual setup:
200
+ Add to ~/.claude/claude_desktop_config.json:
201
+
202
+ {
203
+ "mcpServers": {
204
+ "prinfer": {
205
+ "command": "prinfer-mcp"
206
+ }
207
+ }
208
+ }
209
+
210
+ Provided tools:
211
+ infer_type(file, name, line?, project?)
212
+ Infer the TypeScript type of a function or variable.
213
+
214
+ See also:
215
+ prinfer --help CLI for direct type inspection
216
+ `.trim();
217
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
218
+ console.log(HELP);
219
+ process.exit(0);
220
+ }
190
221
  var server = new import_mcp.McpServer({
191
222
  name: "prinfer",
192
223
  version: "0.2.1"
package/dist/mcp.cjs.map CHANGED
@@ -1 +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":["import_node_path","ts","path","ts","path","fs"]}
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 HELP = `\nprinfer-mcp - MCP server for TypeScript type inference\n\nThis is an MCP (Model Context Protocol) server. It's designed to be run\nby Claude Code, not directly from the command line.\n\nSetup:\n Run 'prinfer setup' to configure Claude Code automatically.\n\nManual setup:\n Add to ~/.claude/claude_desktop_config.json:\n\n {\n \"mcpServers\": {\n \"prinfer\": {\n \"command\": \"prinfer-mcp\"\n }\n }\n }\n\nProvided tools:\n infer_type(file, name, line?, project?)\n Infer the TypeScript type of a function or variable.\n\nSee also:\n prinfer --help CLI for direct type inspection\n`.trim();\n\nif (process.argv.includes(\"--help\") || process.argv.includes(\"-h\")) {\n\tconsole.log(HELP);\n\tprocess.exit(0);\n}\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":["import_node_path","ts","path","ts","path","fs"]}
package/dist/mcp.js CHANGED
@@ -164,6 +164,37 @@ function inferType(file, name, options) {
164
164
  }
165
165
 
166
166
  // src/mcp.ts
167
+ var HELP = `
168
+ prinfer-mcp - MCP server for TypeScript type inference
169
+
170
+ This is an MCP (Model Context Protocol) server. It's designed to be run
171
+ by Claude Code, not directly from the command line.
172
+
173
+ Setup:
174
+ Run 'prinfer setup' to configure Claude Code automatically.
175
+
176
+ Manual setup:
177
+ Add to ~/.claude/claude_desktop_config.json:
178
+
179
+ {
180
+ "mcpServers": {
181
+ "prinfer": {
182
+ "command": "prinfer-mcp"
183
+ }
184
+ }
185
+ }
186
+
187
+ Provided tools:
188
+ infer_type(file, name, line?, project?)
189
+ Infer the TypeScript type of a function or variable.
190
+
191
+ See also:
192
+ prinfer --help CLI for direct type inspection
193
+ `.trim();
194
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
195
+ console.log(HELP);
196
+ process.exit(0);
197
+ }
167
198
  var server = new McpServer({
168
199
  name: "prinfer",
169
200
  version: "0.2.1"
package/dist/mcp.js.map CHANGED
@@ -1 +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,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,SAAS;;;ACHlB,OAAO,QAAQ;AACf,OAAOA,WAAU;;;ACDjB,OAAO,UAAU;AACjB,OAAO,QAAQ;AAMR,SAAS,oBAAoB,UAAsC;AACzE,SACC,GAAG,eAAe,UAAU,GAAG,IAAI,YAAY,eAAe,KAC9D;AAEF;AAKO,SAAS,YACf,cACA,SACa;AACb,QAAM,UAAU,KAAK,QAAQ,YAAY;AACzC,QAAM,eAAe,UAClB,KAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO,IACnC,oBAAoB,OAAO;AAE9B,MAAI,CAAC,cAAc;AAElB,WAAO,GAAG,cAAc,CAAC,YAAY,GAAG;AAAA,MACvC,QAAQ,GAAG,aAAa;AAAA,MACxB,QAAQ,GAAG,WAAW;AAAA,MACtB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,kBAAkB,GAAG,qBAAqB;AAAA,MAC1C,cAAc;AAAA,IACf,CAAC;AAAA,EACF;AAEA,QAAM,MAAM,GAAG,eAAe,cAAc,GAAG,IAAI,QAAQ;AAC3D,MAAI,IAAI,OAAO;AACd,UAAM,IAAI;AAAA,MACT,GAAG,6BAA6B,IAAI,MAAM,aAAa,IAAI;AAAA,IAC5D;AAAA,EACD;AAEA,QAAM,SAAS,GAAG;AAAA,IACjB,IAAI;AAAA,IACJ,GAAG;AAAA,IACH,KAAK,QAAQ,YAAY;AAAA,EAC1B;AACA,SAAO,GAAG,cAAc;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,EACjB,CAAC;AACF;AAEA,SAAS,gBACR,GACgD;AAChD,SAAO,CAAC,CAAC,MAAM,GAAG,gBAAgB,CAAC,KAAK,GAAG,qBAAqB,CAAC;AAClE;AAEA,SAAS,aACR,GACqB;AACrB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,GAAG,aAAa,CAAC,EAAG,QAAO,EAAE;AACjC,MAAI,GAAG,gBAAgB,CAAC,EAAG,QAAO,EAAE;AACpC,MAAI,GAAG,iBAAiB,CAAC,EAAG,QAAO,EAAE;AACrC,SAAO;AACR;AAEA,SAAS,oBAAoB,MAAe,MAAuB;AAElE,MAAI,GAAG,sBAAsB,IAAI,KAAK,KAAK,MAAM,SAAS,KAAM,QAAO;AAGvE,MACC,GAAG,sBAAsB,IAAI,KAC7B,GAAG,aAAa,KAAK,IAAI,KACzB,KAAK,KAAK,SAAS,MAClB;AACD,WAAO,gBAAgB,KAAK,WAAW;AAAA,EACxC;AAGA,OACE,GAAG,oBAAoB,IAAI,KAAK,GAAG,kBAAkB,IAAI,MAC1D,aAAa,KAAK,IAAI,MAAM,MAC3B;AACD,WAAO;AAAA,EACR;AAIA,MAAI,GAAG,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,GAAG,sBAAsB,IAAI,KAC7B,GAAG,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,OAAG,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,GAAG,sBAAsB,IAAI,KAAK,GAAG,oBAAoB,IAAI,GAAG;AACnE,UAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACpD,WAAW,GAAG,sBAAsB,IAAI,GAAG;AAC1C,UAAM,OAAO,KAAK;AAClB,QAAI,gBAAgB,IAAI;AACvB,YAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACrD,WAAW,GAAG,qBAAqB,IAAI,GAAG;AACzC,UAAM,OAAO,KAAK;AAClB,QAAI,gBAAgB,IAAI;AACvB,YAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACrD,WAAW,GAAG,kBAAkB,IAAI,GAAG;AAEtC,UAAM,QAAQ,4BAA4B,IAAI,KAAK;AAAA,EACpD;AAEA,QAAM,QAAQ,GAAG,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,GAAG,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,eAAeC,MAAK,QAAQ,QAAQ,IAAI,GAAG,IAAI;AAErD,MAAI,CAAC,GAAG,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,UAAU;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AACV,CAAC;AAED,OAAO;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,IACC,MAAM,EAAE,OAAO,EAAE,SAAS,6BAA6B;AAAA,IACvD,MAAM,EACJ,OAAO,EACP,SAAS,6CAA6C;AAAA,IACxD,MAAM,EACJ,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD;AAAA,IAC5D,SAAS,EACP,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,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC/B;AAEA,KAAK,EAAE,MAAM,QAAQ,KAAK;","names":["path","path"]}
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 HELP = `\nprinfer-mcp - MCP server for TypeScript type inference\n\nThis is an MCP (Model Context Protocol) server. It's designed to be run\nby Claude Code, not directly from the command line.\n\nSetup:\n Run 'prinfer setup' to configure Claude Code automatically.\n\nManual setup:\n Add to ~/.claude/claude_desktop_config.json:\n\n {\n \"mcpServers\": {\n \"prinfer\": {\n \"command\": \"prinfer-mcp\"\n }\n }\n }\n\nProvided tools:\n infer_type(file, name, line?, project?)\n Infer the TypeScript type of a function or variable.\n\nSee also:\n prinfer --help CLI for direct type inspection\n`.trim();\n\nif (process.argv.includes(\"--help\") || process.argv.includes(\"-h\")) {\n\tconsole.log(HELP);\n\tprocess.exit(0);\n}\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"]}
@@ -63,28 +63,53 @@ Use the \`infer_type\` MCP tool to check the type:
63
63
  `;
64
64
  function main() {
65
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
66
  const claudeDir = import_node_path.default.join(homeDir, ".claude");
69
67
  if (!import_node_fs.default.existsSync(claudeDir)) {
70
68
  console.log(
71
- "~/.claude directory not found. Skipping skill installation."
69
+ "~/.claude not found. Run 'prinfer setup' after installing Claude Code."
72
70
  );
73
- console.log("To manually install, create ~/.claude/skills/prinfer.md");
74
71
  return;
75
72
  }
76
- if (!import_node_fs.default.existsSync(skillsDir)) {
77
- import_node_fs.default.mkdirSync(skillsDir, { recursive: true });
73
+ let success = true;
74
+ const configFile = import_node_path.default.join(claudeDir, "claude_desktop_config.json");
75
+ try {
76
+ let config = {};
77
+ if (import_node_fs.default.existsSync(configFile)) {
78
+ config = JSON.parse(import_node_fs.default.readFileSync(configFile, "utf-8"));
79
+ }
80
+ if (!config.mcpServers?.prinfer) {
81
+ config.mcpServers = config.mcpServers || {};
82
+ config.mcpServers.prinfer = { command: "prinfer-mcp" };
83
+ import_node_fs.default.writeFileSync(
84
+ configFile,
85
+ `${JSON.stringify(config, null, 2)}
86
+ `
87
+ );
88
+ console.log("[ok] Added MCP server to claude_desktop_config.json");
89
+ }
90
+ } catch (err) {
91
+ console.error(`[error] MCP setup failed: ${err.message}`);
92
+ success = false;
78
93
  }
79
- if (import_node_fs.default.existsSync(skillFile)) {
80
- console.log(
81
- "prinfer skill already installed at ~/.claude/skills/prinfer.md"
82
- );
83
- return;
94
+ const skillsDir = import_node_path.default.join(claudeDir, "skills");
95
+ const skillFile = import_node_path.default.join(skillsDir, "prefer-infer.md");
96
+ try {
97
+ if (!import_node_fs.default.existsSync(skillsDir)) {
98
+ import_node_fs.default.mkdirSync(skillsDir, { recursive: true });
99
+ }
100
+ if (!import_node_fs.default.existsSync(skillFile)) {
101
+ import_node_fs.default.writeFileSync(skillFile, SKILL_CONTENT);
102
+ console.log(
103
+ "[ok] Installed skill to ~/.claude/skills/prefer-infer.md"
104
+ );
105
+ }
106
+ } catch (err) {
107
+ console.error(`[error] Skill setup failed: ${err.message}`);
108
+ success = false;
109
+ }
110
+ if (!success) {
111
+ console.error("\nSome steps failed. Run 'prinfer setup' for details.");
84
112
  }
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
113
  }
89
114
  main();
90
115
  //# sourceMappingURL=postinstall.cjs.map
@@ -1 +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"]}
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\ninterface McpConfig {\n\tmcpServers?: Record<string, { command: string; args?: string[] }>;\n}\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 claudeDir = path.join(homeDir, \".claude\");\n\n\tif (!fs.existsSync(claudeDir)) {\n\t\tconsole.log(\n\t\t\t\"~/.claude not found. Run 'prinfer setup' after installing Claude Code.\",\n\t\t);\n\t\treturn;\n\t}\n\n\tlet success = true;\n\n\t// Install MCP server\n\tconst configFile = path.join(claudeDir, \"claude_desktop_config.json\");\n\ttry {\n\t\tlet config: McpConfig = {};\n\t\tif (fs.existsSync(configFile)) {\n\t\t\tconfig = JSON.parse(fs.readFileSync(configFile, \"utf-8\"));\n\t\t}\n\n\t\tif (!config.mcpServers?.prinfer) {\n\t\t\tconfig.mcpServers = config.mcpServers || {};\n\t\t\tconfig.mcpServers.prinfer = { command: \"prinfer-mcp\" };\n\t\t\tfs.writeFileSync(\n\t\t\t\tconfigFile,\n\t\t\t\t`${JSON.stringify(config, null, 2)}\\n`,\n\t\t\t);\n\t\t\tconsole.log(\"[ok] Added MCP server to claude_desktop_config.json\");\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(`[error] MCP setup failed: ${(err as Error).message}`);\n\t\tsuccess = false;\n\t}\n\n\t// Install skill\n\tconst skillsDir = path.join(claudeDir, \"skills\");\n\tconst skillFile = path.join(skillsDir, \"prefer-infer.md\");\n\ttry {\n\t\tif (!fs.existsSync(skillsDir)) {\n\t\t\tfs.mkdirSync(skillsDir, { recursive: true });\n\t\t}\n\n\t\tif (!fs.existsSync(skillFile)) {\n\t\t\tfs.writeFileSync(skillFile, SKILL_CONTENT);\n\t\t\tconsole.log(\n\t\t\t\t\"[ok] Installed skill to ~/.claude/skills/prefer-infer.md\",\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(`[error] Skill setup failed: ${(err as Error).message}`);\n\t\tsuccess = false;\n\t}\n\n\tif (!success) {\n\t\tconsole.error(\"\\nSome steps failed. Run 'prinfer setup' for details.\");\n\t}\n}\n\nmain();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,qBAAe;AACf,qBAAe;AACf,uBAAiB;AAMjB,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,SAAS;AAE9C,MAAI,CAAC,eAAAC,QAAG,WAAW,SAAS,GAAG;AAC9B,YAAQ;AAAA,MACP;AAAA,IACD;AACA;AAAA,EACD;AAEA,MAAI,UAAU;AAGd,QAAM,aAAa,iBAAAD,QAAK,KAAK,WAAW,4BAA4B;AACpE,MAAI;AACH,QAAI,SAAoB,CAAC;AACzB,QAAI,eAAAC,QAAG,WAAW,UAAU,GAAG;AAC9B,eAAS,KAAK,MAAM,eAAAA,QAAG,aAAa,YAAY,OAAO,CAAC;AAAA,IACzD;AAEA,QAAI,CAAC,OAAO,YAAY,SAAS;AAChC,aAAO,aAAa,OAAO,cAAc,CAAC;AAC1C,aAAO,WAAW,UAAU,EAAE,SAAS,cAAc;AACrD,qBAAAA,QAAG;AAAA,QACF;AAAA,QACA,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA;AAAA,MACnC;AACA,cAAQ,IAAI,qDAAqD;AAAA,IAClE;AAAA,EACD,SAAS,KAAK;AACb,YAAQ,MAAM,6BAA8B,IAAc,OAAO,EAAE;AACnE,cAAU;AAAA,EACX;AAGA,QAAM,YAAY,iBAAAD,QAAK,KAAK,WAAW,QAAQ;AAC/C,QAAM,YAAY,iBAAAA,QAAK,KAAK,WAAW,iBAAiB;AACxD,MAAI;AACH,QAAI,CAAC,eAAAC,QAAG,WAAW,SAAS,GAAG;AAC9B,qBAAAA,QAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAC5C;AAEA,QAAI,CAAC,eAAAA,QAAG,WAAW,SAAS,GAAG;AAC9B,qBAAAA,QAAG,cAAc,WAAW,aAAa;AACzC,cAAQ;AAAA,QACP;AAAA,MACD;AAAA,IACD;AAAA,EACD,SAAS,KAAK;AACb,YAAQ,MAAM,+BAAgC,IAAc,OAAO,EAAE;AACrE,cAAU;AAAA,EACX;AAEA,MAAI,CAAC,SAAS;AACb,YAAQ,MAAM,uDAAuD;AAAA,EACtE;AACD;AAEA,KAAK;","names":["os","path","fs"]}
@@ -40,28 +40,53 @@ Use the \`infer_type\` MCP tool to check the type:
40
40
  `;
41
41
  function main() {
42
42
  const homeDir = os.homedir();
43
- const skillsDir = path.join(homeDir, ".claude", "skills");
44
- const skillFile = path.join(skillsDir, "prinfer.md");
45
43
  const claudeDir = path.join(homeDir, ".claude");
46
44
  if (!fs.existsSync(claudeDir)) {
47
45
  console.log(
48
- "~/.claude directory not found. Skipping skill installation."
46
+ "~/.claude not found. Run 'prinfer setup' after installing Claude Code."
49
47
  );
50
- console.log("To manually install, create ~/.claude/skills/prinfer.md");
51
48
  return;
52
49
  }
53
- if (!fs.existsSync(skillsDir)) {
54
- fs.mkdirSync(skillsDir, { recursive: true });
50
+ let success = true;
51
+ const configFile = path.join(claudeDir, "claude_desktop_config.json");
52
+ try {
53
+ let config = {};
54
+ if (fs.existsSync(configFile)) {
55
+ config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
56
+ }
57
+ if (!config.mcpServers?.prinfer) {
58
+ config.mcpServers = config.mcpServers || {};
59
+ config.mcpServers.prinfer = { command: "prinfer-mcp" };
60
+ fs.writeFileSync(
61
+ configFile,
62
+ `${JSON.stringify(config, null, 2)}
63
+ `
64
+ );
65
+ console.log("[ok] Added MCP server to claude_desktop_config.json");
66
+ }
67
+ } catch (err) {
68
+ console.error(`[error] MCP setup failed: ${err.message}`);
69
+ success = false;
55
70
  }
56
- if (fs.existsSync(skillFile)) {
57
- console.log(
58
- "prinfer skill already installed at ~/.claude/skills/prinfer.md"
59
- );
60
- return;
71
+ const skillsDir = path.join(claudeDir, "skills");
72
+ const skillFile = path.join(skillsDir, "prefer-infer.md");
73
+ try {
74
+ if (!fs.existsSync(skillsDir)) {
75
+ fs.mkdirSync(skillsDir, { recursive: true });
76
+ }
77
+ if (!fs.existsSync(skillFile)) {
78
+ fs.writeFileSync(skillFile, SKILL_CONTENT);
79
+ console.log(
80
+ "[ok] Installed skill to ~/.claude/skills/prefer-infer.md"
81
+ );
82
+ }
83
+ } catch (err) {
84
+ console.error(`[error] Skill setup failed: ${err.message}`);
85
+ success = false;
86
+ }
87
+ if (!success) {
88
+ console.error("\nSome steps failed. Run 'prinfer setup' for details.");
61
89
  }
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
90
  }
66
91
  main();
67
92
  //# sourceMappingURL=postinstall.js.map
@@ -1 +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":[]}
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\ninterface McpConfig {\n\tmcpServers?: Record<string, { command: string; args?: string[] }>;\n}\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 claudeDir = path.join(homeDir, \".claude\");\n\n\tif (!fs.existsSync(claudeDir)) {\n\t\tconsole.log(\n\t\t\t\"~/.claude not found. Run 'prinfer setup' after installing Claude Code.\",\n\t\t);\n\t\treturn;\n\t}\n\n\tlet success = true;\n\n\t// Install MCP server\n\tconst configFile = path.join(claudeDir, \"claude_desktop_config.json\");\n\ttry {\n\t\tlet config: McpConfig = {};\n\t\tif (fs.existsSync(configFile)) {\n\t\t\tconfig = JSON.parse(fs.readFileSync(configFile, \"utf-8\"));\n\t\t}\n\n\t\tif (!config.mcpServers?.prinfer) {\n\t\t\tconfig.mcpServers = config.mcpServers || {};\n\t\t\tconfig.mcpServers.prinfer = { command: \"prinfer-mcp\" };\n\t\t\tfs.writeFileSync(\n\t\t\t\tconfigFile,\n\t\t\t\t`${JSON.stringify(config, null, 2)}\\n`,\n\t\t\t);\n\t\t\tconsole.log(\"[ok] Added MCP server to claude_desktop_config.json\");\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(`[error] MCP setup failed: ${(err as Error).message}`);\n\t\tsuccess = false;\n\t}\n\n\t// Install skill\n\tconst skillsDir = path.join(claudeDir, \"skills\");\n\tconst skillFile = path.join(skillsDir, \"prefer-infer.md\");\n\ttry {\n\t\tif (!fs.existsSync(skillsDir)) {\n\t\t\tfs.mkdirSync(skillsDir, { recursive: true });\n\t\t}\n\n\t\tif (!fs.existsSync(skillFile)) {\n\t\t\tfs.writeFileSync(skillFile, SKILL_CONTENT);\n\t\t\tconsole.log(\n\t\t\t\t\"[ok] Installed skill to ~/.claude/skills/prefer-infer.md\",\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(`[error] Skill setup failed: ${(err as Error).message}`);\n\t\tsuccess = false;\n\t}\n\n\tif (!success) {\n\t\tconsole.error(\"\\nSome steps failed. Run 'prinfer setup' for details.\");\n\t}\n}\n\nmain();\n"],"mappings":";;;AACA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AAMjB,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,SAAS;AAE9C,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC9B,YAAQ;AAAA,MACP;AAAA,IACD;AACA;AAAA,EACD;AAEA,MAAI,UAAU;AAGd,QAAM,aAAa,KAAK,KAAK,WAAW,4BAA4B;AACpE,MAAI;AACH,QAAI,SAAoB,CAAC;AACzB,QAAI,GAAG,WAAW,UAAU,GAAG;AAC9B,eAAS,KAAK,MAAM,GAAG,aAAa,YAAY,OAAO,CAAC;AAAA,IACzD;AAEA,QAAI,CAAC,OAAO,YAAY,SAAS;AAChC,aAAO,aAAa,OAAO,cAAc,CAAC;AAC1C,aAAO,WAAW,UAAU,EAAE,SAAS,cAAc;AACrD,SAAG;AAAA,QACF;AAAA,QACA,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA;AAAA,MACnC;AACA,cAAQ,IAAI,qDAAqD;AAAA,IAClE;AAAA,EACD,SAAS,KAAK;AACb,YAAQ,MAAM,6BAA8B,IAAc,OAAO,EAAE;AACnE,cAAU;AAAA,EACX;AAGA,QAAM,YAAY,KAAK,KAAK,WAAW,QAAQ;AAC/C,QAAM,YAAY,KAAK,KAAK,WAAW,iBAAiB;AACxD,MAAI;AACH,QAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC9B,SAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAC5C;AAEA,QAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC9B,SAAG,cAAc,WAAW,aAAa;AACzC,cAAQ;AAAA,QACP;AAAA,MACD;AAAA,IACD;AAAA,EACD,SAAS,KAAK;AACb,YAAQ,MAAM,+BAAgC,IAAc,OAAO,EAAE;AACrE,cAAU;AAAA,EACX;AAEA,MAAI,CAAC,SAAS;AACb,YAAQ,MAAM,uDAAuD;AAAA,EACtE;AACD;AAEA,KAAK;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prinfer",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript type inference inspection tool",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -34,7 +34,7 @@
34
34
  "release": "bun run build && changeset publish",
35
35
  "prepublishOnly": "bun run ci",
36
36
  "prepare": "simple-git-hooks",
37
- "postinstall": "node ./dist/postinstall.js || true"
37
+ "postinstall": "[ ! -f ./dist/postinstall.js ] || node ./dist/postinstall.js"
38
38
  },
39
39
  "simple-git-hooks": {
40
40
  "pre-commit": "bunx biome check ."