grain-tools 0.10.16 → 0.10.17

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.
@@ -19663,10 +19663,10 @@ function toolRuleFor(profile, tool) {
19663
19663
  return profile.policy?.rules?.[TOOL_RESOURCE_CLASS[tool]] ?? "allow";
19664
19664
  }
19665
19665
  function refusalNextStep(tool) {
19666
- return REFUSAL_NEXT_STEP[TOOL_RESOURCE_CLASS[tool]] ?? DEFAULT_REFUSAL_NEXT_STEP;
19666
+ return REFUSAL_NEXT_STEP[tool] ?? `The '${tool}' call requires a profile that permits '${TOOL_RESOURCE_CLASS[tool]}' actions.`;
19667
19667
  }
19668
19668
  function toolRefusalMessage(profile, tool) {
19669
- return `Tool '${tool}' is not permitted for the '${profile.name}' profile: its resource class '${TOOL_RESOURCE_CLASS[tool]}' is refused by policy. The tool exists and is advertised; this call was declined, not lost. Next step: ${refusalNextStep(tool)}`;
19669
+ return `Tool '${tool}' is not permitted for the '${profile.name}' profile: its resource class '${TOOL_RESOURCE_CLASS[tool]}' is refused by policy. The tool exists and is advertised; this call was declined, not lost. ${refusalNextStep(tool)}`;
19670
19670
  }
19671
19671
  function toolRefusalResult(profile, tool) {
19672
19672
  return {
@@ -19765,7 +19765,7 @@ function createToolProfileResolver(explicit, onUnknownEnvValue) {
19765
19765
  }
19766
19766
  };
19767
19767
  }
