@bongos/core 1.19.576 → 1.19.578
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 +37 -17
- package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +140 -0
- package/docs/adr/README.md +1 -0
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/public-landing/public/projects.html +94 -59
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/box-connect-lib.js +1 -1
- package/scripts/gds/build-cli-package.js +517 -0
- package/src/module-api.js +1 -1
- package/tests/cli_package.mjs +217 -0
- package/tests/manage_manifest_shared_read.mjs +205 -0
- package/tests/projects_hub.mjs +31 -4
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/build-cli-package.js — generate the public, client-only `@cloudbongos/cli`
|
|
3
|
+
// npm package from this repo (task 1003679, goal 1000054).
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS EXISTS
|
|
6
|
+
// The core ships as `@bongos/core`, which is PRIVATE. That makes the front door unusable for
|
|
7
|
+
// anyone without a credential: a newcomer cannot install anything, so "take on a task" has no
|
|
8
|
+
// terminal path at all and the web hall is the only way in. This script cuts the CLI *surface*
|
|
9
|
+
// out of the core and emits a small, public, server-free package, so `npx @cloudbongos/cli
|
|
10
|
+
// login <instance>` works from a bare laptop.
|
|
11
|
+
//
|
|
12
|
+
// WHAT IT IS NOT
|
|
13
|
+
// It does NOT publish the core. No server, no routes, no auth middleware, no pool/db, no
|
|
14
|
+
// provisioning. Publishing the core would bypass the ADR 0099 redaction pipeline; this package
|
|
15
|
+
// carries a file list narrow enough to actually audit, and REDACTION_PATTERNS below fails the
|
|
16
|
+
// build if instance identity leaks into it.
|
|
17
|
+
//
|
|
18
|
+
// WHY THE FILE LIST IS DECLARED, NOT COMPUTED
|
|
19
|
+
// A static require-closure is unusable here. `src/module-api.js` is the published module doorway
|
|
20
|
+
// (ADR 0083) and by design *names* every kernel capability, so any static walk that reaches a
|
|
21
|
+
// module file appears to reach the whole server — even though the getters are lazy and never
|
|
22
|
+
// resolve. The honest instrument is behaviour: FILES is declared, and tests/cli_package.mjs packs
|
|
23
|
+
// the tarball, installs it into an empty directory with no repo present, and RUNS every verb.
|
|
24
|
+
// If a real code path needs a file that isn't here, that test fails with MODULE_NOT_FOUND.
|
|
25
|
+
//
|
|
26
|
+
// Usage:
|
|
27
|
+
// node scripts/gds/build-cli-package.js [--out dist/cloudbongos-cli] [--version 0.1.0] [--quiet]
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const fs = require('node:fs');
|
|
32
|
+
const path = require('node:path');
|
|
33
|
+
|
|
34
|
+
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
35
|
+
|
|
36
|
+
// The package's OWN version — deliberately independent of the core's. The core moves many times
|
|
37
|
+
// a day (CI auto-patch, ADR 0161); the CLI's public contract should not.
|
|
38
|
+
const PACKAGE_VERSION = '0.1.0';
|
|
39
|
+
const PACKAGE_NAME = '@cloudbongos/cli';
|
|
40
|
+
|
|
41
|
+
// ── What the package carries ────────────────────────────────────────────────────────────────
|
|
42
|
+
// Every entry is repo-relative and copied verbatim. Grouped by why it's here, because the
|
|
43
|
+
// grouping is the audit: anything that doesn't fit a group below does not belong in a public
|
|
44
|
+
// client package.
|
|
45
|
+
const FILES = [
|
|
46
|
+
// The shared CLI plumbing every verb goes through: session file, HTTP, error shapes.
|
|
47
|
+
'scripts/gds/cli-lib.js',
|
|
48
|
+
'scripts/gds/api.js',
|
|
49
|
+
|
|
50
|
+
// Account + sign-in. login.js is the whole point — GitHub device flow, no paste, no credential.
|
|
51
|
+
'scripts/gds/login.js',
|
|
52
|
+
'scripts/gds/reauth.js',
|
|
53
|
+
'scripts/gds/setup.js',
|
|
54
|
+
'scripts/gds/onboarding-config.js', // lazy dep of setup.js
|
|
55
|
+
'scripts/gds/install-git-hooks.js', // lazy dep of setup.js
|
|
56
|
+
'scripts/gds/preflight.js',
|
|
57
|
+
|
|
58
|
+
// Read the board and move your own work along.
|
|
59
|
+
'scripts/gds/start.js',
|
|
60
|
+
'scripts/gds/context-pack.js', // start.js renders through this
|
|
61
|
+
'scripts/gds/status.js',
|
|
62
|
+
'scripts/gds/task.js',
|
|
63
|
+
'scripts/gds/recall.js',
|
|
64
|
+
'scripts/gds/cost.js',
|
|
65
|
+
'scripts/gds/release.js',
|
|
66
|
+
|
|
67
|
+
// Get onto a real environment without installing one: the cloud dev box.
|
|
68
|
+
'scripts/gds/shell.js',
|
|
69
|
+
'scripts/gds/code.js',
|
|
70
|
+
'scripts/gds/box-connect-lib.js',
|
|
71
|
+
|
|
72
|
+
// Module files the above touch. Each imports nothing from the kernel at load time.
|
|
73
|
+
'modules/lifecycle/task-classifier.js',
|
|
74
|
+
'modules/lifecycle/task-visuals.js',
|
|
75
|
+
'modules/economy/reward-policy.js',
|
|
76
|
+
|
|
77
|
+
// The only core files in the package: config/branding readers and one constant table.
|
|
78
|
+
// src/module-api.js is here because task-classifier.js reads branding through the doorway
|
|
79
|
+
// (ADR 0083 forbids a module importing src/branding directly). Its `branding` getter resolves
|
|
80
|
+
// against src/branding.js, which ships; the other getters point at server files that do not,
|
|
81
|
+
// and no client path touches them — the smoke test is what proves that.
|
|
82
|
+
'src/branding.js',
|
|
83
|
+
'src/instance-config.js',
|
|
84
|
+
'src/module-seams.js',
|
|
85
|
+
'src/module-api.js',
|
|
86
|
+
'src/bongos/api-prefix.js',
|
|
87
|
+
|
|
88
|
+
// The generated typed API client (ADR 0118), which cli-lib.js reaches by a dynamic
|
|
89
|
+
// `import()` of this exact path. Vendored as a single file rather than depended on: it is
|
|
90
|
+
// published as `@bongos/client`, which is PRIVATE, so a public package that declared it as a
|
|
91
|
+
// dependency would be uninstallable for everyone. index.mjs is self-contained and imports
|
|
92
|
+
// nothing; the sibling package.json is deliberately NOT shipped (it carries
|
|
93
|
+
// publishConfig.access=restricted and a nested manifest only confuses packing).
|
|
94
|
+
'clients/bongos-client/index.mjs',
|
|
95
|
+
|
|
96
|
+
// Neutral branding defaults, so the CLI has sane strings with no instance checkout present.
|
|
97
|
+
'config/branding.neutral.json',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
// Paths that must NEVER appear in the generated tree, whatever FILES says. A belt-and-braces
|
|
101
|
+
// assertion against a future edit quietly widening the package back into the server.
|
|
102
|
+
const FORBIDDEN_PREFIXES = [
|
|
103
|
+
'src/bongos/routes/',
|
|
104
|
+
'src/server',
|
|
105
|
+
'migrations/',
|
|
106
|
+
'infra/',
|
|
107
|
+
'public/',
|
|
108
|
+
'modules/hall-ui/',
|
|
109
|
+
'.github/',
|
|
110
|
+
'devbox-app/',
|
|
111
|
+
];
|
|
112
|
+
const FORBIDDEN_BASENAMES = [
|
|
113
|
+
'pool.js', 'auth.js', 'db.js', 'server.js', 'serve-internal.js',
|
|
114
|
+
'logger.js', 'project-door.js', 'project-settings.js',
|
|
115
|
+
];
|
|
116
|
+
// src/bongos/ is the kernel. Exactly one file from it is allowed, and it imports nothing.
|
|
117
|
+
const ALLOWED_SRC_BONGOS = new Set(['src/bongos/api-prefix.js']);
|
|
118
|
+
|
|
119
|
+
// ── The verbs the package advertises ────────────────────────────────────────────────────────
|
|
120
|
+
// `script` is resolved inside the package. Keep in lockstep with FILES.
|
|
121
|
+
const VERBS = {
|
|
122
|
+
login: { script: 'login.js', group: 'account', summary: 'Sign in to an instance (bongos login <url> — browser click, no paste)' },
|
|
123
|
+
reauth: { script: 'reauth.js', group: 'account', summary: 'Refresh your session' },
|
|
124
|
+
setup: { script: 'setup.js', group: 'account', summary: 'Finish builder setup on this instance (disciplines, consent)' },
|
|
125
|
+
box: { script: 'api.js', prefixArgs: ['POST', '/api/gds/box/ensure'], group: 'account', summary: 'Turn on / wake your cloud dev box' },
|
|
126
|
+
shell: { script: 'shell.js', group: 'account', summary: 'Open a terminal on your dev box' },
|
|
127
|
+
code: { script: 'code.js', group: 'account', summary: 'Open VS Code on your dev box over Remote-SSH' },
|
|
128
|
+
|
|
129
|
+
start: { script: 'start.js', group: 'work', summary: 'List tasks you can claim right now' },
|
|
130
|
+
status: { script: 'status.js', group: 'work', summary: 'Version / criterion progress (bongos status C8)', selfHelp: true },
|
|
131
|
+
task: { script: 'task.js', group: 'work', summary: 'Create or show a task', selfHelp: true },
|
|
132
|
+
recall: { script: 'recall.js', group: 'work', summary: 'Search the docs + project knowledge (bongos recall "x")' },
|
|
133
|
+
cost: { script: 'cost.js', group: 'work', summary: 'Log a cost entry' },
|
|
134
|
+
release: { script: 'release.js', group: 'work', summary: 'Cancel a claim — task returns to ready' },
|
|
135
|
+
|
|
136
|
+
api: { script: 'api.js', group: 'work', summary: 'Call any instance endpoint (bongos api GET /api/gds/me)' },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Verbs that exist in the full core CLI and are deliberately absent here. The dispatcher prints
|
|
140
|
+
// the reason and the actual next step, because a newcomer hitting a bare "unknown command" learns
|
|
141
|
+
// nothing — that silence is the exact failure this whole task is fixing.
|
|
142
|
+
const CHECKOUT_ONLY = {
|
|
143
|
+
claim: 'Claiming a task writes code, so it needs a real checkout and a worktree.',
|
|
144
|
+
ship: 'Shipping grades, merges and deploys from a real checkout.',
|
|
145
|
+
dev: 'Runs an instance locally — needs the instance repo.',
|
|
146
|
+
serve: 'Runs an instance on a host — needs the instance repo.',
|
|
147
|
+
module: 'Scaffolds a module into an instance repo.',
|
|
148
|
+
upgrade: 'Moves an instance to a new core version.',
|
|
149
|
+
onboard: 'Provisions new infrastructure — owner tooling, not in the public CLI.',
|
|
150
|
+
doctor: 'Checks a local checkout toolchain.',
|
|
151
|
+
exec: 'Runs a core script from inside the core package.',
|
|
152
|
+
'package-core': 'Packages the core — owner tooling.',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// ── Redaction gate ──────────────────────────────────────────────────────────────────────────
|
|
156
|
+
// Generic shapes only. Instance-specific needles are discovered at build time from the host's
|
|
157
|
+
// own config (below) so the core itself carries no instance identity — the ADR 0062 §7 split.
|
|
158
|
+
const REDACTION_PATTERNS = [
|
|
159
|
+
{ name: 'non-loopback IPv4', re: /\b(?!127\.|0\.0\.0\.0|255\.)(?:\d{1,3}\.){3}\d{1,3}\b/g },
|
|
160
|
+
{ name: 'github token', re: /\bgh[pousr]_[A-Za-z0-9]{16,}/g },
|
|
161
|
+
{ name: 'github fine-grained token', re: /\bgithub_pat_[A-Za-z0-9_]{20,}/g },
|
|
162
|
+
{ name: 'npm token', re: /\bnpm_[A-Za-z0-9]{30,}/g },
|
|
163
|
+
{ name: 'private key block', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g },
|
|
164
|
+
{ name: 'aws access key', re: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
// Version strings (1.19.576) and dotted identifiers look like IPv4 to a loose regex; a real
|
|
168
|
+
// address has four all-numeric octets each <= 255 and is not a semver.
|
|
169
|
+
//
|
|
170
|
+
// The documentation ranges (RFC 5737) and link-local are exempt: they can never name real
|
|
171
|
+
// infrastructure, so they are the CORRECT thing to write in an example. Nothing else is
|
|
172
|
+
// exempt — private ranges included, because an internal address still leaks topology and
|
|
173
|
+
// deserves a human look before it goes public.
|
|
174
|
+
const DOC_IPV4 = [/^192\.0\.2\./, /^198\.51\.100\./, /^203\.0\.113\./, /^169\.254\./];
|
|
175
|
+
|
|
176
|
+
function isRealIPv4(s) {
|
|
177
|
+
const parts = s.split('.');
|
|
178
|
+
if (parts.length !== 4) return false;
|
|
179
|
+
if (!parts.every((p) => /^\d{1,3}$/.test(p) && Number(p) <= 255)) return false;
|
|
180
|
+
return !DOC_IPV4.some((re) => re.test(s));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The project's own public identity. `cloudbongos.com` is the flagship instance and the CLI's
|
|
184
|
+
// documented default — the way `registry.npmjs.org` is baked into npm — so it is published on
|
|
185
|
+
// purpose and must not trip the gate. Anything NOT on this list that comes out of an instance's
|
|
186
|
+
// branding is treated as private identity and blocks the build.
|
|
187
|
+
const PUBLIC_BY_DESIGN = new Set(['cloudbongos.com']);
|
|
188
|
+
|
|
189
|
+
// Instance identity to refuse: the host's own domains and owner login, read from the instance's
|
|
190
|
+
// committed branding if this checkout has one. Absent (a neutral core), the gate still runs the
|
|
191
|
+
// generic patterns above.
|
|
192
|
+
function instanceNeedles() {
|
|
193
|
+
const needles = new Set();
|
|
194
|
+
const add = (v) => {
|
|
195
|
+
if (typeof v !== 'string') return;
|
|
196
|
+
const s = v.trim().toLowerCase();
|
|
197
|
+
if (s.length > 3 && !PUBLIC_BY_DESIGN.has(s)) needles.add(s);
|
|
198
|
+
};
|
|
199
|
+
try {
|
|
200
|
+
const b = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'config', 'branding.json'), 'utf8'));
|
|
201
|
+
for (const v of Object.values(b.domains || {})) add(String(v).replace(/^https?:\/\//, '').replace(/\/.*$/, ''));
|
|
202
|
+
add(b.identity && b.identity.ownerLogin);
|
|
203
|
+
add(b.repo && b.repo.owner);
|
|
204
|
+
} catch (_) { /* neutral core: no instance branding, nothing to redact */ }
|
|
205
|
+
for (const extra of String(process.env.CLI_PACKAGE_FORBIDDEN || '').split(',')) add(extra);
|
|
206
|
+
return [...needles];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function scanRedaction(outDir, files) {
|
|
210
|
+
const needles = instanceNeedles();
|
|
211
|
+
const hits = [];
|
|
212
|
+
for (const rel of files) {
|
|
213
|
+
const abs = path.join(outDir, rel);
|
|
214
|
+
let text;
|
|
215
|
+
try { text = fs.readFileSync(abs, 'utf8'); } catch { continue; }
|
|
216
|
+
for (const { name, re } of REDACTION_PATTERNS) {
|
|
217
|
+
for (const m of text.matchAll(re)) {
|
|
218
|
+
if (name === 'non-loopback IPv4' && !isRealIPv4(m[0])) continue;
|
|
219
|
+
const line = text.slice(0, m.index).split('\n').length;
|
|
220
|
+
hits.push(`${rel}:${line} ${name}: ${m[0]}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const lower = text.toLowerCase();
|
|
224
|
+
for (const n of needles) {
|
|
225
|
+
let i = lower.indexOf(n);
|
|
226
|
+
while (i !== -1) {
|
|
227
|
+
hits.push(`${rel}:${text.slice(0, i).split('\n').length} instance identity: ${n}`);
|
|
228
|
+
i = lower.indexOf(n, i + n.length);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return hits;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ── Generated files ─────────────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
function dispatcherSource() {
|
|
238
|
+
const verbs = JSON.stringify(VERBS, null, 2).replace(/\n/g, '\n');
|
|
239
|
+
const checkout = JSON.stringify(CHECKOUT_ONLY, null, 2).replace(/\n/g, '\n');
|
|
240
|
+
return `#!/usr/bin/env node
|
|
241
|
+
// bin/bongos.js — the public Cloud Bongos CLI.
|
|
242
|
+
//
|
|
243
|
+
// GENERATED by scripts/gds/build-cli-package.js in the Cloud Bongos core. Do not edit by hand:
|
|
244
|
+
// edit the generator and rebuild.
|
|
245
|
+
//
|
|
246
|
+
// A thin dispatcher over the client scripts in this package. It spawns each with process.execPath
|
|
247
|
+
// and forwards argv + the exit code; no auth or API logic lives here (that is cli-lib.js).
|
|
248
|
+
|
|
249
|
+
'use strict';
|
|
250
|
+
|
|
251
|
+
const path = require('node:path');
|
|
252
|
+
const { spawnSync } = require('node:child_process');
|
|
253
|
+
|
|
254
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
255
|
+
const SCRIPTS_DIR = path.join(PKG_ROOT, 'scripts', 'gds');
|
|
256
|
+
const VERSION = require('../package.json').version;
|
|
257
|
+
|
|
258
|
+
const VERBS = ${verbs};
|
|
259
|
+
|
|
260
|
+
const CHECKOUT_ONLY = ${checkout};
|
|
261
|
+
|
|
262
|
+
const GROUPS = [['account', 'Account & environment'], ['work', 'Work']];
|
|
263
|
+
|
|
264
|
+
function helpText() {
|
|
265
|
+
const lines = ['bongos — the Cloud Bongos CLI', '', 'Usage: bongos <command> [args]'];
|
|
266
|
+
lines.push('', 'First time here?');
|
|
267
|
+
lines.push(' bongos login https://your-instance.com sign in with a browser click');
|
|
268
|
+
lines.push(' bongos start see what you can pick up');
|
|
269
|
+
for (const [key, label] of GROUPS) {
|
|
270
|
+
lines.push('', label + ':');
|
|
271
|
+
for (const [verb, def] of Object.entries(VERBS)) {
|
|
272
|
+
if (def.group !== key) continue;
|
|
273
|
+
lines.push(' ' + verb.padEnd(10) + ' ' + def.summary);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
lines.push('', ' help Show this help', ' version Show the bongos version');
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push('Writing code (claim, ship) happens in a checkout. \`bongos shell\` opens one');
|
|
279
|
+
lines.push('on a cloud dev box with nothing to install locally.');
|
|
280
|
+
return lines.join('\\n');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function verbHelpText(verb, def) {
|
|
284
|
+
return [
|
|
285
|
+
'bongos ' + verb + ' — ' + def.summary,
|
|
286
|
+
'',
|
|
287
|
+
'Forwards to scripts/gds/' + def.script + '; extra arguments are passed through.',
|
|
288
|
+
'Run \`bongos help\` to see every command.',
|
|
289
|
+
].join('\\n');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Returns the exit code (does not call process.exit, so it stays testable).
|
|
293
|
+
function main(argv) {
|
|
294
|
+
const args = argv.slice(2);
|
|
295
|
+
const verb = args[0];
|
|
296
|
+
|
|
297
|
+
if (!verb || verb === 'help' || verb === '--help' || verb === '-h') {
|
|
298
|
+
process.stdout.write(helpText() + '\\n');
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
if (verb === 'version' || verb === '--version' || verb === '-v') {
|
|
302
|
+
process.stdout.write('bongos ' + VERSION + '\\n');
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (CHECKOUT_ONLY[verb]) {
|
|
307
|
+
process.stderr.write(
|
|
308
|
+
'bongos: "' + verb + '" is not in the public CLI.\\n ' + CHECKOUT_ONLY[verb] + '\\n\\n' +
|
|
309
|
+
'To get a checkout without installing anything locally:\\n' +
|
|
310
|
+
' bongos shell open a terminal on your cloud dev box\\n' +
|
|
311
|
+
' bongos code open VS Code on it over Remote-SSH\\n\\n' +
|
|
312
|
+
'The full CLI (including ' + verb + ') is already installed inside that box.\\n'
|
|
313
|
+
);
|
|
314
|
+
return 2;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const def = VERBS[verb];
|
|
318
|
+
if (!def) {
|
|
319
|
+
process.stderr.write('bongos: unknown command "' + verb + '"\\n\\n' + helpText() + '\\n');
|
|
320
|
+
return 2;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!def.selfHelp && args.slice(1).some((a) => a === '--help' || a === '-h')) {
|
|
324
|
+
process.stdout.write(verbHelpText(verb, def) + '\\n');
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const target = path.join(SCRIPTS_DIR, def.script);
|
|
329
|
+
const spawnArgs = [target, ...(def.prefixArgs || []), ...args.slice(1)];
|
|
330
|
+
const res = spawnSync(process.execPath, spawnArgs, { stdio: 'inherit' });
|
|
331
|
+
if (res.error) {
|
|
332
|
+
process.stderr.write('bongos: could not run ' + def.script + ': ' + res.error.message + '\\n');
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
335
|
+
return res.status == null ? 1 : res.status;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (require.main === module) process.exit(main(process.argv));
|
|
339
|
+
module.exports = { main, helpText, VERBS, CHECKOUT_ONLY };
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function packageJsonSource(version) {
|
|
344
|
+
// DERIVED from the manifest, never hand-listed. A hand-written `files` array silently drops
|
|
345
|
+
// whatever it forgets: the first build of this package copied clients/bongos-client/index.mjs
|
|
346
|
+
// correctly and then `npm pack` left it out, so the tarball installed and died on first use.
|
|
347
|
+
const roots = [...new Set(FILES.map((f) => f.split('/')[0] + '/'))].sort();
|
|
348
|
+
return JSON.stringify({
|
|
349
|
+
name: PACKAGE_NAME,
|
|
350
|
+
version,
|
|
351
|
+
description: 'The Cloud Bongos CLI — sign in to an instance, see what you can build, and get onto a dev box. Client only: no server.',
|
|
352
|
+
bin: { bongos: 'bin/bongos.js' },
|
|
353
|
+
files: ['bin/', ...roots, 'README.md'],
|
|
354
|
+
engines: { node: '>=20' },
|
|
355
|
+
dependencies: { undici: '^6.19.8' },
|
|
356
|
+
keywords: ['cloud-bongos', 'cli', 'ai-agents', 'build-platform'],
|
|
357
|
+
license: 'AGPL-3.0-or-later',
|
|
358
|
+
// No `repository` field on purpose. The core repo is private today, so a repository URL
|
|
359
|
+
// would 404 for every user of a public package AND put the owner's login in it for nothing.
|
|
360
|
+
// Point it at the redacted public mirror once that lands (ADR 0099).
|
|
361
|
+
homepage: 'https://cloudbongos.com',
|
|
362
|
+
bugs: { url: 'https://cloudbongos.com/builders' },
|
|
363
|
+
publishConfig: { access: 'public' },
|
|
364
|
+
}, null, 2) + '\n';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function readmeSource(version) {
|
|
368
|
+
return `# ${PACKAGE_NAME}
|
|
369
|
+
|
|
370
|
+
The [Cloud Bongos](https://cloudbongos.com) CLI. Sign in to an instance, see what you can pick
|
|
371
|
+
up, and get onto a dev box — from a terminal, with nothing installed.
|
|
372
|
+
|
|
373
|
+
## Start here
|
|
374
|
+
|
|
375
|
+
\`\`\`sh
|
|
376
|
+
npx ${PACKAGE_NAME} login https://your-instance.com
|
|
377
|
+
npx ${PACKAGE_NAME} start
|
|
378
|
+
\`\`\`
|
|
379
|
+
|
|
380
|
+
\`login\` opens your browser, you click once, and the session is written to
|
|
381
|
+
\`~/.config/cloudbongos/\`. There is no token to copy and no password to set. If the instance
|
|
382
|
+
gates admission, \`login\` files your access request and the owner approves it.
|
|
383
|
+
|
|
384
|
+
Install it properly once you're past the first run:
|
|
385
|
+
|
|
386
|
+
\`\`\`sh
|
|
387
|
+
npm install -g ${PACKAGE_NAME}
|
|
388
|
+
bongos start
|
|
389
|
+
\`\`\`
|
|
390
|
+
|
|
391
|
+
## Commands
|
|
392
|
+
|
|
393
|
+
Run \`bongos help\` for the current list. In short: \`login\`, \`reauth\`, \`setup\`, \`box\`,
|
|
394
|
+
\`shell\`, \`code\` for your account and environment; \`start\`, \`status\`, \`task\`, \`recall\`,
|
|
395
|
+
\`cost\`, \`release\`, \`api\` for the work.
|
|
396
|
+
|
|
397
|
+
\`bongos api\` is the escape hatch — it calls any endpoint on the instance as you, so anything
|
|
398
|
+
the web hall can do is reachable from the terminal:
|
|
399
|
+
|
|
400
|
+
\`\`\`sh
|
|
401
|
+
bongos api GET /api/gds/me
|
|
402
|
+
\`\`\`
|
|
403
|
+
|
|
404
|
+
## Writing code
|
|
405
|
+
|
|
406
|
+
\`claim\` and \`ship\` need a real checkout, so they aren't in this package. The shortest path to
|
|
407
|
+
one is a cloud dev box, which needs nothing on your machine:
|
|
408
|
+
|
|
409
|
+
\`\`\`sh
|
|
410
|
+
bongos box # turn it on
|
|
411
|
+
bongos shell # a terminal on it, full CLI already installed
|
|
412
|
+
bongos code # or VS Code over Remote-SSH
|
|
413
|
+
\`\`\`
|
|
414
|
+
|
|
415
|
+
## What this package is
|
|
416
|
+
|
|
417
|
+
The client surface of the Cloud Bongos core, and nothing else — no server, no routes, no
|
|
418
|
+
database, no provisioning. It is generated from the core by
|
|
419
|
+
\`scripts/gds/build-cli-package.js\` and verified by installing the packed tarball into an empty
|
|
420
|
+
directory and running every command.
|
|
421
|
+
|
|
422
|
+
Version ${version} · AGPL-3.0-or-later
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── Build ───────────────────────────────────────────────────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
function build(opts = {}) {
|
|
429
|
+
const outDir = path.resolve(REPO_ROOT, opts.out || 'dist/cloudbongos-cli');
|
|
430
|
+
const version = opts.version || PACKAGE_VERSION;
|
|
431
|
+
const log = opts.quiet ? () => {} : (m) => process.stdout.write(m + '\n');
|
|
432
|
+
|
|
433
|
+
// Refuse a file list that contradicts the package's whole premise, before copying anything.
|
|
434
|
+
const violations = [];
|
|
435
|
+
for (const rel of FILES) {
|
|
436
|
+
if (rel.startsWith('src/bongos/') && !ALLOWED_SRC_BONGOS.has(rel)) {
|
|
437
|
+
violations.push(`${rel} — src/bongos/ is the kernel; only ${[...ALLOWED_SRC_BONGOS].join(', ')} may ship`);
|
|
438
|
+
}
|
|
439
|
+
if (FORBIDDEN_PREFIXES.some((p) => rel.startsWith(p))) violations.push(`${rel} — forbidden path prefix`);
|
|
440
|
+
if (FORBIDDEN_BASENAMES.includes(path.basename(rel)) && !ALLOWED_SRC_BONGOS.has(rel)) {
|
|
441
|
+
violations.push(`${rel} — forbidden filename (server surface)`);
|
|
442
|
+
}
|
|
443
|
+
if (!fs.existsSync(path.join(REPO_ROOT, rel))) violations.push(`${rel} — missing from the repo`);
|
|
444
|
+
}
|
|
445
|
+
if (violations.length) {
|
|
446
|
+
const err = new Error('cli package manifest rejected:\n ' + violations.join('\n '));
|
|
447
|
+
err.violations = violations;
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Dynamic `import()` of a relative path is invisible to any require()-based reasoning, and
|
|
452
|
+
// that is not hypothetical: cli-lib.js reaches the generated API client that way, and the
|
|
453
|
+
// first build shipped without it. Every relative import target in a shipped file must itself
|
|
454
|
+
// be shipped, or the package installs fine and dies on first use.
|
|
455
|
+
const unshipped = [];
|
|
456
|
+
for (const rel of FILES) {
|
|
457
|
+
if (!/\.(js|mjs|cjs)$/.test(rel)) continue;
|
|
458
|
+
const text = fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8');
|
|
459
|
+
for (const m of text.matchAll(/\bimport\(\s*['"](\.[^'"]+)['"]\s*\)/g)) {
|
|
460
|
+
const target = path.relative(REPO_ROOT, path.resolve(path.dirname(path.join(REPO_ROOT, rel)), m[1]));
|
|
461
|
+
if (!FILES.includes(target)) unshipped.push(`${rel} dynamically imports ${target}, which the manifest does not ship`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (unshipped.length) {
|
|
465
|
+
const err = new Error('cli package manifest incomplete:\n ' + unshipped.join('\n '));
|
|
466
|
+
err.violations = unshipped;
|
|
467
|
+
throw err;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
471
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
472
|
+
|
|
473
|
+
for (const rel of FILES) {
|
|
474
|
+
const dest = path.join(outDir, rel);
|
|
475
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
476
|
+
fs.copyFileSync(path.join(REPO_ROOT, rel), dest);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
fs.mkdirSync(path.join(outDir, 'bin'), { recursive: true });
|
|
480
|
+
fs.writeFileSync(path.join(outDir, 'bin', 'bongos.js'), dispatcherSource(), { mode: 0o755 });
|
|
481
|
+
fs.writeFileSync(path.join(outDir, 'package.json'), packageJsonSource(version));
|
|
482
|
+
fs.writeFileSync(path.join(outDir, 'README.md'), readmeSource(version));
|
|
483
|
+
|
|
484
|
+
const generated = [...FILES, 'bin/bongos.js', 'package.json', 'README.md'];
|
|
485
|
+
const leaks = scanRedaction(outDir, generated);
|
|
486
|
+
if (leaks.length) {
|
|
487
|
+
const err = new Error(
|
|
488
|
+
'cli package redaction gate FAILED — refusing to emit:\n ' + leaks.join('\n ') +
|
|
489
|
+
'\n\nThis package is PUBLIC. Build it from the neutral core, not from an instance checkout' +
|
|
490
|
+
'\n(an instance\'s config/branding.json names its own domains and owner).'
|
|
491
|
+
);
|
|
492
|
+
err.leaks = leaks;
|
|
493
|
+
throw err;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const bytes = generated.reduce((n, rel) => {
|
|
497
|
+
try { return n + fs.statSync(path.join(outDir, rel)).size; } catch { return n; }
|
|
498
|
+
}, 0);
|
|
499
|
+
|
|
500
|
+
log(`${PACKAGE_NAME}@${version} → ${path.relative(REPO_ROOT, outDir)}`);
|
|
501
|
+
log(` ${generated.length} files · ${(bytes / 1024).toFixed(0)} KB · ${Object.keys(VERBS).length} verbs · 1 dependency (undici)`);
|
|
502
|
+
log(` redaction gate: clean (${instanceNeedles().length} instance needles checked)`);
|
|
503
|
+
return { outDir, version, files: generated, verbs: Object.keys(VERBS) };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
module.exports = { build, FILES, VERBS, CHECKOUT_ONLY, FORBIDDEN_PREFIXES, FORBIDDEN_BASENAMES, ALLOWED_SRC_BONGOS, PACKAGE_NAME, PACKAGE_VERSION, scanRedaction, instanceNeedles };
|
|
507
|
+
|
|
508
|
+
if (require.main === module) {
|
|
509
|
+
const argv = process.argv.slice(2);
|
|
510
|
+
const flag = (name) => { const i = argv.indexOf(name); return i === -1 ? null : argv[i + 1]; };
|
|
511
|
+
try {
|
|
512
|
+
build({ out: flag('--out'), version: flag('--version'), quiet: argv.includes('--quiet') });
|
|
513
|
+
} catch (err) {
|
|
514
|
+
process.stderr.write(String(err.message) + '\n');
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
}
|
package/src/module-api.js
CHANGED
|
@@ -55,7 +55,7 @@ const { buildInfo } = require('./build-info');
|
|
|
55
55
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
56
56
|
// the entry to that file. Look for a version's history there, not here.
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
|
-
const CORE_VERSION = '1.19.
|
|
58
|
+
const CORE_VERSION = '1.19.578'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
59
59
|
|
|
60
60
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
61
61
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|