tabctl 0.6.0-rc.1 → 0.6.0-rc.10

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # tabctl
2
2
 
3
- Every open tab is a thread you forgot to pull. Tabctl finds them all.
3
+ Every open tab is a thread you forgot to pull. Tabctl helps you query and change them safely.
4
4
 
5
- A command-line instrument for browser tab orchestration list, search, group, archive, close, undo wired into Edge or Chrome through a native messaging bridge. Built for humans who hoard tabs and the AI agents who clean up after them.
5
+ A command-line instrument for browser tab orchestration, now centered on a GraphQL API exposed through `tabctl query` and `tabctl schema`, plus `ping` and `history` convenience commands. Built for humans who hoard tabs and the AI agents who clean up after them.
6
6
 
7
7
  ## Install
8
8
 
@@ -35,7 +35,7 @@ npx skills add https://github.com/ekroon/tabctl --skill tabctl -a opencode -a gi
35
35
 
36
36
  Nothing leaves your machine. No cloud. No telemetry. Just a socket between your terminal and your browser, quiet as rain on neon.
37
37
 
38
- Every mutation is undoable — `tabctl undo` rewinds closes, archives, and group changes like they never happened. A configurable policy layer shields pinned tabs and protected domains from accidental destruction. You pull the trigger; tabctl keeps the safety on until you mean it.
38
+ Every mutation is undoable — `undoAction` rewinds closes, archives, and group changes like they never happened. A configurable policy layer shields pinned tabs and protected domains from accidental destruction. You pull the trigger; tabctl keeps the safety on until you mean it.
39
39
 
40
40
  ## What You Can Say
41
41
 
@@ -129,53 +129,66 @@ Optional setup release overrides:
129
129
 
130
130
  ### 3. Verify and explore
131
131
 
132
- <!-- test: "ping sends ping action", "list sends list action" -->
133
132
  ```bash
134
- tabctl ping # check connection + runtime version sync
135
- tabctl list # see your open tabs
133
+ tabctl ping
134
+ tabctl query '{ tabs { total items { tabId title url } } }'
135
+ tabctl schema
136
136
  ```
137
137
 
138
138
  > **Multiple browsers?** See [Multi-Browser Setup](#multi-browser-setup) for running tabctl with both Chrome and Edge.
139
139
 
140
140
  ## Commands
141
141
 
142
- <!-- test: "list sends list action", "analyze passes tab ids and progress option", "inspect passes signal options", "close without confirm fails", "report format md returns markdown content", "undo sends undo action with txid" -->
143
142
  | Command | Description |
144
143
  |---------|-------------|
145
- | `tabctl list` | List open tabs and groups |
146
- | `tabctl open --url <url> --group <name>` | Open tabs into a group (reuses existing, skips duplicates) |
147
- | `tabctl analyze` | Find stale or duplicate tabs |
148
- | `tabctl inspect --tab <id>` | Extract page metadata or CSS selectors |
149
- | `tabctl group-gather` | Merge duplicate groups with the same name |
150
- | `tabctl close --tab <id>` | Close tabs with full undo support |
151
- | `tabctl report` | Generate reports in JSON, Markdown, or CSV |
152
- | `tabctl undo` | Revert the last action |
144
+ | `tabctl query '<GRAPHQL>'` | Query and mutate browser state through GraphQL |
145
+ | `tabctl schema` | Print the GraphQL schema |
146
+ | `tabctl ping` | Check host/browser connectivity and runtime version sync |
147
+ | `tabctl history` | Show recent undo history entries |
148
+ | `tabctl setup`, `doctor`, `policy`, `profile-*` | Local/admin profile management |
153
149
 
154
- See [CLI.md](CLI.md) for the full command reference, options, and examples.
150
+ Read-only browser features include:
151
+ - `inspectTabs` for page metadata and selector-based reads
152
+ - `readTabs` for page HTML → Markdown conversion with Kreuzberg preprocessing and per-tab diagnostics
153
+ - `reportTabs`, `captureScreenshots`, and browser-state history queries for summaries and context
155
154
 
156
- ## Screenshot output
157
- When `--out` is omitted, screenshots are written to `./.tabctl/screenshots/<timestamp>` and the JSON response includes `writtenTo`.
155
+ See [CLI.md](CLI.md) for the full command reference, options, and examples.
158
156
 
159
- ## Agent workflow (context -> selector)
160
- Use screenshots only when you need visual context, then extract selectors with `inspect`.
157
+ ## GraphQL examples
161
158
 
162
- 1) Capture context (full page tiles):
163
- <!-- test: "screenshot passes capture options" -->
164
159
  ```bash
165
- tabctl screenshot --tab <id> --mode full
166
- ```
160
+ # Query tabs and groups
161
+ tabctl query '{ windows { windowId groups { groupId title } tabs { tabId title url groupTitle } } }'
167
162
 
