@trops/dash-core 0.1.382 → 0.1.384

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.
@@ -18,7 +18,8 @@ var require$$8$1 = require('https');
18
18
  var require$$0$6 = require('@modelcontextprotocol/sdk/client/index.js');
19
19
  var require$$1$4 = require('@modelcontextprotocol/sdk/client/stdio.js');
20
20
  var require$$0$5 = require('pkce-challenge');
21
- var require$$6$1 = require('child_process');
21
+ var require$$5$1 = require('os');
22
+ var require$$7 = require('child_process');
22
23
  var require$$2$2 = require('algoliasearch');
23
24
  var require$$3$3 = require('node:path');
24
25
  var require$$0$7 = require('openai');
@@ -28,7 +29,6 @@ var require$$1$5 = require('crypto');
28
29
  var require$$0$8 = require('http');
29
30
  var require$$1$6 = require('http2');
30
31
  var require$$2$3 = require('node-forge');
31
- var require$$2$5 = require('os');
32
32
  var require$$3$4 = require('adm-zip');
33
33
  var require$$4$1 = require('url');
34
34
  var require$$2$4 = require('vm');
@@ -25629,6 +25629,7 @@ const {
25629
25629
  } = streamableHttp$1;
25630
25630
  const path$c = require$$1$2;
25631
25631
  const fs$8 = require$$0$2;
25632
+ const os = require$$5$1;
25632
25633
  const responseCache$2 = responseCache_1;
25633
25634
 
25634
25635
  /**
@@ -25676,6 +25677,8 @@ function isReadOnlyTool(toolName) {
25676
25677
  */
25677
25678
  const DEFAULT_TOOL_CACHE_TTL = 5000;
25678
25679
 
25680
+ const IS_WINDOWS$1 = process.platform === "win32";
25681
+
25679
25682
  /**
25680
25683
  * Cached shell PATH result (resolved once, reused for all spawns).
25681
25684
  */
