@docyrus/docyrus 0.0.59 → 0.0.60

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.
Files changed (44) hide show
  1. package/README.md +46 -0
  2. package/agent-loader.js +1 -1
  3. package/agent-loader.js.map +2 -2
  4. package/main.js +315 -22
  5. package/main.js.map +2 -2
  6. package/package.json +1 -1
  7. package/resources/browser-tools/browser-click.js +74 -0
  8. package/resources/browser-tools/browser-client.js +236 -0
  9. package/resources/browser-tools/browser-close.js +19 -0
  10. package/resources/browser-tools/browser-console.js +73 -0
  11. package/resources/browser-tools/browser-content.js +36 -75
  12. package/resources/browser-tools/browser-cookies.js +19 -14
  13. package/resources/browser-tools/browser-daemon.js +452 -0
  14. package/resources/browser-tools/browser-devtools.js +62 -0
  15. package/resources/browser-tools/browser-eval.js +16 -22
  16. package/resources/browser-tools/browser-fill.js +70 -0
  17. package/resources/browser-tools/browser-info.js +13 -0
  18. package/resources/browser-tools/browser-nav.js +21 -22
  19. package/resources/browser-tools/browser-network.js +91 -0
  20. package/resources/browser-tools/browser-run-script.js +12 -30
  21. package/resources/browser-tools/browser-screenshot.js +22 -22
  22. package/resources/browser-tools/browser-select.js +59 -0
  23. package/resources/browser-tools/browser-snapshot.js +100 -0
  24. package/resources/browser-tools/browser-start.js +101 -85
  25. package/resources/browser-tools/browser-tabs.js +38 -0
  26. package/resources/browser-tools/browser-wait.js +50 -0
  27. package/resources/pi-agent/skills/docyrus-chrome-devtools-cli/SKILL.md +157 -46
  28. package/server-loader.js +17 -229
  29. package/server-loader.js.map +4 -4
  30. package/resources/browser-tools/browser-connect.js +0 -172
  31. package/resources/browser-tools/browser-pick.js +0 -143
  32. package/resources/pi-agent/extensions/docyrus-web-browser.ts +0 -31
  33. package/resources/pi-agent/shared/docyrusWebBrowserProtocol.ts +0 -169
  34. package/resources/pi-agent/skills/agent-browser/SKILL.md +0 -779
  35. package/resources/pi-agent/skills/agent-browser/references/authentication.md +0 -303
  36. package/resources/pi-agent/skills/agent-browser/references/commands.md +0 -295
  37. package/resources/pi-agent/skills/agent-browser/references/profiling.md +0 -120
  38. package/resources/pi-agent/skills/agent-browser/references/proxy-support.md +0 -194
  39. package/resources/pi-agent/skills/agent-browser/references/session-management.md +0 -193
  40. package/resources/pi-agent/skills/agent-browser/references/snapshot-refs.md +0 -219
  41. package/resources/pi-agent/skills/agent-browser/references/video-recording.md +0 -173
  42. package/resources/pi-agent/skills/agent-browser/templates/authenticated-session.sh +0 -105
  43. package/resources/pi-agent/skills/agent-browser/templates/capture-workflow.sh +0 -69
  44. package/resources/pi-agent/skills/agent-browser/templates/form-automation.sh +0 -62