168
- 2) Identify the element visually, then extract its selector:
169
- <!-- test: "inspect passes signal options" -->
170
- ```bash
171
- tabctl inspect --tab <id> --signal selector --selector '{"name":"target","selector":".your-selector"}'
172
- ```
163
+ # Analyze stale and duplicate tabs
164
+ tabctl query '{ analyze(windowId: 123, staleDays: 30) { totalTabs duplicateTabs staleTabs } }'
173
165
 
174
- 3) If you need an absolute URL, set `--selector-attr href-url` or set `attr` to `href-url`/`src-url`:
175
- <!-- test: "inspect passes selector attr" -->
176
- ```bash
177
- tabctl inspect --tab <id> --signal selector --selector '{"name":"link","selector":"a[href]","attr":"href-url"}'
178
- tabctl inspect --tab <id> --signal selector --selector "link=a[href]" --selector-attr href-url
166
+ # Inspect page metadata
167
+ tabctl query 'query { inspectTabs(tabIds: [456], signals: ["page-meta"]) { entries { tabId signals { name valueJson } } } }'
168
+
169
+ # Inspect selectors with typed attrs and filters
170
+ tabctl query 'query { inspectTabs(windowId: 123, selectors: [{ name: "prices", selector: ".price", attr: "text", all: true }, { name: "buy_now_visible", selector: "button.buy-now", attr: "visible" }, { name: "buy_now_style", selector: "button.buy-now", attr: "styles", styleProps: ["color", "background-color"] }, { name: "review_count", selector: ".review", attr: "count" }, { name: "email_value", selector: "input[type=email]", attr: "value" }]) { entries { tabId url signals { name valueJson } } } }'
171
+
172
+ # Read tab content as Markdown (Kreuzberg preprocessing by default)
173
+ tabctl query 'query { readTabs(windowId: 123, extract: true, maxChars: 30000) { entries { tabId title url markdown chars truncated extracted cached status emptyReason diagnostics { source sourceHtmlChars sourceTextChars documentReadyState truncatedHtml cachedAt cacheAgeMs } error } } }'
174
+
175
+ # Generate reports
176
+ tabctl query '{ reportTabs(windowId: 123) { entries { tabId title url description } } }'
177
+
178
+ # Capture screenshots
179
+ tabctl query 'query { captureScreenshots(tabIds: [456], mode: "viewport") { entries { tabId tiles { index width height } } } }'
180
+
181
+ # Inspect persisted browser-state history for future restore tooling
182
+ tabctl query 'query { latestBrowserState { snapshotId reason groups { logicalGroupId title browserGroupId tabUrls } } }'
183
+ tabctl query 'query { browserStateHistory(limit: 10) { snapshotId recordedAt reason eventCount eventKinds } }'
184
+ tabctl query 'query { browserStateGroupHistory(title: "Research", limit: 10) { snapshotId logicalGroupId title browserGroupId tabUrls } }'
185
+
186
+ # Open tabs in a new grouped window
187
+ tabctl query 'mutation { openTabs(urls: ["https://example.com"], group: "Research", newWindow: true) { windowId groupId tabs { tabId url } } }'
188
+
189
+ # Close tabs with undo support
190
+ tabctl query 'mutation { closeTabs(tabIds: [456], confirm: true) { txid closedTabs } }'
191
+ tabctl query 'mutation { undoAction(latest: true) { txid summary } }'
179
192
  ```
