@sma1lboy/rove 0.9.108 → 0.9.110
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/dist/cli/index.js +81 -76
- package/dist/cli/kobe-run.js +81 -76
- package/dist/cli/kobe.js +1 -1
- package/dist/cli/pty-host-node.mjs +60 -24
- package/dist/cli/rove-run.js +81 -76
- package/dist/cli/rove.js +1 -1
- package/dist/skills/rove/references/api-flags.md +1 -1
- package/package.json +2 -2
package/dist/cli/kobe.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{join as $1}from"node:path";import{createInterface as J1}from"node:readline/promises";import{pathToFileURL as Q1}from"node:url";import{spawnSync as L}from"node:child_process";import{constants as u,accessSync as l}from"node:fs";import{homedir as t}from"node:os";import{basename as o,delimiter as r,dirname as i,join as Y}from"node:path";import{fileURLToPath as C}from"node:url";var U={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.
|
|
2
|
+
import{join as $1}from"node:path";import{createInterface as J1}from"node:readline/promises";import{pathToFileURL as Q1}from"node:url";import{spawnSync as L}from"node:child_process";import{constants as u,accessSync as l}from"node:fs";import{homedir as t}from"node:os";import{basename as o,delimiter as r,dirname as i,join as Y}from"node:path";import{fileURLToPath as C}from"node:url";var U={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.110",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{kobe:"dist/cli/kobe.js",rove:"dist/cli/rove.js"},files:["dist/cli","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:kobe":"KOBE_DEV=1 bun --conditions=browser ./src/cli/kobe.ts","dev:mock":"KOBE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"KOBE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"KOBE_INCLUDE_SOCKET=1 KOBE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"KOBE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun test test/render --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage-render",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0","node-pty":"^1.1.0",react:"^19.2.8",ws:"^8.18.0"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/kobe-daemon":"0.7.18","@tsconfig/bun":"1.0.10","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var x="rove",j="kobe";var E="ROVE_INVOKED_AS";function F(W=process.env){return W[E]===x?x:j}var k="ROVE_BUN",a="ROVE_NO_BUN_BOOTSTRAP",M="ROVE_SKIP_BUN_CHECK",G=U.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",T=(W)=>{try{return l(W,u.X_OK),!0}catch{return!1}},n=(W)=>W==="win32"?"bun.exe":"bun",e=(W)=>{let b=L(W,["--version"],{encoding:"utf8",timeout:5000});if(b.error||b.status!==0)return null;return b.stdout?.split(`
|
|
3
3
|
`)[0]?.trim()??""};function K(W){return W.trim().match(/^v?(\d+\.\d+\.\d+)/)?.[1]??null}function P(W,b=G){let z=K(W),Q=K(b);if(!z||!Q)return!0;let J=z.split(".").map(Number),$=Q.split(".").map(Number);for(let X=0;X<3;X++){let q=J[X]??0,H=$[X]??0;if(q!==H)return q>H}return!0}function g(W=process.env){return W[M]==="1"}function p(W={}){let b=W.env??process.env,z=W.platform??process.platform,Q=W.home??b.HOME??b.USERPROFILE??t(),J=n(z),$=[],X=b[k]?.trim();if(X)$.push(X);for(let D of(b.PATH??b.Path??"").split(r))if(D)$.push(Y(D,J));let q=b.BUN_INSTALL?.trim();if(q)$.push(Y(q,"bin",J));if(Q)$.push(Y(Q,".bun","bin",J));let H=W.launcherDir;if(H)$.push(Y(H,"..","..","node_modules","bun","bin",J)),$.push(Y(H,"..","..","..","bun","bin",J));return $}function W1(W={}){let b=W.isExecutable??T;return p(W).find((z)=>b(z))??null}function w(W={}){let b=W.isExecutable??T,z=W.bunVersionOf??e;if(g(W.env??process.env))return{bun:W1(W),stale:null,unusable:null};let Q=null,J=null;for(let $ of p(W)){if(!b($))continue;let X=z($);if(X===null){J??=$;continue}if(P(X))return{bun:$,stale:Q,unusable:J};Q??={path:$,version:K(X)??X}}return{bun:null,stale:Q,unusable:J}}function B(W){return i(C(W))}function O(W){return o(C(W)).replace(/\.[cm]?[jt]s$/,"")}function b1(W=process.platform){return W==="win32"?["powershell","-NoProfile","-Command","irm bun.sh/install.ps1 | iex"]:["bash","-c","curl -fsSL https://bun.sh/install | bash"]}function y(W=F(),b=process.platform){let z=b==="win32"?'powershell -c "irm bun.sh/install.ps1 | iex"':"curl -fsSL https://bun.sh/install | bash";return[`${W}: Rove runs on the Bun runtime, and no Bun was found on this machine.`,"","Install Bun, then run this command again:",` ${z}`," npm install -g bun # any platform, if you already have npm","","Or install Bun and Rove together in one step:"," curl -fsSL https://rove.run/install.sh | sh","",`Already have Bun somewhere unusual? Point Rove at it: ${k}=/path/to/bun`,""].join(`
|
|
4
4
|
`)}function h(W,b,z=F()){return[`${z}: this machine's Bun is too old for Rove — found ${b} at ${W}, need ${G} or newer.`,"","Rove's terminals are built on Bun's PTY API. An older Bun ignores it silently,","so every terminal and engine tab would open empty and stay empty.","","Upgrade Bun, then run this command again:"," bun upgrade # Bun installed itself (~/.bun)"," brew upgrade bun # Homebrew"," npm install -g bun@latest # npm-managed Bun","",`Have a newer Bun elsewhere? Point Rove at it: ${k}=/path/to/bun`,`Run on this one anyway (unsupported, terminals will be blank): ${M}=1`,""].join(`
|
|
5
5
|
`)}function A(W,b=F()){return[`${b}: the Bun at ${W} could not be run — \`${W} --version\` failed or never returned.`,"","Rove skipped it rather than starting through it, which would hang or fail","with a message from that binary instead of this one.","","Reinstall Bun, then run this command again:"," curl -fsSL https://bun.sh/install | bash","",`Have a working Bun elsewhere? Point Rove at it: ${k}=/path/to/bun`,""].join(`
|
|
@@ -68,12 +68,7 @@ function legacyPtyHostPidPath(homeDir) {
|
|
|
68
68
|
return legacyRuntimePath(homeDir, "pty.pid");
|
|
69
69
|
}
|
|
70
70
|
function runtimeDataPath(homeDir, name) {
|
|
71
|
-
|
|
72
|
-
const canonicalPath = join(canonical, name);
|
|
73
|
-
if (existsSync2(canonicalPath))
|
|
74
|
-
return canonicalPath;
|
|
75
|
-
const legacyPath = join(legacy, name);
|
|
76
|
-
return existsSync2(legacyPath) ? legacyPath : canonicalPath;
|
|
71
|
+
return join(stateDirs(homeDir).canonical, name);
|
|
77
72
|
}
|
|
78
73
|
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
79
74
|
function shortHomeTag(homeDir) {
|
|
@@ -187,7 +182,7 @@ async function nodePtyDriver(spawn) {
|
|
|
187
182
|
|
|
188
183
|
// ../kobe-daemon/src/daemon/pty-server.ts
|
|
189
184
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
190
|
-
import { mkdir
|
|
185
|
+
import { mkdir, unlink as unlink2, writeFile } from "node:fs/promises";
|
|
191
186
|
import { createServer } from "node:net";
|
|
192
187
|
import { dirname as dirname3 } from "node:path";
|
|
193
188
|
import { StringDecoder as StringDecoder3 } from "node:string_decoder";
|
|
@@ -288,18 +283,19 @@ class ClientWriter {
|
|
|
288
283
|
}
|
|
289
284
|
|
|
290
285
|
// ../kobe-daemon/src/daemon/compat-link.ts
|
|
291
|
-
import { lstat,
|
|
286
|
+
import { lstat, symlink, unlink } from "node:fs/promises";
|
|
292
287
|
import { dirname } from "node:path";
|
|
293
288
|
async function linkLegacyRuntimePath(canonical, legacy) {
|
|
294
289
|
if (canonical === legacy)
|
|
295
290
|
return false;
|
|
296
291
|
try {
|
|
292
|
+
if (!await lstat(dirname(legacy)).catch(() => null))
|
|
293
|
+
return false;
|
|
297
294
|
const existing = await lstat(legacy).catch(() => null);
|
|
298
295
|
if (existing && !existing.isSymbolicLink())
|
|
299
296
|
return false;
|
|
300
297
|
if (existing)
|
|
301
298
|
await unlink(legacy).catch(() => {});
|
|
302
|
-
await mkdir(dirname(legacy), { recursive: true });
|
|
303
299
|
await symlink(canonical, legacy);
|
|
304
300
|
return true;
|
|
305
301
|
} catch {
|
|
@@ -366,9 +362,10 @@ var DAEMON_CHANNELS = [
|
|
|
366
362
|
// ../kobe-daemon/src/daemon/channels.ts
|
|
367
363
|
var CHANNEL_NAMES = DAEMON_CHANNELS;
|
|
368
364
|
var CHANNEL_NAME_SET = new Set(CHANNEL_NAMES);
|
|
365
|
+
// ../kobe-daemon/src/daemon/protocol-compat.ts
|
|
366
|
+
var DAEMON_PROTOCOL_VERSION = 4;
|
|
369
367
|
|
|
370
368
|
// ../kobe-daemon/src/daemon/protocol.ts
|
|
371
|
-
var DAEMON_PROTOCOL_VERSION = 4;
|
|
372
369
|
var BLOCKING_RPCS = new Set([
|
|
373
370
|
"ui.prompt",
|
|
374
371
|
"task.land",
|
|
@@ -388,8 +385,46 @@ function frameToLine(frame) {
|
|
|
388
385
|
`;
|
|
389
386
|
}
|
|
390
387
|
|
|
388
|
+
// ../kobe-daemon/src/daemon/pty-data-migration.ts
|
|
389
|
+
import { existsSync as existsSync3, lstatSync, mkdirSync, renameSync as renameSync2, symlinkSync } from "node:fs";
|
|
390
|
+
import { homedir as homedir2 } from "node:os";
|
|
391
|
+
import { join as join2 } from "node:path";
|
|
392
|
+
var PTY_HOST_DATA_ENTRIES = ["pty-exits.json", "pty-sessions"];
|
|
393
|
+
function lstatIfExists(path) {
|
|
394
|
+
try {
|
|
395
|
+
return lstatSync(path);
|
|
396
|
+
} catch {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function migrateLegacyPtyHostData(homeDir = readRoveEnv("HOME_DIR") ?? homedir2()) {
|
|
401
|
+
const canonicalDir = join2(homeDir, ROVE_STATE_DIR_BASENAME);
|
|
402
|
+
const legacyDir = join2(homeDir, COMPAT_STATE_DIR_BASENAME);
|
|
403
|
+
if (canonicalDir === legacyDir)
|
|
404
|
+
return [];
|
|
405
|
+
const moved = [];
|
|
406
|
+
for (const name of PTY_HOST_DATA_ENTRIES) {
|
|
407
|
+
const canonical = join2(canonicalDir, name);
|
|
408
|
+
const legacy = join2(legacyDir, name);
|
|
409
|
+
try {
|
|
410
|
+
if (existsSync3(canonical))
|
|
411
|
+
continue;
|
|
412
|
+
const stat = lstatIfExists(legacy);
|
|
413
|
+
if (!stat || stat.isSymbolicLink())
|
|
414
|
+
continue;
|
|
415
|
+
mkdirSync(canonicalDir, { recursive: true });
|
|
416
|
+
renameSync2(legacy, canonical);
|
|
417
|
+
moved.push(name);
|
|
418
|
+
try {
|
|
419
|
+
symlinkSync(canonical, legacy);
|
|
420
|
+
} catch {}
|
|
421
|
+
} catch {}
|
|
422
|
+
}
|
|
423
|
+
return moved;
|
|
424
|
+
}
|
|
425
|
+
|
|
391
426
|
// ../kobe-daemon/src/daemon/pty-exit-store.ts
|
|
392
|
-
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
427
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
393
428
|
import { dirname as dirname2 } from "node:path";
|
|
394
429
|
|
|
395
430
|
// ../kobe-daemon/src/daemon/terminal-rows.ts
|
|
@@ -451,13 +486,13 @@ function writeRecord(storeKey, record, path) {
|
|
|
451
486
|
const records = readPtyExitRecords(path);
|
|
452
487
|
records[storeKey] = record;
|
|
453
488
|
const newest = Object.entries(records).sort(([, a], [, b]) => a.at < b.at ? 1 : -1).slice(0, MAX_RECORDS);
|
|
454
|
-
|
|
489
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
455
490
|
writeFileSync(path, JSON.stringify(Object.fromEntries(newest), null, 2), { encoding: "utf8", mode: 384 });
|
|
456
491
|
}
|
|
457
492
|
|
|
458
493
|
// ../kobe-daemon/src/daemon/pty-freeze-store.ts
|
|
459
|
-
import { chmodSync, mkdirSync as
|
|
460
|
-
import { join as
|
|
494
|
+
import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
495
|
+
import { join as join3 } from "node:path";
|
|
461
496
|
import { StringDecoder } from "node:string_decoder";
|
|
462
497
|
|
|
463
498
|
// ../kobe-daemon/src/daemon/terminal-colors.ts
|
|
@@ -567,7 +602,7 @@ function thawSession(record, cap) {
|
|
|
567
602
|
};
|
|
568
603
|
}
|
|
569
604
|
function recordFile(dir, key) {
|
|
570
|
-
return
|
|
605
|
+
return join3(dir, `${encodeURIComponent(key)}.json`);
|
|
571
606
|
}
|
|
572
607
|
function parseRecord(raw) {
|
|
573
608
|
try {
|
|
@@ -607,7 +642,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now()) {
|
|
|
607
642
|
continue;
|
|
608
643
|
let record = null;
|
|
609
644
|
try {
|
|
610
|
-
record = parseRecord(readFileSync3(
|
|
645
|
+
record = parseRecord(readFileSync3(join3(dir, name), "utf8"));
|
|
611
646
|
} catch {}
|
|
612
647
|
if (!record)
|
|
613
648
|
continue;
|
|
@@ -621,7 +656,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now()) {
|
|
|
621
656
|
stale.push(over.name);
|
|
622
657
|
for (const name of stale) {
|
|
623
658
|
try {
|
|
624
|
-
rmSync(
|
|
659
|
+
rmSync(join3(dir, name), { force: true });
|
|
625
660
|
} catch {}
|
|
626
661
|
}
|
|
627
662
|
return kept.map((entry) => entry.record);
|
|
@@ -642,7 +677,7 @@ function tightenExistingPermissions(dir) {
|
|
|
642
677
|
if (!name.endsWith(".json"))
|
|
643
678
|
continue;
|
|
644
679
|
try {
|
|
645
|
-
chmodSync(
|
|
680
|
+
chmodSync(join3(dir, name), FILE_MODE);
|
|
646
681
|
} catch {}
|
|
647
682
|
}
|
|
648
683
|
}
|
|
@@ -651,11 +686,11 @@ function fileFreezeSink(dir = defaultPtyFreezeDir()) {
|
|
|
651
686
|
return {
|
|
652
687
|
save(record) {
|
|
653
688
|
try {
|
|
654
|
-
|
|
689
|
+
mkdirSync3(dir, { recursive: true, mode: DIR_MODE });
|
|
655
690
|
const target = recordFile(dir, record.key);
|
|
656
691
|
const staging = `${target}.${process.pid}.tmp`;
|
|
657
692
|
writeFileSync2(staging, JSON.stringify(record), { encoding: "utf8", mode: FILE_MODE });
|
|
658
|
-
|
|
693
|
+
renameSync3(staging, target);
|
|
659
694
|
} catch {}
|
|
660
695
|
},
|
|
661
696
|
drop(key) {
|
|
@@ -672,7 +707,7 @@ function clearFrozenSessions(dir = defaultPtyFreezeDir()) {
|
|
|
672
707
|
}
|
|
673
708
|
|
|
674
709
|
// ../kobe-daemon/src/daemon/platform-shell.js
|
|
675
|
-
import { existsSync as
|
|
710
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
676
711
|
function windowsBashCandidates(env) {
|
|
677
712
|
const roots = [env.ProgramFiles, env["ProgramFiles(x86)"], env.LOCALAPPDATA && `${env.LOCALAPPDATA}\\Programs`];
|
|
678
713
|
return roots.filter(Boolean).map((root) => `${root}\\Git\\bin\\bash.exe`);
|
|
@@ -686,7 +721,7 @@ function resolveLoginShell(options = {}) {
|
|
|
686
721
|
const fallback = options.fallback ?? "/bin/bash";
|
|
687
722
|
const platform = options.platform ?? process.platform;
|
|
688
723
|
const env = options.env ?? process.env;
|
|
689
|
-
const exists = options.exists ??
|
|
724
|
+
const exists = options.exists ?? existsSync4;
|
|
690
725
|
const cacheable = platform === process.platform && env === process.env && options.exists === undefined;
|
|
691
726
|
if (cacheable) {
|
|
692
727
|
const hit = cache.get(fallback);
|
|
@@ -1341,6 +1376,7 @@ function resolveIdleExitMs() {
|
|
|
1341
1376
|
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_IDLE_EXIT_MS;
|
|
1342
1377
|
}
|
|
1343
1378
|
async function startPtyHostServer(options = {}) {
|
|
1379
|
+
migrateLegacyPtyHostData();
|
|
1344
1380
|
const socketPath = options.socketPath ?? defaultPtyHostSocketPath();
|
|
1345
1381
|
const pidPath = options.pidPath ?? defaultPtyHostPidPath();
|
|
1346
1382
|
const freezeDir = options.freezeDir ?? defaultPtyFreezeDir();
|
|
@@ -1414,8 +1450,8 @@ async function startPtyHostServer(options = {}) {
|
|
|
1414
1450
|
ptys.restoreFrozen(loadFrozenSessions(freezeDir));
|
|
1415
1451
|
const pipeSocket = isWindowsPipePath(socketPath);
|
|
1416
1452
|
if (!pipeSocket)
|
|
1417
|
-
await
|
|
1418
|
-
await
|
|
1453
|
+
await mkdir(dirname3(socketPath), { recursive: true });
|
|
1454
|
+
await mkdir(dirname3(pidPath), { recursive: true });
|
|
1419
1455
|
const server = createServer((socket) => {
|
|
1420
1456
|
const client = {
|
|
1421
1457
|
socket,
|