@spencerboucher/switchyard 0.4.0 → 0.6.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 (49) hide show
  1. package/README.md +8 -5
  2. package/dist/bridge/pi-model.js +44 -0
  3. package/dist/bridge/switchyard-bridge.js +24 -2
  4. package/dist/bridge/switchyard-terminal.js +608 -0
  5. package/dist/switchyard.mjs +3811 -1811
  6. package/dist/terminal/holder.js +305 -0
  7. package/package.json +6 -3
  8. package/web-dist/assets/{AutomationView-BhEfjAtI.js → AutomationView-DgZn3ltn.js} +2 -2
  9. package/web-dist/assets/{AutomationView-BhEfjAtI.js.map → AutomationView-DgZn3ltn.js.map} +1 -1
  10. package/web-dist/assets/{InteractionCard-DxEZUNrD.js → InteractionCard-cvPWKSu1.js} +2 -2
  11. package/web-dist/assets/{InteractionCard-DxEZUNrD.js.map → InteractionCard-cvPWKSu1.js.map} +1 -1
  12. package/web-dist/assets/{Menu-C4GUY_ym.js → Menu-GymfuHGP.js} +2 -2
  13. package/web-dist/assets/Menu-GymfuHGP.js.map +1 -0
  14. package/web-dist/assets/{Overlay-C-GqmwUn.js → Overlay-oYnnwvFM.js} +2 -2
  15. package/web-dist/assets/{Overlay-C-GqmwUn.js.map → Overlay-oYnnwvFM.js.map} +1 -1
  16. package/web-dist/assets/{attention-Dzqdk9Gz.js → attention-Bxg9W15n.js} +2 -2
  17. package/web-dist/assets/{attention-Dzqdk9Gz.js.map → attention-Bxg9W15n.js.map} +1 -1
  18. package/web-dist/assets/{automations-DzTTwxBl.js → automations-DdB97dnL.js} +2 -2
  19. package/web-dist/assets/{automations-DzTTwxBl.js.map → automations-DdB97dnL.js.map} +1 -1
  20. package/web-dist/assets/{board-C5mG_8rg.js → board-s4LuDPcg.js} +2 -2
  21. package/web-dist/assets/{board-C5mG_8rg.js.map → board-s4LuDPcg.js.map} +1 -1
  22. package/web-dist/assets/code-B0KXn7lD.js +8 -0
  23. package/web-dist/assets/code-B0KXn7lD.js.map +1 -0
  24. package/web-dist/assets/{describe-BbCZVvJP.js → describe-C_3FJuSs.js} +2 -2
  25. package/web-dist/assets/{describe-BbCZVvJP.js.map → describe-C_3FJuSs.js.map} +1 -1
  26. package/web-dist/assets/dist-Bgkh2hc5.js +56 -0
  27. package/web-dist/assets/dist-Bgkh2hc5.js.map +1 -0
  28. package/web-dist/assets/{enroll-DWJANJtw.js → enroll-DijmoolY.js} +2 -2
  29. package/web-dist/assets/{enroll-DWJANJtw.js.map → enroll-DijmoolY.js.map} +1 -1
  30. package/web-dist/assets/index-C5n2QB-Y.css +2 -0
  31. package/web-dist/assets/{index-afOJ8NRR.js → index-Cf6ZUCfK.js} +3 -3
  32. package/web-dist/assets/{index-afOJ8NRR.js.map → index-Cf6ZUCfK.js.map} +1 -1
  33. package/web-dist/assets/{more-B6qxC6M-.js → more-CRGJsz6E.js} +2 -2
  34. package/web-dist/assets/{more-B6qxC6M-.js.map → more-CRGJsz6E.js.map} +1 -1
  35. package/web-dist/assets/{now-BOWxgbt3.js → now-D6lDofYy.js} +2 -2
  36. package/web-dist/assets/{now-BOWxgbt3.js.map → now-D6lDofYy.js.map} +1 -1
  37. package/web-dist/assets/{settings-BpkmIv_4.js → settings-Csal1F-W.js} +6 -6
  38. package/web-dist/assets/settings-Csal1F-W.js.map +1 -0
  39. package/web-dist/assets/{useNow-D9Zxr5Jb.js → useNow-Bc_NlDY3.js} +2 -2
  40. package/web-dist/assets/{useNow-D9Zxr5Jb.js.map → useNow-Bc_NlDY3.js.map} +1 -1
  41. package/web-dist/index.html +2 -2
  42. package/web-dist/sw.js +1 -1
  43. package/web-dist/assets/Menu-C4GUY_ym.js.map +0 -1
  44. package/web-dist/assets/code-9shyh1Gw.js +0 -8
  45. package/web-dist/assets/code-9shyh1Gw.js.map +0 -1
  46. package/web-dist/assets/dist-PaQekTP8.js +0 -56
  47. package/web-dist/assets/dist-PaQekTP8.js.map +0 -1
  48. package/web-dist/assets/index-ByiuDoG0.css +0 -2
  49. package/web-dist/assets/settings-BpkmIv_4.js.map +0 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ flowchart TB
