mcp-google-map 0.4.0 → 0.5.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/dist/cli.js CHANGED
@@ -2,14 +2,185 @@
2
2
  import {
3
3
  Logger,
4
4
  PlacesSearcher
5
- } from "./chunk-K27FX5QH.js";
5
+ } from "./chunk-NAUIQEK4.js";
6
6
 
7
7
  // src/cli.ts
8
- import { config as dotenvConfig } from "dotenv";
9
- import { resolve } from "path";
8
+ import { config as dotenvConfig2 } from "dotenv";
9
+ import { resolve as resolve5 } from "path";
10
10
  import yargs from "yargs";
11
11
  import { hideBin } from "yargs/helpers";
12
12
 
13
+ // src/utils/configResolver.ts
14
+ import { config as dotenvConfig } from "dotenv";
15
+ import { existsSync } from "fs";
16
+
17
+ // src/utils/autoUpdate.ts
18
+ var AUTO_UPDATE_DISABLED_ENV = "MCP_AUTO_UPDATE_DISABLED";
19
+ var DEFAULT_PACKAGE_NAME = "mcp-google-map";
20
+ var DEFAULT_REGISTRY_BASE_URL = "https://registry.npmjs.org";
21
+ var DEFAULT_UPDATE_CHECK_TIMEOUT_MS = 2e3;
22
+ function isAutoUpdateDisabled(env = process.env) {
23
+ const raw = env[AUTO_UPDATE_DISABLED_ENV];
24
+ return typeof raw === "string" && /^(1|true|yes)$/i.test(raw.trim());
25
+ }
26
+ function isNewerVersion(candidate, current) {
27
+ const toSegments = (version) => version.trim().split(".").map((segment) => /^\d+$/.test(segment) ? Number(segment) : Number.NaN);
28
+ const candidateSegments = toSegments(candidate);
29
+ const currentSegments = toSegments(current);
30
+ if (candidateSegments.some((segment) => !Number.isInteger(segment)) || currentSegments.some((segment) => !Number.isInteger(segment))) {
31
+ return false;
32
+ }
33
+ const length = Math.max(candidateSegments.length, currentSegments.length);
34
+ for (let index = 0; index < length; index += 1) {
35
+ const candidatePart = candidateSegments[index] ?? 0;
36
+ const currentPart = currentSegments[index] ?? 0;
37
+ if (candidatePart !== currentPart) {
38
+ return candidatePart > currentPart;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+ async function checkForUpdates(deps = {}) {
44
+ const logger = deps.logger ?? Logger;
45
+ const env = deps.env ?? process.env;
46
+ try {
47
+ if (isAutoUpdateDisabled(env)) {
48
+ return null;
49
+ }
50
+ const currentVersion = deps.currentVersion;
51
+ if (!currentVersion) {
52
+ logger.error(
53
+ `\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check skipped: running package version is unknown.`
54
+ );
55
+ return null;
56
+ }
57
+ const packageName = deps.packageName ?? DEFAULT_PACKAGE_NAME;
58
+ const registryBaseUrl = deps.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL;
59
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_UPDATE_CHECK_TIMEOUT_MS;
60
+ const fetchImpl = deps.fetchImpl ?? fetch;
61
+ const response = await fetchImpl(`${registryBaseUrl}/${packageName}/latest`, {
62
+ headers: { accept: "application/vnd.npm.install-v1+json" },
63
+ signal: AbortSignal.timeout(timeoutMs)
64
+ });
65
+ if (!response.ok) {
66
+ logger.error(
67
+ `\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry responded with HTTP ${response.status}.`
68
+ );
69
+ return null;
70
+ }
71
+ const payload = await response.json();
72
+ if (typeof payload?.version !== "string") {
73
+ logger.error(
74
+ `\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry response has no version field.`
75
+ );
76
+ return null;
77
+ }
78
+ if (!isNewerVersion(payload.version, currentVersion)) {
79
+ return null;
80
+ }
81
+ logger.log(
82
+ `\u26A0\uFE0F Update available: ${packageName} ${currentVersion} \u2192 ${payload.version}. Upgrade with: npm install -g ${packageName}@latest (or run npx -y ${packageName}@latest).`
83
+ );
84
+ return { currentVersion, latestVersion: payload.version };
85
+ } catch (error) {
86
+ const message = error instanceof Error ? error.message : String(error);
87
+ logger.error(`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: ${message}`);
88
+ return null;
89
+ }
90
+ }
91
+
92
+ // src/utils/configResolver.ts
93
+ var NODE_ENGINE_FLOOR = 22;
94
+ var DEFAULT_HTTP_HOST = "127.0.0.1";
95
+ var DEFAULT_HTTP_PORT = 3e3;
96
+ function resolveRuntimeConfig(env = process.env, overrides = {}) {
97
+ const envPort = env.MCP_SERVER_PORT?.trim() || void 0;
98
+ let port;
99
+ let portRaw;
100
+ let portSource;
101
+ if (overrides.port !== void 0) {
102
+ port = overrides.port;
103
+ portRaw = String(overrides.port);
104
+ portSource = "flag";
105
+ } else if (envPort !== void 0) {
106
+ const parsed = Number(envPort);
107
+ port = Number.isFinite(parsed) ? parsed : void 0;
108
+ portRaw = envPort;
109
+ portSource = "environment";
110
+ } else {
111
+ port = DEFAULT_HTTP_PORT;
112
+ portRaw = void 0;
113
+ portSource = "default";
114
+ }
115
+ const apiKey = overrides.apiKey ?? env.GOOGLE_MAPS_API_KEY;
116
+ const enabledToolsRaw = env.GOOGLE_MAPS_ENABLED_TOOLS?.trim() || void 0;
117
+ return {
118
+ apiKey,
119
+ apiKeySource: overrides.apiKey !== void 0 ? "flag" : apiKey ? "environment" : "none",
120
+ host: overrides.host ?? env.MCP_SERVER_HOST ?? DEFAULT_HTTP_HOST,
121
+ hostSource: overrides.host !== void 0 ? "flag" : env.MCP_SERVER_HOST ? "environment" : "default",
122
+ port,
123
+ portRaw,
124
+ portSource,
125
+ enabledToolsRaw,
126
+ enabledToolsSource: enabledToolsRaw ? "environment" : "default",
127
+ autoUpdateDisabled: isAutoUpdateDisabled(env),
128
+ envFile: overrides.envFile
129
+ };
130
+ }
131
+ function maskSecret(value) {
132
+ if (!value) {
133
+ return "unset";
134
+ }
135
+ if (value.length <= 8) {
136
+ return "****";
137
+ }
138
+ return `${value.slice(0, 4)}****${value.slice(-4)}`;
139
+ }
140
+ function describeRuntimeConfig(config) {
141
+ return {
142
+ apiKey: { value: maskSecret(config.apiKey), source: config.apiKeySource },
143
+ httpHost: { value: config.host, source: config.hostSource },
144
+ httpPort: {
145
+ value: config.port ?? null,
146
+ raw: config.portRaw ?? null,
147
+ source: config.portSource
148
+ },
149
+ enabledTools: { value: config.enabledToolsRaw ?? "*", source: config.enabledToolsSource },
150
+ autoUpdateCheck: { enabled: !config.autoUpdateDisabled },
151
+ envFile: { value: config.envFile ?? null }
152
+ };
153
+ }
154
+ function formatConfigDiagnostics(diagnostics) {
155
+ return [
156
+ `host=${diagnostics.httpHost.value} (${diagnostics.httpHost.source})`,
157
+ `port=${diagnostics.httpPort.value ?? "invalid"} (raw: ${diagnostics.httpPort.raw ?? "n/a"}, ${diagnostics.httpPort.source})`,
158
+ `apiKey=${diagnostics.apiKey.value} (${diagnostics.apiKey.source})`,
159
+ `enabledTools=${diagnostics.enabledTools.value} (${diagnostics.enabledTools.source})`,
160
+ `updateCheck=${diagnostics.autoUpdateCheck.enabled ? "enabled" : "disabled"}`
161
+ ].join("; ");
162
+ }
163
+ function applyExplicitEnvFile(path) {
164
+ if (!existsSync(path)) {
165
+ return { ok: false, error: `env file not found: ${path}` };
166
+ }
167
+ dotenvConfig({ path });
168
+ return { ok: true };
169
+ }
170
+ function resolveEnabledToolNames(allToolNames, raw) {
171
+ const trimmed = raw?.trim();
172
+ if (!trimmed || trimmed === "*") {
173
+ return { names: [...allToolNames], matched: allToolNames.length, fallback: false, requested: [] };
174
+ }
175
+ const requested = trimmed.split(",").map((item) => item.trim()).filter(Boolean);
176
+ const enabled = new Set(requested);
177
+ const names = allToolNames.filter((name) => enabled.has(name));
178
+ if (names.length === 0) {
179
+ return { names: [...allToolNames], matched: 0, fallback: true, requested };
180
+ }
181
+ return { names, matched: names.length, fallback: false, requested };
182
+ }
183
+
13
184
  // src/tools/maps/searchNearby.ts
14
185
  import { z } from "zod";
15
186
 
@@ -1023,18 +1194,19 @@ var serverConfigs = [
1023
1194
  }
1024
1195
  ];
1025
1196
  function filterTools(tools) {
1026
- const raw = process.env.GOOGLE_MAPS_ENABLED_TOOLS?.trim();
1027
- if (!raw || raw === "*") return tools;
1028
- const enabled = new Set(
1029
- raw.split(",").map((s) => s.trim()).filter(Boolean)
1197
+ const resolution = resolveEnabledToolNames(
1198
+ tools.map((tool) => tool.name),
1199
+ process.env.GOOGLE_MAPS_ENABLED_TOOLS
1030
1200
  );
1031
- const filtered = tools.filter((t) => enabled.has(t.name));
1032
- if (filtered.length === 0) {
1201
+ if (resolution.fallback) {
1033
1202
  Logger.error(`GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools. Available: ${tools.map((t) => t.name).join(", ")}`);
1034
1203
  return tools;
1035
1204
  }
1036
- Logger.log(`GOOGLE_MAPS_ENABLED_TOOLS: ${filtered.length}/${tools.length} tools active`);
1037
- return filtered;
1205
+ if (resolution.matched !== tools.length) {
1206
+ Logger.log(`GOOGLE_MAPS_ENABLED_TOOLS: ${resolution.matched}/${tools.length} tools active`);
1207
+ }
1208
+ const enabled = new Set(resolution.names);
1209
+ return tools.filter((tool) => enabled.has(tool.name));
1038
1210
  }
1039
1211
  var config_default = serverConfigs;
1040
1212
 
@@ -1140,7 +1312,20 @@ var BaseMcpServer = class {
1140
1312
  };
1141
1313
  Logger.log(`${this.serverName} connected and ready to process requests`);
1142
1314
  }
1143
- async startHttpServer(port, host = "0.0.0.0") {
1315
+ /**
1316
+ * @brief Starts the Streamable HTTP listener and resolves once it is accepting connections.
1317
+ * @details Builds the Express app with the `/mcp` POST/GET/DELETE handlers,
1318
+ * then wraps `app.listen` in a Promise that resolves from the listen callback
1319
+ * and rejects on the server `error` event (e.g. EADDRINUSE), so callers get
1320
+ * a reliable startup outcome instead of an unhandled error. Default host is
1321
+ * loopback `127.0.0.1` (REQ-008); pass `0.0.0.0` to bind all interfaces.
1322
+ * @param port {number} TCP port to listen on (1-65535).
1323
+ * @param host {string} Bind address; defaults to `127.0.0.1`.
1324
+ * @return {Promise<void>} Resolves when listening; rejects with the listen error.
1325
+ * @throws {Error} Rejects when the HTTP listener emits an error (port in use, permission denied).
1326
+ * @satisfies REQ-008, REQ-095
1327
+ */
1328
+ async startHttpServer(port, host = "127.0.0.1") {
1144
1329
  const app = express();
1145
1330
  app.use(express.json());
1146
1331
  app.post("/mcp", async (req, res) => {
@@ -1212,42 +1397,53 @@ var BaseMcpServer = class {
1212
1397
  app.get("/mcp", handleSessionRequest);
1213
1398
  app.delete("/mcp", handleSessionRequest);
1214
1399
  const displayHost = host === "0.0.0.0" ? "localhost" : host;
1215
- this.httpServer = app.listen(port, host, () => {
1216
- Logger.log(`[${this.serverName}] HTTP server listening on ${host}:${port}`);
1217
- Logger.log(`[${this.serverName}] MCP endpoint available at http://${displayHost}:${port}/mcp`);
1400
+ await new Promise((resolve6, reject) => {
1401
+ this.httpServer = app.listen(port, host, () => {
1402
+ Logger.log(`[${this.serverName}] HTTP server listening on ${host}:${port}`);
1403
+ Logger.log(`[${this.serverName}] MCP endpoint available at http://${displayHost}:${port}/mcp`);
1404
+ resolve6();
1405
+ });
1406
+ this.httpServer.on("error", (error) => reject(error));
1218
1407
  });
1219
1408
  }
1220
1409
  async startStdio() {
1221
1410
  const transport = new StdioServerTransport();
1222
1411
  await this.connect(transport);
1223
1412
  }
1413
+ /**
1414
+ * @brief Stops the HTTP listener and closes every session transport in order.
1415
+ * @details Idempotent: when no listener exists it logs and resolves. Otherwise
1416
+ * it detaches the server reference, clears the session map, awaits
1417
+ * `transport.close()` for every session (errors tolerated via allSettled),
1418
+ * calls `closeAllConnections` when available, and finally closes the socket.
1419
+ * Wired to SIGINT/SIGTERM through `installShutdownHandlers` (REQ-096).
1420
+ * @return {Promise<void>} Resolves after the socket closes; rejects when `server.close` errors.
1421
+ * @throws {Error} Rejects with the `server.close` error.
1422
+ * @satisfies REQ-096
1423
+ */
1224
1424
  async stopHttpServer() {
1225
- if (!this.httpServer) {
1425
+ const server = this.httpServer;
1426
+ if (!server) {
1226
1427
  Logger.error(`[${this.serverName}] HTTP server is not running or already stopped.`);
1227
1428
  return;
1228
1429
  }
1229
- return new Promise((resolve2, reject) => {
1230
- this.httpServer.close((err) => {
1430
+ this.httpServer = null;
1431
+ const closingSessions = Object.values(this.sessions);
1432
+ this.sessions = {};
1433
+ await Promise.allSettled(closingSessions.map((context) => context.transport.close()));
1434
+ if (closingSessions.length > 0) {
1435
+ Logger.log(`[${this.serverName}] All transports closed.`);
1436
+ }
1437
+ return new Promise((resolve6, reject) => {
1438
+ server.closeAllConnections?.();
1439
+ server.close((err) => {
1231
1440
  if (err) {
1232
1441
  Logger.error(`[${this.serverName}] Error stopping HTTP server:`, err);
1233
1442
  reject(err);
1234
1443
  return;
1235
1444
  }
1236
1445
  Logger.log(`[${this.serverName}] HTTP server stopped.`);
1237
- this.httpServer = null;
1238
- const closingSessions = Object.values(this.sessions).map((context) => {
1239
- if (context.transport.sessionId) {
1240
- delete this.sessions[context.transport.sessionId];
1241
- }
1242
- return Promise.resolve();
1243
- });
1244
- Promise.all(closingSessions).then(() => {
1245
- Logger.log(`[${this.serverName}] All transports closed.`);
1246
- resolve2();
1247
- }).catch((transportCloseErr) => {
1248
- Logger.error(`[${this.serverName}] Error during bulk transport closing:`, transportCloseErr);
1249
- reject(transportCloseErr);
1250
- });
1446
+ resolve6();
1251
1447
  });
1252
1448
  });
1253
1449
  }
@@ -1256,89 +1452,648 @@ var BaseMcpServer = class {
1256
1452
  // src/cli.ts
1257
1453
  import { fileURLToPath } from "url";
1258
1454
  import { dirname } from "path";
1259
- import { readFileSync, writeFileSync, existsSync } from "fs";
1455
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
1260
1456
  import { createInterface } from "readline";
1261
1457
 
1262
- // src/utils/autoUpdate.ts
1263
- var AUTO_UPDATE_DISABLED_ENV = "MCP_AUTO_UPDATE_DISABLED";
1264
- var DEFAULT_PACKAGE_NAME = "mcp-google-map";
1265
- var DEFAULT_REGISTRY_BASE_URL = "https://registry.npmjs.org";
1266
- var DEFAULT_UPDATE_CHECK_TIMEOUT_MS = 2e3;
1267
- function isAutoUpdateDisabled(env = process.env) {
1268
- const raw = env[AUTO_UPDATE_DISABLED_ENV];
1269
- return typeof raw === "string" && /^(1|true|yes)$/i.test(raw.trim());
1458
+ // src/utils/toolProfiles.ts
1459
+ var TOOL_API_PROFILES = {
1460
+ maps_geocode: { tool: "maps_geocode", apis: ["geocoding"], dependsOnTools: [] },
1461
+ maps_reverse_geocode: { tool: "maps_reverse_geocode", apis: ["geocoding"], dependsOnTools: [] },
1462
+ maps_batch_geocode: { tool: "maps_batch_geocode", apis: ["geocoding"], dependsOnTools: ["maps_geocode"] },
1463
+ maps_search_nearby: {
1464
+ tool: "maps_search_nearby",
1465
+ apis: ["places-new", "geocoding"],
1466
+ dependsOnTools: ["maps_geocode"]
1467
+ },
1468
+ maps_search_places: { tool: "maps_search_places", apis: ["places-new"], dependsOnTools: [] },
1469
+ maps_place_details: { tool: "maps_place_details", apis: ["places-new"], dependsOnTools: [] },
1470
+ maps_directions: { tool: "maps_directions", apis: ["routes"], dependsOnTools: [] },
1471
+ maps_distance_matrix: { tool: "maps_distance_matrix", apis: ["routes"], dependsOnTools: [] },
1472
+ maps_elevation: { tool: "maps_elevation", apis: ["elevation"], dependsOnTools: [] },
1473
+ maps_timezone: { tool: "maps_timezone", apis: ["timezone"], dependsOnTools: [] },
1474
+ maps_weather: { tool: "maps_weather", apis: ["weather"], dependsOnTools: [] },
1475
+ maps_air_quality: { tool: "maps_air_quality", apis: ["air-quality"], dependsOnTools: [] },
1476
+ maps_static_map: { tool: "maps_static_map", apis: ["static-maps"], dependsOnTools: [] },
1477
+ maps_explore_area: {
1478
+ tool: "maps_explore_area",
1479
+ apis: ["places-new", "geocoding"],
1480
+ dependsOnTools: ["maps_geocode", "maps_search_nearby", "maps_place_details"]
1481
+ },
1482
+ maps_plan_route: {
1483
+ tool: "maps_plan_route",
1484
+ apis: ["routes", "geocoding", "places-new"],
1485
+ dependsOnTools: ["maps_geocode", "maps_search_places", "maps_directions"]
1486
+ },
1487
+ maps_compare_places: {
1488
+ tool: "maps_compare_places",
1489
+ apis: ["places-new", "routes"],
1490
+ dependsOnTools: ["maps_search_places", "maps_place_details", "maps_distance_matrix"]
1491
+ },
1492
+ maps_search_along_route: {
1493
+ tool: "maps_search_along_route",
1494
+ apis: ["routes", "places-new"],
1495
+ dependsOnTools: ["maps_directions", "maps_search_places"]
1496
+ },
1497
+ maps_local_rank_tracker: {
1498
+ tool: "maps_local_rank_tracker",
1499
+ apis: ["places-new"],
1500
+ dependsOnTools: ["maps_place_details", "maps_search_places"]
1501
+ }
1502
+ };
1503
+ function describeToolsProfile(toolNames) {
1504
+ const tools = [];
1505
+ const apiOrder = [];
1506
+ const seenApis = /* @__PURE__ */ new Set();
1507
+ const seenTools = /* @__PURE__ */ new Set();
1508
+ for (const name of toolNames) {
1509
+ if (seenTools.has(name)) {
1510
+ continue;
1511
+ }
1512
+ const profile = TOOL_API_PROFILES[name];
1513
+ if (!profile) {
1514
+ continue;
1515
+ }
1516
+ seenTools.add(name);
1517
+ tools.push({
1518
+ tool: profile.tool,
1519
+ apis: [...profile.apis],
1520
+ dependsOnTools: [...profile.dependsOnTools]
1521
+ });
1522
+ for (const api of profile.apis) {
1523
+ if (!seenApis.has(api)) {
1524
+ seenApis.add(api);
1525
+ apiOrder.push(api);
1526
+ }
1527
+ }
1528
+ }
1529
+ return { toolCount: tools.length, apis: apiOrder, tools };
1270
1530
  }
1271
- function isNewerVersion(candidate, current) {
1272
- const toSegments = (version) => version.trim().split(".").map((segment) => /^\d+$/.test(segment) ? Number(segment) : Number.NaN);
1273
- const candidateSegments = toSegments(candidate);
1274
- const currentSegments = toSegments(current);
1275
- if (candidateSegments.some((segment) => !Number.isInteger(segment)) || currentSegments.some((segment) => !Number.isInteger(segment))) {
1276
- return false;
1531
+
1532
+ // src/utils/clientSnippets.ts
1533
+ var API_KEY_PLACEHOLDER = "<your-google-maps-api-key>";
1534
+ var SNIPPET_DEFAULT_HOST = "127.0.0.1";
1535
+ var SNIPPET_DEFAULT_PORT = 3e3;
1536
+ var SNIPPET_CLIENT_TARGETS = {
1537
+ generic: "your MCP client configuration file",
1538
+ claude: "Claude Desktop: claude_desktop_config.json",
1539
+ cursor: "Cursor: .cursor/mcp.json",
1540
+ pi: "pi CLI: ~/.pi/agent/mcp.json (user) or .pi/mcp.json (project)"
1541
+ };
1542
+ function renderClientSnippet(options = {}) {
1543
+ const client = options.client ?? "generic";
1544
+ const transport = options.transport ?? "stdio";
1545
+ const serverName = options.serverName ?? "mcp-google-map";
1546
+ const notes = [`Paste this snippet into ${SNIPPET_CLIENT_TARGETS[client]}.`];
1547
+ notes.push("This command prints the snippet only; it never writes or edits configuration files.");
1548
+ let entry;
1549
+ if (transport === "http") {
1550
+ const host = options.host ?? SNIPPET_DEFAULT_HOST;
1551
+ const port = options.port ?? SNIPPET_DEFAULT_PORT;
1552
+ entry = { url: `http://${host}:${port}/mcp` };
1553
+ notes.push(`Start the server first: mcp-google-map --host ${host} --port ${port}.`);
1554
+ } else {
1555
+ const launcher = options.launcher ?? "npx";
1556
+ if (launcher === "node" && !options.commandPath) {
1557
+ throw new Error("--launcher node requires --command <path> pointing at dist/cli.js");
1558
+ }
1559
+ const command = launcher === "npx" ? "npx" : launcher === "bin" ? "mcp-google-map" : "node";
1560
+ const args = launcher === "npx" ? ["-y", "mcp-google-map", "--stdio"] : launcher === "bin" ? ["--stdio"] : [options.commandPath, "--stdio"];
1561
+ if (launcher === "npx") {
1562
+ notes.push("The npx launcher runs the published package; npm must be available on PATH.");
1563
+ }
1564
+ const apiKeyValue = options.envKey ? `\${${options.envKey}}` : API_KEY_PLACEHOLDER;
1565
+ entry = { command, args, env: { GOOGLE_MAPS_API_KEY: apiKeyValue } };
1566
+ if (options.envKey) {
1567
+ notes.push(
1568
+ `Variable references (\${${options.envKey}}) are client-specific: confirm your client expands environment variables before relying on them.`
1569
+ );
1570
+ } else {
1571
+ notes.push(`Replace ${API_KEY_PLACEHOLDER} with your Google Maps API key.`);
1572
+ }
1277
1573
  }
1278
- const length = Math.max(candidateSegments.length, currentSegments.length);
1279
- for (let index = 0; index < length; index += 1) {
1280
- const candidatePart = candidateSegments[index] ?? 0;
1281
- const currentPart = currentSegments[index] ?? 0;
1282
- if (candidatePart !== currentPart) {
1283
- return candidatePart > currentPart;
1574
+ const json = JSON.stringify({ mcpServers: { [serverName]: entry } }, null, 2);
1575
+ return { json, notes };
1576
+ }
1577
+
1578
+ // src/utils/cliSettings.ts
1579
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
1580
+ import { join, resolve } from "path";
1581
+ var SETTINGS_PROGRAM_NAME = "mcp-google-map";
1582
+ var CLI_SETTINGS_RELATIVE = [".config", SETTINGS_PROGRAM_NAME, "config.json"];
1583
+ var MCP_SERVER_NAME = "mcp-google-map";
1584
+ function resolveCliSettingsPath(home) {
1585
+ const root = home ?? (process.env.HOME ?? process.cwd());
1586
+ return resolve(root, ...CLI_SETTINGS_RELATIVE);
1587
+ }
1588
+ function uniqueStrings(value) {
1589
+ if (!Array.isArray(value)) return [];
1590
+ const result = [];
1591
+ for (const item of value) {
1592
+ if (typeof item === "string" && item.length > 0 && !result.includes(item)) {
1593
+ result.push(item);
1284
1594
  }
1285
1595
  }
1286
- return false;
1596
+ return result;
1287
1597
  }
1288
- async function checkForUpdates(deps = {}) {
1289
- const logger = deps.logger ?? Logger;
1290
- const env = deps.env ?? process.env;
1598
+ function loadCliSettings(home) {
1291
1599
  try {
1292
- if (isAutoUpdateDisabled(env)) {
1293
- return null;
1600
+ const raw = JSON.parse(readFileSync(resolveCliSettingsPath(home), "utf-8"));
1601
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
1602
+ return { enabled: [] };
1294
1603
  }
1295
- const currentVersion = deps.currentVersion;
1296
- if (!currentVersion) {
1297
- logger.error(
1298
- `\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check skipped: running package version is unknown.`
1299
- );
1300
- return null;
1604
+ return { enabled: uniqueStrings(raw.enabled) };
1605
+ } catch {
1606
+ return { enabled: [] };
1607
+ }
1608
+ }
1609
+ function saveCliSettings(settings, home) {
1610
+ const path = resolveCliSettingsPath(home);
1611
+ const temporary = `${path}.tmp`;
1612
+ try {
1613
+ mkdirSync(join(path, ".."), { recursive: true });
1614
+ writeFileSync(temporary, JSON.stringify({ enabled: uniqueStrings(settings.enabled) }, null, 2) + "\n", "utf-8");
1615
+ renameSync(temporary, path);
1616
+ } catch (error) {
1617
+ throw new Error(`settings write failed: ${error instanceof Error ? error.message : String(error)}`);
1618
+ }
1619
+ return path;
1620
+ }
1621
+
1622
+ // src/utils/cliTargets.ts
1623
+ import {
1624
+ cpSync,
1625
+ existsSync as existsSync2,
1626
+ mkdirSync as mkdirSync2,
1627
+ readFileSync as readFileSync2,
1628
+ readdirSync,
1629
+ renameSync as renameSync2,
1630
+ rmSync,
1631
+ statSync,
1632
+ writeFileSync as writeFileSync2
1633
+ } from "fs";
1634
+ import { join as join2, relative } from "path";
1635
+ function homeRoot(home) {
1636
+ return home ?? (process.env.HOME ?? process.cwd());
1637
+ }
1638
+ var PI_TARGET = {
1639
+ id: "pi",
1640
+ configFormat: "json",
1641
+ mcpConfigPath: (home) => join2(homeRoot(home), ".pi", "agent", "mcp.json"),
1642
+ skillsDir: (home) => join2(homeRoot(home), ".pi", "agent", "skills")
1643
+ };
1644
+ var OPENCLAW_TARGET = {
1645
+ id: "openclaw",
1646
+ configFormat: "json",
1647
+ mcpConfigPath: (home) => join2(homeRoot(home), ".openclaw", "openclaw.json"),
1648
+ skillsDir: (home) => join2(homeRoot(home), ".openclaw", "workspace", "skills")
1649
+ };
1650
+ var CODEX_TARGET = {
1651
+ id: "codex",
1652
+ configFormat: "toml",
1653
+ mcpConfigPath: (home) => join2(homeRoot(home), ".codex", "config.toml"),
1654
+ skillsDir: (home) => join2(homeRoot(home), ".codex", "skills")
1655
+ };
1656
+ var ANTIGRAVITY_TARGET = {
1657
+ id: "antigravity",
1658
+ configFormat: "json",
1659
+ mcpConfigPath: (home) => join2(homeRoot(home), ".gemini", "settings.json"),
1660
+ skillsDir: (home) => join2(homeRoot(home), ".gemini", "config", "skills")
1661
+ };
1662
+ var CLAUDE_TARGET = {
1663
+ id: "claude",
1664
+ configFormat: "json",
1665
+ mcpConfigPath: (home) => join2(homeRoot(home), ".claude.json"),
1666
+ skillsDir: (home) => join2(homeRoot(home), ".claude", "skills")
1667
+ };
1668
+ var CLI_TARGETS = {
1669
+ pi: PI_TARGET,
1670
+ openclaw: OPENCLAW_TARGET,
1671
+ codex: CODEX_TARGET,
1672
+ antigravity: ANTIGRAVITY_TARGET,
1673
+ claude: CLAUDE_TARGET
1674
+ };
1675
+ function resolveCliTarget(name) {
1676
+ return CLI_TARGETS[name.trim().toLowerCase()];
1677
+ }
1678
+ function supportedCliNames() {
1679
+ return Object.keys(CLI_TARGETS).join(", ");
1680
+ }
1681
+ function readJsonConfig(configPath) {
1682
+ if (!existsSync2(configPath)) return {};
1683
+ const parsed = JSON.parse(readFileSync2(configPath, "utf-8"));
1684
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1685
+ throw new Error(`configuration is not a JSON object: ${configPath}`);
1686
+ }
1687
+ return parsed;
1688
+ }
1689
+ function writeJsonConfig(configPath, value) {
1690
+ const temporary = `${configPath}.tmp`;
1691
+ mkdirSync2(join2(configPath, ".."), { recursive: true });
1692
+ writeFileSync2(temporary, JSON.stringify(value, null, 2) + "\n", "utf-8");
1693
+ renameSync2(temporary, configPath);
1694
+ }
1695
+ function writeJsonMcpServerEntry(configPath, serverName, entry) {
1696
+ const config = readJsonConfig(configPath);
1697
+ const mcpServers = config.mcpServers ?? {};
1698
+ if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
1699
+ throw new Error(`mcpServers is not an object: ${configPath}`);
1700
+ }
1701
+ mcpServers[serverName] = entry;
1702
+ config.mcpServers = mcpServers;
1703
+ writeJsonConfig(configPath, config);
1704
+ }
1705
+ function removeJsonMcpServerEntry(configPath, serverName) {
1706
+ if (!existsSync2(configPath)) return false;
1707
+ const config = readJsonConfig(configPath);
1708
+ const mcpServers = config.mcpServers;
1709
+ if (mcpServers === void 0) return false;
1710
+ if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
1711
+ throw new Error(`mcpServers is not an object: ${configPath}`);
1712
+ }
1713
+ const map = mcpServers;
1714
+ if (!(serverName in map)) return false;
1715
+ delete map[serverName];
1716
+ config.mcpServers = map;
1717
+ writeJsonConfig(configPath, config);
1718
+ return true;
1719
+ }
1720
+ function readJsonMcpServerEntry(configPath, serverName) {
1721
+ try {
1722
+ const config = readJsonConfig(configPath);
1723
+ const mcpServers = config.mcpServers;
1724
+ if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
1725
+ return void 0;
1301
1726
  }
1302
- const packageName = deps.packageName ?? DEFAULT_PACKAGE_NAME;
1303
- const registryBaseUrl = deps.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL;
1304
- const timeoutMs = deps.timeoutMs ?? DEFAULT_UPDATE_CHECK_TIMEOUT_MS;
1305
- const fetchImpl = deps.fetchImpl ?? fetch;
1306
- const response = await fetchImpl(`${registryBaseUrl}/${packageName}/latest`, {
1307
- headers: { accept: "application/vnd.npm.install-v1+json" },
1308
- signal: AbortSignal.timeout(timeoutMs)
1309
- });
1310
- if (!response.ok) {
1311
- logger.error(
1312
- `\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry responded with HTTP ${response.status}.`
1313
- );
1314
- return null;
1727
+ return mcpServers[serverName];
1728
+ } catch {
1729
+ return void 0;
1730
+ }
1731
+ }
1732
+ function tomlString(value) {
1733
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1734
+ }
1735
+ function tomlSectionHeader(serverName) {
1736
+ return `[mcp_servers.${serverName}]`;
1737
+ }
1738
+ function removeTomlSection(content, serverName) {
1739
+ const lines = content.split("\n");
1740
+ const header = tomlSectionHeader(serverName);
1741
+ const kept = [];
1742
+ let skipping = false;
1743
+ for (const line of lines) {
1744
+ if (skipping) {
1745
+ if (line.trimStart().startsWith("[")) {
1746
+ skipping = false;
1747
+ } else {
1748
+ continue;
1749
+ }
1750
+ } else if (line.trim() === header) {
1751
+ skipping = true;
1752
+ while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
1753
+ kept.pop();
1754
+ }
1755
+ continue;
1315
1756
  }
1316
- const payload = await response.json();
1317
- if (typeof payload?.version !== "string") {
1318
- logger.error(
1319
- `\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry response has no version field.`
1320
- );
1321
- return null;
1757
+ kept.push(line);
1758
+ }
1759
+ while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
1760
+ kept.pop();
1761
+ }
1762
+ return kept.length > 0 ? kept.join("\n") + "\n" : "";
1763
+ }
1764
+ function hasTomlSection(content, serverName) {
1765
+ return content.split("\n").some((line) => line.trim() === tomlSectionHeader(serverName));
1766
+ }
1767
+ function registrationToTomlLines(entry) {
1768
+ const lines = [`command = ${tomlString(entry.command)}`];
1769
+ if (entry.args !== void 0) {
1770
+ lines.push(`args = [${entry.args.map((arg) => tomlString(arg)).join(", ")}]`);
1771
+ }
1772
+ if (entry.env !== void 0) {
1773
+ const pairs = Object.entries(entry.env).map(([key, value]) => `${tomlString(key)} = ${tomlString(value)}`);
1774
+ if (pairs.length > 0) lines.push(`env = { ${pairs.join(", ")} }`);
1775
+ }
1776
+ return lines;
1777
+ }
1778
+ function writeTomlMcpServerSection(configPath, serverName, entry) {
1779
+ const previous = existsSync2(configPath) ? readFileSync2(configPath, "utf-8") : "";
1780
+ const base = removeTomlSection(previous, serverName);
1781
+ const table = [tomlSectionHeader(serverName), ...registrationToTomlLines(entry), ""].join("\n");
1782
+ const content = base.length > 0 ? base.replace(/\n*$/, "\n") + table : table;
1783
+ const temporary = `${configPath}.tmp`;
1784
+ mkdirSync2(join2(configPath, ".."), { recursive: true });
1785
+ writeFileSync2(temporary, content, "utf-8");
1786
+ renameSync2(temporary, configPath);
1787
+ }
1788
+ function removeTomlMcpServerSection(configPath, serverName) {
1789
+ if (!existsSync2(configPath)) return false;
1790
+ const previous = readFileSync2(configPath, "utf-8");
1791
+ if (!hasTomlSection(previous, serverName)) return false;
1792
+ const content = removeTomlSection(previous, serverName);
1793
+ const temporary = `${configPath}.tmp`;
1794
+ writeFileSync2(temporary, content, "utf-8");
1795
+ renameSync2(temporary, configPath);
1796
+ return true;
1797
+ }
1798
+ function hasTomlMcpServerSection(configPath, serverName) {
1799
+ if (!existsSync2(configPath)) return false;
1800
+ return hasTomlSection(readFileSync2(configPath, "utf-8"), serverName);
1801
+ }
1802
+ function listFilesRecursive(root) {
1803
+ if (!existsSync2(root)) return [];
1804
+ const files = [];
1805
+ const walk = (dir) => {
1806
+ for (const entry of readdirSync(dir)) {
1807
+ const full = join2(dir, entry);
1808
+ if (statSync(full).isDirectory()) {
1809
+ walk(full);
1810
+ } else {
1811
+ files.push(relative(root, full).split("\\").join("/"));
1812
+ }
1322
1813
  }
1323
- if (!isNewerVersion(payload.version, currentVersion)) {
1324
- return null;
1814
+ };
1815
+ walk(root);
1816
+ return files;
1817
+ }
1818
+ function syncSkillTree(source, destination) {
1819
+ mkdirSync2(destination, { recursive: true });
1820
+ cpSync(source, destination, { recursive: true, force: true });
1821
+ const expected = new Set(listFilesRecursive(source));
1822
+ const existing = listFilesRecursive(destination);
1823
+ existing.sort((a, b) => b.length - a.length);
1824
+ for (const rel of existing) {
1825
+ if (expected.has(rel)) continue;
1826
+ rmSync(join2(destination, rel), { recursive: true, force: true });
1827
+ }
1828
+ }
1829
+ function removeSkillDir(skillDir) {
1830
+ rmSync(skillDir, { recursive: true, force: true });
1831
+ }
1832
+ function compareSkillTree(source, destination) {
1833
+ const packaged = listFilesRecursive(source);
1834
+ if (packaged.length === 0) return "MISSING";
1835
+ if (!existsSync2(destination)) return "MISSING";
1836
+ for (const rel of packaged) {
1837
+ const target = join2(destination, ...rel.split("/"));
1838
+ if (!existsSync2(target)) return "MISSING";
1839
+ if (readFileSync2(target).toString("utf-8") !== readFileSync2(join2(source, ...rel.split("/"))).toString("utf-8")) {
1840
+ return "STALE";
1325
1841
  }
1326
- logger.log(
1327
- `\u26A0\uFE0F Update available: ${packageName} ${currentVersion} \u2192 ${payload.version}. Upgrade with: npm install -g ${packageName}@latest (or run npx -y ${packageName}@latest).`
1328
- );
1329
- return { currentVersion, latestVersion: payload.version };
1842
+ }
1843
+ return "OK";
1844
+ }
1845
+ function isServerRegistered(target, serverName, home) {
1846
+ const configPath = target.mcpConfigPath(home);
1847
+ if (target.configFormat === "toml") {
1848
+ return hasTomlMcpServerSection(configPath, serverName);
1849
+ }
1850
+ return readJsonMcpServerEntry(configPath, serverName) !== void 0;
1851
+ }
1852
+ function applyServerRegistration(target, serverName, entry, home) {
1853
+ const configPath = target.mcpConfigPath(home);
1854
+ if (target.configFormat === "toml") {
1855
+ if (entry === void 0) {
1856
+ removeTomlMcpServerSection(configPath, serverName);
1857
+ } else {
1858
+ writeTomlMcpServerSection(configPath, serverName, entry);
1859
+ }
1860
+ return;
1861
+ }
1862
+ if (entry === void 0) {
1863
+ removeJsonMcpServerEntry(configPath, serverName);
1864
+ } else {
1865
+ writeJsonMcpServerEntry(configPath, serverName, entry);
1866
+ }
1867
+ }
1868
+
1869
+ // src/utils/cliInstaller.ts
1870
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1871
+ import { join as join3, resolve as resolve3 } from "path";
1872
+ function buildServerRegistration(apiKey) {
1873
+ const env = {};
1874
+ if (apiKey !== void 0 && apiKey.length > 0) {
1875
+ env.GOOGLE_MAPS_API_KEY = apiKey;
1876
+ }
1877
+ return { command: "npx", args: ["-y", MCP_SERVER_NAME, "--stdio"], env };
1878
+ }
1879
+ function resolveSkillsResourcesRoot(packageRoot) {
1880
+ const candidate = resolve3(packageRoot, "src", "resources", "skills");
1881
+ if (!existsSync3(candidate)) {
1882
+ throw new Error(`skills resources root not found: ${candidate}`);
1883
+ }
1884
+ return candidate;
1885
+ }
1886
+ function loadSkillsManifest(resourcesRoot) {
1887
+ const manifestPath = join3(resourcesRoot, "manifest.json");
1888
+ if (!existsSync3(manifestPath)) {
1889
+ throw new Error(`skills manifest not found: ${manifestPath}`);
1890
+ }
1891
+ let parsed;
1892
+ try {
1893
+ parsed = JSON.parse(readFileSync3(manifestPath, "utf-8"));
1330
1894
  } catch (error) {
1331
- const message = error instanceof Error ? error.message : String(error);
1332
- logger.error(`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: ${message}`);
1333
- return null;
1895
+ throw new Error(`skills manifest is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
1896
+ }
1897
+ const skills = parsed?.skills;
1898
+ if (!Array.isArray(skills)) {
1899
+ throw new Error(`skills manifest has no skills array: ${manifestPath}`);
1900
+ }
1901
+ return skills.map((entry) => {
1902
+ const skill = entry;
1903
+ if (typeof skill.id !== "string" || typeof skill.path !== "string") {
1904
+ throw new Error(`skills manifest entry is missing id or path: ${manifestPath}`);
1905
+ }
1906
+ if (!existsSync3(join3(resourcesRoot, skill.path))) {
1907
+ throw new Error(`manifest skill file not found: ${join3(resourcesRoot, skill.path)}`);
1908
+ }
1909
+ return {
1910
+ id: skill.id,
1911
+ name: skill.name ?? skill.id,
1912
+ version: skill.version ?? "",
1913
+ path: skill.path,
1914
+ description: skill.description ?? ""
1915
+ };
1916
+ });
1917
+ }
1918
+ function installEnabledCliResources(options) {
1919
+ const manifest = loadSkillsManifest(options.resourcesRoot);
1920
+ const entry = buildServerRegistration(options.apiKey);
1921
+ const result = { summaries: [], errors: [] };
1922
+ for (const cli of loadCliSettings(options.home).enabled) {
1923
+ const target = CLI_TARGETS[cli];
1924
+ if (target === void 0) {
1925
+ result.errors.push(`Unsupported CLI: ${cli}`);
1926
+ continue;
1927
+ }
1928
+ try {
1929
+ applyServerRegistration(target, MCP_SERVER_NAME, entry, options.home);
1930
+ let skills = 0;
1931
+ for (const skill of manifest) {
1932
+ syncSkillTree(join3(options.resourcesRoot, skill.id), join3(target.skillsDir(options.home), skill.id));
1933
+ skills += 1;
1934
+ }
1935
+ result.summaries.push({ cli: target.id, server: 1, skills });
1936
+ } catch (error) {
1937
+ result.errors.push(`${target.id}: ${error instanceof Error ? error.message : String(error)}`);
1938
+ }
1939
+ }
1940
+ return result;
1941
+ }
1942
+ function uninstallEnabledCliResources(options) {
1943
+ const manifest = loadSkillsManifest(options.resourcesRoot);
1944
+ const result = { summaries: [], errors: [] };
1945
+ for (const cli of loadCliSettings(options.home).enabled) {
1946
+ const target = CLI_TARGETS[cli];
1947
+ if (target === void 0) {
1948
+ result.errors.push(`Unsupported CLI: ${cli}`);
1949
+ continue;
1950
+ }
1951
+ try {
1952
+ applyServerRegistration(target, MCP_SERVER_NAME, void 0, options.home);
1953
+ let skills = 0;
1954
+ for (const skill of manifest) {
1955
+ removeSkillDir(join3(target.skillsDir(options.home), skill.id));
1956
+ skills += 1;
1957
+ }
1958
+ result.summaries.push({ cli: target.id, server: 0, skills });
1959
+ } catch (error) {
1960
+ result.errors.push(`${target.id}: ${error instanceof Error ? error.message : String(error)}`);
1961
+ }
1962
+ }
1963
+ return result;
1964
+ }
1965
+
1966
+ // src/utils/cliShow.ts
1967
+ import { join as join4 } from "path";
1968
+ import { relative as relative2, resolve as resolve4 } from "path";
1969
+ var MAX_CELL_WIDTH = 48;
1970
+ var STATUS_OK = "OK";
1971
+ var STATUS_MISSING = "MISSING";
1972
+ function wrapCell(text, width) {
1973
+ const normalized = String(text).split(/\s+/).filter((word) => word.length > 0);
1974
+ if (normalized.length === 0) return [""];
1975
+ const lines = [];
1976
+ let current = "";
1977
+ for (let word of normalized) {
1978
+ while (word.length > width) {
1979
+ if (current.length > 0) {
1980
+ lines.push(current);
1981
+ current = "";
1982
+ }
1983
+ lines.push(word.slice(0, width));
1984
+ word = word.slice(width);
1985
+ }
1986
+ if (current.length === 0) {
1987
+ current = word;
1988
+ } else if (current.length + 1 + word.length <= width) {
1989
+ current = `${current} ${word}`;
1990
+ } else {
1991
+ lines.push(current);
1992
+ current = word;
1993
+ }
1994
+ }
1995
+ if (current.length > 0) lines.push(current);
1996
+ return lines.length > 0 ? lines : [""];
1997
+ }
1998
+ function renderAsciiTable(headers, rows, maxWidth = MAX_CELL_WIDTH) {
1999
+ const widths = headers.map((header, index) => {
2000
+ const lengths = [header.length, ...rows.map((row) => (row[index] ?? "").length)];
2001
+ return Math.max(1, Math.min(Math.max(...lengths), Math.max(1, maxWidth)));
2002
+ });
2003
+ const border = "+" + widths.map((width) => "-".repeat(width + 2)).join("+") + "+";
2004
+ const renderRow = (cells) => {
2005
+ const wrapped = cells.map((cell, index) => wrapCell(cell ?? "", widths[index]));
2006
+ const height = Math.max(...wrapped.map((part) => part.length));
2007
+ const lines = [];
2008
+ for (let line = 0; line < height; line += 1) {
2009
+ const parts = wrapped.map((part, index) => (part[line] ?? "").padEnd(widths[index]));
2010
+ lines.push("| " + parts.join(" | ") + " |");
2011
+ }
2012
+ return lines;
2013
+ };
2014
+ const output = [border, ...renderRow(headers), border];
2015
+ for (const row of rows) {
2016
+ output.push(...renderRow(row));
2017
+ output.push(border);
2018
+ }
2019
+ return output.join("\n");
2020
+ }
2021
+ function buildCliRows(home) {
2022
+ const root = home ?? (process.env.HOME ?? process.cwd());
2023
+ const enabled = new Set(loadCliSettings(home).enabled);
2024
+ return Object.keys(CLI_TARGETS).map((id) => {
2025
+ const target = CLI_TARGETS[id];
2026
+ return [
2027
+ target.id,
2028
+ enabled.has(id) ? "yes" : "no",
2029
+ relative2(resolve4(root), resolve4(target.mcpConfigPath(home))).split("\\").join("/"),
2030
+ relative2(resolve4(root), resolve4(target.skillsDir(home))).split("\\").join("/")
2031
+ ];
2032
+ });
2033
+ }
2034
+ function buildInstallRows(home, resourcesRoot) {
2035
+ const enabled = loadCliSettings(home).enabled;
2036
+ const manifest = loadSkillsManifest(resourcesRoot);
2037
+ const rows = [];
2038
+ for (const cli of enabled) {
2039
+ const target = CLI_TARGETS[cli];
2040
+ if (target === void 0) continue;
2041
+ rows.push([
2042
+ "Server",
2043
+ MCP_SERVER_NAME,
2044
+ target.id,
2045
+ "yes",
2046
+ isServerRegistered(target, MCP_SERVER_NAME, home) ? STATUS_OK : STATUS_MISSING
2047
+ ]);
2048
+ for (const skill of manifest) {
2049
+ rows.push([
2050
+ "Skill",
2051
+ skill.id,
2052
+ target.id,
2053
+ "yes",
2054
+ compareSkillTree(join4(resourcesRoot, skill.id), join4(target.skillsDir(home), skill.id))
2055
+ ]);
2056
+ }
1334
2057
  }
2058
+ return rows;
2059
+ }
2060
+ function buildCliShowReport(options) {
2061
+ return [
2062
+ "CLI Configuration",
2063
+ renderAsciiTable(["CLI", "Enabled", "MCP Config Path", "Skills Config Path"], buildCliRows(options.home)),
2064
+ "",
2065
+ "Installation Status",
2066
+ renderAsciiTable(["Type", "Name", "CLI", "Enabled", "Installed"], buildInstallRows(options.home, options.resourcesRoot))
2067
+ ].join("\n");
1335
2068
  }
1336
2069
 
1337
2070
  // src/cli.ts
1338
2071
  var __filename = fileURLToPath(import.meta.url);
1339
2072
  var __dirname = dirname(__filename);
1340
- dotenvConfig({ path: resolve(process.cwd(), ".env") });
1341
- dotenvConfig({ path: resolve(__dirname, "../.env") });
2073
+ function extractEnvFileOption(argv) {
2074
+ for (let i = 0; i < argv.length; i++) {
2075
+ const arg = argv[i];
2076
+ if (arg === "--env-file" && i + 1 < argv.length) {
2077
+ return argv[i + 1];
2078
+ }
2079
+ if (arg.startsWith("--env-file=")) {
2080
+ return arg.slice("--env-file=".length);
2081
+ }
2082
+ }
2083
+ return void 0;
2084
+ }
2085
+ function loadEnvironment(envFile) {
2086
+ if (envFile !== void 0) {
2087
+ const result = applyExplicitEnvFile(envFile);
2088
+ if (!result.ok) {
2089
+ process.stderr.write(JSON.stringify({ error: result.error }, null, 2) + "\n");
2090
+ process.exit(1);
2091
+ }
2092
+ return;
2093
+ }
2094
+ dotenvConfig2({ path: resolve5(process.cwd(), ".env") });
2095
+ dotenvConfig2({ path: resolve5(__dirname, "../.env") });
2096
+ }
1342
2097
  async function startServer(port, apiKey, host) {
1343
2098
  if (port) {
1344
2099
  process.env.MCP_SERVER_PORT = port.toString();
@@ -1355,36 +2110,69 @@ async function startServer(port, apiKey, host) {
1355
2110
  "\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."
1356
2111
  );
1357
2112
  Logger.log("");
1358
- const startPromises = config_default.map(async (config) => {
1359
- const portString = process.env[config.portEnvVar];
1360
- if (!portString) {
1361
- Logger.error(`\u26A0\uFE0F [${config.name}] Port environment variable ${config.portEnvVar} not set.`);
1362
- Logger.log(`\u{1F4A1} Please set ${config.portEnvVar} in your .env file or use --port parameter.`);
1363
- Logger.log(` Example: ${config.portEnvVar}=3000 or --port 3000`);
1364
- return;
1365
- }
1366
- const serverPort = Number(portString);
1367
- if (isNaN(serverPort) || serverPort <= 0) {
1368
- Logger.error(`\u274C [${config.name}] Invalid port number "${portString}" defined in ${config.portEnvVar}.`);
1369
- return;
1370
- }
1371
- try {
1372
- const server = new BaseMcpServer(config.name, filterTools(config.tools));
1373
- const serverHost = process.env.MCP_SERVER_HOST || "0.0.0.0";
1374
- Logger.log(`\u{1F527} [${config.name}] Initializing MCP Server in HTTP mode on ${serverHost}:${serverPort}...`);
1375
- await server.startHttpServer(serverPort, serverHost);
1376
- const displayHost = serverHost === "0.0.0.0" ? "localhost" : serverHost;
1377
- Logger.log(`\u2705 [${config.name}] MCP Server started successfully!`);
1378
- Logger.log(` \u{1F310} Endpoint: http://${displayHost}:${serverPort}/mcp`);
1379
- Logger.log(` \u{1F4DA} Tools: ${config.tools.length} available`);
1380
- } catch (error) {
1381
- Logger.error(`\u274C [${config.name}] Failed to start MCP Server on port ${serverPort}:`, error);
1382
- }
1383
- });
1384
- await Promise.allSettled(startPromises);
2113
+ const servers = [];
2114
+ let started = 0;
2115
+ let failed = 0;
2116
+ await Promise.all(
2117
+ config_default.map(async (config) => {
2118
+ const portString = process.env[config.portEnvVar];
2119
+ if (!portString) {
2120
+ Logger.error(`\u26A0\uFE0F [${config.name}] Port environment variable ${config.portEnvVar} not set.`);
2121
+ Logger.log(`\u{1F4A1} Please set ${config.portEnvVar} in your .env file or use --port parameter.`);
2122
+ Logger.log(` Example: ${config.portEnvVar}=3000 or --port 3000`);
2123
+ failed++;
2124
+ return;
2125
+ }
2126
+ const serverPort = Number(portString);
2127
+ if (!Number.isInteger(serverPort) || serverPort < 1 || serverPort > 65535) {
2128
+ Logger.error(
2129
+ `\u274C [${config.name}] Invalid port number "${portString}" defined in ${config.portEnvVar} (expected integer 1-65535).`
2130
+ );
2131
+ failed++;
2132
+ return;
2133
+ }
2134
+ try {
2135
+ const activeTools = filterTools(config.tools);
2136
+ const server = new BaseMcpServer(config.name, activeTools);
2137
+ const serverHost = process.env.MCP_SERVER_HOST || "127.0.0.1";
2138
+ Logger.log(`\u{1F527} [${config.name}] Initializing MCP Server in HTTP mode on ${serverHost}:${serverPort}...`);
2139
+ await server.startHttpServer(serverPort, serverHost);
2140
+ servers.push(server);
2141
+ started++;
2142
+ const displayHost = serverHost === "0.0.0.0" ? "localhost" : serverHost;
2143
+ Logger.log(`\u2705 [${config.name}] MCP Server started successfully!`);
2144
+ Logger.log(` \u{1F310} Endpoint: http://${displayHost}:${serverPort}/mcp`);
2145
+ Logger.log(` \u{1F4DA} Tools: ${activeTools.length} available`);
2146
+ } catch (error) {
2147
+ Logger.error(`\u274C [${config.name}] Failed to start MCP Server on port ${serverPort}:`, error);
2148
+ failed++;
2149
+ }
2150
+ })
2151
+ );
2152
+ if (started > 0) {
2153
+ installShutdownHandlers(servers);
2154
+ }
1385
2155
  Logger.log("");
1386
- Logger.log("\u{1F389} Server initialization completed!");
2156
+ Logger.log(started > 0 ? "\u{1F389} Server initialization completed!" : "\u274C No MCP server instance started.");
1387
2157
  Logger.log("\u{1F4A1} Need help? Check the README.md for configuration details.");
2158
+ return { started, failed, servers };
2159
+ }
2160
+ function createShutdownHandler(servers, exitHook = (code) => process.exit(code)) {
2161
+ let invoked = false;
2162
+ return async (signal) => {
2163
+ if (invoked) {
2164
+ return;
2165
+ }
2166
+ invoked = true;
2167
+ Logger.log(`\u{1F4F6} Received ${signal}; shutting down MCP HTTP server...`);
2168
+ await Promise.allSettled(servers.map((server) => server.stopHttpServer()));
2169
+ exitHook(0);
2170
+ };
2171
+ }
2172
+ function installShutdownHandlers(servers) {
2173
+ const handler = createShutdownHandler(servers);
2174
+ process.on("SIGINT", () => void handler("SIGINT"));
2175
+ process.on("SIGTERM", () => void handler("SIGTERM"));
1388
2176
  }
1389
2177
  var EXEC_TOOLS = [
1390
2178
  "geocode",
@@ -1550,13 +2338,58 @@ async function runExecCommand(toolName, params, apiKey, runner = execTool, strea
1550
2338
  return 1;
1551
2339
  }
1552
2340
  }
1553
- async function runDoctor(packageVersion, apiKey, live = false, runner = execTool) {
1554
- const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
2341
+ var DOCTOR_PROBES = [
2342
+ { id: "geocoding", name: "geocoding-api", tool: "geocode", params: { address: "Tokyo Tower" } },
2343
+ { id: "places-new", name: "places-api-new", tool: "search-places", params: { query: "Tokyo Tower" } },
2344
+ {
2345
+ id: "routes",
2346
+ name: "routes-api",
2347
+ tool: "directions",
2348
+ params: { origin: "Tokyo Tower", destination: "Tokyo Station", mode: "walking" }
2349
+ }
2350
+ ];
2351
+ var DOCTOR_PROBE_ALIASES = {
2352
+ geocoding: "geocoding",
2353
+ "geocoding-api": "geocoding",
2354
+ "places-new": "places-new",
2355
+ places: "places-new",
2356
+ "places-api-new": "places-new",
2357
+ routes: "routes",
2358
+ "routes-api": "routes"
2359
+ };
2360
+ function parseDoctorProbes(raw) {
2361
+ if (raw === void 0) {
2362
+ return { probes: void 0 };
2363
+ }
2364
+ const items = raw.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
2365
+ if (items.length === 0) {
2366
+ return { probes: void 0, error: "--probes received no probe names (expected: geocoding, places-new, routes)" };
2367
+ }
2368
+ const probes = [];
2369
+ for (const item of items) {
2370
+ const id = DOCTOR_PROBE_ALIASES[item];
2371
+ if (!id) {
2372
+ return { probes: [], error: `unknown probe "${item}" (expected: geocoding, places-new, routes)` };
2373
+ }
2374
+ if (!probes.includes(id)) {
2375
+ probes.push(id);
2376
+ }
2377
+ }
2378
+ return { probes };
2379
+ }
2380
+ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool, options = {}) {
2381
+ const nodeVersion = options.nodeVersion ?? process.versions.node;
2382
+ const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
2383
+ const config = options.config ?? resolveRuntimeConfig(process.env);
2384
+ const allToolNames = options.allToolNames ?? config_default[0].tools.map((tool) => tool.name);
2385
+ const toolsResolution = resolveEnabledToolNames(allToolNames, config.enabledToolsRaw);
2386
+ const diagnostics = describeRuntimeConfig(config);
2387
+ const toolsProfile = describeToolsProfile(toolsResolution.names);
1555
2388
  const checks = [
1556
2389
  {
1557
2390
  name: "node",
1558
- status: nodeMajor >= 18 ? "pass" : "fail",
1559
- detail: `Node.js ${process.versions.node} detected; version 18 or newer is required.`
2391
+ status: nodeMajor >= NODE_ENGINE_FLOOR ? "pass" : "fail",
2392
+ detail: `Node.js ${nodeVersion} detected; version ${NODE_ENGINE_FLOOR} or newer is required (package.json engines).`
1560
2393
  },
1561
2394
  {
1562
2395
  name: "package",
@@ -1567,6 +2400,21 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
1567
2400
  name: "api-key",
1568
2401
  status: apiKey ? "pass" : "fail",
1569
2402
  detail: apiKey ? "GOOGLE_MAPS_API_KEY is configured (value hidden)." : "GOOGLE_MAPS_API_KEY is not configured. Set it in the environment before making API calls."
2403
+ },
2404
+ {
2405
+ name: "config",
2406
+ status: toolsResolution.fallback ? "fail" : "pass",
2407
+ detail: toolsResolution.fallback ? `GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools; the server falls back to all ${allToolNames.length} tools. Effective configuration: ${formatConfigDiagnostics(diagnostics)}` : `Effective configuration: ${formatConfigDiagnostics(diagnostics)}`
2408
+ },
2409
+ {
2410
+ name: "env-file",
2411
+ status: "pass",
2412
+ detail: config.envFile ? `Explicit environment file loaded: ${config.envFile}` : "No explicit --env-file requested; implicit .env discovery (current directory, then package directory) applies."
2413
+ },
2414
+ {
2415
+ name: "tools",
2416
+ status: "pass",
2417
+ detail: `${toolsProfile.toolCount} tool(s) enabled; Google APIs: ${toolsProfile.apis.join(", ") || "none"}; per-tool: ${toolsProfile.tools.map((tool) => `${tool.tool}[${tool.apis.join("+")}]`).join(", ")}`
1570
2418
  }
1571
2419
  ];
1572
2420
  if (!live) {
@@ -1582,16 +2430,8 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
1582
2430
  detail: "Live API checks require GOOGLE_MAPS_API_KEY."
1583
2431
  });
1584
2432
  } else {
1585
- const liveChecks = [
1586
- { name: "geocoding-api", tool: "geocode", params: { address: "Tokyo Tower" } },
1587
- { name: "places-api-new", tool: "search-places", params: { query: "Tokyo Tower" } },
1588
- {
1589
- name: "routes-api",
1590
- tool: "directions",
1591
- params: { origin: "Tokyo Tower", destination: "Tokyo Station", mode: "walking" }
1592
- }
1593
- ];
1594
- for (const check of liveChecks) {
2433
+ const selected = options.probes ? DOCTOR_PROBES.filter((probe) => options.probes.includes(probe.id)) : DOCTOR_PROBES;
2434
+ for (const check of selected) {
1595
2435
  try {
1596
2436
  const result = await runner(check.tool, check.params, apiKey);
1597
2437
  if (isExecFailureResponse(result)) {
@@ -1623,13 +2463,169 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
1623
2463
  checks
1624
2464
  };
1625
2465
  }
2466
+ async function runSetup(options, deps = {}) {
2467
+ const fsImpl = deps.fs ?? {
2468
+ existsSync: (path) => existsSync4(path),
2469
+ readFileSync: (path, encoding) => readFileSync4(path, encoding),
2470
+ writeFileSync: (path, data, encoding) => writeFileSync3(path, data, encoding)
2471
+ };
2472
+ const nodeVersion = deps.nodeVersion ?? process.versions.node;
2473
+ const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
2474
+ const config = deps.config ?? resolveRuntimeConfig(process.env);
2475
+ const examplePath = resolve5(options.packageDir, ".env.example");
2476
+ const envPath = resolve5(options.targetDir, ".env");
2477
+ const checks = [
2478
+ {
2479
+ name: "node",
2480
+ status: nodeMajor >= NODE_ENGINE_FLOOR ? "pass" : "fail",
2481
+ detail: `Node.js ${nodeVersion} detected; version ${NODE_ENGINE_FLOOR} or newer is required (package.json engines).`
2482
+ },
2483
+ {
2484
+ name: "env-template",
2485
+ status: fsImpl.existsSync(examplePath) ? "pass" : "fail",
2486
+ detail: fsImpl.existsSync(examplePath) ? `Template found: ${examplePath}` : `Template missing: ${examplePath} (expected next to the installed package).`
2487
+ }
2488
+ ];
2489
+ let created = false;
2490
+ if (options.writeEnv) {
2491
+ if (fsImpl.existsSync(envPath)) {
2492
+ checks.push({
2493
+ name: "env-file",
2494
+ status: "pass",
2495
+ detail: `${envPath} already exists; left untouched (no overwrite).`
2496
+ });
2497
+ } else if (!fsImpl.existsSync(examplePath)) {
2498
+ checks.push({
2499
+ name: "env-file",
2500
+ status: "fail",
2501
+ detail: `Cannot create ${envPath}: template ${examplePath} is missing.`
2502
+ });
2503
+ } else {
2504
+ try {
2505
+ fsImpl.writeFileSync(envPath, fsImpl.readFileSync(examplePath, "utf-8"), "utf-8");
2506
+ created = true;
2507
+ checks.push({
2508
+ name: "env-file",
2509
+ status: "pass",
2510
+ detail: `Created ${envPath} from template; edit it and set GOOGLE_MAPS_API_KEY.`
2511
+ });
2512
+ } catch (error) {
2513
+ checks.push({
2514
+ name: "env-file",
2515
+ status: "fail",
2516
+ detail: `Failed to create ${envPath}: ${error instanceof Error ? error.message : String(error)}`
2517
+ });
2518
+ }
2519
+ }
2520
+ } else {
2521
+ checks.push({
2522
+ name: "env-file",
2523
+ status: fsImpl.existsSync(envPath) ? "pass" : "skip",
2524
+ detail: fsImpl.existsSync(envPath) ? `${envPath} found.` : `${envPath} not found; rerun with --write-env to create it from the template.`
2525
+ });
2526
+ }
2527
+ checks.push({
2528
+ name: "api-key",
2529
+ status: config.apiKey ? "pass" : "fail",
2530
+ detail: config.apiKey ? `GOOGLE_MAPS_API_KEY is configured (source: ${config.apiKeySource}, value hidden).` : "GOOGLE_MAPS_API_KEY is not configured. Set it in the environment or in .env after scaffolding."
2531
+ });
2532
+ return {
2533
+ success: checks.every((check) => check.status !== "fail"),
2534
+ created,
2535
+ checks
2536
+ };
2537
+ }
2538
+ function runSnippetCommand(options, streams = { stdout: process.stdout, stderr: process.stderr }) {
2539
+ try {
2540
+ const snippet = renderClientSnippet(options);
2541
+ streams.stdout.write(snippet.json + "\n");
2542
+ for (const note of snippet.notes) {
2543
+ streams.stderr.write(`[snippet] ${note}
2544
+ `);
2545
+ }
2546
+ return 0;
2547
+ } catch (error) {
2548
+ streams.stderr.write(
2549
+ JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) + "\n"
2550
+ );
2551
+ return 1;
2552
+ }
2553
+ }
2554
+ function runEnableDisableCliCommand(name, enable, options = {}) {
2555
+ const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
2556
+ const target = resolveCliTarget(name);
2557
+ if (target === void 0) {
2558
+ streams.stderr.write(
2559
+ JSON.stringify({ error: `Unsupported CLI: ${name}. Supported: ${supportedCliNames()}.` }, null, 2) + "\n"
2560
+ );
2561
+ return 2;
2562
+ }
2563
+ const settings = loadCliSettings(options.home);
2564
+ const already = settings.enabled.includes(target.id);
2565
+ if (enable === already) {
2566
+ streams.stdout.write(
2567
+ JSON.stringify({ cli: target.id, enabled: enable, message: `${target.id} is already ${enable ? "enabled" : "disabled"}.` }, null, 2) + "\n"
2568
+ );
2569
+ return 0;
2570
+ }
2571
+ settings.enabled = enable ? [...settings.enabled, target.id] : settings.enabled.filter((item) => item !== target.id);
2572
+ saveCliSettings(settings, options.home);
2573
+ streams.stdout.write(
2574
+ JSON.stringify({ cli: target.id, enabled: enable, message: `${enable ? "Enabled" : "Disabled"} ${target.id}.` }, null, 2) + "\n"
2575
+ );
2576
+ return 0;
2577
+ }
2578
+ function runInstallUninstallCommand(operation, options) {
2579
+ const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
2580
+ if (loadCliSettings(options.home).enabled.length === 0) {
2581
+ streams.stdout.write(
2582
+ JSON.stringify(
2583
+ {
2584
+ success: true,
2585
+ message: "No CLI enabled. Use 'mcp-google-map enable <cli name>' first.",
2586
+ summaries: [],
2587
+ errors: []
2588
+ },
2589
+ null,
2590
+ 2
2591
+ ) + "\n"
2592
+ );
2593
+ return 0;
2594
+ }
2595
+ let result;
2596
+ try {
2597
+ result = operation === "install" ? installEnabledCliResources({ home: options.home, apiKey: options.apiKey, resourcesRoot: options.resourcesRoot }) : uninstallEnabledCliResources({ home: options.home, resourcesRoot: options.resourcesRoot });
2598
+ } catch (error) {
2599
+ streams.stderr.write(
2600
+ JSON.stringify({ success: false, errors: [error instanceof Error ? error.message : String(error)] }, null, 2) + "\n"
2601
+ );
2602
+ return 1;
2603
+ }
2604
+ streams.stdout.write(
2605
+ JSON.stringify({ success: result.errors.length === 0, operation, summaries: result.summaries, errors: result.errors }, null, 2) + "\n"
2606
+ );
2607
+ return result.errors.length > 0 ? 1 : 0;
2608
+ }
2609
+ function runShowCommand(options) {
2610
+ const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
2611
+ try {
2612
+ streams.stdout.write(buildCliShowReport(options) + "\n");
2613
+ return 0;
2614
+ } catch (error) {
2615
+ streams.stderr.write(
2616
+ JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) + "\n"
2617
+ );
2618
+ return 1;
2619
+ }
2620
+ }
1626
2621
  var isRunDirectly = process.argv[1] && (process.argv[1].endsWith("cli.ts") || process.argv[1].endsWith("cli.js") || process.argv[1].endsWith("mcp-google-map") || process.argv[1].includes("mcp-google-map"));
1627
2622
  var isMainModule = import.meta.url === `file://${process.argv[1]}`;
1628
2623
  if (isRunDirectly || isMainModule) {
2624
+ loadEnvironment(extractEnvFileOption(process.argv));
1629
2625
  let packageVersion = "0.0.0";
1630
2626
  try {
1631
- const packageJsonPath = resolve(__dirname, "../package.json");
1632
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2627
+ const packageJsonPath = resolve5(__dirname, "../package.json");
2628
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
1633
2629
  packageVersion = packageJson.version;
1634
2630
  } catch (e) {
1635
2631
  packageVersion = "0.0.0";
@@ -1641,6 +2637,9 @@ if (isRunDirectly || isMainModule) {
1641
2637
  type: "boolean",
1642
2638
  default: false,
1643
2639
  description: "Make billable test requests to Geocoding, Places (New), and Routes APIs"
2640
+ }).option("probes", {
2641
+ type: "string",
2642
+ description: "Comma-separated subset of live probes: geocoding, places-new, routes (default: all three)"
1644
2643
  }).option("apikey", {
1645
2644
  alias: "k",
1646
2645
  type: "string",
@@ -1648,13 +2647,175 @@ if (isRunDirectly || isMainModule) {
1648
2647
  default: process.env.GOOGLE_MAPS_API_KEY
1649
2648
  }).example([
1650
2649
  ["$0 doctor", "Check local runtime and API key configuration without network calls"],
1651
- ["$0 doctor --live", "Also verify three Google Maps APIs with billable requests"]
2650
+ ["$0 doctor --live", "Also verify three Google Maps APIs with billable requests"],
2651
+ ["$0 doctor --live --probes geocoding,routes", "Probe only the selected APIs"]
1652
2652
  ]),
1653
2653
  async (argv) => {
1654
- const report = await runDoctor(packageVersion, argv.apikey, argv.live);
2654
+ const parsedProbes = parseDoctorProbes(argv.probes);
2655
+ if (parsedProbes.error) {
2656
+ process.stderr.write(JSON.stringify({ error: parsedProbes.error }, null, 2) + "\n");
2657
+ process.exitCode = 1;
2658
+ return;
2659
+ }
2660
+ const report = await runDoctor(packageVersion, argv.apikey, argv.live, void 0, {
2661
+ probes: parsedProbes.probes,
2662
+ config: resolveRuntimeConfig(process.env, { envFile: extractEnvFileOption(process.argv) })
2663
+ });
1655
2664
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
1656
2665
  process.exitCode = report.success ? 0 : 1;
1657
2666
  }
2667
+ ).command(
2668
+ "setup",
2669
+ "Non-interactive onboarding: verify local readiness, optionally scaffold .env",
2670
+ (yargs2) => yargs2.option("write-env", {
2671
+ type: "boolean",
2672
+ default: false,
2673
+ description: "Create .env from .env.example in --env-dir when it does not exist (never overwrites)"
2674
+ }).option("env-dir", {
2675
+ type: "string",
2676
+ default: process.cwd(),
2677
+ description: "Directory where .env is looked up / created"
2678
+ }).example([
2679
+ ["$0 setup", "Check readiness without writing anything"],
2680
+ ["$0 setup --write-env", "Also scaffold .env from the package template"]
2681
+ ]),
2682
+ async (argv) => {
2683
+ const report = await runSetup({
2684
+ packageDir: resolve5(__dirname, ".."),
2685
+ targetDir: argv.envDir,
2686
+ writeEnv: argv.writeEnv
2687
+ });
2688
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
2689
+ process.exitCode = report.success ? 0 : 1;
2690
+ }
2691
+ ).command(
2692
+ "snippet",
2693
+ "Print an MCP client registration snippet (JSON on stdout; never writes files)",
2694
+ (yargs2) => yargs2.option("client", {
2695
+ type: "string",
2696
+ choices: ["generic", "claude", "cursor", "pi"],
2697
+ default: "generic",
2698
+ description: "Target client (selects the documented target file)"
2699
+ }).option("transport", {
2700
+ type: "string",
2701
+ choices: ["stdio", "http"],
2702
+ default: "stdio",
2703
+ description: "MCP transport of the snippet"
2704
+ }).option("launcher", {
2705
+ type: "string",
2706
+ choices: ["npx", "node", "bin"],
2707
+ default: "npx",
2708
+ description: "stdio launcher command"
2709
+ }).option("command", {
2710
+ type: "string",
2711
+ description: "Absolute path to dist/cli.js (required for --launcher node)"
2712
+ }).option("host", {
2713
+ type: "string",
2714
+ default: "127.0.0.1",
2715
+ description: "Host used in the URL (transport http)"
2716
+ }).option("port", {
2717
+ type: "number",
2718
+ default: 3e3,
2719
+ description: "Port used in the URL (transport http)"
2720
+ }).option("env-key", {
2721
+ type: "string",
2722
+ description: "Emit a ${NAME} variable reference instead of a literal placeholder (expansion is client-specific)"
2723
+ }).option("name", {
2724
+ type: "string",
2725
+ default: "mcp-google-map",
2726
+ description: "Server name inside mcpServers"
2727
+ }).example([
2728
+ ["$0 snippet --client claude", "stdio snippet using the npx launcher"],
2729
+ ["$0 snippet --client pi --launcher node --command /abs/path/dist/cli.js", "stdio snippet with an explicit artifact"],
2730
+ ["$0 snippet --transport http --port 3100", "HTTP URL snippet"]
2731
+ ]),
2732
+ (argv) => {
2733
+ process.exitCode = runSnippetCommand({
2734
+ client: argv.client,
2735
+ transport: argv.transport,
2736
+ launcher: argv.launcher,
2737
+ commandPath: argv.command,
2738
+ host: argv.host,
2739
+ port: argv.port,
2740
+ envKey: argv.envKey,
2741
+ serverName: argv.name
2742
+ });
2743
+ }
2744
+ ).command(
2745
+ "enable <cli>",
2746
+ "Enable MCP-server and skill installation for a supported CLI",
2747
+ (yargs2) => yargs2.positional("cli", {
2748
+ type: "string",
2749
+ describe: `CLI name: ${supportedCliNames()}`
2750
+ }).option("home", {
2751
+ type: "string",
2752
+ description: "Home-directory override for the settings file (default: user home)"
2753
+ }).example([["$0 enable pi", "Enable pi for install/uninstall/show"]]),
2754
+ (argv) => {
2755
+ process.exitCode = runEnableDisableCliCommand(argv.cli, true, {
2756
+ home: argv.home
2757
+ });
2758
+ }
2759
+ ).command(
2760
+ "disable <cli>",
2761
+ "Disable MCP-server and skill installation for a supported CLI",
2762
+ (yargs2) => yargs2.positional("cli", {
2763
+ type: "string",
2764
+ describe: `CLI name: ${supportedCliNames()}`
2765
+ }).option("home", {
2766
+ type: "string",
2767
+ description: "Home-directory override for the settings file (default: user home)"
2768
+ }).example([["$0 disable claude", "Disable claude for install/uninstall/show"]]),
2769
+ (argv) => {
2770
+ process.exitCode = runEnableDisableCliCommand(argv.cli, false, {
2771
+ home: argv.home
2772
+ });
2773
+ }
2774
+ ).command(
2775
+ "install",
2776
+ "Register the mcp-google-map stdio server and install packaged skills for every enabled CLI",
2777
+ (yargs2) => yargs2.option("home", {
2778
+ type: "string",
2779
+ description: "Home-directory override for CLI configuration paths (default: user home)"
2780
+ }).option("apikey", {
2781
+ alias: "k",
2782
+ type: "string",
2783
+ description: "Google Maps API key embedded in the registered entry (prefer GOOGLE_MAPS_API_KEY)",
2784
+ default: resolveRuntimeConfig(process.env, { envFile: extractEnvFileOption(process.argv) }).apiKey
2785
+ }).example([["$0 install", "Register the server and skills in every enabled CLI"]]),
2786
+ (argv) => {
2787
+ process.exitCode = runInstallUninstallCommand("install", {
2788
+ home: argv.home,
2789
+ apiKey: argv.apikey,
2790
+ resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
2791
+ });
2792
+ }
2793
+ ).command(
2794
+ "uninstall",
2795
+ "Remove the mcp-google-map server entry and managed skills from every enabled CLI",
2796
+ (yargs2) => yargs2.option("home", {
2797
+ type: "string",
2798
+ description: "Home-directory override for CLI configuration paths (default: user home)"
2799
+ }).example([["$0 uninstall", "Reciprocal removal of install for every enabled CLI"]]),
2800
+ (argv) => {
2801
+ process.exitCode = runInstallUninstallCommand("uninstall", {
2802
+ home: argv.home,
2803
+ resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
2804
+ });
2805
+ }
2806
+ ).command(
2807
+ "show",
2808
+ "Show CLI configuration and installation status tables (human-readable)",
2809
+ (yargs2) => yargs2.option("home", {
2810
+ type: "string",
2811
+ description: "Home-directory override for CLI configuration paths (default: user home)"
2812
+ }).example([["$0 show", "Print CLI Configuration and Installation Status tables"]]),
2813
+ (argv) => {
2814
+ process.exitCode = runShowCommand({
2815
+ home: argv.home,
2816
+ resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
2817
+ });
2818
+ }
1658
2819
  ).command(
1659
2820
  "exec <tool> [params]",
1660
2821
  "Execute a tool directly and output JSON",
@@ -1744,11 +2905,11 @@ if (isRunDirectly || isMainModule) {
1744
2905
  if (trimmed) lines.push(trimmed);
1745
2906
  }
1746
2907
  } else {
1747
- if (!existsSync(argv.input)) {
2908
+ if (!existsSync4(argv.input)) {
1748
2909
  console.error(`Error: File not found: ${argv.input}`);
1749
2910
  process.exit(1);
1750
2911
  }
1751
- lines = readFileSync(argv.input, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
2912
+ lines = readFileSync4(argv.input, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
1752
2913
  }
1753
2914
  if (lines.length === 0) {
1754
2915
  console.error("Error: No addresses found in input.");
@@ -1791,7 +2952,7 @@ if (isRunDirectly || isMainModule) {
1791
2952
  const summary = { total: lines.length, succeeded, failed, results };
1792
2953
  const json = JSON.stringify(summary, null, 2);
1793
2954
  if (argv.output) {
1794
- writeFileSync(argv.output, json, "utf-8");
2955
+ writeFileSync3(argv.output, json, "utf-8");
1795
2956
  console.error(`Done: ${succeeded}/${lines.length} succeeded. Output: ${argv.output}`);
1796
2957
  } else {
1797
2958
  console.log(json);
@@ -1809,8 +2970,8 @@ if (isRunDirectly || isMainModule) {
1809
2970
  default: process.env.MCP_SERVER_PORT ? parseInt(process.env.MCP_SERVER_PORT) : 3e3
1810
2971
  }).option("host", {
1811
2972
  type: "string",
1812
- description: "Hostname to bind the server to (e.g. 0.0.0.0 for all interfaces)",
1813
- default: process.env.MCP_SERVER_HOST || "0.0.0.0"
2973
+ description: "Hostname to bind the server to (default 127.0.0.1 loopback; 0.0.0.0 for all interfaces)",
2974
+ default: process.env.MCP_SERVER_HOST || "127.0.0.1"
1814
2975
  }).option("apikey", {
1815
2976
  alias: "k",
1816
2977
  type: "string",
@@ -1845,19 +3006,37 @@ if (isRunDirectly || isMainModule) {
1845
3006
  Logger.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file");
1846
3007
  Logger.log("");
1847
3008
  }
1848
- startServer(argv.port, argv.apikey, argv.host).catch((error) => {
3009
+ startServer(argv.port, argv.apikey, argv.host).then((outcome) => {
3010
+ if (outcome.started === 0) {
3011
+ process.exit(1);
3012
+ }
3013
+ }).catch((error) => {
1849
3014
  Logger.error("\u274C Failed to start server:", error);
1850
3015
  process.exit(1);
1851
3016
  });
1852
3017
  }
1853
3018
  }
1854
- ).version(packageVersion).alias("version", "v").help().parse();
3019
+ ).version(packageVersion).alias("version", "v").option("env-file", {
3020
+ type: "string",
3021
+ global: true,
3022
+ description: "Load environment variables from this exact file (error if missing; no implicit .env fallback)"
3023
+ }).help().parse();
1855
3024
  }
1856
3025
  export {
3026
+ DOCTOR_PROBES,
1857
3027
  EXEC_TOOLS,
3028
+ createShutdownHandler,
1858
3029
  execTool,
3030
+ extractEnvFileOption,
3031
+ installShutdownHandlers,
3032
+ parseDoctorProbes,
1859
3033
  runDoctor,
3034
+ runEnableDisableCliCommand,
1860
3035
  runExecCommand,
3036
+ runInstallUninstallCommand,
3037
+ runSetup,
3038
+ runShowCommand,
3039
+ runSnippetCommand,
1861
3040
  startServer
1862
3041
  };
1863
3042
  //# sourceMappingURL=cli.js.map