switchboard-fyi 0.1.0 → 0.2.1

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 ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable Switchboard CLI changes are recorded here. Keep this file in sync
4
+ with the hosted release notes at `https://www.switchboard.fyi/release/latest`.
5
+
6
+ ## 0.2.1 - 2026-05-26
7
+
8
+ ### Changed
9
+
10
+ - Require `switchboard login` before local routing, observe mode, dashboard,
11
+ install, uninstall, settings, and difficulty mapping commands do any work.
12
+ - Updated the signed-out home and status screens to point users toward login
13
+ instead of showing local routing stats or enabled actions.
14
+
15
+ ## 0.2.0 - 2026-05-26
16
+
17
+ ### Changed
18
+
19
+ - Reworked the published README into a shorter install and quick-start guide.
20
+
21
+ ## 0.1.0 - 2026-05-26
22
+
23
+ ### Added
24
+
25
+ - First public `switchboard-fyi` CLI release on npm.
26
+ - Homebrew tap formula for `brew install switchboardfyi/tap/switchboard-fyi`.
27
+ - Local routing, observe mode, dashboard, model catalog, and update checks for
28
+ Codex and Claude Code workflows.
package/README.md CHANGED
@@ -1,538 +1,36 @@
1
1
  # Switchboard
2
2
 
3
- ## Local routing CLI
3
+ Switchboard picks the right model for your Codex and Claude Code requests, so you stop wasting expensive ones on easy tasks.
4
4
 
5
- Switchboard is designed to sit in front of normal `codex` and `claude` usage.
6
- The install step creates local PATH shims so those commands route through
7
- Switchboard anywhere in the terminal.
8
-
9
- Install from npm:
5
+ ## Install
10
6
 
11
7
  ```bash
12
8
  npm install -g switchboard-fyi
13
9
  ```
14
10
 
15
- Or install with Homebrew after the public tap is published:
16
-
17
- ```bash
18
- brew install switchboardfyi/tap/switchboard-fyi
19
- ```
20
-
21
- Installed CLI entrypoint:
22
-
23
- ```bash
24
- switchboard
25
- ```
26
-
27
- Bare `switchboard` opens the interactive local console. The published CLI
28
- supports macOS and Linux for launch; Windows shims are intentionally not
29
- included in this release.
30
-
31
- The interactive `start` action opens the live dashboard for that harness. It
32
- does not launch Codex or Claude Code.
33
-
34
- Harness state is runtime scoped:
35
-
36
- - `installed`: Switchboard is ready, but routing is off. Codex and Claude use
37
- your normal model settings.
38
- - `observe`: `switchboard observe <harness>` is running in a terminal. Harness
39
- calls go through Switchboard for logging, but models are preserved.
40
- - `routing`: `switchboard start <harness>` is running in a terminal. Harness calls
41
- go through Switchboard and can be routed according to the selected profile.
42
-
43
- Closing the `start` or `observe` terminal turns that harness back to
44
- `installed`.
45
-
46
- Only one `start` or `observe` terminal can own a harness at a time. If Codex or
47
- Claude Code is already active in another window, Switchboard refuses the second
48
- start and shows the owning process id.
49
-
50
- Only one wrapped Codex or Claude Code session can run through Switchboard for a
51
- harness at a time. That keeps local gateways tied to visible terminal sessions
52
- instead of accumulating in the background.
53
-
54
- The home screen is harness-first: before a shim is active, each harness only
55
- offers `install`; after install, it offers `start`, `observe`, `settings`, and
56
- `uninstall`.
57
-
58
- Install the shims:
11
+ ## Set up
59
12
 
60
13
  ```bash
61
14
  switchboard install
62
15
  ```
63
16
 
