maxsimcli 2.1.0 → 2.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.
@@ -10,8 +10,8 @@
10
10
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
11
11
  rel="stylesheet"
12
12
  />
13
- <script type="module" crossorigin src="/assets/index-LGK5h_Pf.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-G0lFZow6.css">
13
+ <script type="module" crossorigin src="/assets/index-I1VQFVyf.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BjfrCU-v.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -69,6 +69,8 @@ node_tty = __toESM(node_tty);
69
69
  let fs_promises = require("fs/promises");
70
70
  let node_stream = require("node:stream");
71
71
  let os = require("os");
72
+ let node_pty = require("node-pty");
73
+ node_pty = __toESM(node_pty);
72
74
 
73
75
  //#region ../../node_modules/.pnpm/depd@2.0.0/node_modules/depd/index.js
74
76
  var require_depd = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -41393,6 +41395,162 @@ function watch(paths, options = {}) {
41393
41395
  return watcher;
41394
41396
  }
41395
41397
 
41398
+ //#endregion
41399
+ //#region src/terminal/session-store.ts
41400
+ const MAX_SCROLLBACK = 5e4;
41401
+ var SessionStore = class {
41402
+ scrollback = [];
41403
+ append(data) {
41404
+ this.scrollback.push(data);
41405
+ if (this.scrollback.length > MAX_SCROLLBACK) this.scrollback = this.scrollback.slice(-MAX_SCROLLBACK);
41406
+ }
41407
+ getAll() {
41408
+ return this.scrollback.join("");
41409
+ }
41410
+ clear() {
41411
+ this.scrollback = [];
41412
+ }
41413
+ };
41414
+
41415
+ //#endregion
41416
+ //#region src/terminal/pty-manager.ts
41417
+ const DISCONNECT_TIMEOUT_MS = 6e4;
41418
+ const STATUS_INTERVAL_MS = 1e3;
41419
+ const ACTIVE_THRESHOLD_MS = 2e3;
41420
+ var PtyManager = class PtyManager {
41421
+ static instance = null;
41422
+ session = null;
41423
+ connectedClients = /* @__PURE__ */ new Set();
41424
+ lastOutputTime = 0;
41425
+ statusInterval = null;
41426
+ static getInstance() {
41427
+ if (!PtyManager.instance) PtyManager.instance = new PtyManager();
41428
+ return PtyManager.instance;
41429
+ }
41430
+ spawn(opts) {
41431
+ if (this.session) this.kill();
41432
+ const shell = process.platform === "win32" ? "claude.cmd" : "claude";
41433
+ const args = [];
41434
+ if (opts.skipPermissions) args.push("--dangerously-skip-permissions");
41435
+ const proc = node_pty.spawn(shell, args, {
41436
+ name: "xterm-256color",
41437
+ cols: opts.cols ?? 120,
41438
+ rows: opts.rows ?? 30,
41439
+ cwd: opts.cwd,
41440
+ env: process.env
41441
+ });
41442
+ const store = new SessionStore();
41443
+ this.session = {
41444
+ process: proc,
41445
+ pid: proc.pid,
41446
+ startTime: Date.now(),
41447
+ cwd: opts.cwd,
41448
+ skipPermissions: opts.skipPermissions,
41449
+ disconnectTimer: null,
41450
+ store
41451
+ };
41452
+ this.lastOutputTime = Date.now();
41453
+ proc.onData((data) => {
41454
+ this.lastOutputTime = Date.now();
41455
+ store.append(data);
41456
+ this.broadcastToClients({
41457
+ type: "output",
41458
+ data
41459
+ });
41460
+ });
41461
+ proc.onExit(({ exitCode }) => {
41462
+ this.broadcastToClients({
41463
+ type: "exit",
41464
+ code: exitCode
41465
+ });
41466
+ this.stopStatusBroadcast();
41467
+ this.session = null;
41468
+ });
41469
+ this.broadcastToClients({
41470
+ type: "started",
41471
+ pid: proc.pid
41472
+ });
41473
+ this.startStatusBroadcast();
41474
+ }
41475
+ write(data) {
41476
+ if (this.session) this.session.process.write(data);
41477
+ }
41478
+ resize(cols, rows) {
41479
+ if (this.session) this.session.process.resize(cols, rows);
41480
+ }
41481
+ kill() {
41482
+ if (this.session) {
41483
+ this.stopStatusBroadcast();
41484
+ try {
41485
+ this.session.process.kill();
41486
+ } catch {}
41487
+ if (this.session.disconnectTimer) clearTimeout(this.session.disconnectTimer);
41488
+ this.session = null;
41489
+ }
41490
+ }
41491
+ getStatus() {
41492
+ if (!this.session) return null;
41493
+ return {
41494
+ pid: this.session.pid,
41495
+ uptime: Math.floor((Date.now() - this.session.startTime) / 1e3),
41496
+ cwd: this.session.cwd,
41497
+ memoryMB: Math.round(process.memoryUsage().rss / 1024 / 1024 * 10) / 10,
41498
+ isActive: Date.now() - this.lastOutputTime < ACTIVE_THRESHOLD_MS,
41499
+ skipPermissions: this.session.skipPermissions,
41500
+ alive: true
41501
+ };
41502
+ }
41503
+ addClient(ws) {
41504
+ this.connectedClients.add(ws);
41505
+ if (this.session?.disconnectTimer) {
41506
+ clearTimeout(this.session.disconnectTimer);
41507
+ this.session.disconnectTimer = null;
41508
+ }
41509
+ if (this.session) {
41510
+ const scrollback = this.session.store.getAll();
41511
+ if (scrollback) ws.send(JSON.stringify({
41512
+ type: "scrollback",
41513
+ data: scrollback
41514
+ }));
41515
+ const status = this.getStatus();
41516
+ if (status) ws.send(JSON.stringify({
41517
+ type: "status",
41518
+ ...status
41519
+ }));
41520
+ }
41521
+ }
41522
+ removeClient(ws) {
41523
+ this.connectedClients.delete(ws);
41524
+ if (this.connectedClients.size === 0 && this.session) this.session.disconnectTimer = setTimeout(() => {
41525
+ console.error("[pty] No clients connected for 60s, killing process");
41526
+ this.kill();
41527
+ }, DISCONNECT_TIMEOUT_MS);
41528
+ }
41529
+ isAlive() {
41530
+ return this.session !== null;
41531
+ }
41532
+ broadcastToClients(message) {
41533
+ const data = JSON.stringify(message);
41534
+ for (const client of this.connectedClients) if (client.readyState === import_websocket.default.OPEN) client.send(data);
41535
+ }
41536
+ startStatusBroadcast() {
41537
+ this.stopStatusBroadcast();
41538
+ this.statusInterval = setInterval(() => {
41539
+ const status = this.getStatus();
41540
+ if (status) this.broadcastToClients({
41541
+ type: "status",
41542
+ ...status
41543
+ });
41544
+ }, STATUS_INTERVAL_MS);
41545
+ }
41546
+ stopStatusBroadcast() {
41547
+ if (this.statusInterval) {
41548
+ clearInterval(this.statusInterval);
41549
+ this.statusInterval = null;
41550
+ }
41551
+ }
41552
+ };
41553
+
41396
41554
  //#endregion
