@bigbrainforge/setup 3.19.0 → 3.21.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 +207 -81
  2. package/package.json +2 -2
package/dist/setup.cjs CHANGED
@@ -8,6 +8,28 @@ var __commonJS = (cb, mod) => function __require() {
8
8
  }
9
9
  };
10
10
 
11
+ // ../forge-plugin/lib/paste-command.js
12
+ var require_paste_command = __commonJS({
13
+ "../forge-plugin/lib/paste-command.js"(exports2, module2) {
14
+ function pasteCommand(alias, subcommand = "set") {
15
+ if (process.env.FORGE_DIST_BIN === "1") {
16
+ return `npx forge secrets ${subcommand} ${alias}`;
17
+ }
18
+ const path2 = require("node:path");
19
+ const scriptAbs = path2.resolve(__dirname, "..", "scripts", "secrets.js");
20
+ return `node "${scriptAbs}" ${subcommand} ${alias}`;
21
+ }
22
+ function pasteCommandIfAvailable(alias, subcommand = "set") {
23
+ if (process.env.FORGE_DIST_BIN === "1") return pasteCommand(alias, subcommand);
24
+ const fs = require("node:fs");
25
+ const path2 = require("node:path");
26
+ const scriptAbs = path2.resolve(__dirname, "..", "scripts", "secrets.js");
27
+ return fs.existsSync(scriptAbs) ? pasteCommand(alias, subcommand) : null;
28
+ }
29
+ module2.exports = { pasteCommand, pasteCommandIfAvailable };
30
+ }
31
+ });
32
+
11
33
  // ../forge-plugin/lib/rename-with-retry.js