180
193
 
181
194
  ## Agent skills
@@ -230,34 +243,38 @@ See [CLI.md](CLI.md#configuration) for full details.
230
243
  ## Runtime state
231
244
  - Socket: `<dataDir>/tabctl.sock` (default: `~/.local/state/tabctl/tabctl.sock`)
232
245
  - Undo log: `<dataDir>/undo.jsonl` (default: `~/.local/state/tabctl/undo.jsonl`)
246
+ - Browser-state history DB: `<dataDir>/state.db` (default: `~/.local/state/tabctl/state.db`)
233
247
  - Profile registry: `<configDir>/profiles.json`
234
- - WSL TCP port file: `<dataDir>/tcp-port` (written by the Windows host)
248
+ - Windows pipe endpoint file: `<dataDir>/pipe-endpoint`
235
249
 
236
250
  ## Windows + WSL transport
237
251
 
238
- On Windows, the host exposes a dual endpoint model:
252
+ On Windows, the host exposes a named-pipe endpoint model:
239
253
  - Windows native clients use a named pipe endpoint (`\\.\pipe\tabctl-<hash>`).
240
- - WSL/Linux clients use `tcp://127.0.0.1:<port>`, with the host writing `<dataDir>/tcp-port`.
254
+ - WSL/Linux clients use a Windows named-pipe bridge; the Windows host publishes `<dataDir>/pipe-endpoint`, and the WSL CLI relays through `powershell.exe`.
241
255
 
242
256
  WSL endpoint discovery (CLI):
243
- 1. `TABCTL_SOCKET` (explicit endpoint); if this is a pipe endpoint in WSL, CLI still prefers discovered TCP.
244
- 2. `TABCTL_TCP_PORT` (forces `127.0.0.1:<port>`).
245
- 3. `tcp-port` file discovery from resolved data dir (and equivalent `/mnt/c/Users/*/.../tabctl/.../tcp-port` locations).
246
- 4. Fallback: `tcp://127.0.0.1:38000`.
257
+ 1. `TABCTL_SOCKET` (explicit endpoint).
258
+ 2. `pipe-endpoint` file discovery from resolved data dir (and equivalent `/mnt/c/Users/*/.../tabctl/.../pipe-endpoint` locations).
247
259
 
248
- Relevant knobs: `TABCTL_SOCKET`, `TABCTL_TCP_PORT`, `TABCTL_PROFILE`, `TABCTL_DATA_DIR`, `TABCTL_STATE_DIR`, `TABCTL_CONFIG_DIR`.
260
+ WSL named-pipe mode:
261
+ - This is the default WSL transport now.
262
+ - The CLI discovers the pipe endpoint from `<dataDir>/pipe-endpoint` (including the mirrored `/mnt/c/Users/*/...` candidate paths used for other WSL bridge files).
263
+ - TCP is disabled for the WSL transport path.
264
+
265
+ Relevant knobs: `TABCTL_SOCKET`, `TABCTL_PROFILE`, `TABCTL_DATA_DIR`, `TABCTL_STATE_DIR`, `TABCTL_CONFIG_DIR`.
249
266
 
250
267
  ## Troubleshooting (setup/ping on Windows + WSL)
251
268
 
252
269
  - `tabctl setup` fails with `Windows setup verification failed`: check `data.verification.reason` in JSON output (`ping-timeout`, `socket-not-found`, `socket-refused`, `ping-not-ok`, `extension-id-mismatch`), then follow printed manual steps.
253
270
  - Runtime ID mismatch (`extension-id-mismatch`): compare expected vs runtime IDs from setup output, then rerun setup with the runtime ID shown by `edge://extensions` / `chrome://extensions`:
254
271
  - `tabctl setup --browser <edge|chrome> --extension-id <runtime-id>`
255
- - Runtime command runs can auto-sync extension files when host/extension versions drift; rerun `tabctl reload` if the browser does not pick up changes immediately.
272
+ - Runtime command runs can auto-sync extension files when host/extension versions drift; rerun `tabctl query 'mutation { reloadExtension { reloading } }'` if the browser does not pick up changes immediately.
256
273
  - For local release-like testing while developing, force runtime sync behavior with `TABCTL_AUTO_SYNC_MODE=release-like`.
257
274
  - Disable runtime sync entirely with `TABCTL_AUTO_SYNC_MODE=off`.
258
- - `tabctl ping --json` is the canonical runtime version check (`versionsInSync`, `hostBaseVersion`, `baseVersion`).
259
- - Version metadata is intentionally health-only: regular command payloads (`open`, `list`, etc.) do not include version fields.
260
- - `tabctl ping` returns connect errors (`ENOENT`, `ECONNREFUSED`, timeout): ensure extension is loaded and active, rerun `tabctl setup`, and in WSL verify `TABCTL_TCP_PORT` or `<dataDir>/tcp-port` matches a listening localhost port.
275
+ - `tabctl ping --json` is a host connectivity/health check; use it to confirm the native host is reachable and healthy.
276
+ - Version metadata is intentionally health-only: regular GraphQL payloads do not include version fields unless you explicitly query health surfaces, which may expose fields such as `versionsInSync`, `hostBaseVersion`, and `baseVersion`.
277
+ - `tabctl ping` returns connect errors (`ENOENT`, `ECONNREFUSED`, timeout): ensure extension is loaded and active, rerun `tabctl setup`, and in WSL verify the profile data dir contains a current `pipe-endpoint` file.
261
278
  - `tabctl doctor --fix --json` includes per-profile connectivity diagnostics in `data.profiles[].connectivity`; if ping remains unhealthy after local repairs, follow `manualSteps`.
262
279
 
263
280
  Local release-like sync test recipe:
@@ -266,9 +283,9 @@ Local release-like sync test recipe:
266
283
  tabctl setup --browser edge --extension-id <extension-id> --release-tag v0.5.2
267
284
 
268
285
  # 2) Run the current binary with forced release-like auto-sync
269
- TABCTL_AUTO_SYNC_MODE=release-like cargo run --manifest-path rust/Cargo.toml -p tabctl -- list --all
286
+ TABCTL_AUTO_SYNC_MODE=release-like cargo run --manifest-path rust/Cargo.toml -p tabctl -- query '{ tabs { total } }'
270
287
 
271
- # 3) Verify host/extension base versions are back in sync
288
+ # 3) Verify host connectivity/health after auto-sync
272
289
  tabctl ping --json
273
290
  ```
274
291
 
@@ -293,7 +310,7 @@ tabctl profile-list
293
310
  tabctl profile-switch edge
294
311
 
295
312
  # One-off command with different profile
296
- tabctl list --profile chrome-work
313
+ tabctl --profile chrome-work query '{ tabs { total } }'
297
314
  ```
298
315
 
299
316
  ### Custom Chrome Profile Directories
@@ -320,8 +337,7 @@ Policy is shared across all profiles.
320
337
  ## Security
321
338
  - The native host is locked to your extension ID.
322
339
  - All data stays local; no external API keys are used.
323
- - TCP connections (used for WSL ↔ Windows communication) are secured with a per-session auth token. The host generates a random token on startup; the CLI reads it automatically. See [CLI.md](CLI.md) for details.
324
- - TCP transport is available on all platforms via `TABCTL_HOST_TCP=1` (host) and `TABCTL_TRANSPORT=tcp` (CLI). All TCP connections are authenticated. See [CLI.md](CLI.md) for details.
340
+ - WSL ↔ Windows communication uses a local named-pipe bridge via `powershell.exe`; no TCP fallback is used on that path.
325
341
 
326
342
  ## Development
327
343
 
@@ -339,8 +355,20 @@ npm test # unit tests
339
355
  Rust-only validation:
340
356
  ```bash
341
357
  npm run rust:verify
358
+ npm run check:targets # local cross-target cfg/type check
359
+ ```
360
+
361
+ On macOS, `npm run check:targets` can use Zig for the C cross-compiler needed
362
+ by `libsqlite3-sys`:
363
+
364
+ ```bash
365
+ brew install zig
342
366
  ```
343
367
 
368
+ The script auto-detects Zig outside CI and wires the Linux/Windows C compiler
369
+ environment for the check. The pre-push hook does not run this optional check;
370
+ run it manually when you want local cross-target coverage.
371
+
344
372
  Browser-backed integration harness (requires built dist artifacts and Chrome):
345
373
  ```bash
346
374
  npm run test:integration
@@ -366,8 +394,14 @@ Pre-release staging flow:
366
394
  - `bump:rc` promotes alpha to `x.y.z-rc.1` (or increments RC)
367
395
  - `bump:stable` drops the prerelease suffix for final stable publish
368
396
 
369
- Release publishing (`.github/workflows/publish.yml`) enforces:
397
+ Release automation:
398
+ - Run the **Prepare Release** workflow to choose or auto-detect the next version and open a release PR.
399
+ - When that PR merges, **Tag Release** creates `v<version>` and dispatches **Release**.
400
+ - The root `package.json` version and `optionalDependencies.tabctl-win32-x64` are kept in sync by `scripts/bump-version.js`.
401
+
402
+ Release publishing (`.github/workflows/release.yml`) supports both tag pushes and explicit workflow dispatch, and enforces:
370
403
  - Git tag must match `package.json` version (`v<version>`)
404
+ - `package.json.optionalDependencies["tabctl-win32-x64"]` must match `package.json` version
371
405
  - prerelease tags publish to `alpha`/`rc`; stable publishes to `latest`
372
406
  - `npm run build` and `npm test` must pass before publish
373
407
  - release assets include `tabctl-extension.zip` plus `tabctl-extension.zip.sha256`
@@ -390,15 +424,11 @@ TABCTL_VERSION_MODE=release npm run build
390
424
  ```
391
425
 
392
426
  Notes:
393
- - `close --apply` uses the most recent analysis by `analysisId`.
394
- - `close` without `--apply` requires `--confirm` to prevent accidental closure.
427
+ - Browser reads and mutations now go through GraphQL via `tabctl query`.
395
428
  - Reports include short descriptions from page metadata and a fallback snippet.
396
- - `list` and `group-list` paginate by default (limit 100); use `--limit`, `--offset`, or `--no-page`.
397
- - Use `--group-id -1` or `--ungrouped` to target ungrouped tabs.
398
- - `--selector` implies `--signal selector`.
399
- - Unknown inspect signals are rejected (valid: `page-meta`, `selector`).
400
- - Selector `attr` supports `href-url`/`src-url` to return absolute http(s) URLs.
401
- - `screenshot --out` writes per-tab folders into the target directory.
402
- - `tabctl undo` accepts a positional txid, `--txid`, or `--latest`.
429
+ - `inspectTabs` supports `page-meta` plus selector reads with `all`, `text`, `textMode`, `styleProps`, and attrs such as `html`, `value`, `count`, `box`, `styles`, `visible`, `enabled`, and `checked`.
430
+ - `readTabs` converts main-frame page HTML to Markdown with Kreuzberg `html-to-markdown`; per-tab `status`, `emptyReason`, `diagnostics`, and `error` distinguish empty pages, unsupported URLs, injection failures, conversion failures, timeouts, and cached fallbacks (`status: CACHED`, `cached: true`, `diagnostics.source: "cache"`).
431
+ - Cached fallbacks use a bounded profile-local open-tab HTML cache refreshed after successful reads, active tab switches, and quiescent active pages; diagnostics include cache age and match mode when cached content is used.
432
+ - `captureScreenshots` returns tile metadata and image data from GraphQL.
433
+ - `undoAction` accepts either an explicit `txid` or `latest: true`.
403
434
  - `tabctl history --json` returns a top-level JSON array.
404
- - `--format` is only supported by `report` (use `--json` elsewhere).