@@ -1,172 +0,0 @@
1
- /**
2
- * Shared browser connection module.
3
- *
4
- * Reads `.docyrus/browser.json` (or DOCYRUS_BROWSER_SANDBOX env) to decide
5
- * between local Chrome on :9222 and a remote Cloudflare Browser Rendering
6
- * session. Remote sessions are cached in `.docyrus/browser-session.json` and
7
- * reused until they expire.
8
- *
9
- * Exports:
10
- * connectBrowser() → { browser, mode, session }
11
- * isSandboxMode() → boolean
12
- * getSessionInfo() → cached session object | null
13
- */
14
-
15
- import puppeteer from "puppeteer-core";
16
- import { spawnSync } from "node:child_process";
17
- import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
18
- import { join } from "node:path";
19
-
20
- const DOCYRUS_DIR = ".docyrus";
21
- const BROWSER_CONFIG_FILE = "browser.json";
22
- const BROWSER_SESSION_FILE = "browser-session.json";
23
- const LOCAL_TIMEOUT_MS = 5_000;
24
- const REMOTE_TIMEOUT_MS = 30_000;
25
- const SESSION_EXPIRY_BUFFER_MS = 30_000;
26
-
27
- function resolveDocyrusDir() {
28
- return join(process.cwd(), DOCYRUS_DIR);
29
- }
30
-
31
- function readJsonFile(filePath) {
32
- try {
33
- return JSON.parse(readFileSync(filePath, "utf8"));
34
- } catch {
35
- return null;
36
- }
37
- }
38
-
39
- function writeJsonFile(filePath, data) {
40
- const dir = join(filePath, "..");
41
- mkdirSync(dir, { recursive: true, mode: 0o700 });
42
- writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", {
43
- encoding: "utf8",
44
- mode: 0o600,
45
- });
46
- }
47
-
48
- function readBrowserConfig() {
49
- return readJsonFile(join(resolveDocyrusDir(), BROWSER_CONFIG_FILE));
50
- }
51
-
52
- function readCachedSession() {
53
- return readJsonFile(join(resolveDocyrusDir(), BROWSER_SESSION_FILE));
54
- }
55
-
56
- function writeCachedSession(session) {
57
- writeJsonFile(join(resolveDocyrusDir(), BROWSER_SESSION_FILE), session);
58
- }
59
-
60
- export function isSandboxMode() {
61
- if (process.env.DOCYRUS_BROWSER_SANDBOX === "1") {
62
- return true;
63
- }
64
- const config = readBrowserConfig();
65
- return config?.mode === "sandbox";
66
- }
67
-
68
- export function getSessionInfo() {
69
- if (!isSandboxMode()) {
70
- return null;
71
- }
72
- const cached = readCachedSession();
73
- if (!cached?.expiresAt) {
74
- return null;
75
- }
76
- if (new Date(cached.expiresAt).getTime() - SESSION_EXPIRY_BUFFER_MS <= Date.now()) {
77
- return null;
78
- }
79
- return cached;
80
- }
81
-
82
- function resolveAppId() {
83
- const config = readBrowserConfig();
84
- return config?.appId || process.env.DOCYRUS_SANDBOX_APP_ID || null;
85
- }
86
-
87
- function createRemoteSession(appId) {
88
- const result = spawnSync("docyrus", [
89
- "curl", "-X", "POST",
90
- `/api/v1/ai/sandbox/app/${appId}/createBrowserSession`,
91
- "--format", "json",
92
- ], {
93
- encoding: "utf8",
94
- env: { ...process.env },
95
- });
96
-
97
- if (result.status !== 0) {
98
- const msg = result.stderr?.trim() || "unknown error";
99
- console.error(`✗ Failed to create browser session: ${msg}`);
100
- process.exit(1);
101
- }
102
-
103
- let data;
104
- try {
105
- data = JSON.parse(result.stdout);
106
- } catch (e) {
107
- console.error("✗ Invalid session response:", e.message);
108
- process.exit(1);
109
- }
110
-
111
- const now = Date.now();
112
- const session = {
113
- ...data,
114
- createdAt: new Date(now).toISOString(),
115
- expiresAt: new Date(now + (data.keepAliveMs || 600_000)).toISOString(),
116
- };
117
-
118
- writeCachedSession(session);
119
- return session;
120
- }
121
-
122
- async function connectRemote(session) {
123
- const browser = await Promise.race([
124
- puppeteer.connect({
125
- browserWSEndpoint: session.browserWSEndpoint,
126
- headers: { Authorization: session.authHeader },
127
- }),
128
- new Promise((_, reject) =>
129
- setTimeout(() => reject(new Error("Timed out connecting to remote browser")), REMOTE_TIMEOUT_MS),
130
- ),
131
- ]);
132
- return browser;
133
- }
134
-
135
- async function connectLocal() {
136
- const browser = await Promise.race([
137
- puppeteer.connect({
138
- browserURL: "http://localhost:9222",
139
- defaultViewport: null,
140
- }),
141
- new Promise((_, reject) =>
142
- setTimeout(() => reject(new Error("timeout")), LOCAL_TIMEOUT_MS),
143
- ),
144
- ]).catch((e) => {
145
- console.error("✗ Could not connect to browser:", e.message);
146
- console.error(" Run: docyrus browser start");
147
- process.exit(1);
148
- });
149
- return browser;
150
- }
151
-
152
- export async function connectBrowser() {
153
- if (isSandboxMode()) {
154
- const appId = resolveAppId();
155
- if (!appId) {
156
- console.error("✗ Sandbox mode active but no app ID found.");
157
- console.error(" Set DOCYRUS_SANDBOX_APP_ID or configure .docyrus/browser.json");
158
- process.exit(1);
159
- }
160
-
161
- let session = getSessionInfo();
162
- if (!session) {
163
- session = createRemoteSession(appId);
164
- }
165
-
166
- const browser = await connectRemote(session);
167
- return { browser, mode: "remote", session };
168
- }
169
-
170
- const browser = await connectLocal();
171
- return { browser, mode: "local", session: null };
172
- }
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { connectBrowser } from "./browser-connect.js";
4
-
5
- const message = process.argv.slice(2).join(" ");
6
- if (!message) {
7
- console.log("Usage: browser-pick.js 'message'");
8
- console.log("\nExample:");
9
- console.log(' browser-pick.js "Click the submit button"');
10
- process.exit(1);
11
- }
12
-
13
- const { browser: b, mode, session } = await connectBrowser();
14
-
15
- const p = (await b.pages()).at(-1);
16
-
17
- if (!p) {
18
- console.error("✗ No active tab found");
19
- process.exit(1);
20
- }
21
-
22
- // Inject pick() helper into current page
23
- await p.evaluate(() => {
24
- if (!window.pick) {
25
- window.pick = async(message) => {
26
- if (!message) {
27
- throw new Error("pick() requires a message parameter");
28
- }
29
- return new Promise((resolve) => {
30
- const selections = [];
31
- const selectedElements = new Set();
32
-
33
- const overlay = document.createElement("div");
34
- overlay.style.cssText =
35
- "position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;pointer-events:none";
36
-
37
- const highlight = document.createElement("div");
38
- highlight.style.cssText =
39
- "position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);transition:all 0.1s";
40
- overlay.appendChild(highlight);
41
-
42
- const banner = document.createElement("div");
43
- banner.style.cssText =
44
- "position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1f2937;color:white;padding:12px 24px;border-radius:8px;font:14px sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.3);pointer-events:auto;z-index:2147483647";
45
-
46
- const updateBanner = () => {
47
- banner.textContent = `${message} (${selections.length} selected, Cmd/Ctrl+click to add, Enter to finish, ESC to cancel)`;
48
- };
49
- updateBanner();
50
-
51
- document.body.append(banner, overlay);
52
-
53
- const cleanup = () => {
54
- document.removeEventListener("mousemove", onMove, true);
55
- document.removeEventListener("click", onClick, true);
56
- document.removeEventListener("keydown", onKey, true);
57
- overlay.remove();
58
- banner.remove();
59
- selectedElements.forEach((el) => {
60
- el.style.outline = "";
61
- });
62
- };
63
-
64
- const onMove = (e) => {
65
- const el = document.elementFromPoint(e.clientX, e.clientY);
66
- if (!el || overlay.contains(el) || banner.contains(el)) {return;}
67
- const r = el.getBoundingClientRect();
68
- highlight.style.cssText = `position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);top:${r.top}px;left:${r.left}px;width:${r.width}px;height:${r.height}px`;
69
- };
70
-
71
- const buildElementInfo = (el) => {
72
- const parents = [];
73
- let current = el.parentElement;
74
- while (current && current !== document.body) {
75
- const parentInfo = current.tagName.toLowerCase();
76
- const id = current.id ? `#${current.id}` : "";
77
- const cls = current.className
78
- ? `.${current.className.trim().split(/\s+/).join(".")}`
79
- : "";
80
- parents.push(parentInfo + id + cls);
81
- current = current.parentElement;
82
- }
83
-
84
- return {
85
- tag: el.tagName.toLowerCase(),
86
- id: el.id || null,
87
- class: el.className || null,
88
- text: el.textContent?.trim().slice(0, 200) || null,
89
- html: el.outerHTML.slice(0, 500),
90
- parents: parents.join(" > "),
91
- };
92
- };
93
-
94
- const onClick = (e) => {
95
- if (banner.contains(e.target)) {return;}
96
- e.preventDefault();
97
- e.stopPropagation();
98
- const el = document.elementFromPoint(e.clientX, e.clientY);
99
- if (!el || overlay.contains(el) || banner.contains(el)) {return;}
100
-
101
- if (e.metaKey || e.ctrlKey) {
102
- if (!selectedElements.has(el)) {
103
- selectedElements.add(el);
104
- el.style.outline = "3px solid #10b981";
105
- selections.push(buildElementInfo(el));
106
- updateBanner();
107
- }
108
- } else {
109
- cleanup();
110
- const info = buildElementInfo(el);
111
- resolve(selections.length > 0 ? selections : info);
112
- }
113
- };
114
-
115
- const onKey = (e) => {
116
- if (e.key === "Escape") {
117
- e.preventDefault();
118
- cleanup();
119
- resolve(null);
120
- } else if (e.key === "Enter" && selections.length > 0) {
121
- e.preventDefault();
122
- cleanup();
123
- resolve(selections);
124
- }
125
- };
126
-
127
- document.addEventListener("mousemove", onMove, true);
128
- document.addEventListener("click", onClick, true);
129
- document.addEventListener("keydown", onKey, true);
130
- });
131
- };
132
- }
133
- });
134
-
135
- const selection = await p.evaluate((msg) => window.pick(msg), message);
136
-
137
- const output = { mode, selection };
138
- if (session?.devtoolsFrontendUrl) {
139
- output.devtoolsFrontendUrl = session.devtoolsFrontendUrl;
140
- }
141
- console.log(JSON.stringify(output));
142
-
143
- await b.disconnect();
@@ -1,31 +0,0 @@
1
- import { isToolCallEventType, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { buildDocyrusWebBrowserProtocolInstructions } from "../shared/docyrusWebBrowserProtocol";
3
-
4
- const DOCYRUS_BROWSER_COMMAND_PATTERN = /\bdocyrus\b(?:\s+-g|\s+--global)?\s+(?:browser|chrome)\b/;
5
- const DOCYRUS_BROWSER_BLOCK_REASON =
6
- "Docyrus Server sessions must use the docyrus-web-browser preview tools instead of `docyrus browser`.";
7
-
8
- export default function docyrusWebBrowserExtension(pi: ExtensionAPI) {
9
- pi.on("before_agent_start", async(event) => {
10
- const protocolInstructions = buildDocyrusWebBrowserProtocolInstructions();
11
- return {
12
- systemPrompt: [event.systemPrompt, protocolInstructions].filter(Boolean).join("\n\n"),
13
- };
14
- });
15
-
16
- pi.on("tool_call", async(event) => {
17
- if (!isToolCallEventType("bash", event)) {
18
- return;
19
- }
20
-
21
- const command = typeof event.input.command === "string" ? event.input.command.trim() : "";
22
- if (!command || !DOCYRUS_BROWSER_COMMAND_PATTERN.test(command)) {
23
- return;
24
- }
25
-
26
- return {
27
- block: true,
28
- reason: DOCYRUS_BROWSER_BLOCK_REASON,
29
- };
30
- });
31
- }
@@ -1,169 +0,0 @@
1
- export const DOCYRUS_WEB_BROWSER_EXTENSION_NAME = "docyrus-web-browser";
2
- export const DOCYRUS_WEB_BROWSER_TAG = "docyrus_web_browser";
3
- export const DOCYRUS_WEB_BROWSER_OPEN = `<${DOCYRUS_WEB_BROWSER_TAG}>`;
4
- export const DOCYRUS_WEB_BROWSER_CLOSE = `</${DOCYRUS_WEB_BROWSER_TAG}>`;
5
- export const DOCYRUS_WEB_BROWSER_RESULT_TAG = "docyrus_web_browser_result";
6
- export const DOCYRUS_WEB_BROWSER_RESULT_OPEN = `<${DOCYRUS_WEB_BROWSER_RESULT_TAG}>`;
7
- export const DOCYRUS_WEB_BROWSER_RESULT_CLOSE = `</${DOCYRUS_WEB_BROWSER_RESULT_TAG}>`;
8
- export const WEB_PREVIEW_CONTEXT_TOOL = "web_preview_context";
9
- export const WEB_PREVIEW_PLAYWRIGHT_TOOL = "web_preview_playwright";
10
-
11
- export const DOCYRUS_WEB_BROWSER_TOOL_NAMES = [
12
- WEB_PREVIEW_CONTEXT_TOOL,
13
- WEB_PREVIEW_PLAYWRIGHT_TOOL,
14
- ] as const;
15
-
16
- export type IDocyrusWebBrowserToolName = typeof DOCYRUS_WEB_BROWSER_TOOL_NAMES[number];
17
-
18
- export interface IDocyrusWebBrowserToolRequest {
19
- tool: IDocyrusWebBrowserToolName;
20
- input?: Record<string, unknown>;
21
- }
22
-
23
- export interface IDocyrusWebBrowserToolResponsePrompt {
24
- tool: IDocyrusWebBrowserToolName;
25
- request?: Record<string, unknown>;
26
- status: "success" | "error";
27
- output?: unknown;
28
- errorText?: string;
29
- }
30
-
31
- export interface IDocyrusWebBrowserClientToolInfo {
32
- name: IDocyrusWebBrowserToolName;
33
- description: string;
34
- inputSchema?: Record<string, unknown>;
35
- }
36
-
37
- export const DOCYRUS_WEB_BROWSER_CLIENT_TOOLS: IDocyrusWebBrowserClientToolInfo[] = [
38
- {
39
- name: WEB_PREVIEW_CONTEXT_TOOL,
40
- description:
41
- "Inspect the current Docyrus web preview state before automation. Prefer this first when preview availability or bridge state is unknown.",
42
- inputSchema: {
43
- type: "object",
44
- properties: {
45
- includeSnapshot: { type: "boolean" },
46
- },
47
- additionalProperties: false,
48
- },
49
- },
50
- {
51
- name: WEB_PREVIEW_PLAYWRIGHT_TOOL,
52
- description:
53
- "Run Playwright-style actions against the Docyrus web preview. Prefer structured steps over raw scripts.",
54
- inputSchema: {
55
- type: "object",
56
- properties: {
57
- script: { type: "string" },
58
- steps: {
59
- type: "array",
60
- items: {
61
- type: "object",
62
- properties: {
63
- action: { type: "string" },
64
- selector: { type: "string" },
65
- url: { type: "string" },
66
- value: { type: "string" },
67
- values: {
68
- type: "array",
69
- items: { type: "string" },
70
- },
71
- key: { type: "string" },
72
- attribute: { type: "string" },
73
- timeoutMs: { type: "number" },
74
- state: { type: "string" },
75
- },
76
- additionalProperties: true,
77
- },
78
- },
79
- timeoutMs: { type: "number" },
80
- stopOnError: { type: "boolean" },
81
- },
82
- additionalProperties: true,
83
- },
84
- },
85
- ];
86
-
87
- function hashString(value: string): string {
88
- let hash = 0;
89
- for (let index = 0; index < value.length; index += 1) {
90
- hash = ((hash << 5) - hash) + value.charCodeAt(index);
91
- hash |= 0;
92
- }
93
- return Math.abs(hash).toString(36);
94
- }
95
-
96
- function isRecord(value: unknown): value is Record<string, unknown> {
97
- return typeof value === "object" && value !== null && !Array.isArray(value);
98
- }
99
-
100
- export function isDocyrusWebBrowserToolName(value: unknown): value is IDocyrusWebBrowserToolName {
101
- return DOCYRUS_WEB_BROWSER_TOOL_NAMES.some((toolName) => toolName === value);
102
- }
103
-
104
- export function normalizeDocyrusWebBrowserToolRequest(value: unknown): IDocyrusWebBrowserToolRequest | undefined {
105
- if (!isRecord(value) || !isDocyrusWebBrowserToolName(value.tool)) {
106
- return undefined;
107
- }
108
-
109
- return {
110
- tool: value.tool,
111
- input: isRecord(value.input) ? value.input : undefined,
112
- };
113
- }
114
-
115
- export function serializeDocyrusWebBrowserToolRequest(request: IDocyrusWebBrowserToolRequest): string {
116
- return `${DOCYRUS_WEB_BROWSER_OPEN}\n${JSON.stringify(request, null, 2)}\n${DOCYRUS_WEB_BROWSER_CLOSE}`;
117
- }
118
-
119
- export function createDocyrusWebBrowserToolCallId(request: IDocyrusWebBrowserToolRequest): string {
120
- return `docyrus_web_browser_${hashString(JSON.stringify(request))}`;
121
- }
122
-
123
- export function parseDocyrusWebBrowserRequestFromText(text: string): IDocyrusWebBrowserToolRequest | undefined {
124
- const trimmed = text.trim();
125
- if (!trimmed.startsWith(DOCYRUS_WEB_BROWSER_OPEN) || !trimmed.endsWith(DOCYRUS_WEB_BROWSER_CLOSE)) {
126
- return undefined;
127
- }
128
-
129
- const body = trimmed.slice(DOCYRUS_WEB_BROWSER_OPEN.length, trimmed.length - DOCYRUS_WEB_BROWSER_CLOSE.length).trim();
130
- if (!body) {
131
- return undefined;
132
- }
133
-
134
- try {
135
- return normalizeDocyrusWebBrowserToolRequest(JSON.parse(body));
136
- } catch {
137
- return undefined;
138
- }
139
- }
140
-
141
- export function buildDocyrusWebBrowserProtocolInstructions(): string {
142
- return [
143
- "When you need to inspect or automate the Docyrus web preview in server-backed sessions, use the docyrus-web-browser client tools.",
144
- "Do not use `docyrus browser` or any visible Chrome DevTools workflow in this environment.",
145
- `Instead, output only a single ${DOCYRUS_WEB_BROWSER_OPEN}...${DOCYRUS_WEB_BROWSER_CLOSE} block and nothing else in the assistant message.`,
146
- "Inside that block, emit strict JSON with this shape:",
147
- "{\"tool\":\"web_preview_context\",\"input\":{\"includeSnapshot\":true}}",
148
- "or",
149
- "{\"tool\":\"web_preview_playwright\",\"input\":{\"steps\":[{\"action\":\"goto\",\"url\":\"/login\"},{\"action\":\"fill\",\"selector\":\"input[name='email']\",\"value\":\"demo@example.com\"}],\"timeoutMs\":10000,\"stopOnError\":true}}",
150
- "Call `web_preview_context` first when preview state is unknown.",
151
- "Prefer `steps` over `script` for `web_preview_playwright`.",
152
- "Supported playwright step actions are: goto, click, dblclick, hover, fill, press, select, check, uncheck, waitForSelector, waitForTimeout, textContent, getAttribute, title, url, snapshot.",
153
- "If the tool reports that the preview is unavailable or blocked by bridge/cross-origin constraints, stop and explain the exact blocker to the user.",
154
- ].join("\n");
155
- }
156
-
157
- export function formatDocyrusWebBrowserToolResponsePrompt(
158
- response: IDocyrusWebBrowserToolResponsePrompt,
159
- ): string {
160
- return [
161
- "The docyrus-web-browser client tool returned a result.",
162
- "",
163
- `${DOCYRUS_WEB_BROWSER_RESULT_OPEN}`,
164
- JSON.stringify(response, null, 2),
165
- `${DOCYRUS_WEB_BROWSER_RESULT_CLOSE}`,
166
- "",
167
- "Continue the task using this result. If the tool reported an availability or bridge blocker, explain that blocker exactly and do not claim success.",
168
- ].join("\n");
169
- }