@jackwener/opencli 0.7.5 → 0.7.8

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 (56) hide show
  1. package/.agents/skills/cross-project-adapter-migration/SKILL.md +249 -0
  2. package/.agents/workflows/cross-project-adapter-migration.md +54 -0
  3. package/dist/browser/discover.d.ts +8 -0
  4. package/dist/browser/discover.js +83 -0
  5. package/dist/browser/errors.d.ts +21 -0
  6. package/dist/browser/errors.js +54 -0
  7. package/dist/browser/index.d.ts +22 -0
  8. package/dist/browser/index.js +22 -0
  9. package/dist/browser/mcp.d.ts +33 -0
  10. package/dist/browser/mcp.js +304 -0
  11. package/dist/browser/page.d.ts +41 -0
  12. package/dist/browser/page.js +142 -0
  13. package/dist/browser/tabs.d.ts +13 -0
  14. package/dist/browser/tabs.js +70 -0
  15. package/dist/browser.test.js +1 -1
  16. package/dist/completion.d.ts +21 -0
  17. package/dist/completion.js +116 -0
  18. package/dist/doctor.js +7 -7
  19. package/dist/engine.js +6 -4
  20. package/dist/errors.d.ts +25 -0
  21. package/dist/errors.js +42 -0
  22. package/dist/logger.d.ts +22 -0
  23. package/dist/logger.js +47 -0
  24. package/dist/main.js +37 -2
  25. package/dist/pipeline/executor.js +8 -8
  26. package/dist/pipeline/steps/browser.d.ts +7 -7
  27. package/dist/pipeline/steps/intercept.d.ts +1 -1
  28. package/dist/pipeline/steps/tap.d.ts +1 -1
  29. package/dist/setup.js +1 -1
  30. package/package.json +4 -3
  31. package/scripts/clean-yaml.cjs +19 -0
  32. package/scripts/copy-yaml.cjs +21 -0
  33. package/scripts/postinstall.js +200 -0
  34. package/src/bilibili.ts +1 -1
  35. package/src/browser/discover.ts +90 -0
  36. package/src/browser/errors.ts +89 -0
  37. package/src/browser/index.ts +26 -0
  38. package/src/browser/mcp.ts +305 -0
  39. package/src/browser/page.ts +152 -0
  40. package/src/browser/tabs.ts +76 -0
  41. package/src/browser.test.ts +1 -1
  42. package/src/completion.ts +129 -0
  43. package/src/doctor.ts +13 -1
  44. package/src/engine.ts +9 -4
  45. package/src/errors.ts +48 -0
  46. package/src/logger.ts +57 -0
  47. package/src/main.ts +39 -3
  48. package/src/pipeline/executor.ts +8 -7
  49. package/src/pipeline/steps/browser.ts +18 -18
  50. package/src/pipeline/steps/intercept.ts +8 -8
  51. package/src/pipeline/steps/tap.ts +2 -2
  52. package/src/setup.ts +1 -1
  53. package/tsconfig.json +1 -2
  54. package/dist/browser.d.ts +0 -105
  55. package/dist/browser.js +0 -644
  56. package/src/browser.ts +0 -698
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Shell tab-completion support for opencli.
3
+ *
4
+ * Provides:
5
+ * - Shell script generators for bash, zsh, and fish
6
+ * - Dynamic completion logic that returns candidates for the current cursor position
7
+ */
8
+ import { getRegistry } from './registry.js';
9
+ import { CliError } from './errors.js';
10
+ // ── Dynamic completion logic ───────────────────────────────────────────────
11
+ /**
12
+ * Built-in (non-dynamic) top-level commands.
13
+ */
14
+ const BUILTIN_COMMANDS = [
15
+ 'list',
16
+ 'validate',
17
+ 'verify',
18
+ 'explore',
19
+ 'probe', // alias for explore
20
+ 'synthesize',
21
+ 'generate',
22
+ 'cascade',
23
+ 'doctor',
24
+ 'setup',
25
+ 'completion',
26
+ ];
27
+ /**
28
+ * Return completion candidates given the current command-line words and cursor index.
29
+ *
30
+ * @param words - The argv after 'opencli' (words[0] is the first arg, e.g. site name)
31
+ * @param cursor - 1-based position of the word being completed (1 = first arg)
32
+ */
33
+ export function getCompletions(words, cursor) {
34
+ // cursor === 1 → completing the first argument (site name or built-in command)
35
+ if (cursor <= 1) {
36
+ const sites = new Set();
37
+ for (const [, cmd] of getRegistry()) {
38
+ sites.add(cmd.site);
39
+ }
40
+ return [...BUILTIN_COMMANDS, ...sites].sort();
41
+ }
42
+ const site = words[0];
43
+ // If the first word is a built-in command, no further completion
44
+ if (BUILTIN_COMMANDS.includes(site)) {
45
+ return [];
46
+ }
47
+ // cursor === 2 → completing the sub-command name under a site
48
+ if (cursor === 2) {
49
+ const subcommands = [];
50
+ for (const [, cmd] of getRegistry()) {
51
+ if (cmd.site === site) {
52
+ subcommands.push(cmd.name);
53
+ }
54
+ }
55
+ return subcommands.sort();
56
+ }
57
+ // cursor >= 3 → no further completion
58
+ return [];
59
+ }
60
+ // ── Shell script generators ────────────────────────────────────────────────
61
+ export function bashCompletionScript() {
62
+ return `# Bash completion for opencli
63
+ # Add to ~/.bashrc: eval "$(opencli completion bash)"
64
+ _opencli_completions() {
65
+ local cur words cword
66
+ _get_comp_words_by_ref -n : cur words cword
67
+
68
+ local completions
69
+ completions=$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
70
+
71
+ COMPREPLY=( $(compgen -W "$completions" -- "$cur") )
72
+ __ltrim_colon_completions "$cur"
73
+ }
74
+ complete -F _opencli_completions opencli
75
+ `;
76
+ }
77
+ export function zshCompletionScript() {
78
+ return `# Zsh completion for opencli
79
+ # Add to ~/.zshrc: eval "$(opencli completion zsh)"
80
+ _opencli() {
81
+ local -a completions
82
+ local cword=$((CURRENT - 1))
83
+ completions=(\${(f)"$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
84
+ compadd -a completions
85
+ }
86
+ compdef _opencli opencli
87
+ `;
88
+ }
89
+ export function fishCompletionScript() {
90
+ return `# Fish completion for opencli
91
+ # Add to ~/.config/fish/config.fish: opencli completion fish | source
92
+ complete -c opencli -f -a '(
93
+ set -l tokens (commandline -cop)
94
+ set -l cursor (count (commandline -cop))
95
+ opencli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
96
+ )'
97
+ `;
98
+ }
99
+ /**
100
+ * Print the completion script for the requested shell.
101
+ */
102
+ export function printCompletionScript(shell) {
103
+ switch (shell) {
104
+ case 'bash':
105
+ process.stdout.write(bashCompletionScript());
106
+ break;
107
+ case 'zsh':
108
+ process.stdout.write(zshCompletionScript());
109
+ break;
110
+ case 'fish':
111
+ process.stdout.write(fishCompletionScript());
112
+ break;
113
+ default:
114
+ throw new CliError('UNSUPPORTED_SHELL', `Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
115
+ }
116
+ }
package/dist/doctor.js CHANGED
@@ -4,7 +4,7 @@ import * as path from 'node:path';
4
4
  import { createInterface } from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
6
  import chalk from 'chalk';
7
- import { PlaywrightMCP, getTokenFingerprint } from './browser.js';
7
+ import { PlaywrightMCP, getTokenFingerprint } from './browser/index.js';
8
8
  const PLAYWRIGHT_SERVER_NAME = 'playwright';
9
9
  export const PLAYWRIGHT_TOKEN_ENV = 'PLAYWRIGHT_MCP_EXTENSION_TOKEN';
10
10
  const PLAYWRIGHT_EXTENSION_ID = 'mmlmfjhmonkocbjadbfplnigmagldckm';
@@ -266,14 +266,14 @@ export function discoverExtensionToken() {
266
266
  const platform = os.platform();
267
267
  const bases = [];
268
268
  if (platform === 'darwin') {
269
- bases.push(path.join(home, 'Library', 'Application Support', 'Google', 'Chrome'), path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Canary'), path.join(home, 'Library', 'Application Support', 'Chromium'), path.join(home, 'Library', 'Application Support', 'Microsoft Edge'));
269
+ bases.push(path.join(home, 'Library', 'Application Support', 'Google', 'Chrome'), path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Dev'), path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Beta'), path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Canary'), path.join(home, 'Library', 'Application Support', 'Chromium'), path.join(home, 'Library', 'Application Support', 'Microsoft Edge'));
270
270
  }
271
271
  else if (platform === 'linux') {
272
- bases.push(path.join(home, '.config', 'google-chrome'), path.join(home, '.config', 'chromium'), path.join(home, '.config', 'microsoft-edge'));
272
+ bases.push(path.join(home, '.config', 'google-chrome'), path.join(home, '.config', 'google-chrome-unstable'), path.join(home, '.config', 'google-chrome-beta'), path.join(home, '.config', 'chromium'), path.join(home, '.config', 'microsoft-edge'));
273
273
  }
274
274
  else if (platform === 'win32') {
275
275
  const appData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
276
- bases.push(path.join(appData, 'Google', 'Chrome', 'User Data'), path.join(appData, 'Microsoft', 'Edge', 'User Data'));
276
+ bases.push(path.join(appData, 'Google', 'Chrome', 'User Data'), path.join(appData, 'Google', 'Chrome Dev', 'User Data'), path.join(appData, 'Google', 'Chrome Beta', 'User Data'), path.join(appData, 'Microsoft', 'Edge', 'User Data'));
277
277
  }
278
278
  const profiles = enumerateProfiles(bases);
279
279
  const tokenRe = /([A-Za-z0-9_-]{40,50})/;
@@ -388,14 +388,14 @@ export function checkExtensionInstalled() {
388
388
  const platform = os.platform();
389
389
  const browserDirs = [];
390
390
  if (platform === 'darwin') {
391
- browserDirs.push({ name: 'Chrome', base: path.join(home, 'Library', 'Application Support', 'Google', 'Chrome') }, { name: 'Chrome Canary', base: path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Canary') }, { name: 'Chromium', base: path.join(home, 'Library', 'Application Support', 'Chromium') }, { name: 'Edge', base: path.join(home, 'Library', 'Application Support', 'Microsoft Edge') });
391
+ browserDirs.push({ name: 'Chrome', base: path.join(home, 'Library', 'Application Support', 'Google', 'Chrome') }, { name: 'Chrome Dev', base: path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Dev') }, { name: 'Chrome Beta', base: path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Beta') }, { name: 'Chrome Canary', base: path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Canary') }, { name: 'Chromium', base: path.join(home, 'Library', 'Application Support', 'Chromium') }, { name: 'Edge', base: path.join(home, 'Library', 'Application Support', 'Microsoft Edge') });
392
392
  }
393
393
  else if (platform === 'linux') {
394
- browserDirs.push({ name: 'Chrome', base: path.join(home, '.config', 'google-chrome') }, { name: 'Chromium', base: path.join(home, '.config', 'chromium') }, { name: 'Edge', base: path.join(home, '.config', 'microsoft-edge') });
394
+ browserDirs.push({ name: 'Chrome', base: path.join(home, '.config', 'google-chrome') }, { name: 'Chrome Dev', base: path.join(home, '.config', 'google-chrome-unstable') }, { name: 'Chrome Beta', base: path.join(home, '.config', 'google-chrome-beta') }, { name: 'Chromium', base: path.join(home, '.config', 'chromium') }, { name: 'Edge', base: path.join(home, '.config', 'microsoft-edge') });
395
395
  }
396
396
  else if (platform === 'win32') {
397
397
  const appData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
398
- browserDirs.push({ name: 'Chrome', base: path.join(appData, 'Google', 'Chrome', 'User Data') }, { name: 'Edge', base: path.join(appData, 'Microsoft', 'Edge', 'User Data') });
398
+ browserDirs.push({ name: 'Chrome', base: path.join(appData, 'Google', 'Chrome', 'User Data') }, { name: 'Chrome Dev', base: path.join(appData, 'Google', 'Chrome Dev', 'User Data') }, { name: 'Chrome Beta', base: path.join(appData, 'Google', 'Chrome Beta', 'User Data') }, { name: 'Edge', base: path.join(appData, 'Microsoft', 'Edge', 'User Data') });
399
399
  }
400
400
  const profiles = enumerateProfiles(browserDirs.map(d => d.base));
401
401
  const foundBrowsers = [];
package/dist/engine.js CHANGED
@@ -12,6 +12,8 @@ import * as path from 'node:path';
12
12
  import yaml from 'js-yaml';
13
13
  import { Strategy, registerCommand } from './registry.js';
14
14
  import { executePipeline } from './pipeline.js';
15
+ import { log } from './logger.js';
16
+ import { AdapterLoadError } from './errors.js';
15
17
  /** Set of TS module paths that have been loaded */
16
18
  const _loadedModules = new Set();
17
19
  /**
@@ -81,7 +83,7 @@ function loadFromManifest(manifestPath, clisDir) {
81
83
  }
82
84
  }
83
85
  catch (err) {
84
- process.stderr.write(`Warning: failed to load manifest ${manifestPath}: ${err.message}\n`);
86
+ log.warn(`Failed to load manifest ${manifestPath}: ${err.message}`);
85
87
  }
86
88
  }
87
89
  /**
@@ -103,7 +105,7 @@ async function discoverClisFromFs(dir) {
103
105
  else if ((file.endsWith('.js') && !file.endsWith('.d.js')) ||
104
106
  (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts'))) {
105
107
  promises.push(import(`file://${filePath}`).catch((err) => {
106
- process.stderr.write(`Warning: failed to load module ${filePath}: ${err.message}\n`);
108
+ log.warn(`Failed to load module ${filePath}: ${err.message}`);
107
109
  }));
