@dollhousemcp/mcp-server 2.0.15 → 2.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md.backup +18 -0
- package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
- package/dist/elements/base/BaseElementManager.js +17 -1
- package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
- package/dist/elements/memories/MemoryManager.js +13 -2
- 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/element-crud/createElement.d.ts.map +1 -1
- package/dist/handlers/element-crud/createElement.js +6 -2
- package/dist/handlers/element-crud/editElement.d.ts.map +1 -1
- package/dist/handlers/element-crud/editElement.js +6 -2
- package/dist/handlers/element-crud/helpers.d.ts +2 -0
- package/dist/handlers/element-crud/helpers.d.ts.map +1 -1
- package/dist/handlers/element-crud/helpers.js +21 -2
- package/dist/handlers/mcp-aql/IntrospectionResolver.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/IntrospectionResolver.js +34 -7
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/MCPAQLHandler.js +51 -20
- package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationSchema.js +6 -4
- 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 +23 -11
- package/dist/handlers/mcp-aql/policies/ElementPolicies.d.ts +8 -0
- package/dist/handlers/mcp-aql/policies/ElementPolicies.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/policies/ElementPolicies.js +26 -1
- 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 +74 -0
- package/dist/utils/permissionHooks.d.ts.map +1 -0
- package/dist/utils/permissionHooks.js +771 -0
- package/dist/web/public/index.html +12 -6
- package/dist/web/public/permissions.css +11 -0
- package/dist/web/public/permissions.js +78 -35
- package/dist/web/public/setup.css +172 -1
- package/dist/web/public/setup.js +644 -38
- package/dist/web/routes/permissionRoutes.d.ts.map +1 -1
- package/dist/web/routes/permissionRoutes.js +95 -27
- package/dist/web/routes/setupRoutes.d.ts +4 -0
- package/dist/web/routes/setupRoutes.d.ts.map +1 -1
- package/dist/web/routes/setupRoutes.js +122 -39
- package/package.json +8 -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-vscode.sh +163 -0
- package/scripts/pretooluse-windsurf.sh +168 -0
- package/server.json +2 -2
package/dist/web/public/setup.js
CHANGED
|
@@ -11,22 +11,165 @@
|
|
|
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.' },
|
|
22
|
-
{ 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.' },
|
|
26
|
-
{ id: 'cline', rootKey: 'mcpServers', installClient: 'cline', configPath: '<code>cline_mcp_settings.json</code>
|
|
27
|
-
{ id: 'lmstudio', rootKey: 'mcpServers', openClient: 'lmstudio',
|
|
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: 'partial', hookCommand: `bash ${HOOKS_DIR}/pretooluse-cursor.sh`, hookConfigPath: '<code>.cursor/hooks.json</code> in your project, or <code>~/.cursor/hooks.json</code> for all projects' },
|
|
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>.', hookSupport: 'partial', hookCommand: `bash ${HOOKS_DIR}/pretooluse-vscode.sh`, hookConfigPath: '<code>~/.copilot/hooks/dollhouse-permissions.json</code> plus <code>chat.hookFilesLocations</code> in VS Code user settings' },
|
|
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: 'partial', hookCommand: `bash ${HOOKS_DIR}/pretooluse-codex.sh`, hookConfigPath: '<code>~/.codex/hooks.json</code> and <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: 'partial', 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: 'partial', hookCommand: `bash ${HOOKS_DIR}/pretooluse-windsurf.sh`, hookConfigPath: '<code>~/.codeium/windsurf/hooks.json</code> or <code>.windsurf/hooks.json</code> in your project' },
|
|
28
|
+
{ id: 'cline', rootKey: 'mcpServers', installClient: 'cline', openClient: 'cline', configPath: 'Cline stores MCP servers in <code>cline_mcp_settings.json</code> inside its extension settings. Use Configure Now or open the file directly.' },
|
|
29
|
+
{ id: 'lmstudio', rootKey: 'mcpServers', installClient: 'lmstudio', 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
|
+
|
|
85
|
+
const GEMINI_HOOK_SETTINGS = `{
|
|
86
|
+
"hooks": {
|
|
87
|
+
"BeforeTool": [
|
|
88
|
+
{
|
|
89
|
+
"matcher": ".*",
|
|
90
|
+
"hooks": [
|
|
91
|
+
{
|
|
92
|
+
"type": "command",
|
|
93
|
+
"command": "bash ${HOOKS_DIR}/pretooluse-gemini.sh"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
}`;
|
|
100
|
+
|
|
101
|
+
const CODEX_HOOK_SETTINGS = `{
|
|
102
|
+
"hooks": {
|
|
103
|
+
"PreToolUse": [
|
|
104
|
+
{
|
|
105
|
+
"matcher": "Bash",
|
|
106
|
+
"hooks": [
|
|
107
|
+
{
|
|
108
|
+
"type": "command",
|
|
109
|
+
"command": "bash ${HOOKS_DIR}/pretooluse-codex.sh",
|
|
110
|
+
"statusMessage": "Checking Bash permissions"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
}`;
|
|
117
|
+
|
|
118
|
+
const CURSOR_HOOK_SETTINGS = `{
|
|
119
|
+
"version": 1,
|
|
120
|
+
"hooks": {
|
|
121
|
+
"preToolUse": [
|
|
122
|
+
{
|
|
123
|
+
"type": "command",
|
|
124
|
+
"command": "bash ${HOOKS_DIR}/pretooluse-cursor.sh",
|
|
125
|
+
"matcher": ".*"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}`;
|
|
130
|
+
|
|
131
|
+
const VSCODE_HOOK_SETTINGS = `{
|
|
132
|
+
"hooks": {
|
|
133
|
+
"PreToolUse": [
|
|
134
|
+
{
|
|
135
|
+
"matcher": "*",
|
|
136
|
+
"hooks": [
|
|
137
|
+
{
|
|
138
|
+
"type": "command",
|
|
139
|
+
"command": "bash ${HOOKS_DIR}/pretooluse-vscode.sh"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
}`;
|
|
146
|
+
|
|
147
|
+
const VSCODE_HOOK_LOCATIONS_SETTINGS = `{
|
|
148
|
+
"chat.hookFilesLocations": {
|
|
149
|
+
"~/.copilot/hooks": true
|
|
150
|
+
}
|
|
151
|
+
}`;
|
|
152
|
+
|
|
153
|
+
const WINDSURF_HOOK_SETTINGS = `{
|
|
154
|
+
"hooks": {
|
|
155
|
+
"pre_run_command": [
|
|
156
|
+
{
|
|
157
|
+
"type": "command",
|
|
158
|
+
"command": "bash ${HOOKS_DIR}/pretooluse-windsurf.sh"
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
"pre_mcp_tool_use": [
|
|
162
|
+
{
|
|
163
|
+
"type": "command",
|
|
164
|
+
"command": "bash ${HOOKS_DIR}/pretooluse-windsurf.sh"
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
}`;
|
|
169
|
+
|
|
170
|
+
const CODEX_HOOK_FEATURES_TOML = `[features]
|
|
171
|
+
codex_hooks = true`;
|
|
172
|
+
|
|
30
173
|
/** Build a JSON config block for a given npx command string */
|
|
31
174
|
function jsonConfig(rootKey, npxCmd) {
|
|
32
175
|
const parts = npxCmd.split(' ');
|
|
@@ -140,10 +283,11 @@
|
|
|
140
283
|
|
|
141
284
|
if (prereq) prereq.hidden = method !== 'global';
|
|
142
285
|
if (mcpbSection) mcpbSection.hidden = method !== 'global';
|
|
143
|
-
if (channelToggle) channelToggle.hidden = method
|
|
286
|
+
if (channelToggle) channelToggle.hidden = method !== 'npx';
|
|
144
287
|
|
|
145
|
-
updateAllConfigs(method);
|
|
288
|
+
updateAllConfigs(method === 'permissions' ? 'npx' : method);
|
|
146
289
|
updateInstallButtonLabels();
|
|
290
|
+
updateSetupModeSections();
|
|
147
291
|
updateDetectionState();
|
|
148
292
|
};
|
|
149
293
|
|
|
@@ -154,7 +298,7 @@
|
|
|
154
298
|
// Sync initial visibility — if the browser restored a non-default
|
|
155
299
|
// active button (e.g. pinned was selected before reload), apply
|
|
156
300
|
// the hidden state now without waiting for a click.
|
|
157
|
-
if (channelToggle) channelToggle.hidden = currentMethod
|
|
301
|
+
if (channelToggle) channelToggle.hidden = currentMethod !== 'npx';
|
|
158
302
|
};
|
|
159
303
|
|
|
160
304
|
// ── Channel selector ──────────────────────────────────────────────────
|
|
@@ -221,6 +365,24 @@
|
|
|
221
365
|
}
|
|
222
366
|
};
|
|
223
367
|
|
|
368
|
+
const updateSetupModeSections = () => {
|
|
369
|
+
document.querySelectorAll('[data-setup-modes]').forEach((section) => {
|
|
370
|
+
const modes = (section.dataset.setupModes || '')
|
|
371
|
+
.split(/\s+/)
|
|
372
|
+
.filter(Boolean);
|
|
373
|
+
section.hidden = !modes.includes(currentMethod);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const permissionsIntro = document.getElementById('setup-permissions-intro');
|
|
377
|
+
if (permissionsIntro) {
|
|
378
|
+
permissionsIntro.hidden = currentMethod !== 'permissions';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
document.querySelectorAll('.setup-installed-notice').forEach((notice) => {
|
|
382
|
+
notice.hidden = currentMethod === 'permissions';
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
|
|
224
386
|
/** Update a single code block's displayed code and copy button */
|
|
225
387
|
const updateCodeBlock = (block, config) => {
|
|
226
388
|
if (!block || !config) return;
|
|
@@ -385,6 +547,32 @@
|
|
|
385
547
|
: `${msg}. Try the manual config below.`;
|
|
386
548
|
};
|
|
387
549
|
|
|
550
|
+
const buildInstallPayload = (client) => {
|
|
551
|
+
const payload = { client };
|
|
552
|
+
if (currentMethod === 'global' && pinnedVersion && pinnedVersion !== 'latest') {
|
|
553
|
+
payload.version = pinnedVersion;
|
|
554
|
+
} else if (currentChannel !== 'latest') {
|
|
555
|
+
payload.channel = currentChannel;
|
|
556
|
+
}
|
|
557
|
+
return payload;
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const applyInstallSuccessState = (btn, status, data, verified) => {
|
|
561
|
+
btn.textContent = 'Installed';
|
|
562
|
+
btn.classList.remove('is-loading');
|
|
563
|
+
btn.classList.add('is-success');
|
|
564
|
+
if (!status) return;
|
|
565
|
+
|
|
566
|
+
if (data.hookInstall?.supported && !data.hookInstall?.configured && data.hookInstall?.assetsPrepared) {
|
|
567
|
+
status.textContent = 'Configured MCP server. Dollhouse hook assets were also prepared; finish manual permission setup in Permissions & Security.';
|
|
568
|
+
} else {
|
|
569
|
+
status.textContent = verified
|
|
570
|
+
? 'Verified — config written. Restart the application to activate.'
|
|
571
|
+
: 'Restart the application to activate.';
|
|
572
|
+
}
|
|
573
|
+
status.classList.add('is-success');
|
|
574
|
+
};
|
|
575
|
+
|
|
388
576
|
/** Handle Configure Now button click */
|
|
389
577
|
const handleInstallClick = async (btn) => {
|
|
390
578
|
const client = btn.dataset.installClient;
|
|
@@ -402,17 +590,10 @@
|
|
|
402
590
|
}
|
|
403
591
|
|
|
404
592
|
try {
|
|
405
|
-
const payload = { client };
|
|
406
|
-
if (currentMethod === 'global' && pinnedVersion && pinnedVersion !== 'latest') {
|
|
407
|
-
payload.version = pinnedVersion;
|
|
408
|
-
} else if (currentChannel !== 'latest') {
|
|
409
|
-
payload.channel = currentChannel;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
593
|
const res = await DollhouseAuth.apiFetch('/api/setup/install', {
|
|
413
594
|
method: 'POST',
|
|
414
595
|
headers: { 'Content-Type': 'application/json' },
|
|
415
|
-
body: JSON.stringify(
|
|
596
|
+
body: JSON.stringify(buildInstallPayload(client)),
|
|
416
597
|
});
|
|
417
598
|
|
|
418
599
|
const data = await res.json();
|
|
@@ -426,19 +607,72 @@
|
|
|
426
607
|
await fetchDetection();
|
|
427
608
|
updateDetectionState();
|
|
428
609
|
const verified = detectedConfigs[clientToPlatformReverse[client]]?.installed;
|
|
610
|
+
applyInstallSuccessState(btn, status, data, verified);
|
|
429
611
|
|
|
430
|
-
|
|
612
|
+
// Show the completion banner after any successful install
|
|
613
|
+
showCompletionBanner(client);
|
|
614
|
+
} catch (err) {
|
|
615
|
+
btn.textContent = originalText;
|
|
616
|
+
btn.disabled = false;
|
|
617
|
+
btn.classList.remove('is-loading');
|
|
618
|
+
if (status) {
|
|
619
|
+
status.textContent = formatInstallError(err);
|
|
620
|
+
status.classList.add('is-error');
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const updatePermissionInstallButton = (btn, detected) => {
|
|
626
|
+
if (!btn || btn.classList.contains('is-success')) return;
|
|
627
|
+
|
|
628
|
+
if (detected?.hookInstalled) {
|
|
629
|
+
btn.textContent = 'Permissions enabled';
|
|
630
|
+
btn.disabled = true;
|
|
631
|
+
btn.classList.add('is-match');
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
btn.textContent = 'Configure Now';
|
|
636
|
+
btn.disabled = false;
|
|
637
|
+
btn.classList.remove('is-match');
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const handlePermissionInstallClick = async (btn) => {
|
|
641
|
+
const client = btn.dataset.permissionInstallClient;
|
|
642
|
+
if (!client) return;
|
|
643
|
+
|
|
644
|
+
const status = document.querySelector(`[data-permission-install-status="${client}"]`);
|
|
645
|
+
const originalText = btn.textContent;
|
|
646
|
+
|
|
647
|
+
btn.disabled = true;
|
|
648
|
+
btn.textContent = 'Configuring...';
|
|
649
|
+
btn.classList.add('is-loading');
|
|
650
|
+
if (status) {
|
|
651
|
+
status.textContent = '';
|
|
652
|
+
status.className = 'setup-install-status';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
const res = await DollhouseAuth.apiFetch('/api/setup/install', {
|
|
657
|
+
method: 'POST',
|
|
658
|
+
headers: { 'Content-Type': 'application/json' },
|
|
659
|
+
body: JSON.stringify({ client }),
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const data = await res.json();
|
|
663
|
+
if (!data.success) throw new Error(data.error || 'Installation failed');
|
|
664
|
+
|
|
665
|
+
await fetchDetection();
|
|
666
|
+
updateDetectionState();
|
|
667
|
+
|
|
668
|
+
btn.textContent = 'Permissions enabled';
|
|
431
669
|
btn.classList.remove('is-loading');
|
|
432
670
|
btn.classList.add('is-success');
|
|
671
|
+
|
|
433
672
|
if (status) {
|
|
434
|
-
status.textContent =
|
|
435
|
-
? 'Verified — config written. Restart the application to activate.'
|
|
436
|
-
: 'Restart the application to activate.';
|
|
673
|
+
status.textContent = data.hookInstall?.message || 'Permissions are enabled. Restart the client if it is already running.';
|
|
437
674
|
status.classList.add('is-success');
|
|
438
675
|
}
|
|
439
|
-
|
|
440
|
-
// Show the completion banner after any successful install
|
|
441
|
-
showCompletionBanner(client);
|
|
442
676
|
} catch (err) {
|
|
443
677
|
btn.textContent = originalText;
|
|
444
678
|
btn.disabled = false;
|
|
@@ -551,6 +785,19 @@
|
|
|
551
785
|
});
|
|
552
786
|
};
|
|
553
787
|
|
|
788
|
+
const initPermissionInstallButtons = () => {
|
|
789
|
+
document.querySelectorAll('.setup-permission-install-btn').forEach((btn) => {
|
|
790
|
+
btn.addEventListener('click', () => handlePermissionInstallClick(btn));
|
|
791
|
+
const client = btn.dataset.permissionInstallClient;
|
|
792
|
+
const status = document.querySelector(`[data-permission-install-status="${client}"]`);
|
|
793
|
+
if (status) {
|
|
794
|
+
const statusId = `permission-install-status-${client}`;
|
|
795
|
+
status.id = statusId;
|
|
796
|
+
btn.setAttribute('aria-describedby', statusId);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
};
|
|
800
|
+
|
|
554
801
|
// ── Open config file buttons ───────────────────────────────────────────
|
|
555
802
|
|
|
556
803
|
/** Handle Open config file button click */
|
|
@@ -712,19 +959,205 @@
|
|
|
712
959
|
* Called on init and whenever the method toggle changes.
|
|
713
960
|
*/
|
|
714
961
|
const updateDetectionState = () => {
|
|
715
|
-
|
|
962
|
+
const platformIds = new Set(['claude-desktop', ...PLATFORMS.map((platform) => platform.id)]);
|
|
963
|
+
for (const platformId of platformIds) {
|
|
716
964
|
updatePlatformDetectionState(platformId);
|
|
717
965
|
}
|
|
718
966
|
};
|
|
719
967
|
|
|
968
|
+
const PERMISSION_PLATFORM_LABELS = {
|
|
969
|
+
'claude-desktop': 'Claude Desktop',
|
|
970
|
+
'claude-code': 'Claude Code',
|
|
971
|
+
cursor: 'Cursor',
|
|
972
|
+
vscode: 'VS Code',
|
|
973
|
+
codex: 'Codex',
|
|
974
|
+
gemini: 'Gemini CLI',
|
|
975
|
+
windsurf: 'Windsurf',
|
|
976
|
+
cline: 'Cline',
|
|
977
|
+
lmstudio: 'LM Studio',
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const VERIFIED_PERMISSION_PLATFORMS = {
|
|
981
|
+
'claude-code': {
|
|
982
|
+
label: 'Claude Code',
|
|
983
|
+
statusTag: 'claude code',
|
|
984
|
+
configPath: '<code>~/.claude/settings.json</code>',
|
|
985
|
+
scriptPath: HOOK_BASE_SCRIPT_PATH,
|
|
986
|
+
settingsBlock: CLAUDE_CODE_HOOK_SETTINGS,
|
|
987
|
+
},
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
const PARTIAL_PERMISSION_PLATFORMS = {
|
|
991
|
+
gemini: {
|
|
992
|
+
label: 'Gemini CLI',
|
|
993
|
+
statusTag: 'allow / deny',
|
|
994
|
+
configPath: '<code>~/.gemini/settings.json</code> or <code>.gemini/settings.json</code> in your project',
|
|
995
|
+
scriptPath: `${HOOKS_DIR}/pretooluse-gemini.sh`,
|
|
996
|
+
settingsBlock: GEMINI_HOOK_SETTINGS,
|
|
997
|
+
limitation: 'Gemini CLI exposes native BeforeTool hooks, but it does not support an ask/confirm response path. Confirmation-style policies currently degrade to deny.',
|
|
998
|
+
},
|
|
999
|
+
cursor: {
|
|
1000
|
+
label: 'Cursor',
|
|
1001
|
+
statusTag: 'native hooks',
|
|
1002
|
+
configPath: '<code>.cursor/hooks.json</code> in your project, or <code>~/.cursor/hooks.json</code> for all projects',
|
|
1003
|
+
scriptPath: `${HOOKS_DIR}/pretooluse-cursor.sh`,
|
|
1004
|
+
settingsBlock: CURSOR_HOOK_SETTINGS,
|
|
1005
|
+
limitation: 'Cursor exposes native hooks, but its permission handling still needs broader runtime verification across allow and ask decisions.',
|
|
1006
|
+
},
|
|
1007
|
+
vscode: {
|
|
1008
|
+
label: 'VS Code',
|
|
1009
|
+
statusTag: 'native hooks',
|
|
1010
|
+
configPath: '<code>~/.copilot/hooks/dollhouse-permissions.json</code> and VS Code user settings',
|
|
1011
|
+
scriptPath: `${HOOKS_DIR}/pretooluse-vscode.sh`,
|
|
1012
|
+
settingsBlock: VSCODE_HOOK_SETTINGS,
|
|
1013
|
+
featureBlock: VSCODE_HOOK_LOCATIONS_SETTINGS,
|
|
1014
|
+
featureHeading: '2. Enable <code>~/.copilot/hooks</code> in VS Code user settings',
|
|
1015
|
+
featureCopyLabel: 'Copy VS Code hookFilesLocations settings',
|
|
1016
|
+
limitation: 'VS Code exposes native PreToolUse hooks, but it ignores matcher values and uses tool names that differ from Claude Code. This adapter normalizes the common built-in tools we know about.',
|
|
1017
|
+
},
|
|
1018
|
+
windsurf: {
|
|
1019
|
+
label: 'Windsurf',
|
|
1020
|
+
statusTag: 'allow / deny',
|
|
1021
|
+
configPath: '<code>~/.codeium/windsurf/hooks.json</code> or <code>.windsurf/hooks.json</code> in your project',
|
|
1022
|
+
scriptPath: `${HOOKS_DIR}/pretooluse-windsurf.sh`,
|
|
1023
|
+
settingsBlock: WINDSURF_HOOK_SETTINGS,
|
|
1024
|
+
limitation: 'Windsurf exposes native pre-run and pre-MCP hooks, but they are binary allow-or-block hooks. Confirmation-style policies currently degrade to block.',
|
|
1025
|
+
},
|
|
1026
|
+
codex: {
|
|
1027
|
+
label: 'Codex',
|
|
1028
|
+
statusTag: 'bash only',
|
|
1029
|
+
configPath: '<code>~/.codex/hooks.json</code> and <code>~/.codex/config.toml</code>',
|
|
1030
|
+
scriptPath: `${HOOKS_DIR}/pretooluse-codex.sh`,
|
|
1031
|
+
settingsBlock: CODEX_HOOK_SETTINGS,
|
|
1032
|
+
featureBlock: CODEX_HOOK_FEATURES_TOML,
|
|
1033
|
+
limitation: 'Codex currently only supports native PreToolUse hooks for Bash, so this turns on Bash permission guardrails only.',
|
|
1034
|
+
},
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
const getVerifiedPermissionStatusCopy = (verified, detected) => {
|
|
1038
|
+
if (detected?.hookInstalled) {
|
|
1039
|
+
return {
|
|
1040
|
+
tone: 'info',
|
|
1041
|
+
titleText: `${verified.label} permission enforcement is enabled.`,
|
|
1042
|
+
messageText: 'No further changes are needed here unless you want to reinstall the hook settings.',
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (detected?.installed) {
|
|
1047
|
+
return {
|
|
1048
|
+
tone: 'warning',
|
|
1049
|
+
titleText: `${verified.label} is connected for this client.`,
|
|
1050
|
+
messageText: `DollhouseMCP is configured as an MCP server. Use Configure Now below to also install the ${verified.label} permission hook.`,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
return {
|
|
1055
|
+
tone: 'info',
|
|
1056
|
+
titleText: `${verified.label} permissions are not configured yet.`,
|
|
1057
|
+
messageText: `First connect DollhouseMCP using Auto-updating or Pinned version, then use Configure Now below to install the ${verified.label} permission hook.`,
|
|
1058
|
+
};
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
const getPartialPermissionStatusCopy = (partial, detected) => {
|
|
1062
|
+
const activationLabel = partial.label === 'Codex' ? 'Bash guardrails' : 'permission hooks';
|
|
1063
|
+
if (detected?.hookInstalled) {
|
|
1064
|
+
return {
|
|
1065
|
+
tone: 'info',
|
|
1066
|
+
titleText: `${partial.label} ${activationLabel} are enabled.`,
|
|
1067
|
+
messageText: partial.limitation,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (detected?.installed) {
|
|
1072
|
+
return {
|
|
1073
|
+
tone: 'warning',
|
|
1074
|
+
titleText: `${partial.label} is connected for this client.`,
|
|
1075
|
+
messageText: `DollhouseMCP is configured as an MCP server. Use Configure Now below to turn on ${partial.label}'s native ${activationLabel}.`,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return {
|
|
1080
|
+
tone: 'info',
|
|
1081
|
+
titleText: `${partial.label} ${activationLabel} are not configured yet.`,
|
|
1082
|
+
messageText: `First connect DollhouseMCP using Auto-updating or Pinned version, then use Configure Now below to install ${partial.label}'s native ${activationLabel}.`,
|
|
1083
|
+
};
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
const getManualPermissionStatusCopy = (detected) => {
|
|
1087
|
+
if (detected?.hookAssetsPrepared) {
|
|
1088
|
+
return {
|
|
1089
|
+
tone: 'info',
|
|
1090
|
+
titleText: 'Hook bridge files are already prepared for this client.',
|
|
1091
|
+
messageText: 'Finish the client-specific hook registration below to turn on permission enforcement.',
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
if (detected?.installed) {
|
|
1095
|
+
return {
|
|
1096
|
+
tone: 'warning',
|
|
1097
|
+
titleText: 'DollhouseMCP is connected for this client.',
|
|
1098
|
+
messageText: 'DollhouseMCP is configured here, but permission enforcement is separate. Use the manual hook steps below to turn it on for this client.',
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
tone: 'info',
|
|
1104
|
+
titleText: 'Manual permissions setup is available for this client.',
|
|
1105
|
+
messageText: 'Use the steps below if you want to turn on permission enforcement for this client manually.',
|
|
1106
|
+
};
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
const getUnsupportedPermissionStatusCopy = (platformLabel, detected) => ({
|
|
1110
|
+
tone: detected?.installed ? 'warning' : 'neutral',
|
|
1111
|
+
titleText: `Permissions & security tools are unavailable for ${platformLabel} right now.`,
|
|
1112
|
+
messageText: detected?.installed
|
|
1113
|
+
? 'DollhouseMCP is connected for this client, but this release does not include a supported permissions setup flow here yet.'
|
|
1114
|
+
: 'This release does not include a supported permissions setup flow for this client yet.',
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
const getPermissionStatusCopy = (platformId, detected) => {
|
|
1118
|
+
const verified = VERIFIED_PERMISSION_PLATFORMS[platformId];
|
|
1119
|
+
if (verified) {
|
|
1120
|
+
return getVerifiedPermissionStatusCopy(verified, detected);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const partial = PARTIAL_PERMISSION_PLATFORMS[platformId];
|
|
1124
|
+
if (partial) {
|
|
1125
|
+
return getPartialPermissionStatusCopy(partial, detected);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const support = PLATFORMS.find((platform) => platform.id === platformId)?.hookSupport || 'unsupported';
|
|
1129
|
+
if (support === 'manual') {
|
|
1130
|
+
return getManualPermissionStatusCopy(detected);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const platformLabel = PERMISSION_PLATFORM_LABELS[platformId] || 'this client';
|
|
1134
|
+
return getUnsupportedPermissionStatusCopy(platformLabel, detected);
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const updatePermissionStatus = (panel, platformId, detected) => {
|
|
1138
|
+
const status = panel?.querySelector('.setup-permission-status');
|
|
1139
|
+
if (!status) return;
|
|
1140
|
+
|
|
1141
|
+
const title = status.querySelector('.setup-permission-status-title');
|
|
1142
|
+
const message = status.querySelector('.setup-permission-status-msg');
|
|
1143
|
+
const { tone, titleText, messageText } = getPermissionStatusCopy(platformId, detected);
|
|
1144
|
+
|
|
1145
|
+
status.dataset.state = tone;
|
|
1146
|
+
if (title) title.textContent = titleText;
|
|
1147
|
+
if (message) message.textContent = messageText;
|
|
1148
|
+
};
|
|
1149
|
+
|
|
720
1150
|
/** Update notice, badge, button, AND current config display for a single platform */
|
|
721
1151
|
const updatePlatformDetectionState = (platformId) => {
|
|
722
1152
|
const detected = detectedConfigs[platformId];
|
|
1153
|
+
const panel = document.getElementById('setup-panel-' + platformId);
|
|
1154
|
+
const tabBtn = document.getElementById('setup-tab-' + platformId);
|
|
1155
|
+
updatePermissionStatus(panel, platformId, detected);
|
|
1156
|
+
updatePermissionInstallButton(panel?.querySelector('.setup-permission-install-btn'), detected);
|
|
1157
|
+
|
|
723
1158
|
if (!detected?.installed) return;
|
|
724
1159
|
|
|
725
1160
|
const matches = configsMatch(platformId, currentMethod);
|
|
726
|
-
const panel = document.getElementById('setup-panel-' + platformId);
|
|
727
|
-
const tabBtn = document.getElementById('setup-tab-' + platformId);
|
|
728
1161
|
|
|
729
1162
|
updateDetectionNotice(panel?.querySelector('.setup-installed-notice'), matches);
|
|
730
1163
|
updateDetectionBadge(tabBtn?.querySelector('.setup-tab-badge'), matches);
|
|
@@ -830,6 +1263,8 @@
|
|
|
830
1263
|
.replaceAll('>', '>')
|
|
831
1264
|
.replaceAll('"', '"');
|
|
832
1265
|
|
|
1266
|
+
const escapeAttr = (str) => escapeHtml(str).replaceAll("'", ''');
|
|
1267
|
+
|
|
833
1268
|
// ── Generate platform panels from registry ─────────────────────────────
|
|
834
1269
|
|
|
835
1270
|
/** Build an Open config file button string, or empty if no openClient */
|
|
@@ -840,13 +1275,13 @@
|
|
|
840
1275
|
const renderInstallSection = (p) => {
|
|
841
1276
|
let html = '';
|
|
842
1277
|
if (p.installClient) {
|
|
843
|
-
html += '<div class="setup-method setup-method-primary">';
|
|
1278
|
+
html += '<div class="setup-method setup-method-primary" data-setup-modes="npx global">';
|
|
844
1279
|
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
1280
|
html += `<span class="setup-install-status" data-install-status="${p.installClient}"></span></div>`;
|
|
846
1281
|
}
|
|
847
1282
|
if (p.cli) {
|
|
848
1283
|
const cmd = `${p.cli} mcp add dollhousemcp -- npx -y ${PKG}@latest`;
|
|
849
|
-
if (!p.installClient) html += '<div class="setup-method setup-method-primary">';
|
|
1284
|
+
if (!p.installClient) html += '<div class="setup-method setup-method-primary" data-setup-modes="npx global">';
|
|
850
1285
|
html += '<h3>Or run in your terminal</h3><p>Run this in your terminal:</p>';
|
|
851
1286
|
html += `<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text="${cmd}" aria-label="Copy command">Copy</button>`;
|
|
852
1287
|
html += `<pre><code>${cmd}</code></pre></div>`;
|
|
@@ -864,9 +1299,9 @@
|
|
|
864
1299
|
let html = '';
|
|
865
1300
|
|
|
866
1301
|
if (hasPrimaryBlock) {
|
|
867
|
-
html += `</div><div class="setup-method"><h3>Or add config manually${openBtnHtml(p.openClient)}</h3>`;
|
|
1302
|
+
html += `</div><div class="setup-method" data-setup-modes="npx global"><h3>Or add config manually${openBtnHtml(p.openClient)}</h3>`;
|
|
868
1303
|
} else {
|
|
869
|
-
html += `<div class="setup-method setup-method-primary"><h3>Config${openBtnHtml(p.openClient)}</h3>`;
|
|
1304
|
+
html += `<div class="setup-method setup-method-primary" data-setup-modes="npx global"><h3>Config${openBtnHtml(p.openClient)}</h3>`;
|
|
870
1305
|
}
|
|
871
1306
|
html += `<p>Add to ${p.configPath}:</p>`;
|
|
872
1307
|
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 +1316,172 @@
|
|
|
881
1316
|
if (!p.tomlPath) return '';
|
|
882
1317
|
const tomlConfig = configs[p.id]?.npxToml;
|
|
883
1318
|
const tomlCode = tomlConfig?.code || '';
|
|
884
|
-
let html = `<div class="setup-method"><h3>Or add to config${openBtnHtml(p.openClient)}</h3>`;
|
|
1319
|
+
let html = `<div class="setup-method" data-setup-modes="npx global"><h3>Or add to config${openBtnHtml(p.openClient)}</h3>`;
|
|
885
1320
|
html += `<p>Add to ${p.tomlPath}:</p>`;
|
|
886
1321
|
html += `<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${tomlCode}' aria-label="Copy config">Copy</button>`;
|
|
887
1322
|
html += `<pre><code>${tomlCode}</code></pre></div></div>`;
|
|
888
1323
|
return html;
|
|
889
1324
|
};
|
|
890
1325
|
|
|
1326
|
+
const buildPartialAutoHint = (p, partial) => {
|
|
1327
|
+
const base = partial.limitation;
|
|
1328
|
+
if (p.id === 'codex') {
|
|
1329
|
+
return `${base} This automatic path writes the shared hook bridge, updates <code>~/.codex/hooks.json</code>, and enables <code>features.codex_hooks</code> in <code>~/.codex/config.toml</code>.`;
|
|
1330
|
+
}
|
|
1331
|
+
if (p.id === 'vscode') {
|
|
1332
|
+
return `${base} This automatic path writes the shared hook bridge, creates <code>~/.copilot/hooks/dollhouse-permissions.json</code>, and enables <code>~/.copilot/hooks</code> in VS Code's <code>chat.hookFilesLocations</code> setting.`;
|
|
1333
|
+
}
|
|
1334
|
+
return `${base} This automatic path writes the shared hook bridge and updates ${partial.configPath}.`;
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
const buildPartialFeatureHeading = (p, partial) => {
|
|
1338
|
+
if (partial.featureHeading) return partial.featureHeading;
|
|
1339
|
+
if (p.id === 'codex') return '2. Enable Codex hooks in <code>~/.codex/config.toml</code>';
|
|
1340
|
+
return '2. Add the additional client settings';
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
const renderVerifiedPermissionSection = (p, verified) => {
|
|
1344
|
+
const permissionInstallClient = p.installClient || p.id;
|
|
1345
|
+
return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
|
|
1346
|
+
<h3>Permissions & Security <span class="setup-support-badge setup-support-badge--verified">${verified.statusTag}</span></h3>
|
|
1347
|
+
<div class="setup-permission-status" data-state="info">
|
|
1348
|
+
<strong class="setup-permission-status-title"></strong>
|
|
1349
|
+
<p class="setup-permission-status-msg"></p>
|
|
1350
|
+
</div>
|
|
1351
|
+
<div class="setup-install-row">
|
|
1352
|
+
<button class="setup-btn setup-btn-primary setup-permission-install-btn" type="button" data-permission-install-client="${permissionInstallClient}">Configure Now</button>
|
|
1353
|
+
<span class="setup-install-status" data-permission-install-status="${permissionInstallClient}"></span>
|
|
1354
|
+
</div>
|
|
1355
|
+
<p class="setup-hint">This writes the shared hook bridge assets and updates ${verified.configPath} automatically.</p>
|
|
1356
|
+
</div>
|
|
1357
|
+
<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
|
|
1358
|
+
<details class="setup-manual-fallback">
|
|
1359
|
+
<summary>Manual fallback</summary>
|
|
1360
|
+
<div class="setup-manual-fallback-body">
|
|
1361
|
+
<h4>1. Save the shared hook bridge once</h4>
|
|
1362
|
+
<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>
|
|
1363
|
+
<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>
|
|
1364
|
+
<pre><code>${escapeHtml(HOOK_BASE_SCRIPT)}</code></pre>
|
|
1365
|
+
</div>
|
|
1366
|
+
<h4>2. Add the ${verified.label} hook settings</h4>
|
|
1367
|
+
<p>Add this block to ${verified.configPath} so ${verified.label} can call the hook bridge before tool use.</p>
|
|
1368
|
+
<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(verified.settingsBlock)}' aria-label="Copy ${verified.label} hook settings">Copy</button>
|
|
1369
|
+
<pre><code>${escapeHtml(verified.settingsBlock)}</code></pre>
|
|
1370
|
+
</div>
|
|
1371
|
+
<p class="setup-hint">Command hook target: <code>${verified.scriptPath}</code></p>
|
|
1372
|
+
</div>
|
|
1373
|
+
</details>
|
|
1374
|
+
</div>`;
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
const renderPartialPermissionSection = (p, partial) => {
|
|
1378
|
+
const permissionInstallClient = p.installClient || p.id;
|
|
1379
|
+
const autoHint = buildPartialAutoHint(p, partial);
|
|
1380
|
+
const featureHeading = buildPartialFeatureHeading(p, partial);
|
|
1381
|
+
const featureSection = partial.featureBlock
|
|
1382
|
+
? `<h4>${featureHeading}</h4>
|
|
1383
|
+
<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(partial.featureBlock)}' aria-label="${escapeAttr(partial.featureCopyLabel || `Copy ${partial.label} settings`)}">Copy</button>
|
|
1384
|
+
<pre><code>${escapeHtml(partial.featureBlock)}</code></pre>
|
|
1385
|
+
</div>`
|
|
1386
|
+
: '';
|
|
1387
|
+
const stepNumber = partial.featureBlock ? '3' : '2';
|
|
1388
|
+
|
|
1389
|
+
return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
|
|
1390
|
+
<h3>Permissions & Security <span class="setup-support-badge setup-support-badge--manual">${partial.statusTag}</span></h3>
|
|
1391
|
+
<div class="setup-permission-status" data-state="info">
|
|
1392
|
+
<strong class="setup-permission-status-title"></strong>
|
|
1393
|
+
<p class="setup-permission-status-msg"></p>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="setup-install-row">
|
|
1396
|
+
<button class="setup-btn setup-btn-primary setup-permission-install-btn" type="button" data-permission-install-client="${permissionInstallClient}">Configure Now</button>
|
|
1397
|
+
<span class="setup-install-status" data-permission-install-status="${permissionInstallClient}"></span>
|
|
1398
|
+
</div>
|
|
1399
|
+
<p class="setup-hint">${autoHint}</p>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
|
|
1402
|
+
<details class="setup-manual-fallback">
|
|
1403
|
+
<summary>Manual fallback</summary>
|
|
1404
|
+
<div class="setup-manual-fallback-body">
|
|
1405
|
+
<h4>1. Save the shared hook bridge once</h4>
|
|
1406
|
+
<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>
|
|
1407
|
+
<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>
|
|
1408
|
+
<pre><code>${escapeHtml(HOOK_BASE_SCRIPT)}</code></pre>
|
|
1409
|
+
</div>
|
|
1410
|
+
${featureSection}
|
|
1411
|
+
<h4>${stepNumber}. Add the ${partial.label} hook settings in ${partial.configPath}</h4>
|
|
1412
|
+
<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(partial.settingsBlock)}' aria-label="Copy ${partial.label} hook settings">Copy</button>
|
|
1413
|
+
<pre><code>${escapeHtml(partial.settingsBlock)}</code></pre>
|
|
1414
|
+
</div>
|
|
1415
|
+
<p class="setup-hint">Command hook target: <code>${partial.scriptPath}</code></p>
|
|
1416
|
+
</div>
|
|
1417
|
+
</details>
|
|
1418
|
+
</div>`;
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
const renderPermissionSection = (p) => {
|
|
1422
|
+
const hookSupport = p.hookSupport || 'unsupported';
|
|
1423
|
+
const configPath = p.hookConfigPath || p.configPath || p.tomlPath || 'this client’s user configuration';
|
|
1424
|
+
|
|
1425
|
+
if (hookSupport === 'verified' && VERIFIED_PERMISSION_PLATFORMS[p.id]) {
|
|
1426
|
+
return renderVerifiedPermissionSection(p, VERIFIED_PERMISSION_PLATFORMS[p.id]);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
if (hookSupport === 'partial' && PARTIAL_PERMISSION_PLATFORMS[p.id]) {
|
|
1430
|
+
return renderPartialPermissionSection(p, PARTIAL_PERMISSION_PLATFORMS[p.id]);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (hookSupport === 'manual') {
|
|
1434
|
+
const platformName = p.id === 'gemini' ? 'gemini' : p.id;
|
|
1435
|
+
const wrapperFilename = `pretooluse-${platformName}.sh`;
|
|
1436
|
+
const wrapperScript = buildHookWrapperScript(platformName);
|
|
1437
|
+
return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
|
|
1438
|
+
<h3>Permissions & Security <span class="setup-support-badge setup-support-badge--manual">manual setup</span></h3>
|
|
1439
|
+
<div class="setup-permission-status" data-state="info">
|
|
1440
|
+
<strong class="setup-permission-status-title"></strong>
|
|
1441
|
+
<p class="setup-permission-status-msg"></p>
|
|
1442
|
+
</div>
|
|
1443
|
+
<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>
|
|
1444
|
+
<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>
|
|
1445
|
+
<pre><code>${escapeHtml(p.hookCommand)}</code></pre>
|
|
1446
|
+
</div>
|
|
1447
|
+
<p>Save this wrapper in <code>${HOOKS_DIR}</code> alongside the shared Dollhouse hook bridge:</p>
|
|
1448
|
+
<div class="setup-code-block"><button class="setup-copy-btn" type="button" data-copy-text='${escapeAttr(wrapperScript)}' aria-label="Copy ${wrapperFilename}">Copy</button>
|
|
1449
|
+
<pre><code>${escapeHtml(wrapperScript)}</code></pre>
|
|
1450
|
+
</div>
|
|
1451
|
+
<p class="setup-hint">Known config location for this client: ${configPath}</p>
|
|
1452
|
+
</div>`;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return `<div class="setup-method setup-security-mode" data-setup-modes="permissions" hidden>
|
|
1456
|
+
<h3>Permissions & Security <span class="setup-support-badge setup-support-badge--unsupported">coming soon</span></h3>
|
|
1457
|
+
<div class="setup-permission-status" data-state="neutral">
|
|
1458
|
+
<strong class="setup-permission-status-title"></strong>
|
|
1459
|
+
<p class="setup-permission-status-msg"></p>
|
|
1460
|
+
</div>
|
|
1461
|
+
</div>`;
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
const renderPermissionsIntro = () => {
|
|
1465
|
+
const intro = document.getElementById('setup-permissions-intro');
|
|
1466
|
+
if (!intro) return;
|
|
1467
|
+
|
|
1468
|
+
intro.innerHTML = `<div class="setup-permissions-note">
|
|
1469
|
+
<strong>Permissions & Security</strong>
|
|
1470
|
+
<p>Use this mode to turn on permission enforcement for supported clients. Claude Code is fully guided in this release, and Gemini CLI, Cursor, VS Code, Windsurf, plus Codex have native partial support. Where we have workable manual steps for other clients, they are shown here. Otherwise, the client will be marked as coming soon.</p>
|
|
1471
|
+
</div>`;
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
const injectStaticPermissionsSections = () => {
|
|
1475
|
+
const claudeDesktopPanel = document.getElementById('setup-panel-claude-desktop');
|
|
1476
|
+
const claudeCodePanel = document.getElementById('setup-panel-claude-code');
|
|
1477
|
+
const claudeCodeConfig = PLATFORMS.find((p) => p.id === 'claude-code');
|
|
1478
|
+
|
|
1479
|
+
claudeDesktopPanel?.insertAdjacentHTML('beforeend', renderPermissionSection({ id: 'claude-desktop' }));
|
|
1480
|
+
if (claudeCodePanel && claudeCodeConfig) {
|
|
1481
|
+
claudeCodePanel.insertAdjacentHTML('beforeend', renderPermissionSection(claudeCodeConfig));
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
|
|
891
1485
|
const renderGeneratedPanels = () => {
|
|
892
1486
|
const container = document.getElementById('setup-generated-panels');
|
|
893
1487
|
if (!container) return;
|
|
@@ -906,7 +1500,8 @@
|
|
|
906
1500
|
section.innerHTML =
|
|
907
1501
|
renderInstallSection(p) +
|
|
908
1502
|
renderJsonSection(p, hasPrimaryBlock) +
|
|
909
|
-
renderTomlSection(p)
|
|
1503
|
+
renderTomlSection(p) +
|
|
1504
|
+
renderPermissionSection(p);
|
|
910
1505
|
|
|
911
1506
|
container.appendChild(section);
|
|
912
1507
|
}
|
|
@@ -1232,9 +1827,16 @@
|
|
|
1232
1827
|
if (license.useCase) rows.push(['Use case', license.useCase]);
|
|
1233
1828
|
}
|
|
1234
1829
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
.
|
|
1830
|
+
const rowNodes = rows.map(([label, value]) => {
|
|
1831
|
+
const tr = document.createElement('tr');
|
|
1832
|
+
const labelCell = document.createElement('td');
|
|
1833
|
+
const valueCell = document.createElement('td');
|
|
1834
|
+
labelCell.textContent = label;
|
|
1835
|
+
valueCell.textContent = value;
|
|
1836
|
+
tr.append(labelCell, valueCell);
|
|
1837
|
+
return tr;
|
|
1838
|
+
});
|
|
1839
|
+
licenseInfoTable.replaceChildren(...rowNodes);
|
|
1238
1840
|
licenseDetailsPanel.hidden = false;
|
|
1239
1841
|
}
|
|
1240
1842
|
|
|
@@ -1350,15 +1952,19 @@
|
|
|
1350
1952
|
// ── Init ──────────────────────────────────────────────────────────────
|
|
1351
1953
|
|
|
1352
1954
|
const os = detectOS();
|
|
1955
|
+
renderPermissionsIntro();
|
|
1353
1956
|
renderGeneratedPanels();
|
|
1957
|
+
injectStaticPermissionsSections();
|
|
1354
1958
|
highlightOSPaths(os);
|
|
1355
1959
|
initMethodToggle();
|
|
1356
1960
|
initChannelSelector();
|
|
1357
1961
|
initPlatformTabs();
|
|
1358
1962
|
initCopyButtons();
|
|
1359
1963
|
initInstallButtons();
|
|
1964
|
+
initPermissionInstallButtons();
|
|
1360
1965
|
initOpenButtons();
|
|
1361
1966
|
fetchVersion();
|
|
1362
1967
|
fetchDetection();
|
|
1363
1968
|
initLicense();
|
|
1969
|
+
updateSetupModeSections();
|
|
1364
1970
|
})();
|