@dollhousemcp/mcp-server 2.0.15 → 2.0.16

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/generated/version.d.ts +2 -2
  3. package/dist/generated/version.js +3 -3
  4. package/dist/handlers/ElementCRUDHandler.d.ts.map +1 -1
  5. package/dist/handlers/ElementCRUDHandler.js +7 -3
  6. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
  7. package/dist/handlers/mcp-aql/MCPAQLHandler.js +35 -19
  8. package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
  9. package/dist/handlers/mcp-aql/OperationSchema.js +4 -3
  10. package/dist/handlers/mcp-aql/evaluatePermission.d.ts +2 -1
  11. package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -1
  12. package/dist/handlers/mcp-aql/evaluatePermission.js +22 -11
  13. package/dist/handlers/strategies/BaseActivationStrategy.d.ts.map +1 -1
  14. package/dist/handlers/strategies/BaseActivationStrategy.js +12 -3
  15. package/dist/handlers/strategies/PersonaActivationStrategy.js +2 -2
  16. package/dist/utils/permissionHooks.d.ts +38 -0
  17. package/dist/utils/permissionHooks.d.ts.map +1 -0
  18. package/dist/utils/permissionHooks.js +194 -0
  19. package/dist/web/public/index.html +12 -6
  20. package/dist/web/public/permissions.css +11 -0
  21. package/dist/web/public/permissions.js +43 -12
  22. package/dist/web/public/setup.css +172 -1
  23. package/dist/web/public/setup.js +353 -20
  24. package/dist/web/routes/permissionRoutes.d.ts.map +1 -1
  25. package/dist/web/routes/permissionRoutes.js +64 -15
  26. package/dist/web/routes/setupRoutes.d.ts +3 -0
  27. package/dist/web/routes/setupRoutes.d.ts.map +1 -1
  28. package/dist/web/routes/setupRoutes.js +14 -3
  29. package/package.json +6 -1
  30. package/scripts/pretooluse-codex.sh +6 -0
  31. package/scripts/pretooluse-cursor.sh +6 -0
  32. package/scripts/pretooluse-dollhouse.sh +110 -0
  33. package/scripts/pretooluse-gemini.sh +6 -0
  34. package/scripts/pretooluse-windsurf.sh +6 -0
  35. package/server.json +2 -2
@@ -11,22 +11,77 @@
11
11
  // ── Config builders ────────────────────────────────────────────────────
12
12
 
13
13
  const PKG = '@dollhousemcp/mcp-server';
14
+ const HOOKS_DIR = '~/.dollhouse/hooks';
15
+ const HOOK_BASE_SCRIPT_PATH = `${HOOKS_DIR}/pretooluse-dollhouse.sh`;
14
16
 
15
17
  /** Platform registry — drives config generation AND panel rendering */
