instagit 0.1.5 → 0.1.6
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 +3 -1
- package/dist/index.js +190 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
[](https://cursor.com/en-US/install-mcp?name=instagit&config=eyJjb21tYW5kIjoibnB4IC15IGluc3RhZ2l0QGxhdGVzdCJ9)
|
|
2
|
+
|
|
1
3
|
# Instagit
|
|
2
4
|
|
|
3
|
-
**Let Your Agents Instantly Understand
|
|
5
|
+
**Let Your Agents Instantly Understand Any GitHub Repo**
|
|
4
6
|
|
|
5
7
|
An MCP server that gives coding agents instant insight into any Git repository — no guessing, no hallucination.
|
|
6
8
|
|
package/dist/index.js
CHANGED
|
@@ -95,6 +95,34 @@ function formatMessage(tracker, status, tokens) {
|
|
|
95
95
|
return parts.join(" \xB7 ");
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// src/retry.ts
|
|
99
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([303, 429, 502, 503, 504]);
|
|
100
|
+
var MAX_RETRIES = 3;
|
|
101
|
+
var RETRY_BASE_DELAY = 5;
|
|
102
|
+
var FETCH_TIMEOUT = 30 * 60 * 1e3;
|
|
103
|
+
function sleep(ms) {
|
|
104
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
105
|
+
}
|
|
106
|
+
var TRANSPORT_ERROR_PATTERNS = [
|
|
107
|
+
"incomplete chunked read",
|
|
108
|
+
"peer closed connection",
|
|
109
|
+
"connection reset",
|
|
110
|
+
"timed out",
|
|
111
|
+
"fetch failed",
|
|
112
|
+
"ECONNREFUSED"
|
|
113
|
+
];
|
|
114
|
+
function isTransportError(error) {
|
|
115
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
116
|
+
const lower = message.toLowerCase();
|
|
117
|
+
return TRANSPORT_ERROR_PATTERNS.some((p) => lower.includes(p.toLowerCase()));
|
|
118
|
+
}
|
|
119
|
+
function isSecurityRejection(text) {
|
|
120
|
+
return text.length < 100 && text.toLowerCase().includes("security validation");
|
|
121
|
+
}
|
|
122
|
+
function getRetryDelay(attempt) {
|
|
123
|
+
return RETRY_BASE_DELAY * 2 ** attempt * 1e3;
|
|
124
|
+
}
|
|
125
|
+
|
|
98
126
|
// src/api.ts
|
|
99
127
|
var DEFAULT_API_URL = "https://instagit--instagit-api-api.modal.run";
|
|
100
128
|
function getApiUrl() {
|
|
@@ -139,68 +167,115 @@ async function analyzeRepoStreaming(opts) {
|
|
|
139
167
|
}, 250);
|
|
140
168
|
}
|
|
141
169
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
171
|
+
collectedText = "";
|
|
172
|
+
usage = {};
|
|
173
|
+
try {
|
|
174
|
+
const response = await fetch(`${apiUrl}/v1/responses`, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers,
|
|
177
|
+
body: JSON.stringify(payload),
|
|
178
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT)
|
|
179
|
+
});
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
const errorBody = await response.text().catch(() => "");
|
|
182
|
+
const error = new Error(`API error: ${response.status}`);
|
|
183
|
+
error.status = response.status;
|
|
184
|
+
error.body = errorBody;
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
if (!response.body) {
|
|
188
|
+
throw new Error("No response body");
|
|
189
|
+
}
|
|
190
|
+
await new Promise((resolve, reject) => {
|
|
191
|
+
const parser = (0, import_eventsource_parser.createParser)({
|
|
192
|
+
onEvent(event) {
|
|
193
|
+
if (event.data === "[DONE]") {
|
|
194
|
+
resolve();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let data;
|
|
198
|
+
try {
|
|
199
|
+
data = JSON.parse(event.data);
|
|
200
|
+
} catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const eventType = data.type ?? "";
|
|
204
|
+
if (eventType === "response.reasoning.delta") {
|
|
205
|
+
const status = data.delta ?? "";
|
|
206
|
+
if (status) tracker.lastStatus = status;
|
|
207
|
+
if (data.tokens) {
|
|
208
|
+
if (data.tokens.input !== void 0) tracker.inputTokens = data.tokens.input;
|
|
209
|
+
if (data.tokens.output !== void 0) tracker.outputTokens = data.tokens.output;
|
|
210
|
+
}
|
|
211
|
+
} else if (eventType === "response.output_text.delta") {
|
|
212
|
+
const delta = data.delta ?? "";
|
|
213
|
+
collectedText += delta;
|
|
214
|
+
if (tracker.outputTokens === 0) {
|
|
215
|
+
tracker.outputTokens = Math.floor(collectedText.length / 4);
|
|
216
|
+
}
|
|
217
|
+
tracker.lastStatus = "Writing response...";
|
|
218
|
+
} else if (eventType === "response.completed") {
|
|
219
|
+
usage = data.response?.usage ?? {};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
const reader = response.body.getReader();
|
|
224
|
+
const decoder = new TextDecoder();
|
|
225
|
+
function read() {
|
|
226
|
+
reader.read().then(({ done, value }) => {
|
|
227
|
+
if (done) {
|
|
228
|
+
resolve();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
232
|
+
read();
|
|
233
|
+
}).catch(reject);
|
|
169
234
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
235
|
+
read();
|
|
236
|
+
});
|
|
237
|
+
if (isSecurityRejection(collectedText)) {
|
|
238
|
+
throw new Error(`Request blocked: ${collectedText.slice(0, 200)}`);
|
|
239
|
+
}
|
|
240
|
+
if (!collectedText) {
|
|
241
|
+
if (attempt < MAX_RETRIES) {
|
|
242
|
+
if (progressCallback) {
|
|
243
|
+
tracker.lastStatus = `Retrying... (attempt ${attempt + 2}/${MAX_RETRIES + 1})`;
|
|
177
244
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
245
|
+
await sleep(getRetryDelay(attempt));
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
throw new Error("Empty response after retries");
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error instanceof Error && error.message.startsWith("Request blocked:")) {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
const statusError = error;
|
|
256
|
+
if (statusError.status && RETRYABLE_STATUS_CODES.has(statusError.status)) {
|
|
257
|
+
if (attempt < MAX_RETRIES) {
|
|
258
|
+
if (progressCallback) {
|
|
259
|
+
tracker.lastStatus = `Retrying... (attempt ${attempt + 2}/${MAX_RETRIES + 1})`;
|
|
183
260
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
usage = data.response?.usage ?? {};
|
|
261
|
+
await sleep(getRetryDelay(attempt));
|
|
262
|
+
continue;
|
|
187
263
|
}
|
|
264
|
+
throw error;
|
|
188
265
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return;
|
|
266
|
+
if (isTransportError(error)) {
|
|
267
|
+
if (attempt < MAX_RETRIES) {
|
|
268
|
+
if (progressCallback) {
|
|
269
|
+
tracker.lastStatus = `Retrying... (attempt ${attempt + 2}/${MAX_RETRIES + 1})`;
|
|
270
|
+
}
|
|
271
|
+
await sleep(getRetryDelay(attempt));
|
|
272
|
+
continue;
|
|
197
273
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
throw error;
|
|
201
277
|
}
|
|
202
|
-
|
|
203
|
-
});
|
|
278
|
+
}
|
|
204
279
|
} finally {
|
|
205
280
|
tracker.done = true;
|
|
206
281
|
if (heartbeatInterval) {
|
|
@@ -293,25 +368,40 @@ function getOrCreateToken() {
|
|
|
293
368
|
return getStoredToken();
|
|
294
369
|
}
|
|
295
370
|
async function registerAnonymousToken(apiUrl) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
371
|
+
const fingerprint = getMachineFingerprint();
|
|
372
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
373
|
+
try {
|
|
374
|
+
const response = await fetch(`${apiUrl}/v1/auth/anonymous`, {
|
|
375
|
+
method: "POST",
|
|
376
|
+
headers: { "Content-Type": "application/json" },
|
|
377
|
+
body: JSON.stringify({ fingerprint }),
|
|
378
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT)
|
|
379
|
+
});
|
|
380
|
+
if (response.ok) {
|
|
381
|
+
const data = await response.json();
|
|
382
|
+
const token = data.token;
|
|
383
|
+
if (token) {
|
|
384
|
+
storeToken(token);
|
|
385
|
+
return token;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (RETRYABLE_STATUS_CODES.has(response.status)) {
|
|
389
|
+
await response.text().catch(() => "");
|
|
390
|
+
if (attempt < MAX_RETRIES) {
|
|
391
|
+
await sleep(getRetryDelay(attempt));
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return null;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (isTransportError(error) && attempt < MAX_RETRIES) {
|
|
398
|
+
await sleep(getRetryDelay(attempt));
|
|
399
|
+
continue;
|
|
309
400
|
}
|
|
401
|
+
return null;
|
|
310
402
|
}
|
|
311
|
-
return null;
|
|
312
|
-
} catch {
|
|
313
|
-
return null;
|
|
314
403
|
}
|
|
404
|
+
return null;
|
|
315
405
|
}
|
|
316
406
|
|
|
317
407
|
// src/index.ts
|
|
@@ -319,14 +409,35 @@ var server = new import_mcp.McpServer({
|
|
|
319
409
|
name: "instagit",
|
|
320
410
|
version: "0.1.5"
|
|
321
411
|
});
|
|
322
|
-
var TOOL_DESCRIPTION = `Analyze any Git repository with AI. Point it at a repo and ask questions about the codebase.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
|
|
412
|
+
var TOOL_DESCRIPTION = `Analyze any Git repository with AI. Point it at a repo and ask questions about the codebase.
|
|
413
|
+
|
|
414
|
+
Example prompts by use case:
|
|
415
|
+
|
|
416
|
+
Understanding Architecture:
|
|
417
|
+
- repo: "nginx/nginx", prompt: "How does nginx handle concurrent connections? Walk through the event loop, worker process model, and connection state transitions."
|
|
418
|
+
|
|
419
|
+
Integration and API Usage:
|
|
420
|
+
- repo: "hashicorp/terraform", prompt: "How do I implement a custom provider? What interfaces does the SDK expose, how are CRUD operations mapped to the resource lifecycle?"
|
|
421
|
+
|
|
422
|
+
Debugging and Troubleshooting:
|
|
423
|
+
- repo: "docker/compose", prompt: "How does Compose resolve service dependencies and startup order? What happens with depends_on and health check conditions?"
|
|
424
|
+
|
|
425
|
+
Security Review:
|
|
426
|
+
- repo: "redis/redis", prompt: "Review the ACL security model. How are per-user command permissions enforced, and how does AUTH prevent privilege escalation?"
|
|
427
|
+
|
|
428
|
+
Code Quality and Evaluation:
|
|
429
|
+
- repo: "vitejs/vite", prompt: "How does Vite's plugin system compare to Rollup's? What are the Vite-specific hooks and tradeoffs?"
|
|
430
|
+
|
|
431
|
+
Deep Technical Analysis:
|
|
432
|
+
- repo: "ggml-org/llama.cpp", prompt: "How does the KV cache work during autoregressive generation? How are past key-value pairs stored, reused, and evicted?"
|
|
433
|
+
|
|
434
|
+
Migration Planning (with ref \u2014 Pro/Max plans only):
|
|
435
|
+
- repo: "mui/material-ui", ref: "v4.12.0", prompt: "Document the Button component's full API surface \u2014 every prop, its type, default value, and behavior."
|
|
436
|
+
- repo: "mui/material-ui", ref: "v5.0.0", prompt: "Document the Button component's full API surface \u2014 every prop, its type, default value, and behavior."
|
|
437
|
+
(Compare both results to build a migration guide between v4 and v5)
|
|
438
|
+
- repo: "kubernetes/kubernetes", ref: "release-1.29", prompt: "How does the scheduler's scoring and filtering pipeline work for pod placement?"
|
|
439
|
+
|
|
440
|
+
Ask detailed, specific questions \u2014 the tool returns real function signatures, parameter types, return values, and source citations with exact file paths and line numbers.
|
|
330
441
|
|
|
331
442
|
If you hit a rate limit (429), the user's monthly token credits are exhausted \u2014 they can wait for the reset or upgrade their plan. Free-tier repos larger than 2 GB will be rejected with a 413 error; suggest upgrading to Pro or Max for unlimited repo size.`;
|
|
332
443
|
server.tool(
|
package/package.json
CHANGED