Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ The install script verifies the release archive's sha256 and installs `sctl` int
1. **Start the daemon** and leave it running:

```bash
sctl serve
sctl serve # in a terminal, or
sctl service install # in the background, starting at login
```

2. **Pair the extensions.** `sctl connect` prints a one-time code; each code pairs one extension, so run it once
Expand Down Expand Up @@ -119,6 +120,7 @@ browser's logins; see [`docs/cli.md`](./docs/cli.md#endpoint-for-playwright-and-
| Group | What it does |
|---|---|
| `sctl serve` / `connect` / `status` / `mcp` | Run the daemon, pair an extension, show connection status, serve MCP over stdio. |
| `sctl service` | Run the daemon in the background as a user service (launchd, systemd, Task Scheduler). |
| `sctl get` / `grep` / `install` / `edit` / `enable` / `disable` / `delete` | Manage ScriptCat userscripts. |
| `sctl browsers` | List paired sctl Browser instances, or forget one. |
| `sctl tabs` / `windows` / `groups` | Tabs, windows, and tab groups. |
Expand Down
4 changes: 3 additions & 1 deletion docs/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ irm https://raw.githubusercontent.com/scriptscat/sctl/main/scripts/install.ps1 |
1. **启动 daemon** 并保持运行:

```bash
sctl serve
sctl serve # 在终端里运行,或者
sctl service install # 在后台运行,登录后自动启动
```

