switchboard-fyi 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable Switchboard CLI changes are recorded here. Keep this file in sync
4
4
  with the hosted release notes at `https://www.switchboard.fyi/release/latest`.
5
5
 
6
+ ## 0.2.2 - 2026-05-26
7
+
8
+ ### Fixed
9
+
10
+ - Regenerated `codex` and `claude` shims now launch Switchboard through stable
11
+ `switchboard-fyi`/`switchboard` commands before falling back to direct script
12
+ paths, so Homebrew and npm upgrades no longer leave shims pointing at removed
13
+ versioned install directories.
14
+
15
+ ## 0.2.1 - 2026-05-26
16
+
17
+ ### Changed
18
+
19
+ - Require `switchboard login` before local routing, observe mode, dashboard,
20
+ install, uninstall, settings, and difficulty mapping commands do any work.
21
+ - Updated the signed-out home and status screens to point users toward login
22
+ instead of showing local routing stats or enabled actions.
23
+
6
24
  ## 0.2.0 - 2026-05-26
7
25
 
8
26
  ### Changed
@@ -42,6 +42,7 @@ const HARNESS_COMMANDS = {
42
42
  codex: "codex",
43
43
  claude: "claude",
44
44
  };
45
+ const SWITCHBOARD_COMMAND_NAMES = ["switchboard-fyi", "switchboard"];
45
46
  const SHIM_PATH_START = "# >>> switchboard PATH >>>";
46
47
  const SHIM_PATH_END = "# <<< switchboard PATH <<<";
47
48
  const COMPONENT_STATUS_IDS = ["claude", "codex", "api"];