13
13
  subgraph clients["Clients: windows onto the same work"]
14
14
  phone["Phone<br/>(installable web app)"]
15
15
  browser["Desktop browser"]
16
- term["Terminal client and CLI"]
16
+ term["Pi terminal and CLI"]
17
17
  end
18
18
 
19
19
  coord["<b>Coordinator</b><br/>tasks, schedules, notifications,<br/>sign-in, sync<br/><i>never runs code</i>"]
@@ -37,7 +37,7 @@ There are three kinds of parts:
37
37
 
38
38
  - **The coordinator** is the one always-on service. It keeps tasks, schedules, notifications, devices and the shared view of every session, and it decides what runs where. It never runs an agent and never holds your provider credentials, so it can live anywhere: a container, a small server, or your laptop.
39
39
  - **Hosts** are your machines. A host agent runs Pi as your OS user, with your repositories, worktrees, Pi packages and model logins, and connects *outbound* to the coordinator. A host needs no open ports, and a laptop can come and go: its sessions keep running while the coordinator is down, and the events catch up when it returns.
40
- - **Clients** are views. The web app works on desktop and as an installed app on a phone; the terminal client and the scriptable CLI work over the same API. Any client can watch, answer and steer a session; none of them owns it.
40
+ - **Clients** are views. The web app works on desktop and as an installed app on a phone; in a terminal you get Pi's own TUI for any session (`switchyard tui`, or a whole herdr session mirroring Switchyard), and the scriptable CLI works over the same API. Any client can watch, answer and steer a session; none of them owns it.
41
41
 
42
42
  For a single machine, `switchyard up` runs the coordinator and a host in one process. For more, run the coordinator as a server and enroll each machine as a host. The protocol is the same either way.
43
43
 
@@ -76,7 +76,7 @@ sequenceDiagram
76
76
 
77
77
  | Area | What you get |
78
78
  |---|---|
79
- | **Sessions** | Persistent Pi conversations on any host; the same live session in every client; steer or queue follow-ups; take over and return to automation; resume after crashes with the same conversation (never a silent new one); import existing Pi sessions |
79
+ | **Sessions** | Persistent Pi conversations on any host; the same live session in every client; steer or queue follow-ups; take over and return to automation; resume after crashes with the same conversation (never a silent new one); import existing Pi sessions; switch model and effort, see context use and compact, a slash-command picker |
80
80
  | **Workspaces** | A git worktree per task or session inside the repository, discovery of worktrees you made yourself, setup commands, removal that refuses to lose uncommitted or unmerged work, and project folders per host whose repositories are added automatically |
81
81
  | **Tasks and board** | Backlog, Ready, In progress, Review, Done; dependencies with cycle detection; priorities; per-project concurrency limits; a Now view explaining why queued work waits |
82
82
  | **Review** | Changed files and diffs on desktop and phone, line comments sent as one follow-up, request changes, accept |
@@ -100,6 +100,8 @@ sequenceDiagram
100
100
  | **Device** | A signed-in client: a browser, an installed web app or a terminal |
101
101
  | **Adapter** | Code that renders a specific Pi plugin's questions and state as structured UI |
102
102
 
103
+ A seeded **Vanilla** profile runs stock Pi (no extensions, skills or prompt templates).
104
+
103
105
  ## Quick start
104
106
 
105
107
  **Try it on one machine** (Node 24 or newer, git, and Pi 1.4 or 1.5 on your PATH):
@@ -116,7 +118,7 @@ switchyard up
116
118
  ```sh
117
119
  docker run -d --name switchyard -p 127.0.0.1:7400:7400 -v switchyard-data:/data \
118
120
  -e SWITCHYARD_PUBLIC_URL=https://switchyard.example.com \
119
- ghcr.io/justmytwospence/switchyard:0.4.0
121
+ ghcr.io/justmytwospence/switchyard:0.6.0
120
122
  docker exec switchyard switchyard init # prints a single-use sign-in link for your first device
121
123
  ```
122
124
 
@@ -154,7 +156,8 @@ Switchyard is young and moving quickly. It is tested against Pi `>=1.4.0 <1.6.0`
154
156
  |---|---|
155
157
  | [Deployment](docs/deployment.md) | Single machine, server, reverse proxies, VPNs, hosts, environment reference, privilege model |
156
158
  | [Sign-in and devices](docs/auth.md) | The owner model, pairing, passkeys, single sign-on, host enrollment, revocation |