16
18
  const PLATFORMS = [
17
19
  // Claude Desktop & Claude Code panels are handwritten in HTML (unique structure)
18
20
  { id: 'claude-desktop', rootKey: 'mcpServers' },
19
- { id: 'claude-code', rootKey: 'mcpServers', cli: 'claude' },
21
+ { id: 'claude-code', rootKey: 'mcpServers', cli: 'claude', hookSupport: 'verified', hookCommand: `bash ${HOOK_BASE_SCRIPT_PATH}`, hookConfigPath: '<code>~/.claude/settings.json</code>' },
20
22
  // These panels are generated from this data by renderGeneratedPanels()
21
- { id: 'cursor', rootKey: 'mcpServers', installClient: 'cursor', openClient: 'cursor', configPath: '<code>.cursor/mcp.json</code> in your project, or <code>~/.cursor/mcp.json</code> for all projects', hint: 'Or configure via Settings &gt; MCP Servers in the Cursor UI.' },
23
+ { id: 'cursor', rootKey: 'mcpServers', installClient: 'cursor', openClient: 'cursor', configPath: '<code>.cursor/mcp.json</code> in your project, or <code>~/.cursor/mcp.json</code> for all projects', hint: 'Or configure via Settings &gt; MCP Servers in the Cursor UI.', hookSupport: 'manual', hookCommand: `bash ${HOOKS_DIR}/pretooluse-cursor.sh`, hookConfigPath: '<code>.cursor/mcp.json</code> in your project, or <code>~/.cursor/mcp.json</code> for all projects' },
22
24
  { id: 'vscode', rootKey: 'servers', installClient: 'vscode', configPath: '<code>.vscode/mcp.json</code> in your workspace', hint: 'VS Code uses <code>"servers"</code>, not <code>"mcpServers"</code>.' },
23
- { id: 'codex', rootKey: 'mcpServers', installClient: 'codex', openClient: 'codex', cli: 'codex', toml: true, tomlPath: '<code>~/.codex/config.toml</code> (Codex uses TOML, not JSON)' },
24
- { id: 'gemini', rootKey: 'mcpServers', installClient: 'gemini-cli', openClient: 'gemini-cli', cli: 'gemini', configPath: '<code>~/.gemini/settings.json</code> or <code>.gemini/settings.json</code> in your project' },
25
- { id: 'windsurf', rootKey: 'mcpServers', installClient: 'windsurf', openClient: 'windsurf', configPath: '<code>~/.codeium/windsurf/mcp_config.json</code>', hint: 'Or click the MCPs icon in the Cascade panel &gt; Configure.' },
25
+ { id: 'codex', rootKey: 'mcpServers', installClient: 'codex', openClient: 'codex', cli: 'codex', toml: true, tomlPath: '<code>~/.codex/config.toml</code> (Codex uses TOML, not JSON)', hookSupport: 'manual', hookCommand: `bash ${HOOKS_DIR}/pretooluse-codex.sh`, hookConfigPath: '<code>~/.codex/config.toml</code>' },
26
+ { id: 'gemini', rootKey: 'mcpServers', installClient: 'gemini-cli', openClient: 'gemini-cli', cli: 'gemini', configPath: '<code>~/.gemini/settings.json</code> or <code>.gemini/settings.json</code> in your project', hookSupport: 'manual', hookCommand: `bash ${HOOKS_DIR}/pretooluse-gemini.sh`, hookConfigPath: '<code>~/.gemini/settings.json</code> or <code>.gemini/settings.json</code> in your project' },
27
+ { id: 'windsurf', rootKey: 'mcpServers', installClient: 'windsurf', openClient: 'windsurf', configPath: '<code>~/.codeium/windsurf/mcp_config.json</code>', hint: 'Or click the MCPs icon in the Cascade panel &gt; Configure.', hookSupport: 'manual', hookCommand: `bash ${HOOKS_DIR}/pretooluse-windsurf.sh`, hookConfigPath: '<code>~/.codeium/windsurf/mcp_config.json</code>' },
26
28
  { id: 'cline', rootKey: 'mcpServers', installClient: 'cline', configPath: '<code>cline_mcp_settings.json</code> via Cline\'s top nav &gt; Configure &gt; Advanced MCP Settings' },
27
29
  { id: 'lmstudio', rootKey: 'mcpServers', openClient: 'lmstudio', configPath: '<code>~/.lmstudio/mcp.json</code> (or open via Program tab &gt; Install &gt; Edit mcp.json)', hint: 'Restart LM Studio after saving.' },
28
30
  ];
29
31
 
