ultimate-jekyll-manager 1.9.0 → 1.9.2
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 +18 -0
- package/PROGRESS.md +24 -0
- package/dist/assets/js/libs/form-manager.js +15 -4
- package/dist/assets/js/pages/account/sections/api-keys.js +143 -38
- package/dist/assets/js/pages/test/libraries/form-manager/index.js +53 -0
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +210 -3
- package/dist/defaults/dist/pages/test/libraries/form-manager.html +72 -0
- package/dist/test/suites/page/form-manager-data.test.js +418 -0
- package/dist/test/suites/page/form-manager-disabled.test.js +170 -0
- package/dist/test/suites/page/form-manager-validation.test.js +374 -0
- package/docs/javascript-libraries.md +13 -3
- package/logs/test.log +72 -39
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
14
14
|
- `Fixed` for any bug fixes.
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
|
+
---
|
|
18
|
+
## [1.9.2] - 2026-06-17
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Account page: API & MCP section.** Renamed "API keys" to "API & MCP". New MCP integration card with server URL (copy-to-clipboard) and tabbed setup instructions for 9 providers: Claude, Cursor, VS Code, Codex, Gemini CLI, OpenCode, Windsurf, Zed, and Other. MCP URL built dynamically from `webManager.getApiUrl()`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
## [1.9.1] - 2026-06-17
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **FormManager: snapshot-and-restore disabled-state model (replaces `data-fm-keep-disabled`).** `_init()` now snapshots every element that has `disabled` in HTML markup (excluding submit buttons — those are loading guards FM takes over). Snapshotted elements stay disabled through every state transition automatically — no data attributes needed. Recommended form pattern: `<form data-form-state="initializing" onsubmit="return false">` with CSS `form[data-form-state]:not([data-form-state="ready"]) { pointer-events: none; }`. Documented in `docs/javascript-libraries.md`.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Comprehensive FormManager test suite (30 page-layer tests).** Disabled-state snapshot (5 tests), getData/setData/input groups (12 tests), validation/honeypot/file-accept (13 tests). Previously FormManager had zero automated tests.
|
|
33
|
+
- **Visual Test 7 on the FM test page** (`/test/libraries/form-manager`) — permanently disabled elements, submit cycle demo, rapid-cycle button for manual verification.
|
|
34
|
+
|
|
17
35
|
---
|
|
18
36
|
## [1.9.0] - 2026-06-17
|
|
19
37
|
|
package/PROGRESS.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Project Progress Tracker
|
|
2
|
+
> Agents and maintainers should update this file regularly to reflect the current state of the project.
|
|
3
|
+
|
|
4
|
+
## Current Focus
|
|
5
|
+
* **Goal:** FormManager disabled-state refactor (snapshot-and-restore)
|
|
6
|
+
* **Current Phase:** Phase 1 — implementation + tests complete, docs pending
|
|
7
|
+
* **Priority:** Medium
|
|
8
|
+
* **Last Updated:** 2026-06-17 6:05 PM PDT
|
|
9
|
+
* **Notes:** FM disabled-state refactor done + comprehensive FM test suite added (110 tests total, up from 80). Covers getData/setData/input groups/validation/honeypot/file-accept/disabled snapshot. Docs (javascript-libraries.md, CHANGELOG) still need updating before shipping.
|
|
10
|
+
|
|
11
|
+
## Active Task List
|
|
12
|
+
* [ ] Phase 1: FormManager disabled-state snapshot-and-restore
|
|
13
|
+
* [x] Task 1.1: Refactor `_setDisabled` to use snapshot instead of `data-fm-keep-disabled`
|
|
14
|
+
* [x] Task 1.2: Add `_permanentlyDisabled` Set, populated in `_init()` before first disable
|
|
15
|
+
* [x] Task 1.3: Write page-layer tests (5 tests: snapshot capture, full disable, selective re-enable, cycle durability, onsubmit HTML guard)
|
|
16
|
+
* [x] Task 1.4: All 85 tests passing
|
|
17
|
+
* [x] Task 1.5: Add visual Test 7 to FM test page (form-manager.html + JS) with permanently disabled fields + rapid-cycle demo
|
|
18
|
+
* [x] Task 1.6: Write comprehensive FM page-layer tests — getData/setData (12 tests), validation/honeypot/file-accept (13 tests). 110 total.
|
|
19
|
+
* [ ] Task 1.7: Update `docs/javascript-libraries.md` — replace `data-fm-keep-disabled` docs with new snapshot pattern + `onsubmit="return false"` + `data-form-state="initializing"` CSS guard
|
|
20
|
+
* [ ] Task 1.8: Update CHANGELOG with the change
|
|
21
|
+
* [ ] Task 1.9: Ship (commit, push, publish)
|
|
22
|
+
|
|
23
|
+
## Completed Task List
|
|
24
|
+
* [x] Phase 0: v1.9.0 release — MCP OAuth flow + CDP debugging docs + dev-URL updates
|
|
@@ -59,6 +59,7 @@ export class FormManager {
|
|
|
59
59
|
// State
|
|
60
60
|
this.state = 'initializing';
|
|
61
61
|
this._isDirty = false;
|
|
62
|
+
this._permanentlyDisabled = new Set();
|
|
62
63
|
|
|
63
64
|
// Event listeners
|
|
64
65
|
this._listeners = {
|
|
@@ -92,6 +93,17 @@ export class FormManager {
|
|
|
92
93
|
* Initialize the form manager
|
|
93
94
|
*/
|
|
94
95
|
_init() {
|
|
96
|
+
// Snapshot elements that are disabled in HTML markup BEFORE the first
|
|
97
|
+
// blanket disable. These are business-logic disabled (e.g. "coming soon"
|
|
98
|
+
// options) and must stay disabled through every state transition.
|
|
99
|
+
// Submit buttons are excluded — disabled submit buttons in HTML are
|
|
100
|
+
// loading guards that FM takes over.
|
|
101
|
+
this.$form.querySelectorAll('button, input, select, textarea').forEach(($el) => {
|
|
102
|
+
if ($el.disabled && $el.type !== 'submit') {
|
|
103
|
+
this._permanentlyDisabled.add($el);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
95
107
|
// Disable form during initialization
|
|
96
108
|
this._setDisabled(true);
|
|
97
109
|
|
|
@@ -782,9 +794,8 @@ export class FormManager {
|
|
|
782
794
|
}
|
|
783
795
|
|
|
784
796
|
/**
|
|
785
|
-
* Enable/disable form controls.
|
|
786
|
-
* disabled
|
|
787
|
-
* managed form) — the toggle never re-enables them.
|
|
797
|
+
* Enable/disable form controls. Elements snapshotted as permanently
|
|
798
|
+
* disabled during _init() are never re-enabled.
|
|
788
799
|
*/
|
|
789
800
|
_setDisabled(disabled) {
|
|
790
801
|
/* @dev-only:start */
|
|
@@ -794,7 +805,7 @@ export class FormManager {
|
|
|
794
805
|
/* @dev-only:end */
|
|
795
806
|
|
|
796
807
|
this.$form.querySelectorAll('button, input, select, textarea').forEach(($el) => {
|
|
797
|
-
if ($el
|
|
808
|
+
if (this._permanentlyDisabled.has($el)) {
|
|
798
809
|
$el.disabled = true;
|
|
799
810
|
return;
|
|
800
811
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API
|
|
2
|
+
* API & MCP Section JavaScript
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// Libraries
|
|
@@ -7,19 +7,19 @@ import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
|
7
7
|
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
8
8
|
import webManager from 'web-manager';
|
|
9
9
|
|
|
10
|
-
// Initialize
|
|
10
|
+
// Initialize section
|
|
11
11
|
export function init() {
|
|
12
12
|
setupButtons();
|
|
13
13
|
setupResetApiKeyForm();
|
|
14
|
+
setupMcp();
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
// Load
|
|
17
|
+
// Load data
|
|
17
18
|
export function loadData(account) {
|
|
18
19
|
if (!account) {
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// Update API key display
|
|
23
23
|
updateApiKey(account.api?.privateKey);
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -28,21 +28,126 @@ function updateApiKey(apiKey) {
|
|
|
28
28
|
const $apiKeyInput = document.getElementById('api-key-input');
|
|
29
29
|
|
|
30
30
|
if ($apiKeyInput) {
|
|
31
|
-
|
|
32
|
-
$apiKeyInput.value = apiKey;
|
|
33
|
-
} else {
|
|
34
|
-
$apiKeyInput.value = 'No API key generated';
|
|
35
|
-
}
|
|
31
|
+
$apiKeyInput.value = apiKey || 'No API key generated';
|
|
36
32
|
}
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
// Setup button handlers
|
|
40
36
|
function setupButtons() {
|
|
41
|
-
// Copy API key button
|
|
42
37
|
const $copyBtn = document.getElementById('copy-api-key-btn');
|
|
43
38
|
if ($copyBtn) {
|
|
44
39
|
$copyBtn.addEventListener('click', handleCopyApiKey);
|
|
45
40
|
}
|
|
41
|
+
|
|
42
|
+
const $copyMcpBtn = document.getElementById('copy-mcp-url-btn');
|
|
43
|
+
if ($copyMcpBtn) {
|
|
44
|
+
$copyMcpBtn.addEventListener('click', () => handleCopyInput('mcp-url-input', $copyMcpBtn));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
document.querySelectorAll('[data-copy-target]').forEach(($btn) => {
|
|
48
|
+
$btn.addEventListener('click', () => {
|
|
49
|
+
const targetId = $btn.getAttribute('data-copy-target');
|
|
50
|
+
const $target = document.getElementById(targetId);
|
|
51
|
+
|
|
52
|
+
if (!$target) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const text = $target.tagName === 'PRE' ? $target.textContent : $target.value;
|
|
57
|
+
|
|
58
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
59
|
+
webManager.utilities().showNotification('Copied!', 'success');
|
|
60
|
+
}).catch(() => {
|
|
61
|
+
webManager.utilities().showNotification('Failed to copy', 'danger');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Setup MCP integration URLs
|
|
68
|
+
function setupMcp() {
|
|
69
|
+
const apiUrl = webManager.getApiUrl();
|
|
70
|
+
const mcpUrl = `${apiUrl}/mcp`;
|
|
71
|
+
const $mcpCard = document.getElementById('mcp-card');
|
|
72
|
+
const brandName = ($mcpCard?.dataset?.brandId || 'backend').toLowerCase().replace(/\s+/g, '-');
|
|
73
|
+
|
|
74
|
+
const $mcpUrlInput = document.getElementById('mcp-url-input');
|
|
75
|
+
if ($mcpUrlInput) {
|
|
76
|
+
$mcpUrlInput.value = mcpUrl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const cmds = {
|
|
80
|
+
'mcp-cmd-claude': `claude mcp add ${brandName} --transport http ${mcpUrl}`,
|
|
81
|
+
'mcp-cmd-codex': `codex mcp add ${brandName} -- npx -y mcp-remote@latest ${mcpUrl}`,
|
|
82
|
+
'mcp-cmd-gemini-auth': `/mcp auth ${brandName}`,
|
|
83
|
+
'mcp-cmd-opencode-auth': `opencode mcp auth ${brandName}`,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const snippets = {
|
|
87
|
+
'mcp-cmd-cursor': {
|
|
88
|
+
mcpServers: {
|
|
89
|
+
[brandName]: { url: mcpUrl },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
'mcp-cmd-vscode': {
|
|
93
|
+
mcp: {
|
|
94
|
+
servers: {
|
|
95
|
+
[brandName]: { url: mcpUrl },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
'mcp-cmd-gemini': {
|
|
100
|
+
mcpServers: {
|
|
101
|
+
[brandName]: { url: mcpUrl },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
'mcp-cmd-opencode': {
|
|
105
|
+
$schema: 'https://opencode.ai/config.json',
|
|
106
|
+
mcp: {
|
|
107
|
+
[brandName]: {
|
|
108
|
+
type: 'remote',
|
|
109
|
+
url: mcpUrl,
|
|
110
|
+
oauth: {},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
'mcp-cmd-windsurf': {
|
|
115
|
+
mcpServers: {
|
|
116
|
+
[brandName]: {
|
|
117
|
+
command: 'npx',
|
|
118
|
+
args: ['-y', 'mcp-remote@latest', mcpUrl],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
'mcp-cmd-zed': {
|
|
123
|
+
context_servers: {
|
|
124
|
+
[brandName]: {
|
|
125
|
+
command: 'npx',
|
|
126
|
+
args: ['-y', 'mcp-remote@latest', mcpUrl],
|
|
127
|
+
settings: {},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
for (const [id, value] of Object.entries(cmds)) {
|
|
134
|
+
const $el = document.getElementById(id);
|
|
135
|
+
if ($el) {
|
|
136
|
+
$el.value = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const [id, config] of Object.entries(snippets)) {
|
|
141
|
+
const $el = document.getElementById(id);
|
|
142
|
+
if ($el) {
|
|
143
|
+
$el.textContent = JSON.stringify(config, null, 2);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const $detailUrl = document.getElementById('mcp-detail-url');
|
|
148
|
+
if ($detailUrl) {
|
|
149
|
+
$detailUrl.textContent = mcpUrl;
|
|
150
|
+
}
|
|
46
151
|
}
|
|
47
152
|
|
|
48
153
|
// Setup reset API key form
|
|
@@ -54,18 +159,14 @@ function setupResetApiKeyForm() {
|
|
|
54
159
|
});
|
|
55
160
|
|
|
56
161
|
formManager.on('submit', async () => {
|
|
57
|
-
// 1ms wait for dialog to appear properly
|
|
58
162
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
59
163
|
|
|
60
|
-
// Show confirmation dialog
|
|
61
164
|
if (!confirm('Are you sure you want to reset your API key? This will invalidate your current key and any applications using it will stop working.')) {
|
|
62
165
|
throw new Error('API key reset cancelled.');
|
|
63
166
|
}
|
|
64
167
|
|
|
65
|
-
// Get server API URL
|
|
66
168
|
const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/api-keys`;
|
|
67
169
|
|
|
68
|
-
// Make API call to reset API key
|
|
69
170
|
const response = await authorizedFetch(serverApiURL, {
|
|
70
171
|
method: 'POST',
|
|
71
172
|
timeout: 30000,
|
|
@@ -77,10 +178,7 @@ function setupResetApiKeyForm() {
|
|
|
77
178
|
throw new Error(response.message || 'Failed to reset API key');
|
|
78
179
|
}
|
|
79
180
|
|
|
80
|
-
// Update the displayed API key
|
|
81
181
|
updateApiKey(response.privateKey);
|
|
82
|
-
|
|
83
|
-
// Show success message
|
|
84
182
|
formManager.showSuccess('API key has been reset successfully!');
|
|
85
183
|
});
|
|
86
184
|
}
|
|
@@ -90,31 +188,38 @@ async function handleCopyApiKey() {
|
|
|
90
188
|
const $apiKeyInput = document.getElementById('api-key-input');
|
|
91
189
|
const $copyBtn = document.getElementById('copy-api-key-btn');
|
|
92
190
|
|
|
93
|
-
|
|
94
|
-
|
|
191
|
+
handleCopyInput('api-key-input', $copyBtn);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Generic copy handler for input elements
|
|
195
|
+
async function handleCopyInput(inputId, $btn) {
|
|
196
|
+
const $input = document.getElementById(inputId);
|
|
197
|
+
|
|
198
|
+
if (!$input || !$input.value || $input.value === 'Loading...') {
|
|
199
|
+
webManager.utilities().showNotification('Nothing to copy', 'warning');
|
|
95
200
|
return;
|
|
96
201
|
}
|
|
97
202
|
|
|
98
203
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
204
|
+
await webManager.utilities().clipboardCopy($input);
|
|
205
|
+
|
|
206
|
+
const $text = $btn.querySelector('.button-text');
|
|
207
|
+
if ($text) {
|
|
208
|
+
const originalText = $text.textContent;
|
|
209
|
+
$text.textContent = 'Copied!';
|
|
210
|
+
$btn.classList.remove('btn-outline-adaptive');
|
|
211
|
+
$btn.classList.add('btn-success');
|
|
212
|
+
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
$text.textContent = originalText;
|
|
215
|
+
$btn.classList.remove('btn-success');
|
|
216
|
+
$btn.classList.add('btn-outline-adaptive');
|
|
217
|
+
}, 2000);
|
|
218
|
+
} else {
|
|
219
|
+
webManager.utilities().showNotification('Copied!', 'success');
|
|
220
|
+
}
|
|
116
221
|
} catch (err) {
|
|
117
|
-
console.error('Failed to copy
|
|
118
|
-
webManager.utilities().showNotification('Failed to copy
|
|
222
|
+
console.error('Failed to copy:', err);
|
|
223
|
+
webManager.utilities().showNotification('Failed to copy', 'danger');
|
|
119
224
|
}
|
|
120
225
|
}
|
|
@@ -19,6 +19,7 @@ export default () => {
|
|
|
19
19
|
initTestFormManual();
|
|
20
20
|
initTestFormGroups();
|
|
21
21
|
initTestFormFileDrop();
|
|
22
|
+
initTestFormSnapshot();
|
|
22
23
|
|
|
23
24
|
// Resolve after initialization
|
|
24
25
|
return resolve();
|
|
@@ -245,6 +246,58 @@ function initTestFormGroups() {
|
|
|
245
246
|
});
|
|
246
247
|
}
|
|
247
248
|
|
|
249
|
+
// Test 7: Disabled-State Snapshot
|
|
250
|
+
function initTestFormSnapshot() {
|
|
251
|
+
const formManager = new FormManager('#test-form-snapshot');
|
|
252
|
+
const $status = document.getElementById('snapshot-status');
|
|
253
|
+
const $cycleCount = document.getElementById('snapshot-cycle-count');
|
|
254
|
+
const $output = document.getElementById('snapshot-output');
|
|
255
|
+
const $cycleBtn = document.getElementById('snapshot-cycle');
|
|
256
|
+
|
|
257
|
+
let cycles = 0;
|
|
258
|
+
|
|
259
|
+
function logStates() {
|
|
260
|
+
const form = document.getElementById('test-form-snapshot');
|
|
261
|
+
const lines = [];
|
|
262
|
+
form.querySelectorAll('input, select, textarea, button').forEach(($el) => {
|
|
263
|
+
const label = $el.name || $el.type || $el.tagName.toLowerCase();
|
|
264
|
+
lines.push(`${label}: disabled=${$el.disabled}`);
|
|
265
|
+
});
|
|
266
|
+
$output.textContent = lines.join('\n');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
formManager.on('statechange', ({ state }) => {
|
|
270
|
+
$status.textContent = `Status: ${state}`;
|
|
271
|
+
logStates();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
formManager.on('submit', async ({ data }) => {
|
|
275
|
+
console.log('[Test 7] Submitting:', data);
|
|
276
|
+
logStates();
|
|
277
|
+
await simulateApi(2000);
|
|
278
|
+
cycles++;
|
|
279
|
+
$cycleCount.textContent = `Cycles: ${cycles}`;
|
|
280
|
+
formManager.showSuccess('Done! Permanently disabled fields should still be disabled.');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Rapid cycle button — triggers 5 fast disable/enable cycles
|
|
284
|
+
$cycleBtn.addEventListener('click', async () => {
|
|
285
|
+
$cycleBtn.disabled = true;
|
|
286
|
+
for (let i = 0; i < 5; i++) {
|
|
287
|
+
formManager._setDisabled(true);
|
|
288
|
+
logStates();
|
|
289
|
+
await simulateApi(300);
|
|
290
|
+
formManager._setDisabled(false);
|
|
291
|
+
logStates();
|
|
292
|
+
await simulateApi(300);
|
|
293
|
+
cycles++;
|
|
294
|
+
}
|
|
295
|
+
$cycleCount.textContent = `Cycles: ${cycles}`;
|
|
296
|
+
$cycleBtn.disabled = false;
|
|
297
|
+
formManager.showSuccess('5 rapid cycles complete. Check that Enterprise/Region/Notes stayed disabled.');
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
248
301
|
// Test 6: File Drop
|
|
249
302
|
function initTestFormFileDrop() {
|
|
250
303
|
const formManager = new FormManager('#test-form-file-drop');
|
|
@@ -55,7 +55,7 @@ sections:
|
|
|
55
55
|
name: "Notifications"
|
|
56
56
|
icon: "bell"
|
|
57
57
|
- id: "api-keys"
|
|
58
|
-
name: "API
|
|
58
|
+
name: "API & MCP"
|
|
59
59
|
icon: "key"
|
|
60
60
|
- id: "data-request"
|
|
61
61
|
name: "Data request"
|
|
@@ -1311,9 +1311,10 @@ badges:
|
|
|
1311
1311
|
|
|
1312
1312
|
<!-- API Keys Section -->
|
|
1313
1313
|
<section id="api-keys-section" class="account-section d-none">
|
|
1314
|
-
<h2 class="h3 mb-4"
|
|
1314
|
+
<h2 class="h3 mb-4">API & MCP</h2>
|
|
1315
1315
|
|
|
1316
|
-
|
|
1316
|
+
<!-- API Key Card -->
|
|
1317
|
+
<div class="card mb-4">
|
|
1317
1318
|
<div class="card-body">
|
|
1318
1319
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1319
1320
|
<h5 class="card-title mb-0">Your API key</h5>
|
|
@@ -1351,6 +1352,212 @@ badges:
|
|
|
1351
1352
|
</div>
|
|
1352
1353
|
</div>
|
|
1353
1354
|
</div>
|
|
1355
|
+
|
|
1356
|
+
<!-- MCP Integration Card -->
|
|
1357
|
+
<div class="card" data-brand-id="{{ site.brand.id }}" id="mcp-card">
|
|
1358
|
+
<div class="card-body">
|
|
1359
|
+
<h5 class="card-title mb-2">MCP integration</h5>
|
|
1360
|
+
<p class="card-text text-muted mb-3">Connect AI assistants to your account using the Model Context Protocol. Paste the URL below into your preferred AI tool.</p>
|
|
1361
|
+
|
|
1362
|
+
<!-- MCP URL display -->
|
|
1363
|
+
<div class="mb-4">
|
|
1364
|
+
<label for="mcp-url-input" class="form-label small text-muted">MCP server URL</label>
|
|
1365
|
+
<div class="input-group">
|
|
1366
|
+
<input type="text" class="form-control font-monospace" id="mcp-url-input" readonly data-mcp-url value="Loading...">
|
|
1367
|
+
<button type="button" class="btn btn-outline-adaptive" id="copy-mcp-url-btn">
|
|
1368
|
+
{% uj_icon "copy", "me-2" %}
|
|
1369
|
+
<span class="button-text d-none d-sm-inline">Copy</span>
|
|
1370
|
+
</button>
|
|
1371
|
+
</div>
|
|
1372
|
+
</div>
|
|
1373
|
+
|
|
1374
|
+
<!-- Provider Tabs -->
|
|
1375
|
+
<div class="border rounded">
|
|
1376
|
+
<ul class="nav nav-tabs px-3 pt-2 flex-nowrap overflow-auto" id="mcp-provider-tabs" role="tablist">
|
|
1377
|
+
<li class="nav-item" role="presentation">
|
|
1378
|
+
<button class="nav-link active small text-nowrap" id="mcp-claude-tab" data-bs-toggle="tab" data-bs-target="#mcp-claude" type="button" role="tab">Claude</button>
|
|
1379
|
+
</li>
|
|
1380
|
+
<li class="nav-item" role="presentation">
|
|
1381
|
+
<button class="nav-link small text-nowrap" id="mcp-cursor-tab" data-bs-toggle="tab" data-bs-target="#mcp-cursor" type="button" role="tab">Cursor</button>
|
|
1382
|
+
</li>
|
|
1383
|
+
<li class="nav-item" role="presentation">
|
|
1384
|
+
<button class="nav-link small text-nowrap" id="mcp-vscode-tab" data-bs-toggle="tab" data-bs-target="#mcp-vscode" type="button" role="tab">VS Code</button>
|
|
1385
|
+
</li>
|
|
1386
|
+
<li class="nav-item" role="presentation">
|
|
1387
|
+
<button class="nav-link small text-nowrap" id="mcp-codex-tab" data-bs-toggle="tab" data-bs-target="#mcp-codex" type="button" role="tab">Codex</button>
|
|
1388
|
+
</li>
|
|
1389
|
+
<li class="nav-item" role="presentation">
|
|
1390
|
+
<button class="nav-link small text-nowrap" id="mcp-gemini-tab" data-bs-toggle="tab" data-bs-target="#mcp-gemini" type="button" role="tab">Gemini CLI</button>
|
|
1391
|
+
</li>
|
|
1392
|
+
<li class="nav-item" role="presentation">
|
|
1393
|
+
<button class="nav-link small text-nowrap" id="mcp-opencode-tab" data-bs-toggle="tab" data-bs-target="#mcp-opencode" type="button" role="tab">OpenCode</button>
|
|
1394
|
+
</li>
|
|
1395
|
+
<li class="nav-item" role="presentation">
|
|
1396
|
+
<button class="nav-link small text-nowrap" id="mcp-windsurf-tab" data-bs-toggle="tab" data-bs-target="#mcp-windsurf" type="button" role="tab">Windsurf</button>
|
|
1397
|
+
</li>
|
|
1398
|
+
<li class="nav-item" role="presentation">
|
|
1399
|
+
<button class="nav-link small text-nowrap" id="mcp-zed-tab" data-bs-toggle="tab" data-bs-target="#mcp-zed" type="button" role="tab">Zed</button>
|
|
1400
|
+
</li>
|
|
1401
|
+
<li class="nav-item" role="presentation">
|
|
1402
|
+
<button class="nav-link small text-nowrap" id="mcp-other-tab" data-bs-toggle="tab" data-bs-target="#mcp-other" type="button" role="tab">Other</button>
|
|
1403
|
+
</li>
|
|
1404
|
+
</ul>
|
|
1405
|
+
<div class="tab-content p-3">
|
|
1406
|
+
<!-- Claude -->
|
|
1407
|
+
<div class="tab-pane fade show active" id="mcp-claude" role="tabpanel">
|
|
1408
|
+
<ol class="small text-muted mb-0">
|
|
1409
|
+
<li class="mb-2">Open your terminal and run:
|
|
1410
|
+
<div class="input-group mt-1 mb-1">
|
|
1411
|
+
<input type="text" class="form-control form-control-sm font-monospace bg-dark text-light" id="mcp-cmd-claude" readonly data-mcp-cmd="claude" value="Loading...">
|
|
1412
|
+
<button type="button" class="btn btn-outline-adaptive btn-sm" data-copy-target="mcp-cmd-claude">{% uj_icon "copy" %}</button>
|
|
1413
|
+
</div>
|
|
1414
|
+
</li>
|
|
1415
|
+
<li class="mb-2">This will trigger an OAuth flow to connect Claude to your account.</li>
|
|
1416
|
+
</ol>
|
|
1417
|
+
<div class="position-relative my-3">
|
|
1418
|
+
<hr class="text-muted">
|
|
1419
|
+
<span class="position-absolute top-50 start-50 translate-middle px-3 text-muted small bg-body-secondary">OR</span>
|
|
1420
|
+
</div>
|
|
1421
|
+
<ol class="small text-muted mb-0">
|
|
1422
|
+
<li class="mb-2">Go to <strong>Claude Desktop → Settings → Integrations → Add</strong>.</li>
|
|
1423
|
+
<li>Paste the MCP URL above and connect.</li>
|
|
1424
|
+
</ol>
|
|
1425
|
+
</div>
|
|
1426
|
+
<!-- Cursor -->
|
|
1427
|
+
<div class="tab-pane fade" id="mcp-cursor" role="tabpanel">
|
|
1428
|
+
<ol class="small text-muted mb-0">
|
|
1429
|
+
<li class="mb-2">Open Cursor Settings: <kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd></li>
|
|
1430
|
+
<li class="mb-2">Select <strong>Skills and Integrations</strong>.</li>
|
|
1431
|
+
<li class="mb-2">Select <strong>New MCP Server</strong> and paste:
|
|
1432
|
+
<div class="position-relative mt-1">
|
|
1433
|
+
<pre class="bg-dark text-light rounded p-3 small mb-0" id="mcp-cmd-cursor" data-mcp-cmd="cursor"><code>Loading...</code></pre>
|
|
1434
|
+
<button type="button" class="btn btn-outline-light btn-sm position-absolute top-0 end-0 m-2" data-copy-target="mcp-cmd-cursor">{% uj_icon "copy" %}</button>
|
|
1435
|
+
</div>
|
|
1436
|
+
</li>
|
|
1437
|
+
</ol>
|
|
1438
|
+
</div>
|
|
1439
|
+
<!-- VS Code -->
|
|
1440
|
+
<div class="tab-pane fade" id="mcp-vscode" role="tabpanel">
|
|
1441
|
+
<ol class="small text-muted mb-0">
|
|
1442
|
+
<li class="mb-2"><kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> and search for <strong>MCP: Add Server</strong>.</li>
|
|
1443
|
+
<li class="mb-2">Select <strong>HTTP (HTTP or Server-Sent Events)</strong>.</li>
|
|
1444
|
+
<li class="mb-2">Enter the MCP URL and hit enter.</li>
|
|
1445
|
+
</ol>
|
|
1446
|
+
<div class="position-relative my-3">
|
|
1447
|
+
<hr class="text-muted">
|
|
1448
|
+
<span class="position-absolute top-50 start-50 translate-middle px-3 text-muted small bg-body-secondary">OR</span>
|
|
1449
|
+
</div>
|
|
1450
|
+
<ol class="small text-muted mb-0">
|
|
1451
|
+
<li class="mb-2">Add to your <code>settings.json</code>:
|
|
1452
|
+
<div class="position-relative mt-1">
|
|
1453
|
+
<pre class="bg-dark text-light rounded p-3 small mb-0" id="mcp-cmd-vscode" data-mcp-cmd="vscode"><code>Loading...</code></pre>
|
|
1454
|
+
<button type="button" class="btn btn-outline-light btn-sm position-absolute top-0 end-0 m-2" data-copy-target="mcp-cmd-vscode">{% uj_icon "copy" %}</button>
|
|
1455
|
+
</div>
|
|
1456
|
+
</li>
|
|
1457
|
+
<li>Save the file and restart VS Code.</li>
|
|
1458
|
+
</ol>
|
|
1459
|
+
</div>
|
|
1460
|
+
<!-- Codex -->
|
|
1461
|
+
<div class="tab-pane fade" id="mcp-codex" role="tabpanel">
|
|
1462
|
+
<ol class="small text-muted mb-0">
|
|
1463
|
+
<li class="mb-2">Open your terminal and run:
|
|
1464
|
+
<div class="input-group mt-1 mb-1">
|
|
1465
|
+
<input type="text" class="form-control form-control-sm font-monospace bg-dark text-light" id="mcp-cmd-codex" readonly data-mcp-cmd="codex" value="Loading...">
|
|
1466
|
+
<button type="button" class="btn btn-outline-adaptive btn-sm" data-copy-target="mcp-cmd-codex">{% uj_icon "copy" %}</button>
|
|
1467
|
+
</div>
|
|
1468
|
+
</li>
|
|
1469
|
+
<li>The MCP server will be available next time you run <code>codex</code>.</li>
|
|
1470
|
+
</ol>
|
|
1471
|
+
</div>
|
|
1472
|
+
<!-- Gemini CLI -->
|
|
1473
|
+
<div class="tab-pane fade" id="mcp-gemini" role="tabpanel">
|
|
1474
|
+
<ol class="small text-muted mb-0">
|
|
1475
|
+
<li class="mb-2">Edit <code>~/.gemini/settings.json</code> and add the HTTP MCP server configuration:
|
|
1476
|
+
<div class="position-relative mt-1">
|
|
1477
|
+
<pre class="bg-dark text-light rounded p-3 small mb-0" id="mcp-cmd-gemini" data-mcp-cmd="gemini"><code>Loading...</code></pre>
|
|
1478
|
+
<button type="button" class="btn btn-outline-light btn-sm position-absolute top-0 end-0 m-2" data-copy-target="mcp-cmd-gemini">{% uj_icon "copy" %}</button>
|
|
1479
|
+
</div>
|
|
1480
|
+
</li>
|
|
1481
|
+
<li class="mb-2">Save the file and restart Gemini CLI.</li>
|
|
1482
|
+
<li class="mb-2">Authenticate by running:
|
|
1483
|
+
<div class="input-group mt-1 mb-1">
|
|
1484
|
+
<input type="text" class="form-control form-control-sm font-monospace bg-dark text-light" id="mcp-cmd-gemini-auth" readonly data-mcp-cmd="gemini-auth" value="Loading...">
|
|
1485
|
+
<button type="button" class="btn btn-outline-adaptive btn-sm" data-copy-target="mcp-cmd-gemini-auth">{% uj_icon "copy" %}</button>
|
|
1486
|
+
</div>
|
|
1487
|
+
</li>
|
|
1488
|
+
<li>This will open a browser window to complete the OAuth flow.</li>
|
|
1489
|
+
</ol>
|
|
1490
|
+
</div>
|
|
1491
|
+
<!-- OpenCode -->
|
|
1492
|
+
<div class="tab-pane fade" id="mcp-opencode" role="tabpanel">
|
|
1493
|
+
<ol class="small text-muted mb-0">
|
|
1494
|
+
<li class="mb-2">Edit <code>~/.config/opencode/opencode.json</code> and add the remote MCP server configuration:
|
|
1495
|
+
<div class="position-relative mt-1">
|
|
1496
|
+
<pre class="bg-dark text-light rounded p-3 small mb-0" id="mcp-cmd-opencode" data-mcp-cmd="opencode"><code>Loading...</code></pre>
|
|
1497
|
+
<button type="button" class="btn btn-outline-light btn-sm position-absolute top-0 end-0 m-2" data-copy-target="mcp-cmd-opencode">{% uj_icon "copy" %}</button>
|
|
1498
|
+
</div>
|
|
1499
|
+
</li>
|
|
1500
|
+
<li class="mb-2">Save the file and restart OpenCode.</li>
|
|
1501
|
+
<li class="mb-2">Authenticate by running:
|
|
1502
|
+
<div class="input-group mt-1 mb-1">
|
|
1503
|
+
<input type="text" class="form-control form-control-sm font-monospace bg-dark text-light" id="mcp-cmd-opencode-auth" readonly data-mcp-cmd="opencode-auth" value="Loading...">
|
|
1504
|
+
<button type="button" class="btn btn-outline-adaptive btn-sm" data-copy-target="mcp-cmd-opencode-auth">{% uj_icon "copy" %}</button>
|
|
1505
|
+
</div>
|
|
1506
|
+
</li>
|
|
1507
|
+
<li>This will open a browser window to complete the OAuth flow.</li>
|
|
1508
|
+
</ol>
|
|
1509
|
+
</div>
|
|
1510
|
+
<!-- Windsurf -->
|
|
1511
|
+
<div class="tab-pane fade" id="mcp-windsurf" role="tabpanel">
|
|
1512
|
+
<ol class="small text-muted mb-0">
|
|
1513
|
+
<li class="mb-2">Open Windsurf Settings.</li>
|
|
1514
|
+
<li class="mb-2">Under <strong>Cascade</strong>, find <strong>Model Context Protocol Servers</strong>.</li>
|
|
1515
|
+
<li class="mb-2">Select <strong>Add Server</strong> and paste:
|
|
1516
|
+
<div class="position-relative mt-1">
|
|
1517
|
+
<pre class="bg-dark text-light rounded p-3 small mb-0" id="mcp-cmd-windsurf" data-mcp-cmd="windsurf"><code>Loading...</code></pre>
|
|
1518
|
+
<button type="button" class="btn btn-outline-light btn-sm position-absolute top-0 end-0 m-2" data-copy-target="mcp-cmd-windsurf">{% uj_icon "copy" %}</button>
|
|
1519
|
+
</div>
|
|
1520
|
+
</li>
|
|
1521
|
+
</ol>
|
|
1522
|
+
</div>
|
|
1523
|
+
<!-- Zed -->
|
|
1524
|
+
<div class="tab-pane fade" id="mcp-zed" role="tabpanel">
|
|
1525
|
+
<ol class="small text-muted mb-0">
|
|
1526
|
+
<li class="mb-2"><kbd>⌘</kbd> + <kbd>,</kbd> to open Zed settings.</li>
|
|
1527
|
+
<li class="mb-2">Add the MCP server configuration:
|
|
1528
|
+
<div class="position-relative mt-1">
|
|
1529
|
+
<pre class="bg-dark text-light rounded p-3 small mb-0" id="mcp-cmd-zed" data-mcp-cmd="zed"><code>Loading...</code></pre>
|
|
1530
|
+
<button type="button" class="btn btn-outline-light btn-sm position-absolute top-0 end-0 m-2" data-copy-target="mcp-cmd-zed">{% uj_icon "copy" %}</button>
|
|
1531
|
+
</div>
|
|
1532
|
+
</li>
|
|
1533
|
+
<li>Restart Zed to activate the server.</li>
|
|
1534
|
+
</ol>
|
|
1535
|
+
</div>
|
|
1536
|
+
<!-- Other -->
|
|
1537
|
+
<div class="tab-pane fade" id="mcp-other" role="tabpanel">
|
|
1538
|
+
<p class="small text-muted mb-2">Use these details to configure any MCP-compatible client:</p>
|
|
1539
|
+
<table class="table table-sm small mb-0">
|
|
1540
|
+
<tbody>
|
|
1541
|
+
<tr>
|
|
1542
|
+
<td class="text-muted fw-medium w-25">Server URL</td>
|
|
1543
|
+
<td class="font-monospace" id="mcp-detail-url" data-mcp-detail="url">Loading...</td>
|
|
1544
|
+
</tr>
|
|
1545
|
+
<tr>
|
|
1546
|
+
<td class="text-muted fw-medium">Transport</td>
|
|
1547
|
+
<td class="font-monospace">Streamable HTTP</td>
|
|
1548
|
+
</tr>
|
|
1549
|
+
<tr>
|
|
1550
|
+
<td class="text-muted fw-medium">Authentication</td>
|
|
1551
|
+
<td>OAuth 2.1 (automatic)</td>
|
|
1552
|
+
</tr>
|
|
1553
|
+
</tbody>
|
|
1554
|
+
</table>
|
|
1555
|
+
</div>
|
|
1556
|
+
</div>
|
|
1557
|
+
</div>
|
|
1558
|
+
|
|
1559
|
+
</div>
|
|
1560
|
+
</div>
|
|
1354
1561
|
</section>
|
|
1355
1562
|
|
|
1356
1563
|
<!-- Data Request Section (Hidden by default) -->
|