prinfer 0.5.2 → 0.6.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
@@ -6,7 +6,8 @@
6
6
 
7
7
  **Typehints for your AI agent.**
8
8
 
9
- prinfer gives AI coding assistants the ability to inspect TypeScript's inferred types so they can write cleaner code without redundant type annotations.
9
+ Give AI coding assistant the ability to inspect TypeScript's inferred types mimicking the IDE's hover behavior.
10
+ so they can write cleaner code without redundant type annotations.
10
11
 
11
12
  ## Why?
12
13
 
@@ -40,13 +41,15 @@ prinfer setup
40
41
 
41
42
  ### MCP Server (`prinfer-mcp`)
42
43
 
43
- Your agent gets an `infer_type` tool to check what TypeScript infers:
44
+ Your agent gets a `hover` tool to check what TypeScript infers at any position:
44
45
 
45
46
  ```
46
- infer_type(file: "src/utils.ts", name: "myFunction")
47
- infer_type(file: "src/utils.ts", name: "commandResult", line: 75)
47
+ hover(file: "src/utils.ts", line: 75, column: 10)
48
+ hover(file: "src/utils.ts", line: 75, column: 10, include_docs: true)
48
49
  ```
49
50
 
51
+ The position-based API matches IDE behavior and returns instantiated generic types at call sites.
52
+
50
53
  ### Claude Skill (`~/.claude/skills/prefer-infer.md`)
51
54
 
52
55
  A coding guideline that encourages your agent to:
@@ -55,22 +58,16 @@ A coding guideline that encourages your agent to:
55
58
  - Use prinfer to verify types before adding redundant hints
56
59
  - Write idiomatic TypeScript
57
60
 
58
- Plus a `/check-type` command for quick lookups.
61
+ Plus a `/hover` command for quick lookups.
59
62
 
60
63
  ## Manual Setup
61
64
 
62
65
  If `prinfer setup` doesn't work, configure manually:
63
66
 
64
- **1. Add MCP server** to `~/.claude/settings.json`:
67
+ **1. Add MCP server** using the Claude CLI:
65
68
 
66
- ```json
67
- {
68
- "mcpServers": {
69
- "prinfer": {
70
- "command": "prinfer-mcp"
71
- }
72
- }
73
- }
69
+ ```bash
70
+ claude mcp add prinfer node /path/to/node_modules/prinfer/dist/mcp.js
74
71
  ```
75
72
 
76
73
  **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)
@@ -80,8 +77,9 @@ If `prinfer setup` doesn't work, configure manually:
80
77
  prinfer also works as a standalone CLI:
81
78
 
82
79
  ```bash
83
- prinfer src/utils.ts myFunction
84
- prinfer src/utils.ts:75 commandResult
80
+ prinfer src/utils.ts:75:10
81
+ prinfer src/utils.ts:75:10 --docs
82
+ prinfer src/utils.ts:75:10 --project ./tsconfig.json
85
83
  ```
86
84
 
87
85
  Output:
@@ -89,18 +87,25 @@ Output:
89
87
  ```
90
88
  (x: number, y: string) => boolean
91
89
  returns: boolean
90
+ name: myFunction
91
+ kind: function
92
+ docs: Adds two numbers together.
92
93
  ```
93
94
 
94
95
  ## Programmatic API
95
96
 
96
97
  ```typescript
97
- import { inferType } from "prinfer";
98
+ import { hover } from "prinfer";
99
+
100
+ const result = hover("./src/utils.ts", 75, 10);
101
+ // => { signature: "(x: number, y: string) => boolean", returnType: "boolean", line: 75, column: 10, kind: "function", name: "myFunction" }
98
102
 
99
- const result = inferType("./src/utils.ts", "myFunction");
100
- // => { signature: "(x: number, y: string) => boolean", returnType: "boolean", line: 4 }
103
+ // With documentation
104
+ const result2 = hover("./src/utils.ts", 75, 10, { include_docs: true });
105
+ // => { ..., documentation: "Adds two numbers together." }
101
106
 
