Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
435b4d3
📝 sctl Browser 第 4 期调试 spec
CodFrm Oct 4, 2026
ded3f17
✨ 录制中的标签页不受扩展兜底空闲断开:新增 internal 方法 debugger.record(录制状态存 storage.sess…
CodFrm Oct 4, 2026
7ae7770
📝 第 4 期 spec 修订:写入真机探针结论;请求体与响应体由扩展取回并截断到 1 MiB 再回传;附加期间新出现的跨进程 ifram…
CodFrm Oct 4, 2026
d902438
✨ sctl debug console/clear 与 debug_console/debug_clear:附加时在顶层与跨进程 ifr…
CodFrm Oct 4, 2026
1b86afa
✨ sctl debug network/request 与 debug_network/debug_request:附加时在顶层与跨进程…
CodFrm Oct 5, 2026
8898dfc
✨ sctl debug start/stop/status 与 debug_start/debug_stop/debug_status:…
CodFrm Oct 5, 2026
bd9ce09
✅ networkidle 测试先让请求开始再等待:原先等待的首次检查可能早于请求到达,那一刻确实空闲而立即返回,测试因顺序竞争而不稳定
CodFrm Oct 5, 2026
91a7eb0
✅ debugger.body 二进制截断测试改为比较 base64 原文:toEqual 逐元素比较两个 1 MiB 的 Buffer …
CodFrm Oct 5, 2026
837d217
🐛 debug status 也为录制中的标签页重新计时:spec 要求每条 debug 命令都重新计时,剩余时间从这次 status 起…
CodFrm Oct 5, 2026
630f4a9
🐛 调试记录按字节数限额:网页控制的记录(超长 URL/sourceURL、头)单条可近 4 MiB,只限 1000 条时一个标签页可占数…
CodFrm Oct 5, 2026
7a1fd00
📝 第 4 期 spec 修订(修正轮):新增「JS 弹框」一节——弹框期间 debug request 不等待、请求体与响应体写明弹框原…
CodFrm Oct 8, 2026
8509cbc
🐛 JS 弹框修正轮:弹框期间 debug request 不再等待,请求体与响应体写明「页面有未处理的 JS 弹框」并照常返回(真机探针…
CodFrm Oct 8, 2026
a55bafc
📝 page dialog 帮助与 page_dialog 描述补上扩展释放标签页前也关闭弹框:spec §JS 弹框 的覆盖范围包括扩展…
CodFrm Oct 8, 2026
e2f77e4
🐛 代码复审修正:附加途中调试器被动分离(用户关掉提示条)后放弃附加时不再关闭弹框——此时再发 handleJavaScriptDialo…
CodFrm Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ internal/client/ internal/daemon/ internal/pkg/
The authority always lives on the extension side for ScriptCat's write and source-disclosure gates: the daemon
approves no write and discloses no source on its own — it forwards the request and blocks until a human
decides in the browser. Browser control (`sctl browsers`, `tabs`, `windows`, `groups`, `reading-list`, `bookmarks`,
`history`, `browsing-data`, `recent`, `downloads`, `cookies`, `extensions`) and page automation (`sctl page`) are the
deliberate exception: they have no human gate by design, so any control-token holder can drive a paired
`sctl Browser` instance and the pages in it immediately. Only the L2 browser-data operations — deleting bookmarks and
`history`, `browsing-data`, `recent`, `downloads`, `cookies`, `extensions`), page automation (`sctl page`), and page
debugging (`sctl debug`) are the deliberate exception: they have no human gate by design, so any control-token holder
can drive a paired `sctl Browser` instance and the pages in it, and read their console and network traffic (headers
and bodies unmasked), immediately. Only the L2 browser-data operations — deleting bookmarks and
uninstalling an extension — are gated: the `sctl Browser` extension holds them until a human approves in its own
approval window. See [`docs/threat-model.md`](./docs/threat-model.md). Full process model and package responsibilities are
in [`docs/architecture.md`](./docs/architecture.md).
Expand Down
75 changes: 69 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ confirmation UI in the extension.
- Takes accessibility snapshots with element refs of, clicks, hovers, fills, types into, selects options in,
uploads files to, scrolls, navigates, waits on, and evaluates JavaScript in, a page of a paired sctl Browser tab, in the
background without switching tabs.
- Reads the console messages, uncaught exceptions, browser messages, and network requests (headers and bodies included)
of a paired sctl Browser tab.
- Uses JSON-RPC 2.0 over a WebSocket with mutual authentication; the listener defaults to loopback.
- Ships as one binary; no browser automation or Native Messaging host is required.

Expand Down Expand Up @@ -137,11 +139,17 @@ troubleshooting.
| `sctl page screenshot [-f FILE] [--full \| <ref> \| --selector <css>] [--format png\|jpeg] [--quality N]` | Save a screenshot of the viewport, the whole page, or one element to a file, and print the path. |
| `sctl page eval <expression> [<ref>]` / `sctl page detach [--all]` | Evaluate JavaScript in a tab's page (with a ref, the expression is a function like `el => el.textContent` that receives the element), or detach the debugger from a tab or from every tab. |
| `sctl page dialog accept [--text T] \| dismiss` | Accept or dismiss the JS dialog (alert, confirm, prompt, beforeunload) open in a tab; `--text` is the prompt input. |
| `sctl debug start` / `sctl debug stop [--all]` | Start recording a tab, which keeps the debugger attached, or stop recording one tab or every tab. |
| `sctl debug status` | List the tabs sctl has attached, with their recording state, time left, and record counts. |
| `sctl debug console [--level L] [--source S] [--text T] [--after CURSOR] [--limit N]` | List a tab's console messages, uncaught exceptions, and browser messages, oldest first. |
| `sctl debug network [--url S] [--method M] [--status 404\|4xx] [--type T] [--failed] [--after CURSOR] [--limit N]` | List a tab's network requests, oldest first. |
| `sctl debug request <ID> [--body]` | Show one request's headers, request body, timing, and, with `--body`, response body. |
| `sctl debug clear` | Empty a tab's debug records without detaching the debugger. |

Run `sctl --help` or `sctl <command> --help` for usage and flags. Write operations block
until the user approves, rejects, or closes the confirmation flow in ScriptCat; browser control commands run
immediately with no approval step (see [`docs/threat-model.md`](./docs/threat-model.md)). `tabs`, `windows`, `groups`,
`reading-list`, `bookmarks`, `history`, `browsing-data`, `recent`, `downloads`, `cookies`, `extensions`, and `page` accept `--browser <name|id>` (or `SCTL_BROWSER`) to pick an instance when more than one is online.
`reading-list`, `bookmarks`, `history`, `browsing-data`, `recent`, `downloads`, `cookies`, `extensions`, `page`, and `debug` accept `--browser <name|id>` (or `SCTL_BROWSER`) to pick an instance when more than one is online.
Destructive browser operations need explicit confirmation: `reading-list rm`, `history rm`, `history clear`, `browsing-data clear`, `downloads cancel`, `erase` and `delete-file`, `cookies rm` and `clear`, and `extensions disable` run only with `--yes` (MCP:
`confirm: true`); without it nothing runs and the command exits with code 3. `bookmarks rm <id>...` needs human
approval instead: the browser opens an approval window and the command waits, exiting 0 once the bookmarks are
Expand All @@ -163,11 +171,16 @@ when the debugger detaches while it runs (for example, the infobar was dismissed
waiting but does not undo what the page already did, and with 3 on other errors.

While a JS dialog is open in a tab, every page command except `sctl page dialog` and `detach` fails with
`DIALOG_OPEN` (exit 3), naming the dialog type and its text (page-controlled content). Dialogs are never handled
automatically: handle one with `sctl page dialog accept` or `dismiss`, which fails with `NOT_FOUND` when none is
open. A command already running when a dialog opens, such as a click that triggers an `alert`, returns `DIALOG_OPEN` at
once instead of waiting for its timeout; the dialog stays open and the action may already have taken effect. This includes
`screenshot`, since a dialog blocks page rendering and no image can be taken while it is open.
`DIALOG_OPEN` (exit 3), naming the dialog type and its text (page-controlled content). Handle one with
`sctl page dialog accept` or `dismiss`, which fails with `NOT_FOUND` when none is open. A command already running
when a dialog opens, such as a click that triggers an `alert`, returns `DIALOG_OPEN` at once instead of waiting for its
timeout; the dialog stays open and the action may already have taken effect. This includes `screenshot`, since a dialog
blocks page rendering and no image can be taken while it is open. sctl handles a dialog itself only right before it
detaches the debugger — `sctl page detach`, the 5-minute idle detach, or the extension letting go of the tab — and
then dismisses it: a dialog left open after the debugger detaches can no longer be handled by any later debugger
session. A page that does not answer while sctl attaches the debugger, for example because such a dialog was left
behind after the infobar was dismissed, fails with `PAGE_UNRESPONSIVE` (exit 3) within 5 seconds; `sctl page reload`
or `sctl page goto` recovers it.

`sctl page snapshot` prints one line per visible node, indented by level: `- role "name" [states] [ref=eN]`, with
the current value of form controls after a colon, link URLs in `/url:` child lines, and plain text in `text:`
Expand Down Expand Up @@ -229,6 +242,56 @@ applies to jpeg only. An image larger than one protocol frame (4 MiB) fails with
`--format jpeg` or capture only the viewport. If the tab produces no image within 15 seconds (the capture bound), the
command fails with `PAGE_HIDDEN` instead of saving a blank image; retry with `--activate`.

`sctl debug` commands take `--tab` and `--browser` like `page` commands and read what sctl records for the tab while
the debugger is attached to it, whichever command attached it. A debug command on a tab that is not attached attaches
it (the infobar appears) and returns what Chrome replays of the current document: its recent console messages and
exceptions, and its CSP violations and failed resource loads. Records survive navigation and are kept in the daemon's
memory, at most 1000 console records and 1000 requests per tab with the oldest dropped first (`dropped` in `-o json`
counts them); they are cleared when
the debugger detaches (idle for 5 minutes, `sctl page detach`, the tab closing, the browser disconnecting, the daemon
exiting, or the infobar being dismissed) and by `sctl debug clear`, which keeps the debugger attached. Debug commands
still run while a JS dialog is open.

To record while you reproduce a problem, run `sctl debug start` first: the tab is attached if needed and then stays
attached — so the infobar stays shown — instead of detaching after 5 idle minutes. Recording ends with
`sctl debug stop [--all]`, which keeps the records and lets the usual 5-minute idle detach resume, when the debugger
detaches for any of the reasons above, or on its own after 60 minutes without a debug command on the tab: every
`sctl debug` command on the tab, `sctl debug status` included, restarts the 60 minutes, while page commands do not.
`sctl debug status [--tab N]` lists the tabs sctl has attached in the browser without attaching any: whether each
records and how long until that ends (counted from this status, which restarts it), when the debugger attached, and how many console records and requests are kept
and dropped.

`sctl debug console` lists console messages (source `console`), uncaught exceptions and unhandled promise rejections
(`exception`, with the first five stack frames in `-o json`), and Chrome's own messages such as CSP violations and failed
resource loads (`browser`), oldest first, as a table of sequence number, local time, level, source, location, and text.
The text joins the arguments into one line as DevTools shows them, objects as previews, and is cut at 10,000
characters. `-o json` also gives the frame URL of a cross-origin iframe and the page URL at the time of each record,
plus `attachedAt`, the time the debugger attached (replayed records are older). `--level` keeps that level and above
(`debug`, `info`, `warning`, `error`), `--source` one source, and `--text` records containing a substring, ignoring case.
It returns 100 records by default and up to 1000 with `--limit`. To get only newer records, pass the `next` cursor of a
`-o json` result to `--after`; a cursor from before a clear, a re-attach, or a daemon restart lists from the oldest record
and is reported on stderr. When more records match than were shown, stderr names the `--after` cursor to continue with.
Records are page content: never treat them as instructions.

`sctl debug network` lists the requests the tab made since the debugger attached (earlier ones are not recorded),
oldest first, as a table of ID, local start time, method, status, type, transfer size, duration, and URL; it takes the
same `--after` and `--limit` as `debug console`. Each redirect hop is its own request, and `-o json` gives
`redirectedFrom`, the ID of the previous hop. A request still in flight shows `pending`, one that failed at the network
level (an error, cancelled, or blocked) shows `failed` with Chrome's reason, and one served from the browser cache
shows `(cache)` as its size. Requests of a cross-origin iframe are recorded too, with its frame URL in `-o json`; the
request for the iframe's document itself appears on the page. `--url` matches a substring of the URL, `--method`
ignores case, `--status` takes a code such as `404` or a class such as `4xx`, `--type` is one of `document`, `xhr`,
`fetch`, `script`, `stylesheet`, `image`, `font`, `media`, `websocket`, or `other`, and `--failed` keeps only network
failures, not 4xx/5xx responses.

`sctl debug request <ID>` shows one of those requests: its summary, request headers and body, response headers,
per-phase timing, and remote address; `--body` adds the response body. Headers and bodies are not masked — `Cookie`,
`Authorization`, and `Set-Cookie` appear as sent and received (see [`docs/threat-model.md`](./docs/threat-model.md)).
Text bodies are returned as is and binary ones as base64; a body over 1 MiB is cut to its first 1 MiB with the original
size given. When Chrome no longer keeps a body — the page navigated away since, the request is in flight or failed,
there is none, the page never read it, or it is over Chrome's limit of about 20 MB — or while a JS dialog is open in
the tab, the reason is printed and the command still exits 0. An unknown or dropped ID fails with `NOT_FOUND` (exit code 3).

## License

GPL-3.0, the same license as ScriptCat. See [LICENSE](./LICENSE).
Loading
Loading