agentgui 1.0.947 → 1.0.949
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/.claude/workflows/gui-audit.js +86 -0
- package/AGENTS.md +29 -11
- package/docs/demo.html +27 -27
- package/lib/codec.js +1 -1
- package/lib/http-handler.js +31 -4
- package/lib/process-message.js +1 -1
- package/lib/stream-event-handler.js +3 -3
- package/lib/terminal.js +1 -1
- package/lib/ws-handlers-util.js +9 -0
- package/lib/ws-setup.js +1 -1
- package/package.json +2 -2
- package/scripts/build-rippleui.mjs +5 -5
- package/scripts/capture-screenshots.mjs +3 -3
- package/scripts/copy-vendor.js +1 -1
- package/scripts/harvest-fixtures.mjs +7 -7
- package/scripts/seed-large-conversation.js +2 -2
- package/scripts/smoke-ws-chat.mjs +2 -2
- package/server.js +1 -1
- package/site/app/index.html +25 -19
- package/site/app/js/app.js +224 -76
- package/site/app/js/backend.js +13 -9
- package/site/app/vendor/anentrypoint-design/247420.css +923 -46
- package/site/app/vendor/anentrypoint-design/247420.js +89 -42
- package/site/theme.mjs +1 -1
- package/test.js +27 -11
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: 'gui-audit',
|
|
3
|
+
description: 'Sweep every agentgui GUI surface for jank, verify findings adversarially, and emit kit-update tasks',
|
|
4
|
+
whenToUse: 'Run a maximum-effort GUI audit of agentgui: review chat, history, settings, a11y, security, performance, and glyph hygiene across the client (site/app) and the design kit (c:\\dev\\anentrypoint-design). Fans out one reviewer per surface, verifies each finding with an independent skeptic, then synthesizes a prioritized punch-list. GUI fixes land in the design kit.',
|
|
5
|
+
phases: [
|
|
6
|
+
{ title: 'Review', detail: 'one reviewer agent per GUI surface/aspect' },
|
|
7
|
+
{ title: 'Verify', detail: 'independent skeptic confirms each finding is real' },
|
|
8
|
+
{ title: 'Synthesize', detail: 'dedupe + prioritize into a punch-list with kit-vs-app split' },
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Every GUI surface and cross-cutting aspect the audit must cover. Each becomes
|
|
13
|
+
// one reviewer agent. Add a row to widen coverage; the fan-out scales with it.
|
|
14
|
+
const SURFACES = [
|
|
15
|
+
{ key: 'chat', prompt: 'Audit the agentgui CHAT surface for jank. Read site/app/js/app.js (chatMain, sendChat, toolPart, applyToolResult), site/app/js/backend.js (streamChat), and the kit AgentChat at c:\\dev\\anentrypoint-design/src/components/agent-chat.js + chat.js. Check: tool_use/tool_result render as structured collapsible cards (not flattened text); markdown/text/tool parts interleave in arrival order; empty state; composer stop button while streaming; thinking/working indicator during silent tool runs; aborted-empty turn pruning; multi-turn resume correctness. Report each issue with file:line, why it is jank, and whether the fix is in the kit or the app.' },
|
|
16
|
+
{ key: 'history', prompt: 'Audit the agentgui HISTORY surface (site/app/js/app.js historyMain, historySide, loadSession, EventList usage). Check: event rendering consistency with chat (same event same way), expand/collapse + keyboard activation, the silent 300-event cap with no load-older, search-result click landing on the matched event, live SSE wiring, session list filtering/subagent toggle. Report each issue with file:line and kit-vs-app.' },
|
|
17
|
+
{ key: 'settings', prompt: 'Audit the agentgui SETTINGS surface (site/app/js/app.js settingsMain, agentsPanel, preferencesPanel, saveBackend). Check: ACP agent start/stop/restart/health controls (read-only today), cwd surfacing + server-side validation, backend URL validation, clear-local-data flow, health summary clarity. Report each issue with file:line and kit-vs-app.' },
|
|
18
|
+
{ key: 'a11y', prompt: 'Audit agentgui ACCESSIBILITY across site/app and the kit. Check: skip-link target reachability/focus, tab order on each tab, agent/model select labels, EventList row role=button keyboard activation + expand announcement, aria-live over/under-announcing, color contrast on the dark theme (WCAG AA 4.5:1 text / 3:1 UI), focus-visible rings, reduced-motion. Report each issue with file:line.' },
|
|
19
|
+
{ key: 'security', prompt: 'Audit agentgui GUI-reachable SECURITY. Read lib/http-handler.js. Check: Access-Control-Allow-Origin:* with Authorization + ?token= cross-origin/leak risk; /api/image arbitrary-file-read (the .. check runs after path.normalize); markdown XSS (kit marked+dompurify) — does DOMPurify actually neutralize script/javascript: URLs in agent output. Report each issue with file:line, severity, and the concrete exploit path.' },
|
|
20
|
+
{ key: 'perf', prompt: 'Audit agentgui GUI PERFORMANCE. Check: per-token render+scroll thrash on the chat stream (should be rAF-coalesced via scheduleStreamRender), history EventList rebuild cost on burst SSE loads (last-300 map per rAF), webjsx keying stability, markdown/Prism cache warmup, resize/relTime tick cost. Report each hotspot with file:line and a concrete fix.' },
|
|
21
|
+
{ key: 'glyph', prompt: 'Sweep EVERY GUI source file (site/app/**, c:\\dev\\anentrypoint-design/src/**, *.css) for decorative tell-tale glyphs: arrows, box-drawing, bullets, status dots, checks, emoji, any non-ASCII decorative symbol. KEEP the typographic set (middot, ellipsis, em/en-dash in prose), SVG line-icons via Icon(), CSS-drawn discs, and intentional ascii-art (AICAT_FACE). Report every decorative glyph found with file:line and its ASCII replacement.' },
|
|
22
|
+
{ key: 'responsive', prompt: 'Audit agentgui RESPONSIVE behavior from site/app/index.html media queries + the kit AppShell. Check: chat controls cluster wrap at 360/640/900px, cwd path overflow, history sidebar drawer (open/close/scrim/select-closes-it), touch-target sizes, settings two-column collapse. Report each issue with the breakpoint and file:line.' },
|
|
23
|
+
{ key: 'consistency', prompt: 'Audit agentgui PREDICTABILITY/CONSISTENCY. Check that the same state reads the same way everywhere: connection/activity status vocabulary across crumb dot, chat status, AgentControls status, settings health, agentsPanel; ellipsis convention (… vs ...); rail tone semantics (green=ok/selected, purple=subagent, flame=error); button labels. Report each divergence with file:line.' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const FINDINGS_SCHEMA = {
|
|
27
|
+
type: 'object',
|
|
28
|
+
required: ['surface', 'findings'],
|
|
29
|
+
properties: {
|
|
30
|
+
surface: { type: 'string' },
|
|
31
|
+
findings: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
required: ['title', 'where', 'why', 'fixLocation'],
|
|
36
|
+
properties: {
|
|
37
|
+
title: { type: 'string' },
|
|
38
|
+
where: { type: 'string', description: 'file:line' },
|
|
39
|
+
why: { type: 'string' },
|
|
40
|
+
fixLocation: { type: 'string', enum: ['kit', 'app', 'server', 'both'] },
|
|
41
|
+
severity: { type: 'string', enum: ['low', 'medium', 'high'] },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const VERDICT_SCHEMA = {
|
|
49
|
+
type: 'object',
|
|
50
|
+
required: ['isReal', 'reason'],
|
|
51
|
+
properties: {
|
|
52
|
+
isReal: { type: 'boolean' },
|
|
53
|
+
reason: { type: 'string' },
|
|
54
|
+
correctedFix: { type: 'string' },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
phase('Review');
|
|
59
|
+
|
|
60
|
+
// Pipeline: each surface is reviewed, then every finding it produced is
|
|
61
|
+
// independently verified — a surface's findings start verifying the moment that
|
|
62
|
+
// surface's review returns, without waiting for slower surfaces.
|
|
63
|
+
const reviewed = await pipeline(
|
|
64
|
+
SURFACES,
|
|
65
|
+
(s) => agent(s.prompt, { label: 'review:' + s.key, phase: 'Review', schema: FINDINGS_SCHEMA }),
|
|
66
|
+
(review, surface) =>
|
|
67
|
+
parallel((review.findings || []).map((f) => () =>
|
|
68
|
+
agent(
|
|
69
|
+
'Adversarially verify this GUI audit finding. Default to isReal=false if you cannot confirm it from the actual source. Finding: "' + f.title + '" at ' + f.where + ' — ' + f.why,
|
|
70
|
+
{ label: 'verify:' + surface.key, phase: 'Verify', schema: VERDICT_SCHEMA }
|
|
71
|
+
).then((v) => ({ ...f, surface: surface.key, verdict: v }))
|
|
72
|
+
))
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const confirmed = reviewed.flat().filter(Boolean).filter((f) => f.verdict && f.verdict.isReal);
|
|
76
|
+
log(confirmed.length + ' confirmed findings across ' + SURFACES.length + ' surfaces');
|
|
77
|
+
|
|
78
|
+
phase('Synthesize');
|
|
79
|
+
|
|
80
|
+
const punchList = await agent(
|
|
81
|
+
'Synthesize these confirmed agentgui GUI audit findings into a prioritized punch-list. Group by fixLocation (kit vs app vs server), order by severity then user impact, dedupe overlaps, and for each give a one-line concrete remedy. Findings JSON:\n' +
|
|
82
|
+
JSON.stringify(confirmed, null, 2),
|
|
83
|
+
{ label: 'synthesize', phase: 'Synthesize' }
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return { confirmedCount: confirmed.length, confirmed, punchList };
|
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ One surface. `server.js` serves `site/app/` under `BASE_URL` (default `/gm`) and
|
|
|
6
6
|
|
|
7
7
|
When `PASSWORD` env var is set, every HTTP route is gated by `lib/http-handler.js` accepting **Basic auth**, **`Authorization: Bearer <pwd>`**, OR **`?token=<pwd>`** query param (added 2026-05-26 so `EventSource` and direct deep-links work — neither can set headers). WS `/sync` requires `?token=` only. The HTML head script injects `window.__BASE_URL`, `window.__SERVER_VERSION`, and `window.__WS_TOKEN`; `site/app/js/backend.js` reads `__WS_TOKEN` and threads it onto every fetch (Bearer header) / EventSource (qs) / WebSocket (qs).
|
|
8
8
|
|
|
9
|
-
- `site/app/index.html` — shell + CSS; imports `anentrypoint-design` from
|
|
9
|
+
- `site/app/index.html` — shell + CSS; imports `anentrypoint-design` from the **local vendored copy** `./vendor/anentrypoint-design/247420.{js,css}` (re-vendored 2026-06-04 for a PREDICTABLE shipped UI that does not shift when upstream publishes, and offline operation — the markdown stack marked/dompurify/prismjs still fetches from jsdelivr on first chat render). Update flow: edit the kit at `c:\dev\anentrypoint-design`, `node scripts/build.mjs`, copy `dist/247420.{js,css}` into `site/app/vendor/anentrypoint-design/`, then publish the kit so unpkg stays in sync.
|
|
10
10
|
- `site/app/js/backend.js` — same-origin WS/HTTP client (`DEFAULT_BACKEND = ''`); the transport glue that wires the kit; `?backend=` query override for cross-origin debugging
|
|
11
11
|
- `site/app/js/app.js` — webjsx view + state; renders the `AgentChat` kit (from `anentrypoint-design`) for the chat surface and wires agentgui's WS/ccsniff state as kit callbacks; history/settings remain agentgui-local. Exposes `window.__agentgui`
|
|
12
12
|
|
|
@@ -18,6 +18,20 @@ Dependencies:
|
|
|
18
18
|
- `ccsniff` (>=1.1.0) — exports `createHistoryRouter({projectsDir})` mountable on Express; serves `/v1/history/{sessions,sessions/:sid/events,search,snapshot,reindex,stream}`. Reads `~/.claude/projects` (override via `CLAUDE_PROJECTS_DIR`).
|
|
19
19
|
- `anentrypoint-design` (>=0.0.119) — kit library, single-file ESM from unpkg
|
|
20
20
|
|
|
21
|
+
## GUI audit pass (2026-06-04) — structured chat, predictable presentation
|
|
22
|
+
|
|
23
|
+
A maximum-effort GUI sweep landed these, GUI changes in the kit `c:\dev\anentrypoint-design` (built+vendored locally), correctness/security in agentgui:
|
|
24
|
+
|
|
25
|
+
- **Structured tool cards in chat.** `app.js sendChat` now emits structured parts — `toolPart()` builds `{kind:'tool',name,label,args,status,_id}` and `applyToolResult()` matches a `tool_result` back to its `tool_use` by id (sets `result`+done/error status). The kit `AgentChat` passes structured `{kind,...}` parts straight through to `ChatMessage.renderPart` (only bare strings are wrapped as text), so the kit's collapsible `ToolCallNode` (args/result sections, status icons) renders instead of the old flattened `'-> '+text`. Full args/result live in the card (no 120/160-char truncation).
|
|
26
|
+
- **AgentChat kit additions** (`src/components/agent-chat.js`): empty state (`.agentchat-empty*`, title/sub/`suggestions`+`onSuggestionClick`), composer stop button while streaming (`busy`+`onCancel:onStop` into `ChatComposer`), a `working…` tail (`.agentchat-working`) when a content-bearing turn is still streaming (silent tool runs no longer read as frozen). CSS in `chat.css`.
|
|
27
|
+
- **Streaming render is rAF-coalesced** via `scheduleStreamRender()` (was render+scroll per token).
|
|
28
|
+
- **Multi-turn resume wired end-to-end.** Server `chat.sendMessage` is one-shot (new ephemeral `chat-` id per call); claude's real session id is `parsed.session_id` in the stream. `ws-handlers-util.js onEvent` now broadcasts `streaming_session{claudeSessionId}` once; `backend.js` yields `{type:'session'}`; `app.js` captures it into `state.chat.resumeSid` (claude-code only) + persists, so the next turn `--resume`s.
|
|
29
|
+
- **Skip link works.** Kit `AppShell` `<main id=app-main>` now has `tabindex=-1` so the skip-link moves focus, not just scrolls.
|
|
30
|
+
- **History:** `state.eventsLimit` (default 300, reset per session) + `load N older` control (no more silently-unreachable older events); expand-all/collapse-all; per-event rail tone (green=normal, purple=tool_use, flame=error) matching the GUI-wide rail semantics; search hits carry `{focusEventI,focusEventTs}` so a click scrolls to + flashes (`.event-flash`) the matched event; mobile drawer auto-closes on session select (`loadSession` removes `.side-open` since DS only auto-closes on `<a>` clicks).
|
|
31
|
+
- **Security** (`lib/http-handler.js`): `/api/image` confined to an allowlist root (`CLAUDE_PROJECTS_DIR`, home, `IMAGE_ROOTS`) — was arbitrary-file-read (the `..` check was a no-op after `path.normalize`); CORS no longer advertises `Access-Control-Allow-Origin:*` when `PASSWORD` is set (reflects `CORS_ORIGIN` if set, else same-origin only). Markdown XSS verified inert (DOMPurify strips script/onerror/`javascript:`).
|
|
32
|
+
- **Consistency:** unified ellipsis to `…` (kept set); settings health chip uses connection vocab (`connected/connecting/offline`) matching the crumb dot; running-chats panel now shows on chat tab too (extracted `runningPanel()`); aborted-empty turns pruned from persisted/restored chat; restored transcript reopens under its own agent (`restoredAgent`).
|
|
33
|
+
- **Workflow:** `.claude/workflows/gui-audit.js` — a reusable multi-agent GUI-audit workflow (per-surface reviewers -> adversarial verify -> synthesize, kit-vs-app fixLocation).
|
|
34
|
+
|
|
21
35
|
## Orchestration agents
|
|
22
36
|
|
|
23
37
|
The four flagship agents the GUI drives are **Claude Code, OpenCode, Kilo, and Antigravity (`agy`)**; the agent picker (`site/app/js/app.js`, `PRIMARY_AGENTS`) sorts these first, then other-available, then npx-installable, then not-installed.
|
|
@@ -60,13 +74,7 @@ Pattern: all imports in lib/plugins/* must be either built-in (crypto, fs, path,
|
|
|
60
74
|
|
|
61
75
|
## Plugin Tool Provisioning on Windows
|
|
62
76
|
|
|
63
|
-
**`lib/tool-spawner.js`
|
|
64
|
-
|
|
65
|
-
On Windows hosts without bun installed, the auto-provisioner on startup and 6h periodic update checker failed silently when spawnBunxProc tried `bun.cmd` directly. The missing command error came via process stderr+close, not the 'error' event, so simple ENOENT detection was insufficient. Additionally, the error message format differs by OS: Windows shows "'bun.cmd' is not recognized as an internal or external command", Linux/Mac show "command not found" or "cannot find".
|
|
66
|
-
|
|
67
|
-
Fix: `BUNX_RUNNERS` array iterates `['bun', 'npx']` and tries each in sequence. Error detection regex `isMissingCmdError` matches `/not recognized|ENOENT|command not found|cannot find/i` on both `error.message` and captured stdout+stderr. Only falls through to next runner when the `missing` flag is set.
|
|
68
|
-
|
|
69
|
-
Pattern: When a binary might not exist on all platforms, use a runner fallback strategy. Always capture and check both error.message and process output streams. Cross-platform error detection requires regex alternation on common message patterns.
|
|
77
|
+
**`lib/tool-spawner.js` iterates `BUNX_RUNNERS=['bun','npx']` and detects missing-command via regex on both error.message and stdout+stderr.** Full detail in rs-learn (recall "agentgui windows tool-spawner bunx fallback").
|
|
70
78
|
|
|
71
79
|
## GM Plugin Autonomy Blocker
|
|
72
80
|
|
|
@@ -97,9 +105,7 @@ The function previously returned "Model: X." when agentId was 'claude-code' and
|
|
|
97
105
|
|
|
98
106
|
## WebSocket Sync Endpoint Testing
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Server sends `sync_connected` with `clientId` on connect. Legacy handler (`lib/ws-legacy-handlers.js`) handles `ping->pong`, `subscribe->subscription_confirmed`, `get_subscriptions->subscriptions`, `unsubscribe`, `latency_report`. All responses use codec encode/decode (`lib/codec.js`). Pattern: queue outbound messages and use a waiters array + sequential promises to avoid race between send and handler registration. Test structure: `const queued = []; let waiting; ws.on('message', ...); queued.forEach(msg => ws.send(msg)); waiting.resolve(...)` ensures the handler is live before messages flow.
|
|
108
|
+
**`/sync` sends `sync_connected`+clientId on connect; legacy handler in `lib/ws-legacy-handlers.js` (ping/subscribe/get_subscriptions/unsubscribe/latency_report) via codec; tests must register the message handler before sending.** Full detail in rs-learn (recall "agentgui websocket /sync testing handler ordering").
|
|
103
109
|
|
|
104
110
|
## better-sqlite3 & Node v24 Startup
|
|
105
111
|
|
|
@@ -154,3 +160,15 @@ GUI source keeps typographic product characters — the middot separator `·`, t
|
|
|
154
160
|
## DS CSS cascade — overriding component styles (2026-05-28)
|
|
155
161
|
|
|
156
162
|
**`installStyles()` injects DS CSS into a runtime `<style>` after the head `<style>`, so local overrides need `!important` or higher specificity than the DS's `.ds-247420`-prefixed rules.** Full detail (font vars `--ff-display`/`--ff-mono`, the `.chat-head .sub` "00 msgs" quirk, EventList `.row[role=button]`, `[data-prog-focus]` focus suppression, `projectLabel()` for cwd slugs) is in rs-learn (recall "agentgui GUI styling DS cascade installStyles").
|
|
163
|
+
|
|
164
|
+
## No decorative glyphs — ASCII-only GUI (2026-06-04)
|
|
165
|
+
|
|
166
|
+
**The GUI carries no decorative tell-tale glyphs; remaining non-ASCII is the deliberate typographic separator set.** The de-jank sweep removed every tell-tale (status dots `[CHAR]`, arrows, box-drawing dividers, checks) in favor of ASCII words + the CSS-drawn status disc. The middot separator `·` is kept as established product design (it reads as a real meta-separator, used consistently across rows/subs); ellipsis `…` and em/en dashes were converted to `...` / `-` on sight. Verified live on buildesk: `document.body.innerText` matches no decorative-glyph regex (excluding the kept middot) across chat/history/settings. The DS Alert dismiss control renders its own close glyph only on a dismissible alert (intentional DS icon affordance, exempt).
|
|
167
|
+
|
|
168
|
+
## DS SearchInput accessible name comes from `label`, not `aria-label` (2026-06-04)
|
|
169
|
+
|
|
170
|
+
**`anentrypoint-design`'s `SearchInput` (internal `De`) sets `aria-label = label || placeholder`; it ignores any `aria-label` prop you pass.** To give the search box a real accessible name, pass `label:` (and a matching `placeholder:` for the visible hint). Passing only `aria-label` leaves AT announcing the placeholder. A post-render `setAttribute` race-loses against the DS re-render, so the prop is the only durable fix.
|
|
171
|
+
|
|
172
|
+
## DS load moved to unpkg @latest — offline regression, re-vendor follow-up (2026-06-04)
|
|
173
|
+
|
|
174
|
+
**`index.html` currently loads the DS kit (`247420.css` + `247420.js`) from `https://unpkg.com/anentrypoint-design@latest` at runtime, NOT the local `vendor/` copy.** Commit `947086e` made this deliberate trade ("always-latest" over offline) because the GUI's chat surface now renders via the kit's **load-bearing `AgentChat` component**, which exists in `@latest` (0.0.186) but NOT in the older locally-vendored bundle (`vendor/anentrypoint-design/247420.js`). Consequences: the GUI requires network, the shipped UI shifts when the upstream package publishes, and the local vendoring (gzip-Ha CSS blob, font/CDN-const localization) is bypassed. The separate jsdelivr imports inside the vendored bundle (`si`=marked@15, `ri`=dompurify@3, `_s`=prismjs@1.30.0/components) are therefore moot while on unpkg. **Follow-up to restore offline + predictability:** re-vendor 0.0.186+ locally (it has AgentChat), re-apply the offline localization (recall "DS bundle gzip Ha vendoring") AND rewire its `si`/`ri`/`_s` markdown-stack consts to the local `vendor/cdn/{marked.js,dompurify.js,prismjs/components/}` copies, then point `index.html` back at `./vendor/`. Witnessed live (browser `page.on('request')`): on `@latest` the page fetches `unpkg.com/anentrypoint-design@latest/dist/247420.{css,js}` at load.
|
package/docs/demo.html
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>AgentGUI
|
|
7
|
-
<meta name="description" content="See what AgentGUI looks like on your machine
|
|
6
|
+
<title>AgentGUI - Live Demo Preview</title>
|
|
7
|
+
<meta name="description" content="See what AgentGUI looks like on your machine - a non-interactive preview of the Gmail-inspired multi-agent interface.">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=JetBrains+Mono:wght@400;500;600&family=Google+Sans:wght@300;400;500;700&display=swap">
|
|
11
11
|
<style>
|
|
12
|
-
/*
|
|
12
|
+
/* ---- Page chrome ---- */
|
|
13
13
|
:root {
|
|
14
14
|
--paper: #EFE9DD; --ink: #0B0B09; --green: #247420; --green-2: #92CEAC;
|
|
15
15
|
--panel-0: var(--paper); --panel-1: #f6f1e7; --panel-2: #ece5d8; --panel-3: #ddd4c3;
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
-webkit-font-smoothing: antialiased;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/*
|
|
40
|
+
/* ---- Page layout ---- */
|
|
41
41
|
.page { display: flex; flex-direction: column; min-height: 100vh; }
|
|
42
42
|
|
|
43
43
|
.topbar {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
.hero p { color: var(--panel-text-2); font-size: 13px; margin: 0 0 8px; }
|
|
67
67
|
.hero .sub { font-size: 12px; color: var(--panel-text-3); }
|
|
68
68
|
|
|
69
|
-
/*
|
|
69
|
+
/* ---- Browser mockup ---- */
|
|
70
70
|
.mockup-wrap {
|
|
71
71
|
margin: 0 auto;
|
|
72
72
|
max-width: 1200px;
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
.browser-viewport { background: #202124; }
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
/*
|
|
115
|
+
/* ---- Gmail-skin app shell (inside viewport) ---- */
|
|
116
116
|
.app-shell {
|
|
117
117
|
display: grid;
|
|
118
118
|
grid-template-columns: 72px 280px 1fr;
|
|
@@ -452,7 +452,7 @@
|
|
|
452
452
|
}
|
|
453
453
|
.send-btn svg { width: 16px; height: 16px; }
|
|
454
454
|
|
|
455
|
-
/*
|
|
455
|
+
/* ---- Below-fold sections ---- */
|
|
456
456
|
.features { margin-top: 64px; }
|
|
457
457
|
.features h2 {
|
|
458
458
|
font-family: var(--ff-display); font-size: 24px; text-transform: lowercase;
|
|
@@ -484,7 +484,7 @@
|
|
|
484
484
|
}
|
|
485
485
|
.install-note { font-size: 12px; color: var(--panel-text-3); margin-top: 8px; }
|
|
486
486
|
|
|
487
|
-
/*
|
|
487
|
+
/* ---- Footer ---- */
|
|
488
488
|
.footer {
|
|
489
489
|
height: 24px; background: var(--panel-accent); color: var(--panel-accent-fg);
|
|
490
490
|
display: flex; align-items: center; padding: 0 14px; gap: 14px;
|
|
@@ -503,7 +503,7 @@
|
|
|
503
503
|
<body>
|
|
504
504
|
<div class="page">
|
|
505
505
|
|
|
506
|
-
<!--
|
|
506
|
+
<!-- ---- Top bar ---- -->
|
|
507
507
|
<header class="topbar">
|
|
508
508
|
<span class="brand">247420<span class="slash">/</span>agentgui</span>
|
|
509
509
|
<nav>
|
|
@@ -514,7 +514,7 @@
|
|
|
514
514
|
</nav>
|
|
515
515
|
</header>
|
|
516
516
|
|
|
517
|
-
<!--
|
|
517
|
+
<!-- ---- Main content ---- -->
|
|
518
518
|
<main class="content">
|
|
519
519
|
|
|
520
520
|
<section class="hero">
|
|
@@ -523,7 +523,7 @@
|
|
|
523
523
|
<p class="sub">Gmail-inspired layout · Claude Code · real project · real output</p>
|
|
524
524
|
</section>
|
|
525
525
|
|
|
526
|
-
<!--
|
|
526
|
+
<!-- ---- Browser mockup ---- -->
|
|
527
527
|
<div class="mockup-wrap">
|
|
528
528
|
|
|
529
529
|
<!-- Browser chrome -->
|
|
@@ -661,7 +661,7 @@
|
|
|
661
661
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
662
662
|
<span class="tool-name">Read</span>
|
|
663
663
|
<span style="flex:1;font-size:11px;color:#9aa0a6">static/css/main.css</span>
|
|
664
|
-
<span class="tool-ok"
|
|
664
|
+
<span class="tool-ok">[ok] ok</span>
|
|
665
665
|
</div>
|
|
666
666
|
<div class="tool-body">read 620 lines · sidebar · main-panel · conversation-item · input-card</div>
|
|
667
667
|
</div>
|
|
@@ -671,7 +671,7 @@
|
|
|
671
671
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
672
672
|
<span class="tool-name">Read</span>
|
|
673
673
|
<span style="flex:1;font-size:11px;color:#9aa0a6">static/css/brand-bible.css</span>
|
|
674
|
-
<span class="tool-ok"
|
|
674
|
+
<span class="tool-ok">[ok] ok</span>
|
|
675
675
|
</div>
|
|
676
676
|
<div class="tool-body">read 449 lines · design tokens · panel vars · typography</div>
|
|
677
677
|
</div>
|
|
@@ -681,7 +681,7 @@
|
|
|
681
681
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
682
682
|
<span class="tool-name">Write</span>
|
|
683
683
|
<span style="flex:1;font-size:11px;color:#9aa0a6">static/css/gmail-skin.css</span>
|
|
684
|
-
<span class="tool-ok"
|
|
684
|
+
<span class="tool-ok">[ok] written</span>
|
|
685
685
|
</div>
|
|
686
686
|
<div class="tool-body">363 lines · grid layout · nav-rail · gm-* tokens · pill rows · search bar</div>
|
|
687
687
|
</div>
|
|
@@ -691,7 +691,7 @@
|
|
|
691
691
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
692
692
|
<span class="tool-name">Edit</span>
|
|
693
693
|
<span style="flex:1;font-size:11px;color:#9aa0a6">static/index.html</span>
|
|
694
|
-
<span class="tool-ok"
|
|
694
|
+
<span class="tool-ok">[ok] updated</span>
|
|
695
695
|
</div>
|
|
696
696
|
<div class="tool-body">added .nav-rail · .header-search-wrap · loaded gmail-skin.css</div>
|
|
697
697
|
</div>
|
|
@@ -703,12 +703,12 @@
|
|
|
703
703
|
</div>
|
|
704
704
|
<div class="msg-body">
|
|
705
705
|
Done. Here's what changed:<br><br>
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
706
|
+
- <strong>Three-column grid</strong> - icon nav rail (72px) -> conversation list (280px) -> main panel<br>
|
|
707
|
+
- <strong>Nav rail</strong> with Compose FAB + Chats / Archived / Tools icons, pill hover/active states<br>
|
|
708
|
+
- <strong>Header search bar</strong> - prominent pill input spanning the header, syncs to sidebar search<br>
|
|
709
|
+
- <strong>Conversation rows</strong> - bold title, muted meta + timestamp, pill hover highlight in Google blue<br>
|
|
710
|
+
- <strong>Input card</strong> - rounded corners, elevation shadow, Google-blue send pill<br>
|
|
711
|
+
- <strong>Soft borders</strong> - <code>#e0e0e0</code> hairlines + drop shadows instead of brand-bible outlines
|
|
712
712
|
</div>
|
|
713
713
|
<div class="msg-ts">10:43 AM</div>
|
|
714
714
|
</div>
|
|
@@ -757,7 +757,7 @@
|
|
|
757
757
|
</div><!-- /viewport -->
|
|
758
758
|
</div><!-- /mockup-wrap -->
|
|
759
759
|
|
|
760
|
-
<!--
|
|
760
|
+
<!-- ---- Features section ---- -->
|
|
761
761
|
<section class="features">
|
|
762
762
|
<h2>what you get</h2>
|
|
763
763
|
<div class="feat-grid">
|
|
@@ -784,7 +784,7 @@
|
|
|
784
784
|
<div class="feat-card">
|
|
785
785
|
<div class="feat-icon">🔄</div>
|
|
786
786
|
<div class="feat-title">Queue & steer</div>
|
|
787
|
-
<div class="feat-desc">Queue messages while the agent is running. Steer mid-session with a single click
|
|
787
|
+
<div class="feat-desc">Queue messages while the agent is running. Steer mid-session with a single click - no need to wait.</div>
|
|
788
788
|
</div>
|
|
789
789
|
<div class="feat-card">
|
|
790
790
|
<div class="feat-icon">🧩</div>
|
|
@@ -794,21 +794,21 @@
|
|
|
794
794
|
</div>
|
|
795
795
|
</section>
|
|
796
796
|
|
|
797
|
-
<!--
|
|
797
|
+
<!-- ---- Install section ---- -->
|
|
798
798
|
<section class="install-section">
|
|
799
799
|
<h2>get started</h2>
|
|
800
800
|
<div class="install-cmd">$ npx agentgui</div>
|
|
801
|
-
<p class="install-note">Requires Node.js 18+. Opens on http://localhost:3000/gm/
|
|
801
|
+
<p class="install-note">Requires Node.js 18+. Opens on http://localhost:3000/gm/ - no account, no cloud, runs entirely local.</p>
|
|
802
802
|
</section>
|
|
803
803
|
|
|
804
804
|
</main>
|
|
805
805
|
|
|
806
806
|
<footer class="footer">
|
|
807
807
|
<span>main</span>
|
|
808
|
-
<span
|
|
808
|
+
<span>- agentgui</span>
|
|
809
809
|
<span class="spread"></span>
|
|
810
810
|
<a href="https://github.com/AnEntrypoint/agentgui">github</a>
|
|
811
|
-
<span
|
|
811
|
+
<span>- 247420 / mmxxvi</span>
|
|
812
812
|
</footer>
|
|
813
813
|
|
|
814
814
|
</div>
|
package/lib/codec.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// agentgui WS wire codec
|
|
1
|
+
// agentgui WS wire codec - plain JSON (UTF-8 text frames).
|
|
2
2
|
// Browser-compatible (no msgpackr). encode() returns a string the ws library
|
|
3
3
|
// sends as a text frame; decode() handles both Buffer/Uint8Array (Node) and
|
|
4
4
|
// string (browser) inputs.
|
package/lib/http-handler.js
CHANGED
|
@@ -6,7 +6,19 @@ import * as term from './terminal.js';
|
|
|
6
6
|
|
|
7
7
|
export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, serveFile, staticDir, messageQueues, getWss, activeExecutions, getACPStatus, discoveredAgents, PKG_VERSION, RATE_LIMIT_MAX, rateLimitMap, routes, PORT }) {
|
|
8
8
|
return async function httpHandler(req, res) {
|
|
9
|
-
|
|
9
|
+
// CORS: when PASSWORD is set the server holds credentials a cross-origin
|
|
10
|
+
// page must not be able to spend, so we do NOT advertise a wildcard origin
|
|
11
|
+
// (a wildcard + a leaked Bearer/token would let any site the user visits
|
|
12
|
+
// drive this server). Reflect an explicitly-allowed origin (CORS_ORIGIN)
|
|
13
|
+
// when configured, else same-origin only. With no PASSWORD the server is
|
|
14
|
+
// already open, so the permissive wildcard is harmless and kept for tools.
|
|
15
|
+
const _corsOrigin = process.env.CORS_ORIGIN;
|
|
16
|
+
if (_corsOrigin) {
|
|
17
|
+
res.setHeader('Access-Control-Allow-Origin', _corsOrigin);
|
|
18
|
+
res.setHeader('Vary', 'Origin');
|
|
19
|
+
} else if (!process.env.PASSWORD) {
|
|
20
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
21
|
+
} // else: no ACAO header -> browsers block cross-origin reads (same-origin still works)
|
|
10
22
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
11
23
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
12
24
|
if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
|
|
@@ -40,7 +52,7 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
|
|
|
40
52
|
} else if (_auth.startsWith('Bearer ')) {
|
|
41
53
|
_ok = _checkToken(_auth.slice(7));
|
|
42
54
|
}
|
|
43
|
-
// EventSource and same-origin links can't set headers
|
|
55
|
+
// EventSource and same-origin links can't set headers - accept ?token= as fallback.
|
|
44
56
|
if (!_ok) {
|
|
45
57
|
try {
|
|
46
58
|
const _qsTok = new URL(req.url, 'http://localhost').searchParams.get('token');
|
|
@@ -86,7 +98,7 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
|
|
|
86
98
|
return;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
|
-
// Terminal sessions
|
|
101
|
+
// Terminal sessions - gated by the Basic-auth check at the top of this handler.
|
|
90
102
|
// Never expose these routes without PASSWORD set.
|
|
91
103
|
if (pathOnly === '/api/terminal/sessions' && req.method === 'GET') {
|
|
92
104
|
sendJSON(req, res, 200, term.listSessions()); return;
|
|
@@ -124,7 +136,22 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
|
|
|
124
136
|
const normalizedPath = path.normalize(expandedPath);
|
|
125
137
|
const isWindows = os.platform() === 'win32';
|
|
126
138
|
const isAbsolute = isWindows ? /^[A-Za-z]:[\\\/]/.test(normalizedPath) : normalizedPath.startsWith('/');
|
|
127
|
-
|
|
139
|
+
// Confine reads to an allowlist root. Without this the route is an
|
|
140
|
+
// arbitrary-file-read of any image-extensioned path on the host (the
|
|
141
|
+
// prior `includes('..')` guard is a no-op after path.normalize resolves
|
|
142
|
+
// the segments). Allowed roots: the Claude projects dir (history images)
|
|
143
|
+
// and the user home; override/add via IMAGE_ROOTS (path-separated).
|
|
144
|
+
const allowRoots = [
|
|
145
|
+
process.env.CLAUDE_PROJECTS_DIR || path.join(os.homedir(), '.claude', 'projects'),
|
|
146
|
+
os.homedir(),
|
|
147
|
+
...(process.env.IMAGE_ROOTS ? process.env.IMAGE_ROOTS.split(path.delimiter) : []),
|
|
148
|
+
].map(r => path.normalize(r));
|
|
149
|
+
const norm = isWindows ? normalizedPath.toLowerCase() : normalizedPath;
|
|
150
|
+
const withinAllowed = allowRoots.some(root => {
|
|
151
|
+
const r = isWindows ? root.toLowerCase() : root;
|
|
152
|
+
return norm === r || norm.startsWith(r + path.sep);
|
|
153
|
+
});
|
|
154
|
+
if (!isAbsolute || !withinAllowed) { res.writeHead(403); res.end('Forbidden'); return; }
|
|
128
155
|
try {
|
|
129
156
|
if (!fs.existsSync(normalizedPath)) { res.writeHead(404); res.end('Not found'); return; }
|
|
130
157
|
const ext = path.extname(normalizedPath).toLowerCase();
|
package/lib/process-message.js
CHANGED
|
@@ -29,7 +29,7 @@ export function createProcessMessage({ queries, activeExecutions, rateLimitState
|
|
|
29
29
|
queries.updateSession(sessionId, { status: 'active' });
|
|
30
30
|
const cwd = conv?.workingDirectory || STARTUP_CWD;
|
|
31
31
|
// Resolve agent before building stateRef so isJsonlBacked can be set correctly.
|
|
32
|
-
// claude-code (protocol: direct) writes JSONL
|
|
32
|
+
// claude-code (protocol: direct) writes JSONL -> JsonlParser owns event broadcasting.
|
|
33
33
|
// All other agents (protocol: acp) rely solely on onEvent for streaming.
|
|
34
34
|
let resolvedAgentId = agentId || 'claude-code';
|
|
35
35
|
const wrapperAgent = discoveredAgents.find(a => a.id === resolvedAgentId && a.protocol === 'cli-wrapper' && a.acpId);
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* For claude-code (isJsonlBacked=true):
|
|
5
5
|
* JsonlParser owns all event broadcasting via the JSONL file watcher.
|
|
6
6
|
* This handler only needs to:
|
|
7
|
-
* 1. Extract session_id
|
|
8
|
-
* 2. Detect inline rate-limit messages
|
|
7
|
+
* 1. Extract session_id -> setClaudeSessionId + registerSession
|
|
8
|
+
* 2. Detect inline rate-limit messages -> scheduleRetry
|
|
9
9
|
*
|
|
10
10
|
* For ACP agents (isJsonlBacked=false):
|
|
11
11
|
* No JSONL file is written. This handler broadcasts streaming_start,
|
|
@@ -18,7 +18,7 @@ export function createEventHandler({ queries, activeExecutions, broadcastSync, r
|
|
|
18
18
|
if (entry) entry.lastActivity = Date.now();
|
|
19
19
|
|
|
20
20
|
// Register session with file watcher as soon as we see the session_id.
|
|
21
|
-
// This pre-maps claudeSessionId
|
|
21
|
+
// This pre-maps claudeSessionId -> (convId, dbSessionId) in JsonlParser before
|
|
22
22
|
// the 16ms file-watcher debounce fires, preventing duplicate conversation creation.
|
|
23
23
|
if (parsed.session_id) {
|
|
24
24
|
if (!batcherRef.resumeSessionId || batcherRef.resumeSessionId !== parsed.session_id) {
|
package/lib/terminal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// WebSocket terminal sessions. Ported from acptoapi 2026-05-21.
|
|
2
2
|
// Auth: callers MUST gate /api/terminal/* + the WS upgrade behind agentgui's
|
|
3
|
-
// PASSWORD Basic-auth check. This module does no auth on its own
|
|
3
|
+
// PASSWORD Basic-auth check. This module does no auth on its own - never
|
|
4
4
|
// expose without the gate.
|
|
5
5
|
|
|
6
6
|
import os from 'os';
|
package/lib/ws-handlers-util.js
CHANGED
|
@@ -85,8 +85,17 @@ export function register(router, deps) {
|
|
|
85
85
|
(async () => {
|
|
86
86
|
let eventCount = 0;
|
|
87
87
|
broadcastSync({ type: 'streaming_start', sessionId, agentId, timestamp: Date.now() });
|
|
88
|
+
let claudeSessionBroadcast = false;
|
|
88
89
|
const onEvent = (parsed) => {
|
|
89
90
|
eventCount++;
|
|
91
|
+
// Surface claude's REAL session id (from the stream) once, so the client
|
|
92
|
+
// can --resume this conversation on its next turn. The ephemeral
|
|
93
|
+
// 'chat-...' sessionId is not a claude session id and cannot be resumed.
|
|
94
|
+
if (!claudeSessionBroadcast && parsed?.session_id) {
|
|
95
|
+
claudeSessionBroadcast = true;
|
|
96
|
+
ctrl.claudeSessionId = parsed.session_id;
|
|
97
|
+
broadcastSync({ type: 'streaming_session', sessionId, claudeSessionId: parsed.session_id, agentId, timestamp: Date.now() });
|
|
98
|
+
}
|
|
90
99
|
if (parsed?.type === 'assistant' && parsed.message?.content) {
|
|
91
100
|
for (const block of parsed.message.content) {
|
|
92
101
|
broadcastSync({ type: 'streaming_progress', sessionId, block, blockRole: 'assistant', seq: eventCount, timestamp: Date.now() });
|
package/lib/ws-setup.js
CHANGED
|
@@ -24,7 +24,7 @@ export function createWsSetup(server, { BASE_URL, watch, staticDir, _assetCache,
|
|
|
24
24
|
hotReloadClients.push(ws);
|
|
25
25
|
ws.on('close', () => { const i = hotReloadClients.indexOf(ws); if (i > -1) hotReloadClients.splice(i, 1); });
|
|
26
26
|
} else if (wsRoute.startsWith('/api/terminal/sessions/')) {
|
|
27
|
-
// Terminal session WS
|
|
27
|
+
// Terminal session WS - auth was already enforced by wss-level PASSWORD
|
|
28
28
|
// check at the top of this connection callback. attach to existing session.
|
|
29
29
|
const m = wsRoute.match(/^\/api\/terminal\/sessions\/([0-9a-f]+)$/);
|
|
30
30
|
if (!m) { ws.close(4400, 'bad-terminal-path'); return; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.949",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "electron/main.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"webjsx": "^0.0.73",
|
|
44
44
|
"webtalk": "^1.0.31",
|
|
45
45
|
"ws": "^8.14.2",
|
|
46
|
-
"xstate": "^5.
|
|
46
|
+
"xstate": "^5.32.0",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// One-time build: produces static/vendor/rippleui.css from the rippleui npm package.
|
|
3
|
-
// Buildless at runtime
|
|
3
|
+
// Buildless at runtime - commit the output so `npm install` is enough to serve.
|
|
4
4
|
//
|
|
5
5
|
// Source files (from node_modules/rippleui/dist/css/):
|
|
6
6
|
// base.css - Tailwind preflight/reset (~2KB)
|
|
7
7
|
// components.css - RippleUI components (.btn etc.) (~143KB)
|
|
8
8
|
// utilities.css - RippleUI extra utilities (~0.05KB)
|
|
9
|
-
// styles.css - full TW + RippleUI monolith (~4.7MB)
|
|
9
|
+
// styles.css - full TW + RippleUI monolith (~4.7MB) - we only mine token defs
|
|
10
10
|
//
|
|
11
11
|
// Output: static/vendor/rippleui.css containing
|
|
12
12
|
// 1. :root / html.dark token blocks extracted from styles.css (so components.css works)
|
|
@@ -47,12 +47,12 @@ while ((m = blockRe.exec(styles))) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
if (tokenBlocks.length === 0) {
|
|
50
|
-
console.error('Could not find token blocks in styles.css
|
|
50
|
+
console.error('Could not find token blocks in styles.css - rippleui shape changed?');
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const header = `/* RippleUI v${readPkgVersion()}
|
|
55
|
-
` * Generated by scripts/build-rippleui.mjs
|
|
54
|
+
const header = `/* RippleUI v${readPkgVersion()} - localized for AgentGUI.\n` +
|
|
55
|
+
` * Generated by scripts/build-rippleui.mjs - do not edit by hand.\n` +
|
|
56
56
|
` * Regenerate: bun run scripts/build-rippleui.mjs\n` +
|
|
57
57
|
` */\n`;
|
|
58
58
|
|
|
@@ -33,7 +33,7 @@ function pickChrome() {
|
|
|
33
33
|
// macOS
|
|
34
34
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
35
35
|
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
36
|
-
// Windows
|
|
36
|
+
// Windows - program files variants
|
|
37
37
|
'C:/Program Files/Google/Chrome/Application/chrome.exe',
|
|
38
38
|
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
|
39
39
|
path.join(home, 'AppData/Local/Google/Chrome/Application/chrome.exe'),
|
|
@@ -72,7 +72,7 @@ async function waitForHealthy(url, timeoutMs = 20000) {
|
|
|
72
72
|
async function main() {
|
|
73
73
|
fs.mkdirSync(OUT, { recursive: true });
|
|
74
74
|
if (!fs.existsSync(FIXTURES)) {
|
|
75
|
-
console.warn(`[capture] fixture dir does not exist: ${FIXTURES}
|
|
75
|
+
console.warn(`[capture] fixture dir does not exist: ${FIXTURES} - using empty data dir`);
|
|
76
76
|
fs.mkdirSync(FIXTURES, { recursive: true });
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -112,7 +112,7 @@ async function main() {
|
|
|
112
112
|
try {
|
|
113
113
|
await waitForHealthy(`${BASE}/api/health`);
|
|
114
114
|
if (serverExitCode !== null) throw new Error(`Server exited with code ${serverExitCode} before becoming healthy`);
|
|
115
|
-
console.log('[capture] server healthy
|
|
115
|
+
console.log('[capture] server healthy - launching browser');
|
|
116
116
|
|
|
117
117
|
const { default: puppeteer } = await import('puppeteer-core');
|
|
118
118
|
const browser = await puppeteer.launch({
|
package/scripts/copy-vendor.js
CHANGED
|
@@ -46,5 +46,5 @@ if (fs.existsSync(webjsxDist)) {
|
|
|
46
46
|
fs.writeFileSync(dest, iife);
|
|
47
47
|
console.log('[copy-vendor] built webjsx IIFE ->', 'static/lib/webjsx.js', `(${iife.split('\n').length} lines)`);
|
|
48
48
|
} else {
|
|
49
|
-
console.warn('[copy-vendor] webjsx not found in node_modules
|
|
49
|
+
console.warn('[copy-vendor] webjsx not found in node_modules - run npm install');
|
|
50
50
|
}
|