cssprobe-cli 0.1.0
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/AGENTS.md +77 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/bin/cssprobe-cli.js +6 -0
- package/dist/browser/launcher.d.ts +25 -0
- package/dist/browser/launcher.js +213 -0
- package/dist/cli/command.d.ts +27 -0
- package/dist/cli/command.js +84 -0
- package/dist/cli/commands.d.ts +42 -0
- package/dist/cli/commands.js +166 -0
- package/dist/cli/minimist.d.ts +9 -0
- package/dist/cli/minimist.js +120 -0
- package/dist/cli/output.d.ts +26 -0
- package/dist/cli/output.js +80 -0
- package/dist/cli/program.d.ts +1 -0
- package/dist/cli/program.js +381 -0
- package/dist/collector-bundle.js +342 -0
- package/dist/config/config.d.ts +23 -0
- package/dist/config/config.js +128 -0
- package/dist/config/helpGenerator.d.ts +10 -0
- package/dist/config/helpGenerator.js +156 -0
- package/dist/engine/analyzer.d.ts +2 -0
- package/dist/engine/analyzer.js +385 -0
- package/dist/engine/collector.d.ts +3 -0
- package/dist/engine/collector.js +291 -0
- package/dist/engine/renderer.d.ts +3 -0
- package/dist/engine/renderer.js +159 -0
- package/dist/engine/types.d.ts +130 -0
- package/dist/engine/types.js +4 -0
- package/dist/help.json +107 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/installer/index.d.ts +3 -0
- package/dist/installer/index.js +12 -0
- package/dist/installer/skill-template.d.ts +9 -0
- package/dist/installer/skill-template.js +274 -0
- package/dist/installer/targets/registry.d.ts +9 -0
- package/dist/installer/targets/registry.js +121 -0
- package/dist/installer/targets/shared.d.ts +9 -0
- package/dist/installer/targets/shared.js +112 -0
- package/dist/installer/targets/types.d.ts +22 -0
- package/dist/installer/targets/types.js +3 -0
- package/docs/guide/installation.md +230 -0
- package/package.json +51 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Project
|
|
4
|
+
|
|
5
|
+
`cssprobe-cli` — Runtime CSS probe for layout/scroll/overflow/color/font inspection. Entrypoint: `bin/cssprobe-cli.js` → `require('../dist/index')`.
|
|
6
|
+
Build: `npm run build` (= `tsc` + `esbuild` collector IIFE bundle + `tsx scripts/generate-help.ts`).
|
|
7
|
+
Test: `npm test` (= `tsx --test tests/*.test.ts` — Node.js built-in test runner, 26 tests).
|
|
8
|
+
Dependencies: `playwright-core`, `zod`. Dev: `typescript`, `@types/node`, `tsx`, `esbuild`.
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/
|
|
14
|
+
├── index.ts # export { program } from './cli/program'
|
|
15
|
+
├── cli/
|
|
16
|
+
│ ├── program.ts # Entry: minimist parse → dispatch → engine call
|
|
17
|
+
│ ├── commands.ts # 10 command definitions — all Zod schemas
|
|
18
|
+
│ ├── command.ts # declareCommand(), parseCommand() (Zod validation)
|
|
19
|
+
│ ├── output.ts # TextOutput / JsonOutput strategy pattern
|
|
20
|
+
│ └── minimist.ts # Arguments parser (forked from playwright-cli)
|
|
21
|
+
├── engine/
|
|
22
|
+
│ ├── types.ts # Shared types: Snapshot, TreeNode, Finding, Confidence
|
|
23
|
+
│ ├── collector.ts # Browser-side: DOM traversal, computed styles, declared values
|
|
24
|
+
│ ├── analyzer.ts # Node-side: pure functions, confidence model, pattern detection
|
|
25
|
+
│ └── renderer.ts # Markdown / JSON rendering
|
|
26
|
+
├── browser/
|
|
27
|
+
│ └── launcher.ts # Playwright wrapper: launch, navigate, inject collector, state loading
|
|
28
|
+
└── config/
|
|
29
|
+
├── config.ts # Config loader (CLI → env → ~/.cssprobe-clirc profiles)
|
|
30
|
+
└── helpGenerator.ts # Build-time: Zod schemas → dist/help.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Key Patterns
|
|
34
|
+
|
|
35
|
+
- **Three-layer engine**: `collector.ts` (browser, esbuild IIFE) → `analyzer.ts` (Node, pure functions) → `renderer.ts` (Markdown/JSON). Collector only gathers facts; analyzer does inference with confidence; renderer formats output.
|
|
36
|
+
- **Collector bundling**: `collector.ts` is bundled by esbuild as an IIFE (`format: 'iife', globalName: '__cssprobe-cli'`). The launcher injects it via `page.addScriptTag({ content })`, then calls `window.__cssprobe-cli.collect(cfg)`.
|
|
37
|
+
- **Confidence model**: Every `Finding` carries `confidence: 'DEFINITE' | 'INDEFINITE' | 'UNVERIFIABLE'`. Computed values = DEFINITE. Percent declarations = INDEFINITE. Missing/blocked declarations = UNVERIFIABLE.
|
|
38
|
+
- **Command definition**: `declareCommand({ name, category, description, args?, options? })` — purely declarative, no execution logic.
|
|
39
|
+
- **Command dispatch**: `minimist` parse → `parseCommand()` Zod validate → `switch(commandName)` → handler function.
|
|
40
|
+
- **Output strategy**: `Output` interface → `TextOutput` (Markdown for inspect, tables for lists) / `JsonOutput` (machine-readable JSON).
|
|
41
|
+
- **Config priority**: CLI flags → env vars → `~/.cssprobe-clirc` profiles. Switch with `-p <profile>`.
|
|
42
|
+
- **State management**: `--state <file>` loads Playwright storage state (cookies + localStorage). `state-import` converts Netscape cookies. `login` does interactive login + save state.
|
|
43
|
+
- **Browser discovery**: `findBrowserExecutable()` scans `~/Library/Caches/ms-playwright/chromium-*` for existing Chrome installations.
|
|
44
|
+
- **Error format**: `Error: <message>` → JSON mode: `{ isError: true, error: "<message>" }` → `process.exit(1)`
|
|
45
|
+
|
|
46
|
+
## Adding a Command
|
|
47
|
+
|
|
48
|
+
1. Add `declareCommand({...})` in `src/cli/commands.ts` following the existing pattern
|
|
49
|
+
2. Append to `commandsArray` at the bottom of the file
|
|
50
|
+
3. Add handler in `src/cli/program.ts` (switch case or dedicated function)
|
|
51
|
+
4. Run `npm run build` to regenerate `help.json` and recompile
|
|
52
|
+
5. Run `npm test` to verify
|
|
53
|
+
|
|
54
|
+
## Build Pipeline
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
npm run build
|
|
58
|
+
├── 1. tsc # Compile TS → dist/ (includes .d.ts)
|
|
59
|
+
├── 2. esbuild collector.ts # Bundle browser-side code → dist/collector-bundle.js (IIFE)
|
|
60
|
+
└── 3. tsx generate-help.ts # Zod schemas → dist/help.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The collector bundle is a self-contained IIFE that exposes `window.__cssprobe-cli.collect(cfg)`. It runs entirely in the browser context — no Node imports.
|
|
64
|
+
|
|
65
|
+
## What Makes This Different from dom-report
|
|
66
|
+
|
|
67
|
+
| | dom-report (old) | cssprobe-cli (new) |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Architecture | Browser-side template string (collect + analyze + render mixed) | Three-layer: collector (IIFE) → analyzer (pure) → renderer |
|
|
70
|
+
| Dead code | collector.ts 459 lines never imported | Single implementation, no dead code |
|
|
71
|
+
| Confidence | None — all findings treated equally | DEFINITE / INDEFINITE / UNVERIFIABLE per finding |
|
|
72
|
+
| Output | Single markdown string | Markdown (default) / JSON (--json) |
|
|
73
|
+
| Config | `window.__DOM_REPORT_CFG` global + eval two-step | CLI flags → env → rc file, one command |
|
|
74
|
+
| ROOT_SELECTOR | Manual or eval probe | `--auto-root` auto-detection |
|
|
75
|
+
| Tests | None | 26 unit tests (command, analyzer, output) |
|
|
76
|
+
| Build | Regex stripping CJS module.exports | esbuild IIFE bundle, clean build |
|
|
77
|
+
| Auth | None | `--state`, `login`, `state-import` |
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 strikingly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# cssprobe-cli
|
|
2
|
+
|
|
3
|
+
Runtime CSS probe — inspect layout, scroll, colors, backgrounds, fonts and more in a live browser. Built for both humans and AI agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### For Humans
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g cssprobe-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### For LLM Agents
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cssprobe-cli skill-install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Inspect a Page
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# With explicit selector
|
|
27
|
+
cssprobe-cli inspect https://example.com ".main-content"
|
|
28
|
+
|
|
29
|
+
# Auto-detect root element
|
|
30
|
+
cssprobe-cli inspect https://example.com
|
|
31
|
+
|
|
32
|
+
# Local HTML file
|
|
33
|
+
cssprobe-cli inspect ./test.html ".container"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Login-Protected Pages
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Import cookies from browser export (Netscape format)
|
|
40
|
+
cssprobe-cli state-import cookies.txt --out ~/.cssprobe-cli/states/mysite.json
|
|
41
|
+
|
|
42
|
+
# Or interactive login
|
|
43
|
+
cssprobe-cli login https://mysite.com
|
|
44
|
+
|
|
45
|
+
# Then inspect with state
|
|
46
|
+
cssprobe-cli inspect https://mysite.com/page ".target" --state ~/.cssprobe-cli/states/mysite.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. JSON Output
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cssprobe-cli inspect https://example.com ".content" --json
|
|
53
|
+
cssprobe-cli --json inspect https://example.com ".content" | jq '.findings[] | {id, confidence, message}'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Output Modes
|
|
59
|
+
|
|
60
|
+
| Flag | Output | Use Case |
|
|
61
|
+
|------|--------|----------|
|
|
62
|
+
| (default) | Markdown report with ancestor chain, DOM tree, findings | Terminal viewing |
|
|
63
|
+
| `--json` | Structured JSON with snapshot, findings, confidence summary | Scripts, `jq` pipes, AI agent consumption |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
### Inspection
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cssprobe-cli inspect <url> [selector] # Inspect CSS layout
|
|
73
|
+
cssprobe-cli login <url> # Interactive login, save state
|
|
74
|
+
cssprobe-cli state-import [file] # Import cookies from Netscape format
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Configuration
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cssprobe-cli config-show # Show current config
|
|
81
|
+
cssprobe-cli config-set <key> <value> # Set config value
|
|
82
|
+
cssprobe-cli config-list # List all profiles
|
|
83
|
+
cssprobe-cli config-use <name> # Switch active profile
|
|
84
|
+
cssprobe-cli config-new <name> # Create new profile
|
|
85
|
+
cssprobe-cli config-path # Show config file path
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## inspect Options
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cssprobe-cli inspect <url> [selector] [options]
|
|
94
|
+
|
|
95
|
+
Arguments:
|
|
96
|
+
<url> URL, file:// path, or local HTML file
|
|
97
|
+
[selector] CSS selector for root (auto-detected if omitted)
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--json Output structured JSON
|
|
101
|
+
--headed Show browser window
|
|
102
|
+
--browser <engine> chromium|firefox|webkit (default chromium)
|
|
103
|
+
--zoom Run 1x/0.5x viewport diagnosis
|
|
104
|
+
--depth <n> DOM tree depth (default 6)
|
|
105
|
+
--max-nodes <n> Node count cap (default 60)
|
|
106
|
+
--up-to <tag> Ancestor stop tag (default html)
|
|
107
|
+
--state <file> Load saved state (cookies + localStorage)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## state-import
|
|
113
|
+
|
|
114
|
+
Import cookies from Netscape format (browser export standard) into a Playwright state file.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# From file
|
|
118
|
+
cssprobe-cli state-import cookies.txt --out mystate.json
|
|
119
|
+
|
|
120
|
+
# From stdin
|
|
121
|
+
cat cookies.txt | cssprobe-cli state-import --out mystate.json
|
|
122
|
+
|
|
123
|
+
# Merge into existing state
|
|
124
|
+
cssprobe-cli state-import new-cookies.txt --merge existing.json --out merged.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Netscape format (tab-separated):
|
|
128
|
+
```
|
|
129
|
+
.example.com TRUE / TRUE 1813025057 session_id abc123
|
|
130
|
+
.example.com TRUE / FALSE -1 lang zh-CN
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Confidence Model
|
|
136
|
+
|
|
137
|
+
Every finding carries a confidence level:
|
|
138
|
+
|
|
139
|
+
| Level | Meaning |
|
|
140
|
+
|-------|---------|
|
|
141
|
+
| **DEFINITE** | Based on computed values (facts from getComputedStyle) or accessible declared values |
|
|
142
|
+
| **INDEFINITE** | Declared value uses `%` — resolves at runtime |
|
|
143
|
+
| **UNVERIFIABLE** | Declared value missing or from blocked cross-origin stylesheet |
|
|
144
|
+
|
|
145
|
+
The report header shows: `confidence: DEFINITE 8 | INDEFINITE 0 | UNVERIFIABLE 1`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Configuration
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Set values
|
|
153
|
+
cssprobe-cli config-set browser chromium
|
|
154
|
+
cssprobe-cli config-set depth 8
|
|
155
|
+
|
|
156
|
+
# Show current config
|
|
157
|
+
cssprobe-cli config-show
|
|
158
|
+
|
|
159
|
+
# Profile management
|
|
160
|
+
cssprobe-cli config-new staging
|
|
161
|
+
cssprobe-cli -p staging config-set browser firefox
|
|
162
|
+
cssprobe-cli config-use staging
|
|
163
|
+
cssprobe-cli config-list
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Priority: CLI flags > Environment variables > Config file
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
--browser CSSPROBE_BROWSER
|
|
170
|
+
--depth CSSPROBE_DEPTH
|
|
171
|
+
--headed CSSPROBE_HEADED
|
|
172
|
+
-p, --profile CSSPROBE_PROFILE
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Config file: `~/.cssprobe-clirc`
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Agent Skill
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Install skill for all detected agents
|
|
183
|
+
cssprobe-cli skill-install
|
|
184
|
+
|
|
185
|
+
# Install for a specific agent
|
|
186
|
+
cssprobe-cli skill-install --target opencode
|
|
187
|
+
cssprobe-cli skill-install --target claude
|
|
188
|
+
|
|
189
|
+
# Remove installed skills
|
|
190
|
+
cssprobe-cli skill-uninstall
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Global Options
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
--json Output as JSON (default: Markdown)
|
|
199
|
+
--raw Output raw result without formatting
|
|
200
|
+
--help [command] Show help for a command or global
|
|
201
|
+
--version Show version
|
|
202
|
+
-p, --profile Use named config profile
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npm install
|
|
211
|
+
npm run build # tsc + esbuild collector bundle + generate help.json
|
|
212
|
+
npm test # Run unit tests
|
|
213
|
+
npx tsc --noEmit # Type check only
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snapshot, CollectConfig } from '../engine/types';
|
|
2
|
+
export interface LauncherOptions {
|
|
3
|
+
browser?: string;
|
|
4
|
+
headed?: boolean;
|
|
5
|
+
/** Path to a Playwright storage state file (cookies + localStorage). */
|
|
6
|
+
state?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class BrowserLauncher {
|
|
9
|
+
private options;
|
|
10
|
+
private browser;
|
|
11
|
+
private context;
|
|
12
|
+
private page;
|
|
13
|
+
constructor(options?: LauncherOptions);
|
|
14
|
+
open(url: string): Promise<void>;
|
|
15
|
+
collect(cfg: CollectConfig): Promise<Snapshot>;
|
|
16
|
+
/** Scan the page for likely root elements (modals, dialogs, scroll containers). */
|
|
17
|
+
autoDetectRoot(): Promise<string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Interactive login flow: opens browser in headed mode, navigates to URL,
|
|
20
|
+
* waits for user to complete login, then saves storage state.
|
|
21
|
+
* Returns the path to the saved state file.
|
|
22
|
+
*/
|
|
23
|
+
loginAndSave(url: string, statePath: string): Promise<string>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Playwright launcher — opens a browser, navigates, injects the collector bundle,
|
|
3
|
+
// and returns a Snapshot. The collector bundle is read from dist/collector-bundle.js.
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.BrowserLauncher = void 0;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const playwright_core_1 = require("playwright-core");
|
|
13
|
+
const COLLECTOR_BUNDLE_PATH = path_1.default.join(__dirname, '..', 'collector-bundle.js');
|
|
14
|
+
let bundleContent = null;
|
|
15
|
+
function loadBundle() {
|
|
16
|
+
if (bundleContent !== null)
|
|
17
|
+
return bundleContent;
|
|
18
|
+
if (!fs_1.default.existsSync(COLLECTOR_BUNDLE_PATH)) {
|
|
19
|
+
throw new Error(`collector bundle not found at ${COLLECTOR_BUNDLE_PATH}. Run "npm run build" first.`);
|
|
20
|
+
}
|
|
21
|
+
bundleContent = fs_1.default.readFileSync(COLLECTOR_BUNDLE_PATH, 'utf-8');
|
|
22
|
+
return bundleContent;
|
|
23
|
+
}
|
|
24
|
+
/** Try to find an existing browser executable in the Playwright cache. */
|
|
25
|
+
function findBrowserExecutable(engine) {
|
|
26
|
+
const cacheDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Caches', 'ms-playwright');
|
|
27
|
+
if (!fs_1.default.existsSync(cacheDir))
|
|
28
|
+
return null;
|
|
29
|
+
const platform = process.platform;
|
|
30
|
+
if (engine === 'chromium') {
|
|
31
|
+
// Look for chromium-* directories (sorted newest first)
|
|
32
|
+
const dirs = fs_1.default.readdirSync(cacheDir)
|
|
33
|
+
.filter(d => d.startsWith('chromium-'))
|
|
34
|
+
.sort((a, b) => {
|
|
35
|
+
const na = parseInt(a.split('-')[1]) || 0;
|
|
36
|
+
const nb = parseInt(b.split('-')[1]) || 0;
|
|
37
|
+
return nb - na;
|
|
38
|
+
});
|
|
39
|
+
for (const dir of dirs) {
|
|
40
|
+
if (platform === 'darwin') {
|
|
41
|
+
const app = path_1.default.join(cacheDir, dir, 'chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing');
|
|
42
|
+
if (fs_1.default.existsSync(app))
|
|
43
|
+
return app;
|
|
44
|
+
const appIntel = path_1.default.join(cacheDir, dir, 'chrome-mac', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing');
|
|
45
|
+
if (fs_1.default.existsSync(appIntel))
|
|
46
|
+
return appIntel;
|
|
47
|
+
}
|
|
48
|
+
else if (platform === 'linux') {
|
|
49
|
+
const bin = path_1.default.join(cacheDir, dir, 'chrome-linux', 'chrome');
|
|
50
|
+
if (fs_1.default.existsSync(bin))
|
|
51
|
+
return bin;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
class BrowserLauncher {
|
|
58
|
+
options;
|
|
59
|
+
browser = null;
|
|
60
|
+
context = null;
|
|
61
|
+
page = null;
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
}
|
|
65
|
+
async open(url) {
|
|
66
|
+
const engine = this.options.browser || 'chromium';
|
|
67
|
+
const factory = engine === 'firefox' ? playwright_core_1.firefox :
|
|
68
|
+
engine === 'webkit' ? playwright_core_1.webkit :
|
|
69
|
+
playwright_core_1.chromium;
|
|
70
|
+
const launchOptions = { headless: !this.options.headed };
|
|
71
|
+
// Try to find existing browser installation
|
|
72
|
+
const execPath = findBrowserExecutable(engine);
|
|
73
|
+
if (execPath) {
|
|
74
|
+
launchOptions.executablePath = execPath;
|
|
75
|
+
}
|
|
76
|
+
this.browser = await factory.launch(launchOptions);
|
|
77
|
+
// Load saved state if provided
|
|
78
|
+
const contextOptions = { viewport: { width: 1280, height: 720 } };
|
|
79
|
+
if (this.options.state && fs_1.default.existsSync(this.options.state)) {
|
|
80
|
+
contextOptions.storageState = this.options.state;
|
|
81
|
+
}
|
|
82
|
+
this.context = await this.browser.newContext(contextOptions);
|
|
83
|
+
this.page = await this.context.newPage();
|
|
84
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
85
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
86
|
+
}
|
|
87
|
+
else if (url.startsWith('file://')) {
|
|
88
|
+
await this.page.goto(url, { waitUntil: 'load', timeout: 30000 });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Treat as local path
|
|
92
|
+
const resolved = path_1.default.resolve(url);
|
|
93
|
+
if (!fs_1.default.existsSync(resolved)) {
|
|
94
|
+
throw new Error(`File not found: ${resolved}`);
|
|
95
|
+
}
|
|
96
|
+
await this.page.goto(`file://${resolved}`, { waitUntil: 'load', timeout: 30000 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async collect(cfg) {
|
|
100
|
+
if (!this.page)
|
|
101
|
+
throw new Error('Browser not opened. Call open() first.');
|
|
102
|
+
const bundle = loadBundle();
|
|
103
|
+
await this.page.addScriptTag({ content: bundle });
|
|
104
|
+
const snapshot = await this.page.evaluate((config) => {
|
|
105
|
+
return window.__cssprobe_cli.collect(config);
|
|
106
|
+
}, cfg);
|
|
107
|
+
return snapshot;
|
|
108
|
+
}
|
|
109
|
+
/** Scan the page for likely root elements (modals, dialogs, scroll containers). */
|
|
110
|
+
async autoDetectRoot() {
|
|
111
|
+
if (!this.page)
|
|
112
|
+
throw new Error('Browser not opened. Call open() first.');
|
|
113
|
+
return this.page.evaluate(() => {
|
|
114
|
+
const candidates = [];
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
function addSelector(sel, score) {
|
|
117
|
+
if (seen.has(sel))
|
|
118
|
+
return;
|
|
119
|
+
seen.add(sel);
|
|
120
|
+
candidates.push({ selector: sel, score });
|
|
121
|
+
}
|
|
122
|
+
// Modals / dialogs
|
|
123
|
+
for (const el of Array.from(document.querySelectorAll('.s-kit-modal, [class*="dialog"], [class*="modal"], [class*="popup"]'))) {
|
|
124
|
+
const cls = (el.getAttribute('class') || '').split(/\s+/).filter(Boolean).join('.');
|
|
125
|
+
if (cls)
|
|
126
|
+
addSelector(`.${cls.split(/\s+/)[0]}`, 10);
|
|
127
|
+
}
|
|
128
|
+
// Scroll containers (overflow-y: auto/scroll with content overflow)
|
|
129
|
+
for (const el of Array.from(document.querySelectorAll('*'))) {
|
|
130
|
+
const cs = getComputedStyle(el);
|
|
131
|
+
if ((cs.overflowY === 'auto' || cs.overflowY === 'scroll') &&
|
|
132
|
+
el.scrollHeight > el.clientHeight + 1 &&
|
|
133
|
+
el.clientHeight > 100) {
|
|
134
|
+
const tag = el.tagName.toLowerCase();
|
|
135
|
+
const id = el.id ? `#${el.id}` : '';
|
|
136
|
+
const cls = (el.getAttribute('class') || '').split(/\s+/).filter(Boolean).slice(0, 2).join('.');
|
|
137
|
+
const sel = id || (cls ? `${tag}.${cls}` : tag);
|
|
138
|
+
addSelector(sel, 5);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Sections with substantial content
|
|
142
|
+
for (const el of Array.from(document.querySelectorAll('section, [class*="section"], main, [role="main"]'))) {
|
|
143
|
+
const tag = el.tagName.toLowerCase();
|
|
144
|
+
const cls = (el.getAttribute('class') || '').split(/\s+/).filter(Boolean).slice(0, 2).join('.');
|
|
145
|
+
const sel = cls ? `${tag}.${cls}` : tag;
|
|
146
|
+
addSelector(sel, 3);
|
|
147
|
+
}
|
|
148
|
+
return candidates
|
|
149
|
+
.sort((a, b) => b.score - a.score)
|
|
150
|
+
.slice(0, 5)
|
|
151
|
+
.map(c => c.selector);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Interactive login flow: opens browser in headed mode, navigates to URL,
|
|
156
|
+
* waits for user to complete login, then saves storage state.
|
|
157
|
+
* Returns the path to the saved state file.
|
|
158
|
+
*/
|
|
159
|
+
async loginAndSave(url, statePath) {
|
|
160
|
+
const engine = this.options.browser || 'chromium';
|
|
161
|
+
const factory = engine === 'firefox' ? playwright_core_1.firefox :
|
|
162
|
+
engine === 'webkit' ? playwright_core_1.webkit :
|
|
163
|
+
playwright_core_1.chromium;
|
|
164
|
+
const execPath = findBrowserExecutable(engine);
|
|
165
|
+
const launchOptions = { headless: false };
|
|
166
|
+
if (execPath)
|
|
167
|
+
launchOptions.executablePath = execPath;
|
|
168
|
+
this.browser = await factory.launch(launchOptions);
|
|
169
|
+
this.context = await this.browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
170
|
+
this.page = await this.context.newPage();
|
|
171
|
+
// Navigate to the target URL
|
|
172
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
173
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
throw new Error('--login only works with http/https URLs');
|
|
177
|
+
}
|
|
178
|
+
// Wait for user to complete login (detect URL change or navigation)
|
|
179
|
+
console.error('');
|
|
180
|
+
console.error(' Browser opened. Please complete the login manually.');
|
|
181
|
+
console.error(' The browser will detect when you navigate away from the login page,');
|
|
182
|
+
console.error(' or you can press Enter here when done.');
|
|
183
|
+
console.error('');
|
|
184
|
+
// Wait for either URL change or Enter keypress
|
|
185
|
+
await Promise.race([
|
|
186
|
+
this.page.waitForURL('**/*', { timeout: 300000 }).catch(() => { }),
|
|
187
|
+
new Promise(resolve => {
|
|
188
|
+
process.stdin.once('data', () => resolve());
|
|
189
|
+
}),
|
|
190
|
+
]);
|
|
191
|
+
// Small delay to let cookies/localStorage settle
|
|
192
|
+
await this.page.waitForTimeout(1000);
|
|
193
|
+
// Save state
|
|
194
|
+
const resolvedPath = path_1.default.resolve(statePath);
|
|
195
|
+
const dir = path_1.default.dirname(resolvedPath);
|
|
196
|
+
if (!fs_1.default.existsSync(dir))
|
|
197
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
198
|
+
await this.context.storageState({ path: resolvedPath });
|
|
199
|
+
console.error(` State saved to: ${resolvedPath}`);
|
|
200
|
+
return resolvedPath;
|
|
201
|
+
}
|
|
202
|
+
async close() {
|
|
203
|
+
if (this.context)
|
|
204
|
+
await this.context.close().catch(() => { });
|
|
205
|
+
if (this.browser)
|
|
206
|
+
await this.browser.close().catch(() => { });
|
|
207
|
+
this.context = null;
|
|
208
|
+
this.browser = null;
|
|
209
|
+
this.page = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
exports.BrowserLauncher = BrowserLauncher;
|
|
213
|
+
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import type zodType from 'zod';
|
|
3
|
+
export type Category = 'core' | 'config' | 'skill';
|
|
4
|
+
export interface HelpEntry {
|
|
5
|
+
help: string;
|
|
6
|
+
flags: Record<string, 'boolean' | 'string'>;
|
|
7
|
+
args: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface HelpData {
|
|
10
|
+
global: string;
|
|
11
|
+
commands: Record<string, HelpEntry>;
|
|
12
|
+
booleanOptions: string[];
|
|
13
|
+
}
|
|
14
|
+
export type CommandArgs<Args extends zodType.ZodTypeAny, Options extends zodType.ZodTypeAny> = z.infer<Args> & z.infer<Options>;
|
|
15
|
+
export type CommandSchema<Args extends zodType.ZodTypeAny, Options extends zodType.ZodTypeAny> = {
|
|
16
|
+
name: string;
|
|
17
|
+
category: Category;
|
|
18
|
+
description: string;
|
|
19
|
+
hidden?: boolean;
|
|
20
|
+
args?: Args;
|
|
21
|
+
options?: Options;
|
|
22
|
+
};
|
|
23
|
+
export type AnyCommandSchema = CommandSchema<any, any>;
|
|
24
|
+
export declare function declareCommand<Args extends zodType.ZodTypeAny, Options extends zodType.ZodTypeAny>(command: CommandSchema<Args, Options>): CommandSchema<Args, Options>;
|
|
25
|
+
export declare function parseCommand(command: AnyCommandSchema, args: Record<string, string> & {
|
|
26
|
+
_: string[];
|
|
27
|
+
}): Record<string, any>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.declareCommand = declareCommand;
|
|
37
|
+
exports.parseCommand = parseCommand;
|
|
38
|
+
const z = __importStar(require("zod"));
|
|
39
|
+
function declareCommand(command) {
|
|
40
|
+
return command;
|
|
41
|
+
}
|
|
42
|
+
const kEmptyOptions = z.object({});
|
|
43
|
+
const kEmptyArgs = z.object({});
|
|
44
|
+
function parseCommand(command, args) {
|
|
45
|
+
const optionsObject = { ...args };
|
|
46
|
+
delete optionsObject['_'];
|
|
47
|
+
const optionsSchema = (command.options ?? kEmptyOptions).strict();
|
|
48
|
+
const options = zodParse(optionsSchema, optionsObject, 'option');
|
|
49
|
+
const argsSchema = (command.args ?? kEmptyArgs).strict();
|
|
50
|
+
const argNames = [...Object.keys(argsSchema.shape)];
|
|
51
|
+
const argv = args['_'].slice(1);
|
|
52
|
+
if (argv.length > argNames.length)
|
|
53
|
+
throw new Error(`error: too many arguments: expected ${argNames.length}, received ${argv.length}`);
|
|
54
|
+
const argsObject = {};
|
|
55
|
+
argNames.forEach((name, index) => (argsObject[name] = argv[index]));
|
|
56
|
+
const parsedArgs = zodParse(argsSchema, argsObject, 'argument');
|
|
57
|
+
return { ...parsedArgs, ...options };
|
|
58
|
+
}
|
|
59
|
+
function zodParse(schema, data, type) {
|
|
60
|
+
try {
|
|
61
|
+
return schema.parse(data);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
throw new Error(e.issues
|
|
65
|
+
.map((issue) => {
|
|
66
|
+
const keys = issue.code === 'unrecognized_keys' ? issue.keys : [''];
|
|
67
|
+
const props = keys.map((key) => [...issue.path, key].filter(Boolean).join('.'));
|
|
68
|
+
return props.map((prop) => {
|
|
69
|
+
const label = type === 'option' ? `'--${prop}' option` : `'${prop}' argument`;
|
|
70
|
+
switch (issue.code) {
|
|
71
|
+
case 'invalid_type':
|
|
72
|
+
return 'error: ' + label + ': ' + issue.message.replace(/Invalid input:/, '').trim();
|
|
73
|
+
case 'unrecognized_keys':
|
|
74
|
+
return 'error: unknown ' + label;
|
|
75
|
+
default:
|
|
76
|
+
return 'error: ' + label + ': ' + issue.message;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
})
|
|
80
|
+
.flat()
|
|
81
|
+
.join('\n'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=command.js.map
|