32
+ const HOOK_BASE_SCRIPT = `#!/bin/bash
33
+ # pretooluse-dollhouse.sh — shared hook bridge for DollhouseMCP
34
+
35
+ PORT_FILE="$HOME/.dollhouse/run/permission-server.port"
36
+ HOOK_PLATFORM="\${DOLLHOUSE_HOOK_PLATFORM:-claude_code}"
37
+
38
+ [[ -f "$PORT_FILE" ]] || exit 0
39
+ PORT=$(cat "$PORT_FILE" 2>/dev/null)
40
+ [[ "$PORT" =~ ^[0-9]+$ ]] || exit 0
41
+
42
+ INPUT=$(cat)
43
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .tool // .name // empty' 2>/dev/null)
44
+ TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // {}' 2>/dev/null)
45
+ [[ -n "$TOOL_NAME" ]] || exit 0
46
+
47
+ PAYLOAD=$(jq -cn \\
48
+ --arg tool_name "$TOOL_NAME" \\
49
+ --arg platform "$HOOK_PLATFORM" \\
50
+ --arg session_id "\${DOLLHOUSE_SESSION_ID:-}" \\
51
+ --argjson input "$TOOL_INPUT" \\
52
+ '{ tool_name: $tool_name, input: $input, platform: $platform }
53
+ + (if ($session_id | length) > 0 then { session_id: $session_id } else {} end)')
54
+
55
+ RESPONSE=$(curl -s --max-time 5 -X POST "http://127.0.0.1:$PORT/api/evaluate_permission" \\
56
+ -H "Content-Type: application/json" \\
57
+ -d "$PAYLOAD" 2>/dev/null)
58
+
59
+ [[ -n "$RESPONSE" ]] && echo "$RESPONSE"
60
+ exit 0`;
61
+
62
+ const buildHookWrapperScript = (platform) => `#!/bin/bash
63
+ # pretooluse-${platform}.sh — manual hook wrapper for DollhouseMCP
64
+
65
+ export DOLLHOUSE_HOOK_PLATFORM="${platform}"
66
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
67
+ exec bash "$SCRIPT_DIR/pretooluse-dollhouse.sh"`;
68
+
69
+ const CLAUDE_CODE_HOOK_SETTINGS = `{
70
+ "hooks": {
71
+ "PreToolUse": [
72
+ {
73
+ "matcher": "*",
74
+ "hooks": [
75
+ {
76
+ "type": "command",
77
+ "command": "bash ${HOOK_BASE_SCRIPT_PATH}"
78
+ }
79
+ ]
80
+ }
81
+ ]
82
+ }
83
+ }`;
84
+
30
85
  /** Build a JSON config block for a given npx command string */
31
86
  function jsonConfig(rootKey, npxCmd) {
32
87
  const parts = npxCmd.split(' ');
@@ -140,10 +195,11 @@
140
195
 
141
196
  if (prereq) prereq.hidden = method !== 'global';
142
197
  if (mcpbSection) mcpbSection.hidden = method !== 'global';
143
- if (channelToggle) channelToggle.hidden = method === 'global';
198
+ if (channelToggle) channelToggle.hidden = method !== 'npx';
144
199
 
145
- updateAllConfigs(method);
200
+ updateAllConfigs(method === 'permissions' ? 'npx' : method);
146
201
  updateInstallButtonLabels();
202
+ updateSetupModeSections();
147
203
  updateDetectionState();
148
204
  };
149
205
 
@@ -154,7 +210,7 @@
154
210
  // Sync initial visibility — if the browser restored a non-default
155
211
  // active button (e.g. pinned was selected before reload), apply
156
212
  // the hidden state now without waiting for a click.
157
- if (channelToggle) channelToggle.hidden = currentMethod === 'global';
213
+ if (channelToggle) channelToggle.hidden = currentMethod !== 'npx';
158
214
  };
159
215
 
160
216
  // ── Channel selector ──────────────────────────────────────────────────
@@ -221,6 +277,24 @@
221
277
  }
222
278
  };
223
279
 
280
+ const updateSetupModeSections = () => {
281
+ document.querySelectorAll('[data-setup-modes]').forEach((section) => {
282
+ const modes = (section.dataset.setupModes || '')
283
+ .split(/\s+/)
284
+ .filter(Boolean);
285
+ section.hidden = !modes.includes(currentMethod);
286
+ });
287
+
288
+ const permissionsIntro = document.getElementById('setup-permissions-intro');
289
+ if (permissionsIntro) {
290
+ permissionsIntro.hidden = currentMethod !== 'permissions';
291
+ }
292
+
293
+ document.querySelectorAll('.setup-installed-notice').forEach((notice) => {
294
+ notice.hidden = currentMethod === 'permissions';
295
+ });
296
+ };
297
+
224
298
  /** Update a single code block's displayed code and copy button */