41397
41555
  //#region src/server.ts
41398
41556
  const projectCwd = process.env.MAXSIM_PROJECT_CWD || process.cwd();
@@ -41894,10 +42052,48 @@ else app.get("/", (_req, res) => {
41894
42052
  });
41895
42053
  async function main() {
41896
42054
  const wss = createWSS();
42055
+ const terminalWss = new import_websocket_server.default({ noServer: true });
42056
+ const ptyManager = PtyManager.getInstance();
42057
+ terminalWss.on("connection", (ws) => {
42058
+ ptyManager.addClient(ws);
42059
+ ws.on("message", (raw) => {
42060
+ try {
42061
+ const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString());
42062
+ switch (msg.type) {
42063
+ case "input":
42064
+ ptyManager.write(msg.data);
42065
+ break;
42066
+ case "resize":
42067
+ ptyManager.resize(msg.cols, msg.rows);
42068
+ break;
42069
+ case "spawn":
42070
+ ptyManager.spawn({
42071
+ skipPermissions: !!msg.skipPermissions,
42072
+ cwd: projectCwd,
42073
+ cols: msg.cols,
42074
+ rows: msg.rows
42075
+ });
42076
+ break;
42077
+ case "kill":
42078
+ ptyManager.kill();
42079
+ break;
42080
+ }
42081
+ } catch {}
42082
+ });
42083
+ ws.on("close", () => {
42084
+ ptyManager.removeClient(ws);
42085
+ });
42086
+ ws.on("error", (err) => {
42087
+ console.error("[terminal-ws] Client error:", err.message);
42088
+ });
42089
+ });
41897
42090
  const server = (0, node_http.createServer)(app);
