One app. Your cloud computer.
Download Cube and you’ll be using your own machine in a few seconds. No config, no SSH, just sync your GitHub projects and start your agents.
Cube gives you a complete computer to run your coding agents 24/7. Our Mac app makes it easy to manage both cloud and local sessions, worktrees, and files, in one coherent UI.
Ran 3 shell commands · read 8 files
● Ported the canvas engine and its selectors across verbatim, and left the shell's own stylesheet behind on the grounds that this app has a theme of its own.
> the ported tiles still don't look like the original
Ran 4 shell commands · read 11 files
● Pushed bd1ad00 — pull and look again. What was wrong, concretely: the engine and the selectors were verbatim, but shell.css's canvas rules consume design tokens that only ever existed in the shell's own :root block (--bg, --fg, --muted, --tile-border, --edge-dot). This app's theme never defines those, so every tile's background, border, muted title and edge indicator resolved to nothing. That is the “not the original's design” you saw, and it is my under-port, not a rewrite.
Three fixes in this commit:
1. The shell's token block, verbatim, scoped to #panel-viewer in light and dark, so tiles get their real card background, borders, muted title and edge-indicator greens.
2. The floating new-tile button, top right — same geometry, same hover, and now the same ink.
3. One canvas tint, not two. #panel-viewer was repainting the tint .app-main already lays down beneath it, double-darkening the glass.
One calibration note: I checked the ported tiles against collab-public main's current shell.css and they match it now — square corners, crosshair corner extensions, flat cards.
✳ Cooked for 9m 4s · 2 shells still running
[Fable 5] 📁canvas-again 🌿canvas-again /rc
56% $153.56 🕐7h 23m 99% ctx
▸▸ bypass permissions on · 2 shells · ← for agents
yiliu@BookPro14 canvas-again % git status --short
M src/windows/app/src/canvas/canvas.css
M src/windows/app/src/canvas/tile-renderer.js
yiliu@BookPro14 canvas-again % bun run dev
VITE v7.1.3 ready in 412 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
starting electron app…
18:08:16.917 › (node:22214) [DEP0040] DeprecationWarning: The punycode module is deprecated. Please use a userland alternative instead.
18:08:17.061 › [analytics] MAIN_VITE_POSTHOG_KEY not set, skipping init
18:08:17.062 › [json-rpc] listening on /Users/yiliu/.collab/dev/worktree-b23c4a36679c/ipc.sock
yiliu@BookPro14 canvas-again %
Ran 4 shell commands
● Wired the sidebar to the daemon's worktree list and added a row per entry. Reads correctly on first paint.
> the rows are still stale after an external add
Ran 9 shell commands · read 31 files
● Found it. The list was built once at mount and then only ever amended by our own actions, so anything git did behind our back — an add from a shell, a directory removed with rm -rf — never reached it. The panel was not stale so much as it was a different list that happened to agree most of the time.
Replaced the whole thing with a projection: read .git/worktrees, watch it, rebuild on change. There is now one source of truth and it is git's.
Ran 4 shell commands
● Sixteen files, all under sidebar/ and daemon/worktrees/. The suite is green and the e2e covers all three cases.
Ran 6 shell commands · read 24 files
● The integration is done and the suite is green. Sixteen files touched, all of them under sidebar/ and daemon/worktrees/ — nothing in the shell, and one new event on the wire.
Before anything irreversible: the branch is 20 commits ahead of main and every one of them is pushed. Three ways to land it:
1. Merge back to main locally
2. Push and open a pull request
3. Keep the branch as it is — I'll handle it later
Which option?
> 3
Ran 1 shell command
● Keeping live-worktree-rows. Nothing merged, nothing deleted.
State: 20 commits ahead of main, all of them pushed. This is the repo's own checkout rather than a worktree, so there is nothing to clean up — you are simply standing on the branch.
What is on it: worktree rows are a live projection of git worktree list now, rather than a list the app keeps its own copy of. One made outside Cube is adopted as managed: false, one deleted behind our back is pruned, and both are driven by an fs.watch on git's own worktree records. Proven end to end:
1. a row appears for an externally created worktree
2. the row goes when the directory does
3. adoption survives a restart
That closes #86, which is still open — worth closing by hand, or letting the PR do it.
[Opus 5 (1M context)] 📁collab-computer 🌿live-wor…
45% $94.55 🕐3h 8m 99% ctx
▸▸ bypass permissions on (shift+tab to cycle) · ← 1 ag…
Ran 2 shell commands
● Suite is up on the new watcher. Running it cold to see what the branch actually looks like from a clean checkout rather than from my machine.
> four of the watcher tests fail on a clean checkout
Ran 7 shell commands · read 22 files
● Four failures, and they are not four bugs. Two are the same missing await; the third is a teardown racing a claim it never took; the fourth is not a failure at all, it is the test asserting against its own mock.
Writing them up in that order so the commits read as one argument rather than four patches.
> go ahead and commit, then push
Ran 5 shell commands · read 18 files
● Reviewed the watcher against the four failures you listed. Three were real; the fourth was the test lying to us. exists() was swallowing ENOENT and returning true, so the prune path never ran — and the assertion that should have caught it was asserting against the mock.
Splitting the fix so each commit closes one failure, then running the suite against a real watcher rather than the fake.
Ran 3 shell commands
● All six commits are in and the tree is clean. Pushing, then building the e2e:
Pushed to live-worktree-rows, ran 2 shell commands
● Done, all green:
— Six commits matching the branch's own granularity: the confirm-before-prune sweep, the teardown busy-claim, the errno fix on exists(), the watcher hardening, the no-op adopt batch, and one style split. Each carries a body explaining the failure it closes.
— Pushed to origin: f0923fe..5f38d0f live-worktree-rows.
— The e2e asserts against a real fs.watch rather than a fake — four assertions, all passing on macOS, with the Linux caveat written down rather than skipped.
✳ Cogitated for 2m 17s
> why do worktrees made outside Cube never turn up?
Ran 6 shell commands · read 19 files
● Read the catalog end to end before touching it. A worktree reaches the panel by exactly one path today — newWorktree() writes a record and the sidebar renders records — which is why anything created outside the app is invisible to it. There is no reconciliation step to extend; there is no reconciliation step at all.
That is worth saying plainly because it changes the size of the job: this is not a bug in a sync routine, it is the absence of one.
> adopt worktrees that already exist on disk
Ran 5 shell commands · read 14 files
● Two questions to settle before any code, because they decide the shape:
1. What owns a worktree Cube did not create? If we mark it managed, we take responsibility for pruning it, and pruning a directory a person made by hand is the one mistake there is no undo for.
2. What happens on a restart? Adoption has to be idempotent or every boot re-adopts the same rows and the list grows.
Taking the conservative answer to both: adopt as managed: false, never prune what we did not create, and key adoption on the worktree's own path so a second pass is a no-op.
Ran 3 shell commands
● The watcher is the interesting part. fs.watch on a directory that git rewrites rather than edits fires rename, not change, and fires it twice — once for the temp file and once for the move. Debounced to a tick and re-read wholesale, which is cheap: the file is a few hundred bytes.
Debouncing to a tick rather than to a timer, deliberately: a timer would make the panel's freshness a function of how fast the machine is, and the whole complaint was that the panel disagreed with git.
The Linux caveat is written down rather than skipped. fs.watch there is inotify, which watches inodes, so a directory replaced wholesale takes the watch with it. Re-arming on every rebuild costs nothing and closes it.
Ran 2 shell commands
● A worktree made outside Cube now turns up in the sidebar within a tick of git worktree add returning, marked managed: false so nothing tries to own it.
The watcher reads git's own records rather than polling the directory, so a worktree removed with rm -rf is pruned by the same path.
Cogitated for 41s · gpt-5.6
[GPT-5.6] 📁collab-computer 🌿adopt-extern…
31% $18.40 🕐52m 99% ctx
▸▸ ask before writing · ← for agents
● Pushed bd1ad00 — the shell's token block, scoped to #panel-viewer, so tiles get their real card background and borders.
The floating new-tile button takes the same ink, and the double tint under the glass is gone.
✳ Cooked for 9m 4s
Ran 1 shell command
● Keeping live-worktree-rows. 20 commits ahead of main, all of them pushed.
Rows are a live projection of git worktree list now, driven by an fs.watch on git's own records.
Ran 3 shell commands
● The token refresh runs on boot now, before the first fetch rather than after it fails.
Download Cube and you’ll be using your own machine in a few seconds. No config, no SSH, just sync your GitHub projects and start your agents.
Both sessions from the cloud machine and your local Mac are displayed in the sidebar. Click the “+” next to the cloud machine cell to import a GitHub Project. Keep track of your projects easily.
As soon as you add a project to Cube, we scaffold a worktree to isolate your agents and prevent tedious merge conflicts. Each session gets its own checkout, listed under the repo it came from. Agents never queue for a working directory, and nothing they do lands on your branch by accident.
Whether you use Ghostty, cmux, tmux, or Warp, you’ll feel at home in no time. Cube comes complete with a multi-pane layout to give you a bird’s eye view of your sessions. Just drag and drop the panes to re-organize them on the rail. Click on any file or session to open it in the rail, next to your current focus.
If the rail gets too busy, just switch to the canvas view and click on the sessions in the sidebar to open them on the canvas as well. You can resize them, drag them around, and create your own layout. Just like in the rail, sessions and files can co-exist on the canvas.