102
- // With line number for disambiguation
103
- const result2 = inferType("./src/utils.ts", "commandResult", { line: 75 });
107
+ // With custom tsconfig
108
+ const result3 = hover("./src/utils.ts", 75, 10, { project: "./tsconfig.json" });
104
109
  ```
105
110
 
106
111
  ## Requirements
package/dist/cli.cjs CHANGED
@@ -24,6 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli.ts
27
+ var import_node_child_process = require("child_process");
27
28
  var import_node_fs2 = __toESM(require("fs"), 1);
28
29
  var import_node_os = __toESM(require("os"), 1);
29
30
  var import_node_path3 = __toESM(require("path"), 1);
@@ -71,66 +72,203 @@ function loadProgram(entryFileAbs, project) {
71
72
  function isArrowOrFnExpr(n) {
72
73
  return !!n && (import_typescript.default.isArrowFunction(n) || import_typescript.default.isFunctionExpression(n));
73
74
  }
74
- function nodeNameText(n) {
75
- if (!n) return void 0;
76
- if (import_typescript.default.isIdentifier(n)) return n.text;
77
- if (import_typescript.default.isStringLiteral(n)) return n.text;
78
- if (import_typescript.default.isNumericLiteral(n)) return n.text;
79
- return void 0;
80
- }
81
- function isFunctionLikeNamed(node, name) {
82
- if (import_typescript.default.isFunctionDeclaration(node) && node.name?.text === name) return true;
83
- if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name) && node.name.text === name) {
84
- return isArrowOrFnExpr(node.initializer);
85
- }
86
- if ((import_typescript.default.isMethodDeclaration(node) || import_typescript.default.isMethodSignature(node)) && nodeNameText(node.name) === name) {
87
- return true;
88
- }
89
- if (import_typescript.default.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
90
- return isArrowOrFnExpr(node.initializer);
75
+ function findSmallestNodeAtPosition(sourceFile, position) {
76
+ let result;
77
+ function visit(node) {
78
+ const start = node.getStart(sourceFile);
79
+ const end = node.getEnd();
80
+ if (position < start || position >= end) {
81
+ return;
82
+ }
83
+ if (!result || node.getWidth(sourceFile) <= result.getWidth(sourceFile)) {
84
+ result = node;
85
+ }
86
+ import_typescript.default.forEachChild(node, visit);
91
87
  }
92
- return false;
88
+ visit(sourceFile);
89
+ return result;
93
90
  }
94
- function isVariableNamed(node, name) {
95
- if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name) && node.name.text === name) {
91
+ function findHoverableAncestor(sourceFile, position) {
92
+ const smallestNode = findSmallestNodeAtPosition(sourceFile, position);
93
+ if (!smallestNode) return void 0;
94
+ let bestMatch = smallestNode;
95
+ function visit(n) {
96
+ const start = n.getStart(sourceFile);
97
+ const end = n.getEnd();
98
+ if (position < start || position >= end) {
99
+ return false;
100
+ }
101
+ if (import_typescript.default.isCallExpression(n)) {
102
+ const expr = n.expression;
103
+ if (import_typescript.default.isIdentifier(expr)) {
104
+ if (position >= expr.getStart(sourceFile) && position < expr.getEnd()) {
105
+ bestMatch = n;
106
+ }
107
+ } else if (import_typescript.default.isPropertyAccessExpression(expr)) {
108
+ if (position >= expr.name.getStart(sourceFile) && position < expr.name.getEnd()) {
109
+ bestMatch = n;
110
+ }
111
+ }
112
+ }
113
+ if (import_typescript.default.isFunctionDeclaration(n) && n.name) {
114
+ if (position >= n.name.getStart(sourceFile) && position < n.name.getEnd()) {
115
+ bestMatch = n;
116
+ }
117
+ }
118
+ if (import_typescript.default.isVariableDeclaration(n) && import_typescript.default.isIdentifier(n.name)) {
119
+ if (position >= n.name.getStart(sourceFile) && position < n.name.getEnd()) {
120
+ bestMatch = n;
121
+ }
122
+ }
123
+ if ((import_typescript.default.isMethodDeclaration(n) || import_typescript.default.isMethodSignature(n)) && import_typescript.default.isIdentifier(n.name)) {
124
+ if (position >= n.name.getStart(sourceFile) && position < n.name.getEnd()) {
125
+ bestMatch = n;
126
+ }
127
+ }
128
+ if (import_typescript.default.isClassDeclaration(n) && n.name) {
129
+ if (position >= n.name.getStart(sourceFile) && position < n.name.getEnd()) {
130
+ bestMatch = n;
131
+ }
132
+ }
133
+ if (import_typescript.default.isInterfaceDeclaration(n) && n.name) {
134
+ if (position >= n.name.getStart(sourceFile) && position < n.name.getEnd()) {
135
+ bestMatch = n;
136
+ }
137
+ }
138
+ import_typescript.default.forEachChild(n, visit);
96
139
  return true;
97
140
  }
98
- return false;
99
- }
100
- function isNamedNode(node, name) {
101
- return isFunctionLikeNamed(node, name) || isVariableNamed(node, name);
141
+ visit(sourceFile);
142
+ return bestMatch;
102
143
  }
103
- function getLineNumber(sourceFile, node) {
104
- const { line } = sourceFile.getLineAndCharacterOfPosition(
105
- node.getStart(sourceFile)
144
+ function findNodeAtPosition(sourceFile, line, column) {
145
+ const lineCount = sourceFile.getLineStarts().length;
146
+ if (line < 1 || line > lineCount) {
147
+ return void 0;
148
+ }
149
+ const lineStart = sourceFile.getLineStarts()[line - 1];
150
+ const lineEnd = line < lineCount ? sourceFile.getLineStarts()[line] : sourceFile.getEnd();
151
+ const lineLength = lineEnd - lineStart;
152
+ if (column < 1 || column > lineLength + 1) {
153
+ return void 0;
154
+ }
155
+ const position = sourceFile.getPositionOfLineAndCharacter(
156
+ line - 1,
157
+ column - 1
106
158
  );
107
- return line + 1;
159
+ return findHoverableAncestor(sourceFile, position);
108
160
  }
109
- function findNodeByNameAndLine(sourceFile, name, line) {
110
- let found;
111
- const visit = (node) => {
112
- if (found) return;
113
- if (isNamedNode(node, name)) {
114
- if (line !== void 0) {
115
- const nodeLine = getLineNumber(sourceFile, node);
116
- if (nodeLine === line) {
117
- found = node;
118
- return;
119
- }
120
- } else {
121
- found = node;
122
- return;
123
- }
161
+ function getSymbolKind(node) {
162
+ if (import_typescript.default.isFunctionDeclaration(node)) return "function";
163
+ if (import_typescript.default.isArrowFunction(node)) return "function";
164
+ if (import_typescript.default.isFunctionExpression(node)) return "function";
165
+ if (import_typescript.default.isMethodDeclaration(node)) return "method";
166
+ if (import_typescript.default.isMethodSignature(node)) return "method";
167
+ if (import_typescript.default.isVariableDeclaration(node)) {
168
+ const init = node.initializer;
169
+ if (init && (import_typescript.default.isArrowFunction(init) || import_typescript.default.isFunctionExpression(init))) {
170
+ return "function";
124
171
  }
125
- import_typescript.default.forEachChild(node, visit);
126
- };
127
- visit(sourceFile);
128
- return found;
172
+ return "variable";
173
+ }
174
+ if (import_typescript.default.isParameter(node)) return "parameter";
175
+ if (import_typescript.default.isPropertyDeclaration(node)) return "property";
176
+ if (import_typescript.default.isPropertySignature(node)) return "property";
177
+ if (import_typescript.default.isPropertyAccessExpression(node)) return "property";
178
+ if (import_typescript.default.isCallExpression(node)) return "call";
179
+ if (import_typescript.default.isTypeAliasDeclaration(node)) return "type";
180
+ if (import_typescript.default.isInterfaceDeclaration(node)) return "interface";
181
+ if (import_typescript.default.isClassDeclaration(node)) return "class";
182
+ if (import_typescript.default.isIdentifier(node)) return "identifier";
183
+ return "unknown";
129
184
  }
130
- function getTypeInfo(program, node, sourceFile) {
185
+ function getNodeName(node) {
186
+ if (import_typescript.default.isFunctionDeclaration(node) && node.name) {
187
+ return node.name.text;
188
+ }
189
+ if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name)) {
190
+ return node.name.text;
191
+ }
192
+ if ((import_typescript.default.isMethodDeclaration(node) || import_typescript.default.isMethodSignature(node)) && import_typescript.default.isIdentifier(node.name)) {
193
+ return node.name.text;
194
+ }
195
+ if (import_typescript.default.isPropertyAccessExpression(node)) {
196
+ return node.name.text;
197
+ }
198
+ if (import_typescript.default.isCallExpression(node)) {
199
+ const expr = node.expression;
200
+ if (import_typescript.default.isIdentifier(expr)) return expr.text;
201
+ if (import_typescript.default.isPropertyAccessExpression(expr)) return expr.name.text;
202
+ }
203
+ if (import_typescript.default.isParameter(node) && import_typescript.default.isIdentifier(node.name)) {
204
+ return node.name.text;
205
+ }
206
+ if (import_typescript.default.isIdentifier(node)) {
207
+ return node.text;
208
+ }
209
+ if (import_typescript.default.isTypeAliasDeclaration(node) || import_typescript.default.isInterfaceDeclaration(node) || import_typescript.default.isClassDeclaration(node)) {
210
+ return node.name?.text;
211
+ }
212
+ return void 0;
213
+ }
214
+ function getDocumentation(checker, symbol) {
215
+ if (!symbol) return void 0;
216
+ const docs = symbol.getDocumentationComment(checker);
217
+ if (docs.length === 0) return void 0;
218
+ return import_typescript.default.displayPartsToString(docs);
219
+ }
220
+ function getHoverInfo(program, node, sourceFile, includeDocs) {
131
221
  const checker = program.getTypeChecker();
132
- const sf = sourceFile ?? node.getSourceFile();
133
- const line = getLineNumber(sf, node);
222
+ const sf = sourceFile;
223
+ const { line, character } = sf.getLineAndCharacterOfPosition(
224
+ node.getStart(sf)
225
+ );
226
+ const flags = import_typescript.default.TypeFormatFlags.NoTruncation;
227
+ const kind = getSymbolKind(node);
228
+ const name = getNodeName(node);
229
+ let symbol;
230
+ if (import_typescript.default.isCallExpression(node)) {
231
+ const expr = node.expression;
232
+ if (import_typescript.default.isPropertyAccessExpression(expr)) {
233
+ symbol = checker.getSymbolAtLocation(expr.name);
234
+ } else {
235
+ symbol = checker.getSymbolAtLocation(expr);
236
+ }
237
+ } else {
238
+ const nodeWithName2 = node;
239
+ if (nodeWithName2.name) {
240
+ symbol = checker.getSymbolAtLocation(nodeWithName2.name);
241
+ } else {
242
+ symbol = checker.getSymbolAtLocation(node);
243
+ }
244
+ }
245
+ const documentation = includeDocs ? getDocumentation(checker, symbol) : void 0;
246
+ if (import_typescript.default.isCallExpression(node)) {
247
+ const sig2 = checker.getResolvedSignature(node);
248
+ if (sig2) {
249
+ const signature = checker.signatureToString(sig2, void 0, flags);
250
+ const ret = checker.getReturnTypeOfSignature(sig2);
251
+ const returnType = checker.typeToString(ret, void 0, flags);
252
+ return {
253
+ signature,
254
+ returnType,
255
+ line: line + 1,
256
+ column: character + 1,
257
+ documentation,
258
+ kind,
259
+ name
260
+ };
261
+ }
262
+ const t2 = checker.getTypeAtLocation(node);
263
+ return {
264
+ signature: checker.typeToString(t2, void 0, flags),
265
+ line: line + 1,
266
+ column: character + 1,
267
+ documentation,
268
+ kind,
269
+ name
270
+ };
271
+ }
134
272
  let sig;
135
273
  if (import_typescript.default.isFunctionDeclaration(node) || import_typescript.default.isMethodDeclaration(node)) {
136
274
  sig = checker.getSignatureFromDeclaration(node) ?? void 0;
@@ -145,26 +283,39 @@ function getTypeInfo(program, node, sourceFile) {
145
283
  } else if (import_typescript.default.isMethodSignature(node)) {
146
284
  sig = checker.getSignatureFromDeclaration(node) ?? void 0;
147
285
  }
148
- const flags = import_typescript.default.TypeFormatFlags.NoTruncation;
149
286
  if (sig) {
150
287
  const signature = checker.signatureToString(sig, void 0, flags);
151
288
  const ret = checker.getReturnTypeOfSignature(sig);
152
289
  const returnType = checker.typeToString(ret, void 0, flags);
153
- return { signature, returnType, line };
290
+ return {
291
+ signature,
292
+ returnType,
293
+ line: line + 1,
294
+ column: character + 1,
295
+ documentation,
296
+ kind,
297
+ name
298
+ };
154
299
  }
155
300
  const nodeWithName = node;
156
- let nameNode = node;
301
+ let targetNode = node;
157
302
  if (nodeWithName.name && import_typescript.default.isIdentifier(nodeWithName.name)) {
158
- nameNode = nodeWithName.name;
303
+ targetNode = nodeWithName.name;
159
304
  }
160
- const t = checker.getTypeAtLocation(nameNode);
161
- return { signature: checker.typeToString(t, void 0, flags), line };
305
+ const t = checker.getTypeAtLocation(targetNode);
306
+ return {
307
+ signature: checker.typeToString(t, void 0, flags),
308
+ line: line + 1,
309
+ column: character + 1,
310
+ documentation,
311
+ kind,
312
+ name
313
+ };
162
314
  }
163
315
 
164
316
  // src/index.ts
165
- function inferType(file, name, options) {
166
- const opts = typeof options === "string" ? { project: options } : options ?? {};
167
- const { line, project } = opts;
317
+ function hover(file, line, column, options) {
318
+ const { project, include_docs = false } = options ?? {};
168
319
  const entryFileAbs = import_node_path2.default.resolve(process.cwd(), file);
169
320
  if (!import_node_fs.default.existsSync(entryFileAbs)) {
170
321
  throw new Error(`File not found: ${entryFileAbs}`);
@@ -176,58 +327,69 @@ function inferType(file, name, options) {
176
327
  `Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