225
299
  const updateCodeBlock = (block, config) => {
226
300
  if (!block || !config) return;
@@ -450,6 +524,68 @@
450
524
  }
451
525
  };
452
526
 
527
+ const updatePermissionInstallButton = (btn, detected) => {
528
+ if (!btn || btn.classList.contains('is-success')) return;
529
+
530
+ if (detected?.hookInstalled) {
531
+ btn.textContent = 'Permissions enabled';
532
+ btn.disabled = true;
533
+ btn.classList.add('is-match');
534
+ return;
535
+ }
536
+
537
+ btn.textContent = 'Configure Now';
538
+ btn.disabled = false;
539
+ btn.classList.remove('is-match');
540
+ };
541
+
542
+ const handlePermissionInstallClick = async (btn) => {
543
+ const client = btn.dataset.permissionInstallClient;
544
+ if (!client) return;
545
+
546
+ const status = document.querySelector(`[data-permission-install-status="${client}"]`);
547
+ const originalText = btn.textContent;
548
+
549
+ btn.disabled = true;
550
+ btn.textContent = 'Configuring...';
551
+ btn.classList.add('is-loading');
552
+ if (status) {
553
+ status.textContent = '';
554
+ status.className = 'setup-install-status';
555
+ }
556
+
557
+ try {
558
+ const res = await DollhouseAuth.apiFetch('/api/setup/install', {
559
+ method: 'POST',
560
+ headers: { 'Content-Type': 'application/json' },
561
+ body: JSON.stringify({ client }),
562
+ });
563
+
564
+ const data = await res.json();
565
+ if (!data.success) throw new Error(data.error || 'Installation failed');
566
+
567
+ await fetchDetection();
568
+ updateDetectionState();
569
+
570
+ btn.textContent = 'Permissions enabled';
571
+ btn.classList.remove('is-loading');
572
+ btn.classList.add('is-success');
573
+
574
+ if (status) {
575
+ status.textContent = 'Claude Code permissions are enabled. Restart Claude Code if it is already running.';
576
+ status.classList.add('is-success');
577
+ }
578
+ } catch (err) {
579
+ btn.textContent = originalText;
580
+ btn.disabled = false;
581
+ btn.classList.remove('is-loading');
582
+ if (status) {
583
+ status.textContent = formatInstallError(err);
584
+ status.classList.add('is-error');
585
+ }
586
+ }
587
+ };
588
+
453
589
  // ── Completion banner ────────────────────────────────────────────────
454
590
 
455
591
  /** Friendly display names for install clients */
@@ -551,6 +687,19 @@
551
687
  });
552
688
  };
553
689
 
690
+ const initPermissionInstallButtons = () => {
691
+ document.querySelectorAll('.setup-permission-install-btn').forEach((btn) => {
692
+ btn.addEventListener('click', () => handlePermissionInstallClick(btn));
693
+ const client = btn.dataset.permissionInstallClient;
694
+ const status = document.querySelector(`[data-permission-install-status="${client}"]`);
695
+ if (status) {
696
+ const statusId = `permission-install-status-${client}`;
697
+ status.id = statusId;
698
+ btn.setAttribute('aria-describedby', statusId);
699
+ }
700
+ });
701
+ };
702
+
554
703
  // ── Open config file buttons ───────────────────────────────────────────
555
704
 
556
705
  /** Handle Open config file button click */
@@ -712,23 +861,104 @@
712
861
  * Called on init and whenever the method toggle changes.
713
862
  */
