@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.
- package/CHANGELOG.md +4 -0
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +3 -3
- package/dist/handlers/ElementCRUDHandler.d.ts.map +1 -1
- package/dist/handlers/ElementCRUDHandler.js +7 -3
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/MCPAQLHandler.js +35 -19
- package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationSchema.js +4 -3
- package/dist/handlers/mcp-aql/evaluatePermission.d.ts +2 -1
- package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/evaluatePermission.js +22 -11
- package/dist/handlers/strategies/BaseActivationStrategy.d.ts.map +1 -1
- package/dist/handlers/strategies/BaseActivationStrategy.js +12 -3
- package/dist/handlers/strategies/PersonaActivationStrategy.js +2 -2
- package/dist/utils/permissionHooks.d.ts +38 -0
- package/dist/utils/permissionHooks.d.ts.map +1 -0
- package/dist/utils/permissionHooks.js +194 -0
- package/dist/web/public/index.html +12 -6
- package/dist/web/public/permissions.css +11 -0
- package/dist/web/public/permissions.js +43 -12
- package/dist/web/public/setup.css +172 -1
- package/dist/web/public/setup.js +353 -20
- package/dist/web/routes/permissionRoutes.d.ts.map +1 -1
- package/dist/web/routes/permissionRoutes.js +64 -15
- package/dist/web/routes/setupRoutes.d.ts +3 -0
- package/dist/web/routes/setupRoutes.d.ts.map +1 -1
- package/dist/web/routes/setupRoutes.js +14 -3
- package/package.json +6 -1
- package/scripts/pretooluse-codex.sh +6 -0
- package/scripts/pretooluse-cursor.sh +6 -0
- package/scripts/pretooluse-dollhouse.sh +110 -0
- package/scripts/pretooluse-gemini.sh +6 -0
- package/scripts/pretooluse-windsurf.sh +6 -0
- package/server.json +2 -2
package/dist/web/public/setup.js
CHANGED
|
@@ -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 > 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 > 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 > 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 > 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 > Configure > Advanced MCP Settings' },
|
|
27
29
|
{ id: 'lmstudio', rootKey: 'mcpServers', openClient: 'lmstudio', configPath: '<code>~/.lmstudio/mcp.json</code> (or open via Program tab > Install > 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
|
|
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
|
|
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
|
-
|
|
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('>', '>')
|
|
831
1061
|
.replaceAll('"', '"');
|
|
832
1062
|
|
|
1063
|
+
const escapeAttr = (str) => escapeHtml(str).replaceAll("'", ''');
|
|
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 & 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 & 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 & 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 & 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
|
-
|
|
1236
|
-
|
|
1237
|
-
.
|
|
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;
|
|
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"}
|