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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +190 -79
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=instagit&config=eyJjb21tYW5kIjoibnB4IC15IGluc3RhZ2l0QGxhdGVzdCJ9)
2
+
1
3
  # Instagit
2
4
 
3
- **Let Your Agents Instantly Understand the World's Code**
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
- const response = await fetch(`${apiUrl}/v1/responses`, {
143
- method: "POST",
144
- headers,
145
- body: JSON.stringify(payload)
146
- });
147
- if (!response.ok) {
148
- const errorBody = await response.text().catch(() => "");
149
- const error = new Error(`API error: ${response.status}`);
150
- error.status = response.status;
151
- error.body = errorBody;
152
- throw error;
153
- }
154
- if (!response.body) {
155
- throw new Error("No response body");
156
- }
157
- await new Promise((resolve, reject) => {
158
- const parser = (0, import_eventsource_parser.createParser)({
159
- onEvent(event) {
160
- if (event.data === "[DONE]") {
161
- resolve();
162
- return;
163
- }
164
- let data;
165
- try {
166
- data = JSON.parse(event.data);
167
- } catch {
168
- return;
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
- const eventType = data.type ?? "";
171
- if (eventType === "response.reasoning.delta") {
172
- const status = data.delta ?? "";
173
- if (status) tracker.lastStatus = status;
174
- if (data.tokens) {
175
- if (data.tokens.input !== void 0) tracker.inputTokens = data.tokens.input;
176
- if (data.tokens.output !== void 0) tracker.outputTokens = data.tokens.output;
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
- } else if (eventType === "response.output_text.delta") {
179
- const delta = data.delta ?? "";
180
- collectedText += delta;
181
- if (tracker.outputTokens === 0) {
182
- tracker.outputTokens = Math.floor(collectedText.length / 4);
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
- tracker.lastStatus = "Writing response...";
185
- } else if (eventType === "response.completed") {
186
- usage = data.response?.usage ?? {};
261
+ await sleep(getRetryDelay(attempt));
262
+ continue;
187
263
  }
264
+ throw error;
188
265
  }
189
- });
190
- const reader = response.body.getReader();
191
- const decoder = new TextDecoder();
192
- function read() {
193
- reader.read().then(({ done, value }) => {
194
- if (done) {
195
- resolve();
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
- parser.feed(decoder.decode(value, { stream: true }));
199
- read();
200
- }).catch(reject);
274
+ throw error;
275
+ }
276
+ throw error;
201
277
  }
202
- read();
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
- try {
297
- const fingerprint = getMachineFingerprint();
298
- const response = await fetch(`${apiUrl}/v1/auth/anonymous`, {
299
- method: "POST",
300
- headers: { "Content-Type": "application/json" },
301
- body: JSON.stringify({ fingerprint })
302
- });
303
- if (response.ok) {
304
- const data = await response.json();
305
- const token = data.token;
306
- if (token) {
307
- storeToken(token);
308
- return token;
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. Use cases include:
323
- - Understanding unfamiliar codebases: 'Explain the architecture and main components'
324
- - Code review assistance: 'Review the authentication implementation for security issues'
325
- - Documentation generation: 'Document the public API of this library'
326
- - Dependency analysis: 'What external services does this app depend on?'
327
- - Onboarding help: 'How would I add a new API endpoint following existing patterns?'
328
- - Bug investigation: 'Where might null pointer exceptions occur in the data pipeline?'
329
- - Migration planning: 'What would it take to upgrade from React 17 to 18?'
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instagit",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "mcpName": "io.github.instagitai/instagit",
5
5
  "description": "MCP server for Instagit — AI-powered Git repository analysis for coding agents",
6
6
  "author": "Instalabs, LLC",