granttap-mcp 0.8.91 → 0.8.92
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/apps/bridge/src/provider-account-status/README.md +21 -4
- package/apps/bridge/src/provider-account-status/index.ts +1 -0
- package/apps/bridge/src/provider-account-status/native-usage/README.md +12 -0
- package/apps/bridge/src/provider-account-status/native-usage/credentials.ts +69 -0
- package/apps/bridge/src/provider-account-status/native-usage/index.ts +30 -0
- package/apps/bridge/src/provider-account-status/native-usage/projection.ts +52 -0
- package/apps/bridge/src/provider-account-status/native-usage/reader.ts +85 -0
- package/apps/bridge/src/provider-model-catalog/index.ts +2 -3
- package/cursor-plugin/README.md +1 -1
- package/cursor-plugin/mcp.json +1 -1
- package/cursor-plugin/stdio-bootstrap.cjs +1 -1
- package/package.json +1 -1
|
@@ -9,10 +9,27 @@ expire after five minutes and changing the native auth file clears the cache.
|
|
|
9
9
|
Only visible picker metadata and reported usage percentages, window durations,
|
|
10
10
|
reset times and observation times cross the existing encrypted Mesh channel.
|
|
11
11
|
Identity, credentials, credits, instructions and raw provider responses stay out.
|
|
12
|
-
Missing windows remain unknown.
|
|
12
|
+
Missing windows remain unknown. `native-usage/reader.ts` independently reads the
|
|
13
|
+
already signed-in Claude, Cursor and Grok account interfaces, with an eight-second
|
|
14
|
+
request bound, a 64 KiB response bound and no redirects. Requests coalesce and
|
|
15
|
+
refresh at most once every two minutes. Expired windows disappear without
|
|
16
|
+
inventing a reset allowance. `native-usage/credentials.ts` reads existing native files
|
|
17
|
+
or the native macOS keychain entries; it never starts login, refreshes credentials
|
|
18
|
+
or changes provider settings. A relocated profile cannot use another account's
|
|
19
|
+
keychain. Temporary HOME directories never read the OS user's keychain.
|
|
20
|
+
|
|
21
|
+
Claude CLI aliases remain available. The GrantTap status-line wrapper observes
|
|
13
22
|
Claude's documented `rate_limits` after a native response, preserves the prior
|
|
14
23
|
status command/options, and stores only independently present 5h/7d windows.
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
Its newest real observation takes precedence over an older direct read. Expired
|
|
25
|
+
Claude credentials require a native Claude refresh; they are not a full allowance.
|
|
26
|
+
|
|
27
|
+
Cursor reports its weekly account usage, with the native billing-period pool as
|
|
28
|
+
a fallback for older plans. Grok reports its current subscription period and
|
|
29
|
+
credit usage percentage through its CLI billing interface. Unified subscriptions
|
|
30
|
+
may share a pool across Cursor and Grok: these are provider views, not additive
|
|
31
|
+
allowances. Grok's reported percentage can be rounded by the provider. A missing
|
|
32
|
+
or unauthenticated account report stays unknown. Session tokens, context size and
|
|
33
|
+
tool activity do not stand in for subscription limits.
|
|
17
34
|
|
|
18
|
-
Tests
|
|
35
|
+
Tests live in `tests/`. GrantTap Commercial Source License, see root LICENSE.
|
|
@@ -6,6 +6,7 @@ import { codexHookRpc, type HookRpc } from "../codex-hook-trust";
|
|
|
6
6
|
import { projectAccountModels, projectAccountUsage } from "./projection";
|
|
7
7
|
|
|
8
8
|
export { projectAccountModels, projectAccountUsage } from "./projection";
|
|
9
|
+
export { cachedNativeAccountUsage } from "./native-usage";
|
|
9
10
|
|
|
10
11
|
/** A bounded read of an already signed-in provider; no threads, turns or login. */
|
|
11
12
|
export function createProviderAccountReader(rpc: HookRpc, clock = Date.now) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Native account allowances
|
|
2
|
+
|
|
3
|
+
Public entry: `index.ts`, exported by the parent Provider Account Status module.
|
|
4
|
+
Reads the existing Claude, Cursor and Grok account interfaces without creating
|
|
5
|
+
sessions or changing credentials. `projection.ts` retains only usage windows;
|
|
6
|
+
`credentials.ts` bounds local native credential reads; `reader.ts` coalesces,
|
|
7
|
+
throttles and expires the independent requests. Native status-line observations
|
|
8
|
+
keep their actual timestamps and can supersede direct Claude observations.
|
|
9
|
+
|
|
10
|
+
Tests live in `tests/`; the parent README documents data sources and boundaries.
|
|
11
|
+
|
|
12
|
+
GrantTap Commercial Source License. See the repository root LICENSE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { homedir, userInfo } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { NativeUsageProvider } from "./projection";
|
|
6
|
+
|
|
7
|
+
type Options = {
|
|
8
|
+
home?: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
platform?: NodeJS.Platform;
|
|
11
|
+
keychain?: (service: string, account?: string) => Promise<string | undefined>;
|
|
12
|
+
now?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The same existing native entries used by Claude Code and Cursor. Never refresh or write credentials. */
|
|
16
|
+
export function readNativeKeychain(service: string, account?: string, run = execFile): Promise<string | undefined> {
|
|
17
|
+
return new Promise(resolve => run("/usr/bin/security",
|
|
18
|
+
["find-generic-password", "-s", service, ...(account ? ["-a", account] : []), "-w"],
|
|
19
|
+
{ timeout: 3_000, maxBuffer: 65_536, windowsHide: true },
|
|
20
|
+
(error, stdout) => resolve(error ? undefined : stdout.trim() || undefined)));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function jsonFile(path: string): Promise<any> {
|
|
24
|
+
try {
|
|
25
|
+
if ((await stat(path)).size > 65_536) return undefined;
|
|
26
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
27
|
+
} catch { return undefined; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function token(value: unknown): string | undefined {
|
|
31
|
+
return typeof value === "string" && value.length > 0 && value.length <= 32_768
|
|
32
|
+
&& !/[\r\n]/.test(value) ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function claudeToken(value: any, now: number): string | undefined {
|
|
36
|
+
const row = value?.claudeAiOauth;
|
|
37
|
+
return typeof row?.expiresAt === "number" && row.expiresAt <= now ? undefined : token(row?.accessToken);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function readNativeCredential(provider: NativeUsageProvider, options: Options = {}): Promise<string | undefined> {
|
|
41
|
+
const home = options.home ?? homedir();
|
|
42
|
+
const env = options.env ?? process.env;
|
|
43
|
+
const platform = options.platform ?? process.platform;
|
|
44
|
+
const keychain = options.keychain ?? readNativeKeychain;
|
|
45
|
+
const keychainAllowed = options.keychain !== undefined || home === userInfo().homedir;
|
|
46
|
+
if (provider === "grok") {
|
|
47
|
+
const root = env.GRANTTAP_GROK_DIR ?? join(home, ".grok");
|
|
48
|
+
const auth = await jsonFile(join(root, "auth.json"));
|
|
49
|
+
return auth && Object.entries(auth).flatMap(([id, row]: [string, any]) =>
|
|
50
|
+
id.startsWith("https://auth.x.ai::") && row?.auth_mode === "oidc" ? [token(row.key)] : [])
|
|
51
|
+
.find(Boolean);
|
|
52
|
+
}
|
|
53
|
+
if (provider === "claude") {
|
|
54
|
+
const override = env.GRANTTAP_CLAUDE_DIR ?? env.NODVOX_CLAUDE_DIR ?? env.CLAUDE_CONFIG_DIR;
|
|
55
|
+
const saved = claudeToken(await jsonFile(join(override ?? join(home, ".claude"), ".credentials.json")), options.now ?? Date.now());
|
|
56
|
+
if (saved || platform !== "darwin" || override || !keychainAllowed) return saved;
|
|
57
|
+
if (!await jsonFile(join(home, ".claude", "settings.json"))) return undefined;
|
|
58
|
+
try {
|
|
59
|
+
const raw = await keychain("Claude Code-credentials");
|
|
60
|
+
return raw ? claudeToken(JSON.parse(raw), options.now ?? Date.now()) : undefined;
|
|
61
|
+
} catch { return undefined; }
|
|
62
|
+
}
|
|
63
|
+
const root = platform === "win32" ? join(env.APPDATA ?? join(home, "AppData", "Roaming"), "Cursor")
|
|
64
|
+
: platform === "darwin" ? join(home, ".cursor") : join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "cursor");
|
|
65
|
+
const saved = token((await jsonFile(join(root, "auth.json")))?.accessToken);
|
|
66
|
+
if (saved || platform !== "darwin" || !keychainAllowed || !await jsonFile(join(root, "cli-config.json"))) return saved;
|
|
67
|
+
try { return token(await keychain("cursor-access-token", "cursor-user")); }
|
|
68
|
+
catch { return undefined; }
|
|
69
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import type { ProviderAccountUsage } from "../../../../../packages/protocol/schema";
|
|
3
|
+
import { readClaudeUsage } from "../claude-statusline";
|
|
4
|
+
import { readNativeCredential } from "./credentials";
|
|
5
|
+
import { createNativeAccountUsageReader } from "./reader";
|
|
6
|
+
|
|
7
|
+
let scope = "";
|
|
8
|
+
let reader = createNativeAccountUsageReader({ credential: readNativeCredential });
|
|
9
|
+
|
|
10
|
+
export function mergeNativeUsage(direct: ProviderAccountUsage[], statusLine: ProviderAccountUsage[],
|
|
11
|
+
now = Date.now()): ProviderAccountUsage[] {
|
|
12
|
+
const newest = new Map<string, ProviderAccountUsage>();
|
|
13
|
+
for (const report of [...direct, ...statusLine]) {
|
|
14
|
+
const key = `${report.provider}\u001f${report.limitId}`;
|
|
15
|
+
const windows = report.windows.filter(window => window.resetsAt > now);
|
|
16
|
+
if (!windows.length || now - report.observedAt > 300_000 || report.observedAt > now + 60_000) continue;
|
|
17
|
+
if ((newest.get(key)?.observedAt ?? -Infinity) <= report.observedAt) newest.set(key, { ...report, windows });
|
|
18
|
+
}
|
|
19
|
+
return [...newest.values()];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Account reads are scheduled beside the catalog; provider credentials never enter its result. */
|
|
23
|
+
export function cachedNativeAccountUsage(): ProviderAccountUsage[] {
|
|
24
|
+
const env = process.env;
|
|
25
|
+
const current = [homedir(), env.GRANTTAP_CLAUDE_DIR, env.NODVOX_CLAUDE_DIR, env.CLAUDE_CONFIG_DIR,
|
|
26
|
+
env.GRANTTAP_GROK_DIR, env.XDG_CONFIG_HOME, env.APPDATA].join("\u001f");
|
|
27
|
+
if (scope !== current) { scope = current; reader = createNativeAccountUsageReader({ credential: readNativeCredential }); }
|
|
28
|
+
void reader.refresh();
|
|
29
|
+
return mergeNativeUsage(reader.current(), readClaudeUsage());
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ProviderAccountUsage } from "../../../../../packages/protocol/schema";
|
|
2
|
+
|
|
3
|
+
type ProviderUsageWindow = ProviderAccountUsage["windows"][number];
|
|
4
|
+
|
|
5
|
+
export type NativeUsageProvider = "claude" | "cursor" | "grok";
|
|
6
|
+
|
|
7
|
+
function percent(value: unknown): value is number {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function timestamp(value: unknown): number {
|
|
12
|
+
return typeof value === "string" ? Date.parse(value) : NaN;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Only allowance windows cross the wire; identity, money and top-up settings never do. */
|
|
16
|
+
export function projectNativeAccountUsage(provider: NativeUsageProvider, input: any,
|
|
17
|
+
now = Date.now()): ProviderAccountUsage | undefined {
|
|
18
|
+
let windows: ProviderUsageWindow[] = [];
|
|
19
|
+
if (provider === "claude") {
|
|
20
|
+
windows = ([["five_hour", "primary", 300], ["seven_day", "secondary", 10_080]] as const)
|
|
21
|
+
.flatMap(([key, id, windowMinutes]) => {
|
|
22
|
+
const row = input?.[key];
|
|
23
|
+
const resetsAt = timestamp(row?.resets_at);
|
|
24
|
+
return percent(row?.utilization) && resetsAt > now
|
|
25
|
+
? [{ id, usedPercent: row.utilization, windowMinutes, resetsAt }] : [];
|
|
26
|
+
});
|
|
27
|
+
} else {
|
|
28
|
+
const start = timestamp(provider === "cursor" ? input?.currentPeriodStart : input?.config?.currentPeriod?.start);
|
|
29
|
+
const resetsAt = timestamp(provider === "cursor" ? input?.nextResetTimestampUtc : input?.config?.currentPeriod?.end);
|
|
30
|
+
const usedPercent = provider === "cursor" ? input?.usagePercent : input?.config?.creditUsagePercent;
|
|
31
|
+
if (percent(usedPercent) && Number.isFinite(start) && start <= now && resetsAt > now && resetsAt > start) {
|
|
32
|
+
const windowMinutes = Math.ceil((resetsAt - start) / 60_000);
|
|
33
|
+
if (Number.isSafeInteger(windowMinutes) && windowMinutes > 0) {
|
|
34
|
+
windows = [{ id: "primary", usedPercent, windowMinutes, resetsAt }];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return windows.length ? { provider, limitId: provider, observedAt: now, windows } : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Older Cursor plans report a billing-period pool rather than a weekly pool. */
|
|
42
|
+
export function projectCursorPeriodUsage(input: any, now = Date.now()): ProviderAccountUsage | undefined {
|
|
43
|
+
const start = Number(input?.billingCycleStart), end = Number(input?.billingCycleEnd);
|
|
44
|
+
const plan = input?.planUsage;
|
|
45
|
+
const usedPercent = percent(plan?.totalPercentUsed) ? plan.totalPercentUsed
|
|
46
|
+
: Number.isFinite(plan?.totalSpend) && plan.totalSpend >= 0 && Number.isFinite(plan?.limit) && plan.limit > 0
|
|
47
|
+
? plan.totalSpend / plan.limit * 100 : undefined;
|
|
48
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= start
|
|
49
|
+
|| start > 8.64e15 || end > 8.64e15) return undefined;
|
|
50
|
+
return projectNativeAccountUsage("cursor", { usagePercent: usedPercent,
|
|
51
|
+
currentPeriodStart: new Date(start).toISOString(), nextResetTimestampUtc: new Date(end).toISOString() }, now);
|
|
52
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ProviderAccountUsage } from "../../../../../packages/protocol/schema";
|
|
2
|
+
import { projectCursorPeriodUsage, projectNativeAccountUsage, type NativeUsageProvider } from "./projection";
|
|
3
|
+
|
|
4
|
+
type Options = {
|
|
5
|
+
credential: (provider: NativeUsageProvider) => Promise<string | undefined>;
|
|
6
|
+
fetcher?: (url: string, init: RequestInit) => Promise<Response>;
|
|
7
|
+
clock?: () => number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const requests = {
|
|
11
|
+
claude: { url: "https://api.anthropic.com/api/oauth/usage", method: "GET" },
|
|
12
|
+
cursor: { url: "https://api2.cursor.sh/aiserver.v1.DashboardService/GetSandUsageStatus", method: "POST" },
|
|
13
|
+
grok: { url: "https://cli-chat-proxy.grok.com/v1/billing?format=credits", method: "GET" },
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
async function boundedResponse(response: Response): Promise<unknown> {
|
|
17
|
+
if (!response.ok || !response.body) return undefined;
|
|
18
|
+
const reader = response.body.getReader();
|
|
19
|
+
const chunks: Uint8Array[] = [];
|
|
20
|
+
let length = 0;
|
|
21
|
+
try {
|
|
22
|
+
while (true) {
|
|
23
|
+
const chunk = await reader.read();
|
|
24
|
+
if (chunk.done) break;
|
|
25
|
+
length += chunk.value.byteLength;
|
|
26
|
+
if (length > 65_536) return undefined;
|
|
27
|
+
chunks.push(chunk.value);
|
|
28
|
+
}
|
|
29
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
30
|
+
} finally { await reader.cancel(); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Independent, bounded account reads. A catalog never waits for a native login or a turn. */
|
|
34
|
+
export function createNativeAccountUsageReader(options: Options) {
|
|
35
|
+
const clock = options.clock ?? Date.now;
|
|
36
|
+
const fetcher = options.fetcher ?? fetch;
|
|
37
|
+
const reports = new Map<NativeUsageProvider, ProviderAccountUsage>();
|
|
38
|
+
let pending: Promise<void> | undefined;
|
|
39
|
+
let attemptedAt = -Infinity;
|
|
40
|
+
|
|
41
|
+
async function update(provider: NativeUsageProvider): Promise<void> {
|
|
42
|
+
try {
|
|
43
|
+
const token = await options.credential(provider);
|
|
44
|
+
if (!token) { reports.delete(provider); return; }
|
|
45
|
+
const request = requests[provider];
|
|
46
|
+
const response = await fetcher(request.url, {
|
|
47
|
+
method: request.method, redirect: "error", signal: AbortSignal.timeout(8_000),
|
|
48
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json",
|
|
49
|
+
...(provider === "claude" ? { "anthropic-beta": "oauth-2025-04-20" } : {}),
|
|
50
|
+
...(provider === "cursor" ? { "Connect-Protocol-Version": "1" } : {}),
|
|
51
|
+
},
|
|
52
|
+
...(request.method === "POST" ? { body: "{}" } : {}),
|
|
53
|
+
});
|
|
54
|
+
const value = await boundedResponse(response);
|
|
55
|
+
let report = projectNativeAccountUsage(provider, value, clock());
|
|
56
|
+
if (provider === "cursor" && !report && value !== undefined) {
|
|
57
|
+
const legacy = await fetcher("https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
58
|
+
method: "POST", redirect: "error", signal: AbortSignal.timeout(8_000), body: "{}",
|
|
59
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
60
|
+
});
|
|
61
|
+
report = projectCursorPeriodUsage(await boundedResponse(legacy), clock());
|
|
62
|
+
}
|
|
63
|
+
if (report) reports.set(provider, report);
|
|
64
|
+
else reports.delete(provider);
|
|
65
|
+
} catch { reports.delete(provider); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
current(): ProviderAccountUsage[] {
|
|
70
|
+
const now = clock();
|
|
71
|
+
return [...reports.values()].flatMap(report => {
|
|
72
|
+
const windows = report.windows.filter(window => window.resetsAt > now);
|
|
73
|
+
return now - report.observedAt <= 300_000 && windows.length ? [{ ...report, windows }] : [];
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
refresh(): Promise<void> {
|
|
77
|
+
if (pending) return pending;
|
|
78
|
+
if (clock() - attemptedAt < 120_000) return Promise.resolve();
|
|
79
|
+
attemptedAt = clock();
|
|
80
|
+
pending = Promise.allSettled((Object.keys(requests) as NativeUsageProvider[]).map(update))
|
|
81
|
+
.then(() => undefined).finally(() => { pending = undefined; });
|
|
82
|
+
return pending;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -3,9 +3,8 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import type { AdvertisedModel, EndpointModelCatalog } from "../../../../packages/protocol/schema";
|
|
5
5
|
|
|
6
|
-
import { readClaudeUsage } from "../provider-account-status/claude-statusline";
|
|
7
6
|
import { cachedCLIModels } from "./cli-models";
|
|
8
|
-
import { cachedProviderAccount } from "../provider-account-status";
|
|
7
|
+
import { cachedNativeAccountUsage, cachedProviderAccount } from "../provider-account-status";
|
|
9
8
|
|
|
10
9
|
const MAX_BYTES = 2_000_000;
|
|
11
10
|
const MAX_AGE = 24 * 60 * 60_000;
|
|
@@ -82,6 +81,6 @@ export function readProviderModelCatalog(endpointId: string): EndpointModelCatal
|
|
|
82
81
|
const codex = readCodexModelCatalog(endpointId);
|
|
83
82
|
const cli = cachedCLIModels(endpointId);
|
|
84
83
|
return { ...codex, observedAt: Math.max(codex.observedAt, ...cli.map(row => row.observedAt)),
|
|
85
|
-
accountUsage: [...(codex.accountUsage ?? []), ...
|
|
84
|
+
accountUsage: [...(codex.accountUsage ?? []), ...cachedNativeAccountUsage()],
|
|
86
85
|
stale: codex.stale === true && cli.length === 0, models: [...codex.models, ...cli].slice(0, 256) };
|
|
87
86
|
}
|
package/cursor-plugin/README.md
CHANGED
|
@@ -10,7 +10,7 @@ keeps each phone response tied to the exact originating chat and prompt.
|
|
|
10
10
|
Install the supported CLI on this computer, then run:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g granttap-mcp@0.8.
|
|
13
|
+
npm install -g granttap-mcp@0.8.92
|
|
14
14
|
granttap setup
|
|
15
15
|
granttap status
|
|
16
16
|
```
|
package/cursor-plugin/mcp.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"command": "node",
|
|
5
5
|
"args": [
|
|
6
6
|
"-e",
|
|
7
|
-
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.
|
|
7
|
+
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.92\";\nconst dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === \"1\";\nconst platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM\n ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;\nconst nodeRoot = dryRun && process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT\n ? process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT : dirname(process.execPath);\nconst pathRoots = (process.env.PATH || \"\").split(delimiter).filter(Boolean);\nconst appData = process.env.APPDATA;\nconst roots = [nodeRoot, ...pathRoots, ...(appData ? [join(appData, \"npm\")] : [])];\nconst first = (paths) => paths.find((path) => existsSync(path));\n\nfunction launch() {\n if (platform !== \"win32\") {\n const installed = first(roots.map((root) => join(root, \"granttap-mcp\")));\n if (installed) return { command: installed, args: [] };\n const npx = first(roots.map((root) => join(root, \"npx\"))) || \"npx\";\n return { command: npx, args: [\"-y\", PACKAGE] };\n }\n const installed = first(roots.map((root) =>\n join(root, \"node_modules\", \"granttap-mcp\", \"bin\", \"granttap-mcp.mjs\")));\n if (installed) return { command: process.execPath, args: [installed] };\n const npx = first(roots.map((root) =>\n join(root, \"node_modules\", \"npm\", \"bin\", \"npx-cli.js\")));\n if (npx) return { command: process.execPath, args: [npx, \"-y\", PACKAGE] };\n throw new Error(\"Node.js/npm 22.13.0+ is required on this Windows device. Install npm and retry GrantTap.\");\n}\n\nlet target;\ntry { target = launch(); }\ncatch (error) {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n}\n\nif (dryRun) {\n process.stdout.write(JSON.stringify(target));\n process.exit(0);\n}\n\nconst child = spawn(target.command, target.args, { stdio: \"inherit\", windowsHide: true });\nchild.on(\"error\", (error) => {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n});\nchild.on(\"exit\", (code, signal) => {\n if (signal && platform !== \"win32\") {\n try { process.kill(process.pid, signal); } catch { process.exit(code ?? 1); }\n return;\n }\n process.exit(code ?? 1);\n});\n"
|
|
8
8
|
]
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -7,7 +7,7 @@ const { spawn } = require("node:child_process");
|
|
|
7
7
|
const { existsSync } = require("node:fs");
|
|
8
8
|
const { delimiter, dirname, join } = require("node:path");
|
|
9
9
|
|
|
10
|
-
const PACKAGE = "granttap-mcp@0.8.
|
|
10
|
+
const PACKAGE = "granttap-mcp@0.8.92";
|
|
11
11
|
const dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === "1";
|
|
12
12
|
const platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM
|
|
13
13
|
? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;
|