157
- | [CLI and terminal client](docs/cli.md) | Every `switchyard` command, credentials, terminal key bindings |
159
+ | [CLI](docs/cli.md) | Every `switchyard` command and credentials |
160
+ | [Terminal sessions](docs/terminal-sessions.md) | Pi's own TUI for every session, from any terminal, and the herdr integration |
158
161
  | [Operations](docs/operations.md) | Backup, restore, migrations, retention, logs, health, diagnostics |
159
162
  | [Adapters](docs/adapters.md) | Rendering a Pi plugin's UI in every client; shipping an adapter with your Pi package |
160
163
  | [Architecture](docs/architecture.md) | Components, data ownership, the host and client protocols, packages |
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Model and thinking-level helpers shared by the Switchyard Pi extensions and `ManagedSession`.
3
+ * Imports nothing, so it can be loaded by Pi next to the extensions.
4
+ */
5
+ /** Pi 1.4's thinking levels (pi-ai EXTENDED_THINKING_LEVELS), in order. */
6
+ export const PI_THINKING_LEVELS = [
7
+ "off",
8
+ "minimal",
9
+ "low",
10
+ "medium",
11
+ "high",
12
+ "xhigh",
13
+ "max",
14
+ ];
15
+ /** Pi 1.4 `getSupportedThinkingLevels` (pi-ai dist/models.js:693); passes through an already-mapped `thinkingLevels`. */
16
+ export function supportedThinkingLevels(model) {
17
+ const known = PI_THINKING_LEVELS;
18
+ if (Array.isArray(model?.thinkingLevels)) {
19
+ return model.thinkingLevels.filter((l) => typeof l === "string" && known.includes(l));
20
+ }
21
+ if (!model?.reasoning)
22
+ return ["off"];
23
+ return PI_THINKING_LEVELS.filter((level) => {
24
+ const mapped = model.thinkingLevelMap?.[level];
25
+ if (mapped === null)
26
+ return false;
27
+ if (level === "xhigh" || level === "max")
28
+ return mapped !== undefined;
29
+ return true;
30
+ });
31
+ }
32
+ export function modelInfo(model) {
33
+ if (!model || typeof model.provider !== "string" || typeof model.id !== "string")
34
+ return null;
35
+ return {
36
+ provider: model.provider,
37
+ id: model.id,
38
+ name: typeof model.name === "string" && model.name ? model.name : null,
39
+ contextWindow: typeof model.contextWindow === "number" ? model.contextWindow : null,
40
+ reasoning: model.reasoning === true,
41
+ thinkingLevels: supportedThinkingLevels(model),
42
+ };
43
+ }
44
+ //# sourceMappingURL=pi-model.js.map
@@ -8,6 +8,7 @@
8
8
  * never throws: any socket error disables forwarding for the rest of the process.
9
9
  */
10
10
  import { createConnection } from "node:net";
11
+ import { modelInfo } from "./pi-model.js";
11
12
  export default function switchyardBridge(pi) {
12
13
  const socketPath = process.env.SWITCHYARD_BRIDGE_SOCKET;
13
14
  if (!socketPath)
@@ -46,8 +47,9 @@ export default function switchyardBridge(pi) {
46
47
  return null;
47
48
  }
48
49
  };
49
- const send = (s, msg) => {
50
+ const send = (s, msg, extra = {}) => {
50
51
  const full = {
52
+ ...extra,
51
53
  type: msg.type,
52
54
  ...(msg.kind !== undefined ? { kind: msg.kind } : {}),
53
55
  title: msg.title ?? null,
@@ -77,15 +79,35 @@ export default function switchyardBridge(pi) {
77
79
  disabled = true;
78
80
  }
79
81
  };
82
+ const sendModelState = (ctx) => {
83
+ try {
84
+ const s = ensureSocket();
85
+ if (!s)
86
+ return;
87
+ send(s, { type: "model_state" }, {
88
+ model: modelInfo(ctx?.model),
89
+ thinkingLevel: ctx?.thinkingLevel ?? null,
90
+ scoped: (ctx?.scopedModels ?? [])
91
+ .map((s) => s?.model?.provider && s?.model?.id ? `${s.model.provider}/${s.model.id}` : null)
92
+ .filter(Boolean),
93
+ });
94
+ }
95
+ catch {
96
+ disabled = true;
97
+ }
98
+ };
80
99
  try {
81
- pi.on("session_start", () => {
100
+ pi.on("session_start", (_event, ctx) => {
82
101
  try {
83
102
  ensureSocket();
84
103
  }
85
104
  catch {
86
105
  disabled = true;
87
106
  }
107
+ sendModelState(ctx);
88
108
  });
109
+ pi.on("model_select", (_event, ctx) => sendModelState(ctx));
110
+ pi.on("thinking_level_select", (_event, ctx) => sendModelState(ctx));
89
111
  pi.on("ui_prompt_start", forward);
90
112
  pi.on("ui_prompt_end", forward);
91
113
  pi.on("session_shutdown", () => {