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.
Files changed (44) hide show
  1. package/AGENTS.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +218 -0
  4. package/bin/cssprobe-cli.js +6 -0
  5. package/dist/browser/launcher.d.ts +25 -0
  6. package/dist/browser/launcher.js +213 -0
  7. package/dist/cli/command.d.ts +27 -0
  8. package/dist/cli/command.js +84 -0
  9. package/dist/cli/commands.d.ts +42 -0
  10. package/dist/cli/commands.js +166 -0
  11. package/dist/cli/minimist.d.ts +9 -0
  12. package/dist/cli/minimist.js +120 -0
  13. package/dist/cli/output.d.ts +26 -0
  14. package/dist/cli/output.js +80 -0
  15. package/dist/cli/program.d.ts +1 -0
  16. package/dist/cli/program.js +381 -0
  17. package/dist/collector-bundle.js +342 -0
  18. package/dist/config/config.d.ts +23 -0
  19. package/dist/config/config.js +128 -0
  20. package/dist/config/helpGenerator.d.ts +10 -0
  21. package/dist/config/helpGenerator.js +156 -0
  22. package/dist/engine/analyzer.d.ts +2 -0
  23. package/dist/engine/analyzer.js +385 -0
  24. package/dist/engine/collector.d.ts +3 -0
  25. package/dist/engine/collector.js +291 -0
  26. package/dist/engine/renderer.d.ts +3 -0
  27. package/dist/engine/renderer.js +159 -0
  28. package/dist/engine/types.d.ts +130 -0
  29. package/dist/engine/types.js +4 -0
  30. package/dist/help.json +107 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +6 -0
  33. package/dist/installer/index.d.ts +3 -0
  34. package/dist/installer/index.js +12 -0
  35. package/dist/installer/skill-template.d.ts +9 -0
  36. package/dist/installer/skill-template.js +274 -0
  37. package/dist/installer/targets/registry.d.ts +9 -0
  38. package/dist/installer/targets/registry.js +121 -0
  39. package/dist/installer/targets/shared.d.ts +9 -0
  40. package/dist/installer/targets/shared.js +112 -0
  41. package/dist/installer/targets/types.d.ts +22 -0
  42. package/dist/installer/targets/types.js +3 -0
  43. package/docs/guide/installation.md +230 -0
  44. 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,6 @@
1
+ #!/usr/bin/env node
2
+ const { program } = require('../dist/index');
3
+ program().catch(e => {
4
+ console.error(e.message);
5
+ process.exit(1);
6
+ });
@@ -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