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 +26 -21
- package/dist/cli.cjs +279 -130
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +278 -130
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +298 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -1
- package/dist/index.d.ts +66 -1
- package/dist/index.js +294 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +233 -85
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +233 -85
- package/dist/mcp.js.map +1 -1
- package/dist/postinstall.cjs +2 -25
- package/dist/postinstall.cjs.map +1 -1
- package/dist/postinstall.js +2 -25
- package/dist/postinstall.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
**Typehints for your AI agent.**
|
|
8
8
|
|
|
9
|
-
|
|
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
|
|
44
|
+
Your agent gets a `hover` tool to check what TypeScript infers at any position:
|
|
44
45
|
|
|
45
46
|
```
|
|
46
|
-
|
|
47
|
-
|
|
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 `/
|
|
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**
|
|
67
|
+
**1. Add MCP server** using the Claude CLI:
|
|
65
68
|
|
|
66
|
-
```
|
|
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
|
|
84
|
-
prinfer src/utils.ts:75
|
|
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 {
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
103
|
-
const
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
+
visit(sourceFile);
|
|
89
|
+
return result;
|
|
93
90
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
function isNamedNode(node, name) {
|
|
101
|
-
return isFunctionLikeNamed(node, name) || isVariableNamed(node, name);
|
|
141
|
+
visit(sourceFile);
|
|
142
|
+
return bestMatch;
|
|
102
143
|
}
|
|
103
|
-
function
|
|
104
|
-
const
|
|
105
|
-
|
|
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
|
|
159
|
+
return findHoverableAncestor(sourceFile, position);
|
|
108
160
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return
|
|
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
|
|
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
|
|
133
|
-
const line =
|
|
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 {
|
|
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
|
|
301
|
+
let targetNode = node;
|
|
157
302
|
if (nodeWithName.name && import_typescript.default.isIdentifier(nodeWithName.name)) {
|
|
158
|
-
|
|
303
|
+
targetNode = nodeWithName.name;
|
|
159
304
|
}
|
|
160
|
-
const t = checker.getTypeAtLocation(
|
|
161
|
-
return {
|
|
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
|
|
166
|
-
const
|
|
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 =
|
|
330
|
+
const node = findNodeAtPosition(sourceFile, line, column);
|
|
180
331
|
if (!node) {
|
|
181
|
-
|
|
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
|
|
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>[
|
|
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
|
|
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
|
|
211
|
-
prinfer src/utils.ts:75
|
|
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
|
|
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 (\`
|
|
406
|
+
Use the \`prinfer\` MCP tool (\`hover\`) to verify what TypeScript infers before adding explicit types.
|
|
245
407
|
|
|
246
408
|
## Commands
|
|
247
409
|
|
|
248
|
-
### /
|
|
410
|
+
### /hover
|
|
249
411
|
|
|
250
|
-
Check the inferred type
|
|
412
|
+
Check the inferred type at a specific position in a TypeScript file.
|
|
251
413
|
|
|
252
|
-
Usage: \`/
|
|
414
|
+
Usage: \`/hover <file>:<line>:<column>\`
|
|
253
415
|
|
|
254
416
|
Examples:
|
|
255
|
-
- \`/
|
|
256
|
-
- \`/
|
|
417
|
+
- \`/hover src/utils.ts:75:10\`
|
|
418
|
+
- \`/hover src/utils.ts:42:5\`
|
|
257
419
|
|
|
258
|
-
<command-name>
|
|
420
|
+
<command-name>hover</command-name>
|
|
259
421
|
|
|
260
|
-
Use the \`
|
|
261
|
-
1. Parse the arguments to extract file,
|
|
262
|
-
2. Call \`
|
|
263
|
-
3. Report the inferred signature
|
|
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
|
-
|
|
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
|
|
331
|
-
const match = arg.match(/^(.+):(\d+)$/);
|
|
467
|
+
function parsePositionArg(arg) {
|
|
468
|
+
const match = arg.match(/^(.+):(\d+):(\d+)$/);
|
|
332
469
|
if (match) {
|
|
333
|
-
return {
|
|
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
|
|
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
|
|
348
|
-
const
|
|
349
|
-
if (!
|
|
488
|
+
const positionArg = args[0];
|
|
489
|
+
const parsed = parsePositionArg(positionArg);
|
|
490
|
+
if (!parsed) {
|
|
350
491
|
console.error(
|
|
351
|
-
"Error:
|
|
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 } =
|
|
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,
|
|
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 =
|
|
376
|
-
|
|
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);
|