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.
- switchboard_viewer-0.8.0/.gitignore +27 -0
- switchboard_viewer-0.8.0/PKG-INFO +150 -0
- switchboard_viewer-0.8.0/README.md +130 -0
- switchboard_viewer-0.8.0/pyproject.toml +66 -0
- switchboard_viewer-0.8.0/switchboard_viewer/__init__.py +9 -0
- switchboard_viewer-0.8.0/switchboard_viewer/__main__.py +6 -0
- switchboard_viewer-0.8.0/switchboard_viewer/viewer.py +790 -0
- switchboard_viewer-0.8.0/switchboard_viewer/web/index.html +411 -0
- switchboard_viewer-0.8.0/switchboard_viewer/web/render.js +214 -0
- switchboard_viewer-0.8.0/switchboard_viewer/web/switchboard-open.js +141 -0
- switchboard_viewer-0.8.0/switchboard_viewer/web/switchboard-room.js +377 -0
|
@@ -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"]
|