@viraatdas/rudder 3.3.0 → 3.4.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/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # rudder
2
2
 
3
3
  <p>
4
- <a href="https://rudder.viraat.dev">
5
- <img src="https://rudder.viraat.dev/favicon.svg" width="36" height="36" alt="Rudder logo" />
4
+ <a href="https://rudder.exla.ai">
5
+ <img src="https://rudder.exla.ai/favicon.svg" width="36" height="36" alt="Rudder logo" />
6
6
  </a>
7
7
  </p>
8
8
 
9
- [![TUI e2e](https://github.com/viraatdas/rudder/actions/workflows/tui.yml/badge.svg)](https://github.com/viraatdas/rudder/actions/workflows/tui.yml)
10
- [![npm version](https://img.shields.io/npm/v/@viraatdas/rudder.svg)](https://www.npmjs.com/package/@viraatdas/rudder)
11
- [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-43853d.svg)](https://nodejs.org/)
12
- [![Website](https://img.shields.io/badge/site-rudder.viraat.dev-111111.svg)](https://rudder.viraat.dev)
9
+ [![TUI e2e](https://github.com/exla-ai/rudder/actions/workflows/tui.yml/badge.svg)](https://github.com/exla-ai/rudder/actions/workflows/tui.yml)
10
+ [![npm version](https://img.shields.io/npm/v/@exla/rudder.svg)](https://www.npmjs.com/package/@exla/rudder)
11
+ [![Node >=22](https://img.shields.io/badge/node-%3E%3D22-43853d.svg)](https://nodejs.org/)
12
+ [![Website](https://img.shields.io/badge/site-rudder.exla.ai-111111.svg)](https://rudder.exla.ai)
13
13
 
14
14
  Rudder is a TUI for managing coding agents. Run Claude Code, Codex and opencode
15
15
  side by side in one native dashboard: every task you type gets its own isolated
@@ -26,26 +26,30 @@ key. `/main` puts an agent in the main checkout when you want one there, and
26
26
  ## Install
27
27
 
28
28
  ```bash
29
- npm install -g @viraatdas/rudder@latest
29
+ npm install -g @exla/rudder@latest
30
30
  rudder
31
31
  ```
32
32
 
33
33
  After the global install, every user-facing `rudder` launch checks npm and
34
34
  auto-updates itself before running when a newer version is available. Set
35
35
  `RUDDER_DISABLE_AUTO_UPDATE=1` to opt out. Run without a global install using
36
- `npx @viraatdas/rudder@latest`.
36
+ `npx @exla/rudder@latest`.
37
+
38
+ Rudder used to be published as `@viraatdas/rudder`. Existing installs move
39
+ themselves to `@exla/rudder` on their next update; nothing to do by hand.
37
40
 
38
41
  ## Requirements
39
42
 
40
43
  - Node.js 22 or newer
41
44
  - Git
42
45
  - Claude Code and/or Codex installed and logged in
46
+ - macOS (Apple Silicon or Intel) or Linux x64; the dashboard binary for each
47
+ ships in the package
43
48
 
44
49
  Rudder runs agents in isolated [jj (Jujutsu)](https://jj-vcs.github.io)
45
50
  workspaces; your repo stays plain git. You do not need to install jj: if it is
46
51
  not already on your PATH, Rudder sets up its own copy in `~/.rudder/bin` during
47
52
  install (or on first run). A jj you installed yourself is always used instead.
48
- - macOS, Linux, or another Unix-like terminal
49
53
 
50
54
  Check your setup at any time:
51
55
 
@@ -145,6 +149,16 @@ between files, `r` refreshes, `Esc` or `⌥d` closes it, and `⌥h` solos it lik
145
149
  any other pane. On a narrow terminal it takes the worker's place instead of
146
150
  squeezing both. `v` opens the same panel.
147
151
 
152
+ ### God's view
153
+
154
+ Press `⌥g` (also `^W g`) to see every running agent at once: the screen turns
155
+ into a grid with one tile per agent showing the bottom of its terminal, titled
156
+ with its id, task, model and whether it is working, needs you, or is done.
157
+ Arrows or `h`/`j`/`k`/`l` move the highlight, `Enter` or a click opens that
158
+ agent in the worker pane, and `⌥g` or `Esc` closes the grid. Agents are never
159
+ resized for it, so their scrollback is untouched. With more agents than fit,
160
+ the grid pages.
161
+
148
162
  ### Web board
149
163
 
150
164
  Press `o` in the agents pane (or type `/web`) to open the live project board in
@@ -168,6 +182,9 @@ through Review before it can merge.
168
182
  | `Option-1` / `Option-2` / `Option-3` | Focus the agents, worker, or task pane |
169
183
  | `Option-[` / `Option-]` | Step to the previous / next agent, staying in the pane you are in |
170
184
  | `Option-h` | Full screen: the focused pane takes the whole terminal; press again to bring the panes back |
185
+ | `Option-g` | God's view: every agent tiled on one screen; press again to close |
186
+ | `Option-d` | Diff panel for the selected agent |
187
+ | `Ctrl-V` | Paste a screenshot or copied files into an agent (works in `rudder cloud` too) |
171
188
  | `Option-v` / `Option-b` | Previous question in the selected worker / back to its latest message (Claude Code and Codex) |
172
189
  | `Cmd-C` | Copy the active Rudder selection |
173
190
  | `Ctrl-C` | Quit (asks to confirm if agents are still running) |
@@ -483,6 +500,7 @@ rudder login # sign in with Google (opens straight to the accoun
483
500
  rudder cloud # open this repo on your home machine and attach to it
484
501
  rudder cloud home # home machine status: projects, agents, previews
485
502
  rudder cloud move # move this repo's running agents to the home machine
503
+ rudder cloud sync [repo] # bring this checkout and the cloud's clone level (/sync)
486
504
  rudder cloud app # the phone app link
487
505
  rudder cloud list # list cloud workers
488
506
  rudder cloud logs <id> # worker status
@@ -505,6 +523,26 @@ agent state live on its disk, so agents resume where they were.
505
523
  `rudder cloud --snapshot` keeps the old behavior: upload this folder to a
506
524
  machine of its own.
507
525
 
526
+ **Same repo on the laptop and the cloud (`/sync`).** `/sync` in the dashboard
527
+ (or `rudder cloud sync`) brings this checkout and the cloud machine's clone of
528
+ it up to the newest default branch, and carries the work GitHub does not have
529
+ yet, uncommitted edits included, from one side to the other directly. Nothing is
530
+ pushed to your repo, and gitignored files such as `.env` never travel. The side
531
+ receiving work only takes it when nothing of its own would be lost; when both
532
+ sides changed, both versions are kept and the other one waits as the jj bookmark
533
+ `rudder-sync/cloud` (or `rudder-sync/laptop`). `rudder cloud sync <repo>` with a
534
+ name clones a repo that so far exists only on the cloud into `~/code/<repo>`.
535
+ `/sync all` covers every repo, and `/sync move` hands this laptop's running
536
+ agents to the cloud.
537
+
538
+ **Screenshots and files.** In `rudder cloud`, `Ctrl-V` with a screenshot or
539
+ files copied in Finder, or dragging a file into the terminal, uploads it to the
540
+ cloud machine and gives the agent its path there: Claude Code attaches an image
541
+ as `[Image #1]` exactly as it does locally. Up to 25 MB per file.
542
+
543
+ **Updates.** After a release the cloud machine updates itself once its agents
544
+ have been idle for five minutes, so you never restart it by hand.
545
+
508
546
  **Phone.** Open `rudder cloud app` on your phone and sign in with the same
509
547
  Google account. You see every project and agent, read conversations as they
510
548
  happen, text any agent, start tasks (`/model` picks Claude or Codex for new ones),
@@ -678,7 +716,7 @@ usage event, and a GitHub issue when `gh` is installed and authenticated. Add
678
716
  ## Building from source
679
717
 
680
718
  ```bash
681
- git clone https://github.com/viraatdas/rudder.git
719
+ git clone https://github.com/exla-ai/rudder.git
682
720
  cd rudder
683
721
  ./setup.sh
684
722
  ```
package/dist/cloud.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import { AttachSession, containsCtrlC } from "./attach-session.js";
4
+ import { PasteBridge, readLocalClipboard } from "./paste-bridge.js";
4
5
  import fsp from "node:fs/promises";
5
6
  import os from "node:os";
6
7
  import path from "node:path";
@@ -749,7 +750,7 @@ async function quickstart(options) {
749
750
  "Rudder Cloud — copy/paste quickstart",
750
751
  "",
751
752
  "1. Install Rudder:",
752
- " npm install -g @viraatdas/rudder@latest",
753
+ " npm install -g @exla/rudder@latest",
753
754
  "",
754
755
  "2. Log in to the cloud control plane:",
755
756
  " rudder login",
@@ -2851,6 +2852,22 @@ function raceLeave(session, promise) {
2851
2852
  promise.then((value) => { off(); resolve(value); }, (error) => { off(); reject(error); });
2852
2853
  });
2853
2854
  }
2855
+ // One pasted file up to the home machine, through the phone's upload route.
2856
+ async function uploadPastedFile(baseUrl, token, workspaceId, file, name) {
2857
+ const bytes = await fsp.readFile(file);
2858
+ const url = `${baseUrl.replace(/\/$/, "")}/api/app/upload?workspace=${encodeURIComponent(workspaceId)}&name=${encodeURIComponent(name)}`;
2859
+ const response = await fetch(url, {
2860
+ method: "POST",
2861
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/octet-stream" },
2862
+ body: bytes,
2863
+ signal: AbortSignal.timeout(90_000),
2864
+ });
2865
+ const body = await response.json().catch(() => ({}));
2866
+ if (!response.ok || typeof body.path !== "string") {
2867
+ throw new Error(body.error || `HTTP ${response.status}`);
2868
+ }
2869
+ return body.path;
2870
+ }
2854
2871
  async function runAttach(target, options, sharedSession) {
2855
2872
  const client = await cloudClient({ requireToken: true });
2856
2873
  const baseUrl = client.baseUrl;
@@ -2938,6 +2955,22 @@ async function runAttach(target, options, sharedSession) {
2938
2955
  }
2939
2956
  resolve(value);
2940
2957
  };
2958
+ // Files and clipboard images from this computer reach the cloud machine
2959
+ // as uploads (src/paste-bridge.ts); only the home's repo rows take them.
2960
+ const paste = new PasteBridge({
2961
+ send: (data) => {
2962
+ if (socket.readyState === WebSocket.OPEN) {
2963
+ socket.send(data, { binary: true });
2964
+ predictor?.onInput(data);
2965
+ }
2966
+ },
2967
+ upload: (file, name) => uploadPastedFile(baseUrl, token, target.id, file, name),
2968
+ readClipboard: target.kind === "workspace" && isInteractive ? readLocalClipboard : undefined,
2969
+ status: (text) => (text ? session.showStatus(text) : session.clearStatus()),
2970
+ });
2971
+ if (target.kind !== "workspace") {
2972
+ paste.disabled = true;
2973
+ }
2941
2974
  let lastCtrlC = 0;
2942
2975
  const onStdin = (chunk) => {
2943
2976
  const buffer = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
@@ -2998,8 +3031,7 @@ async function runAttach(target, options, sharedSession) {
2998
3031
  }
2999
3032
  lastCtrlC = now;
3000
3033
  }
3001
- socket.send(buffer, { binary: true });
3002
- predictor?.onInput(buffer);
3034
+ paste.forward(buffer);
3003
3035
  };
3004
3036
  const onResize = () => {
3005
3037
  if (hasControl) {