108
110
  }
109
111
  }
@@ -150,7 +152,7 @@ function registerYamlCli(filePath, defaultSite) {
150
152
  registerCommand(cmd);
151
153
  }
152
154
  catch (err) {
153
- process.stderr.write(`Warning: failed to load ${filePath}: ${err.message}\n`);
155
+ log.warn(`Failed to load ${filePath}: ${err.message}`);
154
156
  }
155
157
  }
156
158
  /**
@@ -167,7 +169,7 @@ export async function executeCommand(cmd, page, kwargs, debug = false) {
167
169
  _loadedModules.add(modulePath);
168
170
  }
169
171
  catch (err) {
170
- throw new Error(`Failed to load adapter module ${modulePath}: ${err.message}`);
172
+ throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${err.message}`, 'Check that the adapter file exists and has no syntax errors.');
171
173
  }
172
174
  }
173
175
  // After loading, the module's cli() call will have updated the registry
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Unified error types for opencli.
3
+ *
4
+ * All errors thrown by the framework should extend CliError so that
5
+ * the top-level handler in main.ts can render consistent, helpful output.
6
+ */
7
+ export declare class CliError extends Error {
8
+ /** Machine-readable error code (e.g. 'BROWSER_CONNECT', 'ADAPTER_LOAD') */
9
+ readonly code: string;
10
+ /** Human-readable hint on how to fix the problem */
11
+ readonly hint?: string;
12
+ constructor(code: string, message: string, hint?: string);
13
+ }
14
+ export declare class BrowserConnectError extends CliError {
15
+ constructor(message: string, hint?: string);
16
+ }
17
+ export declare class AdapterLoadError extends CliError {
18
+ constructor(message: string, hint?: string);
19
+ }
20
+ export declare class CommandExecutionError extends CliError {
21
+ constructor(message: string, hint?: string);
22
+ }
23
+ export declare class ConfigError extends CliError {
24
+ constructor(message: string, hint?: string);
25
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Unified error types for opencli.
3
+ *
4
+ * All errors thrown by the framework should extend CliError so that
5
+ * the top-level handler in main.ts can render consistent, helpful output.
6
+ */
7
+ export class CliError extends Error {
8
+ /** Machine-readable error code (e.g. 'BROWSER_CONNECT', 'ADAPTER_LOAD') */
9
+ code;
10
+ /** Human-readable hint on how to fix the problem */
11
+ hint;
12
+ constructor(code, message, hint) {
13
+ super(message);
14
+ this.name = 'CliError';
15
+ this.code = code;
16
+ this.hint = hint;
17
+ }
18
+ }
19
+ export class BrowserConnectError extends CliError {
20
+ constructor(message, hint) {
21
+ super('BROWSER_CONNECT', message, hint);
22
+ this.name = 'BrowserConnectError';
23
+ }
24
+ }
25
+ export class AdapterLoadError extends CliError {
26
+ constructor(message, hint) {
27
+ super('ADAPTER_LOAD', message, hint);
28
+ this.name = 'AdapterLoadError';
29
+ }
30
+ }
31
+ export class CommandExecutionError extends CliError {
32
+ constructor(message, hint) {
33
+ super('COMMAND_EXEC', message, hint);
34
+ this.name = 'CommandExecutionError';
35
+ }
36
+ }
37
+ export class ConfigError extends CliError {
38
+ constructor(message, hint) {
39
+ super('CONFIG', message, hint);
40
+ this.name = 'ConfigError';
41
+ }
42
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Unified logging for opencli.
3
+ *
4
+ * All framework output (warnings, debug info, errors) should go through
5
+ * this module so that verbosity levels are respected consistently.
6
+ */
7
+ export declare const log: {
8
+ /** Informational message (always shown) */
9
+ info(msg: string): void;
10
+ /** Warning (always shown) */
11
+ warn(msg: string): void;
12
+ /** Error (always shown) */
13
+ error(msg: string): void;
14
+ /** Verbose output (only when OPENCLI_VERBOSE is set or -v flag) */
15
+ verbose(msg: string): void;
16
+ /** Debug output (only when DEBUG includes 'opencli') */
17
+ debug(msg: string): void;
18
+ /** Step-style debug (for pipeline steps, etc.) */
19
+ step(stepNum: number, total: number, op: string, preview?: string): void;
20
+ /** Step result summary */
21
+ stepResult(summary: string): void;
22
+ };
package/dist/logger.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Unified logging for opencli.
3
+ *
4
+ * All framework output (warnings, debug info, errors) should go through
5
+ * this module so that verbosity levels are respected consistently.
6
+ */
7
+ import chalk from 'chalk';
8
+ function isVerbose() {
9
+ return !!process.env.OPENCLI_VERBOSE;
10
+ }
11
+ function isDebug() {
12
+ return !!process.env.DEBUG?.includes('opencli');
13
+ }
14
+ export const log = {
15
+ /** Informational message (always shown) */
16
+ info(msg) {
17
+ process.stderr.write(`${chalk.blue('ℹ')} ${msg}\n`);
18
+ },
19
+ /** Warning (always shown) */
20
+ warn(msg) {
21
+ process.stderr.write(`${chalk.yellow('⚠')} ${msg}\n`);
22
+ },
23
+ /** Error (always shown) */
24
+ error(msg) {
25
+ process.stderr.write(`${chalk.red('✖')} ${msg}\n`);
26
+ },
27
+ /** Verbose output (only when OPENCLI_VERBOSE is set or -v flag) */
28
+ verbose(msg) {
29
+ if (isVerbose()) {
30
+ process.stderr.write(`${chalk.dim('[verbose]')} ${msg}\n`);
31
+ }
32
+ },
33
+ /** Debug output (only when DEBUG includes 'opencli') */
34
+ debug(msg) {
35
+ if (isDebug()) {
36
+ process.stderr.write(`${chalk.dim('[debug]')} ${msg}\n`);
37
+ }
38
+ },
39
+ /** Step-style debug (for pipeline steps, etc.) */
40
+ step(stepNum, total, op, preview = '') {
41
+ process.stderr.write(` ${chalk.dim(`[${stepNum}/${total}]`)} ${chalk.bold.cyan(op)}${preview}\n`);
42
+ },
43
+ /** Step result summary */
44
+ stepResult(summary) {
45
+ process.stderr.write(` ${chalk.dim(`→ ${summary}`)}\n`);
46
+ },
47
+ };
package/dist/main.js CHANGED
@@ -10,14 +10,38 @@ import chalk from 'chalk';
10
10
  import { discoverClis, executeCommand } from './engine.js';
11
11
  import { fullName, getRegistry, strategyLabel } from './registry.js';
12
12
  import { render as renderOutput } from './output.js';
13
- import { PlaywrightMCP } from './browser.js';
13
+ import { PlaywrightMCP } from './browser/index.js';
14
14
  import { browserSession, DEFAULT_BROWSER_COMMAND_TIMEOUT, runWithTimeout } from './runtime.js';
15
15
  import { PKG_VERSION } from './version.js';
16
+ import { getCompletions, printCompletionScript } from './completion.js';
17
+ import { CliError } from './errors.js';
16
18
  const __filename = fileURLToPath(import.meta.url);
17
19
  const __dirname = path.dirname(__filename);
18
20
  const BUILTIN_CLIS = path.resolve(__dirname, 'clis');
19
21
  const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
20
22
  await discoverClis(BUILTIN_CLIS, USER_CLIS);
23
+ // ── Fast-path: handle --get-completions before commander parses ─────────
24
+ // Usage: opencli --get-completions --cursor <N> [word1 word2 ...]
25
+ const getCompIdx = process.argv.indexOf('--get-completions');
26
+ if (getCompIdx !== -1) {
27
+ const rest = process.argv.slice(getCompIdx + 1);
28
+ let cursor;
29
+ const words = [];
30
+ for (let i = 0; i < rest.length; i++) {
31
+ if (rest[i] === '--cursor' && i + 1 < rest.length) {
32
+ cursor = parseInt(rest[i + 1], 10);
33
+ i++; // skip the value
34
+ }
35
+ else {
36
+ words.push(rest[i]);
37
+ }
38
+ }
39
+ if (cursor === undefined)
40
+ cursor = words.length;
41
+ const candidates = getCompletions(words, cursor);
42
+ process.stdout.write(candidates.join('\n') + '\n');
43
+ process.exit(0);
44
+ }
21
45
  const program = new Command();
22
46
  program.name('opencli').description('Make any website your CLI. Zero setup. AI-powered.').version(PKG_VERSION);
23
47
  // ── Built-in commands ──────────────────────────────────────────────────────
@@ -130,6 +154,12 @@ program.command('setup')
130
154
  const { runSetup } = await import('./setup.js');
131
155
  await runSetup({ cliVersion: PKG_VERSION, token: opts.token });
132
156
  });