177
328
  );
178
329
  }
179
- const node = findNodeByNameAndLine(sourceFile, name, line);
330
+ const node = findNodeAtPosition(sourceFile, line, column);
180
331
  if (!node) {
181
- const lineInfo = line !== void 0 ? ` at line ${line}` : "";
182
- throw new Error(
183
- `No symbol named "${name}"${lineInfo} found in ${entryFileAbs}`
184
- );
332
+ throw new Error(`No symbol found at ${entryFileAbs}:${line}:${column}`);
185
333
  }
186
- return getTypeInfo(program, node, sourceFile);
334
+ return getHoverInfo(program, node, sourceFile, include_docs);
187
335
  }
188
336
 
189
337
  // src/cli.ts
338
+ var import_meta = {};
190
339
  var HELP = `
191
340
  prinfer - TypeScript type inference inspection tool
192
341
 
193
342
  Usage:
194
- prinfer <file.ts>[:<line>] <name> [--project <tsconfig.json>]
343
+ prinfer <file.ts>:<line>:<column> [--docs] [--project <tsconfig.json>]
195
344
  prinfer setup
196
345
 
197
346
  Commands:
198
347
  setup Install MCP server and skill for Claude Code
199
348
 
200
349
  Arguments:
201
- file.ts Path to the TypeScript file
202
- :line Optional line number to narrow search (e.g., file.ts:75)
203
- name Name of the function/variable to inspect
350
+ file.ts:line:column Path to TypeScript file with 1-based line and column
204
351
 
205
352
  Options:
353
+ --docs, -d Include JSDoc/TSDoc documentation
206
354
  --project, -p Path to tsconfig.json (optional)
207
355
  --help, -h Show this help message
208
356
 
209
357
  Examples:
210
- prinfer src/utils.ts myFunction
211
- prinfer src/utils.ts:75 commandResult
358
+ prinfer src/utils.ts:75:10
359
+ prinfer src/utils.ts:75:10 --docs
360
+ prinfer src/utils.ts:75:10 --project ./tsconfig.json
212
361
  prinfer setup
213
362
  `.trim();
