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/dist/recipes.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
const isWindows = process.platform === "win32";
|
|
8
|
+
function resolvePackageEntry(packageName, relativeEntry) {
|
|
9
|
+
try {
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const pkg = require.resolve(`${packageName}/package.json`);
|
|
12
|
+
const entry = join(dirname(pkg), relativeEntry);
|
|
13
|
+
return existsSync(entry) ? entry : null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
let globalNpmRoot;
|
|
20
|
+
function resolveGlobalNpmRoot() {
|
|
21
|
+
if (globalNpmRoot !== undefined)
|
|
22
|
+
return globalNpmRoot;
|
|
23
|
+
const candidates = [];
|
|
24
|
+
if (isWindows && process.env.APPDATA)
|
|
25
|
+
candidates.push(join(process.env.APPDATA, "npm", "node_modules"));
|
|
26
|
+
try {
|
|
27
|
+
const out = execSync("npm root -g", { encoding: "utf8", shell: isWindows ? "cmd.exe" : "/bin/sh", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
28
|
+
if (out)
|
|
29
|
+
candidates.push(out);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
}
|
|
33
|
+
globalNpmRoot = candidates.find((c) => existsSync(c)) ?? null;
|
|
34
|
+
return globalNpmRoot;
|
|
35
|
+
}
|
|
36
|
+
function resolveGlobalPackageEntry(packageName, relativeEntry) {
|
|
37
|
+
const root = resolveGlobalNpmRoot();
|
|
38
|
+
if (!root)
|
|
39
|
+
return null;
|
|
40
|
+
const entry = join(root, packageName, relativeEntry);
|
|
41
|
+
return existsSync(entry) ? entry : null;
|
|
42
|
+
}
|
|
43
|
+
function resolveCursorAgent() {
|
|
44
|
+
const base = isWindows
|
|
45
|
+
? process.env.LOCALAPPDATA && join(process.env.LOCALAPPDATA, "cursor-agent")
|
|
46
|
+
: join(homedir(), ".local", "share", "cursor-agent");
|
|
47
|
+
if (!base || !existsSync(join(base, "versions")))
|
|
48
|
+
return null;
|
|
49
|
+
const versions = readdirSync(join(base, "versions"), { withFileTypes: true })
|
|
50
|
+
.filter((d) => d.isDirectory() && /^\d{4}\.\d{1,2}\.\d{1,2}/.test(d.name))
|
|
51
|
+
.map((d) => d.name)
|
|
52
|
+
.sort((a, b) => versionKey(b) - versionKey(a));
|
|
53
|
+
for (const version of versions) {
|
|
54
|
+
const dir = join(base, "versions", version);
|
|
55
|
+
const node = join(dir, isWindows ? "node.exe" : "node");
|
|
56
|
+
const index = join(dir, "index.js");
|
|
57
|
+
if (existsSync(node) && existsSync(index))
|
|
58
|
+
return { node, index };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function versionKey(name) {
|
|
63
|
+
const [y, m, d] = name.split("-")[0].split(".").map((n) => Number(n));
|
|
64
|
+
return y * 10000 + m * 100 + d;
|
|
65
|
+
}
|
|
66
|
+
function resolveOnPath(names) {
|
|
67
|
+
for (const name of names) {
|
|
68
|
+
try {
|
|
69
|
+
const out = execSync(isWindows ? `where ${name}` : `command -v ${name}`, {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
shell: isWindows ? "cmd.exe" : "/bin/sh",
|
|
72
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
73
|
+
});
|
|
74
|
+
const first = out
|
|
75
|
+
.split(/\r?\n/)
|
|
76
|
+
.map((l) => l.trim())
|
|
77
|
+
.find((l) => l && existsSync(l) && (!isWindows || /\.(exe|cmd|bat)$/i.test(l)));
|
|
78
|
+
if (first)
|
|
79
|
+
return first;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function resolveOpenCode() {
|
|
87
|
+
const fromNpm = resolveGlobalPackageEntry("opencode-ai", join("bin", isWindows ? "opencode.exe" : "opencode")) ??
|
|
88
|
+
resolvePackageEntry("opencode-ai", join("bin", isWindows ? "opencode.exe" : "opencode"));
|
|
89
|
+
if (fromNpm)
|
|
90
|
+
return fromNpm;
|
|
91
|
+
const onPath = resolveOnPath(["opencode"]);
|
|
92
|
+
return onPath && !/\.(cmd|bat)$/i.test(onPath) ? onPath : null;
|
|
93
|
+
}
|
|
94
|
+
function resolveCopilot() {
|
|
95
|
+
const onPath = resolveOnPath(["copilot"]);
|
|
96
|
+
if (onPath && !/\.(cmd|bat)$/i.test(onPath))
|
|
97
|
+
return onPath;
|
|
98
|
+
if (isWindows && process.env.LOCALAPPDATA) {
|
|
99
|
+
const winget = join(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Links", "copilot.exe");
|
|
100
|
+
if (existsSync(winget))
|
|
101
|
+
return winget;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const claudeEntry = resolvePackageEntry("@agentclientprotocol/claude-agent-acp", join("dist", "index.js"));
|
|
106
|
+
const codexEntry = resolvePackageEntry("@agentclientprotocol/codex-acp", join("dist", "index.js"));
|
|
107
|
+
const geminiEntry = resolvePackageEntry("@google/gemini-cli", join("bundle", "gemini.js")) ??
|
|
108
|
+
resolveGlobalPackageEntry("@google/gemini-cli", join("bundle", "gemini.js"));
|
|
109
|
+
const cursorAgent = resolveCursorAgent();
|
|
110
|
+
const openCodeExe = resolveOpenCode();
|
|
111
|
+
const copilotExe = resolveCopilot();
|
|
112
|
+
const recipes = [
|
|
113
|
+
{
|
|
114
|
+
id: "claude",
|
|
115
|
+
label: "Claude (claude-agent-acp)",
|
|
116
|
+
vendor: "Claude",
|
|
117
|
+
icon: "/vendor-icons/claude.svg",
|
|
118
|
+
tested: true,
|
|
119
|
+
note: "Adapter around the Claude Agent SDK; uses the machine's Claude Code login and settings.",
|
|
120
|
+
modelPresets: ["haiku", "sonnet", "opus", "default"],
|
|
121
|
+
defaultModel: "sonnet",
|
|
122
|
+
effortPresets: ["low", "medium", "high", "default"],
|
|
123
|
+
defaultEffort: "low",
|
|
124
|
+
modePresets: ["default", "acceptEdits", "plan", "auto", "bypassPermissions"],
|
|
125
|
+
defaultMode: "default",
|
|
126
|
+
bypassMode: "bypassPermissions",
|
|
127
|
+
unavailableReason: claudeEntry ? null : "@agentclientprotocol/claude-agent-acp is not installed",
|
|
128
|
+
installedAt: claudeEntry,
|
|
129
|
+
installHint: "npm install (bundled with viberoom); needs a Claude Code login on this machine",
|
|
130
|
+
build: ({ model }) => ({
|
|
131
|
+
command: process.execPath,
|
|
132
|
+
args: [claudeEntry ?? ""],
|
|
133
|
+
env: model && model !== "default" ? { ANTHROPIC_MODEL: model } : undefined,
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "codex",
|
|
138
|
+
label: "Codex (codex-acp)",
|
|
139
|
+
vendor: "Codex",
|
|
140
|
+
icon: "/vendor-icons/codex.svg",
|
|
141
|
+
tested: true,
|
|
142
|
+
note: "Adapter around the Codex App Server (bundled Codex CLI); uses the machine's Codex login (~/.codex) or CODEX_API_KEY. Mode 'agent' edits the working directory without asking; 'read-only' for a chat-only participant.",
|
|
143
|
+
modelPresets: ["gpt-5.4-mini", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
|
|
144
|
+
defaultModel: "gpt-5.4-mini",
|
|
145
|
+
effortPresets: ["low", "medium", "high", "xhigh"],
|
|
146
|
+
defaultEffort: "low",
|
|
147
|
+
modePresets: ["read-only", "agent", "agent-full-access"],
|
|
148
|
+
defaultMode: "read-only",
|
|
149
|
+
bypassMode: "agent-full-access",
|
|
150
|
+
unavailableReason: codexEntry ? null : "@agentclientprotocol/codex-acp is not installed",
|
|
151
|
+
installedAt: codexEntry,
|
|
152
|
+
installHint: "npm install (bundled with viberoom); needs a Codex login (codex login) or CODEX_API_KEY",
|
|
153
|
+
build: () => ({
|
|
154
|
+
command: process.execPath,
|
|
155
|
+
args: [codexEntry ?? ""],
|
|
156
|
+
env: { NO_BROWSER: "1" },
|
|
157
|
+
}),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "gemini",
|
|
161
|
+
label: "Gemini CLI (gemini --acp)",
|
|
162
|
+
vendor: "Gemini",
|
|
163
|
+
icon: "/vendor-icons/gemini.svg",
|
|
164
|
+
tested: true,
|
|
165
|
+
note: "Native ACP mode of the globally installed Gemini CLI; uses the machine's Gemini login / API key. Exposes no config options over ACP: the model is fixed at launch (--model).",
|
|
166
|
+
modelPresets: ["gemini-3.8-flash", "gemini-3.7-flash"],
|
|
167
|
+
defaultModel: null,
|
|
168
|
+
effortPresets: [],
|
|
169
|
+
defaultEffort: null,
|
|
170
|
+
modePresets: ["default", "autoEdit", "yolo", "plan"],
|
|
171
|
+
defaultMode: "default",
|
|
172
|
+
bypassMode: "yolo",
|
|
173
|
+
unavailableReason: geminiEntry ? null : "Gemini CLI not found",
|
|
174
|
+
installedAt: geminiEntry,
|
|
175
|
+
installHint: "npm install -g @google/gemini-cli, then sign in once (gemini)",
|
|
176
|
+
modelAtLaunch: true,
|
|
177
|
+
build: ({ model }) => ({
|
|
178
|
+
command: process.execPath,
|
|
179
|
+
args: [geminiEntry ?? "", "--acp", ...(model ? ["--model", model] : [])],
|
|
180
|
+
env: { GEMINI_CLI_TRUST_WORKSPACE: "true" },
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "cursor",
|
|
185
|
+
label: "Cursor (cursor-agent acp)",
|
|
186
|
+
vendor: "Cursor",
|
|
187
|
+
icon: "/vendor-icons/cursor.svg",
|
|
188
|
+
tested: true,
|
|
189
|
+
note: "Cursor's CLI agent in native ACP mode; uses the machine's Cursor login (agent login) or CURSOR_API_KEY. Mode 'agent' edits without asking; 'ask' is read-only Q&A. Models: see the session settings after joining.",
|
|
190
|
+
modelPresets: [],
|
|
191
|
+
defaultModel: null,
|
|
192
|
+
effortPresets: [],
|
|
193
|
+
defaultEffort: null,
|
|
194
|
+
modePresets: ["agent", "plan", "ask"],
|
|
195
|
+
defaultMode: "ask",
|
|
196
|
+
bypassMode: "agent",
|
|
197
|
+
unavailableReason: cursorAgent ? null : "Cursor CLI not found",
|
|
198
|
+
installedAt: cursorAgent?.index ?? null,
|
|
199
|
+
installHint: "install cursor-agent (cursor.com/cli), then agent login",
|
|
200
|
+
build: () => ({
|
|
201
|
+
command: cursorAgent?.node ?? "",
|
|
202
|
+
args: [cursorAgent?.index ?? "", "acp"],
|
|
203
|
+
env: { CURSOR_INVOKED_AS: "cursor-agent" },
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "opencode",
|
|
208
|
+
label: "OpenCode (opencode acp)",
|
|
209
|
+
vendor: "OpenCode",
|
|
210
|
+
icon: "/vendor-icons/opencode.svg",
|
|
211
|
+
tested: false,
|
|
212
|
+
note: "The open-source coding agent in native ACP mode; the model list comes from the providers configured in OpenCode (opencode providers). Mode 'plan' is read-only; 'build' edits the working directory (OpenCode's own permission config decides what still asks; the questions arrive here).",
|
|
213
|
+
modelPresets: [],
|
|
214
|
+
defaultModel: null,
|
|
215
|
+
effortPresets: [],
|
|
216
|
+
defaultEffort: null,
|
|
217
|
+
modePresets: ["plan", "build"],
|
|
218
|
+
defaultMode: "plan",
|
|
219
|
+
bypassMode: "build",
|
|
220
|
+
unavailableReason: openCodeExe ? null : "OpenCode not found",
|
|
221
|
+
installedAt: openCodeExe,
|
|
222
|
+
installHint: "npm install -g opencode-ai (or curl -fsSL https://opencode.ai/install | bash), then opencode providers",
|
|
223
|
+
build: () => ({
|
|
224
|
+
command: openCodeExe ?? "",
|
|
225
|
+
args: ["acp"],
|
|
226
|
+
}),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "copilot",
|
|
230
|
+
label: "GitHub Copilot (copilot --acp)",
|
|
231
|
+
vendor: "Copilot",
|
|
232
|
+
icon: "/vendor-icons/copilot.svg",
|
|
233
|
+
tested: false,
|
|
234
|
+
note: "GitHub Copilot CLI in native ACP mode; uses the machine's Copilot login (copilot login). Session modes agent / plan / autopilot; the 'allow_all' option decides whether tool calls ask for permission. Exposes no model option over ACP: the model is fixed at launch (--model, e.g. auto).",
|
|
235
|
+
modelPresets: ["auto"],
|
|
236
|
+
defaultModel: null,
|
|
237
|
+
effortPresets: [],
|
|
238
|
+
defaultEffort: null,
|
|
239
|
+
modePresets: [
|
|
240
|
+
"https://agentclientprotocol.com/protocol/session-modes#agent",
|
|
241
|
+
"https://agentclientprotocol.com/protocol/session-modes#plan",
|
|
242
|
+
],
|
|
243
|
+
defaultMode: "https://agentclientprotocol.com/protocol/session-modes#agent",
|
|
244
|
+
bypassMode: "https://agentclientprotocol.com/protocol/session-modes#agent",
|
|
245
|
+
bypassConfig: { allow_all: "on" },
|
|
246
|
+
unavailableReason: copilotExe ? null : "GitHub Copilot CLI not found",
|
|
247
|
+
installedAt: copilotExe,
|
|
248
|
+
installHint: "winget install GitHub.Copilot / brew install copilot-cli / npm install -g @github/copilot, then copilot login",
|
|
249
|
+
modelAtLaunch: true,
|
|
250
|
+
build: ({ model }) => ({
|
|
251
|
+
command: copilotExe ?? "",
|
|
252
|
+
args: ["--acp", ...(model ? ["--model", model] : [])],
|
|
253
|
+
}),
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
export function listRecipes() {
|
|
257
|
+
return recipes;
|
|
258
|
+
}
|
|
259
|
+
export function getRecipe(id) {
|
|
260
|
+
return recipes.find((r) => r.id === id);
|
|
261
|
+
}
|