switchboard-viewer 0.8.0__tar.gz

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.
@@ -0,0 +1,27 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .uv-cache/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ *.db
11
+ *.db-wal
12
+ *.db-shm
13
+ .env
14
+ .cursor/
15
+ **/.claude/settings.local.json
16
+
17
+ # This repo does not commit its own switchboard wiring. The hooks and
18
+ # .mcp.json carry the workspace name in the clear, and this workspace is an
19
+ # opaque one minted by `init --new-key` — publishing it in a public repo
20
+ # defeats the unguessability it was minted for. Contributors run `init`
21
+ # themselves and get their own.
22
+ .mcp.json
23
+ .switchboard/
24
+ .claude/settings.json
25
+ .claude/skills/
26
+ CLAUDE.md
27
+ uv.lock
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.5
2
+ Name: switchboard-viewer
3
+ Version: 0.8.0
4
+ Summary: A read-only page showing your Switchboard rooms to a human: who is awake, what is claimed, and the conversation as it happens.
5
+ Project-URL: Homepage, https://github.com/gald33/switchboard
6
+ Project-URL: Documentation, https://github.com/gald33/switchboard/blob/main/docs/viewer.md
7
+ Author: Switchboard contributors
8
+ License: MIT
9
+ Keywords: agents,orchestration,switchboard,viewer
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: agent-switchboard>=0.8.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # switchboard-viewer
22
+
23
+ A read-only page showing your [Switchboard](https://github.com/gald33/switchboard)
24
+ rooms to a human: who is awake and on what, what is claimed and for how long,
25
+ what is on the blackboard, and the conversation as it happens.
26
+
27
+ ```bash
28
+ pip install switchboard-viewer # or: pipx install / uvx switchboard-viewer
29
+ cd your-repo # one `switchboard init` has been run in
30
+ switchboard-viewer # → http://127.0.0.1:8799
31
+ ```
32
+
33
+ That is the whole setup. It reads the hub, room and key the way the CLI does —
34
+ `.mcp.json`, `.claude/settings.local.json`, `.env`, environment first — and
35
+ prints where each came from, so there is nothing to type. `--repo` and
36
+ `--scan ~/code` add other checkouts as tabs, each with its own hub and key.
37
+
38
+ It is a **client**, and deliberately a separate package from the SDK: it is the
39
+ only thing in the project that consumes `switchboard` from outside, so anything
40
+ it needs has to be exported rather than merely reachable. It reads and never
41
+ writes — no registering, no posting, and every read leaves agents' cursors
42
+ where it found them, so watching a room cannot make an agent's next `inbox`
43
+ come back empty.
44
+
45
+ Full documentation: [docs/viewer.md](https://github.com/gald33/switchboard/blob/main/docs/viewer.md).
46
+
47
+ ## A room somebody sent you
48
+
49
+ ```bash
50
+ switchboard-viewer --invite swb1_…
51
+ ```
52
+
53
+ One string instead of four fields, from `switchboard invite`. In the browser
54
+ build it is the first field of the settings sheet and fills in the rest. If
55
+ the invite carries a proof-of-room, the viewer checks it on every refresh and
56
+ says `WRONG ROOM` rather than showing you an empty room you assume is quiet.
57
+
58
+ ## The same page, in a browser
59
+
60
+ `switchboard_viewer/web/` does the reading in the browser instead of in a local
61
+ Python process. Four static files, no build step, no server of its own:
62
+
63
+ | | |
64
+ |---|---|
65
+ | `index.html` | the page — markup, styling, and which of the two data paths to use |
66
+ | `render.js` | painting a room. Shared with the local viewer, which is what keeps them one product |
67
+ | `switchboard-room.js` | reading a hub and assembling the view, in the browser |
68
+ | `switchboard-open.js` | the read half of the cipher, on WebCrypto |
69
+
70
+ ## The published one
71
+
72
+ <https://gald33.github.io/switchboard/> — `switchboard_viewer/web/` at the
73
+ commit on `main`, deployed by [`.github/workflows/pages.yml`](../../.github/workflows/pages.yml)
74
+ with no build step, so what is served can be diffed against the commit it
75
+ claims to come from.
76
+
77
+ It opens on the managed hub with its published token already filled in, so
78
+ reading a room there takes a workspace id and — if the room is encrypted — the
79
+ key. Both come from the checkout that coordinates in it:
80
+
81
+ ```bash
82
+ switchboard whoami # workspace, and whether it is encrypted
83
+ echo "$SWITCHBOARD_KEY" # or the `key` in .mcp.json / .env
84
+ ```
85
+
86
+ ## Running it yourself
87
+
88
+ Anything that serves static files:
89
+
90
+ ```bash
91
+ python -m http.server 8899 --directory switchboard_viewer/web
92
+ ```
93
+
94
+ Then open it and enter a hub, a workspace and — if the room is encrypted —
95
+ the key. Settings live in that browser's `localStorage` and nowhere
96
+ else; add several rooms and they become tabs.
97
+
98
+ ## The hub has to allow your origin
99
+
100
+ A browser refuses a cross-origin read before it is sent, whatever credentials
101
+ the page holds. So the hub needs to be told which page may read it:
102
+
103
+ ```bash
104
+ switchboard serve --cors-origin https://you.github.io
105
+ # or
106
+ SWITCHBOARD_CORS_ORIGINS=https://you.github.io,http://127.0.0.1:8899 switchboard serve
107
+ ```
108
+
109
+ Off by default in the library, and set in `docker-compose.yml` to
110
+ `https://gald33.github.io` — the origin above — so the managed hub allows the
111
+ published page and nothing else. Override it in `.env` if you host the page
112
+ elsewhere, or set it empty to refuse browsers entirely. A hub with no browser
113
+ client should not carry the attack surface of one. `*` is accepted and is defensible here in a way it usually is
114
+ not — this API authenticates with an `Authorization` header rather than a
115
+ cookie, so a hostile page gains nothing from being allowed to make a request
116
+ it cannot authenticate.
117
+
118
+ ## What you are trusting, stated plainly
119
+
120
+ **Your key is typed into a page that somebody serves you.** It never leaves
121
+ the browser — `tests/test_web_page.py` asserts that no request carries it, and
122
+ the hub could not use it if it arrived — but whoever serves these files could
123
+ serve a different `switchboard-open.js` tomorrow that posts the key somewhere.
124
+ That is true of every browser-side crypto tool and it cannot be fixed from
125
+ inside one.
126
+
127
+ What follows from that:
128
+
129
+ - **Do not host it on the hub.** This is why the page is on GitHub Pages
130
+ rather than on `switchboard.lucille-ai.com`, which would have been one line
131
+ of config and no workflow at all. A hub that serves this page can read the room
132
+ it is hosting, by serving one modified script, which is precisely the
133
+ property the project exists to provide. The `--cors-origin` route keeps the
134
+ two parties separate: the hub can be untrusted with your content, the page
135
+ host can be untrusted with your traffic, and neither is trusted with both.
136
+ - **Prefer a host you control**, pinned to a commit you have read. These files
137
+ are small and dependency-free on purpose: they are meant to be read.
138
+ - **Or run the local viewer** — `switchboard-viewer` — which asks you
139
+ to trust only a package you installed, and reads your checkout so there is
140
+ nothing to type at all.
141
+
142
+ Over plain HTTP the page says so and refuses to pretend: WebCrypto is not
143
+ available on an insecure origin, and a key typed into an unencrypted page is a
144
+ key handed to anyone on the path.
145
+
146
+ ## What it does not do
147
+
148
+ Everything the local viewer does not do, for the same reasons: it never posts,
149
+ never registers, and reads through `since=0` with `peek`, so watching a room
150
+ cannot advance any agent's cursor or make its next `inbox` come back empty.
@@ -0,0 +1,130 @@
1
+ # switchboard-viewer
2
+
3
+ A read-only page showing your [Switchboard](https://github.com/gald33/switchboard)
4
+ rooms to a human: who is awake and on what, what is claimed and for how long,
5
+ what is on the blackboard, and the conversation as it happens.
6
+
7
+ ```bash
8
+ pip install switchboard-viewer # or: pipx install / uvx switchboard-viewer
9
+ cd your-repo # one `switchboard init` has been run in
10
+ switchboard-viewer # → http://127.0.0.1:8799
11
+ ```
12
+
13
+ That is the whole setup. It reads the hub, room and key the way the CLI does —
14
+ `.mcp.json`, `.claude/settings.local.json`, `.env`, environment first — and
15
+ prints where each came from, so there is nothing to type. `--repo` and
16
+ `--scan ~/code` add other checkouts as tabs, each with its own hub and key.
17
+
18
+ It is a **client**, and deliberately a separate package from the SDK: it is the
19
+ only thing in the project that consumes `switchboard` from outside, so anything
20
+ it needs has to be exported rather than merely reachable. It reads and never
21
+ writes — no registering, no posting, and every read leaves agents' cursors
22
+ where it found them, so watching a room cannot make an agent's next `inbox`
23
+ come back empty.
24
+
25
+ Full documentation: [docs/viewer.md](https://github.com/gald33/switchboard/blob/main/docs/viewer.md).
26
+
27
+ ## A room somebody sent you
28
+
29
+ ```bash
30
+ switchboard-viewer --invite swb1_…
31
+ ```
32
+
33
+ One string instead of four fields, from `switchboard invite`. In the browser
34
+ build it is the first field of the settings sheet and fills in the rest. If
35
+ the invite carries a proof-of-room, the viewer checks it on every refresh and
36
+ says `WRONG ROOM` rather than showing you an empty room you assume is quiet.
37
+
38
+ ## The same page, in a browser
39
+
40
+ `switchboard_viewer/web/` does the reading in the browser instead of in a local
41
+ Python process. Four static files, no build step, no server of its own:
42
+
43
+ | | |
44
+ |---|---|
45
+ | `index.html` | the page — markup, styling, and which of the two data paths to use |
46
+ | `render.js` | painting a room. Shared with the local viewer, which is what keeps them one product |
47
+ | `switchboard-room.js` | reading a hub and assembling the view, in the browser |
48
+ | `switchboard-open.js` | the read half of the cipher, on WebCrypto |
49
+
50
+ ## The published one
51
+
52
+ <https://gald33.github.io/switchboard/> — `switchboard_viewer/web/` at the
53
+ commit on `main`, deployed by [`.github/workflows/pages.yml`](../../.github/workflows/pages.yml)
54
+ with no build step, so what is served can be diffed against the commit it
55
+ claims to come from.
56
+
57
+ It opens on the managed hub with its published token already filled in, so
58
+ reading a room there takes a workspace id and — if the room is encrypted — the
59
+ key. Both come from the checkout that coordinates in it:
60
+
61
+ ```bash
62
+ switchboard whoami # workspace, and whether it is encrypted
63
+ echo "$SWITCHBOARD_KEY" # or the `key` in .mcp.json / .env
64
+ ```
65
+
66
+ ## Running it yourself
67
+
68
+ Anything that serves static files:
69
+
70
+ ```bash
71
+ python -m http.server 8899 --directory switchboard_viewer/web
72
+ ```
73
+
74
+ Then open it and enter a hub, a workspace and — if the room is encrypted —
75
+ the key. Settings live in that browser's `localStorage` and nowhere
76
+ else; add several rooms and they become tabs.
77
+
78
+ ## The hub has to allow your origin
79
+
80
+ A browser refuses a cross-origin read before it is sent, whatever credentials
81
+ the page holds. So the hub needs to be told which page may read it:
82
+
83
+ ```bash
84
+ switchboard serve --cors-origin https://you.github.io
85
+ # or
86
+ SWITCHBOARD_CORS_ORIGINS=https://you.github.io,http://127.0.0.1:8899 switchboard serve
87
+ ```
88
+
89
+ Off by default in the library, and set in `docker-compose.yml` to
90
+ `https://gald33.github.io` — the origin above — so the managed hub allows the
91
+ published page and nothing else. Override it in `.env` if you host the page
92
+ elsewhere, or set it empty to refuse browsers entirely. A hub with no browser
93
+ client should not carry the attack surface of one. `*` is accepted and is defensible here in a way it usually is
94
+ not — this API authenticates with an `Authorization` header rather than a
95
+ cookie, so a hostile page gains nothing from being allowed to make a request
96
+ it cannot authenticate.
97
+
98
+ ## What you are trusting, stated plainly
99
+
100
+ **Your key is typed into a page that somebody serves you.** It never leaves
101
+ the browser — `tests/test_web_page.py` asserts that no request carries it, and
102
+ the hub could not use it if it arrived — but whoever serves these files could
103
+ serve a different `switchboard-open.js` tomorrow that posts the key somewhere.
104
+ That is true of every browser-side crypto tool and it cannot be fixed from
105
+ inside one.
106
+
107
+ What follows from that:
108
+
109
+ - **Do not host it on the hub.** This is why the page is on GitHub Pages
110
+ rather than on `switchboard.lucille-ai.com`, which would have been one line
111
+ of config and no workflow at all. A hub that serves this page can read the room
112
+ it is hosting, by serving one modified script, which is precisely the
113
+ property the project exists to provide. The `--cors-origin` route keeps the
114
+ two parties separate: the hub can be untrusted with your content, the page
115
+ host can be untrusted with your traffic, and neither is trusted with both.
116
+ - **Prefer a host you control**, pinned to a commit you have read. These files
117
+ are small and dependency-free on purpose: they are meant to be read.
118
+ - **Or run the local viewer** — `switchboard-viewer` — which asks you
119
+ to trust only a package you installed, and reads your checkout so there is
120
+ nothing to type at all.
121
+
122
+ Over plain HTTP the page says so and refuses to pretend: WebCrypto is not
123
+ available on an insecure origin, and a key typed into an unencrypted page is a
124
+ key handed to anyone on the path.
125
+
126
+ ## What it does not do
127
+
128
+ Everything the local viewer does not do, for the same reasons: it never posts,
129
+ never registers, and reads through `since=0` with `peek`, so watching a room
130
+ cannot advance any agent's cursor or make its next `inbox` come back empty.
@@ -0,0 +1,66 @@
1
+ # The viewer, as its own distribution.
2
+ #
3
+ # Not folded into `agent-switchboard`, and the reason is the same one that made
4
+ # the viewer worth writing. It is the only thing in this project that consumes
5
+ # the SDK the way a stranger would, so every wall it hit became a public API
6
+ # change instead of a private import — `from_repo`, `rooms_in`,
7
+ # `read_channels`, `Client.encrypted`, `hub_channel`. Inside the package that
8
+ # discipline is a promise somebody has to keep in review. Installed *against* a
9
+ # released `agent-switchboard`, it is a fact: a private import is an
10
+ # ImportError in CI.
11
+ #
12
+ # It can live apart because it is an ordinary client. The hub ships with the
13
+ # clients because the wire protocol has no version negotiation; a reader has no
14
+ # such constraint, and a lower bound says everything it needs.
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [project]
21
+ name = "switchboard-viewer"
22
+ version = "0.8.0"
23
+ description = "A read-only page showing your Switchboard rooms to a human: who is awake, what is claimed, and the conversation as it happens."
24
+ readme = "README.md"
25
+ requires-python = ">=3.10"
26
+ license = { text = "MIT" }
27
+ authors = [{ name = "Switchboard contributors" }]
28
+ keywords = ["agents", "orchestration", "switchboard", "viewer"]
29
+ classifiers = [
30
+ "Development Status :: 4 - Beta",
31
+ "Intended Audience :: Developers",
32
+ "License :: OSI Approved :: MIT License",
33
+ "Programming Language :: Python :: 3.10",
34
+ "Programming Language :: Python :: 3.11",
35
+ "Programming Language :: Python :: 3.12",
36
+ "Topic :: Software Development :: Build Tools",
37
+ ]
38
+
39
+ # Only the SDK, and only its public surface. httpx arrives with it and is used
40
+ # directly for the one thing the client does not cover — serving this page.
41
+ #
42
+ # The bound is ahead of the newest release on purpose. `main` carries API this
43
+ # viewer needs and 0.7.2 does not export — `rooms_in`, `ClientConfig.from_repo`,
44
+ # `read_channels` — which is exactly the drift a separate distribution is meant
45
+ # to make visible: installing this against the published SDK fails with
46
+ # `ImportError: cannot import name 'rooms_in'` rather than working by accident
47
+ # from a source tree. So it says what it needs, and becomes installable the day
48
+ # 0.8.0 ships.
49
+ dependencies = [
50
+ "agent-switchboard>=0.8.0",
51
+ ]
52
+
53
+ [project.scripts]
54
+ switchboard-viewer = "switchboard_viewer.viewer:main"
55
+
56
+ [project.urls]
57
+ Homepage = "https://github.com/gald33/switchboard"
58
+ Documentation = "https://github.com/gald33/switchboard/blob/main/docs/viewer.md"
59
+
60
+ # `web/` sits inside the package directory and so rides along with it — the
61
+ # browser build and the local viewer are one page (`render.js` is served by
62
+ # both), and a second copy of it in a second distribution is exactly the drift
63
+ # the parity test exists to prevent. No force-include: hatchling already ships
64
+ # everything under the package path, and adding it twice is a build error.
65
+ [tool.hatch.build.targets.wheel]
66
+ packages = ["switchboard_viewer"]
@@ -0,0 +1,9 @@
1
+ """The Switchboard viewer: a read-only window on your rooms.
2
+
3
+ Installed separately from `agent-switchboard` on purpose. See the package
4
+ docstring in `viewer.py`, and `extras/viewer/README.md`.
5
+ """
6
+
7
+ from .viewer import Room, discover, main, snapshot, summarise
8
+
9
+ __all__ = ["Room", "discover", "main", "snapshot", "summarise"]
@@ -0,0 +1,6 @@
1
+ """`python -m switchboard_viewer`, for anyone who would rather not rely on a
2
+ console script being on PATH."""
3
+
4
+ from .viewer import main
5
+
6
+ raise SystemExit(main())