214
363
  var MANUAL_SETUP = `
215
364
  Manual setup instructions:
216
365
 
217
- 1. Add MCP server to ~/.claude/settings.json:
218
-
219
- {
220
- "mcpServers": {
221
- "prinfer": {
222
- "command": "prinfer-mcp"
223
- }
224
- }
225
- }
366
+ 1. Add MCP server:
367
+ Run: claude mcp add prinfer node /path/to/prinfer-mcp
226
368
 
227
369
  2. Create skill file at ~/.claude/skills/prefer-infer.md:
228
-
229
370
  See https://github.com/clockblocker/prinfer for skill content.
230
371
  `.trim();
372
+ function getMcpBinaryPath() {
373
+ const thisScript = new URL(import_meta.url).pathname;
374
+ return import_node_path3.default.join(import_node_path3.default.dirname(thisScript), "mcp.js");
375
+ }
376
+ function installMcpServer() {
377
+ try {
378
+ const mcpPath = getMcpBinaryPath();
379
+ try {
380
+ (0, import_node_child_process.execSync)("claude mcp remove prinfer", { stdio: "pipe" });
381
+ } catch {
382
+ }
383
+ (0, import_node_child_process.execSync)(`claude mcp add prinfer node ${mcpPath}`, {
384
+ stdio: "inherit"
385
+ });
386
+ console.log("[ok] Added prinfer MCP server");
387
+ return true;
388
+ } catch (err) {
389
+ console.error(`[error] MCP setup failed: ${err.message}`);
390
+ return false;
391
+ }
392
+ }
231
393
  var SKILL_CONTENT = `# TypeScript Type Inference (prinfer)
232
394
 
233
395
  ## Coding Guideline
@@ -241,26 +403,26 @@ When writing TypeScript code, prefer relying on type inference over explicit typ
241
403
  - The type serves as documentation for complex structures
242
404
  - You're defining a public API contract
243
405
 
244
- Use the \`prinfer\` MCP tool (\`infer_type\`) to verify what TypeScript infers before adding explicit types.
406
+ Use the \`prinfer\` MCP tool (\`hover\`) to verify what TypeScript infers before adding explicit types.
245
407
 
246
408
  ## Commands
247
409
 
248
- ### /check-type
410
+ ### /hover
249
411
 
250
- Check the inferred type of a TypeScript symbol.
412
+ Check the inferred type at a specific position in a TypeScript file.
251
413
 
252
- Usage: \`/check-type <file>:<line> <name>\` or \`/check-type <file> <name>\`
414
+ Usage: \`/hover <file>:<line>:<column>\`
253
415
 
254
416
  Examples:
255
- - \`/check-type src/utils.ts:75 commandResult\`
256
- - \`/check-type src/utils.ts myFunction\`
417
+ - \`/hover src/utils.ts:75:10\`
418
+ - \`/hover src/utils.ts:42:5\`
257
419
 
258
- <command-name>check-type</command-name>
420
+ <command-name>hover</command-name>
259
421
 
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
422
+ Use the \`hover\` MCP tool to check the type:
423
+ 1. Parse the arguments to extract file, line, and column
424
+ 2. Call \`hover(file, line, column, { include_docs: true })\`
425
+ 3. Report the inferred signature, return type, and documentation
264
426
  `;
