@rubytech/create-maxy-code 0.1.185 → 0.1.187
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/package.json +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +70 -0
- package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +52 -0
- package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +14 -14
- package/payload/platform/plugins/admin/skills/deck-pages/SKILL.md +9 -23
- package/payload/platform/plugins/admin/skills/professional-document/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/skill-builder/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/skill-builder/references/pdf-generation.md +4 -4
- package/payload/platform/plugins/browser/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/browser/PLUGIN.md +78 -8
- package/payload/platform/plugins/browser/mcp/dist/index.js +111 -7
- package/payload/platform/plugins/browser/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-actions.d.ts +98 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-actions.d.ts.map +1 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-actions.js +455 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-actions.js.map +1 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-render.d.ts +9 -31
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-render.d.ts.map +1 -1
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-render.js +6 -165
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-render.js.map +1 -1
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-session.d.ts +153 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-session.d.ts.map +1 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-session.js +401 -0
- package/payload/platform/plugins/browser/mcp/dist/lib/cdp-session.js.map +1 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +6 -2
- package/payload/platform/plugins/email/mcp/dist/index.js +38 -0
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +28 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js +106 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +19 -15
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.js +3 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js +2 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.js +2 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +3 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +1 -1
- package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +15 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +1 -1
- package/payload/platform/scripts/check-cypher-int-params.mjs +277 -0
- package/payload/platform/scripts/setup-account.sh +2 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
- package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -2
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js +33 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +5 -2
- package/payload/server/{chunk-L2YK2VK3.js → chunk-EFJ2EXZK.js} +3 -1
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression test for prompt-optimiser-directive.sh.
|
|
3
|
+
#
|
|
4
|
+
# Covers:
|
|
5
|
+
# 1. Valid UserPromptSubmit stdin -> exit 0, stdout is valid JSON,
|
|
6
|
+
# hookEventName == "UserPromptSubmit", additionalContext non-empty.
|
|
7
|
+
# 2. Empty / garbage stdin -> exit 0, still valid JSON (the hook
|
|
8
|
+
# ignores the prompt text; the directive is standing).
|
|
9
|
+
# 3. python3 unavailable on PATH -> exit 0, empty stdout (fail-open).
|
|
10
|
+
|
|
11
|
+
set -u
|
|
12
|
+
|
|
13
|
+
HOOK="$(cd "$(dirname "$0")/.." && pwd)/prompt-optimiser-directive.sh"
|
|
14
|
+
if [[ ! -x "$HOOK" ]]; then
|
|
15
|
+
echo "FAIL: $HOOK not executable" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
PASS=0
|
|
20
|
+
FAIL=0
|
|
21
|
+
|
|
22
|
+
# Assert: hook on $1 (stdin) exits 0 and stdout is JSON with a non-empty
|
|
23
|
+
# additionalContext and the correct hookEventName.
|
|
24
|
+
assert_valid_injection() {
|
|
25
|
+
local name="$1" stdin="$2" out actual_exit
|
|
26
|
+
out=$(printf '%s' "$stdin" | bash "$HOOK" 2>/dev/null)
|
|
27
|
+
actual_exit=$?
|
|
28
|
+
if [[ "$actual_exit" -ne 0 ]]; then
|
|
29
|
+
echo "FAIL: $name (expected exit=0, got=$actual_exit)" >&2; FAIL=$((FAIL+1)); return
|
|
30
|
+
fi
|
|
31
|
+
if printf '%s' "$out" | python3 -c '
|
|
32
|
+
import json, sys
|
|
33
|
+
d = json.load(sys.stdin)
|
|
34
|
+
h = d["hookSpecificOutput"]
|
|
35
|
+
assert h["hookEventName"] == "UserPromptSubmit", "wrong hookEventName"
|
|
36
|
+
assert isinstance(h["additionalContext"], str) and len(h["additionalContext"]) > 0, "empty additionalContext"
|
|
37
|
+
' 2>/dev/null; then
|
|
38
|
+
echo "PASS: $name"; PASS=$((PASS+1))
|
|
39
|
+
else
|
|
40
|
+
echo "FAIL: $name (stdout not valid directive JSON: $out)" >&2; FAIL=$((FAIL+1))
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Case 1 — valid UserPromptSubmit envelope.
|
|
45
|
+
assert_valid_injection "valid UserPromptSubmit stdin -> directive JSON" \
|
|
46
|
+
'{"hook_event_name":"UserPromptSubmit","prompt":"fix the thing","session_id":"abc"}'
|
|
47
|
+
|
|
48
|
+
# Case 2 — garbage stdin still yields the standing directive.
|
|
49
|
+
assert_valid_injection "garbage stdin -> still directive JSON" \
|
|
50
|
+
'not json at all'
|
|
51
|
+
|
|
52
|
+
# Case 3 — fail-open when python3 is absent from PATH.
|
|
53
|
+
FAKEBIN=$(mktemp -d)
|
|
54
|
+
# A minimal PATH containing the core utils the hook needs (cat, printf, env,
|
|
55
|
+
# command) but NOT python3. Resolve their real dir(s) and symlink them in.
|
|
56
|
+
for util in cat printf env bash; do
|
|
57
|
+
src=$(command -v "$util" 2>/dev/null) && ln -sf "$src" "$FAKEBIN/$util" 2>/dev/null
|
|
58
|
+
done
|
|
59
|
+
nopy_out=$(printf '%s' '{"prompt":"x"}' | PATH="$FAKEBIN" bash "$HOOK" 2>/dev/null)
|
|
60
|
+
nopy_exit=$?
|
|
61
|
+
if [[ "$nopy_exit" -eq 0 && -z "$nopy_out" ]]; then
|
|
62
|
+
echo "PASS: python3 absent -> exit 0, empty stdout (fail-open)"; PASS=$((PASS+1))
|
|
63
|
+
else
|
|
64
|
+
echo "FAIL: python3 absent (expected exit=0 + empty stdout, got exit=$nopy_exit stdout=$nopy_out)" >&2; FAIL=$((FAIL+1))
|
|
65
|
+
fi
|
|
66
|
+
rm -rf "$FAKEBIN"
|
|
67
|
+
|
|
68
|
+
echo "----"
|
|
69
|
+
echo "PASS=$PASS FAIL=$FAIL"
|
|
70
|
+
[[ "$FAIL" -eq 0 ]]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# UserPromptSubmit hook: inject the standing prompt-optimiser directive as
|
|
3
|
+
# additionalContext on every turn, so the agent restates each non-trivial
|
|
4
|
+
# prompt via the prompt-optimiser skill before acting on it. Mirrors
|
|
5
|
+
# doctrine.sh: emit hookSpecificOutput.additionalContext via python3,
|
|
6
|
+
# fail-open with exit 0.
|
|
7
|
+
#
|
|
8
|
+
# Compliance is behavioural, not deterministic. A hook cannot force a skill
|
|
9
|
+
# call; the directive steers the agent, it does not gate the turn. The raw
|
|
10
|
+
# prompt always still reaches the model (UserPromptSubmit output is ADDED to
|
|
11
|
+
# the prompt, never substituted), so "the restatement governs" is an
|
|
12
|
+
# instruction to the agent, not a removal of the original text.
|
|
13
|
+
#
|
|
14
|
+
# Input (stdin, UserPromptSubmit contract): { "prompt": "...", ... } — drained
|
|
15
|
+
# and ignored; the directive is standing and prompt-independent.
|
|
16
|
+
# Output (stdout JSON):
|
|
17
|
+
# { "hookSpecificOutput": { "hookEventName": "UserPromptSubmit",
|
|
18
|
+
# "additionalContext": "<directive>" } }
|
|
19
|
+
#
|
|
20
|
+
# Fail-open: if python3 is unavailable or JSON encoding fails, exit 0 with no
|
|
21
|
+
# stdout. The hook must never block or corrupt a turn.
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
# Drain stdin; the directive does not depend on the prompt text.
|
|
26
|
+
cat >/dev/null 2>&1 || true
|
|
27
|
+
|
|
28
|
+
# Fail-open if the JSON encoder is unavailable.
|
|
29
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
30
|
+
|
|
31
|
+
DIRECTIVE=$(cat <<'DIRECTIVE_EOF'
|
|
32
|
+
PROMPT-OPTIMISER DIRECTIVE (standing)
|
|
33
|
+
Before acting on this turn's request, run the prompt-optimiser skill on the user's raw prompt in its in-session restatement mode, then treat the restated task as the authoritative statement of what was asked and proceed from it. Set aside your own first reading of the raw wording; the restatement governs. The raw prompt stays in context for reference only.
|
|
34
|
+
Skip the restatement when the prompt is a one-word confirmation, a slash-command invocation, or a direct continuation of the immediately preceding turn; restatement adds nothing in those cases.
|
|
35
|
+
DIRECTIVE_EOF
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
OUT=$(printf '%s' "$DIRECTIVE" | python3 -c '
|
|
39
|
+
import json, sys
|
|
40
|
+
ctx = sys.stdin.read()
|
|
41
|
+
print(json.dumps({
|
|
42
|
+
"hookSpecificOutput": {
|
|
43
|
+
"hookEventName": "UserPromptSubmit",
|
|
44
|
+
"additionalContext": ctx,
|
|
45
|
+
}
|
|
46
|
+
}))
|
|
47
|
+
' 2>/dev/null) || exit 0
|
|
48
|
+
|
|
49
|
+
[ -n "$OUT" ] || exit 0
|
|
50
|
+
printf '%s\n' "$OUT"
|
|
51
|
+
echo "[prompt-optimiser-directive] injected len=${#DIRECTIVE}" >&2
|
|
52
|
+
exit 0
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: a4-print-documents
|
|
3
3
|
description: >
|
|
4
4
|
Constraints for producing print-quality A4 HTML documents intended for PDF output
|
|
5
|
-
via
|
|
6
|
-
saved as PDF, uses A4 page dimensions, or contains glassmorphism/backdrop-filter effects
|
|
5
|
+
via the on-device browser-pdf-save tool. Use when creating or modifying any HTML document
|
|
6
|
+
that will be saved as PDF, uses A4 page dimensions, or contains glassmorphism/backdrop-filter effects
|
|
7
7
|
that must survive print. Trigger phrases: "PDF", "print to PDF", "download PDF",
|
|
8
8
|
"one-pager", "A4", "brochure", "executive document", "market analysis", or any request
|
|
9
9
|
to generate a document for print or download.
|
|
@@ -11,11 +11,13 @@ description: >
|
|
|
11
11
|
|
|
12
12
|
# A4 Print-Ready HTML Documents
|
|
13
13
|
|
|
14
|
-
Constraints for producing HTML documents that render correctly when saved as PDF via the
|
|
14
|
+
Constraints for producing HTML documents that render correctly when saved as PDF via the on-device `browser-pdf-save` tool. Every rule exists because violating it caused a visible problem in production documents.
|
|
15
15
|
|
|
16
16
|
## Rendering path
|
|
17
17
|
|
|
18
|
-
Generate the HTML document, then
|
|
18
|
+
Generate the HTML document, then save it as PDF with the on-device browser tools (no Playwright). The order is: capture any glassmorphism print-fallback images first (see below), then `browser-navigate` to the served HTML and `browser-pdf-save` to the output path. `browser-pdf-save` uses Chromium's print pipeline, so `@page` rules, page breaks, and `print-color-adjust` all apply and backgrounds are printed.
|
|
19
|
+
|
|
20
|
+
Serve the HTML over `http://127.0.0.1:<port>` and navigate to that URL — open it from a local server rather than relying on a `file://` path. `browser-navigate` keeps the page alive so a screenshot pass and the PDF pass act on the same rendered document.
|
|
19
21
|
|
|
20
22
|
## Glassmorphism does not survive print
|
|
21
23
|
|
|
@@ -50,19 +52,17 @@ This pattern applies to every full-bleed glassmorphism section (cover pages, bac
|
|
|
50
52
|
|
|
51
53
|
### Capturing the print image
|
|
52
54
|
|
|
53
|
-
Each glassmorphism section needs its own screenshot — you cannot reuse a print image from a different document or a differently-sized section.
|
|
54
|
-
|
|
55
|
-
Delegate to the personal-assistant with a brief that includes:
|
|
55
|
+
Each glassmorphism section needs its own screenshot — you cannot reuse a print image from a different document or a differently-sized section. Capture it with the on-device browser tools against the served HTML:
|
|
56
56
|
|
|
57
|
-
1. **Serve the HTML over HTTP**
|
|
58
|
-
2. **
|
|
59
|
-
3. **Hide UI chrome**
|
|
60
|
-
4. **
|
|
57
|
+
1. **Serve the HTML over HTTP** and `browser-navigate` to `http://127.0.0.1:<port>/...` — open it from a local server, not a `file://` path.
|
|
58
|
+
2. **Match the viewport to the element.** Read the element's bounding box with `browser-evaluate` (`document.querySelector('.cover').getBoundingClientRect()`), then `browser-resize` to `Math.ceil(width) + 1` by `Math.ceil(height) + 1` to eliminate scrollbars.
|
|
59
|
+
3. **Hide UI chrome** with `browser-evaluate` — hide download buttons and navigation overlays and set `document.body.style.overflow = 'hidden'` to suppress body-level scrollbars.
|
|
60
|
+
4. **Capture the specific element** with `browser-screenshot` passing the element's CSS selector (e.g. `.cover`, `.backpage`) — the selector clips to that element, not the full page.
|
|
61
61
|
5. **Save the PNG** alongside the HTML, named for the section it replaces (e.g. `cover-print.png`, `backpage-print.png`).
|
|
62
|
-
6. **Restore** hidden elements and overflow after capture.
|
|
62
|
+
6. **Restore** hidden elements and overflow with `browser-evaluate` after capture.
|
|
63
63
|
7. **Verify** the saved PNG visually — confirm no scrollbars, no UI chrome, correct dimensions.
|
|
64
64
|
|
|
65
|
-
After capturing all print images,
|
|
65
|
+
After capturing all print images, save the final PDF with `browser-pdf-save` to the output path.
|
|
66
66
|
|
|
67
67
|
## Page margins, page numbers, and running footers
|
|
68
68
|
|
|
@@ -211,7 +211,7 @@ Multi-page documents follow this structure:
|
|
|
211
211
|
</div>
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
The backpage print image is captured
|
|
214
|
+
The backpage print image is captured the same way as the cover — `browser-screenshot` with the element selector, UI chrome hidden via `browser-evaluate`.
|
|
215
215
|
|
|
216
216
|
## Single-page documents
|
|
217
217
|
|
|
@@ -17,7 +17,7 @@ Constraints for producing HTML slide decks that render correctly on screen and e
|
|
|
17
17
|
|
|
18
18
|
## How this differs from A4 print documents
|
|
19
19
|
|
|
20
|
-
A4 documents use the browser's native print pipeline (`@page` rules,
|
|
20
|
+
A4 documents use the browser's native print pipeline (`@page` rules, the on-device `browser-pdf-save` tool). Deck pages bypass print entirely — each slide is rasterized to a canvas and composited into a PDF on the client. This means:
|
|
21
21
|
|
|
22
22
|
- Glassmorphism and `backdrop-filter` render correctly (no PNG fallbacks needed).
|
|
23
23
|
- Text in the PDF is raster, not vector — so the canvas `scale` factor matters for sharpness.
|
|
@@ -29,7 +29,7 @@ Print styles are still included as a fallback for users who Cmd+P, but they are
|
|
|
29
29
|
|
|
30
30
|
1. Build the HTML deck with fixed-dimension `.slide` containers.
|
|
31
31
|
2. Add a client-side download button that uses `html2canvas-pro` + `jsPDF`.
|
|
32
|
-
3. Verify each slide visually using
|
|
32
|
+
3. Verify each slide visually using `browser-screenshot` before declaring the deck complete.
|
|
33
33
|
|
|
34
34
|
## Slide dimensions
|
|
35
35
|
|
|
@@ -316,25 +316,17 @@ Slides use a compressed typography scale that fits content within the fixed 210m
|
|
|
316
316
|
|
|
317
317
|
These sizes are calibrated for 297mm × 210mm at `scale: 2` — they produce readable text in the exported PDF. Smaller sizes become illegible; larger sizes cause overflow.
|
|
318
318
|
|
|
319
|
-
## Verifying slides with
|
|
319
|
+
## Verifying slides with on-device screenshots
|
|
320
320
|
|
|
321
|
-
Before declaring a deck complete, verify that every slide renders correctly by taking
|
|
321
|
+
Before declaring a deck complete, verify that every slide renders correctly by taking screenshots with `browser-screenshot`. This catches problems that are invisible in the dev server but appear in the PDF: clipped content, broken images, font loading failures, and layout overflow.
|
|
322
322
|
|
|
323
323
|
### Verification process
|
|
324
324
|
|
|
325
325
|
1. **Start a local server** — the deck must be served over HTTP. `file://` protocol blocks font loading and CORS resources. If Next.js, run `npm run dev` and wait for the ready signal before navigating.
|
|
326
326
|
|
|
327
|
-
2. **Navigate to the deck page** and wait for fonts and images to load. Use `
|
|
327
|
+
2. **Navigate to the deck page** with `browser-navigate` and wait for fonts and images to load. Use `browser-wait-for` or `browser-evaluate` with `document.fonts.ready` to ensure custom fonts are loaded — otherwise screenshots capture system-font fallbacks that look correct in dev but produce the wrong PDF.
|
|
328
328
|
|
|
329
|
-
3. **Screenshot each slide individually** —
|
|
330
|
-
|
|
331
|
-
Pseudocode for the Playwright loop:
|
|
332
|
-
```
|
|
333
|
-
slides = page.locator(".slide").all()
|
|
334
|
-
for i, slide in enumerate(slides):
|
|
335
|
-
slide.scroll_into_view_if_needed()
|
|
336
|
-
slide.screenshot(path=f"slide-{i+1}.png")
|
|
337
|
-
```
|
|
329
|
+
3. **Screenshot each slide individually** — call `browser-screenshot` with each `.slide` element's CSS selector, not a full-page screenshot. Full-page screenshots compress the entire deck into one image and hide per-slide problems. Read the slide selectors from `browser-snapshot` (or compute `.slide:nth-of-type(n)`), and capture each to its own `slide-<n>.png`.
|
|
338
330
|
|
|
339
331
|
4. **Verify each screenshot against these checks:**
|
|
340
332
|
- Content fits within the slide bounds (no clipping at edges).
|
|
@@ -345,15 +337,9 @@ Before declaring a deck complete, verify that every slide renders correctly by t
|
|
|
345
337
|
- No unexpected scrollbars or overflow indicators.
|
|
346
338
|
- Card grids and tables are evenly spaced.
|
|
347
339
|
|
|
348
|
-
5. **Test the PDF download** — click the download button
|
|
349
|
-
```
|
|
350
|
-
page.click("button:has-text('Download PDF')")
|
|
351
|
-
download = page.wait_for_event("download")
|
|
352
|
-
download.save_as("deck.pdf")
|
|
353
|
-
```
|
|
354
|
-
Then open the PDF and confirm page count matches slide count and no pages are blank.
|
|
340
|
+
5. **Test the PDF download** — `browser-click` the download button, then open the generated PDF and confirm the page count matches the slide count and no pages are blank.
|
|
355
341
|
|
|
356
|
-
### Common rendering failures caught by
|
|
342
|
+
### Common rendering failures caught by screenshots
|
|
357
343
|
|
|
358
344
|
| Symptom | Cause | Fix |
|
|
359
345
|
|---------|-------|-----|
|
|
@@ -413,6 +399,6 @@ A complete working deck lives at `/Users/neo/capacity-derivatives/app/deck/` —
|
|
|
413
399
|
- [ ] Download button is outside `.slide` containers (in a fixed overlay).
|
|
414
400
|
- [ ] Dark slides use `backgroundColor: null` in html2canvas config.
|
|
415
401
|
- [ ] Print fallback styles included (`@page`, `@media print`).
|
|
416
|
-
- [ ]
|
|
402
|
+
- [ ] `browser-screenshot` taken of every slide — content verified.
|
|
417
403
|
- [ ] PDF download tested — correct number of pages, no blank pages.
|
|
418
404
|
- [ ] Fonts loaded before any screenshot or export (check `document.fonts.ready`).
|
|
@@ -141,7 +141,7 @@ Before saving the PDF, ask the operator where they want it saved. The operator n
|
|
|
141
141
|
|
|
142
142
|
### 9. Save the PDF
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
`browser-navigate` to the served HTML, then `browser-pdf-save` to write the PDF to the operator-named path (on-device Chromium print pipeline; no Playwright).
|
|
145
145
|
|
|
146
146
|
Emit `[professional-document] pdf-saved path=<operator-named-path> bytes=<n>`.
|
|
147
147
|
|
|
@@ -46,7 +46,7 @@ Ask what rules the agent should follow when using this skill:
|
|
|
46
46
|
|
|
47
47
|
Not every skill needs all of these. Only include what's relevant.
|
|
48
48
|
|
|
49
|
-
If the skill involves generating PDFs or print-ready documents, load `references/pdf-generation.md` for the constraints on HTML layout and the `
|
|
49
|
+
If the skill involves generating PDFs or print-ready documents, load `references/pdf-generation.md` for the constraints on HTML layout and the `browser-pdf-save` rendering path.
|
|
50
50
|
|
|
51
51
|
## Step 4: Decide on References
|
|
52
52
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# PDF Generation in Skills
|
|
2
2
|
|
|
3
|
-
When a skill needs to produce a PDF, the output is always a two-stage process: the skill authors the HTML, and the
|
|
3
|
+
When a skill needs to produce a PDF, the output is always a two-stage process: the skill authors the HTML, and the agent renders it to PDF with the on-device `browser-pdf-save` tool.
|
|
4
4
|
|
|
5
5
|
## The rule
|
|
6
6
|
|
|
7
|
-
Never instruct the agent to write or execute raw Node.js scripts (e.g. `require('playwright')`, `page.pdf()`, `npx playwright`). Playwright is not
|
|
7
|
+
Never instruct the agent to write or execute raw Node.js scripts (e.g. `require('playwright')`, `page.pdf()`, `npx playwright`). Playwright is not on device — these scripts fail with MODULE_NOT_FOUND, waste tool calls searching the filesystem, and produce hardcoded paths that break on reinstall.
|
|
8
8
|
|
|
9
9
|
Instead, skills that produce PDFs should:
|
|
10
10
|
|
|
11
11
|
1. **Build the HTML** — the skill's behaviour section describes what the document contains and how it's structured. The agent writes the HTML file using standard file tools.
|
|
12
|
-
2. **
|
|
12
|
+
2. **Render with `browser-pdf-save`** — serve the HTML over `http://127.0.0.1:<port>`, `browser-navigate` to it, then `browser-pdf-save` to the output path. The tool drives the device's Chromium over CDP (honours `@page`/print CSS, prints backgrounds); no Playwright is involved.
|
|
13
13
|
|
|
14
14
|
## Print-ready HTML constraints
|
|
15
15
|
|
|
@@ -27,4 +27,4 @@ HTML intended for PDF output must follow print-ready layout patterns. The most c
|
|
|
27
27
|
|
|
28
28
|
## What the skill file should contain
|
|
29
29
|
|
|
30
|
-
The skill's SKILL.md describes the document's purpose and structure. A `references/` file holds the HTML template or content specification. The skill should state that PDF rendering
|
|
30
|
+
The skill's SKILL.md describes the document's purpose and structure. A `references/` file holds the HTML template or content specification. The skill should state that PDF rendering goes through `browser-pdf-save` — it should not contain Playwright code, npm commands, or file path assumptions about any Playwright installation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "Drives the device's existing Chromium over the Chrome DevTools Protocol for both retrieval and interactive automation. browser-render reads a JS-rendered page's DOM (the JS-rendering leg of WebFetch / url-get / browser-render retrieval). The browser-navigate family keeps one page alive across calls for click/fill/snapshot automation, and browser-pdf-save / browser-screenshot turn the rendered page into PDF or PNG files on device. Use it for client-rendered SPAs, form automation, and generating A4 PDFs.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,10 +1,61 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: browser
|
|
3
|
-
description: "
|
|
3
|
+
description: "Drives the device's existing Chromium over the Chrome DevTools Protocol for both retrieval and interactive automation. browser-render reads a JS-rendered page's DOM (the JS-rendering leg of WebFetch / url-get / browser-render retrieval). The browser-navigate family keeps one page alive across calls for click/fill/snapshot automation, and browser-pdf-save / browser-screenshot turn the rendered page into PDF or PNG files on device. Use it for client-rendered SPAs, form automation, and generating A4 PDFs."
|
|
4
4
|
tools:
|
|
5
5
|
- name: browser-render
|
|
6
6
|
publicAllowlist: false
|
|
7
7
|
adminAllowlist: true
|
|
8
|
+
- name: browser-navigate
|
|
9
|
+
publicAllowlist: false
|
|
10
|
+
adminAllowlist: true
|
|
11
|
+
- name: browser-snapshot
|
|
12
|
+
publicAllowlist: false
|
|
13
|
+
adminAllowlist: true
|
|
14
|
+
- name: browser-click
|
|
15
|
+
publicAllowlist: false
|
|
16
|
+
adminAllowlist: true
|
|
17
|
+
- name: browser-fill
|
|
18
|
+
publicAllowlist: false
|
|
19
|
+
adminAllowlist: true
|
|
20
|
+
- name: browser-fill-form
|
|
21
|
+
publicAllowlist: false
|
|
22
|
+
adminAllowlist: true
|
|
23
|
+
- name: browser-type
|
|
24
|
+
publicAllowlist: false
|
|
25
|
+
adminAllowlist: true
|
|
26
|
+
- name: browser-press-key
|
|
27
|
+
publicAllowlist: false
|
|
28
|
+
adminAllowlist: true
|
|
29
|
+
- name: browser-hover
|
|
30
|
+
publicAllowlist: false
|
|
31
|
+
adminAllowlist: true
|
|
32
|
+
- name: browser-select-option
|
|
33
|
+
publicAllowlist: false
|
|
34
|
+
adminAllowlist: true
|
|
35
|
+
- name: browser-wait-for
|
|
36
|
+
publicAllowlist: false
|
|
37
|
+
adminAllowlist: true
|
|
38
|
+
- name: browser-handle-dialog
|
|
39
|
+
publicAllowlist: false
|
|
40
|
+
adminAllowlist: true
|
|
41
|
+
- name: browser-evaluate
|
|
42
|
+
publicAllowlist: false
|
|
43
|
+
adminAllowlist: true
|
|
44
|
+
- name: browser-console-messages
|
|
45
|
+
publicAllowlist: false
|
|
46
|
+
adminAllowlist: true
|
|
47
|
+
- name: browser-tabs
|
|
48
|
+
publicAllowlist: false
|
|
49
|
+
adminAllowlist: true
|
|
50
|
+
- name: browser-pdf-save
|
|
51
|
+
publicAllowlist: false
|
|
52
|
+
adminAllowlist: true
|
|
53
|
+
- name: browser-screenshot
|
|
54
|
+
publicAllowlist: false
|
|
55
|
+
adminAllowlist: true
|
|
56
|
+
- name: browser-resize
|
|
57
|
+
publicAllowlist: false
|
|
58
|
+
adminAllowlist: true
|
|
8
59
|
always: true
|
|
9
60
|
embed: false
|
|
10
61
|
metadata: {"platform":{}}
|
|
@@ -23,22 +74,41 @@ mcp-manifest: auto
|
|
|
23
74
|
|
|
24
75
|
# Browser
|
|
25
76
|
|
|
26
|
-
|
|
77
|
+
Drives the device's Chromium over the Chrome DevTools Protocol for three things: reading JS-rendered pages, automating a page (click/fill/snapshot), and turning a page into a PDF or PNG file on device.
|
|
27
78
|
|
|
28
79
|
## Why it exists
|
|
29
80
|
|
|
30
|
-
`WebFetch` summarises and refuses verbatim output; `url-get` returns the verbatim source but does not execute JavaScript. Client-rendered pages
|
|
81
|
+
`WebFetch` summarises and refuses verbatim output; `url-get` returns the verbatim source but does not execute JavaScript. Client-rendered pages come back from both as an empty shell. `browser-render` runs the page's JavaScript and reads the resulting DOM. The automation and output tools exist because the specialists previously named a Playwright tool surface (interaction + `browser_pdf_save`) that never actually shipped on a Pi — these tools provide the same capabilities backed by the on-device CDP connection instead.
|
|
31
82
|
|
|
32
83
|
## How it works
|
|
33
84
|
|
|
34
|
-
The device already runs a per-brand Chromium for the VNC surface, launched by `platform/scripts/vnc.sh` with `--remote-debugging-port=${CDP_PORT}`.
|
|
85
|
+
The device already runs a per-brand Chromium for the VNC surface, launched by `platform/scripts/vnc.sh` with `--remote-debugging-port=${CDP_PORT}`. Every tool attaches to that running browser over the Chrome DevTools Protocol — it never launches its own browser, so nothing extra ships on device and there is no on-demand download. The brand's CDP port reaches this server through the `CDP_PORT` spawn placeholder.
|
|
86
|
+
|
|
87
|
+
`browser-render` is **stateless**: each call opens a fresh background target, navigates, reads the DOM, and closes it. The automation tools share **one persistent page**: `browser-navigate` creates (or reuses) a single live target, and the action tools drive it across calls. If that page dies (operator closes the tab, crash, browser restart) the action tools return `session-lost` and the agent calls `browser-navigate` again — only navigate re-attaches, so a dead page is never silently replaced mid-action. `browser-tabs` opens/switches/closes pages.
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
Retrieval (stateless):
|
|
92
|
+
- `browser-render` — `url`, optional `loadTimeoutMs`. Returns rendered HTML + visible text.
|
|
35
93
|
|
|
36
|
-
|
|
94
|
+
Automation (shared persistent page; selectors come from `browser-snapshot`):
|
|
95
|
+
- `browser-navigate` — open a URL and keep the page alive. Entry point for automation.
|
|
96
|
+
- `browser-snapshot` — accessibility snapshot: each interactive/landmark node as role, name, and a unique CSS selector to use with click/fill/hover.
|
|
97
|
+
- `browser-click`, `browser-fill`, `browser-fill-form`, `browser-hover`, `browser-select-option` — act on a CSS selector.
|
|
98
|
+
- `browser-type`, `browser-press-key` — real keyboard input (`Input.*`).
|
|
99
|
+
- `browser-wait-for` — wait for a selector or text to appear.
|
|
100
|
+
- `browser-handle-dialog` — pre-arm how the next JS dialog(s) are answered (CDP dialogs block, so policy must be set before the triggering action).
|
|
101
|
+
- `browser-evaluate` — run a JS expression in the page and return its value.
|
|
102
|
+
- `browser-console-messages` — drain buffered console output + uncaught errors.
|
|
103
|
+
- `browser-tabs` — list / new / select / close pages.
|
|
37
104
|
|
|
38
|
-
|
|
105
|
+
Output (to a file on device):
|
|
106
|
+
- `browser-pdf-save` — `Page.printToPDF` of the current page; the on-device replacement for the old Playwright `browser_pdf_save`.
|
|
107
|
+
- `browser-screenshot` — PNG of the page or one element (CSS selector clip), e.g. glassmorphism print-fallback images.
|
|
108
|
+
- `browser-resize` — set the viewport size.
|
|
39
109
|
|
|
40
|
-
|
|
110
|
+
All tools are admin-allowlisted; none are exposed to public chat.
|
|
41
111
|
|
|
42
112
|
## Observability
|
|
43
113
|
|
|
44
|
-
A successful render emits `[browser-render] url=… rendered=true domBytes=…` on the server's stderr (captured by the mcp-spawn-tee log).
|
|
114
|
+
A successful render emits `[browser-render] url=… rendered=true domBytes=…` on the server's stderr (captured by the mcp-spawn-tee log). Every other tool returns a structured outcome: `ok`, or one of `cdp-unreachable`, `session-lost`, `selector-not-found`, `option-not-found`, `wait-timeout`, `navigate-failed`, `load-timeout`, `evaluate-failed`, `command-timeout`, `pdf-failed`, `screenshot-failed`, `unsupported-key`. On a device with no running Chromium (e.g. a laptop native-display install with no VNC browser) the tools return `cdp-unreachable` rather than hanging.
|
|
@@ -4,7 +4,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { eagerTool } from "../../../../lib/mcp-eager/dist/index.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { writeFile } from "node:fs/promises";
|
|
7
8
|
import { renderPage } from "./lib/cdp-render.js";
|
|
9
|
+
import { BrowserSession, DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_LOAD_TIMEOUT_MS, } from "./lib/cdp-session.js";
|
|
10
|
+
import * as actions from "./lib/cdp-actions.js";
|
|
8
11
|
// Cast to `any` to avoid TS2589 the same way the other plugin servers do.
|
|
9
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
13
|
const server = new McpServer({ name: "maxy-browser", version: "0.1.0" });
|
|
@@ -14,6 +17,9 @@ const cdpPortRaw = process.env.CDP_PORT ?? "";
|
|
|
14
17
|
const cdpPort = Number.parseInt(cdpPortRaw, 10);
|
|
15
18
|
process.stderr.write(`[browser] boot cdpPort=${cdpPortRaw || "-"}\n`);
|
|
16
19
|
const log = (line) => process.stderr.write(`${line}\n`);
|
|
20
|
+
const WebSocketImpl = typeof WebSocket !== "undefined"
|
|
21
|
+
? WebSocket
|
|
22
|
+
: undefined;
|
|
17
23
|
// Cap what we hand back to the model so a large SPA cannot blow the agent's
|
|
18
24
|
// context window. domBytes in the header always reports the true size.
|
|
19
25
|
const MAX_TEXT_CHARS = 200_000;
|
|
@@ -27,13 +33,7 @@ eagerTool(server, "browser-render", "Render a JavaScript-heavy web page in the d
|
|
|
27
33
|
url: z.string().url().describe("Absolute http(s) URL to render"),
|
|
28
34
|
loadTimeoutMs: z.number().int().positive().optional().describe("Max wait for the page load event (default 30000)"),
|
|
29
35
|
}, async (params) => {
|
|
30
|
-
const result = await renderPage(params.url, { loadTimeoutMs: params.loadTimeoutMs }, {
|
|
31
|
-
fetchImpl: fetch,
|
|
32
|
-
WebSocketImpl: typeof WebSocket !== "undefined" ? WebSocket : undefined,
|
|
33
|
-
cdpHost: "127.0.0.1",
|
|
34
|
-
cdpPort,
|
|
35
|
-
log,
|
|
36
|
-
});
|
|
36
|
+
const result = await renderPage(params.url, { loadTimeoutMs: params.loadTimeoutMs }, { fetchImpl: fetch, WebSocketImpl, cdpHost: "127.0.0.1", cdpPort, log });
|
|
37
37
|
if (!result.rendered) {
|
|
38
38
|
return {
|
|
39
39
|
content: [{
|
|
@@ -55,6 +55,110 @@ eagerTool(server, "browser-render", "Render a JavaScript-heavy web page in the d
|
|
|
55
55
|
}],
|
|
56
56
|
};
|
|
57
57
|
});
|
|
58
|
+
// ── Interactive automation + output tools ────────────────────────────────────
|
|
59
|
+
//
|
|
60
|
+
// All of these drive ONE persistent page held in `browserSession`, against the
|
|
61
|
+
// same device Chromium over CDP. The page is created by browser-navigate and
|
|
62
|
+
// reused across calls; browser-tabs opens/switches/closes pages. A dead page
|
|
63
|
+
// (operator closed the tab, crash, browser restart) yields a `session-lost`
|
|
64
|
+
// outcome telling the agent to navigate again — only navigate re-attaches.
|
|
65
|
+
const cdpDeps = { fetchImpl: fetch, WebSocketImpl, cdpHost: "127.0.0.1", cdpPort, log };
|
|
66
|
+
const browserSession = new BrowserSession(cdpDeps);
|
|
67
|
+
const actionCtx = {
|
|
68
|
+
deps: cdpDeps,
|
|
69
|
+
commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
|
|
70
|
+
loadTimeoutMs: DEFAULT_LOAD_TIMEOUT_MS,
|
|
71
|
+
writeFile: (path, data) => writeFile(path, data),
|
|
72
|
+
};
|
|
73
|
+
function reply(label, r, successText) {
|
|
74
|
+
if (!r.ok) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: `${label} failed: ${r.outcome}${r.detail ? ` — ${r.detail}` : ""}` }],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { content: [{ type: "text", text: successText ?? `${label}: ok` }] };
|
|
81
|
+
}
|
|
82
|
+
eagerTool(server, "browser-navigate", "Open a URL in the device's Chromium and keep the page alive for follow-up automation (click, fill, snapshot, pdf-save, screenshot). This is the entry point for interactive browser work — call it before any other browser-* automation tool. Reuses the one live automation page; use browser-tabs to open a separate page. For one-shot read-only retrieval of a JS page use browser-render instead.", {
|
|
83
|
+
url: z.string().url().describe("Absolute http(s) URL to open"),
|
|
84
|
+
loadTimeoutMs: z.number().int().positive().optional().describe("Max wait for the page load event (default 30000)"),
|
|
85
|
+
}, async (params) => reply("browser-navigate", await actions.navigate(browserSession, params, actionCtx), `Navigated to ${params.url}`));
|
|
86
|
+
eagerTool(server, "browser-snapshot", "Return an accessibility snapshot of the current page: each interactive or landmark element as role, accessible name, and a unique CSS selector. Use the returned selector verbatim with browser-click / browser-fill / browser-hover — it targets exactly the element you saw. Call this after browser-navigate (or after an action changes the page) to know what is on the page.", {}, async () => {
|
|
87
|
+
const r = await actions.snapshot(browserSession, {}, actionCtx);
|
|
88
|
+
return reply("browser-snapshot", r, r.data?.text);
|
|
89
|
+
});
|
|
90
|
+
eagerTool(server, "browser-click", "Click the element matching a CSS selector on the current page. Use a selector from browser-snapshot. Returns selector-not-found if nothing matches.", { selector: z.string().describe("CSS selector of the element to click") }, async (params) => reply("browser-click", await actions.click(browserSession, params, actionCtx), `Clicked ${params.selector}`));
|
|
91
|
+
eagerTool(server, "browser-fill", "Set the value of one input, textarea, or contenteditable matching a CSS selector, firing input/change events. For multiple fields in one call use browser-fill-form.", {
|
|
92
|
+
selector: z.string().describe("CSS selector of the field"),
|
|
93
|
+
value: z.string().describe("Value to set"),
|
|
94
|
+
}, async (params) => reply("browser-fill", await actions.fill(browserSession, params, actionCtx), `Filled ${params.selector}`));
|
|
95
|
+
eagerTool(server, "browser-fill-form", "Fill several fields in one call. Each field is a CSS selector and the value to set. Reports every selector that matched nothing.", {
|
|
96
|
+
fields: z.array(z.object({
|
|
97
|
+
selector: z.string().describe("CSS selector of the field"),
|
|
98
|
+
value: z.string().describe("Value to set"),
|
|
99
|
+
})).describe("Fields to fill"),
|
|
100
|
+
}, async (params) => reply("browser-fill-form", await actions.fillForm(browserSession, params, actionCtx), `Filled ${params.fields.length} field(s)`));
|
|
101
|
+
eagerTool(server, "browser-type", "Type text into the focused element via real keyboard input. Pass a selector to focus that element first. Use this when setting value directly (browser-fill) does not trigger the page's keystroke handlers (autocompletes, rich editors).", {
|
|
102
|
+
text: z.string().describe("Text to type"),
|
|
103
|
+
selector: z.string().optional().describe("Optional CSS selector to focus before typing"),
|
|
104
|
+
}, async (params) => reply("browser-type", await actions.type(browserSession, params, actionCtx), "Typed text"));
|
|
105
|
+
eagerTool(server, "browser-press-key", "Press a single key on the focused element (Enter, Tab, Escape, Backspace, Delete, Arrow keys, Home, End, PageUp, PageDown, Space, or a single character). Use for submitting forms (Enter) or keyboard navigation.", { key: z.string().describe("Key name or single character") }, async (params) => reply("browser-press-key", await actions.pressKey(browserSession, params, actionCtx), `Pressed ${params.key}`));
|
|
106
|
+
eagerTool(server, "browser-hover", "Hover the pointer over the element matching a CSS selector, firing pointer/mouse-over events. Use to reveal hover menus or tooltips before reading or clicking.", { selector: z.string().describe("CSS selector of the element to hover") }, async (params) => reply("browser-hover", await actions.hover(browserSession, params, actionCtx), `Hovered ${params.selector}`));
|
|
107
|
+
eagerTool(server, "browser-select-option", "Choose an option in a <select> dropdown by option value or visible label, firing a change event.", {
|
|
108
|
+
selector: z.string().describe("CSS selector of the <select>"),
|
|
109
|
+
value: z.string().optional().describe("Option value to select"),
|
|
110
|
+
label: z.string().optional().describe("Option visible text/label to select"),
|
|
111
|
+
}, async (params) => reply("browser-select-option", await actions.selectOption(browserSession, params, actionCtx), `Selected option in ${params.selector}`));
|
|
112
|
+
eagerTool(server, "browser-wait-for", "Wait until an element (CSS selector) appears or some text is present on the page, up to a timeout. Use after an action that loads content asynchronously, before reading or interacting.", {
|
|
113
|
+
selector: z.string().optional().describe("CSS selector to wait for"),
|
|
114
|
+
text: z.string().optional().describe("Visible text to wait for"),
|
|
115
|
+
timeoutMs: z.number().int().positive().optional().describe("Max wait (default 30000)"),
|
|
116
|
+
}, async (params) => reply("browser-wait-for", await actions.waitFor(browserSession, params, actionCtx), "Condition met"));
|
|
117
|
+
eagerTool(server, "browser-handle-dialog", "Arm how the next JavaScript dialog(s) — alert, confirm, prompt — on the current page are answered. Set this BEFORE the action that triggers the dialog, because dialogs block the page and cannot be answered after the fact. The policy stands until changed. Unarmed dialogs are auto-dismissed and reported by browser-console-messages.", {
|
|
118
|
+
accept: z.boolean().describe("true to accept/OK, false to dismiss/Cancel"),
|
|
119
|
+
promptText: z.string().optional().describe("Text to enter into a prompt() dialog when accepting"),
|
|
120
|
+
}, async (params) => reply("browser-handle-dialog", actions.handleDialog(browserSession, params), `Dialog policy armed: accept=${params.accept}`));
|
|
121
|
+
eagerTool(server, "browser-evaluate", "Run a JavaScript expression in the current page and return its value (awaits a returned promise). Use for reading page state or driving behaviour that no other tool covers. The expression runs in the page, not in Node.", { expression: z.string().describe("JavaScript expression to evaluate in the page") }, async (params) => {
|
|
122
|
+
const r = await actions.evaluate(browserSession, params, actionCtx);
|
|
123
|
+
return reply("browser-evaluate", r, `Result: ${JSON.stringify(r.data?.value ?? null)}`);
|
|
124
|
+
});
|
|
125
|
+
eagerTool(server, "browser-console-messages", "Return and clear the buffered console output and uncaught errors from the current page (captured since navigation). Use to debug why a page or interaction did not behave as expected.", {}, async () => {
|
|
126
|
+
const r = actions.consoleMessages(browserSession);
|
|
127
|
+
return reply("browser-console-messages", r, r.data?.text);
|
|
128
|
+
});
|
|
129
|
+
eagerTool(server, "browser-tabs", "Manage browser tabs/pages: list open pages, open a new page (optionally at a URL and switch to it), select an existing page by id, or close a page by id. Use 'new' or 'select' to get a fresh page so stale state from a prior task does not leak in.", {
|
|
130
|
+
action: z.enum(["list", "new", "select", "close"]).describe("Tab action"),
|
|
131
|
+
id: z.string().optional().describe("Target id (required for select/close)"),
|
|
132
|
+
url: z.string().optional().describe("URL to open (optional for new)"),
|
|
133
|
+
}, async (params) => {
|
|
134
|
+
const r = await actions.tabs(browserSession, params, actionCtx);
|
|
135
|
+
const text = params.action === "list"
|
|
136
|
+
? "Tabs:\n" + (r.data?.tabs ?? [])
|
|
137
|
+
.map((t) => `${t.active ? "* " : " "}${t.id} — ${t.title || "(untitled)"} — ${t.url}`).join("\n")
|
|
138
|
+
: `tabs ${params.action}: ok`;
|
|
139
|
+
return reply("browser-tabs", r, text);
|
|
140
|
+
});
|
|
141
|
+
eagerTool(server, "browser-pdf-save", "Render the current page to a PDF file on the device using Chromium's print pipeline (honours @page/@media print CSS, prints backgrounds). Pass an absolute output path. This is the on-device replacement for the old Playwright browser_pdf_save: navigate to the HTML (served over http, or a file path the browser can open), then call this.", {
|
|
142
|
+
path: z.string().describe("Absolute output path for the .pdf file"),
|
|
143
|
+
landscape: z.boolean().optional().describe("Landscape orientation (default false)"),
|
|
144
|
+
scale: z.number().optional().describe("Scale of the page rendering (default 1)"),
|
|
145
|
+
printBackground: z.boolean().optional().describe("Print background graphics (default true)"),
|
|
146
|
+
}, async (params) => {
|
|
147
|
+
const r = await actions.pdfSave(browserSession, params, actionCtx);
|
|
148
|
+
return reply("browser-pdf-save", r, `Saved PDF to ${params.path} (${r.data?.bytes ?? 0} bytes)`);
|
|
149
|
+
});
|
|
150
|
+
eagerTool(server, "browser-screenshot", "Capture a PNG of the current page (or one element, via a CSS selector clip) to a file on the device. Pass an absolute output path. Use selector clips to capture glassmorphism/blur sections as print fallback images for A4 PDFs.", {
|
|
151
|
+
path: z.string().describe("Absolute output path for the .png file"),
|
|
152
|
+
selector: z.string().optional().describe("CSS selector to clip to a single element"),
|
|
153
|
+
fullPage: z.boolean().optional().describe("Capture beyond the viewport (default false)"),
|
|
154
|
+
}, async (params) => {
|
|
155
|
+
const r = await actions.screenshot(browserSession, params, actionCtx);
|
|
156
|
+
return reply("browser-screenshot", r, `Saved screenshot to ${params.path} (${r.data?.bytes ?? 0} bytes)`);
|
|
157
|
+
});
|
|
158
|
+
eagerTool(server, "browser-resize", "Set the current page's viewport size in CSS pixels. Use before a screenshot to control the rendered dimensions (e.g. match an element's measured size to avoid scrollbars).", {
|
|
159
|
+
width: z.number().int().positive().describe("Viewport width in CSS pixels"),
|
|
160
|
+
height: z.number().int().positive().describe("Viewport height in CSS pixels"),
|
|
161
|
+
}, async (params) => reply("browser-resize", await actions.resize(browserSession, params, actionCtx), `Resized to ${params.width}x${params.height}`));
|
|
58
162
|
process.on("SIGINT", () => process.exit(0));
|
|
59
163
|
const transport = new StdioServerTransport();
|
|
60
164
|
await server.connect(transport);
|