cssprobe-cli 0.2.21 → 0.2.23

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
@@ -130,6 +130,7 @@ cssprobe-cli inspect body --json | jq '.findings[] | {id, confidence, message}'
130
130
  | `open [url] --state <file>` | Open with saved cookies + localStorage |
131
131
  | `open [url] --viewport 1280x720` | Custom viewport size |
132
132
  | `close` | Close browser session |
133
+ | `close --all` | Close all sessions across all workspaces |
133
134
  | `status` | Show session status |
134
135
 
135
136
  ### CSS Inspection
@@ -1,4 +1,4 @@
1
- import { type Page } from 'playwright-core';
1
+ import { type BrowserContext, type Page } from 'playwright-core';
2
2
  import type { Snapshot, CollectConfig } from '../engine/types';
3
3
  export interface LauncherOptions {
4
4
  browser?: string;
@@ -23,5 +23,7 @@ export declare class BrowserLauncher {
23
23
  navigate(url: string): Promise<void>;
24
24
  /** Get the current page object. */
25
25
  getPage(): Page | null;
26
+ /** Get the current browser context. */
27
+ getContext(): BrowserContext | null;
26
28
  close(): Promise<void>;
27
29
  }
@@ -129,6 +129,10 @@ class BrowserLauncher {
129
129
  getPage() {
130
130
  return this.page;
131
131
  }
132
+ /** Get the current browser context. */
133
+ getContext() {
134
+ return this.context;
135
+ }
132
136
  async close() {
133
137
  if (this.context)
134
138
  await this.context.close().catch(() => { });
@@ -63,6 +63,9 @@ const close = (0, command_1.declareCommand)({
63
63
  name: 'close',
64
64
  category: 'core',
65
65
  description: 'Close the browser session',
66
+ options: z.object({
67
+ all: z.boolean().optional().describe('close all sessions across all workspaces'),
68
+ }),
66
69
  });
67
70
  const status = (0, command_1.declareCommand)({
68
71
  name: 'status',
@@ -167,6 +170,14 @@ const stateImport = (0, command_1.declareCommand)({
167
170
  merge: z.string().optional().describe('existing state file to merge into'),
168
171
  }),
169
172
  });
173
+ const stateSave = (0, command_1.declareCommand)({
174
+ name: 'state-save',
175
+ category: 'core',
176
+ description: 'Save current browser session state (cookies + localStorage)',
177
+ options: z.object({
178
+ name: z.string().optional().describe('state file name (default: session)'),
179
+ }),
180
+ });
170
181
  // ── config ──
171
182
  const configShow = (0, command_1.declareCommand)({
172
183
  name: 'config-show',
@@ -244,7 +255,7 @@ const commandsArray = [
244
255
  // browser
245
256
  resize, evalCmd, playwrightCmd, screenshot,
246
257
  // state
247
- stateImport,
258
+ stateImport, stateSave,
248
259
  // config
249
260
  configShow, configSet, configList, configUse, configNew, configPath,
250
261
  // skill
@@ -43,7 +43,7 @@ async function program() {
43
43
  await handleOpen(command, rawArgs, output);
44
44
  return;
45
45
  case 'close':
46
- await handleClose(output);
46
+ await handleClose(command, rawArgs, output);
47
47
  return;
48
48
  case 'status':
49
49
  await handleStatus(output);
@@ -51,6 +51,9 @@ async function program() {
51
51
  case 'state-import':
52
52
  await handleStateImport(command, rawArgs, output);
53
53
  return;
54
+ case 'state-save':
55
+ await handleStateSave(command, rawArgs, output);
56
+ return;
54
57
  case 'inspect':
55
58
  case 'tree':
56
59
  case 'layout':
@@ -172,19 +175,47 @@ async function waitForSession(timeoutMs) {
172
175
  }
173
176
  return undefined;
174
177
  }
175
- async function handleClose(output) {
178
+ async function handleClose(command, args, output) {
176
179
  try {
177
- const session = await (0, session_1.loadSession)();
178
- if (!session) {
179
- output.error('No active session. Run: cssprobe-cli open <url>');
180
- return;
181
- }
182
- await session.stop();
183
- if (output.json) {
184
- console.log(JSON.stringify({ closed: true, sessionId: 'default' }, null, 2));
180
+ const cmdArgs = splitArgs(args);
181
+ const parsed = (0, command_1.parseCommand)(command, cmdArgs);
182
+ if (parsed.all) {
183
+ const sessions = await (0, session_1.listAllSessions)();
184
+ if (sessions.length === 0) {
185
+ output.error('No active sessions found.');
186
+ return;
187
+ }
188
+ const results = [];
189
+ for (const session of sessions) {
190
+ try {
191
+ await session.stop();
192
+ results.push({ sessionId: session.name, closed: true });
193
+ }
194
+ catch (e) {
195
+ results.push({ sessionId: session.name, closed: false, error: e.message });
196
+ }
197
+ }
198
+ if (output.json) {
199
+ console.log(JSON.stringify({ closed: results }, null, 2));
200
+ }
201
+ else {
202
+ const closedCount = results.filter(r => r.closed).length;
203
+ console.log(`Closed ${closedCount} session(s).`);
204
+ }
185
205
  }
186
206
  else {
187
- console.log('Browser closed.');
207
+ const session = await (0, session_1.loadSession)();
208
+ if (!session) {
209
+ output.error('No active session. Run: cssprobe-cli open <url>');
210
+ return;
211
+ }
212
+ await session.stop();
213
+ if (output.json) {
214
+ console.log(JSON.stringify({ closed: true, sessionId: 'default' }, null, 2));
215
+ }
216
+ else {
217
+ console.log('Browser closed.');
218
+ }
188
219
  }
189
220
  }
190
221
  catch (e) {
@@ -464,4 +495,44 @@ async function handleStateImport(command, args, output) {
464
495
  output.error(e instanceof Error ? e.message : String(e));
465
496
  }
466
497
  }
498
+ // ── state-save ──
499
+ async function handleStateSave(command, args, output) {
500
+ try {
501
+ const session = await (0, session_1.loadSession)();
502
+ if (!session) {
503
+ output.error('No active session. Run: cssprobe-cli open <url>');
504
+ return;
505
+ }
506
+ const cmdArgs = splitArgs(args);
507
+ const parsed = (0, command_1.parseCommand)(command, cmdArgs);
508
+ const name = parsed.name || 'session';
509
+ const fileName = name.endsWith('.json') ? name : `${name}.json`;
510
+ const outPath = path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', fileName);
511
+ // Ask daemon to save state
512
+ const result = await session.run({ _: ['state-save'] });
513
+ const state = JSON.parse(result.text);
514
+ const resolvedOut = path_1.default.resolve(outPath);
515
+ const dir = path_1.default.dirname(resolvedOut);
516
+ if (!fs_1.default.existsSync(dir))
517
+ fs_1.default.mkdirSync(dir, { recursive: true });
518
+ fs_1.default.writeFileSync(resolvedOut, JSON.stringify(state, null, 2) + '\n');
519
+ if (output.json) {
520
+ console.log(JSON.stringify({
521
+ saved: resolvedOut,
522
+ cookies: state.cookies?.length || 0,
523
+ origins: state.origins?.length || 0,
524
+ usage: `cssprobe-cli open <url> --state ${resolvedOut}`,
525
+ }, null, 2));
526
+ }
527
+ else {
528
+ console.log(`State saved: ${resolvedOut}`);
529
+ console.log(`Cookies: ${state.cookies?.length || 0}`);
530
+ console.log(`Origins: ${state.origins?.length || 0}`);
531
+ console.log(`Usage: cssprobe-cli open <url> --state ${resolvedOut}`);
532
+ }
533
+ }
534
+ catch (e) {
535
+ output.error(e instanceof Error ? e.message : String(e));
536
+ }
537
+ }
467
538
  //# sourceMappingURL=program.js.map
@@ -200,6 +200,13 @@ async function executeCommand(launcher, args) {
200
200
  const result = await asyncFunction(page, browser);
201
201
  return { text: JSON.stringify(result, null, 2) };
202
202
  }
203
+ case 'state-save': {
204
+ const context = launcher.getContext();
205
+ if (!context)
206
+ throw new Error('Browser is not open. Run: cssprobe-cli open <url>');
207
+ const state = await context.storageState();
208
+ return { text: JSON.stringify(state) };
209
+ }
203
210
  default:
204
211
  throw new Error(`Unknown command: ${command}`);
205
212
  }
@@ -50,3 +50,4 @@ export declare function createClientInfo(): ClientInfo;
50
50
  export declare function loadSession(): Promise<Session | undefined>;
51
51
  export declare function saveSession(config: SessionConfig): Promise<void>;
52
52
  export declare function deleteSession(): Promise<void>;
53
+ export declare function listAllSessions(): Promise<Session[]>;
@@ -13,6 +13,7 @@ exports.createClientInfo = createClientInfo;
13
13
  exports.loadSession = loadSession;
14
14
  exports.saveSession = saveSession;
15
15
  exports.deleteSession = deleteSession;
16
+ exports.listAllSessions = listAllSessions;
16
17
  const crypto_1 = __importDefault(require("crypto"));
17
18
  const fs_1 = __importDefault(require("fs"));
18
19
  const net_1 = __importDefault(require("net"));
@@ -229,4 +230,28 @@ async function deleteSession() {
229
230
  const sessionFile = path_1.default.join(clientInfo.daemonProfilesDir, 'default.session');
230
231
  await fs_1.default.promises.unlink(sessionFile).catch(() => { });
231
232
  }
233
+ async function listAllSessions() {
234
+ const sessions = [];
235
+ const baseDir = path_1.default.dirname(createClientInfo().daemonProfilesDir);
236
+ try {
237
+ const entries = await fs_1.default.promises.readdir(baseDir, { withFileTypes: true });
238
+ for (const entry of entries) {
239
+ if (!entry.isDirectory())
240
+ continue;
241
+ const sessionFile = path_1.default.join(baseDir, entry.name, 'default.session');
242
+ try {
243
+ const data = await fs_1.default.promises.readFile(sessionFile, 'utf-8');
244
+ const config = JSON.parse(data);
245
+ sessions.push(new Session({ file: sessionFile, config, daemonDir: path_1.default.join(baseDir, entry.name) }));
246
+ }
247
+ catch {
248
+ // Skip invalid session files
249
+ }
250
+ }
251
+ }
252
+ catch {
253
+ // Base directory doesn't exist
254
+ }
255
+ return sessions;
256
+ }
232
257
  //# sourceMappingURL=session.js.map
@@ -200,9 +200,14 @@ function analyzeScroll(snapshot) {
200
200
  message = `${nodeLabel(n)} no overflow, no scroll needed`;
201
201
  level = 'info';
202
202
  }
203
+ else if (n.props.overflowY === 'visible') {
204
+ id = 'overflow-visible';
205
+ message = `${nodeLabel(n)} content visually overflows (${n.metrics.scrollHeight}>${n.metrics.clientHeight}) but overflow=visible means content is NOT clipped`;
206
+ level = 'info';
207
+ }
203
208
  else {
204
209
  id = 'overflow-clipped';
205
- message = `${nodeLabel(n)} content overflows (${n.metrics.scrollHeight}>${n.metrics.clientHeight}) clipped by overflow=${n.props.overflowY} → height chain unconstrained (anchor issue)`;
210
+ message = `${nodeLabel(n)} content overflows (${n.metrics.scrollHeight}>${n.metrics.clientHeight}) clipped by overflow=${n.props.overflowY}`;
206
211
  level = 'warning';
207
212
  }
208
213
  findings.push(makeFinding({
@@ -7,7 +7,7 @@ function renderNode(node, indent) {
7
7
  return lines;
8
8
  const cls = node.classes.length ? `.${node.classes.join('.')}` : '';
9
9
  const id = node.id ? `#${node.id}` : '';
10
- const repeat = node.repeat ? ` \u00D7${node.repeat}` : '';
10
+ const repeat = node.repeat ? ` \u00D7${node.repeat} (first shown, use :nth-child(N) for others)` : '';
11
11
  const m = node.metrics;
12
12
  const s = node.shape || {};
13
13
  const flag = [];
package/dist/help.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "global": "Usage: cssprobe-cli <command> [args] [options]\n\n\nSession Management:\n open [url] open browser in session mode (non-blocking)\n close close the browser session\n status show current session status\n resize <width> <height> resize the browser viewport\n eval <expression> evaluate javascript expression on the page (browser context)\n playwright <call> execute playwright api call (node.js context)\n state-import [file] import cookies from netscape format file\n\nCSS Inspection:\n inspect <selector> inspect the runtime css of a page element\n tree <selector> show dom tree structure\n layout <selector> show ascii layout diagram\n findings <selector> show only issues/warnings/errors\n inject-css <css> inject css into the current page\n\nExport:\n screenshot take a screenshot of the current page\n\nConfiguration:\n config-show show current configuration\n config-set <key> <value> set a configuration value\n config-list list all profiles\n config-use <name> switch active profile\n config-new <name> create a new profile\n config-path show configuration file path\n\nAgent Skills:\n skill-install install agent skill for cssprobe-cli\n skill-uninstall remove agent skill for cssprobe-cli\n\nGlobal options:\n --help [command] print help\n --json output response as JSON\n --raw output only the result value\n --version print version\n -p, --profile <name> use named profile (or CSSPROBE_PROFILE env)",
2
+ "global": "Usage: cssprobe-cli <command> [args] [options]\n\n\nSession Management:\n open [url] open browser in session mode (non-blocking)\n close close the browser session\n status show current session status\n resize <width> <height> resize the browser viewport\n eval <expression> evaluate javascript expression on the page (browser context)\n playwright <call> execute playwright api call (node.js context)\n state-import [file] import cookies from netscape format file\n state-save save current browser session state (cookies + localstorage)\n\nCSS Inspection:\n inspect <selector> inspect the runtime css of a page element\n tree <selector> show dom tree structure\n layout <selector> show ascii layout diagram\n findings <selector> show only issues/warnings/errors\n inject-css <css> inject css into the current page\n\nExport:\n screenshot take a screenshot of the current page\n\nConfiguration:\n config-show show current configuration\n config-set <key> <value> set a configuration value\n config-list list all profiles\n config-use <name> switch active profile\n config-new <name> create a new profile\n config-path show configuration file path\n\nAgent Skills:\n skill-install install agent skill for cssprobe-cli\n skill-uninstall remove agent skill for cssprobe-cli\n\nGlobal options:\n --help [command] print help\n --json output response as JSON\n --raw output only the result value\n --version print version\n -p, --profile <name> use named profile (or CSSPROBE_PROFILE env)",
3
3
  "commands": {
4
4
  "open": {
5
5
  "help": "cssprobe-cli open [url]\n\nOpen browser in session mode (non-blocking)\n\nArguments:\n [url] url, file:// path, or local html file path\nOptions:\n --browser browser engine (default chromium)\n --headed show the browser window\n --viewport viewport size as wxh (e.g. 1280x720)\n --state path to saved state file (cookies + localstorage)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
@@ -14,8 +14,10 @@
14
14
  ]
15
15
  },
16
16
  "close": {
17
- "help": "cssprobe-cli close \n\nClose the browser session\n\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
18
- "flags": {},
17
+ "help": "cssprobe-cli close \n\nClose the browser session\n\nOptions:\n --all close all sessions across all workspaces\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
18
+ "flags": {
19
+ "all": "boolean"
20
+ },
19
21
  "args": []
20
22
  },
21
23
  "status": {
@@ -105,6 +107,13 @@
105
107
  "file"
106
108
  ]
107
109
  },
110
+ "state-save": {
111
+ "help": "cssprobe-cli state-save \n\nSave current browser session state (cookies + localStorage)\n\nOptions:\n --name state file name (default: session)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
112
+ "flags": {
113
+ "name": "string"
114
+ },
115
+ "args": []
116
+ },
108
117
  "config-show": {
109
118
  "help": "cssprobe-cli config-show \n\nShow current configuration\n\nOptions:\n --profile profile name",
110
119
  "flags": {
@@ -167,6 +176,7 @@
167
176
  },
168
177
  "booleanOptions": [
169
178
  "headed",
179
+ "all",
170
180
  "json",
171
181
  "brief",
172
182
  "layout",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cssprobe-cli",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Runtime CSS probe — inspect layout, colors, backgrounds, fonts and more in a live browser. AI agents and shell automation.",
5
5
  "keywords": [
6
6
  "css",