265
427
  function runSetup() {
266
428
  const homeDir = import_node_os.default.homedir();
@@ -271,33 +433,8 @@ function runSetup() {
271
433
  console.error(MANUAL_SETUP);
272
434
  process.exit(1);
273
435
  }
274
- let mcpOk = false;
436
+ const mcpOk = installMcpServer();
275
437
  let skillOk = false;
276
- const configFile = import_node_path3.default.join(claudeDir, "settings.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 settings.json");
294
- mcpOk = true;
295
- }
296
- } catch (err) {
297
- console.error(
298
- `[error] Failed to configure MCP server: ${err.message}`
299
- );
300
- }
301
438
  const skillsDir = import_node_path3.default.join(claudeDir, "skills");
302
439
  const skillFile = import_node_path3.default.join(skillsDir, "prefer-infer.md");
303
440
  try {
@@ -327,12 +464,16 @@ function runSetup() {
327
464
  process.exit(1);
328
465
  }
329
466
  }
330
- function parseFileArg(arg) {
331
- const match = arg.match(/^(.+):(\d+)$/);
467
+ function parsePositionArg(arg) {
468
+ const match = arg.match(/^(.+):(\d+):(\d+)$/);
332
469
  if (match) {
333
- return { file: match[1], line: Number.parseInt(match[2], 10) };
470
+ return {
471
+ file: match[1],
472
+ line: Number.parseInt(match[2], 10),
473
+ column: Number.parseInt(match[3], 10)
474
+ };
334
475
  }
335
- return { file: arg };
476
+ return null;
336
477
  }
337
478
  function parseArgs(argv) {
338
479
  const args = argv.slice(2);
@@ -344,16 +485,17 @@ function parseArgs(argv) {
344
485
  runSetup();
345
486
  return null;
346
487
  }
347
- const fileArg = args[0];
348
- const name = args[1];
349
- if (!fileArg || !name) {
488
+ const positionArg = args[0];
489
+ const parsed = parsePositionArg(positionArg);
490
+ if (!parsed) {
350
491
  console.error(
351
- "Error: Both <file> and <name> arguments are required.\n"
492
+ "Error: Position argument must be in format <file>:<line>:<column>\n"
352
493
  );
353
494
  console.log(HELP);
354
495
  process.exit(1);
355
496
  }
356
- const { file, line } = parseFileArg(fileArg);
497
+ const { file, line, column } = parsed;
498
+ const includeDocs = args.includes("--docs") || args.includes("-d");
357
499
  let project;
358
500
  const projectIdx = args.findIndex((a) => a === "--project" || a === "-p");
359
501
  if (projectIdx >= 0) {
@@ -364,7 +506,7 @@ function parseArgs(argv) {
364
506
  process.exit(1);
365
507
  }
366
508
  }
367
- return { file, name, line, project };
509
+ return { file, line, column, includeDocs, project };
368
510
  }
369
511
  function main() {
370
512
  const options = parseArgs(process.argv);
@@ -372,14 +514,21 @@ function main() {
372
514
  process.exit(0);
373
515
  }
374
516
  try {
375
- const result = inferType(options.file, options.name, {
376
- line: options.line,
517
+ const result = hover(options.file, options.line, options.column, {
518
+ include_docs: options.includeDocs,
377
519
  project: options.project
378
520
  });
379
521
  console.log(result.signature);
380
522
  if (result.returnType) {
381
523
  console.log("returns:", result.returnType);
382
524
  }
525
+ if (result.name) {
526
+ console.log("name:", result.name);
527
+ }
528
+ console.log("kind:", result.kind);
529
+ if (result.documentation) {
530
+ console.log("docs:", result.documentation);
531
+ }
383
532
  } catch (error) {
384
533
  console.error(error.message);
385
534
  process.exit(1);