12
34
  var require_rename_with_retry = __commonJS({
13
35
  "../forge-plugin/lib/rename-with-retry.js"(exports2, module2) {
@@ -86,6 +108,7 @@ var require_license = __commonJS({
86
108
  var fs = require("node:fs");
87
109
  var os2 = require("node:os");
88
110
  var path2 = require("node:path");
111
+ var { pasteCommandIfAvailable } = require_paste_command();
89
112
  var { renameWithRetry } = require_rename_with_retry();
90
113
  var LICENSE_RE2 = /^forge_lic_[0-9A-Za-z]{43}$/;
91
114
  function licensePath(io = {}) {
@@ -106,7 +129,8 @@ var require_license = __commonJS({
106
129
  return raw !== null && LICENSE_RE2.test(raw);
107
130
  }
108
131
  function missingHint() {
109
- return "no Forge license on this machine \u2014 run /forge:setup license, or paste it in your own terminal with: node <plugin-root>/scripts/secrets.js set license";
132
+ const paste = pasteCommandIfAvailable("license");
133
+ return "no Forge license on this machine \u2014 run /forge:setup license" + (paste ? `, or paste it in your own terminal with: ${paste}` : "");
110
134
  }
111
135
  function resolveLicense(io = {}) {
112
136
  const env = io.env ?? process.env;
@@ -189,6 +213,21 @@ var require_license = __commonJS({
189
213
  }
190
214
  });
191
215
 
216
+ // ../forge-plugin/lib/sanitize-terminal.js
217
+ var require_sanitize_terminal = __commonJS({
218
+ "../forge-plugin/lib/sanitize-terminal.js"(exports2, module2) {
219
+ var TERMINAL_CONTROL_RE = (
220
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching control characters is the entire point.
221
+ /[\u0000-\u001F\u007F-\u009F\u00AD\u061C\u200B-\u200F\u2028-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/g
222
+ );
223
+ function sanitizeForTerminal2(value) {
224
+ const text = value === null || value === void 0 ? "" : String(value);
225
+ return text.replace(TERMINAL_CONTROL_RE, "");
226
+ }
227
+ module2.exports = { sanitizeForTerminal: sanitizeForTerminal2 };
228
+ }
229
+ });
230
+
192
231
  // ../forge-plugin/lib/discover-instance.js
193
232
  var require_discover_instance = __commonJS({
194
233
  "../forge-plugin/lib/discover-instance.js"(exports2, module2) {
@@ -196,6 +235,14 @@ var require_discover_instance = __commonJS({
196
235
  var os2 = require("node:os");
197
236
  var path2 = require("node:path");
198
237
  var { resolveLicense } = require_license();
238
+ var { sanitizeForTerminal: sanitizeForTerminal2 } = require_sanitize_terminal();
239
+ function failure(reason, hint, extra = {}) {
240
+ const cleaned = {};
241
+ for (const [key, value] of Object.entries(extra)) {
242
+ cleaned[key] = typeof value === "string" ? sanitizeForTerminal2(value) : value;
243
+ }
244
+ return { ok: false, reason, hint: sanitizeForTerminal2(hint), ...cleaned };
245
+ }
199
246
  var DEFAULT_LICENSING_URL2 = "https://licensing.bigbrainforge.com";
200
247
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
201
248
  function isSecureUrl2(value) {
@@ -215,17 +262,12 @@ var require_discover_instance = __commonJS({
215
262
  const parsed = new URL(base);
216
263
  scheme = `${parsed.protocol}//${parsed.hostname}`;
217
264
  } catch {
218
- return {
219
- ok: false,
220
- reason: "insecure_licensing_url",
221
- hint: `FORGE_LICENSING_URL is not a valid URL: ${base}`
222
- };
265
+ return failure("insecure_licensing_url", `FORGE_LICENSING_URL is not a valid URL: ${base}`);
223
266
  }
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
- };
267
+ return failure(
268
+ "insecure_licensing_url",
269
+ `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.`
270
+ );
229
271
  }
230
272
  function pluginConfigPath(io = {}) {
231
273
  const env = io.env ?? process.env;
@@ -235,7 +277,8 @@ var require_discover_instance = __commonJS({
235
277
  function readPinnedUrl(io) {
236
278
  try {
237
279
  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;
280
+ const pinned = typeof config.mcp_url === "string" && config.mcp_url.length > 0 ? config.mcp_url : null;
281
+ return pinned === null ? null : sanitizeForTerminal2(pinned);
239
282
  } catch {
240
283
  return null;
241
284
  }
@@ -276,11 +319,10 @@ var require_discover_instance = __commonJS({
276
319
  signal: AbortSignal.timeout(15e3)
277
320
  });
278
321
  } catch {
279
- return {
280
- ok: false,
281
- reason: "discovery_unreachable",
282
- hint: `licensing service unreachable \u2014 check network egress to ${base}`
283
- };
322
+ return failure(
323
+ "discovery_unreachable",
324
+ `licensing service unreachable \u2014 check network egress to ${base}`
325
+ );
284
326
  }
285
327
  let body = {};
286
328
  try {
@@ -289,43 +331,39 @@ var require_discover_instance = __commonJS({
289
331
  body = {};
290
332
  }
291
333
  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
- };
334
+ return failure(
335
+ "license_invalid",
336
+ body.error || "unknown or revoked license \u2014 contact your org admin"
337
+ );
297
338
  }
298
339
  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
- };
340
+ return failure(
341
+ "discovery_unreachable",
342
+ `discovery failed (HTTP ${response.status}) at ${base}/license/discover`
343
+ );
304
344
  }
305
345
  return {
306
346
  ok: true,
307
- instance_url: body.instance_url.replace(/\/$/, ""),
308
- deployment_model: body.deployment_model ?? null
347
+ instance_url: sanitizeForTerminal2(body.instance_url).replace(/\/$/, ""),
348
+ deployment_model: body.deployment_model === null || body.deployment_model === void 0 ? null : sanitizeForTerminal2(body.deployment_model)
309
349
  };
310
350
  }
311
351
  function checkPinConflict(pinned, discovered, acceptNewInstance) {
312
352
  if (!pinned || sameHost(pinned, discovered) || acceptNewInstance === true) {
313
353
  return null;
314
354
  }
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
- };
355
+ return failure(
356
+ "pin_conflict",
357
+ `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.`,
358
+ { pinned_url: pinned, discovered_url: discovered }
359
+ );
322
360
  }
323
361
  async function discoverInstance2(io = {}) {
324
362
  const env = io.env ?? process.env;
325
363
  const doFetch = io.fetchImpl ?? fetch;
326
364
  const license = resolveLicense(io);
327
365
  if (license.value === null) {
328
- return { ok: false, reason: "no_license", hint: license.hint };
366
+ return failure("no_license", license.hint);
329
367
  }
330
368
  const pinned = readPinnedUrl(io);
331
369
  if (pinned && io.forceDiscovery !== true) {
@@ -342,11 +380,10 @@ var require_discover_instance = __commonJS({
342
380
  }
343
381
  const { instance_url: discovered, deployment_model: deploymentModel } = discovery;
344
382
  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
- };
383
+ return failure(
384
+ "insecure_instance_url",
385
+ `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.`
386
+ );
350
387
  }
351
388
  const conflict = checkPinConflict(pinned, discovered, io.acceptNewInstance);
352
389
  if (conflict) {
@@ -355,7 +392,7 @@ var require_discover_instance = __commonJS({
355
392
  if (!pinned || !sameHost(pinned, discovered)) {
356
393
  const pinResult = pinInstanceUrl(io, discovered);
357
394
  if (!pinResult.ok) {
358
- return { ok: false, reason: "discovery_unreachable", hint: pinResult.error };
395
+ return failure("discovery_unreachable", pinResult.error);
359
396
  }
360
397
  }
361
398
  return {
@@ -436,6 +473,27 @@ var require_npmrc_license = __commonJS({
436
473
  const status = foreignScope ? "declined" : changed ? "seeded" : "already";
437
474
  return { content: body, changed, replacedLegacy, foreignScope, foreignScopeTarget, status };
438
475
  }
476
+ function inspectNpmrcLicense(content) {
477
+ const src = String(content ?? "");
478
+ let scopeActive = false;
479
+ let authActive = false;
480
+ let foreignScopeTarget = null;
481
+ for (const raw of src.split("\n")) {
482
+ const trimmed = raw.trim();
483
+ if (trimmed.length === 0 || isCommentLine(trimmed)) continue;
484
+ if (trimmed === SCOPE_LINE) {
485
+ scopeActive = true;
486
+ continue;
487
+ }
488
+ if (trimmed.startsWith(`${DIST_AUTH_KEY}:_authToken=`)) {
489
+ if (trimmed.length > `${DIST_AUTH_KEY}:_authToken=`.length) authActive = true;
490
+ continue;
491
+ }
492
+ const foreignTarget = foreignScopeTargetOf(trimmed);
493
+ if (foreignTarget !== null && !ownsLine(trimmed)) foreignScopeTarget = foreignTarget;
494
+ }
495
+ return { seeded: scopeActive && authActive, foreignScopeTarget };
496
+ }
439
497
  function writeNpmrcLicense2(license, io) {
440
498
  const target = path2.join(io.homedir, ".npmrc");
441
499
  let existing = "";
@@ -453,7 +511,14 @@ var require_npmrc_license = __commonJS({
453
511
  }
454
512
  return { ...result, path: target };
455
513
  }
456
- module2.exports = { applyNpmrcLicense, writeNpmrcLicense: writeNpmrcLicense2, SCOPE_LINE, authLine, DIST_HOST };
514
+ module2.exports = {
515
+ applyNpmrcLicense,
516
+ inspectNpmrcLicense,
517
+ writeNpmrcLicense: writeNpmrcLicense2,
518
+ SCOPE_LINE,
519
+ authLine,
520
+ DIST_HOST
521
+ };
457
522
  }
458
523
  });
459
524
 
@@ -911,6 +976,22 @@ var require_exec_cli = __commonJS({
911
976
  }
912
977
  });
913
978
 
979
+ // ../forge-plugin/lib/prompt-abort.js
980
+ var require_prompt_abort = __commonJS({
981
+ "../forge-plugin/lib/prompt-abort.js"(exports2, module2) {
982
+ var PROMPT_ABORTED = "PROMPT_ABORTED";
983
+ function promptAbortError2(what = "prompt") {
984
+ const err = new Error(`${what} aborted (Ctrl-C or end of input).`);
985
+ err.code = PROMPT_ABORTED;
986
+ return err;
987
+ }
988
+ function isPromptAbort2(err) {
989
+ return Boolean(err) && err.code === PROMPT_ABORTED;
990
+ }
991
+ module2.exports = { PROMPT_ABORTED, promptAbortError: promptAbortError2, isPromptAbort: isPromptAbort2 };
992
+ }
993
+ });
994
+
914
995
  // src/setup.js
915
996
  var os = require("node:os");
916
997
  var path = require("node:path");
@@ -924,6 +1005,8 @@ var {
924
1005
  assertSafeVersion
925
1006
  } = require_plugin_bundle();
926
1007
  var { resolveSpawn } = require_exec_cli();
1008
+ var { sanitizeForTerminal } = require_sanitize_terminal();
1009
+ var { isPromptAbort, promptAbortError } = require_prompt_abort();
927
1010
  var LICENSE_RE = /^forge_lic_[0-9A-Za-z]{43}$/;
928
1011
  var DEFAULT_LICENSING_URL = "https://licensing.bigbrainforge.com";
929
1012
  var DEFAULT_DIST_URL = "https://licensing.bigbrainforge.com";
@@ -937,26 +1020,52 @@ function runShim(io, cmd, args) {
937
1020
  function errMessage(err) {
938
1021
  return err && err.message ? err.message : String(err);
939
1022
  }
1023
+ async function promptHidden(q, { input = process.stdin, output = process.stdout } = {}) {
1024
+ const readline = require("node:readline");
1025
+ return new Promise((res, rej) => {
1026
+ const rl = readline.createInterface({ input, output, terminal: true });
1027
+ let settled = false;
1028
+ rl.on("close", () => {
1029
+ if (settled) return;
1030
+ settled = true;
1031
+ output.write("\n");
1032
+ rej(promptAbortError("license prompt"));
1033
+ });
1034
+ const orig = rl._writeToOutput.bind(rl);
1035
+ const columnsAt = (k) => {
1036
+ const saved = rl.cursor;
1037
+ rl.cursor = k;
1038
+ const pos = rl.getCursorPos();
1039
+ rl.cursor = saved;
1040
+ return pos.rows === 0 ? pos.cols : pos.rows * rl.columns + pos.cols;
1041
+ };
1042
+ rl._writeToOutput = (s) => {
1043
+ if (s.includes("\n") || s.includes("\r")) return orig("");
1044
+ if (s.startsWith(q)) {
1045
+ return orig(q + "*".repeat(Math.max(0, columnsAt(rl.line.length) - columnsAt(0))));
1046
+ }
1047
+ const end = rl.cursor;
1048
+ const start = Math.max(0, end - s.length);
1049
+ if (rl.line.slice(start, end) === s) {
1050
+ return orig("*".repeat(Math.max(0, columnsAt(end) - columnsAt(start))));
1051
+ }
1052
+ return orig(s.replace(/[^ ]/g, "*"));
1053
+ };
1054
+ rl.question(q, (a) => {
1055
+ settled = true;
1056
+ rl.close();
1057
+ output.write("\n");
1058
+ res(a.trim());
1059
+ });
1060
+ });
1061
+ }
940
1062
  function defaultIo() {
941
1063
  return {
942
1064
  homedir: os.homedir(),
943
1065
  env: process.env,
944
1066
  print: (l) => process.stdout.write(`${l}
945
1067
  `),
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
- },
1068
+ promptHidden: (q) => promptHidden(q),
960
1069
  fetch: (url, opts) => globalThis.fetch(url, opts),
961
1070
  run: (cmd, args = [], opts = {}) => {
962
1071
  const { stdio, ...rest } = opts;
@@ -969,15 +1078,19 @@ function defaultIo() {
969
1078
  }
970
1079
  };
971
1080
  }
1081
+ function say(io, line) {
1082
+ io.print(sanitizeForTerminal(line));
1083
+ }
972
1084
  function fail(io, tier, message, fix) {
973
- io.print("");
974
- io.print(`\u2717 [${tier}] ${message}`);
975
- if (fix) io.print(` fix: ${fix}`);
1085
+ say(io, "");
1086
+ say(io, `\u2717 [${tier}] ${message}`);
1087
+ if (fix) say(io, ` fix: ${fix}`);
976
1088
  return 1;
977
1089
  }
978
1090
  async function runSetup(argv, io = defaultIo()) {
979
1091
  const args = argv.filter((a) => !a.startsWith("--"));
980
1092
  const acceptNewInstance = argv.includes("--accept-new-instance");
1093
+ const print = (line) => say(io, line);
981
1094
  const tarProbe = io.run("tar", ["--version"]);
982
1095
  if (tarProbe.error || tarProbe.status !== 0) {
983
1096
  return fail(
@@ -988,7 +1101,19 @@ async function runSetup(argv, io = defaultIo()) {
988
1101
  );
989
1102
  }
990
1103
  let license = args[0] ?? "";
991
- if (!license) license = await io.promptHidden("Paste your Forge license: ");
1104
+ if (!license) {
1105
+ try {
1106
+ license = await io.promptHidden("Paste your Forge license: ");
1107
+ } catch (err) {
1108
+ if (!isPromptAbort(err)) throw err;
1109
+ return fail(
1110
+ io,
1111
+ "credential",
1112
+ "setup was cancelled at the license prompt \u2014 nothing on this machine was changed",
1113
+ `rerun when you have the license to hand: ${RERUN_SETUP_CMD}`
1114
+ );
1115
+ }
1116
+ }
992
1117
  if (!LICENSE_RE.test(license)) {
993
1118
  return fail(
994
1119
  io,
@@ -1006,11 +1131,12 @@ async function runSetup(argv, io = defaultIo()) {
1006
1131
  `fix the named problem (usually permissions on ~/.claude/forge), then rerun: ${RERUN_SETUP_CMD}`
1007
1132
  );
1008
1133
  }
1009
- io.print(`\u2713 license stored (${wrote.path})`);
1134
+ print(`\u2713 license stored (${wrote.path})`);
1010
1135
  const licensingUrl = io.env.FORGE_LICENSING_URL || DEFAULT_LICENSING_URL;
1011
1136
  const discoveryFetch = async (url, opts) => {
1012
1137
  const res = await io.fetch(url, opts);
1013
- return { ...res, ok: res.ok ?? (res.status >= 200 && res.status < 300) };
1138
+ if (res.ok !== void 0) return res;
1139
+ return { ...res, ok: res.status >= 200 && res.status < 300 };
1014
1140
  };
1015
1141
  const discovered = await discoverInstance({
1016
1142
  homedir: io.homedir,
@@ -1034,7 +1160,7 @@ async function runSetup(argv, io = defaultIo()) {
1034
1160
  ) : `check network egress to ${licensingUrl} (it must be on your allowlist)`
1035
1161
  );
1036
1162
  }
1037
- io.print(`\u2713 instance pinned: ${discovered.instance_url} (${discovered.deployment_model})`);
1163
+ print(`\u2713 instance pinned: ${discovered.instance_url} (${discovered.deployment_model})`);
1038
1164
  let npmrc;
1039
1165
  try {
1040
1166
  npmrc = writeNpmrcLicense(license, { homedir: io.homedir, env: io.env });
@@ -1048,11 +1174,11 @@ async function runSetup(argv, io = defaultIo()) {
1048
1174
  }
1049
1175
  if (npmrc.status === "declined") {
1050
1176
  const legacyNote = npmrc.replacedLegacy ? " (a legacy npm.pkg.github.com credential line found alongside it was removed)" : "";
1051
- io.print(
1177
+ print(
1052
1178
  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
1179
  );
1054
1180
  } else {
1055
- io.print(
1181
+ print(
1056
1182
  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
1183
  );
1058
1184
  }
@@ -1148,7 +1274,7 @@ async function runSetup(argv, io = defaultIo()) {
1148
1274
  `the download may be corrupted or tampered \u2014 rerun to retry: ${RERUN_SETUP_CMD} (if it persists, contact your Forge administrator)`
1149
1275
  );
1150
1276
  }
1151
- io.print(`\u2713 plugin bundle ${manifest.version} verified (sha256) and staged at ${staged.target}`);
1277
+ print(`\u2713 plugin bundle ${manifest.version} verified (sha256) and staged at ${staged.target}`);
1152
1278
  const root = bundleRoot(io.homedir);
1153
1279
  const addRes = runShim(io, "claude", ["plugin", "marketplace", "add", root]);
1154
1280
  const installRes = addRes.status === 0 ? runShim(io, "claude", ["plugin", "install", "forge@forge"]) : addRes;
@@ -1157,19 +1283,19 @@ async function runSetup(argv, io = defaultIo()) {
1157
1283
  ${installRes.stderr || ""}`
1158
1284
  );
1159
1285
  if (addRes.status === 0 && installRes.status === 0) {
1160
- io.print("\u2713 plugin installed from the local Forge marketplace");
1286
+ print("\u2713 plugin installed from the local Forge marketplace");
1161
1287
  } else if (alreadyRegistered) {
1162
- io.print("\u2713 plugin already installed from the local Forge marketplace \u2014 nothing to do");
1288
+ print("\u2713 plugin already installed from the local Forge marketplace \u2014 nothing to do");
1163
1289
  } 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("");
1290
+ print("");
1291
+ print("! Claude Code CLI not reachable from this shell \u2014 run these two commands yourself:");
1292
+ print("");
1293
+ print(` claude plugin marketplace add "${root}"`);
1294
+ print(" claude plugin install forge@forge");
1295
+ print("");
1170
1296
  }
1171
1297
  const bundledBootstrap = path.join(staged.target, "scripts", "bootstrap.js");
1172
- io.print("Handing off to the Forge plugin bootstrap (server check + issue tracker)\u2026");
1298
+ print("Handing off to the Forge plugin bootstrap (server check + issue tracker)\u2026");
1173
1299
  const hand = io.run(process.execPath, [bundledBootstrap], { stdio: "inherit" });
1174
1300
  if (hand.status !== 0) {
1175
1301
  return fail(
@@ -1179,15 +1305,15 @@ ${installRes.stderr || ""}`
1179
1305
  `rerun it yourself: node "${bundledBootstrap}"`
1180
1306
  );
1181
1307
  }
1182
- io.print("");
1183
- io.print("Done. Open Claude Code \u2014 Forge is ready.");
1308
+ print("");
1309
+ print("Done. Open Claude Code \u2014 Forge is ready.");
1184
1310
  return 0;
1185
1311
  }
1186
- module.exports = { runSetup, LICENSE_RE, defaultIo, RERUN_SETUP_CMD };
1312
+ module.exports = { runSetup, LICENSE_RE, defaultIo, promptHidden, RERUN_SETUP_CMD };
1187
1313
  if (require.main === module) {
1188
1314
  runSetup(process.argv.slice(2)).then((code) => process.exit(code)).catch((err) => {
1189
1315
  process.stderr.write(`
1190
- \u2717 [unexpected] ${errMessage(err)}
1316
+ \u2717 [unexpected] ${sanitizeForTerminal(errMessage(err))}
1191
1317
  `);
1192
1318
  process.stderr.write(
1193
1319
  " 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.21.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",
@@ -12,5 +12,5 @@
12
12
  },
13
13
  "engines": { "node": ">=24.18.1", "pnpm": ">=11.0.0" },
14
14
  "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" },
15
- "devDependencies": { "esbuild": "0.28.1" }
15
+ "devDependencies": { "esbuild": "0.28.2" }
16
16
  }