@bigbrainforge/setup 3.19.0 → 3.20.0

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.
Files changed (2) hide show
  1. package/dist/setup.cjs +153 -79
  2. package/package.json +1 -1
package/dist/setup.cjs CHANGED
@@ -189,6 +189,21 @@ var require_license = __commonJS({
189
189
  }
190
190
  });
191
191
 
192
+ // ../forge-plugin/lib/sanitize-terminal.js
193
+ var require_sanitize_terminal = __commonJS({
194
+ "../forge-plugin/lib/sanitize-terminal.js"(exports2, module2) {
195
+ var TERMINAL_CONTROL_RE = (
196
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching control characters is the entire point.
197
+ /[\u0000-\u001F\u007F-\u009F\u00AD\u061C\u200B-\u200F\u2028-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/g
198
+ );
199
+ function sanitizeForTerminal2(value) {
200
+ const text = value === null || value === void 0 ? "" : String(value);
201
+ return text.replace(TERMINAL_CONTROL_RE, "");
202
+ }
203
+ module2.exports = { sanitizeForTerminal: sanitizeForTerminal2 };
204
+ }
205
+ });
206
+
192
207
  // ../forge-plugin/lib/discover-instance.js
193
208
  var require_discover_instance = __commonJS({
194
209
  "../forge-plugin/lib/discover-instance.js"(exports2, module2) {
@@ -196,6 +211,14 @@ var require_discover_instance = __commonJS({
196
211
  var os2 = require("node:os");
197
212
  var path2 = require("node:path");
198
213
  var { resolveLicense } = require_license();
214
+ var { sanitizeForTerminal: sanitizeForTerminal2 } = require_sanitize_terminal();
215
+ function failure(reason, hint, extra = {}) {
216
+ const cleaned = {};
217
+ for (const [key, value] of Object.entries(extra)) {
218
+ cleaned[key] = typeof value === "string" ? sanitizeForTerminal2(value) : value;
219
+ }
220
+ return { ok: false, reason, hint: sanitizeForTerminal2(hint), ...cleaned };
221
+ }
199
222
  var DEFAULT_LICENSING_URL2 = "https://licensing.bigbrainforge.com";
200
223
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
201
224
  function isSecureUrl2(value) {
@@ -215,17 +238,12 @@ var require_discover_instance = __commonJS({
215
238
  const parsed = new URL(base);
216
239
  scheme = `${parsed.protocol}//${parsed.hostname}`;
217
240
  } catch {
218
- return {
219
- ok: false,
220
- reason: "insecure_licensing_url",
221
- hint: `FORGE_LICENSING_URL is not a valid URL: ${base}`
222
- };
241
+ return failure("insecure_licensing_url", `FORGE_LICENSING_URL is not a valid URL: ${base}`);
223
242
  }
224
- return {
225
- ok: false,
226
- reason: "insecure_licensing_url",
227
- hint: `FORGE_LICENSING_URL must use https:// (got ${scheme}). Plain http:// is allowed only for loopback (127.0.0.1, localhost, ::1) \u2014 refusing to send the license credential in cleartext.`
228
- };
243
+ return failure(
244
+ "insecure_licensing_url",
245
+ `FORGE_LICENSING_URL must use https:// (got ${scheme}). Plain http:// is allowed only for loopback (127.0.0.1, localhost, ::1) \u2014 refusing to send the license credential in cleartext.`
246
+ );
229
247
  }
230
248
  function pluginConfigPath(io = {}) {
231
249
  const env = io.env ?? process.env;
@@ -235,7 +253,8 @@ var require_discover_instance = __commonJS({
235
253
  function readPinnedUrl(io) {
236
254
  try {
237
255
  const config = JSON.parse(fs.readFileSync(pluginConfigPath(io), "utf8"));
238
- return typeof config.mcp_url === "string" && config.mcp_url.length > 0 ? config.mcp_url : null;
256
+ const pinned = typeof config.mcp_url === "string" && config.mcp_url.length > 0 ? config.mcp_url : null;
257
+ return pinned === null ? null : sanitizeForTerminal2(pinned);
239
258
  } catch {
240
259
  return null;
241
260
  }
@@ -276,11 +295,10 @@ var require_discover_instance = __commonJS({
276
295
  signal: AbortSignal.timeout(15e3)
277
296
  });
278
297
  } catch {
279
- return {
280
- ok: false,
281
- reason: "discovery_unreachable",
282
- hint: `licensing service unreachable \u2014 check network egress to ${base}`
283
- };
298
+ return failure(
299
+ "discovery_unreachable",
300
+ `licensing service unreachable \u2014 check network egress to ${base}`
301
+ );
284
302
  }
285
303
  let body = {};
286
304
  try {
@@ -289,43 +307,39 @@ var require_discover_instance = __commonJS({
289
307
  body = {};
290
308
  }
291
309
  if (response.status === 401) {
292
- return {
293
- ok: false,
294
- reason: "license_invalid",
295
- hint: body.error || "unknown or revoked license \u2014 contact your org admin"
296
- };
310
+ return failure(
311
+ "license_invalid",
312
+ body.error || "unknown or revoked license \u2014 contact your org admin"
313
+ );
297
314
  }
298
315
  if (!response.ok || typeof body.instance_url !== "string" || body.instance_url.length === 0) {
299
- return {
300
- ok: false,
301
- reason: "discovery_unreachable",
302
- hint: `discovery failed (HTTP ${response.status}) at ${base}/license/discover`
303
- };
316
+ return failure(
317
+ "discovery_unreachable",
318
+ `discovery failed (HTTP ${response.status}) at ${base}/license/discover`
319
+ );
304
320
  }
305
321
  return {
306
322
  ok: true,
307
- instance_url: body.instance_url.replace(/\/$/, ""),
308
- deployment_model: body.deployment_model ?? null
323
+ instance_url: sanitizeForTerminal2(body.instance_url).replace(/\/$/, ""),
324
+ deployment_model: body.deployment_model === null || body.deployment_model === void 0 ? null : sanitizeForTerminal2(body.deployment_model)
309
325
  };
310
326
  }
311
327
  function checkPinConflict(pinned, discovered, acceptNewInstance) {
312
328
  if (!pinned || sameHost(pinned, discovered) || acceptNewInstance === true) {
313
329
  return null;
314
330
  }
315
- return {
316
- ok: false,
317
- reason: "pin_conflict",
318
- pinned_url: pinned,
319
- discovered_url: discovered,
320
- hint: `discovery returned ${discovered} but this machine is pinned to ${pinned}. Refusing to release the license to a new host. If the change is expected (your org migrated instances), re-run with --accept-new-instance.`
321
- };
331
+ return failure(
332
+ "pin_conflict",
333
+ `discovery returned ${discovered} but this machine is pinned to ${pinned}. Refusing to release the license to a new host. If the change is expected (your org migrated instances), re-run with --accept-new-instance.`,
334
+ { pinned_url: pinned, discovered_url: discovered }
335
+ );
322
336
  }
323
337
  async function discoverInstance2(io = {}) {
324
338
  const env = io.env ?? process.env;
325
339
  const doFetch = io.fetchImpl ?? fetch;
326
340
  const license = resolveLicense(io);
327
341
  if (license.value === null) {
328
- return { ok: false, reason: "no_license", hint: license.hint };
342
+ return failure("no_license", license.hint);
329
343
  }
330
344
  const pinned = readPinnedUrl(io);
331
345
  if (pinned && io.forceDiscovery !== true) {
@@ -342,11 +356,10 @@ var require_discover_instance = __commonJS({
342
356
  }
343
357
  const { instance_url: discovered, deployment_model: deploymentModel } = discovery;
344
358
  if (!isSecureUrl2(discovered)) {
345
- return {
346
- ok: false,
347
- reason: "insecure_instance_url",
348
- hint: `discovery returned an insecure instance_url (${discovered}) \u2014 refusing to pin it. The instance must be served over https:// (plain http:// is allowed only for loopback), so the license is never exchanged over a cleartext connection.`
349
- };
359
+ return failure(
360
+ "insecure_instance_url",
361
+ `discovery returned an insecure instance_url (${discovered}) \u2014 refusing to pin it. The instance must be served over https:// (plain http:// is allowed only for loopback), so the license is never exchanged over a cleartext connection.`
362
+ );
350
363
  }
351
364
  const conflict = checkPinConflict(pinned, discovered, io.acceptNewInstance);
352
365
  if (conflict) {
@@ -355,7 +368,7 @@ var require_discover_instance = __commonJS({
355
368
  if (!pinned || !sameHost(pinned, discovered)) {
356
369
  const pinResult = pinInstanceUrl(io, discovered);
357
370
  if (!pinResult.ok) {
358
- return { ok: false, reason: "discovery_unreachable", hint: pinResult.error };
371
+ return failure("discovery_unreachable", pinResult.error);
359
372
  }
360
373
  }
361
374
  return {
@@ -911,6 +924,22 @@ var require_exec_cli = __commonJS({
911
924
  }
912
925
  });
913
926
 
927
+ // ../forge-plugin/lib/prompt-abort.js
928
+ var require_prompt_abort = __commonJS({
929
+ "../forge-plugin/lib/prompt-abort.js"(exports2, module2) {
930
+ var PROMPT_ABORTED = "PROMPT_ABORTED";
931
+ function promptAbortError2(what = "prompt") {
932
+ const err = new Error(`${what} aborted (Ctrl-C or end of input).`);
933
+ err.code = PROMPT_ABORTED;
934
+ return err;
935
+ }
936
+ function isPromptAbort2(err) {
937
+ return Boolean(err) && err.code === PROMPT_ABORTED;
938
+ }
939
+ module2.exports = { PROMPT_ABORTED, promptAbortError: promptAbortError2, isPromptAbort: isPromptAbort2 };
940
+ }
941
+ });
942
+
914
943
  // src/setup.js
915
944
  var os = require("node:os");
916
945
  var path = require("node:path");
@@ -924,6 +953,8 @@ var {
924
953
  assertSafeVersion
925
954
  } = require_plugin_bundle();
926
955
  var { resolveSpawn } = require_exec_cli();
956
+ var { sanitizeForTerminal } = require_sanitize_terminal();
957
+ var { isPromptAbort, promptAbortError } = require_prompt_abort();
927
958
  var LICENSE_RE = /^forge_lic_[0-9A-Za-z]{43}$/;
928
959
  var DEFAULT_LICENSING_URL = "https://licensing.bigbrainforge.com";
929
960
  var DEFAULT_DIST_URL = "https://licensing.bigbrainforge.com";
@@ -937,26 +968,52 @@ function runShim(io, cmd, args) {
937
968
  function errMessage(err) {
938
969
  return err && err.message ? err.message : String(err);
939
970
  }
971
+ async function promptHidden(q, { input = process.stdin, output = process.stdout } = {}) {
972
+ const readline = require("node:readline");
973
+ return new Promise((res, rej) => {
974
+ const rl = readline.createInterface({ input, output, terminal: true });
975
+ let settled = false;
976
+ rl.on("close", () => {
977
+ if (settled) return;
978
+ settled = true;
979
+ output.write("\n");
980
+ rej(promptAbortError("license prompt"));
981
+ });
982
+ const orig = rl._writeToOutput.bind(rl);
983
+ const columnsAt = (k) => {
984
+ const saved = rl.cursor;
985
+ rl.cursor = k;
986
+ const pos = rl.getCursorPos();
987
+ rl.cursor = saved;
988
+ return pos.rows === 0 ? pos.cols : pos.rows * rl.columns + pos.cols;
989
+ };
990
+ rl._writeToOutput = (s) => {
991
+ if (s.includes("\n") || s.includes("\r")) return orig("");
992
+ if (s.startsWith(q)) {
993
+ return orig(q + "*".repeat(Math.max(0, columnsAt(rl.line.length) - columnsAt(0))));
994
+ }
995
+ const end = rl.cursor;
996
+ const start = Math.max(0, end - s.length);
997
+ if (rl.line.slice(start, end) === s) {
998
+ return orig("*".repeat(Math.max(0, columnsAt(end) - columnsAt(start))));
999
+ }
1000
+ return orig(s.replace(/[^ ]/g, "*"));
1001
+ };
1002
+ rl.question(q, (a) => {
1003
+ settled = true;
1004
+ rl.close();
1005
+ output.write("\n");
1006
+ res(a.trim());
1007
+ });
1008
+ });
1009
+ }
940
1010
  function defaultIo() {
941
1011
  return {
942
1012
  homedir: os.homedir(),
943
1013
  env: process.env,
944
1014
  print: (l) => process.stdout.write(`${l}
945
1015
  `),
946
- promptHidden: async (q) => {
947
- const readline = require("node:readline");
948
- return new Promise((res) => {
949
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
950
- const orig = rl._writeToOutput.bind(rl);
951
- rl._writeToOutput = (s) => s.includes("\n") || s === q ? orig(s) : orig("");
952
- process.stdout.write(q);
953
- rl.question("", (a) => {
954
- rl.close();
955
- process.stdout.write("\n");
956
- res(a.trim());
957
- });
958
- });
959
- },
1016
+ promptHidden: (q) => promptHidden(q),
960
1017
  fetch: (url, opts) => globalThis.fetch(url, opts),
961
1018
  run: (cmd, args = [], opts = {}) => {
962
1019
  const { stdio, ...rest } = opts;
@@ -969,15 +1026,19 @@ function defaultIo() {
969
1026
  }
970
1027
  };
971
1028
  }
1029
+ function say(io, line) {
1030
+ io.print(sanitizeForTerminal(line));
1031
+ }
972
1032
  function fail(io, tier, message, fix) {
973
- io.print("");
974
- io.print(`\u2717 [${tier}] ${message}`);
975
- if (fix) io.print(` fix: ${fix}`);
1033
+ say(io, "");
1034
+ say(io, `\u2717 [${tier}] ${message}`);
1035
+ if (fix) say(io, ` fix: ${fix}`);
976
1036
  return 1;
977
1037
  }
978
1038
  async function runSetup(argv, io = defaultIo()) {
979
1039
  const args = argv.filter((a) => !a.startsWith("--"));
980
1040
  const acceptNewInstance = argv.includes("--accept-new-instance");
1041
+ const print = (line) => say(io, line);
981
1042
  const tarProbe = io.run("tar", ["--version"]);
982
1043
  if (tarProbe.error || tarProbe.status !== 0) {
983
1044
  return fail(
@@ -988,7 +1049,19 @@ async function runSetup(argv, io = defaultIo()) {
988
1049
  );
989
1050
  }
990
1051
  let license = args[0] ?? "";
991
- if (!license) license = await io.promptHidden("Paste your Forge license: ");
1052
+ if (!license) {
1053
+ try {
1054
+ license = await io.promptHidden("Paste your Forge license: ");
1055
+ } catch (err) {
1056
+ if (!isPromptAbort(err)) throw err;
1057
+ return fail(
1058
+ io,
1059
+ "credential",
1060
+ "setup was cancelled at the license prompt \u2014 nothing on this machine was changed",
1061
+ `rerun when you have the license to hand: ${RERUN_SETUP_CMD}`
1062
+ );
1063
+ }
1064
+ }
992
1065
  if (!LICENSE_RE.test(license)) {
993
1066
  return fail(
994
1067
  io,
@@ -1006,11 +1079,12 @@ async function runSetup(argv, io = defaultIo()) {
1006
1079
  `fix the named problem (usually permissions on ~/.claude/forge), then rerun: ${RERUN_SETUP_CMD}`
1007
1080
  );
1008
1081
  }
1009
- io.print(`\u2713 license stored (${wrote.path})`);
1082
+ print(`\u2713 license stored (${wrote.path})`);
1010
1083
  const licensingUrl = io.env.FORGE_LICENSING_URL || DEFAULT_LICENSING_URL;
1011
1084
  const discoveryFetch = async (url, opts) => {
1012
1085
  const res = await io.fetch(url, opts);
1013
- return { ...res, ok: res.ok ?? (res.status >= 200 && res.status < 300) };
1086
+ if (res.ok !== void 0) return res;
1087
+ return { ...res, ok: res.status >= 200 && res.status < 300 };
1014
1088
  };
1015
1089
  const discovered = await discoverInstance({
1016
1090
  homedir: io.homedir,
@@ -1034,7 +1108,7 @@ async function runSetup(argv, io = defaultIo()) {
1034
1108
  ) : `check network egress to ${licensingUrl} (it must be on your allowlist)`
1035
1109
  );
1036
1110
  }
1037
- io.print(`\u2713 instance pinned: ${discovered.instance_url} (${discovered.deployment_model})`);
1111
+ print(`\u2713 instance pinned: ${discovered.instance_url} (${discovered.deployment_model})`);
1038
1112
  let npmrc;
1039
1113
  try {
1040
1114
  npmrc = writeNpmrcLicense(license, { homedir: io.homedir, env: io.env });
@@ -1048,11 +1122,11 @@ async function runSetup(argv, io = defaultIo()) {
1048
1122
  }
1049
1123
  if (npmrc.status === "declined") {
1050
1124
  const legacyNote = npmrc.replacedLegacy ? " (a legacy npm.pkg.github.com credential line found alongside it was removed)" : "";
1051
- io.print(
1125
+ print(
1052
1126
  npmrc.foreignScopeTarget === "" ? `! npm NOT seeded \u2014 ~/.npmrc has a malformed @bigbrainforge:registry= line (no value). Fix or remove that line, then rerun setup; otherwise no action is needed.${legacyNote}` : `! npm NOT seeded \u2014 @bigbrainforge:registry already points at ${npmrc.foreignScopeTarget} (an enterprise proxy or other custom config). If that isn't your organization's proxy, remove that line from ~/.npmrc and rerun setup; otherwise no action is needed.${legacyNote}`
1053
1127
  );
1054
1128
  } else {
1055
- io.print(
1129
+ print(
1056
1130
  npmrc.status === "seeded" ? `\u2713 npm seeded (${npmrc.path})${npmrc.replacedLegacy ? " \u2014 legacy GitHub Packages lines replaced" : ""}` : `\u2713 npm already seeded (${npmrc.path}) \u2014 no change needed`
1057
1131
  );
1058
1132
  }
@@ -1148,7 +1222,7 @@ async function runSetup(argv, io = defaultIo()) {
1148
1222
  `the download may be corrupted or tampered \u2014 rerun to retry: ${RERUN_SETUP_CMD} (if it persists, contact your Forge administrator)`
1149
1223
  );
1150
1224
  }
1151
- io.print(`\u2713 plugin bundle ${manifest.version} verified (sha256) and staged at ${staged.target}`);
1225
+ print(`\u2713 plugin bundle ${manifest.version} verified (sha256) and staged at ${staged.target}`);
1152
1226
  const root = bundleRoot(io.homedir);
1153
1227
  const addRes = runShim(io, "claude", ["plugin", "marketplace", "add", root]);
1154
1228
  const installRes = addRes.status === 0 ? runShim(io, "claude", ["plugin", "install", "forge@forge"]) : addRes;
@@ -1157,19 +1231,19 @@ async function runSetup(argv, io = defaultIo()) {
1157
1231
  ${installRes.stderr || ""}`
1158
1232
  );
1159
1233
  if (addRes.status === 0 && installRes.status === 0) {
1160
- io.print("\u2713 plugin installed from the local Forge marketplace");
1234
+ print("\u2713 plugin installed from the local Forge marketplace");
1161
1235
  } else if (alreadyRegistered) {
1162
- io.print("\u2713 plugin already installed from the local Forge marketplace \u2014 nothing to do");
1236
+ print("\u2713 plugin already installed from the local Forge marketplace \u2014 nothing to do");
1163
1237
  } else {
1164
- io.print("");
1165
- io.print("! Claude Code CLI not reachable from this shell \u2014 run these two commands yourself:");
1166
- io.print("");
1167
- io.print(` claude plugin marketplace add "${root}"`);
1168
- io.print(" claude plugin install forge@forge");
1169
- io.print("");
1238
+ print("");
1239
+ print("! Claude Code CLI not reachable from this shell \u2014 run these two commands yourself:");
1240
+ print("");
1241
+ print(` claude plugin marketplace add "${root}"`);
1242
+ print(" claude plugin install forge@forge");
1243
+ print("");
1170
1244
  }
1171
1245
  const bundledBootstrap = path.join(staged.target, "scripts", "bootstrap.js");
1172
- io.print("Handing off to the Forge plugin bootstrap (server check + issue tracker)\u2026");
1246
+ print("Handing off to the Forge plugin bootstrap (server check + issue tracker)\u2026");
1173
1247
  const hand = io.run(process.execPath, [bundledBootstrap], { stdio: "inherit" });
1174
1248
  if (hand.status !== 0) {
1175
1249
  return fail(
@@ -1179,15 +1253,15 @@ ${installRes.stderr || ""}`
1179
1253
  `rerun it yourself: node "${bundledBootstrap}"`
1180
1254
  );
1181
1255
  }
1182
- io.print("");
1183
- io.print("Done. Open Claude Code \u2014 Forge is ready.");
1256
+ print("");
1257
+ print("Done. Open Claude Code \u2014 Forge is ready.");
1184
1258
  return 0;
1185
1259
  }
1186
- module.exports = { runSetup, LICENSE_RE, defaultIo, RERUN_SETUP_CMD };
1260
+ module.exports = { runSetup, LICENSE_RE, defaultIo, promptHidden, RERUN_SETUP_CMD };
1187
1261
  if (require.main === module) {
1188
1262
  runSetup(process.argv.slice(2)).then((code) => process.exit(code)).catch((err) => {
1189
1263
  process.stderr.write(`
1190
- \u2717 [unexpected] ${errMessage(err)}
1264
+ \u2717 [unexpected] ${sanitizeForTerminal(errMessage(err))}
1191
1265
  `);
1192
1266
  process.stderr.write(
1193
1267
  " fix: this is unexpected \u2014 please report it (including this message) to your Forge administrator\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbrainforge/setup",
3
- "version": "3.19.0",
3
+ "version": "3.20.0",
4
4
  "description": "Forge workstation setup — paste one license, get a working Forge install.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://github.com/bigbrainforge/forge",