feat(v0.6): project card launchers — Claude/Codex/VS Code logos + active highlight
Replace each card's "+ start claude here" with a row of three brand-logo launcher buttons (logo + caption, touch-friendly, tooltips): - Claude (coral burst) → new terminal tab running `claude` - Codex (OpenAI mark) → new terminal tab running `codex` - VS Code (blue ribbon) → opens the repo in VS Code ON THE HOST VS Code: new guarded POST /open-in-editor runs `<EDITOR_CMD> <path>` (default `code`) on the host via execFile (no shell); path validated as an existing absolute dir; Origin guard (CSRF) like the DELETE routes. EDITOR_CMD config added. Active-session highlight (user request): when a project has a running Claude session (Claude-hook status != unknown — plain shells/codex stay unknown), the Claude logo gets a coral ring + tint, with a count badge when more than one. New: public/icons.ts (simple-icons SVGs, fill=currentColor), src/http/editor.ts. onOpenProject gains an optional cmd; makeProjectCard exported for tests. Tests +16 (431 total): editor validation/launch, EDITOR_CMD config, launcher row (3 buttons, claude/codex commands), and the active-Claude highlight + badge. Verified: coverage >=80x4, build OK, both typechecks clean; in-browser the 3 logos render with brand colours, and POST /open-in-editor actually launched VS Code on the host (403 no-Origin / 400 bad-path / 204 valid-dir all correct).
This commit is contained in:
@@ -40,6 +40,7 @@ import { isOriginAllowed } from './http/origin.js'
|
||||
import { parseHookEvent } from './http/hook.js'
|
||||
import { listSessions } from './http/history.js'
|
||||
import { buildProjects } from './http/projects.js'
|
||||
import { openInEditor } from './http/editor.js'
|
||||
import { createSessionManager } from './session/manager.js'
|
||||
import { detachWs, writeInput, setClientDims } from './session/session.js'
|
||||
import type { Config } from './types.js'
|
||||
@@ -217,6 +218,21 @@ export function startServer(cfg: Config): { close(): Promise<void> } {
|
||||
res.status(ok ? 204 : 404).end()
|
||||
})
|
||||
|
||||
// Open a project in the host's desktop editor (v0.6 Projects panel — VS Code
|
||||
// logo). State-changing (spawns a GUI process), so it carries the same Origin
|
||||
// guard as the DELETE routes. The path is validated + passed via execFile (no
|
||||
// shell) in openInEditor.
|
||||
app.post('/open-in-editor', express.json({ limit: '4kb' }), async (req, res) => {
|
||||
if (!requireAllowedOrigin(req, res)) return
|
||||
const body = (req.body ?? {}) as Record<string, unknown>
|
||||
const result = await openInEditor(cfg, body['path'])
|
||||
if (result.ok) {
|
||||
res.status(204).end()
|
||||
return
|
||||
}
|
||||
res.status(result.status).json({ error: result.error })
|
||||
})
|
||||
|
||||
// ── Claude Code hook side-channel (H2) ────────────────────────────────────
|
||||
// Hooks running inside a spawned shell POST status here (loopback only — the
|
||||
// shell runs on the host). sessionId arrives in the X-Webterm-Session header.
|
||||
|
||||
Reference in New Issue
Block a user