[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81579":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":15,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":14,"starSnapshotCount":14,"syncStatus":13,"lastSyncTime":25,"discoverSource":26},81579,"grok-remote","daniel-farina\u002Fgrok-remote","daniel-farina","Run grok agents on one machine, drive them from any device on your tailnet. Live web UI speaking ACP directly to grok agent stdio.",null,"TypeScript",24,4,2,0,1,42.7,"MIT License",false,"main",true,[],"2026-06-12 04:01:34","```\n  ██████╗ ██████╗\n ██╔════╝ ██╔══██╗\n ██║  ███╗██████╔╝\n ██║   ██║██╔══██╗\n ╚██████╔╝██║  ██║\n  ╚═════╝ ╚═╝  ╚═╝\n  ·  g r o k   r e m o t e  ·  v0.1.0\n```\n\n# grok-remote\n\nRun **grok agents** on one machine. Drive them from any device on your **tailnet**. Multiple conversations in parallel, a live web UI that streams every thought \u002F tool call \u002F response, durable on disk, reachable from your phone.\n\nOne command sets it up. PM2 keeps the server alive. Tailscale handles the networking. The dashboard speaks the **Agent Client Protocol** (ACP) directly to `grok agent stdio`, so you see exactly what the agent sees and does in real time.\n\n> Not affiliated with xAI, grok, or Tailscale.\n\n> **Work in progress.** I'm pushing every change as I make it. Expect minor options to be broken at any given moment. Core agent features (spawn, chat, tool calls, bg processes, history) are functional.\n\n---\n\n## What it does\n\n- **Multi-agent control plane.** Spawn as many independent `grok agent` processes as you want. Each gets its own working directory under `~\u002F.grok-remote\u002Fagents\u002F\u003Cid>\u002Fcwd\u002F`. Click `+ new` and the conversation starts immediately; the first turn auto-names it.\n- **Full ACP host.** Implements the client side of ACP (terminal\u002F*, fs\u002F*, request_permission) so the agent can actually run shell commands, read files, and write files. Without this every tool call would silently fail.\n- **Live streaming.** Server-Sent Events forward every `session\u002Fupdate` event from the agent to the browser: thought chunks, tool-call cards with status pills (Pending → Running → Completed \u002F Failed), streamed terminal output, the final assistant message, and the token-usage footer.\n- **Conversations persist across restarts.** Each agent has a `meta.json` on disk with `lastSessionId`. After a server reboot, agents appear as `disconnected`; sending a new message transparently reconnects via ACP `session\u002Fload` so grok keeps the same conversation memory.\n- **Star, archive, delete-forever.** The familiar trash-bin pattern: closing an active conversation **archives** it (soft); restore or **delete-forever** lives in the archived view.\n- **Image attachments work.** Drop or paste an image in the composer; the bytes land at `\u003Cagent.cwd>\u002Fuploads\u002F`, the prompt carries both an inline ACP `image` content block AND a `resource_link` plus the absolute path. The model sees the image directly.\n- **Files tab.** Browse the agent's workspace, preview text with line numbers, HTML (sandboxed iframe + open-in-new-tab), images, video, audio. Backend serves binary files via a `Range`-aware streaming endpoint.\n- **Mobile + PWA.** Installable as a standalone app on iOS and Android. Sidebar collapses to a slide-in drawer, 44px tap targets, safe-area-inset padding, dynamic-viewport sizing so the composer stays pinned on iOS Safari.\n- **Themes.** Dark (default), light, hacker (phosphor green), unicorn. Persisted per browser. Topbar quick-toggle cycles them; settings has a picker.\n- **A `gr` CLI on your PATH.** From any directory: `gr` opens the dashboard, `gr status` shows PM2 state, `gr install` re-runs the installer, etc.\n\n---\n\n## Requirements\n\n- macOS or Linux\n- Node.js 20+ (installer can install it via Homebrew on macOS)\n- Homebrew on macOS (only used if Node, or Tailscale for tailnet mode, are missing)\n- A Tailscale account, free for personal use at [tailscale.com](https:\u002F\u002Ftailscale.com), if you want tailnet access\n- `grok` CLI installed and authenticated (the dashboard spawns `grok agent stdio` per conversation)\n\n---\n\n## Install\n\n```sh\ngit clone https:\u002F\u002Fgithub.com\u002Fdaniel-farina\u002Fgrok-remote.git\ncd grok-remote\n.\u002Finstall.sh\n```\n\nTo run only on the current machine without Tailscale:\n\n```sh\n.\u002Finstall.sh --local\n```\n\nThe installer walks through, with animated `[ OK ]` \u002F `[skip]` \u002F `[warn]` \u002F `[FAIL]` badges per step:\n\n1. verify node >= 20\n2. ensure pm2 (process manager)\n3. ensure tailscale\n4. start tailscaled (daemon)\n5. check tailscale auth\n6. resolve tailnet url\n7. install app dependencies (`npm install`)\n8. build dashboard (`vite build`)\n9. write pm2 ecosystem config\n10. start under pm2\n11. enable auto-start on boot (optional, opt-in prompt)\n12. save pm2 process list\n13. install `gr` command (global shortcut)\n14. open dashboard in Chrome\n\nAuto-open can be skipped with `--no-open`, `NO_OPEN=1`, `CI=1`, or when the installer detects you're over SSH.\n\nThe installer asks once whether to auto-start the server on reboot. Pick \"yes\" if you want the PWA dock icon to \"just work\" after a restart; on macOS this writes a user-level `launchd` entry that calls `pm2 resurrect` at login. Pre-select with `--auto-start` \u002F `--no-auto-start` or `AUTO_START=1` \u002F `AUTO_START=0`. Non-interactive installs (CI, `NO_PROMPT=1`) default to no.\n\nIf a step warns about Tailscale auth, run `tailscale up` and open the URL it prints. On macOS, open `Tailscale.app` once if `tailscaled` isn't running. Then re-run `.\u002Finstall.sh`, every step is idempotent.\n\nFor local-only installs, re-run setup with `.\u002Finstall.sh --local` or `gr install --local`. This keeps the server bound to `127.0.0.1` and does not touch existing conversations under `~\u002F.grok-remote\u002Fagents`.\n\n---\n\n## Use\n\n### Start a conversation\n\nClick `+ new` in the sidebar. An agent process is spawned in the background; you land on its (empty) conversation immediately. Type a message. The first response triggers an auto-name from grok's own `session_summary_generated` event, and the sidebar relabels the item from `agent-abc12345` to something descriptive within a few seconds.\n\n### Attach images and files\n\nThree ways:\n- Drag a file onto the composer\n- Paste from clipboard (Cmd+V \u002F Ctrl+V)\n- Click `attach image`\n\nLimits: 5 attachments per turn, 5 MB each, png \u002F jpeg \u002F webp \u002F gif. The file is saved to `~\u002F.grok-remote\u002Fagents\u002F\u003Cid>\u002Fcwd\u002Fuploads\u002F\u003Cname>` and the agent receives:\n\n- The absolute path in the text\n- An ACP `image` content block (the inline base64)\n- An ACP `resource_link` content block (formal reference)\n\nVision-capable models describe the image; non-vision models still have it on disk to inspect with shell tools.\n\n### Files tab\n\nEach conversation has a **Files** tab that browses its working directory. Click into folders, preview text files (line-numbered), HTML files (sandboxed Source \u002F Preview toggle + open-in-new-tab), images (with checkered background), video \u002F audio (HTML5 controls). The backend serves binary files via a `Range`-aware `\u002Ffiles\u002Fraw` endpoint so seeking works.\n\n### Slash commands\n\nTyping `\u002F` at the start of the composer opens a palette of grok's currently-available commands (`\u002Fcompact`, `\u002Falways-approve`, `\u002Fcontext`, `\u002Fsession-info`, plus anything grok adds via `available_commands_update`). Arrow keys + Enter to commit; Esc to dismiss.\n\n### Disconnect \u002F reconnect\n\nEach conversation has a `disconnect` button (in the sidebar and in the chat tabs row). Disconnect kills the grok process but keeps the conversation: history, files, settings, the grok `sessionId`. Send another message anytime; the backend transparently respawns the agent and `session\u002Fload`s the saved session so grok's own memory continues.\n\nYou can also resume on the CLI. The Info tab shows the resume commands:\n\n```\ngrok -p \"\u003Cfollow-up>\" -r \u003CsessionId>       # one-shot, headless\ncd \u003Ccwd> && grok --resume \u003CsessionId>      # interactive TUI\n```\n\nwith copy buttons for both.\n\n### Star, archive, delete\n\nPer sidebar item:\n- `☆ \u002F ★` toggle: starred conversations sort to the top\n- `×` archives (soft removal). The agent process is shut down, but the disk record survives. The archived item moves under the collapsible `archived (N)` toggle.\n- In the archived view, `restore` brings it back; `delete` is the only path to permanent removal (history + uploaded files are wiped).\n\n### Themes\n\nTopbar quick-toggle cycles `dark → light → hacker → unicorn → dark`. Settings has a full picker. The choice is persisted in localStorage and applied pre-DOM so there's no flash on reload.\n\n### Copy entire conversation\n\nThe chat tabs row has a `copy conversation` button. Serializes all turns (user prompts, thought summaries, tool calls + their output, assistant messages) to clean plain text and puts it on the clipboard. Useful for pasting into bug reports, notebooks, or another agent.\n\n### Debug controls (optional)\n\nSettings → `debug controls` toggle. When on, a `{ payload }` button appears in the composer. Clicking it opens an inspector showing:\n\n- the composer **draft** (what would be sent now)\n- the **last sent request body** (what your browser actually POSTed)\n- the **server response** (echoed back: composed text, ACP `promptBlocks`, `savedFiles`, sessionId, supportsImage flag)\n\nBase64 image data is truncated in the visible `\u003Cpre>` for readability; the per-section `copy` button copies the FULL payload to the clipboard. Handy when something isn't behaving and you want to see exactly what grok is receiving.\n\n### Mobile + PWA\n\nOpen the URL on your phone over your tailnet. The sidebar collapses to a slide-in drawer (hamburger top-left). On supported browsers an install banner offers to add it to your home screen; on iOS Safari it shows the manual \"Share → Add to Home Screen\" hint. Once installed it runs as a standalone app with the status bar tinted to match the theme.\n\n---\n\n## The `gr` command\n\nAfter `.\u002Finstall.sh` the installer symlinks `gr` into `\u002Fusr\u002Flocal\u002Fbin` (or `~\u002F.local\u002Fbin` with a PATH hint as fallback). Run it from any directory.\n\n| Command       | What it does                                                          |\n|---------------|-----------------------------------------------------------------------|\n| `gr`          | Ensure the server is healthy, show the URL, and offer to open it.      |\n| `gr status`   | PM2 status, uptime, restarts, memory, cpu, tailnet URL.               |\n| `gr open`     | Start the server if needed, then open the dashboard.                  |\n| `gr url`      | Print only the URL on stdout (pipe-friendly).                         |\n| `gr start`    | `pm2 start ecosystem.config.cjs` from the install dir. Pass `--local` to bind localhost. |\n| `gr stop`     | `pm2 stop grok-remote`.                                               |\n| `gr restart`  | `pm2 restart grok-remote`.                                            |\n| `gr logs`     | `pm2 logs grok-remote --lines 100`.                                   |\n| `gr install`  | Re-run the installer (idempotent). Pass `--local` to keep local-only mode. |\n| `gr version`  | Print the grok-remote version.                                        |\n| `gr help`     | Show the subcommand table.                                            |\n\nSet `GR_HOME` to override how `gr` locates the project; otherwise it follows the symlink back to the install directory.\n\n---\n\n## How it works\n\n```\n+----------------------+   tailnet   +-----------------------+   ACP over     +------------+\n|   Browser \u002F iPhone   |  \u003C-------> |  grok-remote server   |   stdio JSON-  |  grok       |\n|   \u002F  dashboard (PWA)  |            |   :7910                |   RPC          |  agent      |\n+----------------------+    SSE     |                        |  \u003C----------> |  (one per   |\n                                    |  - REST + SSE          |                |   convo)    |\n                                    |  - ACP client\u002Fhost     |                +------------+\n                                    |  - meta + history      |\n                                    |    on disk             |\n                                    +-----------------------+\n```\n\n### Per-conversation state on disk\n\n```\n~\u002F.grok-remote\u002F\n├── settings.json                       # global server settings\n└── agents\u002F\n    └── \u003Cagent-uuid>\u002F\n        ├── meta.json                   # name, starred, archived, lastSessionId, ...\n        ├── history.jsonl               # append-only event log (every SSE event)\n        └── cwd\u002F                        # the agent's working directory\n            └── uploads\u002F                # files attached via the composer\n```\n\n`history.jsonl` is the durable record. On reload, the UI fetches the last 50 turns from this file (paginated: `?turns=50` default, `?all=1` to expand). The \"load all earlier turns (N more)\" pill appears at the top of the conversation when there's more.\n\n### REST API surface\n\n| Method | Path                              | Purpose |\n|--------|-----------------------------------|---------|\n| GET    | `\u002Fapi\u002Fagents`                     | list (active + archived; UI filters)                                  |\n| POST   | `\u002Fapi\u002Fagents`                     | spawn a new agent. body `{ name?, model?, cwd? }`                     |\n| GET    | `\u002Fapi\u002Fagents\u002F:id`                 | full record (handshake meta, capabilities, sessionId)                 |\n| PATCH  | `\u002Fapi\u002Fagents\u002F:id`                 | partial update. body any of `{ name, starred, archived }`             |\n| DELETE | `\u002Fapi\u002Fagents\u002F:id`                 | delete forever (kills process, scrubs the on-disk record)             |\n| POST   | `\u002Fapi\u002Fagents\u002F:id\u002Fprompt`          | body `{ text, attachments? }`. Returns 202 + a debug echo.            |\n| POST   | `\u002Fapi\u002Fagents\u002F:id\u002Fcancel`          | cancel an in-flight turn                                              |\n| POST   | `\u002Fapi\u002Fagents\u002F:id\u002Fconnect`         | spawn the grok process and resume the session                         |\n| POST   | `\u002Fapi\u002Fagents\u002F:id\u002Fdisconnect`      | kill the grok process; conversation survives                          |\n| GET    | `\u002Fapi\u002Fagents\u002F:id\u002Fhistory`         | JSONL replay. `?turns=N` or `?all=1`. Headers: `X-Total-Turns`, `X-Returned-Turns` |\n| GET    | `\u002Fapi\u002Fagents\u002F:id\u002Fstream`          | SSE stream of every event for that agent                              |\n| GET    | `\u002Fapi\u002Fagents\u002F:id\u002Ffiles`           | list a directory or read a text file. `?path=\u003Crel>`                   |\n| GET    | `\u002Fapi\u002Fagents\u002F:id\u002Ffiles\u002Fraw`       | stream a file with `Range` support (images, video, audio, anything)   |\n| GET, PATCH | `\u002Fapi\u002Fsettings`               | global settings (defaultModel, defaultCwd, autoApprove, debug, theme) |\n| GET    | `\u002Fapi\u002Fhello`                      | tailscale identity + version                                          |\n| GET    | `\u002Fapi\u002Fhealth`                     | liveness                                                              |\n\nSee [PROTOCOL.md](.\u002FPROTOCOL.md) for the full ACP + SSE wire contract.\n\n---\n\n## Manage\n\nPM2 is the system supervisor. The installer wired it for you; useful direct commands:\n\n```sh\npm2 logs grok-remote       # follow logs\npm2 status                 # all PM2 processes\npm2 restart grok-remote    # restart\npm2 stop grok-remote       # stop\npm2 delete grok-remote     # remove from PM2\n```\n\nTo survive reboot:\n\n```sh\npm2 save\npm2 startup           # follow the instructions it prints\n```\n\nThe server itself handles SIGTERM \u002F SIGINT gracefully: it disconnects every live agent (saves their `lastSessionId`) before exiting, so a restart never loses the conversation thread.\n\n---\n\n## Develop\n\n```sh\nnpm install\nnpm start             # backend on :7910 (serves dist\u002F + \u002Fapi\u002F*)\nnpm run dev           # Vite dev server on :7911, proxies \u002Fapi → 7910\nnpm run typecheck     # tsc --noEmit\nnpm test              # unit tests (node:test + tsx)\nnpm run test:integration  # boots server.ts and hits \u002Fapi\u002F*; needs grok logged in\n```\n\n- Frontend lives under `src\u002F`. Vanilla TypeScript + Vite. Views: `src\u002Fviews\u002F{agents,chat,settings,files}.ts`. Helpers: `src\u002Flib\u002F{api,sse,render,themes,copy,slash-palette,attach-images,pwa}.ts`. The live-flow page (`src\u002Fviews\u002Fsystem\u002Fflow.tsx`) is the only React surface.\n- Backend is plain Node http. Modules: `lib\u002F{acp-client,agent-manager,terminal-host,fs-host,permission-host,sse,history,settings}.ts`.\n- Build is Vite (esbuild type-strip); type-checking is a separate `npm run typecheck` step.\n- No external runtime deps. Vite + tsx + typescript are the devDependencies.\n\n### Tests\n\nUnit tests live under `test\u002F*.test.ts` and run with `node --import tsx --test`. They cover pure helpers (`src\u002Flib\u002F{format,copy,themes,icons,render}`, `lib\u002F{install-mode,launch,dev-url,retention,grok-cli,routes\u002Fhelpers,sse,history,fs-host,terminal-host,permission-host}`) plus the agent-manager filename\u002Fupload helpers.\n\nIntegration tests live under `test\u002Fintegration\u002F*.test.ts` and are gated on `RUN_LOCAL_INTEGRATION=1`. The shared `_helpers.ts` boots the real `server.ts` via `tsx` on a random high port, polls `\u002Fapi\u002Fhealth` until ready, and exercises the public endpoints (`\u002Fapi\u002Fhello`, `\u002Fapi\u002Fversion\u002Fcurrent`, `\u002Fapi\u002Fagents`, `\u002Fapi\u002Fagents\u002Fstream`, `\u002Fapi\u002Fsettings`, `\u002Fapi\u002Fsystem\u002Fhealth`). They need a logged-in `grok` CLI on the host because `\u002Fapi\u002Fsystem\u002Fhealth` shells out to `grok inspect`. Skipped without the env var.\n\n`experiments\u002Fprobe.js` is a small standalone ACP client (~120 lines) that talks to `grok agent stdio` and dumps every JSON-RPC frame to a log file. Run it to regenerate the traces summarized in [PROTOCOL.md](.\u002FPROTOCOL.md):\n\n```sh\nnode experiments\u002Fprobe.js \"Reply with the word ack.\" exp1.log\nnode experiments\u002Fprobe.js \"Run \\`ls\\` and tell me what you see.\" exp2.log\n```\n\n---\n\n## Layout\n\n```\ngrok-remote\u002F\n├── install.sh                  # bash bootstrap (verifies Node, hands off)\n├── installer.ts                # animated 13-step installer\n├── bin\u002Fgr                      # the gr CLI\n├── server.ts                   # Node http server + REST\u002FSSE\n├── ecosystem.config.cjs        # PM2 config\n├── vite.config.ts              # Vite dev server config\n├── tsconfig.json\n├── tsconfig.server.json\n├── index.html                  # dashboard entry\n├── lib\u002F                        # backend modules (ACP host + persistence)\n│   ├── acp-client.ts\n│   ├── agent-manager.ts\n│   ├── terminal-host.ts\n│   ├── fs-host.ts\n│   ├── permission-host.ts\n│   ├── sse.ts\n│   ├── history.ts\n│   ├── settings.ts\n│   ├── grok-cli.ts\n│   ├── dev-url.ts\n│   ├── install-mode.ts\n│   ├── launch.ts\n│   ├── retention.ts\n│   ├── trace-host.ts\n│   ├── version-update.ts\n│   └── routes\u002F                 # request handlers (system\u002F* etc.)\n├── src\u002F                        # frontend modules\n│   ├── main.ts                 # router + intro animation\n│   ├── style.css               # palette + dashboard CSS\n│   ├── views\u002F\n│   │   ├── agents.ts\n│   │   ├── chat.ts\n│   │   ├── settings.ts\n│   │   ├── files.ts\n│   │   ├── trace.ts\n│   │   ├── changelog-modal.ts\n│   │   ├── update-modal.ts\n│   │   └── system\u002F             # settings sub-pages, flow.tsx, …\n│   └── lib\u002F\n│       ├── api.ts              # fetch wrapper\n│       ├── sse.ts              # EventSource helper\n│       ├── render.ts           # tiny DOM builder + markdown-light\n│       ├── themes.ts\n│       ├── copy.ts\n│       ├── slash-palette.ts\n│       ├── attach-images.ts\n│       ├── image-lightbox.ts\n│       ├── intro-animation.ts\n│       ├── icons.ts\n│       ├── version-footer.ts\n│       ├── format.ts\n│       └── pwa.ts\n├── test\u002F                       # unit + integration tests (node:test + tsx)\n│   ├── *.test.ts               # ~21 unit suites, pure helpers\n│   └── integration\u002F            # spawns server.ts, gated on RUN_LOCAL_INTEGRATION\n├── public\u002F                     # PWA assets (manifest, sw, icons)\n├── experiments\u002F                # ACP protocol traces + probe.js\n├── PROTOCOL.md                 # wire format + rendering rules\n└── package.json\n```\n\n---\n\n## What's next\n\n- Per-agent model picker (use a vision-capable model for one conversation, a fast model for another).\n- Server-side OCR fallback so non-vision models can still see text inside attached images.\n- Optional bearer-token auth on top of Tailscale's perimeter.\n- Auto-archive after N days of inactivity (configurable in settings).\n\n---\n\n## Star history\n\n[![Star History Chart](https:\u002F\u002Fapi.star-history.com\u002Fsvg?repos=daniel-farina\u002Fgrok-remote&type=Date)](https:\u002F\u002Fstar-history.com\u002F#daniel-farina\u002Fgrok-remote&Date)\n\n---\n\n## License\n\nMIT. See [LICENSE](.\u002FLICENSE).\n","grok-remote 是一个允许用户在一台机器上运行 grok 代理，并通过任何设备上的 tailnet 进行控制的项目。其核心功能包括多代理控制面板，支持并行处理多个对话；全功能 ACP（Agent Client Protocol）主机实现，使代理能够执行命令、读写文件等操作；以及实时流式传输代理活动，确保所有交互即时可见。此外，该项目还提供了持久化会话存储、多媒体附件支持、文件浏览与预览等功能，并且针对移动设备进行了优化，支持作为PWA安装。适用于需要远程管理和监控AI代理执行环境的各种场景，如开发测试、自动化任务处理等。","2026-06-11 04:05:34","CREATED_QUERY"]