157
+ program.command('completion')
158
+ .description('Output shell completion script')
159
+ .argument('<shell>', 'Shell type: bash, zsh, or fish')
160
+ .action((shell) => {
161
+ printCompletionScript(shell);
162
+ });
133
163
  // ── Dynamic site commands ──────────────────────────────────────────────────
134
164
  const registry = getRegistry();
135
165
  const siteGroups = new Map();
@@ -199,7 +229,12 @@ for (const [, cmd] of registry) {
199
229
  renderOutput(result, { fmt: actionOpts.format, columns: cmd.columns, title: `${cmd.site}/${cmd.name}`, elapsed: (Date.now() - startTime) / 1000, source: fullName(cmd) });
200
230
  }
201
231
  catch (err) {
202
- if (actionOpts.verbose && err.stack) {
232
+ if (err instanceof CliError) {
233
+ console.error(chalk.red(`Error [${err.code}]: ${err.message}`));
234
+ if (err.hint)
235
+ console.error(chalk.yellow(`Hint: ${err.hint}`));
236
+ }
237
+ else if (actionOpts.verbose && err.stack) {
203
238
  console.error(chalk.red(err.stack));
204
239
  }
205
240
  else {
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Pipeline executor: runs YAML pipeline steps sequentially.
3
3
  */
4
- import chalk from 'chalk';
5
4
  import { stepNavigate, stepClick, stepType, stepWait, stepPress, stepSnapshot, stepEvaluate } from './steps/browser.js';
6
5
  import { stepFetch } from './steps/fetch.js';
7
6
  import { stepSelect, stepMap, stepFilter, stepSort, stepLimit } from './steps/transform.js';
8
7
  import { stepIntercept } from './steps/intercept.js';
9
8
  import { stepTap } from './steps/tap.js';
9
+ import { log } from '../logger.js';
10
10
  /** Registry of all available step handlers */
11
11
  const STEP_HANDLERS = {
12
12
  navigate: stepNavigate,
@@ -43,7 +43,7 @@ export async function executePipeline(page, pipeline, ctx = {}) {
43
43
  }
44
44
  else {
45
45
  if (debug)
46
- process.stderr.write(` ${chalk.yellow('⚠')} Unknown step: ${op}\n`);
46
+ log.warn(`Unknown step: ${op}`);
47
47
  }
48
48
  if (debug)
49
49
  debugStepResult(op, data);
@@ -59,24 +59,24 @@ function debugStepStart(stepNum, total, op, params) {
59
59
  else if (params && typeof params === 'object' && !Array.isArray(params)) {
60
60
  preview = ` (${Object.keys(params).join(', ')})`;
61
61
  }
62
- process.stderr.write(` ${chalk.dim(`[${stepNum}/${total}]`)} ${chalk.bold.cyan(op)}${preview}\n`);
62
+ log.step(stepNum, total, op, preview);
63
63
  }
64
64
  function debugStepResult(op, data) {
65
65
  if (data === null || data === undefined) {
66
- process.stderr.write(` ${chalk.dim('(no data)')}\n`);
66
+ log.stepResult('(no data)');
67
67
  }
68
68
  else if (Array.isArray(data)) {
69
- process.stderr.write(` ${chalk.dim(`→ ${data.length} items`)}\n`);
69
+ log.stepResult(`${data.length} items`);
70
70
  }
71
71
  else if (typeof data === 'object') {
72
72
  const keys = Object.keys(data).slice(0, 5);
73
- process.stderr.write(` ${chalk.dim(`→ dict (${keys.join(', ')}${Object.keys(data).length > 5 ? '...' : ''})`)}\n`);
73
+ log.stepResult(`dict (${keys.join(', ')}${Object.keys(data).length > 5 ? '...' : ''})`);
74
74
  }
75
75
  else if (typeof data === 'string') {
76
76
  const p = data.slice(0, 60).replace(/\n/g, '\\n');
77
- process.stderr.write(` ${chalk.dim(`→ "${p}${data.length > 60 ? '...' : ''}"`)}\n`);
77
+ log.stepResult(`"${p}${data.length > 60 ? '...' : ''}"`);
78
78
  }
79
79
  else {
80
- process.stderr.write(` ${chalk.dim(`→ ${typeof data}`)}\n`);
80
+ log.stepResult(`${typeof data}`);
81
81
  }
82
82
  }
@@ -3,10 +3,10 @@
3
3
  * Browser interaction primitives.
4
4
  */
5
5
  import type { IPage } from '../../types.js';
6
- export declare function stepNavigate(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
7
- export declare function stepClick(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
8
- export declare function stepType(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
9
- export declare function stepWait(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
10
- export declare function stepPress(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
11
- export declare function stepSnapshot(page: IPage, params: any, _data: any, _args: Record<string, any>): Promise<any>;
12
- export declare function stepEvaluate(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
6
+ export declare function stepNavigate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
7
+ export declare function stepClick(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
8
+ export declare function stepType(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
9
+ export declare function stepWait(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
10
+ export declare function stepPress(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
11
+ export declare function stepSnapshot(page: IPage | null, params: any, _data: any, _args: Record<string, any>): Promise<any>;
12
+ export declare function stepEvaluate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
@@ -2,4 +2,4 @@
2
2
  * Pipeline step: intercept — declarative XHR interception.
3
3
  */
4
4
  import type { IPage } from '../../types.js';
5
- export declare function stepIntercept(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
5
+ export declare function stepIntercept(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
@@ -9,4 +9,4 @@
9
9
  * 5. Returns the captured data (optionally sub-selected)
10
10
  */
11
11
  import type { IPage } from '../../types.js';
12
- export declare function stepTap(page: IPage, params: any, data: any, args: Record<string, any>): Promise<any>;
12
+ export declare function stepTap(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
package/dist/setup.js CHANGED
@@ -9,7 +9,7 @@ import chalk from 'chalk';
9
9
  import { createInterface } from 'node:readline/promises';
10
10
  import { stdin as input, stdout as output } from 'node:process';
11
11
  import { PLAYWRIGHT_TOKEN_ENV, checkExtensionInstalled, checkTokenConnectivity, discoverExtensionToken, fileExists, getDefaultShellRcPath, runBrowserDoctor, shortenPath, toolName, upsertJsonConfigToken, upsertShellToken, upsertTomlConfigToken, writeFileWithMkdir, } from './doctor.js';
12
- import { getTokenFingerprint } from './browser.js';
12
+ import { getTokenFingerprint } from './browser/index.js';
13
13
  import { checkboxPrompt } from './tui.js';
14
14
  export async function runSetup(opts = {}) {
15
15
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "0.7.5",
3
+ "version": "0.7.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -17,9 +17,10 @@
17
17
  "dev": "tsx src/main.ts",
18
18
  "build": "tsc && npm run clean-yaml && npm run copy-yaml && npm run build-manifest",
19
19
  "build-manifest": "node dist/build-manifest.js",
20
- "clean-yaml": "node -e \"const{readdirSync:r,rmSync:d,existsSync:e,statSync:s}=require('fs'),p=require('path');function w(dir){if(!e(dir))return;for(const f of r(dir)){const fp=p.join(dir,f);s(fp).isDirectory()?w(fp):/\\.ya?ml$/.test(f)&&d(fp)}}w('dist/clis')\"",
21
- "copy-yaml": "node -e \"const{readdirSync:r,copyFileSync:c,mkdirSync:m,existsSync:e,statSync:s}=require('fs'),p=require('path');function w(src,dst){if(!e(src))return;for(const f of r(src)){const sp=p.join(src,f),dp=p.join(dst,f);s(sp).isDirectory()?w(sp,dp):/\\.ya?ml$/.test(f)&&(m(p.dirname(dp),{recursive:!0}),c(sp,dp))}}w('src/clis','dist/clis')\"",
20
+ "clean-yaml": "node scripts/clean-yaml.cjs",
21
+ "copy-yaml": "node scripts/copy-yaml.cjs",
22
22
  "start": "node dist/main.js",
23
+ "postinstall": "node scripts/postinstall.js || true",
23
24
  "typecheck": "tsc --noEmit",
24
25
  "lint": "tsc --noEmit",
25
26
  "prepublishOnly": "npm run build",
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Clean YAML files from dist/clis/ before copying fresh ones.
3
+ */
4
+ const { readdirSync, rmSync, existsSync, statSync } = require('fs');
5
+ const path = require('path');
6
+
7
+ function walk(dir) {
8
+ if (!existsSync(dir)) return;
9
+ for (const f of readdirSync(dir)) {
10
+ const fp = path.join(dir, f);
11
+ if (statSync(fp).isDirectory()) {
12
+ walk(fp);
13
+ } else if (/\.ya?ml$/.test(f)) {
14
+ rmSync(fp);
15
+ }
16
+ }
17
+ }
18
+
19
+ walk('dist/clis');
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copy YAML files from src/clis/ to dist/clis/.
3
+ */
4
+ const { readdirSync, copyFileSync, mkdirSync, existsSync, statSync } = require('fs');
5
+ const path = require('path');
6
+
7
+ function walk(src, dst) {
8
+ if (!existsSync(src)) return;
9
+ for (const f of readdirSync(src)) {
10
+ const sp = path.join(src, f);
11
+ const dp = path.join(dst, f);
12
+ if (statSync(sp).isDirectory()) {
13
+ walk(sp, dp);
14
+ } else if (/\.ya?ml$/.test(f)) {
15
+ mkdirSync(path.dirname(dp), { recursive: true });
16
+ copyFileSync(sp, dp);
17
+ }
18
+ }
19
+ }
20
+
21
+ walk('src/clis', 'dist/clis');