@openparachute/hub 0.7.1 → 0.7.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/README.md +13 -14
- package/package.json +1 -1
- package/src/__tests__/admin-agent-grants.test.ts +1547 -0
- package/src/__tests__/{admin-channel-token.test.ts → admin-agent-token.test.ts} +32 -32
- package/src/__tests__/admin-connections-credentials.test.ts +8 -4
- package/src/__tests__/admin-connections.test.ts +211 -57
- package/src/__tests__/admin-csrf-belt.test.ts +7 -7
- package/src/__tests__/admin-lock.test.ts +600 -0
- package/src/__tests__/admin-module-token.test.ts +36 -8
- package/src/__tests__/admin-vaults.test.ts +8 -8
- package/src/__tests__/api-modules-ops.test.ts +17 -16
- package/src/__tests__/api-modules.test.ts +35 -36
- package/src/__tests__/api-ready.test.ts +2 -2
- package/src/__tests__/clients.test.ts +91 -0
- package/src/__tests__/grants-store.test.ts +219 -0
- package/src/__tests__/hub-server.test.ts +9 -5
- package/src/__tests__/migrate.test.ts +1 -1
- package/src/__tests__/module-manifest.test.ts +11 -11
- package/src/__tests__/oauth-client.test.ts +446 -0
- package/src/__tests__/oauth-flows-store.test.ts +141 -0
- package/src/__tests__/oauth-handlers.test.ts +124 -26
- package/src/__tests__/operator-token.test.ts +2 -2
- package/src/__tests__/scope-explanations.test.ts +3 -3
- package/src/__tests__/serve-boot.test.ts +14 -14
- package/src/__tests__/serve.test.ts +26 -0
- package/src/__tests__/service-spec-discovery.test.ts +26 -18
- package/src/__tests__/services-manifest.test.ts +60 -48
- package/src/__tests__/setup-gate.test.ts +52 -3
- package/src/__tests__/setup-wizard.test.ts +86 -280
- package/src/__tests__/setup.test.ts +1 -1
- package/src/__tests__/upgrade.test.ts +276 -0
- package/src/__tests__/vault-remove.test.ts +393 -0
- package/src/admin-agent-grants.ts +1365 -0
- package/src/admin-agent-token.ts +147 -0
- package/src/admin-connections.ts +67 -50
- package/src/admin-host-admin-token.ts +14 -1
- package/src/admin-lock.ts +281 -0
- package/src/admin-module-token.ts +15 -7
- package/src/admin-vault-admin-token.ts +8 -1
- package/src/admin-vaults.ts +12 -12
- package/src/api-admin-lock.ts +335 -0
- package/src/api-modules-ops.ts +3 -2
- package/src/api-modules.ts +9 -9
- package/src/cli.ts +13 -1
- package/src/clients.ts +88 -0
- package/src/commands/install.ts +7 -0
- package/src/commands/serve-boot.ts +5 -4
- package/src/commands/serve.ts +45 -19
- package/src/commands/setup.ts +4 -3
- package/src/commands/upgrade.ts +118 -2
- package/src/commands/vault-remove.ts +361 -0
- package/src/commands/wizard.ts +4 -4
- package/src/connections-store.ts +3 -3
- package/src/grants-store.ts +272 -0
- package/src/help.ts +4 -1
- package/src/hub-server.ts +209 -27
- package/src/hub-settings.ts +23 -8
- package/src/jwt-sign.ts +5 -1
- package/src/module-manifest.ts +2 -2
- package/src/oauth-client.ts +497 -0
- package/src/oauth-flows-store.ts +163 -0
- package/src/oauth-handlers.ts +40 -13
- package/src/operator-token.ts +1 -1
- package/src/origin-check.ts +7 -2
- package/src/resource-binding.ts +4 -4
- package/src/scope-explanations.ts +3 -3
- package/src/service-spec.ts +56 -43
- package/src/setup-wizard.ts +56 -240
- package/web/ui/dist/assets/index-B5AUE359.js +61 -0
- package/web/ui/dist/assets/{index-E_9wqjEm.css → index-DR6R8EFf.css} +1 -1
- package/web/ui/dist/index.html +2 -2
- package/src/admin-channel-token.ts +0 -135
- package/web/ui/dist/assets/index-Cxtod68O.js +0 -61
package/src/setup-wizard.ts
CHANGED
|
@@ -71,7 +71,6 @@ import {
|
|
|
71
71
|
type IssueOperatorTokenResult,
|
|
72
72
|
type MintOperatorTokenOpts,
|
|
73
73
|
issueOperatorToken,
|
|
74
|
-
mintOperatorToken,
|
|
75
74
|
readOperatorTokenFile,
|
|
76
75
|
} from "./operator-token.ts";
|
|
77
76
|
import { isHttpsRequest } from "./request-protocol.ts";
|
|
@@ -1154,16 +1153,6 @@ export interface RenderDoneStepProps {
|
|
|
1154
1153
|
* (e.g. tests of the wizard's older two-step flow).
|
|
1155
1154
|
*/
|
|
1156
1155
|
exposeMode?: SetupExposeMode;
|
|
1157
|
-
/**
|
|
1158
|
-
* Auto-minted operator token surfaced once on the done screen
|
|
1159
|
-
* (hub#272 Item A). When present, the MCP install command renders
|
|
1160
|
-
* with `--header "Authorization: Bearer <token>"` pre-filled and a
|
|
1161
|
-
* one-click Copy button. Absent means the mint either failed or
|
|
1162
|
-
* the operator already consumed the single-use surface — the tile
|
|
1163
|
-
* falls back to the un-headered command + a "mint at /admin/tokens"
|
|
1164
|
-
* hint.
|
|
1165
|
-
*/
|
|
1166
|
-
mintedToken?: string;
|
|
1167
1156
|
/**
|
|
1168
1157
|
* Optional per-module install tiles to render alongside the MCP
|
|
1169
1158
|
* command (hub#272 Item B). When omitted, the done step renders
|
|
@@ -1175,9 +1164,9 @@ export interface RenderDoneStepProps {
|
|
|
1175
1164
|
}
|
|
1176
1165
|
|
|
1177
1166
|
export function renderDoneStep(props: RenderDoneStepProps): string {
|
|
1178
|
-
const { vaultName, hubOrigin, exposeMode,
|
|
1167
|
+
const { vaultName, hubOrigin, exposeMode, installTiles } = props;
|
|
1179
1168
|
const reachable = exposeMode ? renderReachableTile(exposeMode, hubOrigin) : "";
|
|
1180
|
-
const mcpTile = renderMcpTile(vaultName, hubOrigin
|
|
1169
|
+
const mcpTile = renderMcpTile(vaultName, hubOrigin);
|
|
1181
1170
|
const tiles = installTiles && installTiles.length > 0 ? installTiles : [];
|
|
1182
1171
|
const installSection = tiles.length > 0 ? renderInstallTiles(tiles) : "";
|
|
1183
1172
|
const startTile = renderStartUsingTile(vaultName, hubOrigin);
|
|
@@ -1241,129 +1230,26 @@ export function renderDoneStep(props: RenderDoneStepProps): string {
|
|
|
1241
1230
|
}
|
|
1242
1231
|
|
|
1243
1232
|
/**
|
|
1244
|
-
* The MCP-connect tile.
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
* the
|
|
1233
|
+
* The MCP-connect tile. Renders the bare `claude mcp add` command —
|
|
1234
|
+
* no token, no `--header`. Vault/init is OAuth-default (parachute-vault
|
|
1235
|
+
* #491), so the command triggers browser OAuth on first use: the
|
|
1236
|
+
* operator signs in to this hub + approves access, and Claude Code
|
|
1237
|
+
* stores the resulting credential. For headless clients that can't do
|
|
1238
|
+
* the browser flow, the fine print points at `/admin/tokens` (the
|
|
1239
|
+
* canonical mint surface) + the `--header "Authorization: Bearer
|
|
1240
|
+
* <token>"` form they append themselves.
|
|
1241
|
+
*
|
|
1242
|
+
* History: an earlier build (hub#272 Item A) auto-minted a full-scope
|
|
1243
|
+
* operator token here and pre-filled the command with a Bearer header
|
|
1244
|
+
* + a masked-reveal/Copy widget. That was removed 2026-06-23 (Austen's
|
|
1245
|
+
* report): header-auth was the wrong default once vault went OAuth-
|
|
1246
|
+
* default, and baking an admin-scope bearer into a copy-pasted command
|
|
1247
|
+
* was a privilege over-grant + a shoulder-surf hazard. The bare OAuth
|
|
1248
|
+
* command was always the correct UX; it's now the only one.
|
|
1250
1249
|
*/
|
|
1251
|
-
function renderMcpTile(
|
|
1252
|
-
vaultName: string,
|
|
1253
|
-
hubOrigin: string,
|
|
1254
|
-
mintedToken: string | undefined,
|
|
1255
|
-
): string {
|
|
1250
|
+
function renderMcpTile(vaultName: string, hubOrigin: string): string {
|
|
1256
1251
|
const safeVault = escapeHtml(vaultName);
|
|
1257
1252
|
const bareCmd = `claude mcp add --transport http parachute-${vaultName} ${hubOrigin}/vault/${vaultName}/mcp`;
|
|
1258
|
-
if (mintedToken) {
|
|
1259
|
-
// The token contents are surfaced once + then forgotten by the
|
|
1260
|
-
// server (single-use hub_setting). Two visible variants of the
|
|
1261
|
-
// command live in the DOM:
|
|
1262
|
-
//
|
|
1263
|
-
// * `pre#mcp-cmd` — what the operator sees. The Bearer token is
|
|
1264
|
-
// replaced with a fixed-width row of • so shoulder-surfers,
|
|
1265
|
-
// screencasts, and over-the-shoulder photos don't capture
|
|
1266
|
-
// credentials by default. This is the "discoverable but not
|
|
1267
|
-
// shoulder-surf-able" framing Aaron asked for.
|
|
1268
|
-
// * `script#mcp-cmd-real` (type=text/plain) — the real command
|
|
1269
|
-
// with the live token, stashed in a non-rendering script tag.
|
|
1270
|
-
// The Copy + Show handlers read from this so the operator's
|
|
1271
|
-
// terminal paste still gets the real header without the page
|
|
1272
|
-
// ever painting the token.
|
|
1273
|
-
//
|
|
1274
|
-
// The view-source threat model is unchanged from rc.9 — the token
|
|
1275
|
-
// is part of the response body either way. The improvement is
|
|
1276
|
-
// *visibly hidden by default*, which is what an over-the-shoulder
|
|
1277
|
-
// observer needs (and what existing screencasts of the wizard
|
|
1278
|
-
// currently leak).
|
|
1279
|
-
//
|
|
1280
|
-
// Show toggles textContent between masked + real and flips a
|
|
1281
|
-
// data-state attribute so a screencast / pair-programming session
|
|
1282
|
-
// can briefly reveal-and-rehide without the operator losing the
|
|
1283
|
-
// line of sight on which mode they're in. Auto-hide after 10s so
|
|
1284
|
-
// a forgotten reveal doesn't leak the token into a subsequent
|
|
1285
|
-
// recording.
|
|
1286
|
-
const fullCmd = `${bareCmd} --header "Authorization: Bearer ${mintedToken}"`;
|
|
1287
|
-
// Clamp the dot count to a 8–40 range so very-short or very-long
|
|
1288
|
-
// tokens don't render comically — token format is fixed-width
|
|
1289
|
-
// (JTI-derived), so this is purely visual.
|
|
1290
|
-
const maskedToken = "•".repeat(Math.max(8, Math.min(40, mintedToken.length)));
|
|
1291
|
-
const maskedCmd = `${bareCmd} --header "Authorization: Bearer ${maskedToken}"`;
|
|
1292
|
-
// The real command rides in a hidden <script type="application/json">
|
|
1293
|
-
// block as a JSON-encoded string. <script> element content is parsed
|
|
1294
|
-
// as raw text (no entity references), so HTML escaping would put
|
|
1295
|
-
// literal `"` into the string — and Copy would paste that into
|
|
1296
|
-
// the operator's terminal. JSON encoding (with `</` escaped so the
|
|
1297
|
-
// sequence can't prematurely close the tag) round-trips safely:
|
|
1298
|
-
// textContent returns the JSON, JSON.parse decodes back to the
|
|
1299
|
-
// exact bytes of the original command. Caught while smoke-testing
|
|
1300
|
-
// the rc.11 reveal/copy UX — pre-fix, the copied command included
|
|
1301
|
-
// `"` placeholders that broke shell parsing.
|
|
1302
|
-
const fullCmdJson = JSON.stringify(fullCmd).replace(/<\//g, "<\\/");
|
|
1303
|
-
return `<div class="done-tile">
|
|
1304
|
-
<h2>Connect Claude Code (MCP)</h2>
|
|
1305
|
-
<p>Wire <code>vault:${safeVault}</code> into Claude Code as an MCP server:</p>
|
|
1306
|
-
<div class="mcp-cmd-wrap" data-state="masked">
|
|
1307
|
-
<pre id="mcp-cmd">${escapeHtml(maskedCmd)}</pre>
|
|
1308
|
-
<script type="application/json" id="mcp-cmd-real">${fullCmdJson}</script>
|
|
1309
|
-
<div class="mcp-cmd-actions">
|
|
1310
|
-
<button type="button" class="btn btn-mcp-aux" id="mcp-cmd-show">Show token</button>
|
|
1311
|
-
<button type="button" class="btn btn-copy" id="mcp-cmd-copy">Copy</button>
|
|
1312
|
-
</div>
|
|
1313
|
-
</div>
|
|
1314
|
-
<p class="fine">We minted this token for your first MCP connection.
|
|
1315
|
-
It's masked above so it's safe to leave open on screen; Copy
|
|
1316
|
-
copies the real command. It's a full-scope operator token tied
|
|
1317
|
-
to your admin account; manage and revoke tokens at
|
|
1318
|
-
<a href="/admin/tokens"><code>/admin/tokens</code></a>.</p>
|
|
1319
|
-
<script>
|
|
1320
|
-
(function () {
|
|
1321
|
-
var wrap = document.querySelector('.mcp-cmd-wrap[data-state]');
|
|
1322
|
-
var pre = document.getElementById('mcp-cmd');
|
|
1323
|
-
var real = document.getElementById('mcp-cmd-real');
|
|
1324
|
-
var copyBtn = document.getElementById('mcp-cmd-copy');
|
|
1325
|
-
var showBtn = document.getElementById('mcp-cmd-show');
|
|
1326
|
-
if (!wrap || !pre || !real || !copyBtn || !showBtn) return;
|
|
1327
|
-
var realCmd;
|
|
1328
|
-
try { realCmd = JSON.parse(real.textContent || '""'); }
|
|
1329
|
-
catch (e) { realCmd = ''; }
|
|
1330
|
-
var maskedCmd = pre.textContent || '';
|
|
1331
|
-
var revealTimer = null;
|
|
1332
|
-
function setMasked() {
|
|
1333
|
-
pre.textContent = maskedCmd;
|
|
1334
|
-
wrap.setAttribute('data-state', 'masked');
|
|
1335
|
-
showBtn.textContent = 'Show token';
|
|
1336
|
-
if (revealTimer) { clearTimeout(revealTimer); revealTimer = null; }
|
|
1337
|
-
}
|
|
1338
|
-
function setRevealed() {
|
|
1339
|
-
pre.textContent = realCmd;
|
|
1340
|
-
wrap.setAttribute('data-state', 'revealed');
|
|
1341
|
-
showBtn.textContent = 'Hide token';
|
|
1342
|
-
// Auto-hide after 10s so a stray reveal doesn't leak the
|
|
1343
|
-
// token into a screencast capture that started after the
|
|
1344
|
-
// click.
|
|
1345
|
-
if (revealTimer) { clearTimeout(revealTimer); revealTimer = null; }
|
|
1346
|
-
revealTimer = setTimeout(setMasked, 10000);
|
|
1347
|
-
}
|
|
1348
|
-
showBtn.addEventListener('click', function () {
|
|
1349
|
-
if (wrap.getAttribute('data-state') === 'masked') setRevealed();
|
|
1350
|
-
else setMasked();
|
|
1351
|
-
});
|
|
1352
|
-
copyBtn.addEventListener('click', function () {
|
|
1353
|
-
// Copy ALWAYS pulls from the real command — the operator's
|
|
1354
|
-
// terminal needs the live token regardless of whether the
|
|
1355
|
-
// page is currently masked. This is the load-bearing path:
|
|
1356
|
-
// the visible mask is a UX nicety; the clipboard must
|
|
1357
|
-
// carry the real header.
|
|
1358
|
-
navigator.clipboard.writeText(realCmd).then(function () {
|
|
1359
|
-
copyBtn.textContent = 'Copied ✓';
|
|
1360
|
-
setTimeout(function () { copyBtn.textContent = 'Copy'; }, 2000);
|
|
1361
|
-
});
|
|
1362
|
-
});
|
|
1363
|
-
})();
|
|
1364
|
-
</script>
|
|
1365
|
-
</div>`;
|
|
1366
|
-
}
|
|
1367
1253
|
return `<div class="done-tile">
|
|
1368
1254
|
<h2>Connect Claude Code (MCP)</h2>
|
|
1369
1255
|
<p>Wire <code>vault:${safeVault}</code> into Claude Code as an MCP server:</p>
|
|
@@ -1755,25 +1641,21 @@ export function handleSetupGet(req: Request, deps: SetupWizardDeps): Response {
|
|
|
1755
1641
|
// request from step 2.
|
|
1756
1642
|
if (state.hasAdmin && state.hasVault && state.hasExposeMode) {
|
|
1757
1643
|
if (url.searchParams.get("just_finished") === "1") {
|
|
1758
|
-
// hub#274 security fold: session-gate this branch.
|
|
1759
|
-
//
|
|
1760
|
-
//
|
|
1761
|
-
//
|
|
1762
|
-
//
|
|
1763
|
-
//
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1766
|
-
//
|
|
1767
|
-
//
|
|
1644
|
+
// hub#274 security fold: session-gate this branch. Originally this
|
|
1645
|
+
// protected the `setup_minted_token` read-and-consume below (a
|
|
1646
|
+
// full-scope operator JWT a racing client could have walked off
|
|
1647
|
+
// with). The auto-mint was removed 2026-06-23 (Austen's report —
|
|
1648
|
+
// vault is OAuth-default now, see the expose POST), so no secret
|
|
1649
|
+
// is surfaced here anymore. The gate stays: the done screen is a
|
|
1650
|
+
// post-setup admin surface and shouldn't render to a drive-by
|
|
1651
|
+
// unauthenticated GET. The dispatcher in `hub-server.ts`'s
|
|
1652
|
+
// `shouldGateForSetup` lets `/admin/setup*` through the pre-admin
|
|
1653
|
+
// lockout, and that path stays open post-setup — so this gate has
|
|
1654
|
+
// to live here, not at the dispatcher layer.
|
|
1768
1655
|
//
|
|
1769
1656
|
// A legitimate operator carrying the session cookie minted on
|
|
1770
1657
|
// the account POST sails through. A drive-by GET without the
|
|
1771
|
-
// cookie 302s to /login
|
|
1772
|
-
// operator's other tab, they sign in + the row is already
|
|
1773
|
-
// consumed by the legitimate done-GET (the single-use shape
|
|
1774
|
-
// guarantees they see the fallback shape, never the secret).
|
|
1775
|
-
// If it's an attacker, they can't pass /login without the
|
|
1776
|
-
// password.
|
|
1658
|
+
// cookie 302s to /login.
|
|
1777
1659
|
const session = findActiveSession(deps.db, req);
|
|
1778
1660
|
if (!session) {
|
|
1779
1661
|
// Preserve the CSRF set-cookie header on the 302 — same shape as
|
|
@@ -1790,13 +1672,17 @@ export function handleSetupGet(req: Request, deps: SetupWizardDeps): Response {
|
|
|
1790
1672
|
}
|
|
1791
1673
|
const stored = getSetting(deps.db, "setup_expose_mode");
|
|
1792
1674
|
const exposeMode = isSetupExposeMode(stored) ? stored : undefined;
|
|
1793
|
-
//
|
|
1794
|
-
//
|
|
1795
|
-
//
|
|
1796
|
-
//
|
|
1797
|
-
// /admin/tokens
|
|
1798
|
-
|
|
1799
|
-
|
|
1675
|
+
// No minted-token read here anymore — the auto-mint was removed
|
|
1676
|
+
// 2026-06-23 (Austen's report). The done step always renders the
|
|
1677
|
+
// bare OAuth `claude mcp add` command (no Bearer header), which is
|
|
1678
|
+
// the correct UX for OAuth-default vaults. Headless clients mint a
|
|
1679
|
+
// scoped token at /admin/tokens themselves (the bare tile points
|
|
1680
|
+
// there). The defensive `deleteSetting` below clears any stale row
|
|
1681
|
+
// a pre-upgrade hub left behind, so it never leaks on first render
|
|
1682
|
+
// after upgrade.
|
|
1683
|
+
if (getSetting(deps.db, "setup_minted_token") !== undefined) {
|
|
1684
|
+
deleteSetting(deps.db, "setup_minted_token");
|
|
1685
|
+
}
|
|
1800
1686
|
// Prefer the LIVE vault name from services.json over the
|
|
1801
1687
|
// operator-typed value cached in hub_settings (smoke
|
|
1802
1688
|
// 2026-05-27, finding 2). The cached value is what the
|
|
@@ -1834,7 +1720,7 @@ export function handleSetupGet(req: Request, deps: SetupWizardDeps): Response {
|
|
|
1834
1720
|
installTiles,
|
|
1835
1721
|
};
|
|
1836
1722
|
if (exposeMode !== undefined) doneProps.exposeMode = exposeMode;
|
|
1837
|
-
|
|
1723
|
+
// No `doneProps.mintedToken` — see the OAuth-default note above.
|
|
1838
1724
|
// Substitute CSRF placeholder for the install-tile forms with
|
|
1839
1725
|
// the current CSRF token. Keeping the per-tile renderer pure
|
|
1840
1726
|
// means the substitution lives here (one rewrite per render).
|
|
@@ -2861,35 +2747,23 @@ export async function handleSetupExposePost(
|
|
|
2861
2747
|
console.log(
|
|
2862
2748
|
`[setup-wizard] opened first-client auto-approve window (60min) after expose-mode=${rawMode}`,
|
|
2863
2749
|
);
|
|
2864
|
-
// hub#272 Item A
|
|
2865
|
-
//
|
|
2866
|
-
// the MCP install command with a
|
|
2867
|
-
//
|
|
2868
|
-
//
|
|
2869
|
-
//
|
|
2870
|
-
//
|
|
2871
|
-
//
|
|
2872
|
-
//
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
scopeSet: "admin",
|
|
2878
|
-
});
|
|
2879
|
-
setSetting(deps.db, "setup_minted_token", minted.token);
|
|
2880
|
-
mintedTokenForJson = minted.token;
|
|
2881
|
-
console.log(
|
|
2882
|
-
`[setup-wizard] auto-minted operator token (jti=${minted.jti}, scope-set=admin) for done-screen MCP command`,
|
|
2883
|
-
);
|
|
2884
|
-
} catch (err) {
|
|
2885
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2886
|
-
console.warn(`[setup-wizard] failed to auto-mint operator token: ${msg}`);
|
|
2887
|
-
}
|
|
2750
|
+
// hub#272 Item A USED to auto-mint a broad `admin`-scope operator token
|
|
2751
|
+
// here + stash it (`setup_minted_token`) so the done screen could
|
|
2752
|
+
// pre-fill the MCP install command with a `--header "Authorization:
|
|
2753
|
+
// Bearer <token>"` flag. Removed 2026-06-23 (Austen's report): vault/
|
|
2754
|
+
// init is OAuth-default now (parachute-vault #491), so the bare
|
|
2755
|
+
// `claude mcp add` command is the correct UX — it triggers browser
|
|
2756
|
+
// OAuth on first use. Baking a full admin-scope bearer into a copy-
|
|
2757
|
+
// pasted command was both the wrong default and a privilege over-grant
|
|
2758
|
+
// (a single shoulder-surf / screencast leaked an admin token). The
|
|
2759
|
+
// done step now always renders the bare OAuth command; headless
|
|
2760
|
+
// clients that can't do the browser flow mint a scoped token at
|
|
2761
|
+
// /admin/tokens + append the header themselves (the bare tile points
|
|
2762
|
+
// there). No token is minted or stored by default.
|
|
2888
2763
|
if (form.isJson) {
|
|
2889
2764
|
return jsonOkResponse({
|
|
2890
2765
|
step: "done",
|
|
2891
2766
|
message: "expose mode set",
|
|
2892
|
-
...(mintedTokenForJson ? { minted_token: mintedTokenForJson } : {}),
|
|
2893
2767
|
});
|
|
2894
2768
|
}
|
|
2895
2769
|
return redirect("/admin/setup?just_finished=1");
|
|
@@ -3383,64 +3257,6 @@ const STYLES = `
|
|
|
3383
3257
|
.btn-secondary:hover {
|
|
3384
3258
|
background: ${PALETTE.accentSoft};
|
|
3385
3259
|
}
|
|
3386
|
-
/* Copy + Show buttons ride the right edge of the MCP command pre.
|
|
3387
|
-
Compact vertical sizing so they don't dwarf the snippet on narrow
|
|
3388
|
-
widths; full text wrap on the pre keeps the snippet readable
|
|
3389
|
-
behind them. The Show button toggles the visible mask on the
|
|
3390
|
-
auto-minted Bearer token (rc.11 — discoverable
|
|
3391
|
-
but not shoulder-surf-able). Both buttons share a small flex
|
|
3392
|
-
container so they stack predictably on the wrap; layout-wise we
|
|
3393
|
-
keep the right-edge padding on .mcp-cmd-wrap pre so the buttons
|
|
3394
|
-
never overlap the command text. */
|
|
3395
|
-
.mcp-cmd-wrap {
|
|
3396
|
-
position: relative;
|
|
3397
|
-
margin: 0.5rem 0;
|
|
3398
|
-
}
|
|
3399
|
-
.mcp-cmd-wrap pre {
|
|
3400
|
-
background: ${PALETTE.bg};
|
|
3401
|
-
border: 1px solid ${PALETTE.borderLight};
|
|
3402
|
-
border-radius: 6px;
|
|
3403
|
-
padding: 0.5rem 8.5rem 0.5rem 0.75rem;
|
|
3404
|
-
overflow-x: auto;
|
|
3405
|
-
font-size: 0.82rem;
|
|
3406
|
-
margin: 0;
|
|
3407
|
-
white-space: pre-wrap;
|
|
3408
|
-
word-break: break-all;
|
|
3409
|
-
}
|
|
3410
|
-
.mcp-cmd-actions {
|
|
3411
|
-
position: absolute;
|
|
3412
|
-
top: 0.35rem;
|
|
3413
|
-
right: 0.35rem;
|
|
3414
|
-
display: flex;
|
|
3415
|
-
gap: 0.3rem;
|
|
3416
|
-
}
|
|
3417
|
-
.btn-copy, .btn-mcp-aux {
|
|
3418
|
-
padding: 0.25rem 0.6rem;
|
|
3419
|
-
font-size: 0.78rem;
|
|
3420
|
-
min-height: auto;
|
|
3421
|
-
background: ${PALETTE.cardBg};
|
|
3422
|
-
color: ${PALETTE.fg};
|
|
3423
|
-
border: 1px solid ${PALETTE.border};
|
|
3424
|
-
border-radius: 4px;
|
|
3425
|
-
cursor: pointer;
|
|
3426
|
-
font: inherit;
|
|
3427
|
-
font-size: 0.78rem;
|
|
3428
|
-
}
|
|
3429
|
-
.btn-copy:hover, .btn-mcp-aux:hover {
|
|
3430
|
-
border-color: ${PALETTE.accent};
|
|
3431
|
-
color: ${PALETTE.accent};
|
|
3432
|
-
}
|
|
3433
|
-
.mcp-cmd-wrap[data-state="revealed"] pre {
|
|
3434
|
-
/* Subtle visual cue that the token is currently visible — a warm
|
|
3435
|
-
border so the operator notices on a screencast even at low
|
|
3436
|
-
resolution. */
|
|
3437
|
-
border-color: #d4a017;
|
|
3438
|
-
background: rgba(212, 160, 23, 0.04);
|
|
3439
|
-
}
|
|
3440
|
-
.mcp-cmd-wrap[data-state="revealed"] .btn-mcp-aux {
|
|
3441
|
-
border-color: #d4a017;
|
|
3442
|
-
color: #6b4a00;
|
|
3443
|
-
}
|
|
3444
3260
|
/* Install-tile section (hub#272 Item B). Lives above the .done-grid;
|
|
3445
3261
|
primary "what's next?" surface. Tiles render in a responsive grid
|
|
3446
3262
|
that collapses to one column on narrow viewports. */
|