64
- This writes `codex` and `claude` wrappers to `~/.switchboard/bin`, records the
65
- real binary paths in `~/.switchboard/install.json`, and adds a managed PATH
66
- block to your shell rc file. Open a new terminal after install. Then use the
67
- tools normally:
68
-
69
- ```bash
70
- codex
71
- claude
72
- ```
73
-
74
- To install one harness or remove the shims:
75
-
76
- ```bash
77
- switchboard install codex
78
- switchboard install claude
79
- switchboard uninstall codex
80
- switchboard uninstall claude
81
- switchboard uninstall
82
- ```
83
-
84
- Settings expose each installed harness model set, including the difficulty
85
- `1..5` model map for each harness.
86
-
87
- Run Codex through the Responses model-provider proxy:
88
-
89
- ```bash
90
- codex
91
- ```
92
-
93
- Codex must be routed at the model-provider boundary, not the app-server
94
- `turn/start` boundary. The canonical path is:
95
-
96
- ```text
97
- codex -> Switchboard /v1/responses -> chatgpt.com/backend-api/codex/responses
98
- ```
99
-
100
- This preserves ChatGPT/Codex subscription auth locally and exposes each internal
101
- Codex model request to Switchboard.
102
- Switchboard v1 supports ChatGPT subscription auth only for Codex. API-key Codex
103
- requests are rejected locally before routing, without spending a Switchboard
104
- request credit or forwarding to `api.openai.com`.
105
-
106
- Run Claude Code through the Anthropic-compatible gateway:
107
-
108
- ```bash
109
- claude
110
- ```
111
-
112
- Switchboard v1 supports Claude Code subscription OAuth only, including
113
- `CLAUDE_CODE_OAUTH_TOKEN` from `claude setup-token`. Anthropic API-key,
114
- PAYG, Bedrock, Vertex, and Foundry auth paths are stripped or rejected before
115
- routing.
116
-
117
- Observe mode:
118
-
119
- ```bash
120
- --observe
121
- ```
122
-
123
- Switchboard API routing:
124
-
125
- ```bash
126
- switchboard login
127
- switchboard balance
128
- switchboard config use-switchboard-api https://api.switchboard.fyi
129
- ```
130
-
131
- The gateway sends a compact classification packet and local routing settings to
132
- the Switchboard API. The Cloudflare API owns the classifier prompt, checks
133
- credits, calls Workers AI, and returns difficulty `1..5`, a reason code, and a
134
- short dashboard task label. The local CLI maps that difficulty to the configured
135
- model for the active harness. A successful new API classification spends one request credit,
136
- including best and observe recommendations. It sends a compact routing summary,
137
- not the raw provider request, and the billing ledger does not store full
138
- prompts. If the API is unavailable or the account has no request credits, the
139
- dashboard records an explicit router error or insufficient-balance state.
140
-
141
- After that, this is enough:
142
-
143
- Terminal 1:
144
-
145
- ```bash
146
- switchboard start codex
147
- ```
148
-
149
- Terminal 2:
150
-
151
- ```bash
152
- codex \
153
- exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox \
154
- "Reply with exactly: router-ok"
155
- ```
156
-
157
- For an interactive Codex session, use:
158
-
159
- ```bash
160
- codex
161
- ```
162
-
163
- If the default local proxy port is already occupied, the wrapper automatically
164
- uses the next free port and prints the port it selected.
165
-
166
- Model catalog and difficulty map:
167
-
168
- ```bash
169
- switchboard models
170
- switchboard models list
171
- switchboard models codex
172
- switchboard models claude-code
173
- switchboard difficulty
174
- switchboard difficulty set codex 5 gpt-5.5 xhigh
175
- switchboard difficulty set claude-code 3 claude-sonnet-4-6 medium
176
- ```
177
-
178
- Switchboard classifies each request as difficulty `1..5`, then resolves that
179
- difficulty locally from the configured map for the active harness. Use
180
- `models` to inspect the bundled catalog and `difficulty set` to edit a single
181
- difficulty level. The default Codex map is:
182
-
183
- ```text
184
- 5 gpt-5.5 reasoning=xhigh
185
- 4 gpt-5.5 reasoning=medium
186
- 3 gpt-5.4 reasoning=medium
187
- 2 gpt-5.4-mini reasoning=medium
188
- 1 gpt-5.4-mini reasoning=low
189
- ```
190
-
191
- For Codex, the three main-model choices are `gpt-5.5 reasoning=xhigh`,
192
- `gpt-5.5 reasoning=high`, and `gpt-5.5 reasoning=medium`. For Claude Code,
193
- the default map routes lower difficulty requests to Haiku/Sonnet and higher
194
- difficulty requests to Opus.
195
-
196
- The model catalog lists standard uncached input and output billing rates per
197
- million tokens: Codex uses Codex credits, and Claude uses USD API pricing.
198
- The local CLI dashboard shows requests and provider token usage from local
199
- responses. Account status shows hosted request counts; hosted token totals are
200
- shown only when token accounting is available. It does not show cost estimates.
201
-
202
- The bundled model catalog is curated and updates with new Switchboard releases.
203
-
204
- CLI updates:
205
-
206
- ```bash
207
- switchboard update check
208
- switchboard update
209
- ```
210
-
211
- After the npm package is published, `switchboard update check` reads the npm
212
- registry and `switchboard update` runs the package-manager update flow. The CLI
213
- also checks periodically and can show a TTY-only update prompt:
214
-
215
- ```text
216
- ✨ Switchboard update available! 0.1.0 -> 0.1.1
217
-
218
- Release notes: https://switchboard.fyi/release/latest
219
-
220
- 1. Update Switchboard
221
- 2. Skip
222
- ```
223
-
224
- Skipping only applies to the current run; if the same version is still latest,
225
- Switchboard prompts again on the next interactive `switchboard` launch. Normal
226
- wrapped `codex` and `claude` runs never show this prompt. The latest npm result
227
- is cached in `~/.switchboard/update-state.json` so normal Switchboard launches
228
- do not hit the registry every time.
17
+ Open a new terminal after install.
229
18
 