2. **配对扩展。** `sctl connect` 打印一次性配对码;一个码只能配对一个扩展,每个扩展各运行一次。
Expand Down Expand Up @@ -109,6 +110,7 @@ ln -s "$PWD/skills/sctl" ~/.claude/skills/sctl
| 命令组 | 用途 |
|---|---|
| `sctl serve` / `connect` / `status` / `mcp` | 运行 daemon、配对扩展、查看连接状态、通过 stdio 提供 MCP。 |
| `sctl service` | 以用户服务的形式在后台运行 daemon(launchd、systemd、任务计划程序)。 |
| `sctl get` / `grep` / `install` / `edit` / `enable` / `disable` / `delete` | 管理 ScriptCat 用户脚本。 |
| `sctl browsers` | 列出已配对的 sctl Browser 实例,或忘记其中一个。 |
| `sctl tabs` / `windows` / `groups` | 标签页、窗口和标签组。 |
Expand Down
8 changes: 6 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ CLI verbs (sctl get / edit / install / browsers / tabs / windows / page / debug
`/control/*` HTTP/JSON control API on the daemon's listener — same port as the extensions' WS surface, separate
path, authenticated with the control token described in [threat-model.md](./threat-model.md#4-the-control-channel-internal-local-connection-in-detail).
Frontends never start `serve`: run it
explicitly in the foreground or let an external system service manager own its lifecycle.
explicitly in the foreground or let the user's system service manager own its lifecycle — `sctl service`
(`internal/client/service`) writes that service definition and drives the manager's own commands; it talks to the
daemon only through the health check and the control token, to tell whether the service's daemon came up.

The daemon accepts two kinds of extension connection at once: exactly one active ScriptCat connection (a new
one replaces the previous one), and any number of paired `sctl Browser` instances connecting simultaneously,
Expand Down Expand Up @@ -140,6 +142,7 @@ cmd/sctl/main.go # cobra entry point (unwraps ExitError → os.Exit)
internal/cli/ # subcommand definitions; spans both sides, hence top level
cli.go # root command, global flags (-o/--output, --log-level), output helpers
serve.go # bootstraps the cago app and mounts the daemon Component
service.go # sctl service install|uninstall|start|stop|restart|enable|disable|status
mcp.go # sctl mcp (serves all tools; --name is an audit label)
connect.go status.go version.go
get.go grep.go # read verbs
Expand Down Expand Up @@ -175,13 +178,14 @@ internal/daemon/ # ── sctl serve side ──
internal/client/ # ── sctl mcp / CLI verb side ──
control/ # control API client, shared DTOs, control token
mcpserver/ # go-sdk stdio MCP server: all tools (flat trust), progress while waiting
service/ # user service definitions (launchd, systemd --user, Task Scheduler) and their commands

internal/pkg/ # ── shared by both sides ──
protocol/ # protocol.json authority plus generated language and business-schema artifacts
protocolschema/ # JSON-RPC parsing and business-schema validation at the untrusted WS boundary
audit/ # daemon-side security events (the Event type crosses the control API, hence shared)
paths/ # data directory and derived paths
logging/ # unified zap logging (stderr + <dataDir>/logs/*.log, never stdout)
logging/ # unified zap logging (stderr + <dataDir>/logs/*.log; files only under sctl service; never stdout)
fsutil/ # atomic file writes

extension/ # ── sctl Browser, the second extension kind (MV3, pnpm/Vite/React) ──
Expand Down
48 changes: 47 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ command line can use the skill in [`../skills/sctl/`](../skills/sctl/SKILL.md).

| Command | Purpose |
|---|---|
| `sctl serve` | Run the local bridge daemon. |
| `sctl serve` | Run the local bridge daemon in the foreground. |
| `sctl service install\|uninstall\|start\|stop\|restart\|enable\|disable\|status` | Run `sctl serve` in the background as a user service; see [Background service](#background-service-sctl-service). |
| `sctl connect` | Open a one-time enrollment window for ScriptCat or sctl Browser. |
| `sctl mcp [--name <label>]` | Serve ScriptCat and sctl Browser tools over stdio MCP. |
| `sctl status` | Show daemon and extension connection status. |
Expand Down Expand Up @@ -127,6 +128,51 @@ Chrome's retention limit), and note on stderr when more remain.

`--since` and `--until` accept an RFC 3339 time or a duration ago such as `7d`, `12h`, or `30m`.

## Background service: `sctl service`

`sctl service install` registers `sctl serve` with the current user's service manager, so the daemon starts at login;
launchd and systemd also restart it after a crash. It needs no administrator rights and installs one service per user:

| Platform | Service manager | Definition |
|---|---|---|
| macOS | launchd LaunchAgent | `~/Library/LaunchAgents/com.scriptscat.sctl.plist` |
| Linux | systemd user instance | `~/.config/systemd/user/sctl.service` (under `$XDG_CONFIG_HOME` when set); runs while you are logged in |
| Windows | Task Scheduler, triggered at your logon | task `\sctl`; a console window flashes briefly each time it starts. Task Scheduler does not restart a daemon that crashes after starting: run `sctl service start` |

- **`install`** writes the definition, enables it at login, and starts it. The service runs the `sctl` binary you ran
`install` with (the one on `PATH` when it is the same file, so a Homebrew upgrade keeps working) together with the
`--listen-address` (always, the default included), the `--data-dir` (as an absolute path, also when it comes from
`SCTL_DATA_DIR`), and a non-default `--log-level` in effect for that command, so pass the same ones you use with the
other commands, including the other `sctl service` commands. Running it again rewrites the definition, enables it
if it was disabled, and restarts the service, which is how to pick up a new binary, path, or flag. It refuses when
the service is not installed yet but a daemon answers at the address, even one using another data directory: stop
that daemon first, or the service keeps failing on the busy port. When the service manager cannot take a first
install (for example `systemctl --user` with no user session bus, as under `sudo`), it removes the definition it
just wrote.
- **`start`** and **`restart`** start the installed service; **`stop`** stops it until you start it again or log in
next. All three exit 3 when the service is not installed. `start` does nothing when a daemon is already reachable
at the address, whether it is the service or one you started yourself.
- **`disable`** stops the service and keeps it from starting at login; **`enable`** undoes that and starts it (doing
nothing more when a daemon is already reachable, like `start`). While the service is disabled, `start` and
`restart` exit 3 and point to `enable`. Both exit 3 when the service is not installed.
- **`install`**, **`start`**, **`restart`**, and **`enable`** wait up to 10 seconds for the newly started daemon to
become reachable (recognized by the fresh control token it writes, so another daemon holding the port does not
count) and exit 3, naming the log directory, when it does not. On Windows, stopping a running instance first waits
up to another 10 seconds for it to release its address.
- **`uninstall`** stops the service and removes the definition; it succeeds when nothing is installed.
- **`status`** reports whether the service is installed, where its definition is, whether it starts at login, and
whether a daemon is reachable at the address. It exits 0 either way; add `-o json` for scripts (the other `service`
commands print text only).

On macOS, a process started by launchd cannot read `~/Documents`, `~/Desktop`, or `~/Downloads` without a privacy
permission and hangs there instead of starting, so keep the `sctl` binary and the data directory outside those folders
(the install script's `~/.local/bin` and Homebrew's prefix are fine).

Under the service, the daemon writes its log only to the files in `<data dir>/logs/`; the service manager receives
nothing but crash output (macOS: `<data dir>/logs/service.stderr.log`, Linux: the user journal, Windows: not
captured). Use either the
service or `brew services`, not both: they run the same daemon on the same port.

## Page automation: `sctl page`

### Target tab and debugger attachment
Expand Down
4 changes: 2 additions & 2 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ sctl serve
```

`sctl serve` is the only process that owns the WebSocket connection to ScriptCat. CLI commands and `sctl mcp`
never start it automatically. For long-running use, configure the operating system's user service manager to
run this exact command; service-manager-specific installation is outside this guide.
never start it automatically. For long-running use, `sctl service install` runs it in the background as a user
service that starts at login; see [`cli.md`](./cli.md#background-service-sctl-service).

Before enrollment, this command should reach the daemon and report that no extension is connected:

Expand Down
Loading
Loading