714
863
  const updateDetectionState = () => {
715
- for (const platformId of Object.values(clientToPlatform)) {
864
+ const platformIds = new Set(['claude-desktop', ...PLATFORMS.map((platform) => platform.id)]);
865
+ for (const platformId of platformIds) {
716
866
  updatePlatformDetectionState(platformId);
717
867
  }
718
868
  };
719
869
 
870
+ const PERMISSION_PLATFORM_LABELS = {
871
+ 'claude-desktop': 'Claude Desktop',
872
+ 'claude-code': 'Claude Code',
873
+ cursor: 'Cursor',
874
+ vscode: 'VS Code',
875
+ codex: 'Codex',
876
+ gemini: 'Gemini CLI',
877
+ windsurf: 'Windsurf',
878
+ cline: 'Cline',
879
+ lmstudio: 'LM Studio',
880
+ };
881
+
882
+ const getPermissionStatusCopy = (platformId, detected) => {
883
+ if (platformId === 'claude-code') {
884
+ if (detected?.hookInstalled) {
885
+ return {
886
+ tone: 'info',
887
+ titleText: 'Claude Code permission enforcement is enabled.',
888
+ messageText: 'No further changes are needed here unless you want to reinstall the hook settings.',
889
+ };
890
+ }
891
+
892
+ if (detected?.installed) {
893
+ return {
894
+ tone: 'warning',
895
+ titleText: 'Claude Code is connected for this client.',
896
+ messageText: 'DollhouseMCP is configured as an MCP server. Use Configure Now below to also install the Claude Code permission hook.',
897
+ };
898
+ }
899
+
900
+ return {
901
+ tone: 'info',
902
+ titleText: 'Claude Code permissions are not configured yet.',
903
+ messageText: 'First connect DollhouseMCP using Auto-updating or Pinned version, then use Configure Now below to install the Claude Code permission hook.',
904
+ };
905
+ }
906
+
907
+ const support = PLATFORMS.find((platform) => platform.id === platformId)?.hookSupport || 'unsupported';
908
+ if (support === 'manual') {
909
+ if (detected?.installed) {
910
+ return {
911
+ tone: 'warning',
912
+ titleText: 'DollhouseMCP is connected for this client.',
913
+ messageText: 'DollhouseMCP is configured here, but permission enforcement is separate. Use the manual hook steps below to turn it on for this client.',
914
+ };
915
+ }
916
+
917
+ return {
918
+ tone: 'info',
919
+ titleText: 'Manual permissions setup is available for this client.',
920
+ messageText: 'Use the steps below if you want to turn on permission enforcement for this client manually.',
921
+ };
922
+ }
923
+
924
+ const platformLabel = PERMISSION_PLATFORM_LABELS[platformId] || 'this client';
925
+ return {
926
+ tone: detected?.installed ? 'warning' : 'neutral',
927
+ titleText: `Permissions & security tools are unavailable for ${platformLabel} right now.`,
928
+ messageText: detected?.installed
929
+ ? 'DollhouseMCP is connected for this client, but this release does not include a supported permissions setup flow here yet.'
930
+ : 'This release does not include a supported permissions setup flow for this client yet.',
931
+ };
932
+ };
933
+
934
+ const updatePermissionStatus = (panel, platformId, detected) => {
935
+ const status = panel?.querySelector('.setup-permission-status');
936
+ if (!status) return;
937
+
938
+ const title = status.querySelector('.setup-permission-status-title');
939
+ const message = status.querySelector('.setup-permission-status-msg');
940
+ const { tone, titleText, messageText } = getPermissionStatusCopy(platformId, detected);
941
+
942
+ status.dataset.state = tone;
943
+ if (title) title.textContent = titleText;
944
+ if (message) message.textContent = messageText;
945
+ };
946
+
720
947
  /** Update notice, badge, button, AND current config display for a single platform */
721
948
  const updatePlatformDetectionState = (platformId) => {
722
949
  const detected = detectedConfigs[platformId];
950
+ const panel = document.getElementById('setup-panel-' + platformId);
951
+ const tabBtn = document.getElementById('setup-tab-' + platformId);
952
+ updatePermissionStatus(panel, platformId, detected);
953
+
723
954
  if (!detected?.installed) return;
724
955
 
725
956
  const matches = configsMatch(platformId, currentMethod);
726
- const panel = document.getElementById('setup-panel-' + platformId);
727
- const tabBtn = document.getElementById('setup-tab-' + platformId);
728
957
 
729
958
  updateDetectionNotice(panel?.querySelector('.setup-installed-notice'), matches);
730
959
  updateDetectionBadge(tabBtn?.querySelector('.setup-tab-badge'), matches);
731
960
  updateDetectionButton(panel?.querySelector('.setup-install-btn'), matches);
961
+ updatePermissionInstallButton(panel?.querySelector('.setup-permission-install-btn'), detected);
732
962
 
733
963
  // Refresh the "Current config" code block with the latest detected config
734
964
  if (detected.currentConfig && panel) {
@@ -830,6 +1060,8 @@
830
1060
  .replaceAll('>', '&gt;')
831
1061
  .replaceAll('"', '&quot;');
832
1062
 
1063
+ const escapeAttr = (str) => escapeHtml(str).replaceAll("'", '&#39;');
1064
+
833
1065
  // ── Generate platform panels from registry ─────────────────────────────
834
1066
 
835
1067
  /** Build an Open config file button string, or empty if no openClient */
@@ -840,13 +1072,13 @@
840
1072
  const renderInstallSection = (p) => {
841
1073
  let html = '';
842
1074
  if (p.installClient) {
843
- html += '<div class="setup-method setup-method-primary">';
1075
+ html += '<div class="setup-method setup-method-primary" data-setup-modes="npx global">';
844
1076
  html += `<div class="setup-install-row"><button class="setup-btn setup-btn-primary setup-install-btn" type="button" data-install-client="${p.installClient}">Configure Now</button>`;
845
1077
  html += `<span class="setup-install-status" data-install-status="${p.installClient}"></span></div>`;
846
1078
  }
847
1079
  if (p.cli) {
848
1080
  const cmd = `${p.cli} mcp add dollhousemcp -- npx -y ${PKG}@latest`;
849
- if (!p.installClient) html += '<div class="setup-method setup-method-primary">';
1081
+ if (!p.installClient) html += '<div class="setup-method setup-method-primary" data-setup-modes="npx global">';
850
1082
  html += '<h3>Or run in your terminal</h3><p>Run this in your terminal:</p>';
851
1083
  html += `<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text="${cmd}" aria-label="Copy command">Copy</button>`;
852
1084
  html += `<pre><code>${cmd}</code></pre></div>`;
@@ -864,9 +1096,9 @@
864
1096
  let html = '';
865
1097
 
866
1098
  if (hasPrimaryBlock) {
867
- html += `</div><div class="setup-method"><h3>Or add config manually${openBtnHtml(p.openClient)}</h3>`;
1099
+ html += `</div><div class="setup-method" data-setup-modes="npx global"><h3>Or add config manually${openBtnHtml(p.openClient)}</h3>`;
868
1100
  } else {
869
- html += `<div class="setup-method setup-method-primary"><h3>Config${openBtnHtml(p.openClient)}</h3>`;
1101
+ html += `<div class="setup-method setup-method-primary" data-setup-modes="npx global"><h3>Config${openBtnHtml(p.openClient)}</h3>`;
870
1102
  }
871
1103
  html += `<p>Add to ${p.configPath}:</p>`;
872
1104
  html += `<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${copyText}' aria-label="Copy config">Copy</button>`;
@@ -881,13 +1113,102 @@
881
1113
  if (!p.tomlPath) return '';
882
1114
  const tomlConfig = configs[p.id]?.npxToml;
883
1115
  const tomlCode = tomlConfig?.code || '';
884
- let html = `<div class="setup-method"><h3>Or add to config${openBtnHtml(p.openClient)}</h3>`;
1116
+ let html = `<div class="setup-method" data-setup-modes="npx global"><h3>Or add to config${openBtnHtml(p.openClient)}</h3>`;
885
1117
  html += `<p>Add to ${p.tomlPath}:</p>`;
886
1118
  html += `<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${tomlCode}' aria-label="Copy config">Copy</button>`;
887
1119
  html += `<pre><code>${tomlCode}</code></pre></div></div>`;
888
1120
  return html;
889
1121
  };
890
1122
 
1123
+ const renderPermissionSection = (p) => {
1124
+ const hookSupport = p.hookSupport || 'unsupported';
1125
+ const configPath = p.hookConfigPath || p.configPath || p.tomlPath || 'this client’s user configuration';
1126
+
1127
+ if (hookSupport === 'verified' && p.id === 'claude-code') {
1128
+ return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
1129
+ <h3>Permissions &amp; Security <span class="setup-support-badge setup-support-badge--verified">claude code</span></h3>
1130
+ <div class="setup-permission-status" data-state="info">
1131
+ <strong class="setup-permission-status-title"></strong>
1132
+ <p class="setup-permission-status-msg"></p>
1133
+ </div>
1134
+ <div class="setup-install-row">
1135
+ <button class="setup-btn setup-btn-primary setup-permission-install-btn" type="button" data-permission-install-client="claude-code">Configure Now</button>
1136
+ <span class="setup-install-status" data-permission-install-status="claude-code"></span>
1137
+ </div>
1138
+ <p class="setup-hint">This writes the shared hook bridge to <code>${HOOK_BASE_SCRIPT_PATH}</code> and updates ${configPath} automatically.</p>
1139
+ </div>
1140
+ <div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
1141
+ <details class="setup-manual-fallback">
1142
+ <summary>Manual fallback</summary>
1143
+ <div class="setup-manual-fallback-body">
1144
+ <h4>1. Save the shared hook bridge once</h4>
1145
+ <p>Save this file as <code>${HOOK_BASE_SCRIPT_PATH}</code>, then make it executable with <code>chmod +x ${HOOK_BASE_SCRIPT_PATH}</code>.</p>
1146
+ <div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(HOOK_BASE_SCRIPT)}' aria-label="Copy shared hook bridge">Copy</button>
1147
+ <pre><code>${escapeHtml(HOOK_BASE_SCRIPT)}</code></pre>
1148
+ </div>
1149
+ <h4>2. Add the Claude Code hook settings</h4>
1150
+ <p>Add this block to ${configPath} so Claude Code can call the hook bridge before tool use.</p>
1151
+ <div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(CLAUDE_CODE_HOOK_SETTINGS)}' aria-label="Copy Claude Code hook settings">Copy</button>
1152
+ <pre><code>${escapeHtml(CLAUDE_CODE_HOOK_SETTINGS)}</code></pre>
1153
+ </div>
1154
+ <p class="setup-hint">Command hook target: <code>${HOOK_BASE_SCRIPT_PATH}</code></p>
1155
+ </div>
1156
+ </details>
1157
+ </div>`;
1158
+ }
1159
+
1160
+ if (hookSupport === 'manual') {
1161
+ const platformName = p.id === 'gemini' ? 'gemini' : p.id;
1162
+ const wrapperFilename = `pretooluse-${platformName}.sh`;
1163
+ const wrapperScript = buildHookWrapperScript(platformName);
1164
+ return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
1165
+ <h3>Permissions &amp; Security <span class="setup-support-badge setup-support-badge--manual">manual setup</span></h3>
1166
+ <div class="setup-permission-status" data-state="info">
1167
+ <strong class="setup-permission-status-title"></strong>
1168
+ <p class="setup-permission-status-msg"></p>
1169
+ </div>
1170
+ <p>To turn on permission enforcement for this client manually, add this command anywhere the client supports a pre-tool or pre-command hook:</p>
1171
+ <div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text="${escapeAttr(p.hookCommand)}" aria-label="Copy hook command">Copy</button>
1172
+ <pre><code>${escapeHtml(p.hookCommand)}</code></pre>
1173
+ </div>
1174
+ <p>Save this wrapper in <code>${HOOKS_DIR}</code> alongside the shared Dollhouse hook bridge:</p>
1175
+ <div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(wrapperScript)}' aria-label="Copy ${wrapperFilename}">Copy</button>
1176
+ <pre><code>${escapeHtml(wrapperScript)}</code></pre>
1177
+ </div>
1178
+ <p class="setup-hint">Known config location for this client: ${configPath}</p>
1179
+ </div>`;
1180
+ }
1181
+
1182
+ return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
1183
+ <h3>Permissions &amp; Security <span class="setup-support-badge setup-support-badge--unsupported">coming soon</span></h3>
1184
+ <div class="setup-permission-status" data-state="neutral">
1185
+ <strong class="setup-permission-status-title"></strong>
1186
+ <p class="setup-permission-status-msg"></p>
1187
+ </div>
1188
+ </div>`;
1189
+ };
1190
+
1191
+ const renderPermissionsIntro = () => {
1192
+ const intro = document.getElementById('setup-permissions-intro');
1193
+ if (!intro) return;
1194
+
1195
+ intro.innerHTML = `<div class="setup-permissions-note">
1196
+ <strong>Permissions &amp; Security</strong>
1197
+ <p>Use this mode to turn on permission enforcement for supported clients. Claude Code is fully guided in this release. Where we have workable manual steps for other clients, they are shown here. Otherwise, the client will be marked as coming soon.</p>
1198
+ </div>`;
1199
+ };
1200
+
1201
+ const injectStaticPermissionsSections = () => {
1202
+ const claudeDesktopPanel = document.getElementById('setup-panel-claude-desktop');
1203
+ const claudeCodePanel = document.getElementById('setup-panel-claude-code');
1204
+ const claudeCodeConfig = PLATFORMS.find((p) => p.id === 'claude-code');
1205
+
1206
+ claudeDesktopPanel?.insertAdjacentHTML('beforeend', renderPermissionSection({ id: 'claude-desktop' }));
1207
+ if (claudeCodePanel && claudeCodeConfig) {
1208
+ claudeCodePanel.insertAdjacentHTML('beforeend', renderPermissionSection(claudeCodeConfig));
1209
+ }
1210
+ };
1211
+
891
1212
  const renderGeneratedPanels = () => {
892
1213
  const container = document.getElementById('setup-generated-panels');
893
1214
  if (!container) return;
@@ -906,7 +1227,8 @@
906
1227
  section.innerHTML =
907
1228
  renderInstallSection(p) +
908
1229
  renderJsonSection(p, hasPrimaryBlock) +
909
- renderTomlSection(p);
1230
+ renderTomlSection(p) +
1231
+ renderPermissionSection(p);
910
1232
 
911
1233
  container.appendChild(section);
912
1234
  }
@@ -1232,9 +1554,16 @@
1232
1554
  if (license.useCase) rows.push(['Use case', license.useCase]);
1233
1555
  }
1234
1556
 
1235
- licenseInfoTable.innerHTML = rows
1236
- .map(([label, value]) => `<tr><td>${label}</td><td>${value}</td></tr>`)
1237
- .join('');
1557
+ const rowNodes = rows.map(([label, value]) => {
1558
+ const tr = document.createElement('tr');
1559
+ const labelCell = document.createElement('td');
1560
+ const valueCell = document.createElement('td');
1561
+ labelCell.textContent = label;
1562
+ valueCell.textContent = value;
1563
+ tr.append(labelCell, valueCell);
1564
+ return tr;
1565
+ });
1566
+ licenseInfoTable.replaceChildren(...rowNodes);
1238
1567
  licenseDetailsPanel.hidden = false;
1239
1568
  }
1240
1569
 
@@ -1350,15 +1679,19 @@
1350
1679
  // ── Init ──────────────────────────────────────────────────────────────
1351
1680
 
1352
1681
  const os = detectOS();
1682
+ renderPermissionsIntro();
1353
1683
  renderGeneratedPanels();
1684
+ injectStaticPermissionsSections();
1354
1685
  highlightOSPaths(os);
1355
1686
  initMethodToggle();
1356
1687
  initChannelSelector();
1357
1688
  initPlatformTabs();
1358
1689
  initCopyButtons();
1359
1690
  initInstallButtons();
1691
+ initPermissionInstallButtons();
1360
1692
  initOpenButtons();
1361
1693
  fetchVersion();
1362
1694
  fetchDetection();
1363
1695
  initLicense();
1696
+ updateSetupModeSections();
1364
1697
  })();
@@ -1 +1 @@
1
- {"version":3,"file":"permissionRoutes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/permissionRoutes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAgB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AA4F7E;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CAoHrF"}
1
+ {"version":3,"file":"permissionRoutes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/permissionRoutes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAgB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAsI7E;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CA8HrF"}