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
@@ -0,0 +1,381 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.program = program;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const minimist_1 = require("./minimist");
11
+ const command_1 = require("./command");
12
+ const commands_1 = require("./commands");
13
+ const output_1 = require("./output");
14
+ const config_1 = require("../config/config");
15
+ const launcher_1 = require("../browser/launcher");
16
+ const analyzer_1 = require("../engine/analyzer");
17
+ const renderer_1 = require("../engine/renderer");
18
+ const globalOptions = ['json', 'raw', 'help', 'h', 'version', 'v', 'p', 'profile'];
19
+ const booleanGlobalOptions = ['help', 'json', 'raw', 'version', 'v', 'h'];
20
+ async function program() {
21
+ const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', '..', 'package.json'), 'utf-8'));
22
+ const help = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', 'help.json'), 'utf-8'));
23
+ const rawArgs = parseArgs(help);
24
+ if (rawArgs.p && !rawArgs.profile) {
25
+ rawArgs.profile = rawArgs.p;
26
+ delete rawArgs.p;
27
+ }
28
+ const output = rawArgs.json
29
+ ? new output_1.JsonOutput()
30
+ : new output_1.TextOutput(!!rawArgs.raw);
31
+ const commandName = rawArgs._[0];
32
+ const cmdEntry = commandName ? help.commands[commandName] : undefined;
33
+ const command = commands_1.commands[commandName];
34
+ handleGlobalFlags(rawArgs, commandName, cmdEntry, help, output, pkg.version);
35
+ if (!cmdEntry || !command)
36
+ output.error(`Unknown command: ${commandName}. Run 'cssprobe-cli --help' for usage.`);
37
+ validateFlags(rawArgs, cmdEntry);
38
+ if (handleConfigCommands(commandName, command, rawArgs, output))
39
+ return;
40
+ if (handleSkillCommands(commandName, command, rawArgs, output))
41
+ return;
42
+ if (commandName === 'login') {
43
+ await handleLogin(command, cmdEntry, rawArgs, output);
44
+ return;
45
+ }
46
+ if (commandName === 'state-import') {
47
+ await handleStateImport(command, cmdEntry, rawArgs, output);
48
+ return;
49
+ }
50
+ await handleInspect(command, cmdEntry, rawArgs, output);
51
+ }
52
+ function parseArgs(help) {
53
+ return (0, minimist_1.minimist)(process.argv.slice(2), {
54
+ boolean: [...help.booleanOptions, ...booleanGlobalOptions],
55
+ string: ['_'],
56
+ });
57
+ }
58
+ function handleGlobalFlags(args, commandName, cmdEntry, help, output, version) {
59
+ if (args.version || args.v) {
60
+ output.version(version);
61
+ process.exit(0);
62
+ }
63
+ if (args.help || args.h || !commandName) {
64
+ output.help(cmdEntry ? cmdEntry.help : help.global);
65
+ process.exit(0);
66
+ }
67
+ }
68
+ function splitArgs(args) {
69
+ const result = { _: args._ };
70
+ for (const key of Object.keys(args)) {
71
+ if (key === '_' || globalOptions.includes(key))
72
+ continue;
73
+ result[key] = args[key];
74
+ }
75
+ return result;
76
+ }
77
+ function validateFlags(args, cmdEntry) {
78
+ const unknownFlags = [];
79
+ for (const key of Object.keys(args)) {
80
+ if (key === '_')
81
+ continue;
82
+ if (globalOptions.includes(key))
83
+ continue;
84
+ if (!(key in cmdEntry.flags))
85
+ unknownFlags.push(key);
86
+ }
87
+ if (unknownFlags.length)
88
+ throw new Error(`Unknown option${unknownFlags.length > 1 ? 's' : ''}: ${unknownFlags.map(f => `--${f}`).join(', ')}`);
89
+ }
90
+ // ── inspect ──
91
+ async function handleInspect(command, cmdEntry, args, output) {
92
+ if (command.name !== 'inspect') {
93
+ output.error(`Unknown command: ${command.name}`);
94
+ return;
95
+ }
96
+ try {
97
+ const cmdArgs = splitArgs(args);
98
+ const parsed = (0, command_1.parseCommand)(command, cmdArgs);
99
+ const config = (0, config_1.loadConfig)(args);
100
+ const url = parsed.url;
101
+ const selector = parsed.selector;
102
+ const zoom = !!parsed.zoom;
103
+ const depth = parsed.depth || config.depth;
104
+ const maxNodes = parsed['max-nodes'] || 60;
105
+ const upTo = parsed['up-to'] || 'html';
106
+ const headed = !!parsed.headed || config.headed;
107
+ const browser = parsed.browser || config.browser;
108
+ const state = parsed.state || undefined;
109
+ if (!url)
110
+ output.error('URL is required. Usage: cssprobe-cli inspect <url> [selector]');
111
+ // Auto-detect root selector if not provided
112
+ let rootSelector = selector;
113
+ if (!rootSelector) {
114
+ const detected = await autoDetectRoot(url, { browser, headed, state });
115
+ if (!detected) {
116
+ output.error('Could not auto-detect a root element. Please provide a selector: cssprobe-cli inspect <url> <selector>');
117
+ return;
118
+ }
119
+ rootSelector = detected;
120
+ console.error(`Auto-detected root: ${rootSelector}`);
121
+ }
122
+ const collectCfg = {
123
+ rootSelector,
124
+ upTo,
125
+ downDepth: depth,
126
+ maxNodes,
127
+ };
128
+ const launcher = new launcher_1.BrowserLauncher({ browser, headed, state });
129
+ await launcher.open(url);
130
+ const snapshot = await launcher.collect(collectCfg);
131
+ await launcher.close();
132
+ const findings = (0, analyzer_1.analyze)(snapshot);
133
+ if (output.json) {
134
+ console.log(output.format((0, renderer_1.renderJSON)(snapshot, findings)));
135
+ }
136
+ else {
137
+ console.log((0, renderer_1.renderMarkdown)(snapshot, findings));
138
+ }
139
+ }
140
+ catch (e) {
141
+ output.error(e instanceof Error ? e.message : String(e));
142
+ }
143
+ }
144
+ async function autoDetectRoot(url, opts) {
145
+ const launcher = new launcher_1.BrowserLauncher(opts);
146
+ try {
147
+ await launcher.open(url);
148
+ const candidates = await launcher.autoDetectRoot();
149
+ if (candidates.length > 0)
150
+ return candidates[0];
151
+ return null;
152
+ }
153
+ finally {
154
+ await launcher.close();
155
+ }
156
+ }
157
+ // ── login ──
158
+ async function handleLogin(command, cmdEntry, args, output) {
159
+ try {
160
+ const cmdArgs = splitArgs(args);
161
+ const parsed = (0, command_1.parseCommand)(command, cmdArgs);
162
+ const config = (0, config_1.loadConfig)(args);
163
+ const url = parsed.url;
164
+ const browser = parsed.browser || config.browser;
165
+ const outPath = parsed.out || defaultStatePath(url);
166
+ if (!url)
167
+ output.error('URL is required. Usage: cssprobe-cli login <url>');
168
+ const launcher = new launcher_1.BrowserLauncher({ browser, headed: true });
169
+ const savedPath = await launcher.loginAndSave(url, outPath);
170
+ await launcher.close();
171
+ console.log(output.format({ saved: savedPath, usage: `cssprobe-cli inspect <url> --state ${savedPath}` }));
172
+ }
173
+ catch (e) {
174
+ output.error(e instanceof Error ? e.message : String(e));
175
+ }
176
+ }
177
+ function defaultStatePath(url) {
178
+ try {
179
+ const hostname = new URL(url).hostname.replace(/[^a-z0-9.-]/g, '_');
180
+ const dir = path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states');
181
+ return path_1.default.join(dir, `${hostname}.json`);
182
+ }
183
+ catch {
184
+ return path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', 'default.json');
185
+ }
186
+ }
187
+ // ── config commands ──
188
+ function handleConfigCommands(commandName, command, args, output) {
189
+ try {
190
+ const cmdArgs = splitArgs(args);
191
+ const parsed = (0, command_1.parseCommand)(command, cmdArgs);
192
+ switch (commandName) {
193
+ case 'config-show': {
194
+ const profileName = parsed.profile || args.profile || undefined;
195
+ if (profileName) {
196
+ const rc = (0, config_1.getRcConfig)();
197
+ const profile = rc.profiles[profileName];
198
+ if (!profile)
199
+ output.error(`Profile '${profileName}' not found`);
200
+ console.log(output.format({ active: rc.active, profile: profileName, ...profile }));
201
+ return true;
202
+ }
203
+ const config = (0, config_1.loadConfig)(args);
204
+ console.log(output.format((0, config_1.maskConfig)(config)));
205
+ return true;
206
+ }
207
+ case 'config-set': {
208
+ const profileName = parsed.profile || args.profile || undefined;
209
+ const result = (0, config_1.writeRcConfig)(parsed.key, parsed.value, profileName);
210
+ console.log(output.format(result));
211
+ return true;
212
+ }
213
+ case 'config-path': {
214
+ console.log(output.format(config_1.rcFilePath));
215
+ return true;
216
+ }
217
+ case 'config-list': {
218
+ const rc = (0, config_1.getRcConfig)();
219
+ const profiles = Object.entries(rc.profiles).map(([name, p]) => ({
220
+ name,
221
+ active: name === rc.active,
222
+ ...p,
223
+ }));
224
+ console.log(output.format(profiles));
225
+ return true;
226
+ }
227
+ case 'config-use': {
228
+ (0, config_1.setActiveProfile)(parsed.name);
229
+ console.log(output.format({ active: parsed.name }));
230
+ return true;
231
+ }
232
+ case 'config-new': {
233
+ (0, config_1.createProfile)(parsed.name);
234
+ console.log(output.format({ created: parsed.name }));
235
+ return true;
236
+ }
237
+ }
238
+ }
239
+ catch (e) {
240
+ output.error(e instanceof Error ? e.message : String(e));
241
+ }
242
+ return false;
243
+ }
244
+ // ── skill commands ──
245
+ const installer_1 = require("../installer");
246
+ function handleSkillCommands(commandName, command, args, output) {
247
+ if (commandName !== 'skill-install' && commandName !== 'skill-uninstall')
248
+ return false;
249
+ const targetFlag = args.target || 'auto';
250
+ const loc = args.local ? 'local' : 'global';
251
+ const baseDir = args.path || os_1.default.homedir();
252
+ try {
253
+ const targets = (0, installer_1.resolveTargetFlag)(targetFlag, loc);
254
+ if (targets.length === 0) {
255
+ console.log(output.format({ installed: [], note: 'no targets selected (--target=none)' }));
256
+ return true;
257
+ }
258
+ const files = [];
259
+ for (const target of targets) {
260
+ const result = commandName === 'skill-install'
261
+ ? target.install(loc)
262
+ : target.uninstall(loc);
263
+ for (const f of result.files) {
264
+ if (args.path) {
265
+ const rel = f.path.replace(os_1.default.homedir(), '').replace(/^\//, '');
266
+ f.path = path_1.default.join(baseDir, rel);
267
+ }
268
+ files.push({ ...f, agent: target.displayName });
269
+ }
270
+ }
271
+ const summary = files.map(f => `${f.agent}: ${f.action} ${f.path}`);
272
+ console.log(output.format({ files: summary }));
273
+ return true;
274
+ }
275
+ catch (e) {
276
+ output.error(e instanceof Error ? e.message : String(e));
277
+ }
278
+ return false;
279
+ }
280
+ /**
281
+ * Parse Netscape cookie format (tab-separated):
282
+ * domain flag path secure expires name value
283
+ *
284
+ * Also supports lines starting with # as comments, and blank lines.
285
+ */
286
+ function parseNetscapeCookies(content) {
287
+ const cookies = [];
288
+ const lines = content.split('\n');
289
+ for (const line of lines) {
290
+ const trimmed = line.trim();
291
+ if (!trimmed || trimmed.startsWith('#'))
292
+ continue;
293
+ const parts = trimmed.split('\t');
294
+ if (parts.length < 7)
295
+ continue;
296
+ const [domain, flag, path, secure, expires, name, ...valueParts] = parts;
297
+ const value = valueParts.join('\t'); // value may contain tabs
298
+ const expiresNum = parseInt(expires, 10);
299
+ cookies.push({
300
+ name,
301
+ value,
302
+ domain,
303
+ path: path || '/',
304
+ expires: isNaN(expiresNum) || expiresNum === 0 ? -1 : expiresNum,
305
+ httpOnly: false,
306
+ secure: secure === 'TRUE',
307
+ sameSite: secure === 'TRUE' ? 'None' : 'Lax',
308
+ });
309
+ }
310
+ return cookies;
311
+ }
312
+ async function handleStateImport(command, cmdEntry, args, output) {
313
+ try {
314
+ const cmdArgs = splitArgs(args);
315
+ const parsed = (0, command_1.parseCommand)(command, cmdArgs);
316
+ const filePath = parsed.file;
317
+ const outPath = parsed.out || path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', 'imported.json');
318
+ const mergePath = parsed.merge;
319
+ // Read cookie content from file or stdin
320
+ let content;
321
+ if (filePath) {
322
+ const resolved = path_1.default.resolve(filePath);
323
+ if (!fs_1.default.existsSync(resolved)) {
324
+ output.error(`File not found: ${resolved}`);
325
+ return;
326
+ }
327
+ content = fs_1.default.readFileSync(resolved, 'utf-8');
328
+ }
329
+ else {
330
+ // Read from stdin
331
+ content = '';
332
+ for await (const chunk of process.stdin) {
333
+ content += chunk;
334
+ }
335
+ }
336
+ if (!content.trim()) {
337
+ output.error('No cookie data provided. Provide a file path or pipe data to stdin.');
338
+ return;
339
+ }
340
+ // Parse cookies
341
+ const newCookies = parseNetscapeCookies(content);
342
+ if (newCookies.length === 0) {
343
+ output.error('No valid cookies found. Expected Netscape format (tab-separated: domain\\tflag\\tpath\\tsecure\\texpires\\tname\\tvalue)');
344
+ return;
345
+ }
346
+ // Load existing state if merging
347
+ let state;
348
+ if (mergePath && fs_1.default.existsSync(mergePath)) {
349
+ state = JSON.parse(fs_1.default.readFileSync(mergePath, 'utf-8'));
350
+ console.error(`Merging into existing state: ${mergePath} (${state.cookies.length} cookies)`);
351
+ }
352
+ else {
353
+ state = { cookies: [], origins: [] };
354
+ }
355
+ // Merge cookies (new cookies override existing ones with same domain+name+path)
356
+ const keyed = {};
357
+ for (const c of state.cookies) {
358
+ keyed[`${c.domain}|${c.name}|${c.path}`] = c;
359
+ }
360
+ for (const nc of newCookies) {
361
+ keyed[`${nc.domain}|${nc.name}|${nc.path}`] = nc;
362
+ }
363
+ state.cookies = Object.values(keyed);
364
+ // Write output
365
+ const resolvedOut = path_1.default.resolve(outPath);
366
+ const dir = path_1.default.dirname(resolvedOut);
367
+ if (!fs_1.default.existsSync(dir))
368
+ fs_1.default.mkdirSync(dir, { recursive: true });
369
+ fs_1.default.writeFileSync(resolvedOut, JSON.stringify(state, null, 2) + '\n');
370
+ console.log(output.format({
371
+ saved: resolvedOut,
372
+ cookies: state.cookies.length,
373
+ imported: newCookies.length,
374
+ usage: `cssprobe-cli inspect <url> <selector> --state ${resolvedOut}`,
375
+ }));
376
+ }
377
+ catch (e) {
378
+ output.error(e instanceof Error ? e.message : String(e));
379
+ }
380
+ }
381
+ //# sourceMappingURL=program.js.map