viberoom 0.2.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/LICENSE +661 -0
- package/NOTICE +20 -0
- package/README.md +153 -0
- package/assets/icon-128.png +0 -0
- package/assets/icon-16.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon-32.png +0 -0
- package/assets/icon-48.png +0 -0
- package/assets/icon-512.png +0 -0
- package/assets/icon-64.png +0 -0
- package/assets/icon-vector.svg +30 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.svg +30 -0
- package/assets/vendors/claude.svg +3 -0
- package/assets/vendors/codex.svg +3 -0
- package/assets/vendors/copilot.svg +5 -0
- package/assets/vendors/cursor.svg +3 -0
- package/assets/vendors/gemini.svg +3 -0
- package/assets/vendors/opencode.svg +3 -0
- package/dist/acp-client.js +137 -0
- package/dist/acp-types.js +2 -0
- package/dist/edit.js +34 -0
- package/dist/hub.js +348 -0
- package/dist/icons.js +235 -0
- package/dist/jsonrpc.js +109 -0
- package/dist/launcher.js +161 -0
- package/dist/log.js +35 -0
- package/dist/main.js +389 -0
- package/dist/mcp-skills-server.js +177 -0
- package/dist/open.js +141 -0
- package/dist/persona.js +217 -0
- package/dist/recipes.js +261 -0
- package/dist/room.js +2124 -0
- package/dist/server.js +433 -0
- package/dist/shortcuts.js +176 -0
- package/dist/skills.js +344 -0
- package/dist/tui.js +109 -0
- package/package.json +61 -0
- package/scripts/install.mjs +34 -0
- package/scripts/render-icon.mjs +84 -0
- package/scripts/update.mjs +29 -0
- package/ui/app.css +346 -0
- package/ui/app.js +2834 -0
- package/ui/avatars.js +113 -0
- package/ui/fonts/OFL.txt +93 -0
- package/ui/fonts/nunito-cyrillic-ext.woff2 +0 -0
- package/ui/fonts/nunito-cyrillic.woff2 +0 -0
- package/ui/fonts/nunito-latin-ext.woff2 +0 -0
- package/ui/fonts/nunito-latin.woff2 +0 -0
- package/ui/fonts/nunito-vietnamese.woff2 +0 -0
- package/ui/fonts/nunito.css +6 -0
- package/ui/icons.js +76 -0
- package/ui/index.html +217 -0
- package/ui/manifest.json +14 -0
- package/ui/theme.css +425 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/assets/icon-256.png" width="128" height="128" alt="viberoom">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">viberoom</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><strong>One chat, many coding agents.</strong><br>
|
|
8
|
+
Open a room, summon Claude, Codex, Gemini, Cursor, OpenCode or Copilot into it, and let them talk to you and to each other.</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/viberoom"><img src="https://img.shields.io/npm/v/viberoom?color=6c63ff&label=npm" alt="npm"></a>
|
|
12
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A5%2022-6c63ff" alt="Node 22+"></a>
|
|
13
|
+
<a href="https://agentclientprotocol.com"><img src="https://img.shields.io/badge/protocol-ACP-6c63ff" alt="Agent Client Protocol"></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-6c63ff" alt="AGPL-3.0"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/conversation.png" width="900" alt="Three vibemates working through a task together">
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
viberoom is a small local hub with a web UI. Every coding agent on your machine that speaks the
|
|
22
|
+
[Agent Client Protocol](https://agentclientprotocol.com) can join a room as a **vibemate**: it runs
|
|
23
|
+
as itself, with its own login and subscription, reads the same conversation as everyone else, and
|
|
24
|
+
answers you and the other vibemates. Nothing leaves your machine except what each agent sends to its
|
|
25
|
+
own provider.
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
A single agent is a conversation. Several agents in one room are a team: one defines what "done"
|
|
30
|
+
means, one builds, one reviews, and you steer. viberoom gives that team a place to meet, a shared
|
|
31
|
+
folder to work in, and rules for who speaks when.
|
|
32
|
+
|
|
33
|
+
## Highlights
|
|
34
|
+
|
|
35
|
+
- **Rooms.** A room is a folder and a topic. Every vibemate works in that folder, and the history
|
|
36
|
+
stays with the room. Move the folder and the vibemates move with it.
|
|
37
|
+
- **Vibemates.** Summon any installed agent, give it a name, a one-line persona, a private brief and
|
|
38
|
+
a face. Pick the model, the effort and the permission mode per vibemate.
|
|
39
|
+
- **Talk to all, or to one.** Write to the room and everyone answers in turn; `@Name` one of them and
|
|
40
|
+
the others listen. Vibemates read each other's replies and address each other the same way.
|
|
41
|
+
- **Turn taking.** One vibemate at a time by default, with a short random delay so replies do not
|
|
42
|
+
cross; or everyone at once. A hop limit keeps agent-to-agent chatter from running away.
|
|
43
|
+
- **Hush.** One click stops every running reply; the vibemates stay quiet until you write again.
|
|
44
|
+
- **Skills.** Reusable instructions in a library. Attach them to vibemates, invoke one with `/name`,
|
|
45
|
+
or let a vibemate write its own.
|
|
46
|
+
- **Links, files and diagrams.** Links and file paths in replies open on your machine; a path with a
|
|
47
|
+
`:line` opens in your editor at that line; a ```` ```mermaid ```` block becomes a diagram.
|
|
48
|
+
- **Edit a message.** Change what you said: the vibemates are told, or the conversation is rewound.
|
|
49
|
+
- **A desktop app.** A hidden hub, an app window of its own, a Start Menu / Dock / desktop icon.
|
|
50
|
+
|
|
51
|
+
## A tour
|
|
52
|
+
|
|
53
|
+
<table>
|
|
54
|
+
<tr>
|
|
55
|
+
<td width="50%" valign="top">
|
|
56
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/welcome.png" alt="Welcome: set your vibe"><br>
|
|
57
|
+
<sub><strong>Your vibe.</strong> The first start asks how the vibemates should know you: a name, a face, a line about you.</sub>
|
|
58
|
+
</td>
|
|
59
|
+
<td width="50%" valign="top">
|
|
60
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/home.png" alt="Home page"><br>
|
|
61
|
+
<sub><strong>Home.</strong> Open a room, come back to the last ones, and see what a room can do.</sub>
|
|
62
|
+
</td>
|
|
63
|
+
</tr>
|
|
64
|
+
<tr>
|
|
65
|
+
<td width="50%" valign="top">
|
|
66
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/summon.png" alt="Summon a vibemate"><br>
|
|
67
|
+
<sub><strong>Summon a vibemate.</strong> Pick the agent, the model, the effort and the permission mode; name it and give it a face.</sub>
|
|
68
|
+
</td>
|
|
69
|
+
<td width="50%" valign="top">
|
|
70
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/vibemate.png" alt="A vibemate's persona"><br>
|
|
71
|
+
<sub><strong>Its character.</strong> A public one-liner under the name, and a private brief only this vibemate reads.</sub>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
</table>
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- Node.js 22 or newer.
|
|
79
|
+
- At least one coding agent installed on this machine. viberoom bundles the ACP adapters for Claude
|
|
80
|
+
Code and Codex and finds Gemini CLI, Cursor, OpenCode and Copilot when they are installed. Each
|
|
81
|
+
agent uses its own login and subscription; viberoom never sees your keys.
|
|
82
|
+
- A Chromium-based browser (Chrome, Edge, Brave) for the app window; any modern browser for a tab.
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm install -g viberoom
|
|
88
|
+
viberoom
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The menu that appears can install a desktop icon, open the app window, or open a browser tab
|
|
92
|
+
(Start Menu on Windows, `~/Applications/viberoom.app` on macOS, an applications-menu entry on Linux).
|
|
93
|
+
Upgrade with `npm install -g viberoom@latest`; the next start replaces the running hub.
|
|
94
|
+
|
|
95
|
+
From a clone instead:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
git clone https://github.com/todor-rusev/viberoom.git
|
|
99
|
+
cd viberoom
|
|
100
|
+
npm run install:global
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`install:global` builds the project, links the `viberoom` command and creates the launcher; later,
|
|
104
|
+
`npm run update` rebuilds and replaces the running hub.
|
|
105
|
+
|
|
106
|
+
## Use
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
viberoom a small menu: desktop icon, app window, browser, run here
|
|
110
|
+
viberoom start the hub hidden in the background, plus the app window
|
|
111
|
+
viberoom stop stop the background hub
|
|
112
|
+
viberoom status is a hub running, which build, where
|
|
113
|
+
viberoom open open the window of the running hub
|
|
114
|
+
viberoom logs the background hub's log
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The first start asks for your name and a line about you. Then: open a room (name and folder),
|
|
118
|
+
summon a vibemate (pick the agent, name it, give it a face), say hello. `Enter` sends,
|
|
119
|
+
`Shift+Enter` is a new line, `@Name` addresses one vibemate, `/name` invokes a skill.
|
|
120
|
+
|
|
121
|
+
Data lives in `~/.viberoom` (or `$VIBEROOM_DATA_DIR`): settings, rooms with their history, skills,
|
|
122
|
+
transcripts and the hub log.
|
|
123
|
+
|
|
124
|
+
## Settings worth knowing
|
|
125
|
+
|
|
126
|
+
- **Vibemates act without asking.** On by default: agents edit files and run commands in the room's
|
|
127
|
+
folder without a permission prompt. Turn it off to be asked, or change it per vibemate.
|
|
128
|
+
- **Custom rules.** One rule per line in the room's panel, handed to every vibemate as instructions:
|
|
129
|
+
who speaks to whom, how long a reply may be, where code goes.
|
|
130
|
+
- **Language.** Vibemates follow the language you write in, or a fixed one for the room.
|
|
131
|
+
- **Reply delay.** With two or more vibemates in a room, each waits a random few seconds before it
|
|
132
|
+
answers. A vibemate can override it; alone it answers at once.
|
|
133
|
+
- **Open files at a line.** Which editor gets `path:line` clicks: the first one found (VS Code,
|
|
134
|
+
Cursor, Windsurf, Zed, Sublime Text, Notepad++, JetBrains) or your own command.
|
|
135
|
+
- **Diagrams.** Colours for Mermaid diagrams: multicolour boxes by default, or a single tone.
|
|
136
|
+
- **For geeks.** Every panel folds its technical settings behind a "for geeks" toggle.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
npm install
|
|
142
|
+
npm run build # TypeScript to dist/
|
|
143
|
+
node dist/main.js # run the hub in this terminal
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The web UI in `ui/` is plain HTML, CSS and JavaScript served by the hub; reload the page to see a
|
|
147
|
+
change. `scripts/render-icon.mjs` renders the app icon from `assets/icon-vector.svg`.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
GNU Affero General Public License v3.0 or later. You may use, change and share viberoom; if you
|
|
152
|
+
distribute a changed version, or run one as a service for others, share your changes under the same
|
|
153
|
+
licence. See `LICENSE` and `NOTICE` (third-party fonts and icons).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="256" height="256" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#8b8bff"/><stop offset="1" stop-color="#4040d0"/></linearGradient>
|
|
4
|
+
<radialGradient id="gloss" cx=".3" cy=".1" r=".9"><stop offset="0" stop-color="#fff" stop-opacity=".45"/><stop offset=".6" stop-color="#fff" stop-opacity="0"/></radialGradient>
|
|
5
|
+
<linearGradient id="white" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e6e6ff"/></linearGradient>
|
|
6
|
+
<linearGradient id="lav" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#dcdcff"/><stop offset="1" stop-color="#b3b3ff"/></linearGradient>
|
|
7
|
+
<filter id="drop" x="-20%" y="-20%" width="140%" height="160%"><feDropShadow dx="0" dy="2.5" stdDeviation="1.8" flood-color="#1c1b8a" flood-opacity=".35"/></filter>
|
|
8
|
+
</defs>
|
|
9
|
+
<rect width="64" height="64" rx="18" fill="url(#bg)"/>
|
|
10
|
+
<rect width="64" height="64" rx="18" fill="url(#gloss)"/>
|
|
11
|
+
<rect x="1" y="1" width="62" height="62" rx="17" stroke="#fff" stroke-opacity=".25"/>
|
|
12
|
+
<g filter="url(#drop)">
|
|
13
|
+
<rect x="10" y="13" width="30" height="22" rx="9" fill="url(#white)"/>
|
|
14
|
+
<path d="M16 34v8l9-8z" fill="#e6e6ff"/>
|
|
15
|
+
<rect x="18" y="19" width="14" height="10" rx="4" fill="#4a4ade"/>
|
|
16
|
+
<circle cx="22.5" cy="24" r="1.7" fill="#fff"/>
|
|
17
|
+
<circle cx="27.5" cy="24" r="1.7" fill="#fff"/>
|
|
18
|
+
<path d="M25 19v-3" stroke="#4a4ade" stroke-width="1.6" stroke-linecap="round"/>
|
|
19
|
+
<circle cx="25" cy="15" r="1.4" fill="#ffd166"/>
|
|
20
|
+
</g>
|
|
21
|
+
<g filter="url(#drop)">
|
|
22
|
+
<rect x="27" y="29" width="28" height="20" rx="9" fill="url(#lav)"/>
|
|
23
|
+
<path d="M49 48v8l-9-8z" fill="#b3b3ff"/>
|
|
24
|
+
<circle cx="35" cy="39" r="2.1" fill="#3a3ac8"/>
|
|
25
|
+
<circle cx="41" cy="39" r="2.1" fill="#3a3ac8"/>
|
|
26
|
+
<circle cx="47" cy="39" r="2.1" fill="#3a3ac8"/>
|
|
27
|
+
</g>
|
|
28
|
+
<path d="M51 11l1.2 3.3 3.3 1.2-3.3 1.2L51 20l-1.2-3.3-3.3-1.2 3.3-1.2z" fill="#ffd166"/>
|
|
29
|
+
<circle cx="12" cy="50" r="1.6" fill="#fff" opacity=".7"/>
|
|
30
|
+
</svg>
|
package/assets/icon.icns
ADDED
|
Binary file
|
package/assets/icon.ico
ADDED
|
Binary file
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="256" height="256" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#8b8bff"/><stop offset="1" stop-color="#4040d0"/></linearGradient>
|
|
4
|
+
<radialGradient id="gloss" cx=".3" cy=".1" r=".9"><stop offset="0" stop-color="#fff" stop-opacity=".45"/><stop offset=".6" stop-color="#fff" stop-opacity="0"/></radialGradient>
|
|
5
|
+
<linearGradient id="white" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e6e6ff"/></linearGradient>
|
|
6
|
+
<linearGradient id="lav" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#dcdcff"/><stop offset="1" stop-color="#b3b3ff"/></linearGradient>
|
|
7
|
+
<filter id="drop" x="-20%" y="-20%" width="140%" height="160%"><feDropShadow dx="0" dy="2.5" stdDeviation="1.8" flood-color="#1c1b8a" flood-opacity=".35"/></filter>
|
|
8
|
+
</defs>
|
|
9
|
+
<rect width="64" height="64" rx="18" fill="url(#bg)"/>
|
|
10
|
+
<rect width="64" height="64" rx="18" fill="url(#gloss)"/>
|
|
11
|
+
<rect x="1" y="1" width="62" height="62" rx="17" stroke="#fff" stroke-opacity=".25"/>
|
|
12
|
+
<g filter="url(#drop)">
|
|
13
|
+
<rect x="10" y="13" width="30" height="22" rx="9" fill="url(#white)"/>
|
|
14
|
+
<path d="M16 34v8l9-8z" fill="#e6e6ff"/>
|
|
15
|
+
<rect x="18" y="19" width="14" height="10" rx="4" fill="#4a4ade"/>
|
|
16
|
+
<circle cx="22.5" cy="24" r="1.7" fill="#fff"/>
|
|
17
|
+
<circle cx="27.5" cy="24" r="1.7" fill="#fff"/>
|
|
18
|
+
<path d="M25 19v-3" stroke="#4a4ade" stroke-width="1.6" stroke-linecap="round"/>
|
|
19
|
+
<circle cx="25" cy="15" r="1.4" fill="#ffd166"/>
|
|
20
|
+
</g>
|
|
21
|
+
<g filter="url(#drop)">
|
|
22
|
+
<rect x="27" y="29" width="28" height="20" rx="9" fill="url(#lav)"/>
|
|
23
|
+
<path d="M49 48v8l-9-8z" fill="#b3b3ff"/>
|
|
24
|
+
<circle cx="35" cy="39" r="2.1" fill="#3a3ac8"/>
|
|
25
|
+
<circle cx="41" cy="39" r="2.1" fill="#3a3ac8"/>
|
|
26
|
+
<circle cx="47" cy="39" r="2.1" fill="#3a3ac8"/>
|
|
27
|
+
</g>
|
|
28
|
+
<path d="M51 11l1.2 3.3 3.3 1.2-3.3 1.2L51 20l-1.2-3.3-3.3-1.2 3.3-1.2z" fill="#ffd166"/>
|
|
29
|
+
<circle cx="12" cy="50" r="1.6" fill="#fff" opacity=".7"/>
|
|
30
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 1200 1200">
|
|
2
|
+
<path fill="currentColor" d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
2
|
+
<path fill="currentColor" d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99816 14.2779C12.0678 14.2779 14.9997 11.6274 14.9997 10.9574V9.3189C14.939 8.77306 14.4165 7.82349 13.6074 7.48636C13.5959 7.42498 13.5859 7.36153 13.5756 7.29542C13.5506 7.13548 13.5231 6.95977 13.4649 6.75954C13.6409 6.31508 13.6873 5.81187 13.6873 5.31135C13.6873 4.54947 13.5757 3.76321 13.0811 3.13712C12.5745 2.4959 11.7737 2.15372 10.6972 2.03411C9.64215 1.91688 8.71828 2.06409 8.12157 2.70342C8.07827 2.74981 8.0345 2.79812 7.99655 2.84746C7.95861 2.79812 7.91805 2.74981 7.87475 2.70342C7.27804 2.06409 6.35416 1.91688 5.29908 2.03411C4.22263 2.15372 3.42181 2.4959 2.91521 3.13712C2.42057 3.76321 2.30905 4.54947 2.30905 5.31135C2.30905 5.81187 2.35539 6.31508 2.53142 6.75954C2.47323 6.95977 2.44574 7.13542 2.42072 7.29536C2.41037 7.3615 2.40044 7.42496 2.38893 7.48636C1.57983 7.82349 1.05728 8.77306 0.996582 9.3189V10.9574C0.996582 11.6274 3.92849 14.2779 7.99816 14.2779ZM10.5523 3.33859C9.63863 3.23707 9.25001 3.41798 9.0811 3.59896C8.90273 3.79007 8.76738 4.19254 8.86971 5.0112C8.94909 5.64627 9.13423 6.08757 9.41018 6.36929C9.67188 6.63645 10.0966 6.8426 10.8435 6.8426C11.6506 6.8426 11.9631 6.66867 12.1051 6.51046C12.2619 6.33577 12.3748 6.00462 12.3748 5.31135C12.3748 4.64141 12.2675 4.22455 12.0513 3.95077C11.8469 3.69214 11.4446 3.43773 10.5523 3.33859ZM6.91639 3.59896C6.74748 3.41798 6.35886 3.23707 5.44518 3.33859C4.55289 3.43773 4.15058 3.69214 3.94624 3.95077C3.72994 4.22455 3.62271 4.64141 3.62271 5.31135C3.62271 6.00462 3.73563 6.33577 3.89239 6.51046C4.03436 6.66867 4.34693 6.8426 5.15396 6.8426C5.90092 6.8426 6.32561 6.63645 6.58731 6.36929C6.86326 6.08757 7.0484 5.64627 7.12778 5.0112C7.23011 4.19254 7.09476 3.79007 6.91639 3.59896ZM7.99818 12.9788C9.99307 12.9788 12.0088 12.0076 12.3748 11.7248V8.00133L12.3546 7.90026C11.9257 8.08369 11.4142 8.1551 10.8435 8.1551C9.84049 8.1551 9.04148 7.86851 8.47257 7.28773C8.28092 7.09208 8.12536 6.87308 7.99978 6.6378C7.8742 6.87308 7.71544 7.09208 7.52378 7.28773C6.95488 7.86851 6.15587 8.1551 5.15283 8.1551C4.58212 8.1551 4.07068 8.08369 3.6418 7.90026L3.62158 8.00133V11.7248C3.98755 12.0076 6.00329 12.9788 7.99818 12.9788Z" fill="currentColor"/>
|
|
3
|
+
<path d="M6.46854 9.0301C6.83097 9.0301 7.12479 9.32391 7.12479 9.68635V10.9988C7.12479 11.3613 6.83097 11.6551 6.46854 11.6551C6.1061 11.6551 5.81229 11.3613 5.81229 10.9988V9.68635C5.81229 9.32391 6.1061 9.0301 6.46854 9.0301Z" fill="currentColor"/>
|
|
4
|
+
<path d="M10.1873 9.68635C10.1873 9.32391 9.89347 9.0301 9.53104 9.0301C9.1686 9.0301 8.87479 9.32391 8.87479 9.68635V10.9988C8.87479 11.3613 9.1686 11.6551 9.53104 11.6551C9.89347 11.6551 10.1873 11.3613 10.1873 10.9988V9.68635Z" fill="currentColor"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 466.73 532.09">
|
|
2
|
+
<path fill="currentColor" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
2
|
+
<path fill="currentColor" d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { JsonRpcPeer, MethodNotFound } from "./jsonrpc.js";
|
|
5
|
+
export const PROTOCOL_VERSION = 1;
|
|
6
|
+
export function childEnvironment(extra) {
|
|
7
|
+
const env = {};
|
|
8
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
if (key === "CLAUDECODE" || key.startsWith("CLAUDE_CODE_") || key === "CLAUDE_PID" || key === "CLAUDE_EFFORT")
|
|
12
|
+
continue;
|
|
13
|
+
env[key] = value;
|
|
14
|
+
}
|
|
15
|
+
return { ...env, ...(extra ?? {}) };
|
|
16
|
+
}
|
|
17
|
+
export class AcpAgent {
|
|
18
|
+
launch;
|
|
19
|
+
hooks;
|
|
20
|
+
child;
|
|
21
|
+
peer;
|
|
22
|
+
initResult = null;
|
|
23
|
+
exited = false;
|
|
24
|
+
constructor(launch, hooks) {
|
|
25
|
+
this.launch = launch;
|
|
26
|
+
this.hooks = hooks;
|
|
27
|
+
this.child = spawn(launch.command, launch.args, {
|
|
28
|
+
cwd: launch.cwd,
|
|
29
|
+
env: childEnvironment(launch.env),
|
|
30
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
31
|
+
shell: false,
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
});
|
|
34
|
+
if (!this.child.stdin || !this.child.stdout || !this.child.stderr) {
|
|
35
|
+
throw new Error("agent process has no stdio pipes");
|
|
36
|
+
}
|
|
37
|
+
this.peer = new JsonRpcPeer(this.child.stdin, this.child.stdout, {
|
|
38
|
+
onRequest: (method, params) => this.handleRequest(method, params),
|
|
39
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
40
|
+
onRaw: hooks.onRaw,
|
|
41
|
+
onProtocolError: hooks.onProtocolError,
|
|
42
|
+
});
|
|
43
|
+
const stderr = createInterface({ input: this.child.stderr });
|
|
44
|
+
stderr.on("line", (line) => hooks.onStderr(line));
|
|
45
|
+
this.child.on("exit", (code, signal) => {
|
|
46
|
+
this.exited = true;
|
|
47
|
+
this.peer.close(`agent exited (code ${code}, signal ${signal})`);
|
|
48
|
+
hooks.onExit(code, signal);
|
|
49
|
+
});
|
|
50
|
+
this.child.on("error", (error) => {
|
|
51
|
+
this.exited = true;
|
|
52
|
+
this.peer.close(`agent process error: ${error.message}`);
|
|
53
|
+
hooks.onStderr(`spawn error: ${error.message}`);
|
|
54
|
+
hooks.onExit(null, null);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
get capabilities() {
|
|
58
|
+
return this.initResult;
|
|
59
|
+
}
|
|
60
|
+
get alive() {
|
|
61
|
+
return !this.exited && !this.peer.isClosed;
|
|
62
|
+
}
|
|
63
|
+
hasSessionCapability(name) {
|
|
64
|
+
const caps = this.initResult?.agentCapabilities?.sessionCapabilities;
|
|
65
|
+
return !!caps && Object.prototype.hasOwnProperty.call(caps, name);
|
|
66
|
+
}
|
|
67
|
+
async initialize(clientInfo) {
|
|
68
|
+
const result = (await this.peer.request("initialize", {
|
|
69
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
70
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
71
|
+
clientInfo,
|
|
72
|
+
}));
|
|
73
|
+
this.initResult = result;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
get authMethods() {
|
|
77
|
+
return (this.initResult?.authMethods ?? []);
|
|
78
|
+
}
|
|
79
|
+
async authenticate(methodId) {
|
|
80
|
+
await this.peer.request("authenticate", { methodId });
|
|
81
|
+
}
|
|
82
|
+
newSession(cwd, mcpServers = []) {
|
|
83
|
+
return this.peer.request("session/new", { cwd, mcpServers });
|
|
84
|
+
}
|
|
85
|
+
get supportsLoadSession() {
|
|
86
|
+
return this.initResult?.agentCapabilities?.loadSession === true;
|
|
87
|
+
}
|
|
88
|
+
loadSession(sessionId, cwd, mcpServers = []) {
|
|
89
|
+
return this.peer.request("session/load", { sessionId, cwd, mcpServers }).then((result) => ({
|
|
90
|
+
...(result ?? {}),
|
|
91
|
+
sessionId,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
prompt(sessionId, prompt) {
|
|
95
|
+
return this.peer.request("session/prompt", { sessionId, prompt });
|
|
96
|
+
}
|
|
97
|
+
cancel(sessionId) {
|
|
98
|
+
this.peer.notify("session/cancel", { sessionId });
|
|
99
|
+
}
|
|
100
|
+
async setConfigOption(sessionId, configId, value) {
|
|
101
|
+
const params = typeof value === "boolean" ? { sessionId, configId, type: "boolean", value } : { sessionId, configId, value };
|
|
102
|
+
const result = (await this.peer.request("session/set_config_option", params));
|
|
103
|
+
return result.configOptions;
|
|
104
|
+
}
|
|
105
|
+
async setMode(sessionId, modeId) {
|
|
106
|
+
await this.peer.request("session/set_mode", { sessionId, modeId });
|
|
107
|
+
}
|
|
108
|
+
async closeSession(sessionId) {
|
|
109
|
+
if (!this.hasSessionCapability("close"))
|
|
110
|
+
return;
|
|
111
|
+
await this.peer.request("session/close", { sessionId });
|
|
112
|
+
}
|
|
113
|
+
kill() {
|
|
114
|
+
if (this.exited)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
117
|
+
this.child.stdin?.end();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
}
|
|
121
|
+
this.child.kill();
|
|
122
|
+
}
|
|
123
|
+
async handleRequest(method, params) {
|
|
124
|
+
if (method === "session/request_permission") {
|
|
125
|
+
return this.hooks.onPermissionRequest(params);
|
|
126
|
+
}
|
|
127
|
+
throw new MethodNotFound(method);
|
|
128
|
+
}
|
|
129
|
+
handleNotification(method, params) {
|
|
130
|
+
if (method === "session/update") {
|
|
131
|
+
const p = params;
|
|
132
|
+
this.hooks.onSessionUpdate(p.sessionId, p.update);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.hooks.onStderr(`[protocol] unhandled notification ${method}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/edit.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE
|
|
2
|
+
export function partitionHistory(messages, editedSeq) {
|
|
3
|
+
const kept = [];
|
|
4
|
+
const removed = [];
|
|
5
|
+
for (const m of messages)
|
|
6
|
+
(m.seq > editedSeq ? removed : kept).push(m);
|
|
7
|
+
return { kept, removed };
|
|
8
|
+
}
|
|
9
|
+
export function affectedByEdit(agents, editedSeq) {
|
|
10
|
+
const restart = [];
|
|
11
|
+
const untouched = [];
|
|
12
|
+
const offline = [];
|
|
13
|
+
for (const a of agents) {
|
|
14
|
+
const saw = a.lastSeenSeq >= editedSeq || a.active;
|
|
15
|
+
if (!a.online)
|
|
16
|
+
(saw ? offline : untouched).push(a);
|
|
17
|
+
else
|
|
18
|
+
(saw ? restart : untouched).push(a);
|
|
19
|
+
}
|
|
20
|
+
return { restart, untouched, offline };
|
|
21
|
+
}
|
|
22
|
+
const QUOTE_MAX = 240;
|
|
23
|
+
function quote(text) {
|
|
24
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
25
|
+
return flat.length > QUOTE_MAX ? `${flat.slice(0, QUOTE_MAX)}…` : flat;
|
|
26
|
+
}
|
|
27
|
+
export function editNotice(humanName, previous, next) {
|
|
28
|
+
return `${humanName} edited an earlier message: "${quote(previous)}" → "${quote(next)}". Reply only if the change matters to you; otherwise [silent].`;
|
|
29
|
+
}
|
|
30
|
+
export function rewriteNotice(humanName, removedCount, restarted) {
|
|
31
|
+
const removed = removedCount === 1 ? "1 later message" : `${removedCount} later messages`;
|
|
32
|
+
const who = restarted.length ? ` ${restarted.join(", ")} restarted with a fresh session and the conversation up to here.` : "";
|
|
33
|
+
return `${humanName} rewrote the conversation from here (${removed} removed).${who}`;
|
|
34
|
+
}
|