instagit 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Instalabs, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # instagit
2
+
3
+ **Let Your Agent Instantly Understand the World's Code**
4
+
5
+ An MCP server that gives coding agents instant insight into any Git repository — no guessing, no hallucination.
6
+
7
+ ## Quick Start
8
+
9
+ Add to your MCP client configuration:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "instagit": {
15
+ "command": "npx",
16
+ "args": ["-y", "instagit@latest"]
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ Works with **Claude Code**, **Claude Desktop**, **Cursor**, and any MCP-compatible client. The `@latest` tag ensures you always get the most recent version.
23
+
24
+ ## Why
25
+
26
+ Agents that integrate with external libraries are flying blind. They read docs (if they exist), guess at APIs, and hallucinate patterns that don't match the actual code. The result: broken integrations, wrong function signatures, outdated usage patterns, hours of debugging.
27
+
28
+ When an agent can actually analyze the source code of a library or service it's integrating with, everything changes. It sees the real function signatures, the actual data flow, the patterns the maintainers intended. Integration becomes dramatically easier and less error-prone because the agent is working from ground truth, not guesses.
29
+
30
+ ## What Agents Can Do With This
31
+
32
+ - **Integrate with any library correctly the first time** — "How do I set up authentication with this SDK?" gets answered from the actual code, not outdated docs or training data. Your agent sees the real constructors, the real config options, the real error types.
33
+ - **Migrate between versions without the guesswork** — Point your agent at both the old and new version of a library. It can diff the actual implementations and generate a migration plan that accounts for every breaking change.
34
+ - **Debug issues across repository boundaries** — When a bug spans your code and a dependency, your agent can read both codebases and trace the issue to its root cause — even into libraries you've never opened.
35
+ - **Generate integration code that actually works** — Instead of producing plausible-looking code that fails at runtime, your agent writes integration code based on the real API surface: actual method names, actual parameter types, actual return values.
36
+ - **Evaluate libraries before committing** — "Should we use library A or B?" Your agent can analyze both implementations, compare their approaches to error handling, test coverage, and architectural quality, and give you a grounded recommendation.
37
+ - **Onboard to unfamiliar codebases in minutes** — Point your agent at any repo and ask how things work. It answers from the code itself, with file paths and line numbers, not from memory that may be months out of date.
38
+
39
+ ## Features
40
+
41
+ - **Agent-native context** — Purpose-built for coding agents. Returns the exact context an AI needs to understand, modify, and reason about code.
42
+ - **Architectural truth** — Goes beyond keyword search. Understands how components connect, why decisions were made, and where the real complexity lives.
43
+ - **Any repo, any scale** — From weekend projects to massive monorepos. Public and private repositories, any Git host.
44
+ - **Exact source citations** — Every claim traced back to specific files and line numbers. No hallucination, no hand-waving.
45
+
46
+ ## Configuration
47
+
48
+ ### Environment Variables
49
+
50
+ | Variable | Description | Default |
51
+ |---|---|---|
52
+ | `INSTAGIT_API_KEY` | API key from [instagit.com](https://instagit.com) | Auto-registers anonymous token |
53
+ | `INSTAGIT_API_URL` | Custom API endpoint | Production API |
54
+
55
+ ### Authenticated Usage
56
+
57
+ Sign up at [instagit.com](https://instagit.com) for higher rate limits and faster analysis:
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "instagit": {
63
+ "command": "npx",
64
+ "args": ["-y", "instagit@latest"],
65
+ "env": {
66
+ "INSTAGIT_API_KEY": "ig_your_api_key_here"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Anonymous Usage
74
+
75
+ No API key required — the server automatically registers an anonymous token on first use. Anonymous tokens are stored in `~/.instagit/token.json` and shared with the Python MCP server.
76
+
77
+ ## Tool: `ask_repo`
78
+
79
+ Analyze any Git repository with AI.
80
+
81
+ **Parameters:**
82
+
83
+ | Parameter | Type | Required | Description |
84
+ |---|---|---|---|
85
+ | `repo` | string | yes | Repository URL, shorthand (`owner/repo`), or any public Git URL |
86
+ | `prompt` | string | yes | What to analyze or ask about the codebase |
87
+ | `ref` | string | no | Branch, commit SHA, or tag (default: repository's default branch) |
88
+ | `fast` | boolean | no | Use fast mode for quicker responses (default: true) |
89
+
90
+ **Example prompts:**
91
+ - "Explain the architecture and main components"
92
+ - "Review the authentication implementation for security issues"
93
+ - "How would I add a new API endpoint following existing patterns?"
94
+ - "What would it take to upgrade from React 17 to 18?"
95
+
96
+ ## Requirements
97
+
98
+ - Node.js 18+
99
+
100
+ ## License
101
+
102
+ MIT — Copyright (c) 2026 Instalabs, LLC
103
+
104
+ ---
105
+
106
+ Learn more at [instagit.com](https://instagit.com)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,496 @@
1
+ "use strict";
2
+
3
+ // src/index.ts
4
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ var import_zod = require("zod");
7
+
8
+ // src/api.ts
9
+ var import_eventsource_parser = require("eventsource-parser");
10
+
11
+ // src/kitt.ts
12
+ var WIDTH = 16;
13
+ var SHADES = ["\u2588", "\u2593", "\u2592"];
14
+ var BG = "\u2591";
15
+ function makeKittFrame(pos, direction) {
16
+ const chars = Array(WIDTH).fill(BG);
17
+ for (let offset = 0; offset < SHADES.length; offset++) {
18
+ if (offset === 0) {
19
+ chars[pos] = SHADES[offset];
20
+ } else {
21
+ const trailPos = pos - offset * direction;
22
+ if (trailPos >= 0 && trailPos < WIDTH) {
23
+ chars[trailPos] = SHADES[offset];
24
+ }
25
+ }
26
+ }
27
+ return chars.join("");
28
+ }
29
+ var forward = [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15];
30
+ var backward = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0];
31
+ var LOGO_FRAMES = [];
32
+ for (const i of forward) {
33
+ LOGO_FRAMES.push(makeKittFrame(i, 1));
34
+ }
35
+ for (const i of backward.slice(1)) {
36
+ LOGO_FRAMES.push(makeKittFrame(i, -1));
37
+ }
38
+ function formatDuration(seconds) {
39
+ if (seconds < 60) {
40
+ return `${Math.floor(seconds)}s`;
41
+ }
42
+ const minutes = Math.floor(seconds / 60);
43
+ const secs = Math.floor(seconds % 60);
44
+ return `${minutes}m ${secs}s`;
45
+ }
46
+ function formatTokens(count) {
47
+ if (count >= 1e3) {
48
+ return `${(count / 1e3).toFixed(1)}k`;
49
+ }
50
+ return String(count);
51
+ }
52
+ function createProgressTracker() {
53
+ return {
54
+ startTime: Date.now() / 1e3,
55
+ frameIndex: 0,
56
+ inputTokens: 0,
57
+ outputTokens: 0,
58
+ displayedTokens: 0,
59
+ lastStatus: "Connecting...",
60
+ done: false
61
+ };
62
+ }
63
+ function nextFrame(tracker) {
64
+ const frame = LOGO_FRAMES[tracker.frameIndex % LOGO_FRAMES.length];
65
+ tracker.frameIndex++;
66
+ return frame;
67
+ }
68
+ function elapsed(tracker) {
69
+ return formatDuration(Date.now() / 1e3 - tracker.startTime);
70
+ }
71
+ function animateTokens(tracker) {
72
+ const actual = tracker.inputTokens + tracker.outputTokens;
73
+ if (tracker.displayedTokens < actual) {
74
+ const step = Math.max(50, Math.floor((actual - tracker.displayedTokens) / 20));
75
+ tracker.displayedTokens = Math.min(tracker.displayedTokens + step, actual);
76
+ }
77
+ return tracker.displayedTokens;
78
+ }
79
+ function formatMessage(tracker, status, tokens) {
80
+ if (status) {
81
+ tracker.lastStatus = status;
82
+ }
83
+ if (tokens) {
84
+ if (tokens.input !== void 0) tracker.inputTokens = tokens.input;
85
+ if (tokens.output !== void 0) tracker.outputTokens = tokens.output;
86
+ }
87
+ const parts = [nextFrame(tracker), elapsed(tracker)];
88
+ const displayed = animateTokens(tracker);
89
+ if (displayed > 0) {
90
+ parts.push(`${formatTokens(displayed)} tokens`);
91
+ }
92
+ if (tracker.lastStatus) {
93
+ parts.push(tracker.lastStatus);
94
+ }
95
+ return parts.join(" \xB7 ");
96
+ }
97
+
98
+ // src/api.ts
99
+ var DEFAULT_API_URL = "https://instagit--instagit-api-api.modal.run";
100
+ function getApiUrl() {
101
+ return process.env.INSTAGIT_API_URL || DEFAULT_API_URL;
102
+ }
103
+ function buildModelString(repo, ref, fast) {
104
+ let model = repo;
105
+ if (fast) model += ":fast";
106
+ if (ref) model += `@${ref}`;
107
+ return model;
108
+ }
109
+ async function analyzeRepoStreaming(opts) {
110
+ const {
111
+ repo,
112
+ prompt,
113
+ ref = null,
114
+ fast = true,
115
+ token = null,
116
+ progressCallback
117
+ } = opts;
118
+ const tracker = opts.tracker ?? createProgressTracker();
119
+ const apiUrl = getApiUrl();
120
+ const model = buildModelString(repo, ref, fast);
121
+ const headers = { "Content-Type": "application/json" };
122
+ if (token) {
123
+ headers["Authorization"] = `Bearer ${token}`;
124
+ }
125
+ const payload = {
126
+ model,
127
+ input: prompt,
128
+ stream: true
129
+ };
130
+ let collectedText = "";
131
+ let usage = {};
132
+ let heartbeatInterval = null;
133
+ if (progressCallback) {
134
+ heartbeatInterval = setInterval(async () => {
135
+ if (!tracker.done) {
136
+ try {
137
+ await progressCallback(formatMessage(tracker));
138
+ } catch {
139
+ }
140
+ }
141
+ }, 250);
142
+ }
143
+ try {
144
+ const response = await fetch(`${apiUrl}/v1/responses`, {
145
+ method: "POST",
146
+ headers,
147
+ body: JSON.stringify(payload)
148
+ });
149
+ if (!response.ok) {
150
+ const errorBody = await response.text().catch(() => "");
151
+ const error = new Error(`API error: ${response.status}`);
152
+ error.status = response.status;
153
+ error.body = errorBody;
154
+ throw error;
155
+ }
156
+ if (!response.body) {
157
+ throw new Error("No response body");
158
+ }
159
+ await new Promise((resolve, reject) => {
160
+ const parser = (0, import_eventsource_parser.createParser)({
161
+ onEvent(event) {
162
+ if (event.data === "[DONE]") {
163
+ resolve();
164
+ return;
165
+ }
166
+ let data;
167
+ try {
168
+ data = JSON.parse(event.data);
169
+ } catch {
170
+ return;
171
+ }
172
+ const eventType = data.type ?? "";
173
+ if (eventType === "response.reasoning.delta") {
174
+ const status = data.delta ?? "";
175
+ if (status) tracker.lastStatus = status;
176
+ if (data.tokens) {
177
+ if (data.tokens.input !== void 0) tracker.inputTokens = data.tokens.input;
178
+ if (data.tokens.output !== void 0) tracker.outputTokens = data.tokens.output;
179
+ }
180
+ } else if (eventType === "response.output_text.delta") {
181
+ const delta = data.delta ?? "";
182
+ collectedText += delta;
183
+ if (tracker.outputTokens === 0) {
184
+ tracker.outputTokens = Math.floor(collectedText.length / 4);
185
+ }
186
+ tracker.lastStatus = "Writing response...";
187
+ } else if (eventType === "response.completed") {
188
+ usage = data.response?.usage ?? {};
189
+ }
190
+ }
191
+ });
192
+ const reader = response.body.getReader();
193
+ const decoder = new TextDecoder();
194
+ function read() {
195
+ reader.read().then(({ done, value }) => {
196
+ if (done) {
197
+ resolve();
198
+ return;
199
+ }
200
+ parser.feed(decoder.decode(value, { stream: true }));
201
+ read();
202
+ }).catch(reject);
203
+ }
204
+ read();
205
+ });
206
+ } finally {
207
+ tracker.done = true;
208
+ if (heartbeatInterval) {
209
+ clearInterval(heartbeatInterval);
210
+ }
211
+ }
212
+ return {
213
+ text: collectedText,
214
+ inputTokens: usage.input_tokens ?? 0,
215
+ outputTokens: usage.output_tokens ?? 0,
216
+ totalTokens: usage.total_tokens ?? 0,
217
+ tier: usage.tier ?? "free",
218
+ tokensRemaining: usage.tokens_remaining ?? 0,
219
+ upgradeHint: usage.upgrade_hint ?? null
220
+ };
221
+ }
222
+
223
+ // src/token.ts
224
+ var import_fs = require("fs");
225
+ var import_path = require("path");
226
+ var import_os2 = require("os");
227
+
228
+ // src/fingerprint.ts
229
+ var import_crypto = require("crypto");
230
+ var import_os = require("os");
231
+ function getMacAddress() {
232
+ const nets = (0, import_os.networkInterfaces)();
233
+ for (const name of Object.keys(nets)) {
234
+ for (const iface of nets[name] ?? []) {
235
+ if (!iface.internal && iface.mac && iface.mac !== "00:00:00:00:00:00") {
236
+ return iface.mac;
237
+ }
238
+ }
239
+ }
240
+ return "00:00:00:00:00:00";
241
+ }
242
+ function macToInt(mac) {
243
+ return BigInt("0x" + mac.replace(/:/g, "")).toString();
244
+ }
245
+ function getMachineFingerprint() {
246
+ const parts = [
247
+ (0, import_os.hostname)(),
248
+ // platform.node()
249
+ (0, import_os.platform)(),
250
+ // platform.system() — "darwin", "linux", "win32"
251
+ (0, import_os.arch)(),
252
+ // platform.machine() — "x64", "arm64"
253
+ macToInt(getMacAddress())
254
+ // uuid.getnode() equivalent
255
+ ];
256
+ const platformMap = {
257
+ darwin: "Darwin",
258
+ linux: "Linux",
259
+ win32: "Windows"
260
+ };
261
+ parts[1] = platformMap[parts[1]] ?? parts[1];
262
+ const archMap = {
263
+ x64: "x86_64",
264
+ ia32: "i686"
265
+ };
266
+ parts[2] = archMap[parts[2]] ?? parts[2];
267
+ const raw = parts.join("|");
268
+ return (0, import_crypto.createHash)("sha256").update(raw).digest("hex").slice(0, 32);
269
+ }
270
+
271
+ // src/token.ts
272
+ var CONFIG_DIR = (0, import_path.join)((0, import_os2.homedir)(), ".instagit");
273
+ var TOKEN_FILE = (0, import_path.join)(CONFIG_DIR, "token.json");
274
+ function getStoredToken() {
275
+ if (!(0, import_fs.existsSync)(TOKEN_FILE)) return null;
276
+ try {
277
+ const data = JSON.parse((0, import_fs.readFileSync)(TOKEN_FILE, "utf-8"));
278
+ return data.token ?? null;
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+ function storeToken(token) {
284
+ (0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
285
+ (0, import_fs.writeFileSync)(TOKEN_FILE, JSON.stringify({ token }));
286
+ }
287
+ function clearStoredToken() {
288
+ if ((0, import_fs.existsSync)(TOKEN_FILE)) {
289
+ (0, import_fs.unlinkSync)(TOKEN_FILE);
290
+ }
291
+ }
292
+ function getOrCreateToken() {
293
+ const apiKey = process.env.INSTAGIT_API_KEY;
294
+ if (apiKey) return apiKey;
295
+ return getStoredToken();
296
+ }
297
+ async function registerAnonymousToken(apiUrl) {
298
+ try {
299
+ const fingerprint = getMachineFingerprint();
300
+ const response = await fetch(`${apiUrl}/v1/auth/anonymous`, {
301
+ method: "POST",
302
+ headers: { "Content-Type": "application/json" },
303
+ body: JSON.stringify({ fingerprint })
304
+ });
305
+ if (response.ok) {
306
+ const data = await response.json();
307
+ const token = data.token;
308
+ if (token) {
309
+ storeToken(token);
310
+ return token;
311
+ }
312
+ }
313
+ return null;
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+
319
+ // src/index.ts
320
+ var server = new import_mcp.McpServer({
321
+ name: "instagit",
322
+ version: "0.1.0"
323
+ });
324
+ var TOOL_DESCRIPTION = `Analyze any Git repository with AI. Point it at a repo and ask questions about the codebase. Use cases include:
325
+ - Understanding unfamiliar codebases: 'Explain the architecture and main components'
326
+ - Code review assistance: 'Review the authentication implementation for security issues'
327
+ - Documentation generation: 'Document the public API of this library'
328
+ - Dependency analysis: 'What external services does this app depend on?'
329
+ - Onboarding help: 'How would I add a new API endpoint following existing patterns?'
330
+ - Bug investigation: 'Where might null pointer exceptions occur in the data pipeline?'
331
+ - Migration planning: 'What would it take to upgrade from React 17 to 18?'`;
332
+ server.tool(
333
+ "ask_repo",
334
+ TOOL_DESCRIPTION,
335
+ {
336
+ repo: import_zod.z.string().describe(
337
+ "Repository to analyze. Accepts GitHub URLs (https://github.com/owner/repo), shorthand (owner/repo), GitLab/Bitbucket URLs, or any public Git URL"
338
+ ),
339
+ prompt: import_zod.z.string().describe("What to analyze or ask about the codebase"),
340
+ ref: import_zod.z.string().nullable().optional().default(null).describe("Branch, commit SHA, or tag to analyze (default: repository's default branch)"),
341
+ fast: import_zod.z.boolean().optional().default(true).describe("Use fast mode for quicker responses")
342
+ },
343
+ async ({ repo, prompt, ref, fast }, extra) => {
344
+ const apiUrl = getApiUrl();
345
+ const tracker = createProgressTracker();
346
+ const sendProgress = async (message) => {
347
+ try {
348
+ await extra.sendNotification({
349
+ method: "notifications/progress",
350
+ params: {
351
+ progressToken: `ask_repo_${Date.now()}`,
352
+ progress: tracker.outputTokens,
353
+ message
354
+ }
355
+ });
356
+ } catch {
357
+ }
358
+ };
359
+ let token = getOrCreateToken();
360
+ if (!token) {
361
+ await sendProgress(formatMessage(tracker, "Registering anonymous token..."));
362
+ token = await registerAnonymousToken(apiUrl);
363
+ if (!token) {
364
+ return {
365
+ content: [
366
+ {
367
+ type: "text",
368
+ text: "Unable to register anonymous token. This may be because you've reached the limit of 3 tokens per IP address.\n\nTo continue using Instagit:\n1. Sign up for a free account at https://instagit.ai/signup\n2. Get an API key from your dashboard\n3. Set INSTAGIT_API_KEY in your MCP configuration"
369
+ }
370
+ ]
371
+ };
372
+ }
373
+ }
374
+ await sendProgress(formatMessage(tracker, "Connecting..."));
375
+ const callApi = async (authToken) => {
376
+ return analyzeRepoStreaming({
377
+ repo,
378
+ prompt,
379
+ ref,
380
+ fast,
381
+ token: authToken,
382
+ progressCallback: sendProgress,
383
+ tracker
384
+ });
385
+ };
386
+ let result;
387
+ try {
388
+ result = await callApi(token);
389
+ } catch (err) {
390
+ const error = err;
391
+ if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED")) {
392
+ return {
393
+ content: [
394
+ {
395
+ type: "text",
396
+ text: `Could not connect to Instagit API at ${apiUrl}. Make sure the API server is running.`
397
+ }
398
+ ]
399
+ };
400
+ }
401
+ if (error.status === 429) {
402
+ let rateLimitUntil;
403
+ try {
404
+ const errorData = JSON.parse(error.body ?? "{}");
405
+ rateLimitUntil = errorData.rate_limit_until;
406
+ } catch {
407
+ }
408
+ const resetInfo = rateLimitUntil ? `
409
+ Credits will reset at: ${rateLimitUntil}
410
+ ` : "";
411
+ return {
412
+ content: [
413
+ {
414
+ type: "text",
415
+ text: `Rate limit exceeded. Your free credits have been exhausted.
416
+ ${resetInfo}
417
+ To continue using Instagit immediately:
418
+ - Upgrade to Pro ($20/mo) for 10x more credits and faster analysis
419
+ - Visit: https://instagit.ai/pricing`
420
+ }
421
+ ]
422
+ };
423
+ }
424
+ if (error.status === 401) {
425
+ clearStoredToken();
426
+ const newToken = await registerAnonymousToken(apiUrl);
427
+ if (newToken) {
428
+ try {
429
+ result = await callApi(newToken);
430
+ } catch (retryErr) {
431
+ const retryError = retryErr;
432
+ return {
433
+ content: [
434
+ {
435
+ type: "text",
436
+ text: `API error after re-auth: ${retryError.status ?? retryError.message}`
437
+ }
438
+ ]
439
+ };
440
+ }
441
+ } else {
442
+ return {
443
+ content: [
444
+ {
445
+ type: "text",
446
+ text: "Authentication failed. Unable to register a new token.\n\nPlease set INSTAGIT_API_KEY in your MCP configuration, or visit https://instagit.ai/signup to create an account."
447
+ }
448
+ ]
449
+ };
450
+ }
451
+ }
452
+ if (!result) {
453
+ return {
454
+ content: [
455
+ {
456
+ type: "text",
457
+ text: `API error: ${error.status ?? ""} ${error.message}`
458
+ }
459
+ ]
460
+ };
461
+ }
462
+ }
463
+ let responseText = result.text;
464
+ const footerParts = [];
465
+ if (result.totalTokens > 0) {
466
+ footerParts.push(
467
+ `Tokens: ${result.inputTokens.toLocaleString()} input, ${result.outputTokens.toLocaleString()} output, ${result.totalTokens.toLocaleString()} total`
468
+ );
469
+ }
470
+ if (result.tier) {
471
+ footerParts.push(`Tier: ${result.tier}`);
472
+ }
473
+ if (result.tokensRemaining > 0) {
474
+ footerParts.push(`Credits remaining: ${result.tokensRemaining.toLocaleString()}`);
475
+ }
476
+ if (footerParts.length > 0) {
477
+ responseText += "\n\n---\n" + footerParts.join(" | ");
478
+ }
479
+ if (result.upgradeHint) {
480
+ responseText += `
481
+
482
+ ${result.upgradeHint}`;
483
+ }
484
+ return {
485
+ content: [{ type: "text", text: responseText }]
486
+ };
487
+ }
488
+ );
489
+ async function main() {
490
+ const transport = new import_stdio.StdioServerTransport();
491
+ await server.connect(transport);
492
+ }
493
+ main().catch((err) => {
494
+ console.error("Fatal error:", err);
495
+ process.exit(1);
496
+ });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "instagit",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Instagit — AI-powered Git repository analysis for coding agents",
5
+ "author": "Instalabs, LLC",
6
+ "license": "MIT",
7
+ "type": "commonjs",
8
+ "main": "dist/index.js",
9
+ "bin": {
10
+ "instagit": "./bin/instagit.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "bin"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format cjs --dts --clean",
18
+ "dev": "tsup src/index.ts --format cjs --watch",
19
+ "start": "node dist/index.js"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.12.1",
23
+ "eventsource-parser": "^3.0.1",
24
+ "zod": "^3.24.2"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.15.0",
28
+ "tsup": "^8.4.0",
29
+ "typescript": "^5.8.3"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "keywords": [
35
+ "mcp",
36
+ "git",
37
+ "repository",
38
+ "analysis",
39
+ "ai",
40
+ "claude",
41
+ "coding-agent",
42
+ "model-context-protocol"
43
+ ],
44
+ "homepage": "https://instagit.com",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/AugmentedMind/instagit-worker"
48
+ }
49
+ }