viberoom 0.7.0 → 0.9.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.
- package/NOTICE +5 -3
- package/README.md +90 -19
- package/assets/vendors/grok.svg +3 -0
- package/assets/vendors/hermes.svg +1 -0
- package/dist/agent-health.js +1 -1
- package/dist/hub.js +98 -49
- package/dist/jsonrpc.js +3 -1
- package/dist/look-lint.js +180 -0
- package/dist/looks.js +151 -0
- package/dist/main.js +4 -1
- package/dist/mcp-skills-server.js +87 -0
- package/dist/persona.js +10 -3
- package/dist/recipes.js +110 -8
- package/dist/room-design.js +9 -6
- package/dist/room.js +190 -26
- package/dist/server.js +73 -1
- package/dist/skills.js +23 -2
- package/package.json +1 -1
- package/ui/app.css +23 -16
- package/ui/app.js +198 -66
- package/ui/components.css +96 -115
- package/ui/components.js +8 -3
- package/ui/index.html +3 -2
- package/ui/looks.css +462 -77
- package/ui/theme.css +94 -13
- package/ui/tokens.js +526 -57
package/NOTICE
CHANGED
|
@@ -27,9 +27,11 @@ This product bundles the following third-party material:
|
|
|
27
27
|
- Cascadia Code: Copyright 2019 Microsoft Corporation.
|
|
28
28
|
|
|
29
29
|
- Vendor icons (assets/vendors/): the logos of Claude, Codex, Gemini, Cursor,
|
|
30
|
-
OpenCode
|
|
31
|
-
registry (https://agentclientprotocol.com)
|
|
32
|
-
|
|
30
|
+
OpenCode, GitHub Copilot and Grok, as published in the Agent Client Protocol
|
|
31
|
+
registry (https://agentclientprotocol.com), and the Nous Research mark for
|
|
32
|
+
Hermes Agent, traced in one colour from the icon published on nousresearch.com.
|
|
33
|
+
Each mark belongs to its owner and is used only to identify the agent it
|
|
34
|
+
stands for.
|
|
33
35
|
|
|
34
36
|
- ACP adapters (vendor/acp/, copied from the npm packages at build time by
|
|
35
37
|
scripts/vendor-acp.mjs, each with its licence file):
|
package/README.md
CHANGED
|
@@ -14,16 +14,86 @@ Open a room, summon the agents you already have, give each one a role, and let t
|
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/
|
|
17
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/vibeclassic.html"><img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/looks.gif" width="960" alt="The recorded session, drawn in each of the six looks in turn; click to open the interactive demo"></a>
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
|
-
<
|
|
21
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/vibeclassic.html"><img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/try-the-demo.png" width="880" alt="Try the interactive demo: a recorded session in the real viberoom window, two rooms, six vibemates, six looks, nothing to install"></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<b>Claude</b> · <b>Codex</b> · <b>Gemini</b> · <b>Cursor</b> · <b>OpenCode</b> · <b>GitHub Copilot</b> · <b>Grok Build</b> · <b>Hermes Agent</b><br>
|
|
22
26
|
<sub>whichever of them you have installed, with your own logins, in one room</sub>
|
|
23
27
|
</p>
|
|
24
28
|
|
|
25
29
|
<br>
|
|
26
30
|
|
|
31
|
+
## See it before you install it
|
|
32
|
+
|
|
33
|
+
Two rooms, six vibemates, one afternoon: a small CLI fixed and tested by Claude, Codex and OpenCode, and a
|
|
34
|
+
bakery's launch planned by Claude, Codex and Gemini, with the tool calls, the pictures, the file previews and
|
|
35
|
+
the Mermaid diagrams as they happened. The session is recorded as pages that run the real viberoom window
|
|
36
|
+
with no hub behind it. Every card below opens it in that look: walk between the rooms, open the participants,
|
|
37
|
+
change the look under Settings. (The pages live in [`docs/demo/`](docs/demo/) too, for a copy of your own.)
|
|
38
|
+
|
|
39
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/vibeclassic.html"><img align="right" width="460" src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/card-vibeclassic.jpg" alt="VibeClassic"></a>
|
|
40
|
+
|
|
41
|
+
**VibeClassic** — the default: lavender light, soft tiles, room to breathe.<br>
|
|
42
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/vibeclassic.html">Open the demo in VibeClassic →</a>
|
|
43
|
+
|
|
44
|
+
<br clear="all">
|
|
45
|
+
|
|
46
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/vibeclassic-dark.html"><img align="left" width="460" src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/card-vibeclassic-dark.jpg" alt="VibeClassic Dark"></a>
|
|
47
|
+
|
|
48
|
+
**VibeClassic Dark** — the same room after dark: navy paper, the same tiles, nothing glares.<br>
|
|
49
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/vibeclassic-dark.html">Open the demo in VibeClassic Dark →</a>
|
|
50
|
+
|
|
51
|
+
<br clear="all">
|
|
52
|
+
|
|
53
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/clay.html"><img align="right" width="460" src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/card-clay.jpg" alt="Clay"></a>
|
|
54
|
+
|
|
55
|
+
**Clay** — sand, terracotta and teal on warm paper; matte objects, soft corners.<br>
|
|
56
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/clay.html">Open the demo in Clay →</a>
|
|
57
|
+
|
|
58
|
+
<br clear="all">
|
|
59
|
+
|
|
60
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/eye-comfort.html"><img align="left" width="460" src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/card-eye-comfort.jpg" alt="Eye Comfort"></a>
|
|
61
|
+
|
|
62
|
+
**Eye Comfort** — cream instead of white, dark grey instead of black, air between the lines: made for long evenings.<br>
|
|
63
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/eye-comfort.html">Open the demo in Eye Comfort →</a>
|
|
64
|
+
|
|
65
|
+
<br clear="all">
|
|
66
|
+
|
|
67
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/3d-clayful.html"><img align="right" width="460" src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/card-3d-clayful.jpg" alt="3D clayful"></a>
|
|
68
|
+
|
|
69
|
+
**3D clayful** — cream, lavender, pink and mint with real volume: light from the top left, a wall under every object.<br>
|
|
70
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/3d-clayful.html">Open the demo in 3D clayful →</a>
|
|
71
|
+
|
|
72
|
+
<br clear="all">
|
|
73
|
+
|
|
74
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/terminal.html"><img align="left" width="460" src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/readme/card-terminal.jpg" alt="Terminal"></a>
|
|
75
|
+
|
|
76
|
+
**Terminal** — phosphor green on black, one monospace face, hairlines instead of shadows.<br>
|
|
77
|
+
<a href="https://todor-rusev.github.io/viberoom/demo/terminal.html">Open the demo in Terminal →</a>
|
|
78
|
+
|
|
79
|
+
<br clear="all">
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## Six looks, or your own
|
|
83
|
+
|
|
84
|
+
A look is how the window is drawn: colours, light, corners, fonts. Pick one under Settings, fine-tune it,
|
|
85
|
+
or ask a vibemate for "a warm paper look for long evenings": it reads every design token with what it
|
|
86
|
+
means, checks its draft (the hub measures whether the words read on their paper and reports the numbers,
|
|
87
|
+
since a vibemate cannot see colours), saves the look among your own with your name on it, and proposes
|
|
88
|
+
wearing it as a card you can try on first. Your looks are files: export one to share it, import one
|
|
89
|
+
someone sent you.
|
|
90
|
+
|
|
91
|
+
<p align="center">
|
|
92
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/looks-picker.png" width="820" alt="Settings: the look picker with the six shipped looks">
|
|
93
|
+
</p>
|
|
94
|
+
|
|
95
|
+
<br>
|
|
96
|
+
|
|
27
97
|
## The idea
|
|
28
98
|
|
|
29
99
|
One agent is a conversation. Three agents in one room are a **team**: one decides what "done" means,
|
|
@@ -44,7 +114,7 @@ effort and how much it may do on its own. Three vibemates from the same agent wi
|
|
|
44
114
|
vibios behave like three different people, which is exactly the point.
|
|
45
115
|
|
|
46
116
|
<p align="center">
|
|
47
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/summon.png" width="960" alt="Summon a vibemate:
|
|
117
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/summon.png" width="960" alt="Summon a vibemate: eight agents to pick from, then a name, a face and a private brief">
|
|
48
118
|
</p>
|
|
49
119
|
|
|
50
120
|
<br>
|
|
@@ -59,8 +129,7 @@ vibemate gets the rules as instructions, so the room runs the way you wrote it d
|
|
|
59
129
|
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/rules.png" width="820" alt="Room rules with live @references to the vibemates">
|
|
60
130
|
</p>
|
|
61
131
|
|
|
62
|
-
|
|
63
|
-
both ends.
|
|
132
|
+
Five lines of rules buy you a handoff chain that runs on its own, with the human at both ends.
|
|
64
133
|
|
|
65
134
|
<p align="center">
|
|
66
135
|
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/art/handoff.svg" width="960" alt="One task, four hands: you to Pip to Nova to Rex, review loop, report back">
|
|
@@ -82,11 +151,10 @@ library marks it as theirs until you have read it.
|
|
|
82
151
|
|
|
83
152
|
## Let them design the room
|
|
84
153
|
|
|
85
|
-
A vibemate can design a room as well as work in it
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Apply, and the room is told what you decided.
|
|
154
|
+
A vibemate can design a room as well as work in it: read the room's settings and rules, check a design
|
|
155
|
+
and preview the brief the others would receive, save a template for you to pick under New room, or propose
|
|
156
|
+
a change to the room you are in. A proposal is a card in the chat with the diff; nothing changes until you
|
|
157
|
+
click Apply, and the room is told what you decided.
|
|
90
158
|
|
|
91
159
|
<br>
|
|
92
160
|
|
|
@@ -98,8 +166,8 @@ Apply, and the room is told what you decided.
|
|
|
98
166
|
|
|
99
167
|
Write to the room and every vibemate answers, each after a short pause so replies do not trip over
|
|
100
168
|
each other. `@Name` one of them and the rest read along. Vibemates talk to each other the same way:
|
|
101
|
-
a reply wakes the others, `@Name` picks one, and a hop limit keeps an argument from running all night.
|
|
102
|
-
at once; the room stays quiet until you write again.
|
|
169
|
+
a reply wakes the others, `@Name` picks one, and a hop limit keeps an argument from running all night.
|
|
170
|
+
**Hush** stops every running reply at once; the room stays quiet until you write again.
|
|
103
171
|
|
|
104
172
|
<br>
|
|
105
173
|
|
|
@@ -111,7 +179,7 @@ at that line. A diagram block becomes a diagram. Change one of your own messages
|
|
|
111
179
|
the vibemates are told, or the conversation rewinds to that point.
|
|
112
180
|
|
|
113
181
|
<p align="center">
|
|
114
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/
|
|
182
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/conversation.png" width="960" alt="A room with two vibemates: one explains a git command from a screenshot, the other adds the part that changes whose problem it is">
|
|
115
183
|
</p>
|
|
116
184
|
|
|
117
185
|
<br>
|
|
@@ -141,10 +209,11 @@ it in a browser tab. Later, `npm install -g viberoom@latest` and the next start
|
|
|
141
209
|
sees your keys; every agent keeps its own login.
|
|
142
210
|
- **Edit a message.** Fix what you said; the vibemates get the memo, or the conversation rewinds.
|
|
143
211
|
- **Quote a message.** Select a fragment of a bubble, or copy it with Ctrl+C, and it goes into your next
|
|
144
|
-
message as a quote
|
|
212
|
+
message as a quote, with who said it and when.
|
|
145
213
|
- **Your messages on a timeline.** A thin strip on the chat's right edge, one mark per message of yours:
|
|
146
214
|
hover for the message with its neighbours, click to jump there.
|
|
147
|
-
- **
|
|
215
|
+
- **Nothing is cut in silence.** A persona or a rule longer than the room's limit is refused with the
|
|
216
|
+
numbers, and the window offers to cut it, raise the limit, or go back.
|
|
148
217
|
- **Settings save themselves.** Change a setting and it is saved: on Enter, on leaving the field, on a pick.
|
|
149
218
|
- **Search the room.** Everything anyone said, one search box.
|
|
150
219
|
- **For geeks.** Every panel folds its technical settings behind a toggle. You never have to open it.
|
|
@@ -154,11 +223,12 @@ it in a browser tab. Later, `npm install -g viberoom@latest` and the next start
|
|
|
154
223
|
## What you need
|
|
155
224
|
|
|
156
225
|
- Node.js 22 or newer.
|
|
157
|
-
- At least one coding agent installed and logged in: Claude Code, Codex, Gemini CLI, Cursor, OpenCode
|
|
158
|
-
or
|
|
226
|
+
- At least one coding agent installed and logged in: Claude Code, Codex, Gemini CLI, Cursor, OpenCode,
|
|
227
|
+
GitHub Copilot, Grok Build or Hermes Agent. viberoom finds the ones you have and offers only those. It
|
|
228
|
+
installs none of them.
|
|
159
229
|
- A browser. Chrome, Edge or Brave for the app window; anything modern for a tab.
|
|
160
230
|
|
|
161
|
-
Something does not start? `viberoom doctor` checks these
|
|
231
|
+
Something does not start? `viberoom doctor` checks these things and says which one is missing.
|
|
162
232
|
|
|
163
233
|
<br>
|
|
164
234
|
|
|
@@ -175,7 +245,7 @@ viberoom logs the background hub's log
|
|
|
175
245
|
|
|
176
246
|
`Enter` sends, `Shift+Enter` is a new line, `@Name` addresses one vibemate, `/name` invokes a skill.
|
|
177
247
|
Your data lives in `~/.viberoom` (or `$VIBEROOM_DATA_DIR`): settings, rooms with their history,
|
|
178
|
-
skills and the log.
|
|
248
|
+
skills, looks and the log.
|
|
179
249
|
|
|
180
250
|
<br>
|
|
181
251
|
|
|
@@ -184,6 +254,7 @@ skills and the log.
|
|
|
184
254
|
- A question ("how do I ...?") goes to [Discussions → Q&A](https://github.com/todor-rusev/viberoom/discussions/categories/q-a).
|
|
185
255
|
- An idea goes to [Discussions → Ideas](https://github.com/todor-rusev/viberoom/discussions/categories/ideas).
|
|
186
256
|
- A bug goes to [Issues](https://github.com/todor-rusev/viberoom/issues/new/choose); the form asks for what a fix needs.
|
|
257
|
+
- A change you would like to make: read [CONTRIBUTING.md](CONTRIBUTING.md) first. Pull requests are credited, not merged as they are.
|
|
187
258
|
|
|
188
259
|
<br>
|
|
189
260
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
2
|
+
<path fill="currentColor" d="M0.58392448,14.9254204 L0.8326,14.66295 C2.004465,13.42985 3.1678,12.2082 2.45814,10.48105 C1.50813,8.1701 2.061355,5.4619 3.820575,3.70057 C5.6495,1.8709 8.3431,1.40957 10.59295,2.336505 C11.0907,2.52161 11.5245,2.785025 11.86295,3.02993 L9.98425,3.89849 C8.235,3.163775 6.23115,3.66355 5.0081,4.88809 C3.354105,6.5426 3.019895,9.4117 4.95835,11.2656 L-0.335,15.99995 C-0.066496,15.62975 0.2538896,15.275934 0.58392448,14.9254204 Z M14.0391,2.288155 L16.33165,2.57749377e-12 L16.20795,0.172288 C14.4658,2.574355 13.6153,3.749045 14.29795,6.6879 C14.76445,8.68415 14.261,10.90255 12.63545,12.53005 C10.5861,14.58325 7.3066,15.0403 4.6059,13.19215 L6.48885,12.3193 C8.2125,12.99705 10.0983,12.69945 11.4536,11.34255 C12.80895,9.9856 13.1133,8.00925 12.4321,6.3647 C12.30265,6.05285 11.9144,5.97455 11.64275,6.1753 L6.102,10.27035 L14.0391,2.288155 Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 0 63 63"><path fill="currentColor" d="M25 0h10v1h-10zM28 1h10v1h-10zM19 2h4v1h-4zM30 2h10v1h-10zM16 3h10v1h-10zM32 3h10v1h-10zM14 4h14v1h-14zM33 4h10v1h-10zM12 5h17v1h-17zM34 5h10v1h-10zM11 6h19v1h-19zM35 6h10v1h-10zM9 7h22v1h-22zM35 7h11v1h-11zM8 8h21v1h-21zM30 8h2v1h-2zM36 8h10v1h-10zM7 9h20v1h-20zM31 9h1v1h-1zM37 9h7v1h-7zM6 10h19v1h-19zM26 10h1v1h-1zM30 10h3v1h-3zM37 10h6v1h-6zM47 10h1v1h-1zM5 11h7v1h-7zM13 11h11v1h-11zM30 11h2v1h-2zM38 11h1v1h-1zM41 11h1v1h-1zM47 11h2v1h-2zM8 12h4v1h-4zM13 12h4v1h-4zM21 12h1v1h-1zM27 12h6v1h-6zM38 12h1v1h-1zM41 12h1v1h-1zM43 12h2v1h-2zM46 12h3v1h-3zM9 13h2v1h-2zM27 13h7v1h-7zM38 13h11v1h-11zM12 14h1v1h-1zM15 14h8v1h-8zM27 14h7v1h-7zM39 14h11v1h-11zM3 15h4v1h-4zM9 15h26v1h-26zM39 15h11v1h-11zM3 16h32v1h-32zM36 16h15v1h-15zM3 17h32v1h-32zM36 17h15v1h-15zM2 18h49v1h-49zM2 19h50v1h-50zM2 20h17v1h-17zM20 20h32v1h-32zM2 21h17v1h-17zM20 21h32v1h-32zM2 22h18v1h-18zM21 22h31v1h-31zM2 23h11v1h-11zM14 23h6v1h-6zM21 23h32v1h-32zM2 24h11v1h-11zM17 24h2v1h-2zM29 24h24v1h-24zM3 25h8v1h-8zM19 25h3v1h-3zM30 25h23v1h-23zM3 26h7v1h-7zM18 26h6v1h-6zM30 26h23v1h-23zM4 27h6v1h-6zM11 27h1v1h-1zM20 27h3v1h-3zM24 27h1v1h-1zM30 27h7v1h-7zM41 27h12v1h-12zM5 28h2v1h-2zM8 28h5v1h-5zM20 28h2v1h-2zM24 28h1v1h-1zM30 28h24v1h-24zM9 29h4v1h-4zM21 29h1v1h-1zM30 29h24v1h-24zM9 30h2v1h-2zM12 30h1v1h-1zM30 30h24v1h-24zM9 31h3v1h-3zM30 31h24v1h-24zM9 32h2v1h-2zM30 32h25v1h-25zM9 33h2v1h-2zM30 33h25v1h-25zM9 34h1v1h-1zM29 34h26v1h-26zM8 35h2v1h-2zM29 35h26v1h-26zM8 36h2v1h-2zM29 36h27v1h-27zM8 37h2v1h-2zM29 37h27v1h-27zM8 38h3v1h-3zM12 38h4v1h-4zM29 38h27v1h-27zM8 39h3v1h-3zM13 39h1v1h-1zM16 39h1v1h-1zM29 39h27v1h-27zM8 40h4v1h-4zM28 40h6v1h-6zM35 40h22v1h-22zM8 41h4v1h-4zM28 41h6v1h-6zM35 41h18v1h-18zM54 41h3v1h-3zM8 42h5v1h-5zM28 42h6v1h-6zM35 42h18v1h-18zM54 42h3v1h-3zM7 43h6v1h-6zM28 43h5v1h-5zM35 43h18v1h-18zM55 43h2v1h-2zM0 44h1v1h-1zM7 44h7v1h-7zM28 44h5v1h-5zM34 44h19v1h-19zM55 44h3v1h-3zM0 45h1v1h-1zM7 45h7v1h-7zM28 45h25v1h-25zM55 45h3v1h-3zM0 46h1v1h-1zM7 46h9v1h-9zM19 46h4v1h-4zM28 46h6v1h-6zM36 46h17v1h-17zM55 46h3v1h-3zM0 47h2v1h-2zM6 47h18v1h-18zM28 47h5v1h-5zM36 47h17v1h-17zM55 47h3v1h-3zM60 47h1v1h-1zM0 48h3v1h-3zM6 48h19v1h-19zM28 48h4v1h-4zM36 48h17v1h-17zM54 48h4v1h-4zM60 48h1v1h-1zM0 49h9v1h-9zM10 49h16v1h-16zM28 49h25v1h-25zM54 49h4v1h-4zM60 49h1v1h-1zM1 50h7v1h-7zM9 50h43v1h-43zM53 50h5v1h-5zM60 50h1v1h-1zM0 51h1v1h-1zM2 51h5v1h-5zM9 51h18v1h-18zM29 51h29v1h-29zM59 51h2v1h-2zM0 52h2v1h-2zM8 52h18v1h-18zM30 52h27v1h-27zM59 52h2v1h-2zM1 53h3v1h-3zM6 53h19v1h-19zM27 53h1v1h-1zM31 53h26v1h-26zM59 53h2v1h-2zM2 54h9v1h-9zM12 54h11v1h-11zM27 54h1v1h-1zM32 54h25v1h-25zM58 54h2v1h-2zM4 55h6v1h-6zM12 55h12v1h-12zM25 55h2v1h-2zM28 55h1v1h-1zM33 55h23v1h-23zM57 55h3v1h-3zM6 56h1v1h-1zM11 56h13v1h-13zM26 56h1v1h-1zM28 56h1v1h-1zM32 56h4v1h-4zM38 56h3v1h-3zM46 56h1v1h-1zM49 56h6v1h-6zM56 56h3v1h-3zM6 57h2v1h-2zM9 57h16v1h-16zM27 57h1v1h-1zM30 57h3v1h-3zM36 57h3v1h-3zM47 57h2v1h-2zM50 57h8v1h-8zM7 58h6v1h-6zM14 58h11v1h-11zM28 58h3v1h-3zM35 58h3v1h-3zM49 58h1v1h-1zM51 58h6v1h-6zM8 59h4v1h-4zM14 59h15v1h-15zM34 59h3v1h-3zM50 59h1v1h-1zM52 59h3v1h-3zM13 60h15v1h-15zM34 60h2v1h-2zM50 60h4v1h-4zM12 61h11v1h-11zM24 61h1v1h-1zM33 61h2v1h-2zM51 61h2v1h-2zM12 62h10v1h-10zM23 62h2v1h-2zM30 62h1v1h-1zM33 62h1v1h-1zM52 62h1v1h-1z"/></svg>
|
package/dist/agent-health.js
CHANGED
|
@@ -10,7 +10,7 @@ export function loginState(probe, evidence) {
|
|
|
10
10
|
return "unknown";
|
|
11
11
|
return probe.files.length ? "missing" : "unknown";
|
|
12
12
|
}
|
|
13
|
-
const LOGIN_WORDS = /\b(not logged in|log ?in required|login required|please log ?in|unauthori[sz]ed|authentication (failed|required|error)|invalid api key|api key (is )?(missing|not set|invalid)|no credentials|credentials not found|401|403|oauth|token (expired|invalid))\b/i;
|
|
13
|
+
const LOGIN_WORDS = /\b(not logged in|log ?in required|login required|please log ?in|unauthori[sz]ed|authentication (failed|required|error)|invalid api key|api key (is )?(missing|not set|invalid)|no credentials|credentials not found|no (llm )?provider (configured|available)|401|403|oauth|token (expired|invalid))\b/i;
|
|
14
14
|
const MISSING_WORDS = /\b(enoent|not found|no such file|is not recognized|command not found|spawn\w* (failed|error))\b/i;
|
|
15
15
|
const TIMEOUT_WORDS = /\b(timed out|timeout|took too long|did not answer|no response)\b/i;
|
|
16
16
|
export function classifyStartFailure(input) {
|
package/dist/hub.js
CHANGED
|
@@ -11,6 +11,7 @@ import { DEFAULT_ROOM_SETTINGS } from "./persona.js";
|
|
|
11
11
|
import { Room } from "./room.js";
|
|
12
12
|
import { SkillLibrary } from "./skills.js";
|
|
13
13
|
import { TemplateLibrary, roomSettingsFromTemplate } from "./templates.js";
|
|
14
|
+
import { LookLibrary, checkLookSpec, loadTokens } from "./looks.js";
|
|
14
15
|
export const ADJUSTABLE = {
|
|
15
16
|
canvas: "colour", panel: "colour", bubble: "colour", mine: "colour", ring: "colour", ink: "colour", muted: "colour", accent: "colour",
|
|
16
17
|
face: "colour", logo: "colour", logoInk: "colour", corners: "scale",
|
|
@@ -21,11 +22,61 @@ export const DEFAULT_APPEARANCE = { chatFontSize: 14.5, font: "nunito", mono: "j
|
|
|
21
22
|
export const DIAGRAM_PRESETS = ["pop", "lavender", "mint", "sunset", "slate"];
|
|
22
23
|
const ROOM_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
23
24
|
const INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".cursorrules"];
|
|
25
|
+
export function nextAppearance(current, a) {
|
|
26
|
+
const chatFontSize = Number(a.chatFontSize ?? current.chatFontSize ?? DEFAULT_APPEARANCE.chatFontSize);
|
|
27
|
+
if (!Number.isFinite(chatFontSize) || chatFontSize < 12 || chatFontSize > 24)
|
|
28
|
+
throw new Error("appearance.chatFontSize must be between 12 and 24");
|
|
29
|
+
const font = String(a.font ?? current.font ?? DEFAULT_APPEARANCE.font);
|
|
30
|
+
if (!TEXT_FONTS.includes(font))
|
|
31
|
+
throw new Error(`appearance.font must be one of ${TEXT_FONTS.join(", ")}`);
|
|
32
|
+
const mono = String(a.mono ?? current.mono ?? DEFAULT_APPEARANCE.mono);
|
|
33
|
+
if (!MONO_FONTS.includes(mono))
|
|
34
|
+
throw new Error(`appearance.mono must be one of ${MONO_FONTS.join(", ")}`);
|
|
35
|
+
const look = String(a.look ?? current.look ?? DEFAULT_APPEARANCE.look);
|
|
36
|
+
if (!/^[a-z][a-z0-9-]{0,30}$/.test(look))
|
|
37
|
+
throw new Error("appearance.look must be a short lower-case id");
|
|
38
|
+
const custom = { ...(current.custom ?? {}) };
|
|
39
|
+
if (a.custom !== undefined && typeof a.custom === "object" && a.custom) {
|
|
40
|
+
for (const [lookId, values] of Object.entries(a.custom)) {
|
|
41
|
+
if (!/^[a-z][a-z0-9-]{0,30}$/.test(lookId))
|
|
42
|
+
throw new Error("appearance.custom: a look id is a short lower-case id");
|
|
43
|
+
if (values === null) {
|
|
44
|
+
delete custom[lookId];
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (typeof values !== "object")
|
|
48
|
+
throw new Error("appearance.custom: each look takes an object of adjustments");
|
|
49
|
+
const clean = {};
|
|
50
|
+
for (const [key, value] of Object.entries(values)) {
|
|
51
|
+
const kind = ADJUSTABLE[key];
|
|
52
|
+
if (!kind)
|
|
53
|
+
throw new Error(`appearance.custom: "${key}" is not adjustable (${Object.keys(ADJUSTABLE).join(", ")})`);
|
|
54
|
+
if (kind === "colour") {
|
|
55
|
+
if (typeof value !== "string" || !/^#[0-9a-fA-F]{6}$/.test(value))
|
|
56
|
+
throw new Error(`appearance.custom.${key} must be a colour like #1a2b3c`);
|
|
57
|
+
clean[key] = value.toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const n = typeof value === "string" || typeof value === "number" ? Number(value) : NaN;
|
|
61
|
+
if (!Number.isFinite(n) || n < 0 || n > 2)
|
|
62
|
+
throw new Error(`appearance.custom.${key} must be a number between 0 and 2`);
|
|
63
|
+
clean[key] = String(Math.round(n * 100) / 100);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (Object.keys(clean).length)
|
|
67
|
+
custom[lookId] = clean;
|
|
68
|
+
else
|
|
69
|
+
delete custom[lookId];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { chatFontSize: Math.round(chatFontSize * 2) / 2, font, mono, look, custom };
|
|
73
|
+
}
|
|
24
74
|
export class Hub extends EventEmitter {
|
|
25
75
|
dataDir;
|
|
26
76
|
rooms = new Map();
|
|
27
77
|
skills;
|
|
28
78
|
templates;
|
|
79
|
+
looks;
|
|
29
80
|
settings;
|
|
30
81
|
log;
|
|
31
82
|
optionCache = new Map();
|
|
@@ -39,6 +90,7 @@ export class Hub extends EventEmitter {
|
|
|
39
90
|
mkdirSync(join(this.dataDir, "rooms"), { recursive: true });
|
|
40
91
|
this.skills = new SkillLibrary(join(this.dataDir, "skills"), log.child("skills"));
|
|
41
92
|
this.templates = new TemplateLibrary(join(this.dataDir, "templates"), log.child("templates"));
|
|
93
|
+
this.looks = new LookLibrary(join(this.dataDir, "looks"), log.child("looks"));
|
|
42
94
|
try {
|
|
43
95
|
this.skills.seedBuiltins();
|
|
44
96
|
}
|
|
@@ -51,6 +103,30 @@ export class Hub extends EventEmitter {
|
|
|
51
103
|
hubUrl: () => this.hubUrl,
|
|
52
104
|
templates: this.templates,
|
|
53
105
|
templatesChanged: () => this.emit("event", { type: "templates" }),
|
|
106
|
+
looks: {
|
|
107
|
+
list: () => this.looks.list(),
|
|
108
|
+
describe: () => this.looks.describe(),
|
|
109
|
+
check: (raw) => checkLookSpec(raw),
|
|
110
|
+
save: (raw, options) => this.saveLook(raw, options),
|
|
111
|
+
},
|
|
112
|
+
appearance: {
|
|
113
|
+
current: () => ({ ...this.settings.appearance, custom: { ...(this.settings.appearance.custom ?? {}) } }),
|
|
114
|
+
preview: (patch) => nextAppearance(this.settings.appearance, patch),
|
|
115
|
+
apply: (patch) => {
|
|
116
|
+
this.updateSettings({ appearance: patch });
|
|
117
|
+
},
|
|
118
|
+
ownAdjustments: async (lookId) => {
|
|
119
|
+
const tokens = await loadTokens();
|
|
120
|
+
let look = tokens.looks[lookId];
|
|
121
|
+
if (!look) {
|
|
122
|
+
const spec = this.looks.get(lookId);
|
|
123
|
+
if (!spec)
|
|
124
|
+
return null;
|
|
125
|
+
look = tokens.make(spec);
|
|
126
|
+
}
|
|
127
|
+
return { label: look.label, values: Object.fromEntries(tokens.adjustables.map((f) => [f.key, String(f.of(look))])) };
|
|
128
|
+
},
|
|
129
|
+
},
|
|
54
130
|
needApproval: () => this.settings.agentSkillsNeedApproval === true,
|
|
55
131
|
save: (draft) => {
|
|
56
132
|
const { body: _b, ...meta } = this.saveSkillInternal(draft);
|
|
@@ -209,54 +285,7 @@ export class Hub extends EventEmitter {
|
|
|
209
285
|
next.editor = { mode, command };
|
|
210
286
|
}
|
|
211
287
|
if (patch.appearance !== undefined && typeof patch.appearance === "object" && patch.appearance) {
|
|
212
|
-
|
|
213
|
-
const chatFontSize = Number(a.chatFontSize ?? next.appearance?.chatFontSize ?? DEFAULT_APPEARANCE.chatFontSize);
|
|
214
|
-
if (!Number.isFinite(chatFontSize) || chatFontSize < 12 || chatFontSize > 24)
|
|
215
|
-
throw new Error("appearance.chatFontSize must be between 12 and 24");
|
|
216
|
-
const font = String(a.font ?? next.appearance?.font ?? DEFAULT_APPEARANCE.font);
|
|
217
|
-
if (!TEXT_FONTS.includes(font))
|
|
218
|
-
throw new Error(`appearance.font must be one of ${TEXT_FONTS.join(", ")}`);
|
|
219
|
-
const mono = String(a.mono ?? next.appearance?.mono ?? DEFAULT_APPEARANCE.mono);
|
|
220
|
-
if (!MONO_FONTS.includes(mono))
|
|
221
|
-
throw new Error(`appearance.mono must be one of ${MONO_FONTS.join(", ")}`);
|
|
222
|
-
const look = String(a.look ?? next.appearance?.look ?? DEFAULT_APPEARANCE.look);
|
|
223
|
-
if (!/^[a-z][a-z0-9-]{0,30}$/.test(look))
|
|
224
|
-
throw new Error("appearance.look must be a short lower-case id");
|
|
225
|
-
const custom = { ...(next.appearance?.custom ?? {}) };
|
|
226
|
-
if (a.custom !== undefined && typeof a.custom === "object" && a.custom) {
|
|
227
|
-
for (const [lookId, values] of Object.entries(a.custom)) {
|
|
228
|
-
if (!/^[a-z][a-z0-9-]{0,30}$/.test(lookId))
|
|
229
|
-
throw new Error("appearance.custom: a look id is a short lower-case id");
|
|
230
|
-
if (values === null) {
|
|
231
|
-
delete custom[lookId];
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
if (typeof values !== "object")
|
|
235
|
-
throw new Error("appearance.custom: each look takes an object of adjustments");
|
|
236
|
-
const clean = {};
|
|
237
|
-
for (const [key, value] of Object.entries(values)) {
|
|
238
|
-
const kind = ADJUSTABLE[key];
|
|
239
|
-
if (!kind)
|
|
240
|
-
throw new Error(`appearance.custom: "${key}" is not adjustable (${Object.keys(ADJUSTABLE).join(", ")})`);
|
|
241
|
-
if (kind === "colour") {
|
|
242
|
-
if (typeof value !== "string" || !/^#[0-9a-fA-F]{6}$/.test(value))
|
|
243
|
-
throw new Error(`appearance.custom.${key} must be a colour like #1a2b3c`);
|
|
244
|
-
clean[key] = value.toLowerCase();
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
const n = typeof value === "string" || typeof value === "number" ? Number(value) : NaN;
|
|
248
|
-
if (!Number.isFinite(n) || n < 0 || n > 2)
|
|
249
|
-
throw new Error(`appearance.custom.${key} must be a number between 0 and 2`);
|
|
250
|
-
clean[key] = String(Math.round(n * 100) / 100);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (Object.keys(clean).length)
|
|
254
|
-
custom[lookId] = clean;
|
|
255
|
-
else
|
|
256
|
-
delete custom[lookId];
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
next.appearance = { chatFontSize: Math.round(chatFontSize * 2) / 2, font, mono, look, custom };
|
|
288
|
+
next.appearance = nextAppearance(next.appearance, patch.appearance);
|
|
260
289
|
}
|
|
261
290
|
next.appearance = { ...DEFAULT_APPEARANCE, ...(next.appearance ?? {}) };
|
|
262
291
|
if (!next.editor)
|
|
@@ -402,7 +431,7 @@ export class Hub extends EventEmitter {
|
|
|
402
431
|
});
|
|
403
432
|
if (!choice.agentType) {
|
|
404
433
|
try {
|
|
405
|
-
room.addUnstaffed({ name: choice.name || tv.name, tagline: tv.tagline, role: tv.role, avatar: tv.avatar, skills });
|
|
434
|
+
room.addUnstaffed({ name: choice.name || tv.name, tagline: tv.tagline, role: tv.role, avatar: tv.avatar, skills, textCheck: "notice" });
|
|
406
435
|
}
|
|
407
436
|
catch (error) {
|
|
408
437
|
notices.push(`${choice.name || tv.name} could not be added: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -421,12 +450,15 @@ export class Hub extends EventEmitter {
|
|
|
421
450
|
model: choice.model ?? tv.model,
|
|
422
451
|
effort: choice.effort ?? tv.effort,
|
|
423
452
|
mode: choice.mode ?? tv.mode,
|
|
453
|
+
textCheck: "notice",
|
|
424
454
|
});
|
|
425
455
|
}
|
|
426
456
|
catch (error) {
|
|
427
457
|
notices.push(`${choice.name || tv.name} could not be summoned: ${error instanceof Error ? error.message : String(error)}`);
|
|
428
458
|
}
|
|
429
459
|
}
|
|
460
|
+
if (room.settings.customRules.length > room.settings.briefTextLimit)
|
|
461
|
+
notices.push(`The room rules from the template are ${room.settings.customRules.length} characters, over this room's limit of ${room.settings.briefTextLimit}; kept as they are, but the next edit has to fit.`);
|
|
430
462
|
this.saveRooms();
|
|
431
463
|
return { room, notices };
|
|
432
464
|
}
|
|
@@ -492,12 +524,28 @@ export class Hub extends EventEmitter {
|
|
|
492
524
|
this.update = update;
|
|
493
525
|
this.emit("event", { type: "update", update });
|
|
494
526
|
}
|
|
527
|
+
async saveLook(raw, options) {
|
|
528
|
+
const saved = await this.looks.save(raw, options);
|
|
529
|
+
this.emit("event", { type: "looks", looks: this.looks.list() });
|
|
530
|
+
return saved;
|
|
531
|
+
}
|
|
532
|
+
removeLook(id) {
|
|
533
|
+
const removed = this.looks.remove(id);
|
|
534
|
+
if (!removed)
|
|
535
|
+
return false;
|
|
536
|
+
if (this.settings.appearance?.look === id || this.settings.appearance?.custom?.[id]) {
|
|
537
|
+
this.updateSettings({ appearance: { look: this.settings.appearance.look === id ? DEFAULT_APPEARANCE.look : this.settings.appearance.look, custom: { [id]: null } } });
|
|
538
|
+
}
|
|
539
|
+
this.emit("event", { type: "looks", looks: this.looks.list() });
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
495
542
|
snapshot() {
|
|
496
543
|
return {
|
|
497
544
|
settings: this.settings,
|
|
498
545
|
update: this.update,
|
|
499
546
|
recipes: listRecipes().map(({ build: _b, ...r }) => r),
|
|
500
547
|
skills: this.skills.list(),
|
|
548
|
+
looks: this.looks.list(),
|
|
501
549
|
roomDefaults: { ...DEFAULT_ROOM_SETTINGS, ...this.settings.roomDefaults },
|
|
502
550
|
rooms: [...this.rooms.values()].map((room) => room.snapshot()),
|
|
503
551
|
openRooms: [...this.openRooms],
|
|
@@ -518,6 +566,7 @@ export class Hub extends EventEmitter {
|
|
|
518
566
|
rmSync(join(this.dataDir, "rooms"), { recursive: true, force: true });
|
|
519
567
|
rmSync(this.roomsPath(), { force: true });
|
|
520
568
|
rmSync(this.skills.dir, { recursive: true, force: true });
|
|
569
|
+
rmSync(this.looks.dir, { recursive: true, force: true });
|
|
521
570
|
mkdirSync(join(this.dataDir, "rooms"), { recursive: true });
|
|
522
571
|
mkdirSync(this.skills.dir, { recursive: true });
|
|
523
572
|
this.skills.list();
|
package/dist/jsonrpc.js
CHANGED
|
@@ -11,7 +11,9 @@ export class RemoteError extends Error {
|
|
|
11
11
|
rpc;
|
|
12
12
|
method;
|
|
13
13
|
constructor(rpc, method) {
|
|
14
|
-
|
|
14
|
+
const data = rpc.data;
|
|
15
|
+
const detail = typeof data === "string" ? data : data && typeof data === "object" && typeof data.details === "string" ? data.details : "";
|
|
16
|
+
super(`${method} failed: ${rpc.message}${detail ? `: ${detail}` : ""} (code ${rpc.code})`);
|
|
15
17
|
this.rpc = rpc;
|
|
16
18
|
this.method = method;
|
|
17
19
|
}
|