41898
42091
  server.on("upgrade", (req, socket, head) => {
41899
42092
  const url = req.url || "/";
41900
- if (url === "/api/ws" || url.startsWith("/api/ws?")) wss.handleUpgrade(req, socket, head, (ws) => {
42093
+ if (url === "/ws/terminal" || url.startsWith("/ws/terminal?")) terminalWss.handleUpgrade(req, socket, head, (ws) => {
42094
+ terminalWss.emit("connection", ws, req);
42095
+ });
42096
+ else if (url === "/api/ws" || url.startsWith("/api/ws?")) wss.handleUpgrade(req, socket, head, (ws) => {
41901
42097
  wss.emit("connection", ws, req);
41902
42098
  });
41903
42099
  else socket.destroy();
@@ -41916,7 +42112,9 @@ async function main() {
41916
42112
  });
41917
42113
  function shutdown() {
41918
42114
  console.error("\n[server] Shutting down...");
42115
+ ptyManager.kill();
41919
42116
  if (watcher) watcher.close().catch(() => {});
42117
+ terminalWss.close(() => {});
41920
42118
  wss.close(() => {
41921
42119
  server.close(() => {
41922
42120
  process.exit(0);
@@ -41929,6 +42127,9 @@ async function main() {
41929
42127
  }
41930
42128
  process.on("SIGINT", shutdown);
41931
42129
  process.on("SIGTERM", shutdown);
42130
+ process.on("exit", () => {
42131
+ ptyManager.kill();
42132
+ });
41932
42133
  }
41933
42134
  main().catch((err) => {
41934
42135
  console.error("[server] Fatal error:", err);
package/dist/install.cjs CHANGED
@@ -7548,6 +7548,20 @@ async function promptLocation(runtimes) {
7548
7548
  }) === "global";
7549
7549
  }
7550
7550
  /**
7551
+ * Prompt whether to enable Agent Teams (Claude only, experimental feature)
7552
+ */
7553
+ async function promptAgentTeams() {
7554
+ console.log();
7555
+ console.log(chalk.cyan(" Agent Teams") + chalk.dim(" (experimental)"));
7556
+ console.log(chalk.dim(" Coordinate multiple Claude Code instances working in parallel."));
7557
+ console.log(chalk.dim(" Enables CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS in settings.json."));
7558
+ console.log();
7559
+ return dist_default$1({
7560
+ message: "Enable Agent Teams?",
7561
+ default: false
7562
+ });
7563
+ }
7564
+ /**
7551
7565
  * Install MAXSIM for all selected runtimes
7552
7566
  */
7553
7567
  async function installAllRuntimes(runtimes, isGlobal, isInteractive) {
@@ -7560,8 +7574,15 @@ async function installAllRuntimes(runtimes, isGlobal, isInteractive) {
7560
7574
  const primaryStatuslineResult = results.find((r) => statuslineRuntimes.includes(r.runtime));
7561
7575
  let shouldInstallStatusline = false;
7562
7576
  if (primaryStatuslineResult && primaryStatuslineResult.settings) shouldInstallStatusline = await handleStatusline(primaryStatuslineResult.settings, isInteractive);
7577
+ let enableAgentTeams = false;
7578
+ if (isInteractive && runtimes.includes("claude")) enableAgentTeams = await promptAgentTeams();
7563
7579
  for (const result of results) {
7564
7580
  const useStatusline = statuslineRuntimes.includes(result.runtime) && shouldInstallStatusline;
7581
+ if (result.runtime === "claude" && enableAgentTeams && result.settings) {
7582
+ const env = result.settings.env ?? {};
7583
+ env["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] = "1";
7584
+ result.settings.env = env;
7585
+ }
7565
7586
  finishInstall(result.settingsPath, result.settings, result.statuslineCommand, useStatusline, result.runtime, isGlobal);
7566
7587
  }
7567
7588
  }
@@ -7599,57 +7620,41 @@ const subcommand = args.find((a) => !a.startsWith("-"));
7599
7620
  console.log(chalk.blue("Starting dashboard..."));
7600
7621
  console.log(chalk.gray(` Project: ${projectCwd}`));
7601
7622
  console.log(chalk.gray(` Server: ${serverPath}\n`));
7602
- const child = spawnDash(process.execPath, [serverPath], {
7623
+ spawnDash(process.execPath, [serverPath], {
7603
7624
  cwd: dashboardDir,
7604
7625
  detached: true,
7605
- stdio: [
7606
- "ignore",
7607
- "ignore",
7608
- "pipe"
7609
- ],
7626
+ stdio: "ignore",
7610
7627
  env: {
7611
7628
  ...process.env,
7612
7629
  MAXSIM_PROJECT_CWD: projectCwd,
7613
7630
  NODE_ENV: "production"
7614
7631
  }
7615
- });
7616
- const result = await new Promise((resolve) => {
7617
- let stderrData = "";
7618
- let resolved = false;
7619
- function done(url) {
7620
- if (resolved) return;
7621
- resolved = true;
7622
- resolve({
7623
- url,
7624
- stderr: stderrData
7625
- });
7626
- }
7627
- child.on("error", () => done(null));
7628
- child.on("exit", (code) => {
7629
- if (code !== null && code !== 0) done(null);
7630
- });
7631
- if (child.stderr) {
7632
- child.stderr.setEncoding("utf8");
7633
- child.stderr.on("data", (chunk) => {
7634
- stderrData += chunk;
7635
- const match = stderrData.match(/Dashboard ready at (https?:\/\/[^\s]+)/);
7636
- if (match) done(match[1]);
7637
- });
7638
- }
7639
- setTimeout(() => done(null), 2e4);
7640
- });
7641
- if (result.url) {
7642
- child.unref();
7643
- if (child.stderr) child.stderr.destroy();
7644
- console.log(chalk.green(` Dashboard ready at ${result.url}`));
7645
- } else {
7646
- if (result.stderr.trim()) {
7647
- console.log(chalk.red("\n Dashboard failed to start:\n"));
7648
- console.log(chalk.gray(" " + result.stderr.trim().split("\n").join("\n ")));
7649
- } else console.log(chalk.yellow("\n Dashboard did not respond after 20s. Run with DEBUG=1 for details."));
7650
- child.unref();
7651
- if (child.stderr) child.stderr.destroy();
7632
+ }).unref();
7633
+ const POLL_INTERVAL_MS = 500;
7634
+ const POLL_TIMEOUT_MS = 2e4;
7635
+ const HEALTH_TIMEOUT_MS = 1e3;
7636
+ const DEFAULT_PORT = 3333;
7637
+ const PORT_RANGE_END = 3343;
7638
+ let foundUrl = null;
7639
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
7640
+ while (Date.now() < deadline) {
7641
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
7642
+ for (let p = DEFAULT_PORT; p <= PORT_RANGE_END; p++) try {
7643
+ const controller = new AbortController();
7644
+ const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
7645
+ const res = await fetch(`http://localhost:${p}/api/health`, { signal: controller.signal });
7646
+ clearTimeout(timer);
7647
+ if (res.ok) {
7648
+ if ((await res.json()).status === "ok") {
7649
+ foundUrl = `http://localhost:${p}`;
7650
+ break;
7651
+ }
7652
+ }
7653
+ } catch {}
7654
+ if (foundUrl) break;
7652
7655
  }
7656
+ if (foundUrl) console.log(chalk.green(` Dashboard ready at ${foundUrl}`));
7657
+ else console.log(chalk.yellow("\n Dashboard did not respond after 20s. The server may still be starting — check http://localhost:3333"));
7653
7658
  process.exit(0);
7654
7659
  }
7655
7660
  if (hasGlobal && hasLocal) {