viberoom 0.5.7 → 0.5.8

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/README.md CHANGED
@@ -14,7 +14,7 @@ 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/conversation.png" width="960" alt="A Wren & Quinn room: Wren explains a git command from a screenshot, Quinn adds the part that changes whose problem it is">
17
+ <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">
18
18
  </p>
19
19
 
20
20
  <p align="center">
package/dist/main.js CHANGED
@@ -192,7 +192,8 @@ function openWindow(url, options, log) {
192
192
  }
193
193
  catch {
194
194
  }
195
- spawn(chromium, args, { detached: true, stdio: "ignore" }).unref();
195
+ mkdirSync(options.dataDir, { recursive: true });
196
+ spawn(chromium, args, { cwd: options.dataDir, detached: true, stdio: "ignore" }).unref();
196
197
  if (process.platform === "win32") {
197
198
  const shortcuts = windowsShortcutPaths(homedir(), process.env, true).filter((p) => existsSync(p));
198
199
  if (shortcuts.length)
@@ -325,7 +326,7 @@ async function startBackground(options, log, info) {
325
326
  const args = [fileURLToPath(import.meta.url), "serve", "--port", String(options.port), "--data-dir", options.dataDir, "--no-open"];
326
327
  if (options.name)
327
328
  args.push("--name", options.name);
328
- const child = spawn(process.execPath, args, { detached: true, stdio: ["ignore", fd, fd], windowsHide: true });
329
+ const child = spawn(process.execPath, args, { cwd: options.dataDir, detached: true, stdio: ["ignore", fd, fd], windowsHide: true });
329
330
  child.unref();
330
331
  closeSync(fd);
331
332
  log.info(`hub started in the background (pid ${child.pid}); log: ${logPath}`);
package/dist/server.js CHANGED
@@ -193,7 +193,7 @@ export function startServer(hub, port, log, info, onShutdownRequest) {
193
193
  if (!latest)
194
194
  throw new Error("no newer version is known; check for updates first");
195
195
  log.info(`installing viberoom ${latest} (npm install -g)`);
196
- const result = await installUpdate(latest);
196
+ const result = await installUpdate(latest, hub.dataDir);
197
197
  if (!result.ok)
198
198
  throw new Error(`npm install failed: ${result.output.slice(-600) || "no output"}`);
199
199
  log.info(`viberoom ${latest} installed; starting the new build, which replaces this hub`);
package/dist/shortcuts.js CHANGED
@@ -4,8 +4,8 @@ import { chmodSync, copyFileSync, existsSync, mkdirSync, writeFileSync } from "n
4
4
  import { homedir } from "node:os";
5
5
  import { basename, join } from "node:path";
6
6
  import { findChromium } from "./launcher.js";
7
- export function vbsLauncher(node, main) {
8
- return ["' viberoom: start the hub without a console window and open the app window.", 'Set sh = CreateObject("WScript.Shell")', `sh.Run """${node}"" ""${main}"" start", 0, False`, ""].join("\r\n");
7
+ export function vbsLauncher(node, main, workingDir) {
8
+ return ["' viberoom: start the hub without a console window and open the app window.", 'Set sh = CreateObject("WScript.Shell")', `sh.CurrentDirectory = "${workingDir}"`, `sh.Run """${node}"" ""${main}"" start", 0, False`, ""].join("\r\n");
9
9
  }
10
10
  const AUMID_BASE = { "chrome.exe": "Chrome", "msedge.exe": "MSEdge", "brave.exe": "Brave", "chromium.exe": "Chromium" };
11
11
  export function appUserModelId(browserPath, url = "http://127.0.0.1:4810/", profileDirName = "browser") {
@@ -42,13 +42,13 @@ public static class LnkAumid {
42
42
  }
43
43
  }`;
44
44
  const psq = (s) => s.replace(/'/g, "''");
45
- export function shortcutScript(lnk, wscript, vbs, root, ico, aumid = null) {
45
+ export function shortcutScript(lnk, wscript, vbs, workingDir, ico, aumid = null) {
46
46
  const lines = [
47
47
  "$ErrorActionPreference = 'Stop'",
48
48
  `$s = (New-Object -ComObject WScript.Shell).CreateShortcut('${psq(lnk)}')`,
49
49
  `$s.TargetPath = '${psq(wscript)}'`,
50
50
  `$s.Arguments = '"${psq(vbs)}"'`,
51
- `$s.WorkingDirectory = '${psq(root)}'`,
51
+ `$s.WorkingDirectory = '${psq(workingDir)}'`,
52
52
  `$s.Description = 'viberoom: rooms for you and your coding agents'`,
53
53
  ico ? `$s.IconLocation = '${psq(ico)},0'` : "",
54
54
  "$s.Save()",
@@ -82,8 +82,8 @@ export function windowsShortcutPaths(home, env, desktop) {
82
82
  targets.push(join(home, "Desktop", "viberoom.lnk"));
83
83
  return targets;
84
84
  }
85
- export function desktopEntry(node, main, icon) {
86
- return ["[Desktop Entry]", "Type=Application", "Name=viberoom", "Comment=Rooms for you and your coding agents", `Exec="${node}" "${main}" start`, `Icon=${icon}`, "Terminal=false", "StartupWMClass=viberoom", "Categories=Development;Chat;", ""].join("\n");
85
+ export function desktopEntry(node, main, icon, workingDir) {
86
+ return ["[Desktop Entry]", "Type=Application", "Name=viberoom", "Comment=Rooms for you and your coding agents", `Exec="${node}" "${main}" start`, `Path=${workingDir}`, `Icon=${icon}`, "Terminal=false", "StartupWMClass=viberoom", "Categories=Development;Chat;", ""].join("\n");
87
87
  }
88
88
  export function macPlist(version) {
89
89
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -113,7 +113,7 @@ export function installShortcuts(o) {
113
113
  const icnsSrc = join(o.root, "assets", "icon.icns");
114
114
  if (platform === "win32") {
115
115
  const vbs = join(launcherDir, "viberoom.vbs");
116
- writeFileSync(vbs, vbsLauncher(o.node, main));
116
+ writeFileSync(vbs, vbsLauncher(o.node, main, o.dataDir));
117
117
  result.files.push(vbs);
118
118
  let ico = null;
119
119
  if (existsSync(icoSrc)) {
@@ -126,7 +126,7 @@ export function installShortcuts(o) {
126
126
  const aumid = appUserModelId(browser, "http://127.0.0.1:4810/", basename(join(o.dataDir, "browser")));
127
127
  for (const lnk of windowsShortcutPaths(home, env, o.desktop)) {
128
128
  mkdirSync(join(lnk, ".."), { recursive: true });
129
- const r = spawnSync("powershell", ["-NoProfile", "-Command", shortcutScript(lnk, wscript, vbs, o.root, ico, aumid)], { encoding: "utf8" });
129
+ const r = spawnSync("powershell", ["-NoProfile", "-Command", shortcutScript(lnk, wscript, vbs, o.dataDir, ico, aumid)], { encoding: "utf8" });
130
130
  if (r.status === 0)
131
131
  result.files.push(lnk);
132
132
  else
@@ -142,7 +142,7 @@ export function installShortcuts(o) {
142
142
  mkdirSync(join(app, "Contents", "Resources"), { recursive: true });
143
143
  writeFileSync(join(app, "Contents", "Info.plist"), macPlist(o.version));
144
144
  const exe = join(app, "Contents", "MacOS", "viberoom");
145
- writeFileSync(exe, `#!/bin/sh\nexec "${o.node}" "${main}" start\n`);
145
+ writeFileSync(exe, `#!/bin/sh\ncd "${o.dataDir}" 2>/dev/null\nexec "${o.node}" "${main}" start\n`);
146
146
  chmodSync(exe, 0o755);
147
147
  if (existsSync(icnsSrc))
148
148
  copyFileSync(icnsSrc, join(app, "Contents", "Resources", "icon.icns"));
@@ -157,7 +157,7 @@ export function installShortcuts(o) {
157
157
  copyFileSync(pngSrc, icon);
158
158
  result.files.push(icon);
159
159
  }
160
- const entry = desktopEntry(o.node, main, icon);
160
+ const entry = desktopEntry(o.node, main, icon, o.dataDir);
161
161
  const appsDir = join(home, ".local", "share", "applications");
162
162
  mkdirSync(appsDir, { recursive: true });
163
163
  const menuEntry = join(appsDir, "viberoom.desktop");
package/dist/update.js CHANGED
@@ -74,10 +74,10 @@ export function installCommandLine(version) {
74
74
  throw new Error(`not a version: ${version}`);
75
75
  return `npm install -g viberoom@${version} --no-audit --no-fund`;
76
76
  }
77
- export function installUpdate(version) {
77
+ export function installUpdate(version, cwd) {
78
78
  const line = installCommandLine(version);
79
79
  return new Promise((resolve) => {
80
- const child = process.platform === "win32" ? spawn(line, { shell: true, windowsHide: true }) : spawn("npm", line.split(" ").slice(1));
80
+ const child = process.platform === "win32" ? spawn(line, { cwd, shell: true, windowsHide: true }) : spawn("npm", line.split(" ").slice(1), { cwd });
81
81
  let output = "";
82
82
  const collect = (chunk) => {
83
83
  output = (output + chunk.toString()).slice(-4000);
@@ -90,6 +90,6 @@ export function installUpdate(version) {
90
90
  }
91
91
  export function restartWithNewBuild(mainModuleUrl, port, dataDir) {
92
92
  const main = fileURLToPath(mainModuleUrl);
93
- const child = spawn(process.execPath, [main, "start", "--port", String(port), "--data-dir", dataDir, "--no-open"], { detached: true, stdio: "ignore", windowsHide: true });
93
+ const child = spawn(process.execPath, [main, "start", "--port", String(port), "--data-dir", dataDir, "--no-open"], { cwd: dataDir, detached: true, stdio: "ignore", windowsHide: true });
94
94
  child.unref();
95
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viberoom",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "viberoom: group chat rooms for a human and several coding agents over the Agent Client Protocol",
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,9 +11,14 @@ const args = new Set(process.argv.slice(2));
11
11
  const dataDir = process.env.VIBEROOM_DATA_DIR ? resolve(process.env.VIBEROOM_DATA_DIR) : join(homedir(), ".viberoom");
12
12
  const isWin = process.platform === "win32";
13
13
 
14
+ function npmEnv() {
15
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !/^npm_config_/i.test(name)));
16
+ }
17
+
14
18
  function run(cmd, cmdArgs, opts = {}) {
15
19
  console.log(`> ${cmd} ${cmdArgs.join(" ")}`);
16
- const r = isWin && cmd === "npm" ? spawnSync(`npm ${cmdArgs.join(" ")}`, { cwd: root, stdio: "inherit", shell: true, ...opts }) : spawnSync(cmd, cmdArgs, { cwd: root, stdio: "inherit", ...opts });
20
+ const env = cmd === "npm" ? npmEnv() : process.env;
21
+ const r = isWin && cmd === "npm" ? spawnSync(`npm ${cmdArgs.join(" ")}`, { cwd: root, stdio: "inherit", env, shell: true, ...opts }) : spawnSync(cmd, cmdArgs, { cwd: root, stdio: "inherit", env, ...opts });
17
22
  if (r.status !== 0) throw new Error(`${cmd} ${cmdArgs.join(" ")} failed with exit code ${r.status}`);
18
23
  }
19
24
 
@@ -10,9 +10,14 @@ const args = new Set(process.argv.slice(2));
10
10
  const isWin = process.platform === "win32";
11
11
  const main = join(root, "dist", "main.js");
12
12
 
13
+ function npmEnv() {
14
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !/^npm_config_/i.test(name)));
15
+ }
16
+
13
17
  function run(cmd, cmdArgs, opts = {}) {
14
18
  console.log(`> ${cmd} ${cmdArgs.join(" ")}`);
15
- const r = isWin && cmd === "npm" ? spawnSync(`npm ${cmdArgs.join(" ")}`, { cwd: root, stdio: "inherit", shell: true, ...opts }) : spawnSync(cmd, cmdArgs, { cwd: root, stdio: "inherit", ...opts });
19
+ const env = cmd === "npm" ? npmEnv() : process.env;
20
+ const r = isWin && cmd === "npm" ? spawnSync(`npm ${cmdArgs.join(" ")}`, { cwd: root, stdio: "inherit", env, shell: true, ...opts }) : spawnSync(cmd, cmdArgs, { cwd: root, stdio: "inherit", env, ...opts });
16
21
  if (r.status !== 0) throw new Error(`${cmd} ${cmdArgs.join(" ")} failed with exit code ${r.status}`);
17
22
  }
18
23
 
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "Maken & Elaine",
3
+ "order": 1,
4
+ "emoji": "⚒️",
5
+ "recommended": true,
6
+ "description": "Two equals around one piece of work. Maken makes by default, Elaine explains by default; either does either when asked, and either reviews the other's work when you ask for it. The way viberoom itself was made.",
7
+ "settings": {
8
+ "language": {
9
+ "mode": "follow-human"
10
+ },
11
+ "turnTaking": "parallel",
12
+ "agentsWakeEachOther": true,
13
+ "hopLimit": 24,
14
+ "customRules": "Both make, both explain, both review; @ decides who does what. Without @, a task goes to the one who has worked on that part the most, or most recently; if neither has touched it, to Maken. A question without @ goes to Elaine.\nThe human decides when work is reviewed: ask the other one to review it. Unasked, the other answers questions and otherwise stays silent; it does not review, comment on or extend work it was not asked about. When asked to review, first say in one line what you will check, then check it by that criterion: a number, a live check, the risky case. Whoever made it reports to the human. Address the other vibemate too only when the report changes something it works on or relies on: a shared file, an interface, a convention, a measurement that overturns its claim; then say in one line what you want from it (\"nothing, for your context\" counts). Otherwise it reads the report later, unaddressed. A report is never a request for review; only the human asks for one. Finished work is reported as: what changed (the files, and the commit if the project uses version control), how to verify it (a command, a script, a screenshot), what was not verified, what is left.\nMeasure before diagnosing; say no with a reason and a cheaper alternative; keep replies short.\nWork only on your own task, never on the other's; do not touch what the other is working on.\nReply only when addressed. On a message to everyone, only the one it concerns answers; the other stays silent. Never add to, correct or comment on the other's reply unless the human asks.\nFollow the conventions of the project you work in (its instructions, notes, tests). Where it keeps decision notes, write the decision there before reporting it here.\nRead every proposal and analyse it; agree or reject only with a real argument. Never concede to be agreeable, never object to seem rigorous.\nReply in the human's language. Code identifiers, commands, file paths, protocol and tool names stay exactly as in the code; established technical terms (kernel, thread, commit, pull request, layout) stay in English."
15
+ },
16
+ "vibemates": [
17
+ {
18
+ "name": "Maken",
19
+ "tagline": "makes by default; explains and reviews when asked",
20
+ "role": "You are Maken, one of two equal vibemates, Maken and Elaine. You lean to making: a task without an address is yours when it touches what you have made, or when neither of you has; before you change anything, read what is already there, the notes, the docs and the files, and keep each change small. Everything else is in the room rules.",
21
+ "avatar": "🔨"
22
+ },
23
+ {
24
+ "name": "Elaine",
25
+ "tagline": "explains by default; makes and reviews when asked",
26
+ "role": "You are Elaine, one of two equal vibemates, Maken and Elaine. You lean to explaining: a question without an address is yours; when you review, it is by a criterion, not an impression. Everything else is in the room rules.",
27
+ "avatar": "💡"
28
+ }
29
+ ]
30
+ }
@@ -1,30 +0,0 @@
1
- {
2
- "name": "Wren & Quinn",
3
- "order": 1,
4
- "emoji": "⚒️",
5
- "recommended": true,
6
- "description": "Two equals around one codebase. Wren builds by default, Quinn explains by default; either does either when asked, and either reviews the other's work when you ask for it. The way viberoom itself was built.",
7
- "settings": {
8
- "language": {
9
- "mode": "follow-human"
10
- },
11
- "turnTaking": "parallel",
12
- "agentsWakeEachOther": true,
13
- "hopLimit": 24,
14
- "customRules": "Both build, both explain, both review; @ decides who does what. Without @, a task goes to the one who has worked on that part the most, or most recently; if neither has touched it, to Wren. A question without @ goes to Quinn.\nThe human decides when work is reviewed: ask the other one to review it. Unasked, the other answers questions and otherwise stays silent; it does not review, comment on or extend work it was not asked about. When asked to review, first say in one line what you will check, then check it by that criterion: a number, a live check, the risky case. Whoever built reports to the human. Address the other vibemate too only when the report changes something it works on or relies on: a shared file, an interface, a convention, a measurement that overturns its claim; then say in one line what you want from it (\"nothing, for your context\" counts). Otherwise it reads the report later, unaddressed. A report is never a request for review; only the human asks for one. A build is reported as: what changed (the files, and the commit if the project uses version control), how to verify it (a command, a script, a screenshot), what was not verified, what is left.\nMeasure before diagnosing; say no with a reason and a cheaper alternative; keep replies short.\nWork only on your own task, never on the other's; do not touch what the other is working on.\nReply only when addressed. On a message to everyone, only the one it concerns answers; the other stays silent. Never add to, correct or comment on the other's reply unless the human asks.\nFollow the conventions of the project you work in (its instructions, notes, tests). Where it keeps decision notes, write the decision there before reporting it here.\nRead every proposal and analyse it; agree or reject only with a real argument. Never concede to be agreeable, never object to seem rigorous.\nReply in the human's language. Code identifiers, commands, file paths, protocol and tool names stay exactly as in the code; established technical terms (kernel, thread, commit, pull request, layout) stay in English."
15
- },
16
- "vibemates": [
17
- {
18
- "name": "Wren",
19
- "tagline": "builds by default; explains and reviews when asked",
20
- "role": "You are Wren, one of two equal vibemates, Wren and Quinn. You lean to building: a task without an address is yours when it touches what you have built, or when neither of you has; before you change anything, read what is already there, the notes, the docs and the code, and keep each change small. Everything else is in the room rules.",
21
- "avatar": "🔨"
22
- },
23
- {
24
- "name": "Quinn",
25
- "tagline": "explains by default; builds and reviews when asked",
26
- "role": "You are Quinn, one of two equal vibemates, Wren and Quinn. You lean to explaining: a question without an address is yours; when you review, it is by a criterion, not an impression. Everything else is in the room rules.",
27
- "avatar": "💡"
28
- }
29
- ]
30
- }