@trops/dash-core 0.1.383 → 0.1.385

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.
@@ -28330,11 +28330,21 @@ const cliController$2 = {
28330
28330
  // to — causing long reasoning loops or silent hangs. We pass only the
28331
28331
  // caller's `systemPrompt` as context.
28332
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
+ //
28333
28341
  // (We intentionally avoid `--bare` — it also disables keychain reads,
28334
28342
  // which breaks OAuth login for users authenticated via `claude login`.)
28335
28343
  const args = [
28336
28344
  "-p",
28337
28345
  "--disable-slash-commands",
28346
+ "--permission-mode",
28347
+ "bypassPermissions",
28338
28348
  "--output-format",
28339
28349
  "stream-json",
28340
28350
  "--verbose",
@@ -28696,7 +28706,7 @@ const dashboardTools$1 = [
28696
28706
  {
28697
28707
  name: "create_dashboard",
28698
28708
  description:
28699
- "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.",
28700
28710
  inputSchema: {
28701
28711
  type: "object",
28702
28712
  properties: {
@@ -28950,7 +28960,7 @@ const themeTools$1 = [
28950
28960
  {
28951
28961
  name: "apply_theme",
28952
28962
  description:
28953
- "Apply a saved theme to the active dashboard. The theme must already exist -- use list_themes to see available themes, or create one first with create_theme or create_theme_from_url.",
28963
+ "Apply a saved theme. Omit `dashboard` to set the app-wide default theme (affects every dashboard that doesn't have its own override). Pass `dashboard` (name or ID) to set that dashboard's theme override instead — useful when the user asks for a theme on a specific dashboard (e.g. 'apply ocean to my Sales dashboard'). The theme must already exist; use list_themes to see available themes or create one with create_theme / create_theme_from_url.",
28954
28964
  inputSchema: {
28955
28965
  type: "object",
28956
28966
  properties: {
@@ -28958,6 +28968,11 @@ const themeTools$1 = [
28958
28968
  type: "string",
28959
28969
  description: "Name of the theme to apply",
28960
28970
  },
28971
+ dashboard: {
28972
+ type: "string",
28973
+ description:
28974
+ "Optional dashboard name or numeric ID. Omit for app-wide application.",
28975
+ },
28961
28976
  },
28962
28977
  required: ["name"],
28963
28978
  },
@@ -49317,6 +49332,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
49317
49332
 
49318
49333
  const https$2 = require$$8$1;
49319
49334
  const { randomUUID } = require$$1$5;
49335
+ const { BrowserWindow } = require$$0$1;
49320
49336
  const { McpServer } = mcp;
49321
49337
  const {
49322
49338
  StreamableHTTPServerTransport,
@@ -49325,6 +49341,52 @@ const {
49325
49341
  const settingsController$3 = settingsController_1;
49326
49342
  const { getOrCreateCert } = tlsCert;
49327
49343
 
49344
+ // Tool-name prefixes that indicate a mutation. After a successful call
49345
+ // to any of these, the renderer is notified via "dash-mcp:state-changed"
49346
+ // so it can refresh the relevant UI slice (themes, dashboards, widgets,
49347
+ // providers, etc.) without requiring a manual reload.
49348
+ const MUTATING_PREFIXES = [
49349
+ "create_",
49350
+ "add_",
49351
+ "remove_",
49352
+ "delete_",
49353
+ "update_",
49354
+ "apply_",
49355
+ "install_",
49356
+ "move_",
49357
+ "set_",
49358
+ "configure_",
49359
+ ];
49360
+
49361
+ function isMutatingTool(name) {
49362
+ return MUTATING_PREFIXES.some((p) => name.startsWith(p));
49363
+ }
49364
+
49365
+ function broadcastStateChanged(toolName, result) {
49366
+ // Best-effort parse of the tool's first text content block. MCP tool
49367
+ // results are of shape { content: [{ type: "text", text: "<json>" }] }.
49368
+ // Expose the parsed JSON as `result` so renderers can act on specifics
49369
+ // (e.g. the new dashboard ID from create_dashboard) without a round
49370
+ // trip back to fetch state.
49371
+ let parsed = null;
49372
+ try {
49373
+ const firstText = result?.content?.find?.((c) => c.type === "text")?.text;
49374
+ if (firstText) parsed = JSON.parse(firstText);
49375
+ } catch {
49376
+ /* leave null */
49377
+ }
49378
+ const payload = { toolName, result: parsed };
49379
+ for (const win of BrowserWindow.getAllWindows()) {
49380
+ if (!win.isDestroyed()) {
49381
+ try {
49382
+ win.webContents.send("dash-mcp:state-changed", payload);
49383
+ } catch {
49384
+ /* ignore */
49385
+ }
49386
+ }
49387
+ }
49388
+ }
49389
+
49328
49390
  // --- State ---
49329
49391
  let mcpServer = null;
49330
49392
  let httpsServer = null;
@@ -49408,14 +49470,22 @@ const { jsonSchemaToZod } = jsonSchemaToZod_1;
49408
49470
  function applyRegistrations(server) {
49409
49471
  for (const tool of registeredTools) {
49410
49472
  const zodSchema = jsonSchemaToZod(tool.inputSchema);
49473
+ // Wrap mutating tool handlers so a successful invocation broadcasts
49474
+ // "dash-mcp:state-changed" to all renderer windows. Read-only tools
49475
+ // (list_, get_, search_) are passed through unwrapped.
49476
+ const mutating = isMutatingTool(tool.name);
49477
+ const handler = mutating
49478
+ ? async (...args) => {
49479
+ const result = await tool.handler(...args);
49480
+ if (result && !result.isError) {
49481
+ broadcastStateChanged(tool.name, result);
49482
+ }
49483
+ return result;
49484
+ }
49485
+ : tool.handler;
49411
49486
  // server.tool() expects a raw Zod shape (e.g. { name: z.string() }),
49412
49487
  // NOT a z.object() wrapper. Extract .shape from the Zod object.
49413
- server.tool(
49414
- tool.name,
49415
- tool.description,
49416
- zodSchema.shape || {},
49417
- tool.handler,
49418
- );
49488
+ server.tool(tool.name, tool.description, zodSchema.shape || {}, handler);
49419
49489
  }
49420
49490
  for (const resource of registeredResources) {
49421
49491
  server.resource(
@@ -58936,6 +59006,17 @@ async function handleCreateDashboard$1({ name, layout }) {
58936
59006
  };
58937
59007
  }
58938
59008
 
59009
+ // Default to a 1×1 grid when the caller omits `layout`. A bare
59010
+ // container dashboard has no grid cells, so widgets can't be added
59011
+ // without further editing — even a single-cell grid avoids that
59012
+ // dead-end while staying unopinionated about layout. Callers that
59013
+ // want a specific size pass an explicit `layout` object. Callers
59014
+ // that genuinely want a layout-less container must pass
59015
+ // `layout: null` explicitly.
59016
+ if (layout === undefined) {
59017
+ layout = { rows: 1, cols: 1 };
59018
+ }
59019
+
58939
59020
  // Validate optional layout parameter
58940
59021
  if (layout !== undefined && layout !== null) {
58941
59022
  if (typeof layout !== "object" || Array.isArray(layout)) {
@@ -60529,7 +60610,7 @@ async function handleCreateThemeFromUrl$1({ url, name }) {
60529
60610
  * apply_theme — Applies a saved theme to the active dashboard.
60530
60611
  * Updates settings to set the active theme key and notifies the renderer.
60531
60612
  */
60532
- async function handleApplyTheme$1({ name }) {
60613
+ async function handleApplyTheme$1({ name, dashboard }) {
60533
60614
  if (!name || typeof name !== "string" || !name.trim()) {
60534
60615
  return {
60535
60616
  content: [
@@ -60576,7 +60657,93 @@ async function handleApplyTheme$1({ name }) {
60576
60657
  };
60577
60658
  }
60578
60659
 
60579
- // Update settings to set the active theme
60660
+ // Dashboard-scoped apply: if the caller provided `dashboard`
60661
+ // (either a workspace ID or a workspace name), set that workspace's
60662
+ // theme override instead of the global default. Empty/omitted
60663
+ // `dashboard` means app-level.
60664
+ const dashboardRef =
60665
+ typeof dashboard === "string" ? dashboard.trim() : dashboard;
60666
+ if (
60667
+ dashboardRef !== undefined &&
60668
+ dashboardRef !== null &&
60669
+ dashboardRef !== ""
60670
+ ) {
60671
+ const wsList = workspaceController$2.listWorkspacesForApplication(win, appId);
60672
+ if (wsList?.error) {
60673
+ return {
60674
+ content: [
60675
+ { type: "text", text: JSON.stringify({ error: wsList.message }) },
60676
+ ],
60677
+ isError: true,
60678
+ };
60679
+ }
60680
+ const workspaces = wsList?.workspaces || [];
60681
+ // Match by numeric ID first, then by name (case-insensitive).
60682
+ const asNumber = Number(dashboardRef);
60683
+ const match =
60684
+ (!Number.isNaN(asNumber) &&
60685
+ workspaces.find((w) => Number(w.id) === asNumber)) ||
60686
+ workspaces.find(
60687
+ (w) =>
60688
+ typeof w.name === "string" &&
60689
+ w.name.toLowerCase() === String(dashboardRef).toLowerCase(),
60690
+ );
60691
+ if (!match) {
60692
+ return {
60693
+ content: [
60694
+ {
60695
+ type: "text",
60696
+ text: JSON.stringify({
60697
+ error: `Dashboard not found: ${dashboardRef}. Use list_dashboards to see available dashboards.`,
60698
+ }),
60699
+ },
60700
+ ],
60701
+ isError: true,
60702
+ };
60703
+ }
60704
+ // The renderer reads `workspace.themeKey` (via WorkspaceModel and
60705
+ // DashboardThemeProvider) — NOT `workspace.theme`. Set both for
60706
+ // forward-compat in case anything else reads the older field, but
60707
+ // `themeKey` is the one that actually drives the override.
60708
+ const updated = { ...match, themeKey: themeName, theme: themeName };
60709
+ const saveResult = workspaceController$2.saveWorkspaceForApplication(
60710
+ win,
60711
+ appId,
60712
+ updated,
60713
+ );
60714
+ if (saveResult?.error) {
60715
+ return {
60716
+ content: [
60717
+ {
60718
+ type: "text",
60719
+ text: JSON.stringify({ error: saveResult.message }),
60720
+ },
60721
+ ],
60722
+ isError: true,
60723
+ };
60724
+ }
60725
+ win.webContents.send("workspace:saved");
60726
+ return {
60727
+ content: [
60728
+ {
60729
+ type: "text",
60730
+ text: JSON.stringify(
60731
+ {
60732
+ name: themeName,
60733
+ applied: true,
60734
+ scope: "dashboard",
60735
+ dashboardId: String(match.id),
60736
+ dashboardName: match.name,
60737
+ },
60738
+ null,
60739
+ 2,
60740
+ ),
60741
+ },
60742
+ ],
60743
+ };
60744
+ }
60745
+
60746
+ // App-level apply: update application settings
60580
60747
  const settingsResult = settingsController$2.getSettingsForApplication(win);
60581
60748
  const settings = settingsResult?.settings || {};
60582
60749
  settings.theme = themeName;
@@ -60605,7 +60772,11 @@ async function handleApplyTheme$1({ name }) {
60605
60772
  content: [
60606
60773
  {
60607
60774
  type: "text",
60608
- text: JSON.stringify({ name: themeName, applied: true }, null, 2),
60775
+ text: JSON.stringify(
60776
+ { name: themeName, applied: true, scope: "app" },
60777
+ null,
60778
+ 2,
60779
+ ),
60609
60780
  },
60610
60781
  ],
60611
60782
  };
@@ -74824,6 +74995,23 @@ const mcpDashServerApi$2 = {
74824
74995
  * @returns {Promise<string>}
74825
74996
  */
74826
74997
  getToken: () => ipcRenderer$3.invoke(MCP_DASH_SERVER_GET_TOKEN, {}),
74998
+
74999
+ /**
75000
+ * Subscribe to state-change notifications fired after any mutating
75001
+ * MCP tool call (create_*, add_*, apply_*, remove_*, update_*,
75002
+ * move_*, configure_*, set_*, delete_*, install_*). The callback
75003
+ * receives { toolName }. Use this to refresh renderer state (theme,
75004
+ * dashboards, widgets, providers) so MCP-driven changes are
75005
+ * reflected in the UI without requiring a manual reload.
75006
+ *
75007
+ * @param {(payload: { toolName: string }) => void} callback
75008
+ * @returns {() => void} unsubscribe function
75009
+ */
75010
+ onStateChanged: (callback) => {
75011
+ const handler = (_event, payload) => callback(payload);
75012
+ ipcRenderer$3.on("dash-mcp:state-changed", handler);
75013
+ return () => ipcRenderer$3.removeListener("dash-mcp:state-changed", handler);
75014
+ },
74827
75015
  };
74828
75016
 
74829
75017
  var mcpDashServerApi_1 = mcpDashServerApi$2;