@bongos/core 1.20.44 → 1.20.46
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/.bongos-core.json +118 -73
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +8 -0
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/api/openapi.json +138 -3
- package/docs/api-reference.md +9 -2
- package/docs/copy-inventory.md +65 -29
- package/docs/copy-registry.json +418 -33
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +1 -0
- package/docs/onboarding/slash-commands.md +12 -56
- package/docs/page-inventory.json +35 -4
- package/docs/page-readings.json +994 -959
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands-lib.js +142 -0
- package/modules/hall-ui/public/commands.css +98 -0
- package/modules/hall-ui/public/commands.html +65 -0
- package/modules/hall-ui/public/commands.js +52 -0
- package/modules/hall-ui/public/commands.states.json +14 -0
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.css +45 -0
- package/modules/hall-ui/public/modules.html +24 -2
- package/modules/hall-ui/public/modules.js +94 -2
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +3 -0
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/catalog.md +2 -0
- package/modules/hall-ui/records/commands.md +11 -0
- package/modules/specialities/skills.js +6 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/module-artifact.js +21 -1
- package/scripts/gds/module.js +15 -1
- package/scripts/hall-preview/server.js +3 -0
- package/src/bongos/module-store.js +30 -1
- package/src/bongos/routes/cli-commands.js +49 -0
- package/src/bongos/routes/modules.js +91 -1
- package/src/bongos/routes.js +3 -0
- package/src/bongos/serve-internal.js +4 -1
- package/src/module-api.js +1 -1
- package/tests/hall_catalog_world.mjs +1 -1
- package/tests/hall_commands.mjs +162 -0
- package/tests/hall_nav.mjs +3 -1
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/module_store_howto.mjs +244 -0
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -97,6 +97,15 @@
|
|
|
97
97
|
return `<span class="ov-fact">upkeep <b>${escapeHtml(status)}</b></span>`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// A module installed from the store links the how-to its version shipped with
|
|
101
|
+
// (task 1004366). The server sets store_howto only for a published key@version,
|
|
102
|
+
// and only to a /builders/modules?howto= path, so anything else is dropped.
|
|
103
|
+
function howtoFact(m) {
|
|
104
|
+
const href = m.store_howto;
|
|
105
|
+
if (typeof href !== 'string' || !href.startsWith('/builders/modules?howto=')) return '';
|
|
106
|
+
return `<a class="ov-fact mod-howto" href="${escapeHtml(href)}">how-to</a>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
100
109
|
// The lines the row's details disclosure opens onto: the facts the payload
|
|
101
110
|
// carries that the card never rendered — what the module contributes, the
|
|
102
111
|
// core it needs, who maintains it and what it hands off to.
|
|
@@ -143,7 +152,7 @@
|
|
|
143
152
|
<div class="ov-row__main">
|
|
144
153
|
<span class="ov-row__title">${escapeHtml(m.title || m.key)}<span class="ov-mono">${escapeHtml(m.key)}</span></span>
|
|
145
154
|
${desc ? `<p class="ov-row__sub mod-row__desc">${escapeHtml(desc)}</p>` : ''}
|
|
146
|
-
<div class="ov-row__facts">${provenancePill(m)}${attributionFacts(m)}${upkeepFact(m)}</div>
|
|
155
|
+
<div class="ov-row__facts">${provenancePill(m)}${attributionFacts(m)}${upkeepFact(m)}${howtoFact(m)}</div>
|
|
147
156
|
${lines ? `<details class="mod-more"${openKeys.has(m.key) ? ' open' : ''}><summary aria-label="Details for ${escapeHtml(m.title || m.key)}"></summary>${lines}</details>` : ''}
|
|
148
157
|
</div>
|
|
149
158
|
<div class="ov-row__right mod-row__right">${rightHtml(m)}</div>
|
|
@@ -320,5 +329,88 @@
|
|
|
320
329
|
}
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
|
|
332
|
+
// ---- one store version's how-to (task 1004366, ADR 0347) ----
|
|
333
|
+
//
|
|
334
|
+
// ?howto=<key>&version=<X.Y.Z> opens the HOWTO.md that version shipped with in
|
|
335
|
+
// place of the catalog. A query, not a #/ route: the hall's hash namespace is
|
|
336
|
+
// frozen to the idea and blocker overlays.
|
|
337
|
+
|
|
338
|
+
// The author wrote this file, so a link in it may point anywhere. Only an absolute
|
|
339
|
+
// http(s) link, a mailto: or an in-page #anchor becomes an <a>; a relative path
|
|
340
|
+
// means nothing outside the author's repo, so it renders as its text.
|
|
341
|
+
function howtoLinkHref(href) {
|
|
342
|
+
return /^(https?:\/\/|mailto:|#)/i.test(String(href || '')) ? href : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// The same rule the manifest schema enforces on howto.artifactUrl (an https link on
|
|
346
|
+
// claude.ai), checked again here because this page writes it into an href.
|
|
347
|
+
function isClaudeArtifactUrl(u) {
|
|
348
|
+
try {
|
|
349
|
+
const url = new URL(String(u));
|
|
350
|
+
return url.protocol === 'https:' && (url.hostname === 'claude.ai' || url.hostname.endsWith('.claude.ai'));
|
|
351
|
+
} catch (_) { return false; }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function howtoFail(message) {
|
|
355
|
+
const doc = $('#howto-doc');
|
|
356
|
+
if (doc) { doc.removeAttribute('aria-busy'); doc.innerHTML = kit.emptyStateHtml(message); }
|
|
357
|
+
const print = $('#howto-print');
|
|
358
|
+
if (print) print.hidden = true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function loadHowto(key, version) {
|
|
362
|
+
const catalog = $('#core-scroll');
|
|
363
|
+
if (catalog) catalog.hidden = true;
|
|
364
|
+
const sub = $('#modules-sub');
|
|
365
|
+
if (sub) sub.hidden = true;
|
|
366
|
+
// A how-to is a document, so the page opts into the reading room (room.css
|
|
367
|
+
// scopes its column width and article sizes to body.room).
|
|
368
|
+
document.body.classList.add('room');
|
|
369
|
+
const view = $('#howto-view');
|
|
370
|
+
if (view) view.hidden = false;
|
|
371
|
+
const print = $('#howto-print');
|
|
372
|
+
if (print) print.addEventListener('click', () => window.print());
|
|
373
|
+
|
|
374
|
+
const res = await api.request('GET', `${API}/store/modules/${encodeURIComponent(key)}/versions/${encodeURIComponent(version)}/howto`);
|
|
375
|
+
if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); return; }
|
|
376
|
+
const body = res.data || {};
|
|
377
|
+
if (!res.ok) {
|
|
378
|
+
const err = body.error || {};
|
|
379
|
+
howtoFail((typeof err === 'object' && err.message) || body.message || `Could not load the how-to (${res.status}).`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const title = String(body.title || key);
|
|
383
|
+
document.title = `${title} ${body.version} — how-to`;
|
|
384
|
+
const h = $('#howto-h');
|
|
385
|
+
if (h) h.textContent = `${title} — how to use it`;
|
|
386
|
+
const meta = $('#howto-meta');
|
|
387
|
+
if (meta) {
|
|
388
|
+
meta.innerHTML = `<span class="ov-mono">${escapeHtml(key)}</span> · version <span class="ov-mono">${escapeHtml(String(body.version))}</span>`
|
|
389
|
+
+ (body.delisted ? ' · <span class="fact-pill fact-pill--warn">delisted</span>' : '');
|
|
390
|
+
}
|
|
391
|
+
const extra = $('#howto-extra');
|
|
392
|
+
if (extra && body.artifact_url && isClaudeArtifactUrl(body.artifact_url)) {
|
|
393
|
+
extra.href = body.artifact_url;
|
|
394
|
+
extra.hidden = false;
|
|
395
|
+
}
|
|
396
|
+
const doc = $('#howto-doc');
|
|
397
|
+
if (doc) {
|
|
398
|
+
// HTML comments are the scaffold's writing prompts (ADR 0347 D2); they are
|
|
399
|
+
// not part of the how-to, so they are removed rather than shown as text.
|
|
400
|
+
const md = String(body.markdown || '').replace(/<!--[\s\S]*?-->/g, '');
|
|
401
|
+
doc.innerHTML = window.OTBMdReader.parseMarkdown(md, howtoLinkHref).html;
|
|
402
|
+
doc.removeAttribute('aria-busy');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const params = new URLSearchParams(window.location.search);
|
|
407
|
+
const howtoKey = params.get('howto');
|
|
408
|
+
if (howtoKey) {
|
|
409
|
+
loadHowto(howtoKey, params.get('version') || '').catch((err) => {
|
|
410
|
+
console.error('[modules] how-to failed to load', err);
|
|
411
|
+
howtoFail('Could not load the how-to. Return to the modules list and try again.');
|
|
412
|
+
});
|
|
413
|
+
} else {
|
|
414
|
+
load();
|
|
415
|
+
}
|
|
324
416
|
})();
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
|
|
188
188
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
189
189
|
chrome exists before the page script runs. -->
|
|
190
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
190
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
191
191
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
192
192
|
<script src="/builders/palette.js" defer></script>
|
|
193
193
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
70
70
|
chrome exists before the page script runs. -->
|
|
71
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
71
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
72
72
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
73
73
|
<script src="/builders/palette.js" defer></script>
|
|
74
74
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|
|
@@ -400,7 +400,7 @@
|
|
|
400
400
|
|
|
401
401
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
402
402
|
chrome exists before the page scripts run. -->
|
|
403
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
403
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
404
404
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
405
405
|
<script src="/builders/palette.js" defer></script>
|
|
406
406
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -303,6 +303,9 @@
|
|
|
303
303
|
// stays right whatever `branding.currency.label` is set to. Page is public
|
|
304
304
|
// like /ranks — 'signed' is the same cosmetic "show it once you're in".
|
|
305
305
|
{ id: 'economy', label: 'Economy', icon: 'docs', href: '/drachmae', gate: 'signed' },
|
|
306
|
+
// task 1004473: every skill and every bongos CLI verb, read live. Its page and
|
|
307
|
+
// both reads behind it are sign-in gated, so 'signed' is the real floor here.
|
|
308
|
+
{ id: 'commands', label: 'Commands', icon: 'docs', href: '/commands', gate: 'signed' },
|
|
306
309
|
{ id: 'diagrams', label: 'Diagrams', icon: 'docs', href: '/diagrams', gate: 'signed' },
|
|
307
310
|
{ id: 'atlas', label: 'Repo Atlas', icon: 'docs', href: '/atlas', gate: 'member' },
|
|
308
311
|
{ id: 'modules', label: 'Modules', icon: 'docs', href: '/modules', gate: 'member' },
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
179
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
180
180
|
<script src="/builders/palette.js" defer></script>
|
|
181
181
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
182
182
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
141
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
|
|
141
141
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
142
142
|
|
|
143
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
143
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
146
146
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-09-30-commands"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -8,3 +8,5 @@
|
|
|
8
8
|
- **The island index is the map's keyboard and phone path.** The scene is a hover surface: SVG `<g>`s with mouse listeners, **no focusable element**, and at 390px thirty islands in 360px with nothing under them. `drawIndex` renders every node of the current view as a dense `.ov-row` under a `kit-collapse` (`#ab-index`) whose title is a disclosure button (`aria-expanded`/`aria-controls`) laying the SAME `detailHTML` inline; hovering a row lights the island, and **the index opens itself under 640px (`matchMedia`), where the list is the read.**
|
|
9
9
|
|
|
10
10
|
Also gone, and the one rule in that list: `.ab-caption` takes the lede's `--measure`, never a fresh `90ch`. **Gotcha:** `gen-atlas.js` run from the MAIN checkout walks `.claude/worktrees/*` and emits thousands of context nodes — run it from a worktree, or copy a worktree's `atlas.json` over, before previewing the atlas there.
|
|
11
|
+
|
|
12
|
+
**A store version's how-to is a view of the Modules page, not a new page (task 1004366).** `/builders/modules?howto=<key>&version=<X.Y.Z>` hides the catalog and shows that version's `HOWTO.md` in the reading room's article card: the page adds `body.room` so `room.css`'s sizes apply, and the markdown goes through `md-reader.js` (author-written, so every byte is escaped; only absolute http(s), mailto and #anchor links become links). A query, not a `#/` route — the hash namespace is frozen to the idea and blocker overlays. "Save as PDF" is `window.print()` with an `@media print` block in `modules.css` that keeps only the document and prints in the system ink (`CanvasText`), because a literal colour is the token tier's. Harness fixture: `store__modules__howto.json`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# The Commands page, at /commands (task 1004473)
|
|
2
|
+
|
|
3
|
+
**Every skill and every `bongos` CLI verb on one Docs page, read from the live sources so it cannot drift.** The owner asked for it once task 1004471 hid the rarely used skills from Claude's menu: a skill nobody can see in the menu needs somewhere to be discovered. Five things it settled:
|
|
4
|
+
|
|
5
|
+
- **Two reads, no list in the page.** The skills come from `GET /specialities/installed-skills` (the specialities module's `installedSkills()`: core + instance + ENABLED-module skills, so a default-off module such as `pixel-art` or `design-styles` shows only where it is on); the verbs from the new core `GET /cli/commands`, which hands over `bin/bongos.js` `VERBS` grouped by its `GROUPS` — the order `bongos --help` prints. Core may not import a module, which is why the page asks for the two halves separately; each fails on its own, so a missing skills list does not take the CLI table down.
|
|
6
|
+
- **The only hand-written thing is the skill GROUP.** `commands-lib.js` `SKILL_GROUPS` places a skill by name or by the module it came from; a skill no group names lands under "Everything else", so a new skill always gets a row. Rows follow the order the group names them (the daily loop reads start → claim → ship), then A–Z.
|
|
7
|
+
- **"Shown to Claude" vs "Type it yourself" is `disable-model-invocation`.** `skills.js` now carries it (`typedOnly`), and `explainSkill()` reports `listed`. Only the literal `true` hides a skill, which is how Claude Code reads the key.
|
|
8
|
+
- **Sign-in is the whole gate.** The page joins `SIGNEDIN_BUILDERS_PAGE_RE`, its nav item is `signed` in Docs, and both reads are `requireBuilder`. Reading the list grants nothing (ADR 0310 §1): every command still meets the server's own rank checks when it runs.
|
|
9
|
+
- **At 760px and below each row is a small card.** Five columns cannot fit a phone and a sideways-scrolling table hides the costs, so `commands.css` stacks the cells under their column names (`data-label`). The CLI tables are `table-layout: fixed` with a 220px command column so the help lines align across the three groups.
|
|
10
|
+
|
|
11
|
+
Tests: `tests/hall_commands.mjs` (every SKILL.md and every VERBS entry exactly once, against the real tree; the typed-only marker per skill; the default-off modules absent; the route's gate). `docs/onboarding/slash-commands.md` now points here for the project's own list instead of keeping a second, hand-typed one.
|
|
@@ -92,6 +92,10 @@ function collect(out, dir, source) {
|
|
|
92
92
|
const entry = { name, description: typeof fm.description === 'string' ? fm.description : '', source };
|
|
93
93
|
for (const [key, field] of Object.entries(PEOPLE_KEYS)) if (typeof fm[key] === 'string' && fm[key]) entry[field] = fm[key];
|
|
94
94
|
if (Array.isArray(fm.requires) && fm.requires.length) entry.requires = fm.requires;
|
|
95
|
+
// `disable-model-invocation: true` (task 1004471) keeps a skill out of the
|
|
96
|
+
// menu Claude reads; a person still runs it by typing /name. Only the literal
|
|
97
|
+
// `true` hides it, which is how Claude Code reads the key (task 1004473).
|
|
98
|
+
if (fm['disable-model-invocation'] === 'true') entry.typedOnly = true;
|
|
95
99
|
out.set(name, entry);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
@@ -150,6 +154,8 @@ function explainSkill(entry) {
|
|
|
150
154
|
commands,
|
|
151
155
|
needs: Array.isArray(e.requires) ? e.requires : [],
|
|
152
156
|
rank,
|
|
157
|
+
// Shown to Claude (it can start the skill from plain English) vs typed-only.
|
|
158
|
+
listed: e.typedOnly !== true,
|
|
153
159
|
derived: { does: !e.plain, reachFor: !e.reachFor, cost: !e.cost },
|
|
154
160
|
};
|
|
155
161
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.46",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.20.
|
|
9
|
+
"version": "1.20.46",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.46",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -8377,5 +8377,17 @@
|
|
|
8377
8377
|
"id": "1004365",
|
|
8378
8378
|
"text": "Every store module now comes with a how-to page, and the store refuses to publish one without it."
|
|
8379
8379
|
}
|
|
8380
|
+
],
|
|
8381
|
+
"1.20.45": [
|
|
8382
|
+
{
|
|
8383
|
+
"id": "1004473",
|
|
8384
|
+
"text": "The Builders Hall now has a Commands page listing every slash command and every terminal command, with what each does, when to use it and what it costs, including the ones hidden from Claude's menu."
|
|
8385
|
+
}
|
|
8386
|
+
],
|
|
8387
|
+
"1.20.46": [
|
|
8388
|
+
{
|
|
8389
|
+
"id": "1004366",
|
|
8390
|
+
"text": "Every store module version now has a readable how-to page in the hall, with a Save as PDF button and an optional link to a Claude page."
|
|
8391
|
+
}
|
|
8380
8392
|
]
|
|
8381
8393
|
}
|
|
@@ -266,4 +266,24 @@ async function verifyModuleArtifact(tgz, { key, maxBytes = MAX_TARBALL_BYTES, in
|
|
|
266
266
|
return out;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
// The how-to a published version shipped with (task 1004366): read from the version's
|
|
270
|
+
// own tarball, after the same hash check install runs, so the page always shows the
|
|
271
|
+
// exact file that version was published with. A version from before the gate may have
|
|
272
|
+
// none (text null). Text only — nothing in the module is run or rendered here.
|
|
273
|
+
// Returns { ok, text, artifactUrl, title, version, tarballSha256 } or verifyModuleArtifact's failure.
|
|
274
|
+
async function readHowto(tgz, { key }) {
|
|
275
|
+
const v = await verifyModuleArtifact(tgz, { key, includeFiles: true });
|
|
276
|
+
if (!v.ok) return v;
|
|
277
|
+
const file = v.files.find((f) => f.path === HOWTO_FILE);
|
|
278
|
+
const extra = v.moduleJson.howto || {};
|
|
279
|
+
return {
|
|
280
|
+
ok: true,
|
|
281
|
+
text: file ? file.buf.toString('utf8') : null,
|
|
282
|
+
artifactUrl: extra.artifactUrl || null,
|
|
283
|
+
title: v.moduleJson.title || key,
|
|
284
|
+
version: v.version,
|
|
285
|
+
tarballSha256: v.tarballSha256,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = { MAX_TARBALL_BYTES, HOWTO_FILE, HOWTO_SECTIONS, packModule, verifyModuleArtifact, deniedFiles, checkHowto, readHowto };
|
package/scripts/gds/module.js
CHANGED
|
@@ -930,7 +930,7 @@ function storeRequest() {
|
|
|
930
930
|
const { readSessionToken, dispatchedFetch, noKeepAliveDispatcher, CLI_USER_AGENT } = require('./cli-lib');
|
|
931
931
|
const session = readSessionToken();
|
|
932
932
|
if (!session) throw new Error('no Bongos session — run `bongos setup` first');
|
|
933
|
-
|
|
933
|
+
const call = async (method, urlPath, { json, binary = false } = {}) => {
|
|
934
934
|
const res = await dispatchedFetch(`${session.apiBase}/api/bongos${urlPath}`, {
|
|
935
935
|
method,
|
|
936
936
|
headers: {
|
|
@@ -946,6 +946,18 @@ function storeRequest() {
|
|
|
946
946
|
try { data = text ? JSON.parse(text) : null; } catch { data = { raw: text }; }
|
|
947
947
|
return { ok: res.ok, status: res.status, data };
|
|
948
948
|
};
|
|
949
|
+
// Where the store lives, so install can print a full link to the how-to page.
|
|
950
|
+
call.apiBase = session.apiBase;
|
|
951
|
+
return call;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// The hall page that shows a version's HOWTO.md (task 1004366) — the Modules tab,
|
|
955
|
+
// opened on one version. null when the version shipped without one (published
|
|
956
|
+
// before ADR 0347's gate), so install never prints a link that would 404.
|
|
957
|
+
function howtoLink(key, version, files, apiBase) {
|
|
958
|
+
if (!files.some((f) => f.path === 'HOWTO.md')) return null;
|
|
959
|
+
const p = `/builders/modules?howto=${encodeURIComponent(key)}&version=${encodeURIComponent(version)}`;
|
|
960
|
+
return apiBase ? `${String(apiBase).replace(/\/+$/, '')}${p}` : p;
|
|
949
961
|
}
|
|
950
962
|
|
|
951
963
|
function storeError(res) {
|
|
@@ -1093,6 +1105,8 @@ async function cmdInstall(args, {
|
|
|
1093
1105
|
|
|
1094
1106
|
if (!(await recordPlaced(key, got.described.version, { call, errlog, verb: 'install' }))) return 1;
|
|
1095
1107
|
log(`\n✓ Installed "${key}" ${got.described.version}. It is not switched on: enable it from the hall's Modules tab when you want it.`);
|
|
1108
|
+
const howto = howtoLink(key, got.described.version, got.verified.files, call.apiBase);
|
|
1109
|
+
if (howto) log(` How to use it: ${howto}`);
|
|
1096
1110
|
return 0;
|
|
1097
1111
|
}
|
|
1098
1112
|
|
|
@@ -144,6 +144,9 @@ const FIXTURE_FALLBACKS = [
|
|
|
144
144
|
// the Approval queue's renders (task 1004323): a round's named visuals, one
|
|
145
145
|
// fixture for any task id, as the editor read above serves any page.
|
|
146
146
|
[/^tasks\/\d+\/visuals$/, 'tasks__visuals'],
|
|
147
|
+
// a store version's how-to page (task 1004366): one reading serves any key and
|
|
148
|
+
// version, since the Modules tab's how-to view shows one document.
|
|
149
|
+
[/^store\/modules\/[a-z0-9-]+\/versions\/[^/]+\/howto$/, 'store__modules__howto'],
|
|
147
150
|
];
|
|
148
151
|
|
|
149
152
|
// CANNED WRITES (task 1004321). The tweak editor's whole flow is writes (the
|
|
@@ -179,6 +179,35 @@ async function resolveAcquirable(key, { version = null, db } = {}) {
|
|
|
179
179
|
return { ok: true, module: mod, version: row, price: price || null, free: isFreePrice(price) };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
// Resolve ONE exact version for reading its how-to (task 1004366). Unlike
|
|
183
|
+
// resolveAcquirable this does not refuse a delisted module: ADR 0338 D2 keeps a
|
|
184
|
+
// delisted module readable for the people who already hold it, so it hands the
|
|
185
|
+
// caller the module's status and the route decides who may read on.
|
|
186
|
+
// Returns { ok: true, module, version } or { ok: false, status, code, message }.
|
|
187
|
+
async function resolveReadable(key, version, { db } = {}) {
|
|
188
|
+
const pool = db || require('./pool').pool;
|
|
189
|
+
const refuse = (status, code, message) => ({ ok: false, status, code, message });
|
|
190
|
+
const { rows: [mod] } = await pool.query(
|
|
191
|
+
'SELECT module_key, title, author_id, status FROM store_modules WHERE module_key = $1', [key]);
|
|
192
|
+
if (!mod) return refuse(404, 'module_not_found', `The store has no module "${key}".`);
|
|
193
|
+
const { rows: [row] } = await pool.query(
|
|
194
|
+
`SELECT id, module_key, version, tarball_sha256, artifact_path, published_at
|
|
195
|
+
FROM store_module_versions WHERE module_key = $1 AND version = $2`, [key, version]);
|
|
196
|
+
if (!row) return refuse(404, 'version_not_found', `"${key}" has no published version ${version}.`);
|
|
197
|
+
return { ok: true, module: mod, version: row };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Which of these (key, version) pairs are published store versions, as a Set of
|
|
201
|
+
// "key@version" — so the hall's Modules tab can link an installed module's how-to
|
|
202
|
+
// (task 1004366). One query for the whole list.
|
|
203
|
+
async function publishedVersionSet(keys, { db } = {}) {
|
|
204
|
+
if (!keys.length) return new Set();
|
|
205
|
+
const pool = db || require('./pool').pool;
|
|
206
|
+
const { rows } = await pool.query(
|
|
207
|
+
'SELECT module_key, version FROM store_module_versions WHERE module_key = ANY($1)', [keys]);
|
|
208
|
+
return new Set(rows.map((r) => `${r.module_key}@${r.version}`));
|
|
209
|
+
}
|
|
210
|
+
|
|
182
211
|
// The tarball on disk for a version row. artifact_path is relative to the store dir
|
|
183
212
|
// (relativeArtifactPath), and artifactPath() re-validates key + version, so a row
|
|
184
213
|
// can never point a read outside the store.
|
|
@@ -192,5 +221,5 @@ function versionArtifactFile(row, { dir = storeDir() } = {}) {
|
|
|
192
221
|
|
|
193
222
|
module.exports = {
|
|
194
223
|
stageArtifact, commitArtifact, removeArtifact, relativeArtifactPath, publishVersion,
|
|
195
|
-
isFreePrice, resolveAcquirable, versionArtifactFile,
|
|
224
|
+
isFreePrice, resolveAcquirable, resolveReadable, publishedVersionSet, versionArtifactFile,
|
|
196
225
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/bongos/routes/cli-commands.js — every `bongos <verb>` the CLI answers,
|
|
2
|
+
// for the hall's Commands page (task 1004473).
|
|
3
|
+
//
|
|
4
|
+
// WHY A ROUTE. The page is the one place a builder sees every skill AND every
|
|
5
|
+
// CLI command, and both halves must be read from their live source so the list
|
|
6
|
+
// cannot drift from what the tools actually do. The verb table is bin/bongos.js
|
|
7
|
+
// `VERBS` (grouped by its `GROUPS`, the order `bongos --help` prints); the
|
|
8
|
+
// browser cannot read that file, so this hands it over as data. The skills half
|
|
9
|
+
// comes from the specialities module's GET /specialities/installed-skills —
|
|
10
|
+
// core must not import a module, so the page asks for the two halves separately.
|
|
11
|
+
//
|
|
12
|
+
// Reading the list grants nothing (ADR 0310 §1): every verb still runs against
|
|
13
|
+
// the server's own rank checks. Sign-in is the floor only because the page it
|
|
14
|
+
// feeds sits on the hall's signed-in tier.
|
|
15
|
+
|
|
16
|
+
const express = require('express');
|
|
17
|
+
const auth = require('../auth');
|
|
18
|
+
// Side-effect-free: bin/bongos.js only dispatches when it is the main module.
|
|
19
|
+
const cli = require('../../../bin/bongos.js');
|
|
20
|
+
|
|
21
|
+
/** The CLI's verbs as `bongos --help` groups them: [{ key, label, commands: [{ verb, summary }] }]. */
|
|
22
|
+
function cliCommands(source = cli) {
|
|
23
|
+
const verbs = Object.entries(source.VERBS || {});
|
|
24
|
+
const groups = (source.GROUPS || []).map(([key, label]) => ({
|
|
25
|
+
key,
|
|
26
|
+
label,
|
|
27
|
+
commands: verbs.filter(([, def]) => def.group === key).map(([verb, def]) => ({ verb, summary: def.summary })),
|
|
28
|
+
}));
|
|
29
|
+
// A verb whose group `GROUPS` does not name still exists, so it still gets a row.
|
|
30
|
+
const known = new Set(groups.map((g) => g.key));
|
|
31
|
+
const stray = verbs.filter(([, def]) => !known.has(def.group));
|
|
32
|
+
if (stray.length) groups.push({ key: 'other', label: 'Other', commands: stray.map(([verb, def]) => ({ verb, summary: def.summary })) });
|
|
33
|
+
return groups.filter((g) => g.commands.length);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildCliCommandsRouter() {
|
|
37
|
+
const router = express.Router();
|
|
38
|
+
|
|
39
|
+
// GET /cli/commands — the bongos CLI's verbs, grouped like `bongos --help`.
|
|
40
|
+
// rank: any builder — read-only reference; the hall's Commands page is sign-in gated.
|
|
41
|
+
router.get('/cli/commands', auth.requireBuilder, (_req, res) => {
|
|
42
|
+
res.json({ version: cli.VERSION, groups: cliCommands() });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return router;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = buildCliCommandsRouter;
|
|
49
|
+
module.exports.cliCommands = cliCommands;
|