19768
- var ALL_TOOLS, READ_ONLY_TOOLS, TOOL_RESOURCE_CLASS, TOOL_PROFILES, REFUSAL_NEXT_STEP, DEFAULT_REFUSAL_NEXT_STEP, WEB_PROXIED_TOOLS, toolNameSets;
19768
+ var ALL_TOOLS, READ_ONLY_TOOLS, TOOL_RESOURCE_CLASS, TOOL_PROFILES, REFUSAL_NEXT_STEP, WEB_PROXIED_TOOLS, toolNameSets;
19769
19769
  var init_toolProfiles = __esm({
19770
19770
  "src/common/toolProfiles.ts"() {
19771
19771
  "use strict";
@@ -19855,11 +19855,11 @@ var init_toolProfiles = __esm({
19855
19855
  }
19856
19856
  };
19857
19857
  REFUSAL_NEXT_STEP = {
19858
- "connection-config": "Call create_connection_draft with whatever details you know, leave the rest blank, and give the user the returned URL to finish setup in Grain. Ask the user for details; do not search local files or other repositories for them.",
19859
- credential: "Agents cannot request stored credentials here. Ask the user to enter the password in Grain (VS Code: Grain panel > Settings > Connections > the connection). Never ask for a password in chat.",
19860
- "workspace-write": "Ask the user to save it from Grain."
19858
+ save_connection: "Draft connections are available through create_connection_draft; setup is completed in Grain using details supplied by the user. Grain does not read connection details from local files or other repositories.",
19859
+ delete_connection: "Deleting connections is only available in the Grain workspace (open the connection, then Delete).",
19860
+ request_connection_credentials: "Connection credentials are entered in Grain (VS Code: Grain panel > Settings > Connections > the connection), never in chat.",
19861
+ save_chart: "Charts can be saved from the Grain workspace."
19861
19862
  };
19862
- DEFAULT_REFUSAL_NEXT_STEP = "Tell the user this action is not available to agents in Grain.";
19863
19863
  WEB_PROXIED_TOOLS = [
19864
19864
  "list_connectors",
19865
19865
  "list_connections",
@@ -22031,6 +22031,121 @@ var init_applicationPayload = __esm({
22031
22031
  }
22032
22032
  });
22033
22033
 
22034
+ // src/common/ipcEndpoint.ts
22035
+ function posixSocketPathFits(directory) {
22036
+ return Buffer.byteLength(path22.posix.join(directory, SOCKET_BASENAME)) <= UNIX_SOCKET_PATH_MAX_BYTES && Buffer.byteLength(
22037
+ path22.posix.join(directory, `.${"9".repeat(PRIVATE_SOCKET_BASENAME_LENGTH)}`)
22038
+ ) <= UNIX_SOCKET_PATH_MAX_BYTES;
22039
+ }
22040
+ function relocatedSocketDirectory(configDir) {
22041
+ const key = (0, import_crypto3.createHash)("sha1").update(path22.resolve(configDir)).digest("hex").slice(0, 16);
22042
+ return path22.join(os4.homedir(), ".grain", "s", key);
22043
+ }
22044
+ function isWindowsNamedPipe(endpoint) {
22045
+ return endpoint.startsWith("\\\\.\\pipe\\") || endpoint.startsWith("\\\\?\\pipe\\");
22046
+ }
22047
+ function daemonIpcEndpoint(configDir) {
22048
+ if (process.platform === "win32") {
22049
+ const key = (0, import_crypto3.createHash)("sha1").update(path22.resolve(configDir).toLowerCase()).digest("hex").slice(0, 8);
22050
+ return `\\\\.\\pipe\\sql-preview-${key}`;
22051
+ }
22052
+ if (posixSocketPathFits(configDir)) {
22053
+ return path22.posix.join(configDir, SOCKET_BASENAME);
22054
+ }
22055
+ const directory = relocatedSocketDirectory(configDir);
22056
+ const endpoint = path22.posix.join(directory, SOCKET_BASENAME);
22057
+ if (!posixSocketPathFits(directory)) {
22058
+ throw new Error(
22059
+ `IPC socket path exceeds the ${UNIX_SOCKET_PATH_MAX_BYTES}-byte limit (${Buffer.byteLength(endpoint)} bytes) for ${configDir}.`
22060
+ );
22061
+ }
22062
+ return endpoint;
22063
+ }
22064
+ function daemonControlEndpoint(configDir) {
22065
+ const key = (0, import_crypto3.createHash)("sha1").update(path22.resolve(configDir).toLowerCase()).digest("hex").slice(0, 16);
22066
+ return `\\\\.\\pipe\\grain-control-${key}`;
22067
+ }
22068
+ function isRelocatedDaemonIpcEndpoint(configDir, endpoint) {
22069
+ return process.platform !== "win32" && endpoint === path22.posix.join(relocatedSocketDirectory(configDir), SOCKET_BASENAME);
22070
+ }
22071
+ function endpointExists(endpoint) {
22072
+ if (isWindowsNamedPipe(endpoint)) {
22073
+ return true;
22074
+ }
22075
+ return fs18.existsSync(endpoint);
22076
+ }
22077
+ var fs18, os4, path22, import_crypto3, UNIX_SOCKET_PATH_MAX_BYTES, SOCKET_BASENAME, PRIVATE_SOCKET_BASENAME_LENGTH;
22078
+ var init_ipcEndpoint = __esm({
22079
+ "src/common/ipcEndpoint.ts"() {
22080
+ "use strict";
22081
+ fs18 = __toESM(require("fs"));
22082
+ os4 = __toESM(require("os"));
22083
+ path22 = __toESM(require("path"));
22084
+ import_crypto3 = require("crypto");
22085
+ UNIX_SOCKET_PATH_MAX_BYTES = 104;
22086
+ SOCKET_BASENAME = "srv.sock";
22087
+ PRIVATE_SOCKET_BASENAME_LENGTH = 24;
22088
+ }
22089
+ });
22090
+
22091
+ // src/common/requestDaemonControlStop.ts
22092
+ function requestDaemonControlStop(endpoint, token) {
22093
+ return new Promise((resolve12) => {
22094
+ const socket = net.createConnection(endpoint);
22095
+ const timeout = setTimeout(() => settle2(false), 2e3);
22096
+ const settle2 = (accepted) => {
22097
+ clearTimeout(timeout);
22098
+ socket.destroy();
22099
+ resolve12(accepted);
22100
+ };
22101
+ socket.once("error", () => settle2(false));
22102
+ socket.once(
22103
+ "connect",
22104
+ () => socket.write(JSON.stringify({ method: "graceful-stop", token }) + "\n")
22105
+ );
22106
+ let buffer = "";
22107
+ socket.on("data", (chunk) => {
22108
+ buffer += chunk.toString();
22109
+ if (Buffer.byteLength(buffer) > CONTROL_MESSAGE_LIMIT) {
22110
+ settle2(false);
22111
+ return;
22112
+ }
22113
+ const newline = buffer.indexOf("\n");
22114
+ if (newline === -1) {
22115
+ return;
22116
+ }
22117
+ try {
22118
+ settle2(JSON.parse(buffer.slice(0, newline)).accepted === true);
22119
+ } catch {
22120
+ settle2(false);
22121
+ }
22122
+ });
22123
+ });
22124
+ }
22125
+ var net, CONTROL_MESSAGE_LIMIT;
22126
+ var init_requestDaemonControlStop = __esm({
22127
+ "src/common/requestDaemonControlStop.ts"() {
22128
+ "use strict";
22129
+ net = __toESM(require("net"));
22130
+ CONTROL_MESSAGE_LIMIT = 8 * 1024;
22131
+ }
22132
+ });
22133
+
22134
+ // src/server/config/drainStopBudget.ts
22135
+ function drainStopBudgetMs(configDir) {
22136
+ const drainTimeoutMs = configDir ? new ConfigLoader({ configDir, createDefaultFile: false }).load().config.server.drainTimeoutMs : DEFAULT_CONFIG.server.drainTimeoutMs;
22137
+ return drainTimeoutMs + DRAIN_STOP_MARGIN_MS;
22138
+ }
22139
+ var DRAIN_STOP_MARGIN_MS;
22140
+ var init_drainStopBudget = __esm({
22141
+ "src/server/config/drainStopBudget.ts"() {
22142
+ "use strict";
22143
+ init_ConfigLoader();
22144
+ init_DaemonConfig();
22145
+ DRAIN_STOP_MARGIN_MS = 2e3;
22146
+ }
22147
+ });
22148
+
22034
22149
  // src/server/install/supervisorAdapter.ts
22035
22150
  function createCommandRunner() {
22036
22151
  return {
@@ -22062,6 +22177,14 @@ function isUserServicesForbidden(platform2, output) {
22062
22177
  }
22063
22178
  return /access is denied|0x80070005|error_access_denied/.test(normalized);
22064
22179
  }
22180
+ function isPidAlive(pid) {
22181
+ try {
22182
+ process.kill(pid, 0);
22183
+ return true;
22184
+ } catch (error52) {
22185
+ return error52.code !== "ESRCH";
22186
+ }
22187
+ }
22065
22188
  async function runRegistration(platform2, runner, command, args) {
22066
22189
  const result = await runner.run(command, args);
22067
22190
  if (result.exitCode === 0) {
@@ -22085,7 +22208,7 @@ async function probeRegistration(runner, command, args) {
22085
22208
  function isCommandMissing(error52) {
22086
22209
  return error52?.code === "ENOENT";
22087
22210
  }
22088
- function createSupervisorAdapter(platform2, runner, uid) {
22211
+ function createSupervisorAdapter(platform2, runner, uid, configHome) {
22089
22212
  if (platform2 === "darwin") {
22090
22213
  const target = (domain2, identifier) => `${domain2}/${uid}/${identifier}`;
22091
22214
  const registeredTarget = async (identifier) => {
@@ -22173,7 +22296,28 @@ function createSupervisorAdapter(platform2, runner, uid) {
22173
22296
  "/F"
22174
22297
  ]),
22175
22298
  unregister: async (identifier) => {
22176
- await runner.run("schtasks", ["/End", "/TN", identifier]);
22299
+ let exited = false;
22300
+ const pid = configHome ? readDaemonEndpoint(configHome)?.pid : void 0;
22301
+ if (configHome) {
22302
+ const token = readDaemonAuthToken(configHome);
22303
+ if (pid && token && await requestDaemonControlStop(daemonControlEndpoint(configHome), token)) {
22304
+ const deadline = Date.now() + drainStopBudgetMs(configHome);
22305
+ while (Date.now() < deadline) {
22306
+ if (!isPidAlive(pid)) {
22307
+ exited = true;
22308
+ break;
22309
+ }
22310
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
22311
+ }
22312
+ }
22313
+ }
22314
+ if (!exited) {
22315
+ const args = ["/End", "/TN", identifier];
22316
+ const result = await runner.run("schtasks", args);
22317
+ if (result.exitCode !== 0 && pid && isPidAlive(pid)) {
22318
+ throw new RegistrationError("schtasks", args, result.exitCode, result.output);
22319
+ }
22320
+ }
22177
22321
  await runRegistration(platform2, runner, "schtasks", ["/Delete", "/TN", identifier, "/F"]);
22178
22322
  },
22179
22323
  start: (identifier) => runStart(runner, "schtasks", ["/Run", "/TN", identifier])
@@ -22184,6 +22328,11 @@ var init_supervisorAdapter = __esm({
22184
22328
  "src/server/install/supervisorAdapter.ts"() {
22185
22329
  "use strict";
22186
22330
  import_child_process3 = require("child_process");
22331
+ init_daemonEndpointFile();
22332
+ init_DaemonAuthToken();
22333
+ init_ipcEndpoint();
22334
+ init_requestDaemonControlStop();
22335
+ init_drainStopBudget();
22187
22336
  UserServicesForbiddenError = class extends Error {
22188
22337
  };
22189
22338
  ServiceManagerMissingError = class extends UserServicesForbiddenError {
@@ -22205,7 +22354,7 @@ var require_package = __commonJS({
22205
22354
  name: "grain",
22206
22355
  displayName: "Grain SQL",
22207
22356
  description: "Run SQL in VS Code and expose safe, visible MCP database tools for Claude, Cursor, and other AI agents.",
22208
- version: "0.10.16",
22357
+ version: "0.10.17",
22209
22358
  license: "UNLICENSED",
22210
22359
  mcpName: "io.github.pattrnlabs/grain",
22211
22360
  publisher: "pattrnlabs",
@@ -24690,7 +24839,7 @@ function readRegistry(configDir) {
24690
24839
  }
24691
24840
  }
24692
24841
  function tokenDigest(token) {
24693
- return import_crypto3.default.createHash("sha256").update(token, "utf8").digest("base64url");
24842
+ return import_crypto4.default.createHash("sha256").update(token, "utf8").digest("base64url");
24694
24843
  }
24695
24844
  function digestsMatch(provided, expected) {
24696
24845
  const a = Buffer.from(provided, "utf8");
@@ -24698,7 +24847,7 @@ function digestsMatch(provided, expected) {
24698
24847
  if (a.length !== b.length) {
24699
24848
  return false;
24700
24849
  }
24701
- return import_crypto3.default.timingSafeEqual(a, b);
24850
+ return import_crypto4.default.timingSafeEqual(a, b);
24702
24851
  }
24703
24852
  function assertIdAvailable(registry2, id) {
24704
24853
  if (registry2.clients.some((client) => client.id === id)) {
@@ -24727,7 +24876,7 @@ function registerClient2(configDir, registration) {
24727
24876
  ensureConfigDirMode2(configDir);
24728
24877
  let registry2 = readRegistry(configDir);
24729
24878
  assertIdAvailable(registry2, registration.id);
24730
- const token = import_crypto3.default.randomBytes(TOKEN_BYTES3).toString("base64url");
24879
+ const token = import_crypto4.default.randomBytes(TOKEN_BYTES3).toString("base64url");
24731
24880
  const entry = { ...registration, tokenDigest: tokenDigest(token) };
24732
24881
  const registryPath = clientRegistryPath(configDir);
24733
24882
  try {
@@ -24782,11 +24931,11 @@ function removeClient(configDir, id) {
24782
24931
  writeExistingRegistry(configDir, registry2);
24783
24932
  return true;
24784
24933
  }
24785
- var import_crypto3, import_fs4, import_path5, CLIENT_REGISTRY_FILENAME, TOKEN_BYTES3, REGISTRY_FILE_MODE, CONFIG_DIR_MODE3;
24934
+ var import_crypto4, import_fs4, import_path5, CLIENT_REGISTRY_FILENAME, TOKEN_BYTES3, REGISTRY_FILE_MODE, CONFIG_DIR_MODE3;
24786
24935
  var init_ClientRegistry = __esm({
24787
24936
  "src/server/auth/ClientRegistry.ts"() {
24788
24937
  "use strict";
24789
- import_crypto3 = __toESM(require("crypto"));
24938
+ import_crypto4 = __toESM(require("crypto"));
24790
24939
  import_fs4 = __toESM(require("fs"));
24791
24940
  import_path5 = __toESM(require("path"));
24792
24941
  init_toolProfiles();
@@ -24853,45 +25002,45 @@ async function startCommand(configDir = resolveGrainHome(), spawner = import_chi
24853
25002
  console.log(`Grain is already running at ${formatUserFacingLocalUrl(host, port)}`);
24854
25003
  return 0;
24855
25004
  }
24856
- const lockPath = path27.join(configDir, "start.lock");
25005
+ const lockPath = path28.join(configDir, "start.lock");
24857
25006
  let claimedLock = false;
24858
25007
  try {
24859
- const content = fs22.readFileSync(lockPath, "utf8");
25008
+ const content = fs23.readFileSync(lockPath, "utf8");
24860
25009
  const parts = content.split("\n");
24861
25010
  const pidStr = parts[0];
24862
25011
  const tsStr = parts[1];
24863
25012
  if (pidStr === void 0 || tsStr === void 0) {
24864
- fs22.unlinkSync(lockPath);
25013
+ fs23.unlinkSync(lockPath);
24865
25014
  } else {
24866
25015
  const lockPid = parseInt(pidStr, 10);
24867
25016
  const lockTs = parseInt(tsStr, 10);
24868
25017
  if (!isNaN(lockTs) && clock.now() - lockTs > 2e4) {
24869
- fs22.unlinkSync(lockPath);
25018
+ fs23.unlinkSync(lockPath);
24870
25019
  } else if (!isNaN(lockPid)) {
24871
25020
  try {
24872
25021
  process.kill(lockPid, 0);
24873
25022
  } catch {
24874
- fs22.unlinkSync(lockPath);
25023
+ fs23.unlinkSync(lockPath);
24875
25024
  }
24876
25025
  }
24877
25026
  }
24878
25027
  } catch {
24879
25028
  }
24880
25029
  try {
24881
- const fd = fs22.openSync(lockPath, "wx");
24882
- fs22.writeSync(fd, `${process.pid}
25030
+ const fd = fs23.openSync(lockPath, "wx");
25031
+ fs23.writeSync(fd, `${process.pid}
24883
25032
  ${clock.now()}`);
24884
- fs22.closeSync(fd);
25033
+ fs23.closeSync(fd);
24885
25034
  claimedLock = true;
24886
25035
  } catch (e2) {
24887
25036
  if (e2.code !== "EEXIST") {
24888
25037
  throw e2;
24889
25038
  }
24890
25039
  }
24891
- const logPath = path27.join(configDir, "daemon.log");
25040
+ const logPath = path28.join(configDir, "daemon.log");
24892
25041
  if (claimedLock) {
24893
- const out2 = fs22.openSync(logPath, "a");
24894
- const err = fs22.openSync(logPath, "a");
25042
+ const out2 = fs23.openSync(logPath, "a");
25043
+ const err = fs23.openSync(logPath, "a");
24895
25044
  const child = spawner(process.execPath, [process.argv[1] ?? ""], {
24896
25045
  detached: true,
24897
25046
  stdio: ["ignore", out2, err],
@@ -24926,20 +25075,20 @@ ${clock.now()}`);
24926
25075
  } finally {
24927
25076
  if (claimedLock) {
24928
25077
  try {
24929
- fs22.unlinkSync(lockPath);
25078
+ fs23.unlinkSync(lockPath);
24930
25079
  } catch {
24931
25080
  }
24932
25081
  }
24933
25082
  }
24934
25083
  }
24935
- var import_child_process4, fs22, path27, http5, OWNERSHIP_PROBE_PATH;
25084
+ var import_child_process4, fs23, path28, http5, OWNERSHIP_PROBE_PATH;
24936
25085
  var init_startCommand = __esm({
24937
25086
  "src/server/cli/startCommand.ts"() {
24938
25087
  "use strict";
24939
25088
  import_child_process4 = require("child_process");
24940
25089
  init_onboardingHandoff();
24941
- fs22 = __toESM(require("fs"));
24942
- path27 = __toESM(require("path"));
25090
+ fs23 = __toESM(require("fs"));
25091
+ path28 = __toESM(require("path"));
24943
25092
  http5 = __toESM(require("http"));
24944
25093
  init_grainHome();
24945
25094
  init_statusCommand();
@@ -25019,10 +25168,10 @@ var init_localBrowserPaths = __esm({
25019
25168
  function findPackageRoot(startDir) {
25020
25169
  let dir3 = startDir;
25021
25170
  for (let i = 0; i < 8; i++) {
25022
- if (fs23.existsSync(path28.join(dir3, "package.json"))) {
25171
+ if (fs24.existsSync(path29.join(dir3, "package.json"))) {
25023
25172
  return dir3;
25024
25173
  }
25025
- const parent = path28.dirname(dir3);
25174
+ const parent = path29.dirname(dir3);
25026
25175
  if (parent === dir3) {
25027
25176
  break;
25028
25177
  }
@@ -25032,19 +25181,19 @@ function findPackageRoot(startDir) {
25032
25181
  }
25033
25182
  function readDaemonVersion() {
25034
25183
  try {
25035
- const pkgPath = path28.join(findPackageRoot(__dirname), "package.json");
25036
- const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf8"));
25184
+ const pkgPath = path29.join(findPackageRoot(__dirname), "package.json");
25185
+ const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf8"));
25037
25186
  return pkg.version ?? null;
25038
25187
  } catch {
25039
25188
  return null;
25040
25189
  }
25041
25190
  }
25042
- var fs23, path28;
25191
+ var fs24, path29;
25043
25192
  var init_daemonVersion = __esm({
25044
25193
  "src/server/daemonVersion.ts"() {
25045
25194
  "use strict";
25046
- fs23 = __toESM(require("fs"));
25047
- path28 = __toESM(require("path"));
25195
+ fs24 = __toESM(require("fs"));
25196
+ path29 = __toESM(require("path"));
25048
25197
  }
25049
25198
  });
25050
25199
 
@@ -33703,7 +33852,7 @@ async function stopCommand(configDir = resolveGrainHome(), clock = Date, process
33703
33852
  process.platform,
33704
33853
  createCommandRunner(),
33705
33854
  process.getuid?.() ?? 0
33706
- ), isProcessAlive = defaultIsProcessAlive) {
33855
+ ), isProcessAlive = defaultIsProcessAlive, platform2 = process.platform, requestStop = requestDaemonControlStop) {
33707
33856
  if (await adapter2.isRegistered(DAEMON_SERVICE_IDENTITY)) {
33708
33857
  console.error(
33709
33858
  "Grain is installed as a background service. Run `grain uninstall` to remove it."
@@ -33720,8 +33869,12 @@ async function stopCommand(configDir = resolveGrainHome(), clock = Date, process
33720
33869
  console.error("Could not determine Grain daemon PID.");
33721
33870
  return 1;
33722
33871
  }
33872
+ const token = platform2 === "win32" ? readDaemonAuthToken(configDir) : void 0;
33873
+ const accepted = token ? await requestStop(daemonControlEndpoint(configDir), token) : false;
33723
33874
  try {
33724
- processKiller(pid, "SIGTERM");
33875
+ if (!accepted) {
33876
+ processKiller(pid, "SIGTERM");
33877
+ }
33725
33878
  } catch (error52) {
33726
33879
  const code = error52.code;
33727
33880
  if (code !== "ESRCH") {
@@ -33730,7 +33883,7 @@ async function stopCommand(configDir = resolveGrainHome(), clock = Date, process
33730
33883
  }
33731
33884
  }
33732
33885
  const startMs = clock.now();
33733
- const timeoutMs = 1e4;
33886
+ const timeoutMs = accepted ? drainStopBudgetMs(configDir) : 1e4;
33734
33887
  while (clock.now() - startMs < timeoutMs) {
33735
33888
  const currentStatus = await readGrainServiceStatus(configDir);
33736
33889
  if (!isRunningGrainService(currentStatus) && !isProcessAlive(pid)) {
@@ -33739,7 +33892,7 @@ async function stopCommand(configDir = resolveGrainHome(), clock = Date, process
33739
33892
  }
33740
33893
  await new Promise((resolve12) => setTimeout(resolve12, 250));
33741
33894
  }
33742
- console.error("Grain did not stop within 10 seconds.");
33895
+ console.error(`Grain did not stop within ${timeoutMs / 1e3} seconds.`);
33743
33896
  return 1;
33744
33897
  }
33745
33898
  var init_stopCommand = __esm({
@@ -33750,6 +33903,10 @@ var init_stopCommand = __esm({
33750
33903
  init_statusCommand();
33751
33904
  init_supervisorAdapter();
33752
33905
  init_daemonRecordInstance();
33906
+ init_ipcEndpoint();
33907
+ init_DaemonAuthToken();
33908
+ init_requestDaemonControlStop();
33909
+ init_drainStopBudget();
33753
33910
  }
33754
33911
  });
33755
33912
 
@@ -89674,67 +89831,10 @@ var require_express2 = __commonJS({
89674
89831
  }
89675
89832
  });
89676
89833
 
89677
- // src/common/ipcEndpoint.ts
89678
- function posixSocketPathFits(directory) {
89679
- return Buffer.byteLength(path30.posix.join(directory, SOCKET_BASENAME)) <= UNIX_SOCKET_PATH_MAX_BYTES && Buffer.byteLength(
89680
- path30.posix.join(directory, `.${"9".repeat(PRIVATE_SOCKET_BASENAME_LENGTH)}`)
89681
- ) <= UNIX_SOCKET_PATH_MAX_BYTES;
89682
- }
89683
- function relocatedSocketDirectory(configDir) {
89684
- const key = (0, import_crypto5.createHash)("sha1").update(path30.resolve(configDir)).digest("hex").slice(0, 16);
89685
- return path30.join(os7.homedir(), ".grain", "s", key);
89686
- }
89687
- function isWindowsNamedPipe(endpoint) {
89688
- return endpoint.startsWith("\\\\.\\pipe\\") || endpoint.startsWith("\\\\?\\pipe\\");
89689
- }
89690
- function daemonIpcEndpoint(configDir) {
89691
- if (process.platform === "win32") {
89692
- const key = (0, import_crypto5.createHash)("sha1").update(path30.resolve(configDir).toLowerCase()).digest("hex").slice(0, 8);
89693
- return `\\\\.\\pipe\\sql-preview-${key}`;
89694
- }
89695
- if (posixSocketPathFits(configDir)) {
89696
- return path30.posix.join(configDir, SOCKET_BASENAME);
89697
- }
89698
- const directory = relocatedSocketDirectory(configDir);
89699
- const endpoint = path30.posix.join(directory, SOCKET_BASENAME);
89700
- if (!posixSocketPathFits(directory)) {
89701
- throw new Error(
89702
- `IPC socket path exceeds the ${UNIX_SOCKET_PATH_MAX_BYTES}-byte limit (${Buffer.byteLength(endpoint)} bytes) for ${configDir}.`
89703
- );
89704
- }
89705
- return endpoint;
89706
- }
89707
- function daemonControlEndpoint(configDir) {
89708
- const key = (0, import_crypto5.createHash)("sha1").update(path30.resolve(configDir).toLowerCase()).digest("hex").slice(0, 16);
89709
- return `\\\\.\\pipe\\grain-control-${key}`;
89710
- }
89711
- function isRelocatedDaemonIpcEndpoint(configDir, endpoint) {
89712
- return process.platform !== "win32" && endpoint === path30.posix.join(relocatedSocketDirectory(configDir), SOCKET_BASENAME);
89713
- }
89714
- function endpointExists(endpoint) {
89715
- if (isWindowsNamedPipe(endpoint)) {
89716
- return true;
89717
- }
89718
- return fs26.existsSync(endpoint);
89719
- }
89720
- var fs26, os7, path30, import_crypto5, UNIX_SOCKET_PATH_MAX_BYTES, SOCKET_BASENAME, PRIVATE_SOCKET_BASENAME_LENGTH;
89721
- var init_ipcEndpoint = __esm({
89722
- "src/common/ipcEndpoint.ts"() {
89723
- "use strict";
89724
- fs26 = __toESM(require("fs"));
89725
- os7 = __toESM(require("os"));
89726
- path30 = __toESM(require("path"));
89727
- import_crypto5 = require("crypto");
89728
- UNIX_SOCKET_PATH_MAX_BYTES = 104;
89729
- SOCKET_BASENAME = "srv.sock";
89730
- PRIVATE_SOCKET_BASENAME_LENGTH = 24;
89731
- }
89732
- });
89733
-
89734
89834
  // src/common/tcpPortAvailability.ts
89735
89835
  function isTcpPortAvailable(port, host = "127.0.0.1") {
89736
89836
  return new Promise((resolve12) => {
89737
- const server = net.createServer();
89837
+ const server = net2.createServer();
89738
89838
  const finish = (available) => {
89739
89839
  server.removeAllListeners();
89740
89840
  resolve12(available);
@@ -89745,11 +89845,11 @@ function isTcpPortAvailable(port, host = "127.0.0.1") {
89745
89845
  });
89746
89846
  });
89747
89847
  }
89748
- var net;
89848
+ var net2;
89749
89849
  var init_tcpPortAvailability = __esm({
89750
89850
  "src/common/tcpPortAvailability.ts"() {
89751
89851
  "use strict";
89752
- net = __toESM(require("net"));
89852
+ net2 = __toESM(require("net"));
89753
89853
  }
89754
89854
  });
89755
89855
 
@@ -112272,7 +112372,11 @@ var init_QueryOperationRunner = __esm({
112272
112372
  try {
112273
112373
  const hooks = {
112274
112374
  ...enforceReadOnly !== void 0 ? { enforceReadOnly } : {},
112275
- onProfileResolved: (profileType) => {
112375
+ onProfileResolved: (profileType, profile) => {
112376
+ if (profile) {
112377
+ tab.connectionId = profile.id;
112378
+ tab.connectionName = profile.name;
112379
+ }
112276
112380
  connector = normalizeTelemetryConnector(profileType);
112277
112381
  emitQueryRequested();
112278
112382
  }
@@ -112522,14 +112626,16 @@ var init_CredentialRequestService = __esm({
112522
112626
  "src/server/CredentialRequestService.ts"() {
112523
112627
  "use strict";
112524
112628
  import_crypto8 = __toESM(require("crypto"));
112629
+ init_AppError();
112525
112630
  DEFAULT_TTL_MS = 10 * 60 * 1e3;
112526
112631
  SECRET_KEY_PATTERN = /password|secret|credential|privatekeypassphrase|^privatekey$|authheader|token|apikey/i;
112527
112632
  CredentialRequestService = class {
112528
- constructor(connectionManager, getBaseUrl, ttlMs = DEFAULT_TTL_MS, connectorRegistry) {
112633
+ constructor(connectionManager, getBaseUrl, ttlMs = DEFAULT_TTL_MS, connectorRegistry, telemetry) {
112529
112634
  this.connectionManager = connectionManager;
112530
112635
  this.getBaseUrl = getBaseUrl;
112531
112636
  this.ttlMs = ttlMs;
112532
112637
  this.connectorRegistry = connectorRegistry;
112638
+ this.telemetry = telemetry;
112533
112639
  this.requests = /* @__PURE__ */ new Map();
112534
112640
  this.requestConsents = /* @__PURE__ */ new Map();
112535
112641
  }
@@ -112632,12 +112738,36 @@ var init_CredentialRequestService = __esm({
112632
112738
  )) {
112633
112739
  throw new Error("Setup fields include a protected field.");
112634
112740
  }
112635
- await this.connectionManager.saveProfile({
112636
- ...profile,
112637
- ...fields,
112638
- draft: false,
112639
- ...input.password !== void 0 ? { password: input.password } : {}
112640
- });
112741
+ try {
112742
+ await this.connectionManager.saveProfile({
112743
+ ...profile,
112744
+ ...fields,
112745
+ draft: false,
112746
+ ...input.password !== void 0 ? { password: input.password } : {}
112747
+ });
112748
+ if (request2.kind === "draft") {
112749
+ this.telemetry?.capture(
112750
+ "first_value_step",
112751
+ { step: "connection_saved", outcome: "success", state: "mcp_server" },
112752
+ clientConsent
112753
+ );
112754
+ }
112755
+ } catch (e2) {
112756
+ if (request2.kind !== "draft") {
112757
+ throw e2;
112758
+ }
112759
+ this.telemetry?.capture(
112760
+ "first_value_step",
112761
+ {
112762
+ step: "connection_saved",
112763
+ outcome: "failure",
112764
+ error_id: errorIdOf(e2) ?? "connection_save_unknown",
112765
+ state: "mcp_server"
112766
+ },
112767
+ clientConsent
112768
+ );
112769
+ throw e2;
112770
+ }
112641
112771
  this.requests.delete(id);
112642
112772
  this.requestConsents.delete(id);
112643
112773
  return request2;
@@ -112713,7 +112843,7 @@ async function createConnectionDraft(deps, args, clientConsent) {
112713
112843
  content: [
112714
112844
  {
112715
112845
  type: "text",
112716
- text: `Give the user this Grain setup URL: ${request2.url}. Retry once the user says setup is done.`
112846
+ text: `Draft connection '${name}' (id ${connectionId}) created. Setup page: ${request2.url} (expires ${new Date(request2.expiresAt).toISOString()}).`
112717
112847
  }
112718
112848
  ]
112719
112849
  };
@@ -113431,7 +113561,7 @@ function buildSetupRequiredResult(request2, browserOpened) {
113431
113561
  content: [
113432
113562
  {
113433
113563
  type: "text",
113434
- text: `Connection '${request2.connectionName}' needs setup before this can run. Give the user this Grain setup URL: ${request2.url}. Retry after the user says setup is done. The link expires at ${expiresAt}.`
113564
+ text: `Connection '${request2.connectionName}' (id ${request2.connectionId}) is a draft and has no credentials yet. Setup page: ${request2.url} (expires ${expiresAt}). Queries on this connection will work once setup is saved.`
113435
113565
  }
113436
113566
  ],
113437
113567
  structuredContent: {
@@ -113640,6 +113770,22 @@ function isWhitespace(code) {
113640
113770
  function splitSqlQueries(text, options = {}) {
113641
113771
  return [...iterateSqlStatements(text, options)].map(({ statement }) => statement);
113642
113772
  }
113773
+ function firstExecutableOffset(text, options = {}) {
113774
+ for (const token of iterateSqlTokens(text, options)) {
113775
+ if (token.kind === "line_comment" || token.kind === "block_comment" || token.kind === "statement_separator") {
113776
+ continue;
113777
+ }
113778
+ const raw = text.slice(token.start, token.end);
113779
+ if (raw.trim().length === 0) {
113780
+ continue;
113781
+ }
113782
+ return token.start + (raw.length - raw.trimStart().length);
113783
+ }
113784
+ return -1;
113785
+ }
113786
+ function hasExecutableSql(text, options = {}) {
113787
+ return firstExecutableOffset(text, options) !== -1;
113788
+ }
113643
113789
  var CHAR_NEWLINE, CHAR_ASTERISK, CHAR_SLASH, CHAR_DASH, CHAR_SINGLE_QUOTE, CHAR_DOUBLE_QUOTE, CHAR_DOLLAR, CHAR_SEMICOLON, CHAR_BACKSLASH, CHAR_TAB, CHAR_CR, CHAR_SPACE, CHAR_NBSP, CHAR_HASH, CHAR_UPPER_E, CHAR_LOWER_E;
113644
113790
  var init_querySplitter = __esm({
113645
113791
  "src/utils/querySplitter.ts"() {
@@ -194647,9 +194793,7 @@ ${query.sql}`).join("\n\n")
194647
194793
  content: [
194648
194794
  {
194649
194795
  type: "text",
194650
- text: browserOpened ? `I opened a one-time Grain setup page for '${request2.connectionName}' in your browser. Give the user this URL if it did not open: ${request2.url}. Retry after the user says setup is done. The link expires at ${new Date(request2.expiresAt).toISOString()}.` : `Give the user this one-time Grain setup URL for '${request2.connectionName}': ${request2.url}. Retry after the user says setup is done. The link expires at ${new Date(
194651
- request2.expiresAt
194652
- ).toISOString()}.`
194796
+ text: `One-time Grain setup page for '${request2.connectionName}' (id ${request2.connectionId}): ${request2.url} (expires ${new Date(request2.expiresAt).toISOString()}). ${browserOpened ? "The page was opened in the browser." : "The page was not opened in the browser."} Connection setup takes effect once saved.`
194653
194797
  }
194654
194798
  ]
194655
194799
  };
@@ -195765,6 +195909,10 @@ var init_ConnectorProcessPool = __esm({
195765
195909
  });
195766
195910
 
195767
195911
  // src/server/DaemonQueryExecutor.ts
195912
+ function withoutTerminalSeparator(query) {
195913
+ const statements = splitSqlQueries(query).filter((statement) => hasExecutableSql(statement));
195914
+ return statements.length === 1 && statements[0] ? statements[0] : query;
195915
+ }
195768
195916
  var path39, DaemonQueryExecutor;
195769
195917
  var init_DaemonQueryExecutor = __esm({
195770
195918
  "src/server/DaemonQueryExecutor.ts"() {
@@ -195776,6 +195924,7 @@ var init_DaemonQueryExecutor = __esm({
195776
195924
  init_AppError();
195777
195925
  init_optionalDrivers();
195778
195926
  init_sqlPolicy();
195927
+ init_querySplitter();
195779
195928
  init_classifyQueryError();
195780
195929
  init_TelemetrySchema();
195781
195930
  init_DaemonConfig();
@@ -196007,7 +196156,7 @@ var init_DaemonQueryExecutor = __esm({
196007
196156
  "config"
196008
196157
  );
196009
196158
  }
196010
- hooks?.onProfileResolved?.(profile.type);
196159
+ hooks?.onProfileResolved?.(profile.type, profile);
196011
196160
  const maxRows = maxRowsOverride ?? this.queryConfig.maxRows;
196012
196161
  const connectorConfig = {
196013
196162
  ...profile,
@@ -196037,7 +196186,13 @@ var init_DaemonQueryExecutor = __esm({
196037
196186
  });
196038
196187
  const querySignal = this.createQuerySignal(abortSignal);
196039
196188
  try {
196040
- const generator = connector.runQuery(query, connectorConfig, authHeader, querySignal.signal);
196189
+ const executableQuery = withoutTerminalSeparator(query);
196190
+ const generator = connector.runQuery(
196191
+ executableQuery,
196192
+ connectorConfig,
196193
+ authHeader,
196194
+ querySignal.signal
196195
+ );
196041
196196
  for await (const page of generator) {
196042
196197
  yield {
196043
196198
  ...page,
@@ -203546,12 +203701,14 @@ function registerConnectionSetupRoutes(app, context2) {
203546
203701
  res.status(400).json({ error: "Setup fields include a protected field." });
203547
203702
  return;
203548
203703
  }
203704
+ const profile = { ...state.profile };
203705
+ delete profile["draft"];
203549
203706
  const result = await context2.toolManager.handleToolCall(
203550
203707
  "test_connection",
203551
203708
  {
203552
203709
  type: state.profile["type"],
203553
203710
  connectionProfile: {
203554
- ...state.profile,
203711
+ ...profile,
203555
203712
  ...fields,
203556
203713
  ...typeof body.password === "string" ? { password: body.password } : {}
203557
203714
  }
@@ -204069,7 +204226,7 @@ function registerDaemonHttpRoutes(context2, mcpHttpSurface) {
204069
204226
  await context2.credentialRequestService.fulfill(id, token, password);
204070
204227
  res.send(
204071
204228
  renderCredentialPage({
204072
- success: "Saved. Go back to your agent \u2014 it will retry."
204229
+ success: "Saved. Go back to your agent, and it will retry."
204073
204230
  })
204074
204231
  );
204075
204232
  } catch (err) {
@@ -204214,7 +204371,9 @@ data: ${Date.now()}
204214
204371
  sourceFileUri: t.sourceFileUri,
204215
204372
  wasTruncated: t.wasTruncated,
204216
204373
  totalRowsInFirstBatch: t.totalRowsInFirstBatch,
204217
- executedAt: t.executedAt
204374
+ executedAt: t.executedAt,
204375
+ connectionId: t.connectionId,
204376
+ connectionName: t.connectionName
204218
204377
  }))
204219
204378
  );
204220
204379
  } catch (err) {
@@ -235242,12 +235401,12 @@ var init_ExtensionConnectionTracker = __esm({
235242
235401
  });
235243
235402
 
235244
235403
  // src/server/surfaces/DaemonSocketSurface.ts
235245
- var fs41, net2, path41, bindSequence, DaemonSocketSurface;
235404
+ var fs41, net3, path41, bindSequence, DaemonSocketSurface;
235246
235405
  var init_DaemonSocketSurface = __esm({
235247
235406
  "src/server/surfaces/DaemonSocketSurface.ts"() {
235248
235407
  "use strict";
235249
235408
  fs41 = __toESM(require("fs"));
235250
- net2 = __toESM(require("net"));
235409
+ net3 = __toESM(require("net"));
235251
235410
  path41 = __toESM(require("path"));
235252
235411
  init_ipcEndpoint();
235253
235412
  init_ConsoleLogger();
@@ -235306,7 +235465,7 @@ var init_DaemonSocketSurface = __esm({
235306
235465
  logger.warn("Failed to clean up socket:", e2);
235307
235466
  }
235308
235467
  }
235309
- this.server = net2.createServer(async (socket) => {
235468
+ this.server = net3.createServer(async (socket) => {
235310
235469
  this.sockets.add(socket);
235311
235470
  logger.info("Client connected via Socket");
235312
235471
  this.options.refreshActivity();
@@ -235451,45 +235610,14 @@ var init_DaemonSocketSurface = __esm({
235451
235610
  });
235452
235611
 
235453
235612
  // src/server/surfaces/DaemonControlSurface.ts
235454
- function requestDaemonControlStop(endpoint, token) {
235455
- return new Promise((resolve12) => {
235456
- const socket = net3.createConnection(endpoint);
235457
- const timeout = setTimeout(() => settle2(false), 2e3);
235458
- const settle2 = (accepted) => {
235459
- clearTimeout(timeout);
235460
- socket.destroy();
235461
- resolve12(accepted);
235462
- };
235463
- socket.once("error", () => settle2(false));
235464
- socket.once(
235465
- "connect",
235466
- () => socket.write(JSON.stringify({ method: "graceful-stop", token }) + "\n")
235467
- );
235468
- let buffer = "";
235469
- socket.on("data", (chunk) => {
235470
- buffer += chunk.toString();
235471
- if (Buffer.byteLength(buffer) > CONTROL_MESSAGE_LIMIT) {
235472
- settle2(false);
235473
- return;
235474
- }
235475
- const newline = buffer.indexOf("\n");
235476
- if (newline === -1)
235477
- return;
235478
- try {
235479
- settle2(JSON.parse(buffer.slice(0, newline)).accepted === true);
235480
- } catch {
235481
- settle2(false);
235482
- }
235483
- });
235484
- });
235485
- }
235486
- var net3, CONTROL_MESSAGE_LIMIT, DaemonControlSurface;
235613
+ var net4, CONTROL_MESSAGE_LIMIT2, DaemonControlSurface;
235487
235614
  var init_DaemonControlSurface = __esm({
235488
235615
  "src/server/surfaces/DaemonControlSurface.ts"() {
235489
235616
  "use strict";
235490
- net3 = __toESM(require("net"));
235617
+ net4 = __toESM(require("net"));
235491
235618
  init_DaemonAuthToken();
235492
- CONTROL_MESSAGE_LIMIT = 8 * 1024;
235619
+ init_requestDaemonControlStop();
235620
+ CONTROL_MESSAGE_LIMIT2 = 8 * 1024;
235493
235621
  DaemonControlSurface = class {
235494
235622
  constructor(options) {
235495
235623
  this.options = options;
@@ -235499,13 +235627,13 @@ var init_DaemonControlSurface = __esm({
235499
235627
  const enabled = this.options.platformEnabled ?? process.platform === "win32";
235500
235628
  if (!enabled)
235501
235629
  return;
235502
- this.server = net3.createServer((socket) => {
235630
+ this.server = net4.createServer((socket) => {
235503
235631
  this.sockets.add(socket);
235504
235632
  socket.once("close", () => this.sockets.delete(socket));
235505
235633
  let buffer = "";
235506
235634
  socket.on("data", (chunk) => {
235507
235635
  buffer += chunk.toString();
235508
- if (Buffer.byteLength(buffer) > CONTROL_MESSAGE_LIMIT) {
235636
+ if (Buffer.byteLength(buffer) > CONTROL_MESSAGE_LIMIT2) {
235509
235637
  socket.destroy();
235510
235638
  return;
235511
235639
  }
@@ -252899,7 +253027,8 @@ var init_Daemon = __esm({
252899
253027
  this.connectionManager,
252900
253028
  () => this.getHttpBaseUrl(),
252901
253029
  void 0,
252902
- this.connectorRegistry
253030
+ this.connectorRegistry,
253031
+ this.telemetryService
252903
253032
  );
252904
253033
  this.toolManager = new DaemonMcpToolManager(
252905
253034
  this.sessionManager,
@@ -254366,9 +254495,9 @@ ${line}
254366
254495
 
254367
254496
  // src/server/install/installCommand.ts
254368
254497
  var crypto8 = __toESM(require("crypto"));
254369
- var fs18 = __toESM(require("fs"));
254370
- var os4 = __toESM(require("os"));
254371
- var path22 = __toESM(require("path"));
254498
+ var fs19 = __toESM(require("fs"));
254499
+ var os5 = __toESM(require("os"));
254500
+ var path23 = __toESM(require("path"));
254372
254501
  init_grainHome();
254373
254502
  init_daemonRecordInstance();
254374
254503
  init_AppError();
@@ -255105,8 +255234,8 @@ function unitFor(platform2, launcher, configHome, nodePath) {
255105
255234
  nodePath,
255106
255235
  configHome,
255107
255236
  serviceIdentifier: GRAIN_SERVICE_LABEL,
255108
- stdoutPath: path22.join(configHome, "grain-daemon.out.log"),
255109
- stderrPath: path22.join(configHome, "grain-daemon.err.log")
255237
+ stdoutPath: path23.join(configHome, "grain-daemon.out.log"),
255238
+ stderrPath: path23.join(configHome, "grain-daemon.err.log")
255110
255239
  };
255111
255240
  if (platform2 === "darwin") {
255112
255241
  return generateLaunchAgent(descriptor);
@@ -255117,7 +255246,7 @@ function unitFor(platform2, launcher, configHome, nodePath) {
255117
255246
  return generateWindowsStartupTask(descriptor);
255118
255247
  }
255119
255248
  function platformLauncherPath(platform2, configHome) {
255120
- return platform2 === "win32" ? path22.join(configHome, "grain-daemon-launcher.cmd") : launcherPath(configHome);
255249
+ return platform2 === "win32" ? path23.join(configHome, "grain-daemon-launcher.cmd") : launcherPath(configHome);
255121
255250
  }
255122
255251
  function writeLauncher(fileSystem, platform2, configHome, nodePath) {
255123
255252
  const scriptPath = launcherPath(configHome);
@@ -255219,15 +255348,15 @@ function registerCliPayload(fileSystem, configHome, standalonePath, writeOutput)
255219
255348
  if (!fileSystem.existsSync(standalonePath)) {
255220
255349
  return;
255221
255350
  }
255222
- const installedEntry = path22.join(configHome, "app", version3, "standalone.js");
255223
- const manifestPath = path22.join(configHome, "app", version3, "package.json");
255351
+ const installedEntry = path23.join(configHome, "app", version3, "standalone.js");
255352
+ const manifestPath = path23.join(configHome, "app", version3, "package.json");
255224
255353
  const payloadUnchanged = fileSystem.existsSync(installedEntry) && fileSystem.existsSync(manifestPath) && fileSystem.readFileSync(installedEntry).equals(fileSystem.readFileSync(standalonePath));
255225
255354
  if (!payloadUnchanged) {
255226
255355
  installApplicationPayload({ configHome, version: version3, sourceEntry: standalonePath });
255227
255356
  }
255228
- const versionsDir = path22.join(configHome, "versions");
255357
+ const versionsDir = path23.join(configHome, "versions");
255229
255358
  fileSystem.mkdirSync(versionsDir, { recursive: true });
255230
- const pointerPath2 = path22.join(versionsDir, `${version3}.json`);
255359
+ const pointerPath2 = path23.join(versionsDir, `${version3}.json`);
255231
255360
  const pointerContents = JSON.stringify({ path: installedEntry, version: version3 });
255232
255361
  if (!fileSystem.existsSync(pointerPath2) || fileSystem.readFileSync(pointerPath2, "utf8") !== pointerContents) {
255233
255362
  writeAtomicPointer(fileSystem, pointerPath2, pointerContents);
@@ -255242,11 +255371,11 @@ async function installCommand(deps = {}) {
255242
255371
  throw new Error(`Grain does not support supervised installation on ${platform2}.`);
255243
255372
  }
255244
255373
  const configHome = deps.configHome ?? resolveGrainHome();
255245
- const homeDirectory = deps.homeDirectory ?? os4.homedir();
255374
+ const homeDirectory = deps.homeDirectory ?? os5.homedir();
255246
255375
  const uid = deps.uid ?? process.getuid?.() ?? 0;
255247
- const fileSystem = deps.fileSystem ?? fs18;
255376
+ const fileSystem = deps.fileSystem ?? fs19;
255248
255377
  const runner = deps.commandRunner ?? createCommandRunner();
255249
- const adapter2 = deps.adapter ?? createSupervisorAdapter(platform2, runner, uid);
255378
+ const adapter2 = deps.adapter ?? createSupervisorAdapter(platform2, runner, uid, configHome);
255250
255379
  const readStatus = deps.readStatus ?? readGrainServiceStatus;
255251
255380
  const wait = deps.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
255252
255381
  const writeOutput = deps.writeOutput ?? ((message) => console.log(message));
@@ -255292,7 +255421,7 @@ async function installCommand(deps = {}) {
255292
255421
  });
255293
255422
  const launcher = platformLauncherPath(platform2, configHome);
255294
255423
  const unit = unitFor(platform2, launcher, configHome, nodePath);
255295
- const unitPath = path22.join(homeDirectory, unit.destination);
255424
+ const unitPath = path23.join(homeDirectory, unit.destination);
255296
255425
  const healthy = isRunningGrainService(await readStatus());
255297
255426
  const unitExists = fileSystem.existsSync(unitPath);
255298
255427
  const unitChanged = unitExists && String(fileSystem.readFileSync(unitPath, { encoding: unit.encoding })) !== unit.content;
@@ -255304,7 +255433,7 @@ async function installCommand(deps = {}) {
255304
255433
  return 0;
255305
255434
  }
255306
255435
  writeLauncher(fileSystem, platform2, configHome, nodePath);
255307
- fileSystem.mkdirSync(path22.dirname(unitPath), { recursive: true });
255436
+ fileSystem.mkdirSync(path23.dirname(unitPath), { recursive: true });
255308
255437
  fileSystem.writeFileSync(unitPath, unit.content, { encoding: unit.encoding, mode: 384 });
255309
255438
  if (registered && unitChanged) {
255310
255439
  await adapter2.unregister(GRAIN_SERVICE_LABEL);
@@ -255381,7 +255510,7 @@ async function installCommand(deps = {}) {
255381
255510
  await flushTelemetry(telemetry);
255382
255511
  writeOutput(
255383
255512
  `Fatal error: Grain service did not become reachable at its published endpoint after installation.
255384
- Check the supervisor logs at ${path22.join(configHome, "grain-daemon.out.log")} and ${path22.join(configHome, "grain-daemon.err.log")}`
255513
+ Check the supervisor logs at ${path23.join(configHome, "grain-daemon.out.log")} and ${path23.join(configHome, "grain-daemon.err.log")}`
255385
255514
  );
255386
255515
  return 1;
255387
255516
  }
@@ -255391,9 +255520,9 @@ Check the supervisor logs at ${path22.join(configHome, "grain-daemon.out.log")}
255391
255520
  }
255392
255521
 
255393
255522
  // src/server/install/uninstallCommand.ts
255394
- var fs20 = __toESM(require("fs"));
255395
- var os5 = __toESM(require("os"));
255396
- var path25 = __toESM(require("path"));
255523
+ var fs21 = __toESM(require("fs"));
255524
+ var os6 = __toESM(require("os"));
255525
+ var path26 = __toESM(require("path"));
255397
255526
  var import_readline = require("readline");
255398
255527
  init_grainHome();
255399
255528
  init_applicationPayload();
@@ -255416,7 +255545,7 @@ function reportKept(fileSystem, configHome, writeOutput) {
255416
255545
  return;
255417
255546
  }
255418
255547
  lines = fileSystem.readdirSync(configHome).sort().map((name) => {
255419
- const isDirectory = fileSystem.lstatSync(path25.join(configHome, name)).isDirectory();
255548
+ const isDirectory = fileSystem.lstatSync(path26.join(configHome, name)).isDirectory();
255420
255549
  return ` - ${name}${isDirectory ? "/" : ""}`;
255421
255550
  });
255422
255551
  } catch {
@@ -255465,16 +255594,16 @@ async function uninstallCommand(deps = {}) {
255465
255594
  throw new Error(`Grain does not support supervised installation on ${platform2}.`);
255466
255595
  }
255467
255596
  const configHome = deps.configHome ?? resolveGrainHome();
255468
- const homeDirectory = deps.homeDirectory ?? os5.homedir();
255597
+ const homeDirectory = deps.homeDirectory ?? os6.homedir();
255469
255598
  const uid = deps.uid ?? process.getuid?.() ?? 0;
255470
255599
  const nodePath = deps.nodePath ?? "";
255471
- const fileSystem = deps.fileSystem ?? fs20;
255600
+ const fileSystem = deps.fileSystem ?? fs21;
255472
255601
  const runner = deps.commandRunner ?? createCommandRunner();
255473
- const adapter2 = deps.adapter ?? createSupervisorAdapter(platform2, runner, uid);
255602
+ const adapter2 = deps.adapter ?? createSupervisorAdapter(platform2, runner, uid, configHome);
255474
255603
  const writeOutput = deps.writeOutput ?? ((message) => console.log(message));
255475
255604
  if (deps.purge) {
255476
- const resolvedHome = path25.resolve(configHome);
255477
- if (resolvedHome === path25.resolve(homeDirectory) || resolvedHome === path25.parse(resolvedHome).root) {
255605
+ const resolvedHome = path26.resolve(configHome);
255606
+ if (resolvedHome === path26.resolve(homeDirectory) || resolvedHome === path26.parse(resolvedHome).root) {
255478
255607
  writeOutput(`Refusing to purge ${configHome}: it is a home directory or filesystem root.`);
255479
255608
  return 1;
255480
255609
  }
@@ -255493,13 +255622,13 @@ async function uninstallCommand(deps = {}) {
255493
255622
  }
255494
255623
  const launcher = platformLauncherPath(platform2, configHome);
255495
255624
  const unit = unitFor(platform2, launcher, configHome, nodePath);
255496
- const grainCommand = path25.join(configHome, "bin", platform2 === "win32" ? "grain.cmd" : "grain");
255625
+ const grainCommand = path26.join(configHome, "bin", platform2 === "win32" ? "grain.cmd" : "grain");
255497
255626
  const ownedPaths = [
255498
255627
  grainCommand,
255499
255628
  launcherPath(configHome),
255500
255629
  launcher,
255501
255630
  applicationDir(configHome, ""),
255502
- path25.join(configHome, "runtime", "node")
255631
+ path26.join(configHome, "runtime", "node")
255503
255632
  ];
255504
255633
  const registered = await adapter2.isRegistered(GRAIN_SERVICE_LABEL);
255505
255634
  if (registered) {
@@ -255532,16 +255661,16 @@ async function uninstallCommand(deps = {}) {
255532
255661
  return 0;
255533
255662
  }
255534
255663
  const retainedVersions = retainedApplicationVersions(configHome);
255535
- removeIfExists(fileSystem, path25.join(homeDirectory, unit.destination));
255664
+ removeIfExists(fileSystem, path26.join(homeDirectory, unit.destination));
255536
255665
  removeIfExists(fileSystem, launcherPath(configHome));
255537
255666
  removeIfExists(fileSystem, grainCommand);
255538
255667
  if (platform2 === "win32") {
255539
255668
  removeIfExists(fileSystem, launcher);
255540
- removeIfExists(fileSystem, path25.join(configHome, "bin", "grain.ps1"));
255669
+ removeIfExists(fileSystem, path26.join(configHome, "bin", "grain.ps1"));
255541
255670
  }
255542
255671
  removeDirectoryIfExists(fileSystem, applicationDir(configHome, ""));
255543
- removeDirectoryIfExists(fileSystem, path25.join(configHome, "versions"));
255544
- removeDirectoryIfExists(fileSystem, path25.join(configHome, "runtime", "node"));
255672
+ removeDirectoryIfExists(fileSystem, path26.join(configHome, "versions"));
255673
+ removeDirectoryIfExists(fileSystem, path26.join(configHome, "runtime", "node"));
255545
255674
  const filesToRemove = [
255546
255675
  "endpoint.json",
255547
255676
  "grain-daemon.out.log",
@@ -255549,14 +255678,14 @@ async function uninstallCommand(deps = {}) {
255549
255678
  "service-mode.json"
255550
255679
  ];
255551
255680
  for (const file4 of filesToRemove) {
255552
- removeIfExists(fileSystem, path25.join(configHome, file4));
255681
+ removeIfExists(fileSystem, path26.join(configHome, file4));
255553
255682
  }
255554
255683
  try {
255555
255684
  if (fileSystem.existsSync(configHome)) {
255556
255685
  const allFiles = fileSystem.readdirSync(configHome);
255557
255686
  for (const file4 of allFiles) {
255558
255687
  if (/^server-\d+\.pid$/.test(file4)) {
255559
- removeIfExists(fileSystem, path25.join(configHome, file4));
255688
+ removeIfExists(fileSystem, path26.join(configHome, file4));
255560
255689
  }
255561
255690
  }
255562
255691
  }
@@ -255564,7 +255693,7 @@ async function uninstallCommand(deps = {}) {
255564
255693
  }
255565
255694
  const dirsToRemoveIfEmpty = ["bin", "runtime"];
255566
255695
  for (const dirName of dirsToRemoveIfEmpty) {
255567
- const dirPath = path25.join(configHome, dirName);
255696
+ const dirPath = path26.join(configHome, dirName);
255568
255697
  try {
255569
255698
  if (fileSystem.existsSync(dirPath)) {
255570
255699
  const contents = fileSystem.readdirSync(dirPath);
@@ -255590,7 +255719,7 @@ async function uninstallCommand(deps = {}) {
255590
255719
  }
255591
255720
 
255592
255721
  // src/server/cli/clientsCommand.ts
255593
- var import_crypto4 = __toESM(require("crypto"));
255722
+ var import_crypto5 = __toESM(require("crypto"));
255594
255723
  init_grainHome();
255595
255724
  init_toolProfiles();
255596
255725
  init_ClientRegistry();
@@ -255659,7 +255788,7 @@ async function clientsCommand(args, configDir = resolveGrainHome()) {
255659
255788
  return 1;
255660
255789
  }
255661
255790
  if (!id) {
255662
- id = import_crypto4.default.randomBytes(8).toString("hex");
255791
+ id = import_crypto5.default.randomBytes(8).toString("hex");
255663
255792
  }
255664
255793
  const registration = {
255665
255794
  id,
@@ -255817,7 +255946,7 @@ async function openCommand(configDir = resolveGrainHome(), runner = createComman
255817
255946
  init_daemonVersion();
255818
255947
 
255819
255948
  // src/server/cli/toolCommands.ts
255820
- var fs24 = __toESM(require("fs"));
255949
+ var fs25 = __toESM(require("fs"));
255821
255950
 
255822
255951
  // node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js
255823
255952
  init_protocol();
@@ -256777,7 +256906,7 @@ ${dataCommandHelp()}
256777
256906
  parsed = parseCommand(
256778
256907
  command,
256779
256908
  words,
256780
- deps.readFile ?? ((file4) => fs24.readFileSync(file4 === "-" ? 0 : file4, "utf8"))
256909
+ deps.readFile ?? ((file4) => fs25.readFileSync(file4 === "-" ? 0 : file4, "utf8"))
256781
256910
  );
256782
256911
  } catch (error52) {
256783
256912
  stderr(`${error52.message}