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 +18 -0
- package/bin/switchboard.mjs +103 -47
- package/package.json +1 -1
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
|
package/bin/switchboard.mjs
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
|
-
`
|
|
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
|
-
|
|
1536
|
-
|
|
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
|
|
3999
|
-
const
|
|
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(
|
|
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(
|
|
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
|
-
? `
|
|
4046
|
-
:
|
|
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:
|
|
4054
|
-
?
|
|
4055
|
-
:
|
|
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:
|
|
4062
|
-
?
|
|
4063
|
-
:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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();
|