@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/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.108",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.17","@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(`
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
- const { canonical, legacy } = stateDirs(homeDir);
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 as mkdir2, unlink as unlink2, writeFile } from "node:fs/promises";
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, mkdir, symlink, unlink } from "node:fs/promises";
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
- mkdirSync(dirname2(path), { recursive: true });
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 mkdirSync2, readFileSync as readFileSync3, readdirSync, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
460
- import { join as join2 } from "node:path";
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 join2(dir, `${encodeURIComponent(key)}.json`);
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(join2(dir, name), "utf8"));
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(join2(dir, name), { force: true });
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(join2(dir, name), FILE_MODE);
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
- mkdirSync2(dir, { recursive: true, mode: DIR_MODE });
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
- renameSync2(staging, target);
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 existsSync3 } from "node:fs";
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 ?? existsSync3;
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 mkdir2(dirname3(socketPath), { recursive: true });
1418
- await mkdir2(dirname3(pidPath), { recursive: true });
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,