Skip to content
Back to skills

Run Desktop

ASecurity

Build, run, and drive the Geniro desktop UI on headless Linux (Claude Code on the web). Use when asked to run/start/screenshot the app or interact with its UI in a remote container. Drives the real renderer bundle in Chromium wired to a real daemon — the packaged Electron shell can't launch here. On macOS, prefer `pnpm dev` with the real Electron shell first; use this Chromium harness as the fallback for headless/remote environments and for scripted/automated UI driving via its REPL.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 30, 2026
ai-agentsgoshellbashsqlnodeexpressgitsecurity

Works with

  • claude code
  • cli

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned September 30, 2026

npx -y skills add geniro-io/geniro-app --skill run-desktop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Run Desktop?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Run Desktop
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/geniro-io-run-desktop/badge)](https://www.skillsdirectory.com/skills/geniro-io-run-desktop)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: run-desktop
description: Build, run, and drive the Geniro desktop UI on headless Linux (Claude Code on the web). Use when asked to run/start/screenshot the app or interact with its UI in a remote container. Drives the real renderer bundle in Chromium wired to a real daemon — the packaged Electron shell can't launch here. On macOS, prefer `pnpm dev` with the real Electron shell first; use this Chromium harness as the fallback for headless/remote environments and for scripted/automated UI driving via its REPL.
---

Geniro is a macOS Electron app. **On macOS, the first choice is `pnpm dev`** —
it launches the real Electron shell against a real daemon, and needs nothing
in this skill. Reach for the harness below instead when there is no display to
launch Electron on (a remote/headless container), or when the task wants
**scripted, repeatable UI driving** through a REPL rather than a human at the
window — either way, on a real Mac you can always fall back to `pnpm dev` and
drive the window by hand.

In a remote Linux container, specifically, the **packaged Electron shell
cannot launch** at all — in that environment the Electron binary download hit
a 403 from GitHub releases (egress policy; report, don't route around it). So
for agent/automated use there we drive the **same renderer bundle** the
Electron window loads, in the pre-installed **Chromium**, wired to a **real
daemon** (spawned under host Node), with `window.geniro` stubbed to hand the
renderer a live daemon handle + an onboarded state. This is also the harness to
reach for on a Mac when scripted driving (not a human eyeballing the window) is
what's wanted — it works there too, it's just not the first choice.

Everything is a REPL driver at `.claude/skills/run-desktop/driver.mjs`. Launch
is slow (~15s: daemon boot + the claude modes probe). Screenshots land in
`/tmp/shots/` (override with `SCREENSHOT_DIR`). All paths below are relative to
the repo root.

## Prerequisites (one-time per container)

```bash
# 1. Build the daemon (dist/) and the renderer (out/).
pnpm install
pnpm build

# 2. better-sqlite3 must match HOST Node's ABI — the daemon runs under host Node
#    here (Electron is unavailable). If you ever ran `pnpm rebuild:native` (which
#    targets Electron's ABI), put it back:
pnpm rebuild better-sqlite3

# 3. playwright-core — install it OUTSIDE the repo. Do NOT `npm install` inside
#    this pnpm workspace: npm prunes pnpm's hoisted deps (reflect-metadata, …)
#    and breaks the daemon. A side dir is safe; the driver looks there.
mkdir -p ~/.geniro-run-pw && npm i --prefix ~/.geniro-run-pw playwright-core
```

The driver finds Chromium under `/opt/pw-browsers` and `claude` on `PATH`
automatically. (Override the playwright-core location with `GENIRO_PW=<dir>`.)

## Run (agent path)

```bash
tmux new-session -d -s georun -x 220 -y 50
tmux send-keys -t georun 'node .claude/skills/run-desktop/driver.mjs' Enter
timeout 20 bash -c 'until tmux capture-pane -t georun -p | grep -q "driver>"; do sleep 0.3; done'
tmux send-keys -t georun 'launch' Enter
timeout 70 bash -c 'until tmux capture-pane -t georun -p | grep -qE "app shell ready|WARN"; do sleep 0.5; done'
tmux send-keys -t georun 'ss chats' Enter          # screenshot the chats view
tmux send-keys -t georun 'nav Graphs' Enter
tmux send-keys -t georun 'ss graphs' Enter
tmux capture-pane -t georun -p                       # read command output
```

Then actually open `/tmp/shots/chats.png`. Blank frame = launch failed.

### Commands

| command | what it does |
|---|---|
| `launch` | boot daemon + warm probe + Chromium + stub, open the renderer |
| `caps` | print `GET /v1/capabilities` (the claude modes probe verdict) |
| `nav <Chats\|Graphs\|Settings>` | click a nav-rail item |
| `seed-workflow` | drop a demo workflow (an acceptEdits + an auto agent) — run AFTER `launch`, then `nav Graphs` |
| `fill <css-sel> <text>` | fill an input/textarea |
| `click <css-sel>` / `click-text <text>` | click (DOM click; coords not needed) |
| `send` | click the composer's Send button |
| `approve` / `deny` | answer a pending approval card |
| `ss [name]` | screenshot → `/tmp/shots/<name>.png` |
| `text [css-sel]` | print innerText |
| `options <css-sel>` | print an open menu's `[role="option"]` rows within the matched element (label, `aria-selected`, `disabled`) |
| `js <expr>` | run an expression in the page (Playwright `page.evaluate`) |
| `quit` | close browser, kill daemon, exit |

Useful selectors: composer textarea `textarea[aria-label="Task for the new run"]`,
approval chip `[aria-label="Tool-approval mode"]`, Send `button[aria-label="Send"]`.

### Example: drive the approval-card round-trip

```
launch
fill textarea[aria-label="Task for the new run"] Use the Write tool to create note.txt containing hi. Call the tool directly.
send
# wait ~15s for the real claude turn to reach the Write permission…
ss approval-card
approve
ss approved
```

The chat's cwd is a throwaway (`~/.tmp` via `GENIRO_RUN_CWD`, default
`$TMPDIR/geniro-run-cwd`), so tool calls never touch the repo.

**`GENIRO_RUN_CONFIG_DIR=<path>`** points every chat at a different agent CONFIG
directory — the profile holding that CLI's credentials, so a run can be driven
against a second account. Reach for it when the default profile is rate-limited:
the turn otherwise comes back as "You've hit your weekly limit" and proves
nothing about the code under test. It seeds the stubbed settings, so the
composer's profile chip shows it and every run created carries it.

## Gotchas

- **Electron shell won't launch — by policy.** The Electron binary download is
  a 403 (egress policy). Don't fight it; the renderer-under-Chromium path is the
  supported one here. On a real Mac, just `pnpm dev`.
- **Never `npm install` in this repo.** It's a pnpm workspace; npm prunes the
  hoisted `node_modules` and the daemon then can't find `reflect-metadata`.
  Repair with `pnpm install --force`. Keep playwright-core in the side dir.
  Symptom: `daemon exited code=1` / `Cannot find module` → node_modules is
  damaged (npm-in-pnpm) or the daemon isn't built:
  `pnpm install --force && pnpm build`.
- **Daemon runs under host Node**, so better-sqlite3 must be host-ABI (see
  Prerequisites). `Cannot find module …better_sqlite3.node` / ABI mismatch =
  run `pnpm rebuild better-sqlite3`.
- **This app has no native `<select>`** — every dropdown is `role="option"`
  DOM buttons behind `[data-menu-trigger]` (`menu.tsx`), which is exactly what
  makes one screenshottable and assertable open: click the trigger, then `ss`
  it or read its rows with `options <sel>`.
- **The browser flags are harness-only.** `--no-sandbox`,
  `--disable-web-security` and `bypassCSP: true` are what let the renderer's
  REST + Socket.IO cross loopback ports to the real daemon here; they must
  never be copied into app code or CI, and this browser must only ever load
  the locally-served renderer bundle — never a remote origin — since it can
  reach the daemon and its bearer token.
- **`playwright-core not found`** →
  `mkdir -p ~/.geniro-run-pw && npm i --prefix ~/.geniro-run-pw playwright-core`.
- **`WARN: app shell not detected`** → the renderer isn't built (`pnpm build`),
  or the daemon didn't come up (run `caps`, check the launch log).

Files in this skill

  • SKILL.md7.2 KB
  • driver.mjs25.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…