@@ -25712,11 +25715,21 @@ function isNodeEsmError(errorText) {
25712
25715
  function getShellPath$1() {
25713
25716
  if (_shellPath$1 !== null) return _shellPath$1;
25714
25717
 
25715
- const { execSync } = require$$6$1;
25718
+ // Windows: skip the POSIX shell-path discovery entirely. The nvm /
25719
+ // homebrew / volta fallback dirs below are *nix-specific, and
25720
+ // Windows Electron apps typically inherit a working PATH from the
25721
+ // launcher. Users managing Node via nvm-windows or fnm should
25722
+ // already have their shims on PATH.
25723
+ if (IS_WINDOWS$1) {
25724
+ _shellPath$1 = process.env.PATH || "";
25725
+ return _shellPath$1;
25726
+ }
25727
+
25728
+ const { execSync } = require$$7;
25716
25729
  const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
25717
25730
 
25718
25731
  // Scan nvm versions, tracking both latest and best compatible version
25719
- const home = process.env.HOME || "";
25732
+ const home = os.homedir();
25720
25733
  let compatibleNvmBin = null;
25721
25734
  if (home) {
25722
25735
  fallbackDirs.push(`${home}/.volta/bin`);
@@ -25855,7 +25868,7 @@ function interpolate$1(template, credentials) {
25855
25868
  * @param {object} tokenRefresh { credentialsPath, oauthKeysPath }
25856
25869
  */
25857
25870
  async function refreshGoogleOAuthToken(tokenRefresh) {
25858
- const home = process.env.HOME || "";
25871
+ const home = os.homedir();
25859
25872
  const credPath = tokenRefresh.credentialsPath.replace(/^~/, home);
25860
25873
  const keysPath = tokenRefresh.oauthKeysPath.replace(/^~/, home);
25861
25874
 
@@ -26051,7 +26064,7 @@ const mcpController$2 = {
26051
26064
  // Merge static env vars from mcpConfig (with ~ expansion)
26052
26065
  if (mcpConfig.staticEnv) {
26053
26066
  Object.entries(mcpConfig.staticEnv).forEach(([envVar, value]) => {
26054
- env[envVar] = value.replace(/^~/, process.env.HOME || "");
26067
+ env[envVar] = value.replace(/^~/, os.homedir());
26055
26068
  });
26056
26069
  }
26057
26070
 
@@ -26542,7 +26555,7 @@ const mcpController$2 = {
26542
26555
  * @returns {{ success } | { error, message }}
26543
26556
  */
26544
26557
  runAuth: async (win, mcpConfig, credentials, authCommand) => {
26545
- const { spawn } = require$$6$1;
26558
+ const { spawn } = require$$7;
26546
26559
 
26547
26560
  const env = cleanEnvForChildProcess();
26548
26561
 
@@ -26551,7 +26564,7 @@ const mcpController$2 = {
26551
26564
  const { from, to } = authCommand.setup.copyCredential;
26552
26565
  const sourcePath = credentials?.[from];
26553
26566
  if (sourcePath) {
26554
- const destPath = to.replace(/^~/, process.env.HOME || "");
26567
+ const destPath = to.replace(/^~/, os.homedir());
26555
26568
  const destDir = require$$1$2.dirname(destPath);
26556
26569
  try {
26557
26570
  fs$8.mkdirSync(destDir, { recursive: true });
@@ -26586,7 +26599,7 @@ const mcpController$2 = {
26586
26599
  // Merge static env vars from authCommand with ~ expansion
26587
26600
  if (authCommand.staticEnv) {
26588
26601
  Object.entries(authCommand.staticEnv).forEach(([key, value]) => {
26589
- env[key] = value.replace(/^~/, process.env.HOME || "");
26602
+ env[key] = value.replace(/^~/, os.homedir());
26590
26603
  });
26591
26604
  }
26592
26605
 
@@ -26602,6 +26615,8 @@ const mcpController$2 = {
26602
26615
  const proc = spawn(authCommand.command, resolvedArgs, {
26603
26616
  env,
26604
26617
  stdio: ["ignore", "pipe", "pipe"],
26618
+ // Needed so Windows can launch .cmd/.bat wrappers (npx.cmd, etc).
26619
+ shell: IS_WINDOWS$1,
26605
26620
  });
26606
26621
 
26607
26622
  let stdout = "";
@@ -28149,7 +28164,7 @@ var pluginController_1 = pluginController$1;
28149
28164
  * can use the Chat widget without a separate API key.
28150
28165
  */
28151
28166
 
28152
- const { spawn, execSync } = require$$6$1;
28167
+ const { spawn, execSync } = require$$7;
28153
28168
  const {
28154
28169
  LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
28155
28170
  LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
@@ -28158,6 +28173,8 @@ const {
28158
28173
  LLM_STREAM_ERROR: LLM_STREAM_ERROR$2,
28159
28174
  } = llmEvents$1;
28160
28175
 
28176
+ const IS_WINDOWS = process.platform === "win32";
28177
+
28161
28178
  /**
28162
28179
  * Cached shell PATH result (resolved once, reused for all spawns).
28163
28180
  * Same pattern as mcpController.js.
@@ -28167,6 +28184,13 @@ let _shellPath = null;
28167
28184
  function getShellPath() {
28168
28185
  if (_shellPath !== null) return _shellPath;
28169
28186
 
28187
+ // Windows: no POSIX login-shell trick — just use the inherited PATH,
28188
+ // which is typically correct for GUI-launched Electron apps.
28189
+ if (IS_WINDOWS) {
28190
+ _shellPath = process.env.PATH || "";
28191
+ return _shellPath;
28192
+ }
28193
+
28170
28194
  try {
28171
28195
  const shell = process.env.SHELL || "/bin/bash";
28172
28196
  _shellPath = execSync(`${shell} -ilc 'echo -n "$PATH"'`, {
@@ -28181,7 +28205,7 @@ function getShellPath() {
28181
28205
  }
28182
28206
 
28183
28207
  /**
28184
- * Cached CLI binary path (resolved once via `which claude`).
28208
+ * Cached CLI binary path (resolved once via `which` / `where`).
28185
28209
  */
28186
28210
  let _cliBinaryPath = undefined; // undefined = not yet checked
28187
28211
 
@@ -28190,11 +28214,21 @@ function resolveCliBinary() {
28190
28214
 
28191
28215
  try {
28192
28216
  const fullPath = getShellPath();
28193
- _cliBinaryPath = execSync("which claude", {
28217
+ // `where` on Windows, `which` everywhere else. `where` may list
28218
+ // multiple matches on separate lines (e.g. claude.cmd + claude.ps1)
28219
+ // — take the first hit.
28220
+ const lookup = IS_WINDOWS ? "where claude" : "which claude";
28221
+ const result = execSync(lookup, {
28194
28222
  encoding: "utf8",
28195
28223
  timeout: 5000,
28196
28224
  env: { ...process.env, PATH: fullPath },
28197
- }).trim();
28225
+ });
28226
+ _cliBinaryPath = IS_WINDOWS
28227
+ ? result
28228
+ .split(/\r?\n/)
28229
+ .find((l) => l.trim())
28230
+ ?.trim() || null
28231
+ : result.trim();
28198
28232
  } catch {
28199
28233
  _cliBinaryPath = null;
28200
28234
  }
@@ -28208,6 +28242,33 @@ function resolveCliBinary() {
28208
28242
  */
28209
28243
  const activeProcesses = new Map();
28210
28244
 
28245
+ /**
28246
+ * Kill a child process and its descendants. On Windows, spawning with
28247
+ * shell:true (needed for .cmd targets) means child.kill() only
28248
+ * terminates the cmd.exe — the real CLI keeps running. Use taskkill
28249
+ * with /T (tree) /F (force) to clean up.
28250
+ */
28251
+ function killChildTree(child) {
28252
+ if (!child || child.killed || typeof child.pid !== "number") return;
28253
+ if (IS_WINDOWS) {
28254
+ try {
28255
+ execSync(`taskkill /pid ${child.pid} /T /F`, {
28256
+ stdio: "ignore",
28257
+ timeout: 5000,
28258
+ });
28259
+ } catch {
28260
+ // Fall back to plain kill — best-effort
28261
+ try {
28262
+ child.kill();
28263
+ } catch {
28264
+ /* ignore */
28265
+ }
28266
+ }
28267
+ } else {
28268
+ child.kill("SIGTERM");
28269
+ }
28270
+ }
28271
+
28211
28272
  /**
28212
28273
  * Session IDs for conversation continuity.
28213
28274
  * Map<widgetUuid, sessionId>
@@ -28269,11 +28330,21 @@ const cliController$2 = {
28269
28330
  // to — causing long reasoning loops or silent hangs. We pass only the
28270
28331
  // caller's `systemPrompt` as context.
28271
28332
  //
28333
+ // --permission-mode bypassPermissions: in an embedded in-app assistant,
28334
+ // the user has already opted into the configured MCP servers (they
28335
+ // ran `claude mcp add` themselves). Prompting for tool-use approval
28336
+ // on every call produces "I need permission to..." replies instead of
28337
+ // actual actions. Bypassing matches the user's intent — if they
28338
+ // didn't want the assistant to use a tool, they wouldn't have
28339
+ // configured it.
28340
+ //
28272
28341
  // (We intentionally avoid `--bare` — it also disables keychain reads,
28273
28342
  // which breaks OAuth login for users authenticated via `claude login`.)
28274
28343
  const args = [
28275
28344
  "-p",
28276
28345
  "--disable-slash-commands",
28346
+ "--permission-mode",
28347
+ "bypassPermissions",
28277
28348
  "--output-format",
28278
28349
  "stream-json",
28279
28350
  "--verbose",
@@ -28319,6 +28390,10 @@ const cliController$2 = {
28319
28390
  const spawnOpts = {
28320
28391
  env: { ...process.env, PATH: fullPath },
28321
28392
  stdio: ["pipe", "pipe", "pipe"],
28393
+ // On Windows, the Claude CLI is typically installed as claude.cmd
28394
+ // (a batch wrapper). Node's child_process.spawn can't launch .cmd
28395
+ // files directly without a shell — ENOENT otherwise.
28396
+ shell: IS_WINDOWS,
28322
28397
  };
28323
28398
  if (cwd) {
28324
28399
  const fs = require("fs");
@@ -28523,7 +28598,7 @@ const cliController$2 = {
28523
28598
  abortRequest: (requestId) => {
28524
28599
  const child = activeProcesses.get(requestId);
28525
28600
  if (child) {
28526
- child.kill("SIGTERM");
28601
+ killChildTree(child);
28527
28602
  activeProcesses.delete(requestId);
28528
28603
  return { success: true };
28529
28604
  }
@@ -28580,7 +28655,7 @@ const cliController$2 = {
28580
28655
  // Kill any active processes for this widget
28581
28656
  for (const [reqId, child] of activeProcesses) {
28582
28657
  if (reqId.startsWith(widgetUuid)) {
28583
- child.kill("SIGTERM");
28658
+ killChildTree(child);
28584
28659
  activeProcesses.delete(reqId);
28585
28660
  }
28586
28661
  }
@@ -28631,7 +28706,7 @@ const dashboardTools$1 = [
28631
28706
  {
28632
28707
  name: "create_dashboard",
28633
28708
  description:
28634
- "Create a new dashboard with the given name. Optionally provide a layout to create a grid dashboard. Returns the dashboard ID. After creating, use search_widgets or list_widgets to find widgets, then add_widget to populate the dashboard.",
28709
+ "Create a new dashboard with the given name. Defaults to a 1×1 grid layout if `layout` is omitted the resulting dashboard has a single cell ready for a widget. Pass an explicit `layout` object to use different dimensions. Pass `layout: null` only if the caller specifically wants a layout-less container dashboard (rare — widgets cannot be added without further editing). Returns the dashboard ID.",
28635
28710
  inputSchema: {
28636
28711
  type: "object",
28637
28712
  properties: {
@@ -49252,6 +49327,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
49252
49327
 
49253
49328
  const https$2 = require$$8$1;
49254
49329
  const { randomUUID } = require$$1$5;
49330
+ const { BrowserWindow } = require$$0$1;
49255
49331
  const { McpServer } = mcp;
49256
49332
  const {
49257
49333
  StreamableHTTPServerTransport,
@@ -49260,6 +49336,52 @@ const {
49260
49336
  const settingsController$3 = settingsController_1;
49261
49337
  const { getOrCreateCert } = tlsCert;
49262
49338
 
49339
+ // Tool-name prefixes that indicate a mutation. After a successful call
49340
+ // to any of these, the renderer is notified via "dash-mcp:state-changed"
49341
+ // so it can refresh the relevant UI slice (themes, dashboards, widgets,
49342
+ // providers, etc.) without requiring a manual reload.
49343
+ const MUTATING_PREFIXES = [
49344
+ "create_",
49345
+ "add_",
49346
+ "remove_",
49347
+ "delete_",
49348
+ "update_",
49349
+ "apply_",
49350
+ "install_",
49351
+ "move_",
49352
+ "set_",
49353
+ "configure_",
49354
+ ];
49355
+
49356
+ function isMutatingTool(name) {
49357
+ return MUTATING_PREFIXES.some((p) => name.startsWith(p));
49358
+ }
49359
+
49360
+ function broadcastStateChanged(toolName, result) {
49361
+ // Best-effort parse of the tool's first text content block. MCP tool
49362
+ // results are of shape { content: [{ type: "text", text: "<json>" }] }.
49363
+ // Expose the parsed JSON as `result` so renderers can act on specifics
49364
+ // (e.g. the new dashboard ID from create_dashboard) without a round
49365
+ // trip back to fetch state.
49366
+ let parsed = null;
49367
+ try {
49368
+ const firstText = result?.content?.find?.((c) => c.type === "text")?.text;
49369
+ if (firstText) parsed = JSON.parse(firstText);
49370
+ } catch {
49371
+ /* leave null */
49372
+ }
49373
+ const payload = { toolName, result: parsed };
49374
+ for (const win of BrowserWindow.getAllWindows()) {
49375
+ if (!win.isDestroyed()) {
49376
+ try {
49377
+ win.webContents.send("dash-mcp:state-changed", payload);
49378
+ } catch {
49379
+ /* ignore */
49380
+ }
49381
+ }
49382
+ }
49383
+ }
49384
+
49263
49385
  // --- State ---
49264
49386
  let mcpServer = null;
49265
49387
  let httpsServer = null;
@@ -49343,14 +49465,22 @@ const { jsonSchemaToZod } = jsonSchemaToZod_1;
49343
49465
  function applyRegistrations(server) {
49344
49466
  for (const tool of registeredTools) {
49345
49467
  const zodSchema = jsonSchemaToZod(tool.inputSchema);
49468
+ // Wrap mutating tool handlers so a successful invocation broadcasts
49469
+ // "dash-mcp:state-changed" to all renderer windows. Read-only tools
49470
+ // (list_, get_, search_) are passed through unwrapped.
49471
+ const mutating = isMutatingTool(tool.name);
49472
+ const handler = mutating
49473
+ ? async (...args) => {
49474
+ const result = await tool.handler(...args);
49475
+ if (result && !result.isError) {
49476
+ broadcastStateChanged(tool.name, result);
49477
+ }
49478
+ return result;
49479
+ }
49480
+ : tool.handler;
49346
49481
  // server.tool() expects a raw Zod shape (e.g. { name: z.string() }),
49347
49482
  // NOT a z.object() wrapper. Extract .shape from the Zod object.
49348
- server.tool(
49349
- tool.name,
49350
- tool.description,
49351
- zodSchema.shape || {},
49352
- tool.handler,
49353
- );
49483
+ server.tool(tool.name, tool.description, zodSchema.shape || {}, handler);
49354
49484
  }
49355
49485
  for (const resource of registeredResources) {
49356
49486
  server.resource(
@@ -50645,7 +50775,7 @@ var schedulerController_1 = schedulerController$2;
50645
50775
  (function (module) {
50646
50776
  const fs = require$$0$2;
50647
50777
  const path = require$$1$2;
50648
- const os = require$$2$5;
50778
+ const os = require$$5$1;
50649
50779
  const AdmZip = require$$3$4;
50650
50780
  const { fileURLToPath } = require$$4$1;
50651
50781
  const { app, ipcMain, BrowserWindow } = require$$0$1;
@@ -58871,6 +59001,17 @@ async function handleCreateDashboard$1({ name, layout }) {
58871
59001
  };
58872
59002
  }
58873
59003
 
59004
+ // Default to a 1×1 grid when the caller omits `layout`. A bare
59005
+ // container dashboard has no grid cells, so widgets can't be added
59006
+ // without further editing — even a single-cell grid avoids that
59007
+ // dead-end while staying unopinionated about layout. Callers that
59008
+ // want a specific size pass an explicit `layout` object. Callers
59009
+ // that genuinely want a layout-less container must pass
59010
+ // `layout: null` explicitly.
59011
+ if (layout === undefined) {
59012
+ layout = { rows: 1, cols: 1 };
59013
+ }
59014
+
58874
59015
  // Validate optional layout parameter
58875
59016
  if (layout !== undefined && layout !== null) {
58876
59017
  if (typeof layout !== "object" || Array.isArray(layout)) {
@@ -74759,6 +74900,23 @@ const mcpDashServerApi$2 = {
74759
74900
  * @returns {Promise<string>}
74760
74901
  */
74761
74902
  getToken: () => ipcRenderer$3.invoke(MCP_DASH_SERVER_GET_TOKEN, {}),
74903
+
74904
+ /**
74905
+ * Subscribe to state-change notifications fired after any mutating
74906
+ * MCP tool call (create_*, add_*, apply_*, remove_*, update_*,
74907
+ * move_*, configure_*, set_*, delete_*, install_*). The callback
74908
+ * receives { toolName }. Use this to refresh renderer state (theme,
74909
+ * dashboards, widgets, providers) so MCP-driven changes are
74910
+ * reflected in the UI without requiring a manual reload.
74911
+ *
74912
+ * @param {(payload: { toolName: string }) => void} callback
74913
+ * @returns {() => void} unsubscribe function
74914
+ */
74915
+ onStateChanged: (callback) => {
74916
+ const handler = (_event, payload) => callback(payload);
74917
+ ipcRenderer$3.on("dash-mcp:state-changed", handler);
74918
+ return () => ipcRenderer$3.removeListener("dash-mcp:state-changed", handler);
74919
+ },
74762
74920
  };
74763
74921
 
74764
74922
  var mcpDashServerApi_1 = mcpDashServerApi$2;