@@ -516,6 +517,7 @@ async function runCodex(argv) {
516
517
  return runRealHarnessDirect("codex", codexBinary, argv, {
517
518
  env: directHarnessEnv(),
518
519
  });
520
+ requireSwitchboardAuth();
519
521
  const observeMode =
520
522
  requestedObserve ||
521
523
  activeState.mode === "observe";
@@ -726,6 +728,7 @@ async function runClaude(argv) {
726
728
  return runRealHarnessDirect("claude", claudeBinary, argv, {
727
729
  env: directClaudeEnv(),
728
730
  });
731
+ requireSwitchboardAuth();
729
732
  const observeMode =
730
733
  requestedObserve ||
731
734
  activeState.mode === "observe";
@@ -1110,6 +1113,25 @@ function switchboardScriptPath() {
1110
1113
  return path.resolve(decodeURIComponent(new URL(import.meta.url).pathname));
1111
1114
  }
1112
1115
 
1116
+ function isVersionedSwitchboardInstallPath(file) {
1117
+ const normalized = String(file || "").split(path.sep).join("/");
1118
+ return (
1119
+ normalized.includes("/Cellar/switchboard-fyi/") ||
1120
+ normalized.includes("/.npm/_npx/") ||
1121
+ normalized.includes("/_npx/")
1122
+ );
1123
+ }
1124
+
1125
+ function stableSwitchboardCommandPath() {
1126
+ for (const name of SWITCHBOARD_COMMAND_NAMES) {
1127
+ for (const candidate of commandPaths(name)) {
1128
+ if (!candidate || isVersionedSwitchboardInstallPath(candidate)) continue;
1129
+ if (isUsableExecutable(candidate)) return candidate;
1130
+ }
1131
+ }
1132
+ return "";
1133
+ }
1134
+
1113
1135
  function readInstallState() {
1114
1136
  try {
1115
1137
  const parsed = JSON.parse(fs.readFileSync(installStatePath(), "utf8"));
@@ -1516,12 +1538,36 @@ function installTargets(argv) {
1516
1538
  function writeHarnessShim(harness, realPath) {
1517
1539
  const commandName = HARNESS_COMMANDS[harness];
1518
1540
  const switchboardCommand = harness === "codex" ? "codex" : "claude-code";
1541
+ const switchboardCommandPath = stableSwitchboardCommandPath();
1542
+ const fallbackNodePath = process.execPath;
1543
+ const fallbackScriptPath = switchboardScriptPath();
1519
1544
  const content = [
1520
1545
  "#!/bin/sh",
1521
1546
  "# switchboard-shim",
1522
1547
  `export SWITCHBOARD_SHIM_HARNESS=${shellQuote(harness)}`,
1523
1548
  `export ${envRealBinaryName(harness)}=${shellQuote(realPath)}`,
1524
- `exec ${shellQuote(process.execPath)} ${shellQuote(switchboardScriptPath())} ${shellQuote(switchboardCommand)} "$@"`,
1549
+ `SWITCHBOARD_CLI=${shellQuote(switchboardCommandPath)}`,
1550
+ 'if [ -n "${SWITCHBOARD_CLI_PATH:-}" ]; then',
1551
+ ` SWITCHBOARD_CLI="$SWITCHBOARD_CLI_PATH"`,
1552
+ "fi",
1553
+ `if [ -n "$SWITCHBOARD_CLI" ] && [ -x "$SWITCHBOARD_CLI" ]; then`,
1554
+ ` exec "$SWITCHBOARD_CLI" ${shellQuote(switchboardCommand)} "$@"`,
1555
+ "fi",
1556
+ `for SWITCHBOARD_CANDIDATE in ${SWITCHBOARD_COMMAND_NAMES.join(" ")}; do`,
1557
+ ` SWITCHBOARD_CLI=$(command -v "$SWITCHBOARD_CANDIDATE" 2>/dev/null || true)`,
1558
+ ` if [ -n "$SWITCHBOARD_CLI" ] && [ -x "$SWITCHBOARD_CLI" ]; then`,
1559
+ ` exec "$SWITCHBOARD_CLI" ${shellQuote(switchboardCommand)} "$@"`,
1560
+ " fi",
1561
+ "done",
1562
+ `if [ -x ${shellQuote(fallbackNodePath)} ] && [ -f ${shellQuote(fallbackScriptPath)} ]; then`,
1563
+ ` exec ${shellQuote(fallbackNodePath)} ${shellQuote(fallbackScriptPath)} ${shellQuote(switchboardCommand)} "$@"`,
1564
+ "fi",
1565
+ `SWITCHBOARD_NODE=$(command -v node 2>/dev/null || true)`,
1566
+ `if [ -n "$SWITCHBOARD_NODE" ] && [ -f ${shellQuote(fallbackScriptPath)} ]; then`,
1567
+ ` exec "$SWITCHBOARD_NODE" ${shellQuote(fallbackScriptPath)} ${shellQuote(switchboardCommand)} "$@"`,
1568
+ "fi",
1569
+ `echo "Switchboard shim could not find the Switchboard CLI. Reinstall Switchboard, then run \`switchboard install ${harness}\`." >&2`,
1570
+ "exit 127",
1525
1571
  "",
1526
1572
  ].join("\n");
1527
1573
 
@@ -1532,8 +1578,9 @@ function writeHarnessShim(harness, realPath) {
1532
1578
  command: commandName,
1533
1579
  shimPath: shimPath(harness),
1534
1580
  realPath,
1535
- switchboardScript: switchboardScriptPath(),
1536
- nodePath: process.execPath,
1581
+ switchboardCommandPath,
1582
+ switchboardScript: fallbackScriptPath,
1583
+ nodePath: fallbackNodePath,
1537
1584
  installedAt: new Date().toISOString(),
1538
1585
  };
1539
1586
  }
@@ -2142,6 +2189,7 @@ function renderHarnessEffortChoices(harness, model, value, active = false) {
2142
2189
  }
2143
2190
 
2144
2191
  async function showHarnessSettingsScreen(harness) {
2192
+ requireSwitchboardAuth();
2145
2193
  const config = ensureConfigFile();
2146
2194
  const levels = [...DIFFICULTY_LEVELS].sort((a, b) => b - a);
2147
2195
  const models = harnessModelOptions(harness);
@@ -2301,6 +2349,7 @@ function showDifficulty(argv = []) {
2301
2349
  return;
2302
2350
  }
2303
2351
  if (action !== "set") throw new Error(`Unknown difficulty command: ${action}`);
2352
+ requireSwitchboardAuth();
2304
2353
  const harness = assertHarness(argv.shift());
2305
2354
  const level = Number(argv.shift() || "");
2306
2355
  const model = String(argv.shift() || "").trim();
@@ -2762,6 +2811,13 @@ function authToken() {
2762
2811
  return process.env.SWITCHBOARD_API_TOKEN || readAuth()?.token || "";
2763
2812
  }
2764
2813
 
2814
+ const SIGN_IN_REQUIRED_MESSAGE = "Not logged in. Run `switchboard login` first.";
2815
+
2816
+ function requireSwitchboardAuth() {
2817
+ if (authToken()) return;
2818
+ throw new Error(SIGN_IN_REQUIRED_MESSAGE);
2819
+ }
2820
+
2765
2821
  const ACCOUNT_CACHE_TTL_MS = 30_000;
2766
2822
  const HOME_ACCOUNT_REFRESH_TIMEOUT_MS = 5000;
2767
2823
  let _balanceCache = null;
@@ -3990,13 +4046,15 @@ function switchboardAccountCard() {
3990
4046
  }
3991
4047
 
3992
4048
  function homeMenuOptions(installed, install = installSnapshot()) {
4049
+ const loggedIn = Boolean(authToken());
3993
4050
  const harnessCard = (key, displayName) => {
3994
4051
  const info = install.harnesses[key] || {};
3995
4052
  const present = Boolean(installed[key] || info.realAvailable);
3996
4053
  const installedShim = Boolean(info.installed);
3997
4054
  const active = activeHarnessState(key);
3998
- const startDisabled = active.active;
3999
- const observeDisabled = active.active;
4055
+ const signInDescription = `sign in first to use ${displayName}`;
4056
+ const startDisabled = active.active || !loggedIn;
4057
+ const observeDisabled = active.active || !loggedIn;
4000
4058
 
4001
4059
  let tag, tagTone;
4002
4060
  if (active.mode === "live") {
@@ -4021,12 +4079,20 @@ function homeMenuOptions(installed, install = installSnapshot()) {
4021
4079
  if (installedShim) {
4022
4080
  opts.push({
4023
4081
  type: "card-row",
4024
- content: ` ${dim(`switchboard is ready for ${displayName}`)}`,
4082
+ content: ` ${dim(
4083
+ loggedIn
4084
+ ? `switchboard is ready for ${displayName}`
4085
+ : `sign in before using switchboard for ${displayName}`,
4086
+ )}`,
4025
4087
  });
4026
4088
  } else if (present) {
4027
4089
  opts.push({
4028
4090
  type: "card-row",
4029
- content: ` ${dim(`${displayName} is on your computer but not connected to switchboard yet`)}`,
4091
+ content: ` ${dim(
4092
+ loggedIn
4093
+ ? `${displayName} is on your computer but not connected to switchboard yet`
4094
+ : `${displayName} is on your computer — sign in before connecting switchboard`,
4095
+ )}`,
4030
4096
  });
4031
4097
  } else {
4032
4098
  opts.push({
@@ -4040,38 +4106,50 @@ function homeMenuOptions(installed, install = installSnapshot()) {
4040
4106
  opts.push({
4041
4107
  label: "set up",
4042
4108
  value: `${key}-install`,
4043
- disabled: !present,
4044
- description: present
4045
- ? `start saving money with ${displayName}`
4046
- : `install ${displayName} on your computer first`,
4109
+ disabled: !present || !loggedIn,
4110
+ description: !present
4111
+ ? `install ${displayName} on your computer first`
4112
+ : loggedIn
4113
+ ? `connect switchboard for ${displayName}`
4114
+ : `sign in first to set up ${displayName}`,
4047
4115
  });
4048
4116
  } else {
4049
4117
  opts.push(
4050
4118
  {
4051
4119
  label: "start",
4052
4120
  value: `${key}-start`,
4053
- description: active.active
4054
- ? `close the other window first`
4055
- : `turn on switchboard for ${displayName}`,
4121
+ description: !loggedIn
4122
+ ? signInDescription
4123
+ : active.active
4124
+ ? `close the other window first`
4125
+ : `turn on switchboard for ${displayName}`,
4056
4126
  disabled: startDisabled,
4057
4127
  },
4058
4128
  {
4059
4129
  label: "observe",
4060
4130
  value: `${key}-observe`,
4061
- description: active.active
4062
- ? `close the other window first`
4063
- : `watch ${displayName} without changing anything`,
4131
+ description: !loggedIn
4132
+ ? signInDescription
4133
+ : active.active
4134
+ ? `close the other window first`
4135
+ : `watch ${displayName} without changing anything`,
4064
4136
  disabled: observeDisabled,
4065
4137
  },
4066
4138
  {
4067
4139
  label: "settings",
4068
4140
  value: `${key}-settings`,
4069
- description: `edit ${displayName} difficulty mappings`,
4141
+ description: loggedIn
4142
+ ? `edit ${displayName} difficulty mappings`
4143
+ : `sign in first to edit ${displayName} settings`,
4144
+ disabled: !loggedIn,
4070
4145
  },
4071
4146
  {
4072
4147
  label: "uninstall",
4073
4148
  value: `${key}-uninstall`,
4074
- description: `remove switchboard for ${displayName}`,
4149
+ description: loggedIn
4150
+ ? `remove switchboard for ${displayName}`
4151
+ : `sign in first to change ${displayName} setup`,
4152
+ disabled: !loggedIn,
4075
4153
  },
4076
4154
  );
4077
4155
  }
@@ -4130,6 +4208,7 @@ async function showActiveDashboard(
4130
4208
  activeMode = "live",
4131
4209
  options = {},
4132
4210
  ) {
4211
+ requireSwitchboardAuth();
4133
4212
  const targets = dashboardTargets(argv);
4134
4213
  const claim = claimActiveHarnesses(targets, activeMode);
4135
4214
  if (claim.conflicts.length) {
@@ -4447,6 +4526,7 @@ function showInstall(argv = []) {
4447
4526
  "Switchboard install currently supports macOS and Linux. Windows shims are not available in this release.",
4448
4527
  );
4449
4528
  }
4529
+ requireSwitchboardAuth();
4450
4530
  const noShellRc = argv.includes("--no-shell-rc");
4451
4531
  const forceShellRc = argv.includes("--shell-rc");
4452
4532
  const verbose =
@@ -4480,8 +4560,7 @@ function showInstall(argv = []) {
4480
4560
  );
4481
4561
  }
4482
4562
 
4483
- let shellRc = null;
4484
- if (!noShellRc || forceShellRc) shellRc = ensureShellPathBlock();
4563
+ if (!noShellRc || forceShellRc) ensureShellPathBlock();
4485
4564
 
4486
4565
  console.log(`✓ Switchboard installed`);
4487
4566
  for (const { harness } of installed) {
@@ -4490,14 +4569,6 @@ function showInstall(argv = []) {
4490
4569
  for (const item of skipped) {
4491
4570
  console.log(` ○ ${harnessLabel(item.harness)} not found`);
4492
4571
  }
4493
- console.log("");
4494
- if (noShellRc && !forceShellRc) {
4495
- console.log("Run this once in your shell:");
4496
- console.log(` export PATH=${shellQuote(shimDir())}:$PATH`);
4497
- } else if (shellRc) {
4498
- console.log("Open a new terminal, then use:");
4499
- console.log(` ${installed.map(({ entry }) => entry.command).join(" ")}`);
4500
- }
4501
4572
  ensureProcessPathHasShimDir();
4502
4573
  if (verbose) {
4503
4574
  console.log("");
@@ -4538,12 +4609,6 @@ function showUninstall(argv = []) {
4538
4609
  for (const harness of removed) {
4539
4610
  console.log(` ✓ ${harnessLabel(harness)}`);
4540
4611
  }
4541
- console.log("");
4542
- console.log(
4543
- anyInstalled
4544
- ? "Remaining installed tools still use Switchboard."
4545
- : "Your tools now run normally.",
4546
- );
4547
4612
  if (verbose) {
4548
4613
  console.log("");
4549
4614
  printInstallSummary({ verbose: true });
@@ -5234,18 +5299,7 @@ function renderStatusStrip(
5234
5299
  statLines = [` ${dim("checking account route stats…")}`];
5235
5300
  }
5236
5301
  } else {
5237
- const snapshot = statsSnapshot();
5238
- const totalReq = snapshot.rows.length;
5239
- const hasTraffic = totalReq > 0;
5240
- statLines = hasTraffic
5241
- ? [
5242
- statRow("local requests", compactNumber(totalReq)),
5243
- statRow(
5244
- "local tokens processed",
5245
- compactNumber(snapshot.processedTokens),
5246
- ),
5247
- ]
5248
- : [` ${dim("no routes yet — sign in and start routing")}`];
5302
+ statLines = [` ${dim("sign in to start routing with switchboard")}`];
5249
5303
  }
5250
5304
 
5251
5305
  let identityLine = "";
@@ -6706,11 +6760,13 @@ function routeTaskDisplay(row) {
6706
6760
  }
6707
6761
 
6708
6762
  async function showDashboard(argv, options = {}) {
6763
+ requireSwitchboardAuth();
6709
6764
  const harness = parseHarnessScope(argv);
6710
6765
  console.log(renderDashboard(harness));
6711
6766
  }
6712
6767
 
6713
6768
  async function showDashboardSession(argv, options = {}) {
6769
+ requireSwitchboardAuth();
6714
6770
  const harness = parseHarnessScope(argv);
6715
6771
  const exitOnClose = options.exitOnClose !== false;
6716
6772
  const sessionStart = new Date().toISOString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard-fyi",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Local model-routing CLI for Codex and Claude Code.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.switchboard.fyi",