230
- Component status:
19
+ ## Run it
231
20
 
232
21
  ```bash
233
- switchboard status
234
- switchboard status set codex needs-update "Codex harness needs a Switchboard update"
235
- switchboard status set claude down "Claude harness temporarily disabled"
236
- switchboard status set switchboard api degraded "Classifier API latency is elevated"
237
- switchboard status clear codex
238
- switchboard config set componentStatus.url https://status.example.com/switchboard.json
239
- switchboard status refresh
22
+ switchboard
240
23
  ```
241
24
 
242
- `status set` writes a local override to `~/.switchboard/config.json`. By default,
243
- remote component status is read from `<api.baseUrl>/v1/status`. That API-owned
244
- feed is the production source of truth for `claude`, `codex`, and `api`
245
- compatibility. Its steady state should be `ok`; `unknown` is only a local
246
- fallback when no status source has been read.
247
-
248
- Production incidents should be handled through the API status feed, not local
249
- overrides. The operational runbook is in
250
- `docs/component-status-runbook.md`.
25
+ Then keep using `codex` and `claude` exactly as you do today.
251
26
 
252
- Use API-level component status when an upstream Claude Code or Codex release
253
- breaks Switchboard compatibility, or when the Switchboard API itself is
254
- degraded. The CLI renders `ok` as a checkmark and `degraded`, `needs_update`,
255
- or `down` as attention states. For incidents that should use a different feed,
256
- publish a small JSON manifest at `componentStatus.url`; the CLI caches it in
257
- `~/.switchboard/component-status.json` and still allows local overrides for
258
- testing or emergency support.
259
-
260
- Manifest shape:
261
-
262
- ```json
263
- {
264
- "schemaVersion": 1,
265
- "updatedAt": "2026-05-19T12:00:00Z",
266
- "components": {
267
- "codex": { "state": "ok" },
268
- "claude": { "state": "needs-update", "message": "Run switchboard update before using Claude Code." },
269
- "api": { "state": "degraded", "message": "Classifier latency is elevated." }
270
- }
271
- }
272
- ```
273
-
274
- Logs:
27
+ ## Other handy commands
275
28
 
276
29
  ```bash
277
- switchboard watch codex
278
- switchboard watch claude-code
279
- switchboard inspect
280
- switchboard inspect --harness codex
281
- switchboard inspect --harness claude-code
282
- switchboard status codex
283
- switchboard config show
284
- switchboard dashboard --local codex
285
- switchboard dashboard --local claude-code
286
- switchboard watch codex
287
- switchboard logs codex
288
- tail -f ~/.switchboard/harnesses/codex/events.jsonl
289
- tail -f ~/.switchboard/harnesses/claude/events.jsonl
30
+ switchboard login # sign in for smart routing
31
+ switchboard status # check what's running
32
+ switchboard update # update Switchboard
33
+ switchboard uninstall # remove the shims
290
34
  ```
291
35
 
