ultimate-jekyll-manager 1.9.1 → 1.9.3
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
CHANGED
|
@@ -14,6 +14,18 @@ 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.3] - 2026-06-18
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **Notifly build-error notifications use absolute path.** Switched from bare `notifly` keyword to full application path (`/Applications/Notifly.app/Contents/MacOS/Notifly`) so the notification binary resolves reliably regardless of PATH.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
## [1.9.2] - 2026-06-17
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **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()`.
|
|
28
|
+
|
|
17
29
|
---
|
|
18
30
|
## [1.9.1] - 2026-06-17
|
|
19
31
|
|
|
@@ -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
|
}
|
package/dist/build.js
CHANGED
|
@@ -68,7 +68,7 @@ Manager.reportBuildError = function (error, callback) {
|
|
|
68
68
|
const errorMessage = error.message || error.toString() || 'Unknown error';
|
|
69
69
|
const errorPlugin = error.plugin || 'Build';
|
|
70
70
|
|
|
71
|
-
execute(
|
|
71
|
+
execute(`/Applications/Notifly.app/Contents/MacOS/Notifly --title 'Build Error: ${errorPlugin}' --message '${errorMessage.replace(/'/g, "\\'")}' --appIcon '/Users/ian/claude-ai-icon.png' --timeout 3 --sound 'Sosumi'`)
|
|
72
72
|
.catch((e) => {
|
|
73
73
|
logger.error('Failed to send notification', e);
|
|
74
74
|
});
|
|
@@ -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) -->
|
package/logs/test.log
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
# ujm log — 2026-06-
|
|
2
|
-
[
|
|
3
|
-
[
|
|
1
|
+
# ujm log — 2026-06-18T20:43:18.296Z — pid=99488
|
|
2
|
+
[13:43:18] 'test': Running tests (layer=all)
|
|
3
|
+
[13:43:18] 'test': Test mode: normal (external APIs skipped)
|
|
4
4
|
|
|
5
5
|
Ultimate Jekyll Manager Tests
|
|
6
6
|
|
|
7
7
|
Framework Tests
|
|
8
8
|
⤷ attach-log-file — tee stdout/stderr to a file
|
|
9
|
-
✓ exports the expected surface (
|
|
9
|
+
✓ exports the expected surface (1ms)
|
|
10
10
|
✓ stripAnsi removes color escape codes (0ms)
|
|
11
11
|
hello world
|
|
12
12
|
colored line
|
|
13
13
|
✓ attach + stdout.write + detach: file contains the writes (0ms)
|
|
14
14
|
✓ idempotent: attaching twice with same name returns same fd (0ms)
|
|
15
|
-
✓ attach with falsy name returns null and does nothing (
|
|
15
|
+
✓ attach with falsy name returns null and does nothing (1ms)
|
|
16
16
|
⤷ CLI alias resolution
|
|
17
17
|
✓ cli.js exports a Main class (0ms)
|
|
18
|
-
✓ all expected commands exist on disk (
|
|
18
|
+
✓ all expected commands exist on disk (0ms)
|
|
19
19
|
✓ each command module exports an async function (3ms)
|
|
20
20
|
⤷ collectTextNodes (utils/collectTextNodes.js)
|
|
21
|
-
✓ extracts page title (
|
|
22
|
-
✓ skips <script> and <style> (
|
|
23
|
-
✓ spellcheck dictionary (utils/dictionary.js) (
|
|
21
|
+
✓ extracts page title (98ms)
|
|
22
|
+
✓ skips <script> and <style> (0ms)
|
|
23
|
+
✓ spellcheck dictionary (utils/dictionary.js) (0ms)
|
|
24
24
|
⤷ expect() matcher set
|
|
25
25
|
✓ toBe + toEqual basics (0ms)
|
|
26
|
-
✓ .not negates (
|
|
26
|
+
✓ .not negates (1ms)
|
|
27
27
|
✓ toContain works on arrays and strings (0ms)
|
|
28
28
|
✓ toThrow catches sync + async throws (0ms)
|
|
29
29
|
✓ toBeGreaterThan / toBeLessThan (0ms)
|
|
@@ -44,7 +44,7 @@ colored line
|
|
|
44
44
|
✓ actLikeProduction is true when isBuildMode OR UJ_AUDIT_FORCE (0ms)
|
|
45
45
|
✓ getRootPath("package") points at UJM root (0ms)
|
|
46
46
|
✓ getMemoryUsage returns shape with MB-sized numbers (0ms)
|
|
47
|
-
✓ getArguments returns object with _ array + boolean defaults (
|
|
47
|
+
✓ getArguments returns object with _ array + boolean defaults (1ms)
|
|
48
48
|
✓ logger returns object with log/error/warn/info methods (0ms)
|
|
49
49
|
✓ processBatches processes items in chunks and returns flat results (0ms)
|
|
50
50
|
⤷ mergeJekyllConfigs (utils/merge-jekyll-configs.js)
|
|
@@ -60,66 +60,66 @@ colored line
|
|
|
60
60
|
✓ getVersion returns a non-empty string when run from a package (0ms)
|
|
61
61
|
⤷ createTemplateTransform (utils/template-transform.js)
|
|
62
62
|
✓ replaces [site.theme.id] with config value in .html files (1ms)
|
|
63
|
-
✓ leaves non-matching extensions untouched (e.g. .css) (
|
|
63
|
+
✓ leaves non-matching extensions untouched (e.g. .css) (1ms)
|
|
64
64
|
✓ passes directories through untouched (0ms)
|
|
65
65
|
⤷ node-powertools templating brackets ({} and [])
|
|
66
|
-
✓ default { } brackets resolve nested keys (
|
|
66
|
+
✓ default { } brackets resolve nested keys (0ms)
|
|
67
67
|
✓ [ ] brackets resolve nested keys when explicitly configured (0ms)
|
|
68
68
|
✓ [ ] brackets leave Jekyll {{ }} placeholders alone (0ms)
|
|
69
69
|
⤷ theme contract (structure, swappability, cross-theme JS contracts)
|
|
70
|
-
✓ _template: entry files + config contract (
|
|
71
|
-
✓ classy: entry files + config contract (
|
|
70
|
+
✓ _template: entry files + config contract (1ms)
|
|
71
|
+
✓ classy: entry files + config contract (0ms)
|
|
72
72
|
✓ neobrutalism: entry files + config contract (0ms)
|
|
73
73
|
✓ newsflash: entry files + config contract (0ms)
|
|
74
|
-
✓ _template: layouts swappable, markup clean (
|
|
75
|
-
✓ classy: layouts swappable, markup clean (
|
|
74
|
+
✓ _template: layouts swappable, markup clean (1ms)
|
|
75
|
+
✓ classy: layouts swappable, markup clean (14ms)
|
|
76
76
|
✓ neobrutalism: layouts swappable, markup clean (2ms)
|
|
77
|
-
✓ newsflash: layouts swappable, markup clean (
|
|
77
|
+
✓ newsflash: layouts swappable, markup clean (3ms)
|
|
78
78
|
✓ _template: cross-theme JS contracts (0ms)
|
|
79
|
-
✓ classy: cross-theme JS contracts (
|
|
80
|
-
✓ neobrutalism: cross-theme JS contracts (
|
|
81
|
-
✓ newsflash: cross-theme JS contracts (
|
|
82
|
-
✓ page asset files match a declared asset_path shape (
|
|
79
|
+
✓ classy: cross-theme JS contracts (0ms)
|
|
80
|
+
✓ neobrutalism: cross-theme JS contracts (1ms)
|
|
81
|
+
✓ newsflash: cross-theme JS contracts (0ms)
|
|
82
|
+
✓ page asset files match a declared asset_path shape (4ms)
|
|
83
83
|
⤷ validateYAMLFrontMatter (utils/_validate-yaml.js)
|
|
84
84
|
✓ returns { valid: true } for a file with valid frontmatter (0ms)
|
|
85
85
|
✓ returns { valid: true } when no frontmatter present (1ms)
|
|
86
86
|
✓ flags malformed YAML frontmatter as invalid with error message (0ms)
|
|
87
87
|
⤷ page-layer baseline (DOM + fetch + storage)
|
|
88
88
|
✓ document is interactive or complete (1ms)
|
|
89
|
-
✓ fetch() works against the local harness server (
|
|
90
|
-
✓ localStorage is available (
|
|
89
|
+
✓ fetch() works against the local harness server (8ms)
|
|
90
|
+
✓ localStorage is available (1ms)
|
|
91
91
|
⤷ FormManager getData / setData / input groups
|
|
92
|
-
✓ _setNested builds nested objects from dot paths (
|
|
93
|
-
✓ _setNested accumulates duplicate keys into arrays (
|
|
92
|
+
✓ _setNested builds nested objects from dot paths (0ms)
|
|
93
|
+
✓ _setNested accumulates duplicate keys into arrays (0ms)
|
|
94
94
|
✓ _getNested reads nested values and returns undefined for missing paths (0ms)
|
|
95
|
-
✓ _flattenObject converts nested objects to dot paths (
|
|
96
|
-
✓ getData collects text, select, radio, and textarea values with dot notation (
|
|
97
|
-
✓ getData handles single checkbox (boolean) and checkbox groups (object) (
|
|
95
|
+
✓ _flattenObject converts nested objects to dot paths (1ms)
|
|
96
|
+
✓ getData collects text, select, radio, and textarea values with dot notation (5ms)
|
|
97
|
+
✓ getData handles single checkbox (boolean) and checkbox groups (object) (0ms)
|
|
98
98
|
✓ getData excludes honeypot fields (0ms)
|
|
99
|
-
✓ input group filter includes matching + global fields, excludes others (
|
|
99
|
+
✓ input group filter includes matching + global fields, excludes others (1ms)
|
|
100
100
|
✓ input group filter with multiple groups includes all matching (0ms)
|
|
101
|
-
✓ setData populates text, select, textarea fields (
|
|
101
|
+
✓ setData populates text, select, textarea fields (0ms)
|
|
102
102
|
✓ setData sets radio group to matching value (0ms)
|
|
103
103
|
✓ setData sets single checkbox boolean and checkbox group values (0ms)
|
|
104
104
|
⤷ FormManager disabled-state snapshot
|
|
105
|
-
✓ snapshot captures disabled non-submit elements, ignores submit buttons (
|
|
106
|
-
✓ setDisabled(true) disables everything (
|
|
105
|
+
✓ snapshot captures disabled non-submit elements, ignores submit buttons (1ms)
|
|
106
|
+
✓ setDisabled(true) disables everything (0ms)
|
|
107
107
|
✓ setDisabled(false) re-enables managed elements but keeps snapshotted ones disabled (0ms)
|
|
108
108
|
✓ survives multiple disable/enable cycles (0ms)
|
|
109
|
-
✓ onsubmit="return false" blocks native submission before FM loads (
|
|
109
|
+
✓ onsubmit="return false" blocks native submission before FM loads (10ms)
|
|
110
110
|
⤷ FormManager validation + honeypot + file-accept
|
|
111
111
|
✓ required text field fails when empty, passes when filled (0ms)
|
|
112
|
-
✓ required checkbox fails when unchecked (
|
|
112
|
+
✓ required checkbox fails when unchecked (1ms)
|
|
113
113
|
✓ required radio group fails when none checked (0ms)
|
|
114
|
-
✓ email validation rejects invalid formats and accepts valid ones (
|
|
114
|
+
✓ email validation rejects invalid formats and accepts valid ones (0ms)
|
|
115
115
|
✓ number min/max validation catches out-of-range values (1ms)
|
|
116
116
|
✓ minlength and maxlength validation (0ms)
|
|
117
117
|
✓ pattern attribute validation (0ms)
|
|
118
118
|
✓ honeypot detection catches [data-honey] and [name="honey"] fields (0ms)
|
|
119
119
|
✓ honeypot detects data-honey without name="honey" (0ms)
|
|
120
|
-
✓ file-accept matching: extension, wildcard MIME, exact MIME (
|
|
120
|
+
✓ file-accept matching: extension, wildcard MIME, exact MIME (0ms)
|
|
121
121
|
✓ field error display adds is-invalid class and feedback element (0ms)
|
|
122
|
-
✓ query param population skips UTM/tracking params (
|
|
122
|
+
✓ query param population skips UTM/tracking params (1ms)
|
|
123
123
|
✓ data-form-state attribute reflects state transitions (0ms)
|
|
124
124
|
⤷ harness globals (window.Configuration + dataset)
|
|
125
125
|
✓ window.Configuration has brand + theme + web_manager (0ms)
|
|
@@ -129,16 +129,16 @@ colored line
|
|
|
129
129
|
✓ template#prerendered-icons exists and has the test icon (1ms)
|
|
130
130
|
✓ looking up a missing icon returns null (0ms)
|
|
131
131
|
⤷ boot tests (consumer _site/)
|
|
132
|
-
✓ /service-worker.js served with javascript content type (
|
|
133
|
-
✓ index.html registers SW and reaches activated state (
|
|
134
|
-
✓ SW responds to get-cache-name message with brand-id pattern (
|
|
135
|
-
✓ home page renders with title + body content (
|
|
136
|
-
✓ /about resolves via Jekyll-style .html fallback (
|
|
137
|
-
✓ build.json is served with brand metadata (
|
|
138
|
-
✓ CSS bundle served with text/css content type (
|
|
132
|
+
✓ /service-worker.js served with javascript content type (130ms)
|
|
133
|
+
✓ index.html registers SW and reaches activated state (182ms)
|
|
134
|
+
✓ SW responds to get-cache-name message with brand-id pattern (86ms)
|
|
135
|
+
✓ home page renders with title + body content (187ms)
|
|
136
|
+
✓ /about resolves via Jekyll-style .html fallback (73ms)
|
|
137
|
+
✓ build.json is served with brand metadata (76ms)
|
|
138
|
+
✓ CSS bundle served with text/css content type (87ms)
|
|
139
139
|
|
|
140
140
|
Results
|
|
141
141
|
110 passing
|
|
142
142
|
|
|
143
|
-
Total: 110 tests in
|
|
143
|
+
Total: 110 tests in 3274ms
|
|
144
144
|
|