gogcli-mcp 2.0.6 → 2.0.8

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.
@@ -6,16 +6,16 @@
6
6
  "email": "chris.c.hall@gmail.com"
7
7
  },
8
8
  "metadata": {
9
- "description": "Google Sheets (and more) for Claude via gogcli \u2014 read, write, and manage spreadsheets",
10
- "version": "2.0.3"
9
+ "description": "Google Sheets (and more) for Claude via gogcli read, write, and manage spreadsheets",
10
+ "version": "2.0.8"
11
11
  },
12
12
  "plugins": [
13
13
  {
14
14
  "name": "gogcli-mcp",
15
15
  "displayName": "gogcli",
16
16
  "source": "./",
17
- "description": "Google Sheets (and more) for Claude via gogcli \u2014 read, write, and manage spreadsheets",
18
- "version": "2.0.3",
17
+ "description": "Google Sheets (and more) for Claude via gogcli read, write, and manage spreadsheets",
18
+ "version": "2.0.8",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
3
  "displayName": "gogcli",
4
- "version": "2.0.3",
5
- "description": "Google Sheets (and more) for Claude via gogcli \u2014 read, write, and manage spreadsheets",
4
+ "version": "2.0.8",
5
+ "description": "Google Sheets (and more) for Claude via gogcli read, write, and manage spreadsheets",
6
6
  "author": {
7
7
  "name": "Chris Hall",
8
8
  "email": "chris.c.hall@gmail.com"
package/dist/index.js CHANGED
@@ -8415,8 +8415,8 @@ function emoji() {
8415
8415
  }
8416
8416
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
8417
8417
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
8418
- var mac = (delimiter) => {
8419
- const escapedDelim = escapeRegex(delimiter ?? ":");
8418
+ var mac = (delimiter2) => {
8419
+ const escapedDelim = escapeRegex(delimiter2 ?? ":");
8420
8420
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
8421
8421
  };
8422
8422
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -30863,7 +30863,34 @@ var EMPTY_COMPLETION_RESULT = {
30863
30863
 
30864
30864
  // src/runner.ts
30865
30865
  import { spawn } from "node:child_process";
30866
+ import { delimiter } from "node:path";
30866
30867
  var TIMEOUT_MS = 3e4;
30868
+ function envOrUndefined(key) {
30869
+ const value = process.env[key];
30870
+ if (!value || value.startsWith("${")) return void 0;
30871
+ return value;
30872
+ }
30873
+ function augmentedPath() {
30874
+ const home = process.env.HOME;
30875
+ const candidates = [
30876
+ process.env.PATH ?? "",
30877
+ "/opt/homebrew/bin",
30878
+ "/usr/local/bin",
30879
+ home ? `${home}/.local/bin` : "",
30880
+ home ? `${home}/go/bin` : ""
30881
+ ];
30882
+ const seen = /* @__PURE__ */ new Set();
30883
+ const parts = [];
30884
+ for (const c of candidates) {
30885
+ if (!c) continue;
30886
+ for (const dir of c.split(delimiter)) {
30887
+ if (!dir || seen.has(dir)) continue;
30888
+ seen.add(dir);
30889
+ parts.push(dir);
30890
+ }
30891
+ }
30892
+ return parts.join(delimiter);
30893
+ }
30867
30894
  function formatTimeout(ms) {
30868
30895
  const seconds = Math.round(ms / 1e3);
30869
30896
  if (seconds >= 60) {
@@ -30874,7 +30901,7 @@ function formatTimeout(ms) {
30874
30901
  }
30875
30902
  async function run(args, options = {}) {
30876
30903
  const { account, spawner = spawn, interactive = false, timeout } = options;
30877
- const effectiveAccount = account ?? process.env.GOG_ACCOUNT;
30904
+ const effectiveAccount = account ?? envOrUndefined("GOG_ACCOUNT");
30878
30905
  const fullArgs = ["--json", "--color=never"];
30879
30906
  if (!interactive) {
30880
30907
  fullArgs.push("--no-input");
@@ -30886,7 +30913,8 @@ async function run(args, options = {}) {
30886
30913
  const effectiveTimeout = timeout ?? TIMEOUT_MS;
30887
30914
  return new Promise((resolve, reject) => {
30888
30915
  const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
30889
- const child = spawner(process.env.GOG_PATH ?? "gog", fullArgs, { env: cleanEnv });
30916
+ const childEnv = { ...cleanEnv, PATH: augmentedPath() };
30917
+ const child = spawner(envOrUndefined("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
30890
30918
  const stdoutChunks = [];
30891
30919
  const stderrChunks = [];
30892
30920
  let settled = false;
@@ -30921,6 +30949,12 @@ async function run(args, options = {}) {
30921
30949
  clearTimeout(timer);
30922
30950
  if (settled) return;
30923
30951
  settled = true;
30952
+ if (err.code === "ENOENT") {
30953
+ reject(new Error(
30954
+ "gog executable not found. Install gogcli (https://github.com/steipete/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
30955
+ ));
30956
+ return;
30957
+ }
30924
30958
  reject(err);
30925
30959
  });
30926
30960
  });
@@ -32118,7 +32152,7 @@ function registerTasksTools(server2) {
32118
32152
  }
32119
32153
 
32120
32154
  // src/server.ts
32121
- var VERSION = true ? "2.0.5" : "0.0.0";
32155
+ var VERSION = true ? "2.0.8" : "0.0.0";
32122
32156
  function createServer(options) {
32123
32157
  return new McpServer({
32124
32158
  name: options?.name ?? "gogcli",
package/dist/lib.js CHANGED
@@ -12044,8 +12044,8 @@ function emoji() {
12044
12044
  }
12045
12045
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
12046
12046
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
12047
- var mac = (delimiter) => {
12048
- const escapedDelim = escapeRegex(delimiter ?? ":");
12047
+ var mac = (delimiter2) => {
12048
+ const escapedDelim = escapeRegex(delimiter2 ?? ":");
12049
12049
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
12050
12050
  };
12051
12051
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -30770,7 +30770,34 @@ var EMPTY_COMPLETION_RESULT = {
30770
30770
 
30771
30771
  // src/runner.ts
30772
30772
  import { spawn } from "node:child_process";
30773
+ import { delimiter } from "node:path";
30773
30774
  var TIMEOUT_MS = 3e4;
30775
+ function envOrUndefined(key) {
30776
+ const value = process.env[key];
30777
+ if (!value || value.startsWith("${")) return void 0;
30778
+ return value;
30779
+ }
30780
+ function augmentedPath() {
30781
+ const home = process.env.HOME;
30782
+ const candidates = [
30783
+ process.env.PATH ?? "",
30784
+ "/opt/homebrew/bin",
30785
+ "/usr/local/bin",
30786
+ home ? `${home}/.local/bin` : "",
30787
+ home ? `${home}/go/bin` : ""
30788
+ ];
30789
+ const seen = /* @__PURE__ */ new Set();
30790
+ const parts = [];
30791
+ for (const c of candidates) {
30792
+ if (!c) continue;
30793
+ for (const dir of c.split(delimiter)) {
30794
+ if (!dir || seen.has(dir)) continue;
30795
+ seen.add(dir);
30796
+ parts.push(dir);
30797
+ }
30798
+ }
30799
+ return parts.join(delimiter);
30800
+ }
30774
30801
  function formatTimeout(ms) {
30775
30802
  const seconds = Math.round(ms / 1e3);
30776
30803
  if (seconds >= 60) {
@@ -30781,7 +30808,7 @@ function formatTimeout(ms) {
30781
30808
  }
30782
30809
  async function run(args, options = {}) {
30783
30810
  const { account, spawner = spawn, interactive = false, timeout } = options;
30784
- const effectiveAccount = account ?? process.env.GOG_ACCOUNT;
30811
+ const effectiveAccount = account ?? envOrUndefined("GOG_ACCOUNT");
30785
30812
  const fullArgs = ["--json", "--color=never"];
30786
30813
  if (!interactive) {
30787
30814
  fullArgs.push("--no-input");
@@ -30793,7 +30820,8 @@ async function run(args, options = {}) {
30793
30820
  const effectiveTimeout = timeout ?? TIMEOUT_MS;
30794
30821
  return new Promise((resolve, reject) => {
30795
30822
  const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
30796
- const child = spawner(process.env.GOG_PATH ?? "gog", fullArgs, { env: cleanEnv });
30823
+ const childEnv = { ...cleanEnv, PATH: augmentedPath() };
30824
+ const child = spawner(envOrUndefined("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
30797
30825
  const stdoutChunks = [];
30798
30826
  const stderrChunks = [];
30799
30827
  let settled = false;
@@ -30828,6 +30856,12 @@ async function run(args, options = {}) {
30828
30856
  clearTimeout(timer);
30829
30857
  if (settled) return;
30830
30858
  settled = true;
30859
+ if (err.code === "ENOENT") {
30860
+ reject(new Error(
30861
+ "gog executable not found. Install gogcli (https://github.com/steipete/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
30862
+ ));
30863
+ return;
30864
+ }
30831
30865
  reject(err);
30832
30866
  });
30833
30867
  });
@@ -32025,7 +32059,7 @@ function registerTasksTools(server) {
32025
32059
  }
32026
32060
 
32027
32061
  // src/server.ts
32028
- var VERSION = true ? "2.0.5" : "0.0.0";
32062
+ var VERSION = true ? "2.0.8" : "0.0.0";
32029
32063
  function createServer(options) {
32030
32064
  return new McpServer({
32031
32065
  name: options?.name ?? "gogcli",
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp",
5
5
  "display_name": "gogcli",
6
- "version": "2.0.6",
6
+ "version": "2.0.8",
7
7
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
8
8
  "author": {
9
9
  "name": "Chris Hall",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp",
5
5
  "description": "MCP server wrapping gogcli for Google service access",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.chrischall/gogcli-mcp",
4
- "description": "Google Sheets (and more) for Claude via gogcli \u2014 read, write, and manage spreadsheets",
4
+ "description": "Google Sheets (and more) for Claude via gogcli read, write, and manage spreadsheets",
5
5
  "repository": {
6
6
  "url": "https://github.com/chrischall/gogcli-mcp",
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp"
9
9
  },
10
- "version": "2.0.3",
10
+ "version": "2.0.8",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp",
15
- "version": "2.0.3",
15
+ "version": "2.0.8",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
package/src/runner.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import type { ChildProcess } from 'node:child_process';
3
+ import { delimiter } from 'node:path';
3
4
 
4
5
  export type Spawner = (
5
6
  command: string,
@@ -16,6 +17,42 @@ export interface RunOptions {
16
17
 
17
18
  const TIMEOUT_MS = 30_000;
18
19
 
20
+ // Treat unresolved .mcpb placeholders ("${user_config.gog_path}") and empty
21
+ // strings the same as an unset env var. When an optional .mcpb user_config
22
+ // field is left blank, some clients pass the literal placeholder text through
23
+ // to the spawned server instead of substituting "" or omitting the key.
24
+ function envOrUndefined(key: string): string | undefined {
25
+ const value = process.env[key];
26
+ if (!value || value.startsWith('${')) return undefined;
27
+ return value;
28
+ }
29
+
30
+ // MCP desktop clients often spawn servers with a stripped PATH that excludes
31
+ // Homebrew, user-local, and Go's default install dirs — so even when gog is
32
+ // installed, the spawned server can't find it. Augment the child's PATH with
33
+ // the locations where gogcli is commonly installed.
34
+ function augmentedPath(): string {
35
+ const home = process.env.HOME;
36
+ const candidates = [
37
+ process.env.PATH ?? '',
38
+ '/opt/homebrew/bin',
39
+ '/usr/local/bin',
40
+ home ? `${home}/.local/bin` : '',
41
+ home ? `${home}/go/bin` : '',
42
+ ];
43
+ const seen = new Set<string>();
44
+ const parts: string[] = [];
45
+ for (const c of candidates) {
46
+ if (!c) continue;
47
+ for (const dir of c.split(delimiter)) {
48
+ if (!dir || seen.has(dir)) continue;
49
+ seen.add(dir);
50
+ parts.push(dir);
51
+ }
52
+ }
53
+ return parts.join(delimiter);
54
+ }
55
+
19
56
  function formatTimeout(ms: number): string {
20
57
  const seconds = Math.round(ms / 1000);
21
58
  if (seconds >= 60) {
@@ -28,7 +65,7 @@ function formatTimeout(ms: number): string {
28
65
  export async function run(args: string[], options: RunOptions = {}): Promise<string> {
29
66
  const { account, spawner = spawn as unknown as Spawner, interactive = false, timeout } = options;
30
67
 
31
- const effectiveAccount = account ?? process.env.GOG_ACCOUNT;
68
+ const effectiveAccount = account ?? envOrUndefined('GOG_ACCOUNT');
32
69
 
33
70
  const fullArgs = ['--json', '--color=never'];
34
71
  if (!interactive) {
@@ -45,10 +82,8 @@ export async function run(args: string[], options: RunOptions = {}): Promise<str
45
82
  // Strip GOG_ACCESS_TOKEN so gogcli uses stored refresh tokens instead of
46
83
  // a potentially stale direct access token passed through MCP env config.
47
84
  const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
48
- // Use `||` (not `??`) so an empty-string GOG_PATH — common when the .mcpb
49
- // user_config "gog_path" is left blank and substituted as "" — falls back
50
- // to PATH lookup instead of trying to spawn an empty executable name.
51
- const child = spawner(process.env.GOG_PATH || 'gog', fullArgs, { env: cleanEnv });
85
+ const childEnv = { ...cleanEnv, PATH: augmentedPath() };
86
+ const child = spawner(envOrUndefined('GOG_PATH') ?? 'gog', fullArgs, { env: childEnv });
52
87
  const stdoutChunks: Buffer[] = [];
53
88
  const stderrChunks: Buffer[] = [];
54
89
  let settled = false;
@@ -83,6 +118,14 @@ export async function run(args: string[], options: RunOptions = {}): Promise<str
83
118
  clearTimeout(timer);
84
119
  if (settled) return;
85
120
  settled = true;
121
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
122
+ reject(new Error(
123
+ 'gog executable not found. Install gogcli (https://github.com/steipete/gogcli) ' +
124
+ 'or set GOG_PATH in your MCP client config to the absolute binary path ' +
125
+ '(run `which gog` in a terminal to find it).',
126
+ ));
127
+ return;
128
+ }
86
129
  reject(err);
87
130
  });
88
131
  });
@@ -24,7 +24,7 @@ describe('run', () => {
24
24
  expect(spawner).toHaveBeenCalledWith(
25
25
  'gog',
26
26
  ['--json', '--color=never', '--no-input', 'sheets', 'get', 'id1', 'A1'],
27
- expect.objectContaining({ env: process.env }),
27
+ expect.objectContaining({ env: expect.any(Object) }),
28
28
  );
29
29
  });
30
30
 
@@ -143,6 +143,39 @@ describe('run', () => {
143
143
  }
144
144
  });
145
145
 
146
+ it('falls back to "gog" on PATH when GOG_PATH is an unresolved .mcpb placeholder', async () => {
147
+ const spawner = makeSpawner(0, '{}');
148
+ const originalEnv = process.env.GOG_PATH;
149
+ process.env.GOG_PATH = '${user_config.gog_path}';
150
+ try {
151
+ await run(['sheets', 'metadata', 'id1'], { spawner });
152
+ expect(spawner).toHaveBeenCalledWith('gog', expect.any(Array), expect.any(Object));
153
+ } finally {
154
+ if (originalEnv === undefined) {
155
+ delete process.env.GOG_PATH;
156
+ } else {
157
+ process.env.GOG_PATH = originalEnv;
158
+ }
159
+ }
160
+ });
161
+
162
+ it('omits --account when GOG_ACCOUNT is an unresolved .mcpb placeholder', async () => {
163
+ const spawner = makeSpawner(0, '{}');
164
+ const originalEnv = process.env.GOG_ACCOUNT;
165
+ process.env.GOG_ACCOUNT = '${user_config.gog_account}';
166
+ try {
167
+ await run(['sheets', 'metadata', 'id1'], { spawner });
168
+ const callArgs = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][1] as string[];
169
+ expect(callArgs).not.toContain('--account');
170
+ } finally {
171
+ if (originalEnv === undefined) {
172
+ delete process.env.GOG_ACCOUNT;
173
+ } else {
174
+ process.env.GOG_ACCOUNT = originalEnv;
175
+ }
176
+ }
177
+ });
178
+
146
179
  it('returns stdout on exit code 0', async () => {
147
180
  const spawner = makeSpawner(0, '{"values":[["hello"]]}');
148
181
  const result = await run(['sheets', 'get', 'id1', 'A1'], { spawner });
@@ -173,6 +206,93 @@ describe('run', () => {
173
206
  .rejects.toThrow('gog not found');
174
207
  });
175
208
 
209
+ it('wraps ENOENT spawn errors with an install-or-set-GOG_PATH hint', async () => {
210
+ const spawner = vi.fn(() => {
211
+ const proc = new EventEmitter() as ReturnType<Spawner>;
212
+ (proc as unknown as { stdout: EventEmitter; stderr: EventEmitter }).stdout = new EventEmitter();
213
+ (proc as unknown as { stdout: EventEmitter; stderr: EventEmitter }).stderr = new EventEmitter();
214
+ setTimeout(() => {
215
+ const err = new Error('spawn gog ENOENT') as NodeJS.ErrnoException;
216
+ err.code = 'ENOENT';
217
+ proc.emit('error', err);
218
+ }, 0);
219
+ return proc;
220
+ }) as unknown as Spawner;
221
+ await expect(run(['sheets', 'get', 'id', 'A1'], { spawner }))
222
+ .rejects.toThrow(/gog executable not found.*Install gogcli.*GOG_PATH/s);
223
+ });
224
+
225
+ it('augments child PATH with common gogcli install dirs', async () => {
226
+ const spawner = makeSpawner(0, '{}');
227
+ const originalHome = process.env.HOME;
228
+ const originalPath = process.env.PATH;
229
+ process.env.HOME = '/Users/test';
230
+ process.env.PATH = '/usr/bin:/bin';
231
+ try {
232
+ await run(['sheets', 'metadata', 'id1'], { spawner });
233
+ const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
234
+ const passedPath = passedEnv.PATH!;
235
+ expect(passedPath).toContain('/usr/bin');
236
+ expect(passedPath).toContain('/opt/homebrew/bin');
237
+ expect(passedPath).toContain('/usr/local/bin');
238
+ expect(passedPath).toContain('/Users/test/.local/bin');
239
+ expect(passedPath).toContain('/Users/test/go/bin');
240
+ } finally {
241
+ if (originalHome === undefined) delete process.env.HOME;
242
+ else process.env.HOME = originalHome;
243
+ if (originalPath === undefined) delete process.env.PATH;
244
+ else process.env.PATH = originalPath;
245
+ }
246
+ });
247
+
248
+ it('does not duplicate dirs that are already on PATH', async () => {
249
+ const spawner = makeSpawner(0, '{}');
250
+ const originalPath = process.env.PATH;
251
+ process.env.PATH = '/opt/homebrew/bin:/usr/bin';
252
+ try {
253
+ await run(['sheets', 'metadata', 'id1'], { spawner });
254
+ const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
255
+ const passedPath = passedEnv.PATH!;
256
+ const homebrewCount = passedPath.split(':').filter(d => d === '/opt/homebrew/bin').length;
257
+ expect(homebrewCount).toBe(1);
258
+ } finally {
259
+ if (originalPath === undefined) delete process.env.PATH;
260
+ else process.env.PATH = originalPath;
261
+ }
262
+ });
263
+
264
+ it('augments PATH even when HOME is unset', async () => {
265
+ const spawner = makeSpawner(0, '{}');
266
+ const originalHome = process.env.HOME;
267
+ const originalPath = process.env.PATH;
268
+ delete process.env.HOME;
269
+ process.env.PATH = '/usr/bin';
270
+ try {
271
+ await run(['sheets', 'metadata', 'id1'], { spawner });
272
+ const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
273
+ const passedPath = passedEnv.PATH!;
274
+ expect(passedPath).toContain('/opt/homebrew/bin');
275
+ expect(passedPath).not.toContain('.local/bin');
276
+ } finally {
277
+ if (originalHome !== undefined) process.env.HOME = originalHome;
278
+ if (originalPath === undefined) delete process.env.PATH;
279
+ else process.env.PATH = originalPath;
280
+ }
281
+ });
282
+
283
+ it('handles empty PATH gracefully', async () => {
284
+ const spawner = makeSpawner(0, '{}');
285
+ const originalPath = process.env.PATH;
286
+ delete process.env.PATH;
287
+ try {
288
+ await run(['sheets', 'metadata', 'id1'], { spawner });
289
+ const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
290
+ expect(passedEnv.PATH).toContain('/opt/homebrew/bin');
291
+ } finally {
292
+ if (originalPath !== undefined) process.env.PATH = originalPath;
293
+ }
294
+ });
295
+
176
296
  it('ignores close event if error event already settled the promise', async () => {
177
297
  const spawner = vi.fn(() => {
178
298
  const proc = new EventEmitter() as ReturnType<Spawner>;
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/lib.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export { createBaseServer, VERSION } from './server.js';
2
- export { run } from './runner.js';
3
- export type { RunOptions, Spawner } from './runner.js';
4
- export { accountParam, runOrDiagnose, toText, toError } from './tools/utils.js';
5
- export type { ToolResult } from './tools/utils.js';
6
- //# sourceMappingURL=lib.d.ts.map
package/dist/lib.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChF,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/runner.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { ChildProcess } from 'node:child_process';
2
- export type Spawner = (command: string, args: string[], options: {
3
- env: NodeJS.ProcessEnv;
4
- }) => ChildProcess;
5
- export interface RunOptions {
6
- account?: string;
7
- spawner?: Spawner;
8
- interactive?: boolean;
9
- timeout?: number;
10
- }
11
- export declare function run(args: string[], options?: RunOptions): Promise<string>;
12
- //# sourceMappingURL=runner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,MAAM,OAAO,GAAG,CACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,KAChC,YAAY,CAAC;AAElB,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA0DnF"}
package/dist/server.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare const VERSION: string;
3
- export declare function createBaseServer(options?: {
4
- name?: string;
5
- version?: string;
6
- }): McpServer;
7
- //# sourceMappingURL=server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE,eAAO,MAAM,OAAO,QAAmE,CAAC;AAExF,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAgBzF"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerAuthTools(server: McpServer): void;
3
- //# sourceMappingURL=auth.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/tools/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwEzD"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerCalendarTools(server: McpServer): void;
3
- //# sourceMappingURL=calendar.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../src/tools/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2H7D"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerContactsTools(server: McpServer): void;
3
- //# sourceMappingURL=contacts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../src/tools/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkE7D"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerDocsTools(server: McpServer): void;
3
- //# sourceMappingURL=docs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/tools/docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoGzD"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerDriveTools(server: McpServer): void;
3
- //# sourceMappingURL=drive.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../src/tools/drive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAgH1D"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerGmailTools(server: McpServer): void;
3
- //# sourceMappingURL=gmail.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gmail.d.ts","sourceRoot":"","sources":["../../src/tools/gmail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8D1D"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerSheetsTools(server: McpServer): void;
3
- //# sourceMappingURL=sheets.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sheets.d.ts","sourceRoot":"","sources":["../../src/tools/sheets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuG3D"}
@@ -1,3 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerTasksTools(server: McpServer): void;
3
- //# sourceMappingURL=tasks.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/tools/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsF1D"}
@@ -1,14 +0,0 @@
1
- import { z } from 'zod';
2
- export type ToolResult = {
3
- content: [{
4
- type: 'text';
5
- text: string;
6
- }];
7
- };
8
- export declare const accountParam: z.ZodOptional<z.ZodString>;
9
- export declare function toText(output: string): ToolResult;
10
- export declare function toError(err: unknown): ToolResult;
11
- export declare function runOrDiagnose(args: string[], options: {
12
- account?: string;
13
- }): Promise<ToolResult>;
14
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/tools/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC;AAEvE,eAAO,MAAM,YAAY,4BAExB,CAAC;AAEF,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAEjD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAEhD;AAQD,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,OAAO,CAAC,UAAU,CAAC,CAerB"}