granttap-mcp 0.8.65 → 0.8.66

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.
@@ -67,7 +67,7 @@ async function startNewAgentTask(input: {
67
67
  grok: createGrokSession,
68
68
  }[agent];
69
69
  const result = await create(
70
- message.text, requestedCwd, DELIVERY_TIMEOUT_MS, resolved.attachments, message.model,
70
+ message.text, requestedCwd, DELIVERY_TIMEOUT_MS, resolved.attachments, message.model, undefined, message.effort,
71
71
  );
72
72
  if (result.ok) {
73
73
  await say(result.text, result.sessionId, true);
@@ -1,5 +1,8 @@
1
1
  import type { AdvertisedModel, ProviderAccountUsage } from "../../../../packages/protocol/schema";
2
2
 
3
+ const EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"] as const;
4
+ type Effort = typeof EFFORTS[number];
5
+
3
6
  const ID = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/;
4
7
  const text = (value: unknown) => typeof value === "string"
5
8
  ? value.replace(/[\x00-\x1f\x7f]/g, " ").trim().slice(0, 240) || undefined : undefined;
@@ -11,7 +14,10 @@ export function projectAccountModels(value: any, endpointId: string, now: number
11
14
  for (const item of value.data) {
12
15
  if (item?.hidden === true || typeof item?.model !== "string" || !ID.test(item.model)) continue;
13
16
  rows.set(item.model, { modelId: item.model, endpointId, provider: "codex", source: "advertised",
14
- label: text(item.displayName), description: text(item.description), priority: rows.size, observedAt: now });
17
+ label: text(item.displayName), description: text(item.description), priority: rows.size, observedAt: now,
18
+ ...(Array.isArray(item.supportedReasoningEfforts) ? { supportedEfforts:
19
+ [...new Set<Effort>(item.supportedReasoningEfforts.map((x: any) => x?.reasoningEffort)
20
+ .filter((x: unknown): x is Effort => EFFORTS.includes(x as Effort)))] } : {}) });
15
21
  if (rows.size === 64) break;
16
22
  }
17
23
  return [...rows.values()];
@@ -14,3 +14,7 @@ resources that outlive the agent process need their own cancellation receipts.
14
14
 
15
15
  License: this module is distributed under the Apache License 2.0
16
16
  in the repository-root `LICENSE` file.
17
+
18
+ Model reasoning effort reaches resumed and new provider turns as native arguments.
19
+ Codex uses `model_reasoning_effort`; Claude uses `--effort`; Grok uses
20
+ `--reasoning-effort`. Cursor effort variants are native model IDs.
@@ -4,7 +4,8 @@ import { join } from "node:path";
4
4
  import type { SessionInfo, UserAttachment } from "../../../../packages/protocol/schema";
5
5
  import type { CodingAgent } from "../../../../packages/protocol/schema";
6
6
  import { explainClaudeFailure, resolveClaudeBinary } from "../providers/claude-bin";
7
- import { resolveCodexBinary } from "../providers/codex-bin";
7
+ import { runCodex, runCodexNew } from "./process/codex-headless";
8
+ import { providerEffortArgs } from "./process/provider-effort";
8
9
  import { configDir, isSessionPaused, loadRuntimeConfig, PAUSED_SESSION_REASON } from "../config";
9
10
  import { withAttachments } from "./payload/attachments";
10
11
  import { abortProcesses, runProcess } from "./process";
@@ -65,10 +66,11 @@ export function createCodexSession(
65
66
  attachments: UserAttachment[] = [],
66
67
  model?: string,
67
68
  operationKey?: string,
69
+ effort?: string,
68
70
  ): Promise<ReplyResult> {
69
71
  const workspace = resolveAgentWorkspace(cwd, "codex");
70
72
  return queued("__new_codex_task__", () => withAttachments(attachments, text, (prepared) =>
71
- runCodexNew(prepared.prompt, workspace, timeoutMs, prepared.images, model, operationKey),
73
+ runCodexNew(prepared.prompt, workspace, timeoutMs, prepared.images, model, operationKey, effort),
72
74
  ));
73
75
  }
74
76
 
@@ -80,10 +82,11 @@ export function createClaudeSession(
80
82
  attachments: UserAttachment[] = [],
81
83
  model?: string,
82
84
  operationKey?: string,
85
+ effort?: string,
83
86
  ): Promise<ReplyResult> {
84
87
  const workspace = resolveAgentWorkspace(cwd, "claude");
85
88
  return queued("__new_claude_task__", () => withAttachments(attachments, text, (prepared) =>
86
- runClaudeNew(prepared.claudePrompt, workspace, timeoutMs, model, operationKey),
89
+ runClaudeNew(prepared.claudePrompt, workspace, timeoutMs, model, operationKey, effort),
87
90
  ));
88
91
  }
89
92
 
@@ -94,10 +97,11 @@ export function createCursorSession(
94
97
  attachments: UserAttachment[] = [],
95
98
  model?: string,
96
99
  operationKey?: string,
100
+ effort?: string,
97
101
  ): Promise<ReplyResult> {
98
102
  const workspace = resolveAgentWorkspace(cwd, "cursor");
99
103
  return queued("__new_cursor_task__", () => withAttachments(attachments, text, (prepared) =>
100
- runCursorNew(prepared.claudePrompt, workspace, timeoutMs, model, operationKey),
104
+ runCursorNew(prepared.claudePrompt, workspace, timeoutMs, model, operationKey, effort),
101
105
  ));
102
106
  }
103
107
 
@@ -108,10 +112,11 @@ export function createGrokSession(
108
112
  attachments: UserAttachment[] = [],
109
113
  model?: string,
110
114
  operationKey?: string,
115
+ effort?: string,
111
116
  ): Promise<ReplyResult> {
112
117
  const workspace = resolveAgentWorkspace(cwd, "grok");
113
118
  return queued("__new_grok_task__", () => withAttachments(attachments, text, (prepared) =>
114
- runGrokNew(prepared.claudePrompt, workspace, timeoutMs, model, operationKey),
119
+ runGrokNew(prepared.claudePrompt, workspace, timeoutMs, model, operationKey, effort),
115
120
  ));
116
121
  }
117
122
 
@@ -154,7 +159,7 @@ function runClaude(
154
159
  const modeArgs = options.permissionMode
155
160
  ? ["--permission-mode", options.permissionMode]
156
161
  : [];
157
- const effortArgs = options.effort ? ["--effort", options.effort] : [];
162
+ const effortArgs = providerEffortArgs("claude", options.effort);
158
163
  const args = [
159
164
  "-p", "--resume", session.sessionId, ...mcpArgs, ...modelArgs,
160
165
  ...modeArgs, ...effortArgs,
@@ -180,9 +185,9 @@ function runClaude(
180
185
  }
181
186
 
182
187
  function runClaudeNew(
183
- text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string,
188
+ text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string, effort?: string,
184
189
  ): Promise<ReplyResult> {
185
- const args = ["-p", ...(model ? ["--model", model] : []), "--output-format", "json", text];
190
+ const args = ["-p", ...(model ? ["--model", model] : []), ...providerEffortArgs("claude", effort), "--output-format", "json", text];
186
191
  const claude = resolveClaudeBinary();
187
192
  return runProcess(claude.path, args, cwd, timeoutMs, (stdout) => {
188
193
  try {
@@ -202,100 +207,3 @@ function runClaudeNew(
202
207
  : { ok: false, error: "Claude returned an empty response." };
203
208
  }, undefined, operationKey);
204
209
  }
205
-
206
- function parseCodexJsonl(stdout: string, fallbackSessionId?: string): ReplyResult {
207
- let lastMessage = "";
208
- let sessionId = fallbackSessionId;
209
- let structured = false;
210
- let failure = "";
211
- for (const line of stdout.split("\n")) {
212
- if (!line.trim()) continue;
213
- try {
214
- const event = JSON.parse(line) as {
215
- type?: string;
216
- thread_id?: string;
217
- session_id?: string;
218
- item?: { type?: string; text?: string; message?: string };
219
- error?: { message?: string } | string;
220
- };
221
- structured = true;
222
- if (event.type === "thread.started") {
223
- sessionId = event.thread_id ?? event.session_id ?? sessionId;
224
- }
225
- if (event.item?.type === "agent_message" && typeof event.item.text === "string") {
226
- lastMessage = event.item.text;
227
- }
228
- if (event.type === "error" || event.type === "turn.failed" || event.item?.type === "error") {
229
- failure = (typeof event.error === "string" ? event.error : event.error?.message)
230
- ?? event.item?.message ?? "Codex reported an error.";
231
- }
232
- } catch {
233
- // Ignore non-JSON diagnostic lines.
234
- }
235
- }
236
- if (failure) return { ok: false, error: failure.slice(0, 500) };
237
- if (lastMessage) return { ok: true, text: lastMessage.slice(0, 4000), sessionId };
238
- if (structured) return { ok: false, error: "Codex returned no agent message." };
239
- const trimmed = stdout.trim();
240
- return trimmed
241
- ? { ok: true, text: trimmed.slice(0, 4000), sessionId }
242
- : { ok: false, error: "Codex returned an empty response." };
243
- }
244
-
245
- function runCodex(
246
- session: SessionInfo,
247
- text: string,
248
- timeoutMs: number,
249
- images: string[] = [],
250
- options: DeliveryOptions = {},
251
- ): Promise<ReplyResult> {
252
- const configured = loadRuntimeConfig().sessionAccess[session.sessionId];
253
- const disabledMcp = loadRuntimeConfig().sessionMcpDisabled[session.sessionId] ?? [];
254
- const sandbox = configured === "read-only"
255
- ? "read-only"
256
- : configured === "workspace"
257
- ? "workspace-write"
258
- : configured === "full"
259
- ? "danger-full-access"
260
- : null;
261
- const accessArgs = sandbox ? ["-c", `sandbox_mode="${sandbox}"`] : [];
262
- const mcpArgs = disabledMcp.flatMap((name) => [
263
- "-c",
264
- `mcp_servers.${tomlKey(name)}.enabled=false`,
265
- ]);
266
- const modelArgs = options.model ? ["-m", options.model] : [];
267
- const imageArgs = images.flatMap((path) => ["-i", path]);
268
- const args = [
269
- "exec", "resume", ...accessArgs, ...mcpArgs, ...modelArgs, ...imageArgs,
270
- session.sessionId, "--json", "-",
271
- ];
272
- return runProcess(
273
- resolveCodexBinary(),
274
- args,
275
- session.cwd,
276
- timeoutMs,
277
- (stdout) => parseCodexJsonl(stdout, session.sessionId),
278
- text,
279
- session.sessionId,
280
- );
281
- }
282
-
283
- function tomlKey(value: string): string {
284
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
285
- }
286
-
287
- function runCodexNew(
288
- text: string,
289
- cwd: string,
290
- timeoutMs: number,
291
- images: string[] = [],
292
- model?: string,
293
- operationKey?: string,
294
- ): Promise<ReplyResult> {
295
- // This is the supported non-interactive Codex path. It preserves the user's
296
- // normal sandbox, approval, hooks, model, and auth configuration.
297
- const imageArgs = images.flatMap((path) => ["-i", path]);
298
- const args = ["exec", ...(model ? ["-m", model] : []), ...imageArgs,
299
- "--json", "--skip-git-repo-check", "-"];
300
- return runProcess(resolveCodexBinary(), args, cwd, timeoutMs, parseCodexJsonl, text, operationKey);
301
- }
@@ -0,0 +1,104 @@
1
+ import type { SessionInfo } from "../../../../../packages/protocol/schema";
2
+ import { loadRuntimeConfig } from "../../config";
3
+ import { resolveCodexBinary } from "../../providers/codex-bin";
4
+ import { runProcess } from ".";
5
+ import { providerEffortArgs } from "./provider-effort";
6
+ import type { DeliveryOptions, ReplyResult } from "../payload/types";
7
+
8
+ function parseCodexJsonl(stdout: string, fallbackSessionId?: string): ReplyResult {
9
+ let lastMessage = "";
10
+ let sessionId = fallbackSessionId;
11
+ let structured = false;
12
+ let failure = "";
13
+ for (const line of stdout.split("\n")) {
14
+ if (!line.trim()) continue;
15
+ try {
16
+ const event = JSON.parse(line) as {
17
+ type?: string;
18
+ thread_id?: string;
19
+ session_id?: string;
20
+ item?: { type?: string; text?: string; message?: string };
21
+ error?: { message?: string } | string;
22
+ };
23
+ structured = true;
24
+ if (event.type === "thread.started") {
25
+ sessionId = event.thread_id ?? event.session_id ?? sessionId;
26
+ }
27
+ if (event.item?.type === "agent_message" && typeof event.item.text === "string") {
28
+ lastMessage = event.item.text;
29
+ }
30
+ if (event.type === "error" || event.type === "turn.failed" || event.item?.type === "error") {
31
+ failure = (typeof event.error === "string" ? event.error : event.error?.message)
32
+ ?? event.item?.message ?? "Codex reported an error.";
33
+ }
34
+ } catch {
35
+ // Ignore non-JSON diagnostic lines.
36
+ }
37
+ }
38
+ if (failure) return { ok: false, error: failure.slice(0, 500) };
39
+ if (lastMessage) return { ok: true, text: lastMessage.slice(0, 4000), sessionId };
40
+ if (structured) return { ok: false, error: "Codex returned no agent message." };
41
+ const trimmed = stdout.trim();
42
+ return trimmed
43
+ ? { ok: true, text: trimmed.slice(0, 4000), sessionId }
44
+ : { ok: false, error: "Codex returned an empty response." };
45
+ }
46
+
47
+ export function runCodex(
48
+ session: SessionInfo,
49
+ text: string,
50
+ timeoutMs: number,
51
+ images: string[] = [],
52
+ options: DeliveryOptions = {},
53
+ ): Promise<ReplyResult> {
54
+ const configured = loadRuntimeConfig().sessionAccess[session.sessionId];
55
+ const disabledMcp = loadRuntimeConfig().sessionMcpDisabled[session.sessionId] ?? [];
56
+ const sandbox = configured === "read-only"
57
+ ? "read-only"
58
+ : configured === "workspace"
59
+ ? "workspace-write"
60
+ : configured === "full"
61
+ ? "danger-full-access"
62
+ : null;
63
+ const accessArgs = sandbox ? ["-c", `sandbox_mode="${sandbox}"`] : [];
64
+ const mcpArgs = disabledMcp.flatMap((name) => [
65
+ "-c",
66
+ `mcp_servers.${tomlKey(name)}.enabled=false`,
67
+ ]);
68
+ const modelArgs = options.model ? ["-m", options.model] : [];
69
+ const imageArgs = images.flatMap((path) => ["-i", path]);
70
+ const args = [
71
+ "exec", "resume", ...accessArgs, ...mcpArgs, ...modelArgs, ...providerEffortArgs("codex", options.effort), ...imageArgs,
72
+ session.sessionId, "--json", "-",
73
+ ];
74
+ return runProcess(
75
+ resolveCodexBinary(),
76
+ args,
77
+ session.cwd,
78
+ timeoutMs,
79
+ (stdout) => parseCodexJsonl(stdout, session.sessionId),
80
+ text,
81
+ session.sessionId,
82
+ );
83
+ }
84
+
85
+ function tomlKey(value: string): string {
86
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
87
+ }
88
+
89
+ export function runCodexNew(
90
+ text: string,
91
+ cwd: string,
92
+ timeoutMs: number,
93
+ images: string[] = [],
94
+ model?: string,
95
+ operationKey?: string,
96
+ effort?: string,
97
+ ): Promise<ReplyResult> {
98
+ // This is the supported non-interactive Codex path. It preserves the user's
99
+ // normal sandbox, approval, hooks, model, and auth configuration.
100
+ const imageArgs = images.flatMap((path) => ["-i", path]);
101
+ const args = ["exec", ...(model ? ["-m", model] : []), ...providerEffortArgs("codex", effort), ...imageArgs,
102
+ "--json", "--skip-git-repo-check", "-"];
103
+ return runProcess(resolveCodexBinary(), args, cwd, timeoutMs, parseCodexJsonl, text, operationKey);
104
+ }
@@ -0,0 +1,10 @@
1
+ /** Provider-native controls, never interpolated into a prompt or shell. */
2
+ export function providerEffortArgs(provider: string, effort?: string): string[] {
3
+ if (!effort) return [];
4
+ const supported = provider === "codex" ? ["low", "medium", "high", "xhigh", "max", "ultra"]
5
+ : provider === "claude" ? ["low", "medium", "high", "xhigh", "max"]
6
+ : provider === "grok" ? ["low", "medium", "high"] : [];
7
+ if (!supported.includes(effort)) return [];
8
+ return provider === "codex" ? ["-c", `model_reasoning_effort="${effort}"`]
9
+ : [provider === "grok" ? "--reasoning-effort" : "--effort", effort];
10
+ }
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { SessionInfo } from "../../../../../packages/protocol/schema";
3
3
  import { runProcess } from ".";
4
+ import { providerEffortArgs } from "./provider-effort";
4
5
  import type { ReplyResult } from "../payload/types";
5
6
  import type { DeliveryOptions } from "../payload/types";
6
7
  import { resolveCursorAgentBin } from "./cursor-agent-bin";
@@ -61,7 +62,7 @@ function parseGrok(stdout: string, fallback: string): ReplyResult {
61
62
  }
62
63
 
63
64
  export function runCursorNew(
64
- text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string,
65
+ text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string, effort?: string,
65
66
  ): Promise<ReplyResult> {
66
67
  return runProcess(
67
68
  cursorBin(), ["-p", ...(model ? ["--model", model] : []),
@@ -81,12 +82,12 @@ export function runCursorResume(
81
82
  }
82
83
 
83
84
  export function runGrokNew(
84
- text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string,
85
+ text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string, effort?: string,
85
86
  ): Promise<ReplyResult> {
86
87
  const sessionId = randomUUID();
87
88
  const args = [
88
89
  "--no-auto-update", "--cwd", cwd, "--session-id", sessionId,
89
- ...(model ? ["--model", model] : []),
90
+ ...(model ? ["--model", model] : []), ...providerEffortArgs("grok", effort),
90
91
  "-p", text, "--output-format", "streaming-json",
91
92
  ];
92
93
  return runProcess(grokBin(), args, cwd, timeoutMs, (stdout) => parseGrok(stdout, sessionId),
@@ -101,7 +102,7 @@ export function runGrokResume(
101
102
  "--no-auto-update", ...(session.cwd ? ["--cwd", session.cwd] : []),
102
103
  ...(options.model ? ["--model", options.model] : []),
103
104
  ...(permission ? ["--permission-mode", permission] : []),
104
- ...(options.effort ? ["--reasoning-effort", options.effort] : []),
105
+ ...providerEffortArgs("grok", options.effort),
105
106
  "--resume", session.sessionId, "-p", text, "--output-format", "streaming-json",
106
107
  ];
107
108
  return runProcess(
@@ -31,7 +31,7 @@ export function desktopTaskCreate(
31
31
  const query = input as Record<string, unknown>;
32
32
  const projectId = query.project_id, bindingId = query.binding_id;
33
33
  const operationId = query.operation_id, text = query.text;
34
- const provider = query.provider, model = query.model;
34
+ const provider = query.provider, model = query.model, effort = query.effort;
35
35
  if (typeof projectId !== "string" || !projectId || projectId.length > 128
36
36
  || typeof bindingId !== "string" || !bindingId || bindingId.length > 128
37
37
  || query.endpoint_id !== endpoint
@@ -42,6 +42,9 @@ export function desktopTaskCreate(
42
42
  || model !== undefined && (typeof model !== "string" || model.length > 128)) {
43
43
  return Promise.resolve(undefined);
44
44
  }
45
+ if (effort !== undefined && !["low", "medium", "high", "xhigh", "max", "ultra"].includes(effort as string)) {
46
+ return Promise.resolve(undefined);
47
+ }
45
48
  const loaded = readStoreState(storePath);
46
49
  const binding = loaded.status === "ok"
47
50
  ? loaded.state.bindings.find((row) => row.bindingId === bindingId
@@ -86,7 +89,7 @@ export function desktopTaskCreate(
86
89
  if (!attachments || !text.trim() && attachments.length === 0) return Promise.resolve(undefined);
87
90
  const start = create ?? providers[agent];
88
91
  const result = start(text.trim(), workspace, 240_000, attachments, model as string | undefined,
89
- operationId).then((reply) => ({
92
+ operationId, effort as string | undefined).then((reply) => ({
90
93
  operation: "desktop.task_create", created: reply.ok,
91
94
  session_id: reply.ok ? reply.sessionId ?? null : null,
92
95
  error: reply.ok ? null : reply.error.slice(0, 500),
@@ -15,7 +15,7 @@ export function desktopTaskSend(input: unknown,
15
15
  if (!input || typeof input !== "object" || Array.isArray(input)) return Promise.resolve(undefined);
16
16
  const query = input as Record<string, unknown>;
17
17
  const { project_id: projectId, task_id: taskId, session_id: sessionId,
18
- delivery_id: deliveryId, text, model } = query;
18
+ delivery_id: deliveryId, text, model, effort } = query;
19
19
  if (typeof projectId !== "string" || !projectId || projectId.length > 128
20
20
  || typeof taskId !== "string" || !taskId || taskId.length > 128
21
21
  || typeof sessionId !== "string" || !sessionId || sessionId.length > 128
@@ -25,6 +25,9 @@ export function desktopTaskSend(input: unknown,
25
25
  || model !== undefined && (typeof model !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/.test(model))) {
26
26
  return Promise.resolve(undefined);
27
27
  }
28
+ if (effort !== undefined && !["low", "medium", "high", "xhigh", "max", "ultra"].includes(effort as string)) {
29
+ return Promise.resolve(undefined);
30
+ }
28
31
  const loaded = readStoreState(storePath);
29
32
  if (loaded.status !== "ok") return Promise.resolve(undefined);
30
33
  const task = loaded.state.tasks.find((item) => item.taskId === taskId);
@@ -51,7 +54,8 @@ export function desktopTaskSend(input: unknown,
51
54
  lastActivityAt: execution.activeAt ?? execution.updatedAt ?? execution.startedAt,
52
55
  tokensSession: 0, tokensLastTurn: 0,
53
56
  };
54
- const options = model === undefined ? {} : { model: model as string };
57
+ const options = { ...(model === undefined ? {} : { model: model as string }),
58
+ ...(effort === undefined ? {} : { effort: effort as string }) };
55
59
  const operation = deliver(session, text.trim(), 240_000, attachments, options).then((result) => ({
56
60
  operation: "desktop.task_send", accepted: result.ok,
57
61
  error: result.ok ? null : result.error.slice(0, 500),
@@ -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.65
13
+ npm install -g granttap-mcp@0.8.66
14
14
  granttap setup
15
15
  granttap status
16
16
  ```
@@ -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.65\";\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 20+ 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"
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.66\";\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 20+ 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.65";
10
+ const PACKAGE = "granttap-mcp@0.8.66";
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granttap-mcp",
3
- "version": "0.8.65",
3
+ "version": "0.8.66",
4
4
  "description": "Personal live control center runtime for local coding agents on iPhone and Apple Watch.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,7 +61,7 @@ export const UserMessage = z.object({
61
61
  permissionMode: z.enum([
62
62
  "acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan",
63
63
  ]).optional(),
64
- effort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
64
+ effort: z.enum(["low", "medium", "high", "xhigh", "max", "ultra"]).optional(),
65
65
  createdAt: z.number(),
66
66
  });
67
67
  export type UserMessage = z.infer<typeof UserMessage>;
@@ -40,6 +40,7 @@ export const AdvertisedModel = z.object({
40
40
  description: z.string().trim().min(1).max(240).optional(),
41
41
  priority: z.number().int().nonnegative().max(10_000).optional(),
42
42
  observedAt: z.number().nonnegative(),
43
+ supportedEfforts: z.array(z.enum(["low", "medium", "high", "xhigh", "max", "ultra"])).max(6).optional(),
43
44
  }).strict();
44
45
  export type AdvertisedModel = z.infer<typeof AdvertisedModel>;
45
46