292
- Switchboard keeps runtime logs and health state per harness under
293
- `~/.switchboard/harnesses/<harness>/`, while global config remains shared.
294
- Use `switchboard inspect` for the local web inspector. It groups calls by
295
- `decisionId` and shows the routing payload, API/router response, applied
296
- route, forwarding status, and raw JSONL events.
297
-
298
- More detail is in `docs/mvp-usage.md`, `docs/codex-subscription-provider-proxy.md`,
299
- `docs/routing-api.md`, `docs/known-limitations.md`, and `docs/smoke-test.md`.
300
-
301
- ## 1-Page Product Spec
302
-
303
- Switchboard — 1-Page Product Spec
304
- Product summary
305
-
306
- Switchboard is an API-directed model-routing gateway for developers using tools like Claude Code, Codex, and future AI coding harnesses.
307
-
308
- Developers keep using their existing tools exactly as normal. Switchboard sits behind the harness as the model endpoint, asks the Switchboard API where to route, tries the recommended cheaper model when directed, and automatically falls back to the original model if the routed provider call fails before useful output is sent.
309
-
310
- Use the right model without thinking about it.
311
-
312
- Core promise
313
-
314
- Switchboard does one thing:
315
-
316
- Route when the Switchboard API says to route, and protect the developer with automatic original-call fallback.
317
-
318
- It does not rewrite prompts, compact context, modify tools, change agent behavior, or interfere with the developer’s workflow.
319
-
320
- Problem
321
-
322
- Developers love Claude Code and Codex because the harness is already excellent: repo awareness, shell access, tool use, patches, context handling, and workflow muscle memory.
323
-
324
- But these tools often use expensive models for tasks that do not need them:
325
-
326
- Rename this variable
327
- Write a PR description
328
- Explain this small error
329
- Summarize this diff
330
- Make this copy clearer
331
-
332
- At the same time, many tasks genuinely deserve the strongest model:
333
-
334
- Fix this auth bug
335
- Refactor this module
336
- Debug failing tests
337
- Think through this architecture
338
- Make a complex multi-file change
339
-
340
- Today, users have to manually decide which model is worth using. Most will not. So they either overspend or risk underpowering important work.
341
-
342
- Target users
343
-
344
- Initial users are AI-heavy developers, indie hackers, founders, and small teams who already use Claude Code, Codex, Cursor, or similar tools daily.
345
-
346
- They want:
347
-
348
- - lower AI model spend
349
- - less top-tier quota waste
350
- - no workflow migration
351
- - no degraded quality on hard tasks
352
- - simple override controls
353
- Non-goals
354
-
355
- Switchboard v1 should be intentionally narrow.
356
-
357
- It should not do:
358
-
359
- - prompt rewriting
360
- - context compaction
361
- - tool blocking
362
- - repo analysis
363
- - validation loops
364
- - custom agent behavior
365
- - coding-agent replacement
366
- - new IDE/chat interface
367
-
368
- The trust boundary is simple:
369
-
370
- Switchboard only chooses the destination model.
371
-
372
- How it works
373
- Claude Code / Codex
374
-
375
- Switchboard Gateway
376
-
377
- Route Classifier
378
-
379
- Lower-cost model OR strongest model
380
-
381
- Response streams back to original harness
382
-
383
- The user configures their tool to use Switchboard as the model endpoint.
384
-
385
- Example model alias:
386
-
387
- model = "auto"
388
-
389
- Switchboard maps `auto` to the model configured for the API-assigned
390
- difficulty. It maps `auto` to the configured best model when preserved,
391
- uncertain, or falling back.
392
- Routing policy
393
-
394
- Switchboard classifies each request into difficulty `1..5`:
395
-
396
- - `1` → trivial, exact, mechanical, or simple text work
397
- - `2` → light routine text or bounded work
398
- - `3` → normal bounded coding or analysis
399
- - `4` → substantial multi-step, tool-driven, or multi-file work
400
- - `5` → hard debugging, architecture, large context, or high-risk work
401
-
402
- The API owns difficulty classification. The CLI owns model selection,
403
- execution, retry, and narrow routed-path cooldowns after real routed failures.
404
-
405
- Default principle:
406
-
407
- Route to save money when the API recommends it; fall back to the original model when execution proves that route unhealthy.
408
-
409
- Lower-tier examples
410
- - naming
411
- - simple rewrites
412
- - small explanations
413
- - commit messages
414
- - PR descriptions
415
- - formatting
416
- - command generation
417
-
418
- Mid-tier examples
419
- - summarizing short diffs
420
- - simple terminal-output explanation
421
- - large-context summarization
422
- - straightforward single-file edits
423
- - routine config/docs work
424
-
425
- Best-tier examples
426
- - bug fixing
427
- - auth/payments/security work
428
- - refactors
429
- - multi-file edits
430
- - test failures
431
- - architecture decisions
432
- - unclear requirements
433
- - consequential security review
434
- - repeated failed attempts
435
- - anything the router is unsure about
436
- Product modes
437
- 1. Observe
438
-
439
- No routing. Switchboard only reports what it would have done.
440
-
441
- 12 calls would have been preserved on the strongest model.
442
- 7 calls could likely have used cheaper routing.
443
- 2. Auto
444
-
445
- Switchboard automatically routes obvious low-risk calls down and preserves the strongest model for everything else.
446
-
447
- Three routing modes:
448
-
449
- - `Quality`: prioritize the strongest model for harder work while still routing clear low-risk waste
450
- - `Balanced`: recommended default for everyday sessions
451
- - `Saver`: route more aggressively to reduce spend while still protecting high-risk work
452
- User experience
453
-
454
- The product should be quiet.
455
-
456
- The developer still runs:
457
-
458
- claude
459
-
460
- or:
461
-
462
- codex
463
-
464
- Optional session receipt:
465
-
466
- Disabled by default. Enable it with:
467
-
468
- ```bash
469
- switchboard config set sessionReceipt.enabled true
470
- ```
471
-
472
- Switchboard session receipt
473
-
474
- Requests: 8
475
- Tokens processed: 42k
476
- Cheap-route retries: 0
477
-
478
- Power-user overrides:
479
-
480
- auto
481
- lower
482
- best
483
- MVP
484
- Must-have
485
- - gateway endpoint
486
- - Claude Code / Codex setup instructions
487
- - model alias: auto
488
- - API-directed route classifier
489
- - difficulty-to-model mapping
490
- - streaming response passthrough
491
- - request/cost logging
492
- - session receipt
493
- - user override: force best
494
- Should-have
495
- - observe mode
496
- - auto mode
497
- - simple dashboard
498
- - per-project settings
499
- - routed-request regret tracking
500
- Success metrics
501
-
502
- Primary metric:
503
-
504
- Best-tier requests avoided without increasing retries.
505
-
506
- Track:
507
-
508
- - strongest-model requests avoided
509
- - tokens processed
510
- - routed-request retry rate
511
- - user override rate
512
- - router disable rate
513
- - preserved-request rate
514
- - cost per accepted task
515
-
516
- Most important quality metric:
517
-
518
- Lower-tier regret rate
519
-
520
- Meaning:
521
-
522
- How often did a user rerun, override, or escalate after Switchboard used a lower-cost tier?
523
-
524
- That number must stay low.
525
-
526
- Positioning
527
-
528
- Simple version:
529
-
530
- Switchboard routes AI coding requests through the Switchboard API and falls back to the original model when a routed provider call fails.
531
-
532
- Developer version:
533
-
534
- An API-directed model-routing layer for Claude Code, Codex, and other AI coding harnesses. Keep your workflow. Cut wasted top-tier calls. Fall back safely.
535
-
536
- Punchier tagline:
537
-
538
- Use your strongest model where it matters.
36
+ More docs live at [switchboard.fyi](https://www.switchboard.fyi).
@@ -516,6 +516,7 @@ async function runCodex(argv) {
516
516
  return runRealHarnessDirect("codex", codexBinary, argv, {
517
517
  env: directHarnessEnv(),
518
518
  });
519
+ requireSwitchboardAuth();
519
520
  const observeMode =
520
521
  requestedObserve ||
521
522
  activeState.mode === "observe";
@@ -726,6 +727,7 @@ async function runClaude(argv) {
726
727
  return runRealHarnessDirect("claude", claudeBinary, argv, {
727
728
  env: directClaudeEnv(),
728
729
  });
730
+ requireSwitchboardAuth();
729
731
  const observeMode =
730
732
  requestedObserve ||
731
733
  activeState.mode === "observe";
@@ -2142,6 +2144,7 @@ function renderHarnessEffortChoices(harness, model, value, active = false) {
2142
2144
  }
2143
2145
 
2144
2146
  async function showHarnessSettingsScreen(harness) {
2147
+ requireSwitchboardAuth();
2145
2148
  const config = ensureConfigFile();
2146
2149
  const levels = [...DIFFICULTY_LEVELS].sort((a, b) => b - a);
2147
2150
  const models = harnessModelOptions(harness);
@@ -2301,6 +2304,7 @@ function showDifficulty(argv = []) {
2301
2304
  return;
2302
2305
  }
2303
2306
  if (action !== "set") throw new Error(`Unknown difficulty command: ${action}`);
2307
+ requireSwitchboardAuth();
2304
2308
  const harness = assertHarness(argv.shift());
2305
2309
  const level = Number(argv.shift() || "");
2306
2310
  const model = String(argv.shift() || "").trim();
@@ -2762,6 +2766,13 @@ function authToken() {
2762
2766
  return process.env.SWITCHBOARD_API_TOKEN || readAuth()?.token || "";
2763
2767
  }
2764
2768
 
2769
+ const SIGN_IN_REQUIRED_MESSAGE = "Not logged in. Run `switchboard login` first.";
2770
+
2771
+ function requireSwitchboardAuth() {
2772
+ if (authToken()) return;
2773
+ throw new Error(SIGN_IN_REQUIRED_MESSAGE);
2774
+ }
2775
+
2765
2776
  const ACCOUNT_CACHE_TTL_MS = 30_000;
2766
2777
  const HOME_ACCOUNT_REFRESH_TIMEOUT_MS = 5000;
2767
2778
  let _balanceCache = null;
@@ -3990,13 +4001,15 @@ function switchboardAccountCard() {
3990
4001
  }
3991
4002
 
3992
4003
  function homeMenuOptions(installed, install = installSnapshot()) {
4004
+ const loggedIn = Boolean(authToken());
3993
4005
  const harnessCard = (key, displayName) => {
3994
4006
  const info = install.harnesses[key] || {};
3995
4007
  const present = Boolean(installed[key] || info.realAvailable);
3996
4008
  const installedShim = Boolean(info.installed);
3997
4009
  const active = activeHarnessState(key);
3998
- const startDisabled = active.active;
3999
- const observeDisabled = active.active;
4010
+ const signInDescription = `sign in first to use ${displayName}`;
4011
+ const startDisabled = active.active || !loggedIn;
4012
+ const observeDisabled = active.active || !loggedIn;
4000
4013
 
4001
4014
  let tag, tagTone;
4002
4015
  if (active.mode === "live") {
@@ -4021,12 +4034,20 @@ function homeMenuOptions(installed, install = installSnapshot()) {
4021
4034
  if (installedShim) {
4022
4035
  opts.push({
4023
4036
  type: "card-row",
4024
- content: ` ${dim(`switchboard is ready for ${displayName}`)}`,
4037
+ content: ` ${dim(
4038
+ loggedIn
4039
+ ? `switchboard is ready for ${displayName}`
4040
+ : `sign in before using switchboard for ${displayName}`,
4041
+ )}`,
4025
4042
  });
4026
4043
  } else if (present) {
4027
4044
  opts.push({
4028
4045
  type: "card-row",
4029
- content: ` ${dim(`${displayName} is on your computer but not connected to switchboard yet`)}`,
4046
+ content: ` ${dim(
4047
+ loggedIn
4048
+ ? `${displayName} is on your computer but not connected to switchboard yet`
4049
+ : `${displayName} is on your computer — sign in before connecting switchboard`,
4050
+ )}`,
4030
4051
  });
4031
4052
  } else {
4032
4053
  opts.push({
@@ -4040,38 +4061,50 @@ function homeMenuOptions(installed, install = installSnapshot()) {
4040
4061
  opts.push({
4041
4062
  label: "set up",
4042
4063
  value: `${key}-install`,
4043
- disabled: !present,
4044
- description: present
4045
- ? `start saving money with ${displayName}`
4046
- : `install ${displayName} on your computer first`,
4064
+ disabled: !present || !loggedIn,
4065
+ description: !present
4066
+ ? `install ${displayName} on your computer first`
4067
+ : loggedIn
4068
+ ? `connect switchboard for ${displayName}`
4069
+ : `sign in first to set up ${displayName}`,
4047
4070
  });
4048
4071
  } else {
4049
4072
  opts.push(
4050
4073
  {
4051
4074
  label: "start",
4052
4075
  value: `${key}-start`,
4053
- description: active.active
4054
- ? `close the other window first`
4055
- : `turn on switchboard for ${displayName}`,
4076
+ description: !loggedIn
4077
+ ? signInDescription
4078
+ : active.active
4079
+ ? `close the other window first`
4080
+ : `turn on switchboard for ${displayName}`,
4056
4081
  disabled: startDisabled,
4057
4082
  },
4058
4083
  {
4059
4084
  label: "observe",
4060
4085
  value: `${key}-observe`,
4061
- description: active.active
4062
- ? `close the other window first`
4063
- : `watch ${displayName} without changing anything`,
4086
+ description: !loggedIn
4087
+ ? signInDescription
4088
+ : active.active
4089
+ ? `close the other window first`
4090
+ : `watch ${displayName} without changing anything`,
4064
4091
  disabled: observeDisabled,
4065
4092
  },
4066
4093
  {
4067
4094
  label: "settings",
4068
4095
  value: `${key}-settings`,
4069
- description: `edit ${displayName} difficulty mappings`,
4096
+ description: loggedIn
4097
+ ? `edit ${displayName} difficulty mappings`
4098
+ : `sign in first to edit ${displayName} settings`,
4099
+ disabled: !loggedIn,
4070
4100
  },
4071
4101
  {
4072
4102
  label: "uninstall",
4073
4103
  value: `${key}-uninstall`,
4074
- description: `remove switchboard for ${displayName}`,
4104
+ description: loggedIn
4105
+ ? `remove switchboard for ${displayName}`
4106
+ : `sign in first to change ${displayName} setup`,
4107
+ disabled: !loggedIn,
4075
4108
  },
4076
4109
  );
4077
4110
  }
@@ -4130,6 +4163,7 @@ async function showActiveDashboard(
4130
4163
  activeMode = "live",
4131
4164
  options = {},
4132
4165
  ) {
4166
+ requireSwitchboardAuth();
4133
4167
  const targets = dashboardTargets(argv);
4134
4168
  const claim = claimActiveHarnesses(targets, activeMode);
4135
4169
  if (claim.conflicts.length) {
@@ -4447,6 +4481,7 @@ function showInstall(argv = []) {
4447
4481
  "Switchboard install currently supports macOS and Linux. Windows shims are not available in this release.",
4448
4482
  );
4449
4483
  }
4484
+ requireSwitchboardAuth();
4450
4485
  const noShellRc = argv.includes("--no-shell-rc");
4451
4486
  const forceShellRc = argv.includes("--shell-rc");
4452
4487
  const verbose =
@@ -4480,8 +4515,7 @@ function showInstall(argv = []) {
4480
4515
  );
4481
4516
  }
4482
4517
 
4483
- let shellRc = null;
4484
- if (!noShellRc || forceShellRc) shellRc = ensureShellPathBlock();
4518
+ if (!noShellRc || forceShellRc) ensureShellPathBlock();
4485
4519
 
4486
4520
  console.log(`✓ Switchboard installed`);
4487
4521
  for (const { harness } of installed) {
@@ -4490,14 +4524,6 @@ function showInstall(argv = []) {
4490
4524
  for (const item of skipped) {
4491
4525
  console.log(` ○ ${harnessLabel(item.harness)} not found`);
4492
4526
  }
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
4527
  ensureProcessPathHasShimDir();
4502
4528
  if (verbose) {
4503
4529
  console.log("");
@@ -4538,12 +4564,6 @@ function showUninstall(argv = []) {
4538
4564
  for (const harness of removed) {
4539
4565
  console.log(` ✓ ${harnessLabel(harness)}`);
4540
4566
  }
4541
- console.log("");
4542
- console.log(
4543
- anyInstalled
4544
- ? "Remaining installed tools still use Switchboard."
4545
- : "Your tools now run normally.",
4546
- );
4547
4567
  if (verbose) {
4548
4568
  console.log("");
4549
4569
  printInstallSummary({ verbose: true });
@@ -5234,18 +5254,7 @@ function renderStatusStrip(
5234
5254
  statLines = [` ${dim("checking account route stats…")}`];
5235
5255
  }
5236
5256
  } 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")}`];
5257
+ statLines = [` ${dim("sign in to start routing with switchboard")}`];
5249
5258
  }
5250
5259
 
5251
5260
  let identityLine = "";
@@ -6706,11 +6715,13 @@ function routeTaskDisplay(row) {
6706
6715
  }
6707
6716
 
6708
6717
  async function showDashboard(argv, options = {}) {
6718
+ requireSwitchboardAuth();
6709
6719
  const harness = parseHarnessScope(argv);
6710
6720
  console.log(renderDashboard(harness));
6711
6721
  }
6712
6722
 
6713
6723
  async function showDashboardSession(argv, options = {}) {
6724
+ requireSwitchboardAuth();
6714
6725
  const harness = parseHarnessScope(argv);
6715
6726
  const exitOnClose = options.exitOnClose !== false;
6716
6727
  const sessionStart = new Date().toISOString();
@@ -0,0 +1,133 @@
1
+ # Release Process
2
+
3
+ This is the canonical release path for `switchboard-fyi`. The npm registry is
4
+ the source package; Homebrew installs the npm tarball by URL and checksum.
5
+
6
+ ## One-Time Setup
7
+
8
+ ```bash
9
+ cd /Users/dannywitters/Documents/switchboard/cli
10
+ npm login
11
+ npm whoami
12
+
13
+ brew tap switchboardfyi/tap
14
+ ```
15
+
16
+ ## 1. Prepare The CLI Release
17
+
18
+ Choose the next semver version and update package metadata without creating an
19
+ automatic git tag:
20
+
21
+ ```bash
22
+ npm version patch --no-git-tag-version
23
+ ```
24
+
25
+ Update:
26
+
27
+ - `CHANGELOG.md`
28
+ - `web/app/release/latest/page.tsx` in the web repo, so the update prompt's
29
+ release notes URL matches the shipped version
30
+ - any README/docs affected by the change
31
+
32
+ Run the preflight:
33
+
34
+ ```bash
35
+ npm run release:preflight
36
+ ```
37
+
38
+ The preflight runs tests, checks version/changelog/package consistency, verifies
39
+ the version is not already on npm, and runs `npm publish --dry-run`.
40
+
41
+ Commit the release prep:
42
+
43
+ ```bash
44
+ VERSION="$(node -p "require('./package.json').version")"
45
+ git status --short
46
+ git add package.json package-lock.json CHANGELOG.md README.md docs scripts bin lib test packaging
47
+ git commit -m "Release switchboard-fyi $VERSION"
48
+ git push origin main
49
+ ```
50
+
51
+ ## 2. Publish npm
52
+
53
+ ```bash
54
+ npm publish
55
+ ```
56
+
57
+ If npm asks for two-factor auth:
58
+
59
+ ```bash
60
+ npm publish --otp 123456
61
+ ```
62
+
63
+ Verify the published package:
64
+
65
+ ```bash
66
+ VERSION="$(node -p "require('./package.json').version")"
67
+ npm view "switchboard-fyi@$VERSION" version dist.tarball dist.integrity
68
+
69
+ NPM_PREFIX="$(mktemp -d)"
70
+ npm install -g --prefix "$NPM_PREFIX" "switchboard-fyi@$VERSION"
71
+ "$NPM_PREFIX/bin/switchboard" version
72
+ ```
73
+
74
+ ## 3. Update Homebrew
75
+
76
+ After npm is live, update the Homebrew formula from the actual registry
77
+ tarball:
78
+
79
+ ```bash
80
+ npm run release:homebrew -- --tap ../homebrew-tap
81
+ ```
82
+
83
+ This updates both:
84
+
85
+ - `cli/packaging/homebrew/Formula/switchboard-fyi.rb`
86
+ - `homebrew-tap/Formula/switchboard-fyi.rb`
87
+
88
+ Commit the CLI-side template update:
89
+
90
+ ```bash
91
+ VERSION="$(node -p "require('./package.json').version")"
92
+ git add packaging/homebrew/Formula/switchboard-fyi.rb
93
+ git commit -m "Update Homebrew template for switchboard-fyi $VERSION"
94
+ git tag "v$VERSION"
95
+ git push origin main "v$VERSION"
96
+ ```
97
+
98
+ Commit and push the public tap:
99
+
100
+ ```bash
101
+ cd ../homebrew-tap
102
+ git status --short
103
+ git add Formula/switchboard-fyi.rb
104
+ git commit -m "Update switchboard-fyi to $VERSION"
105
+ git push origin main
106
+ ```
107
+
108
+ ## 4. Verify Homebrew
109
+
110
+ ```bash
111
+ brew update
112
+ brew reinstall switchboardfyi/tap/switchboard-fyi
113
+ brew test switchboardfyi/tap/switchboard-fyi
114
+ switchboard version
115
+ ```
116
+
117
+ If a local npm-global install already owns `/opt/homebrew/bin/switchboard`,
118
+ Homebrew may build the formula but fail to link. For release verification on
119
+ your own machine, this is expected and can be resolved with:
120
+
121
+ ```bash
122
+ brew link --overwrite switchboard-fyi
123
+ ```
124
+
125
+ ## 5. Final Checks
126
+
127
+ ```bash
128
+ npm view switchboard-fyi version
129
+ brew info switchboardfyi/tap/switchboard-fyi
130
+ ```
131
+
132
+ Confirm the latest version shown by npm, Homebrew, `CHANGELOG.md`, and the web
133
+ release page all match.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard-fyi",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Local model-routing CLI for Codex and Claude Code.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.switchboard.fyi",
@@ -12,6 +12,7 @@
12
12
  "switchboard-inspector": "bin/switchboard-inspector.mjs"
13
13
  },
14
14
  "files": [
15
+ "CHANGELOG.md",
15
16
  "bin/switchboard.mjs",
16
17
  "bin/switchboard-gateway.mjs",
17
18
  "bin/switchboard-inspector.mjs",
@@ -19,6 +20,7 @@
19
20
  "docs/codex-subscription-provider-proxy.md",
20
21
  "docs/known-limitations.md",
21
22
  "docs/mvp-usage.md",
23
+ "docs/release.md",
22
24
  "docs/routing-api.md",
23
25
  "docs/smoke-test.md",
24
26
  "README.md",
@@ -42,6 +44,9 @@
42
44
  "config": "node ./bin/switchboard.mjs config",
43
45
  "gateway": "node ./bin/switchboard-gateway.mjs start",
44
46
  "self-test": "node ./bin/switchboard-gateway.mjs self-test",
47
+ "release:check": "node scripts/release-check.mjs",
48
+ "release:preflight": "npm test && node scripts/release-check.mjs --prepublish && npm publish --dry-run",
49
+ "release:homebrew": "node scripts/update-homebrew-formula.mjs",
45
50
  "test": "node --test test/*.test.mjs"
46
51
  },
47
52
  "dependencies": {