@jackwener/opencli 1.7.13 → 1.7.15

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 (97) hide show
  1. package/cli-manifest.json +326 -44
  2. package/clis/bilibili/subtitle.js +1 -1
  3. package/clis/dianping/cityResolver.js +185 -0
  4. package/clis/dianping/dianping.test.js +154 -0
  5. package/clis/dianping/search.js +6 -3
  6. package/clis/douyin/_shared/browser-fetch.js +14 -2
  7. package/clis/douyin/_shared/browser-fetch.test.js +13 -0
  8. package/clis/douyin/stats.js +1 -1
  9. package/clis/douyin/update.js +1 -1
  10. package/clis/jike/search.js +1 -1
  11. package/clis/reddit/search.js +1 -1
  12. package/clis/reddit/subreddit.js +1 -1
  13. package/clis/reddit/user-comments.js +1 -1
  14. package/clis/reddit/user-posts.js +1 -1
  15. package/clis/reddit/user.js +1 -1
  16. package/clis/twitter/article.js +2 -1
  17. package/clis/twitter/bookmark-folder.js +189 -0
  18. package/clis/twitter/bookmark-folder.test.js +334 -0
  19. package/clis/twitter/bookmark-folders.js +117 -0
  20. package/clis/twitter/bookmark-folders.test.js +150 -0
  21. package/clis/twitter/bookmark.js +15 -6
  22. package/clis/twitter/bookmark.test.js +74 -0
  23. package/clis/twitter/bookmarks.js +7 -5
  24. package/clis/twitter/delete.js +11 -35
  25. package/clis/twitter/delete.test.js +21 -9
  26. package/clis/twitter/download.js +5 -5
  27. package/clis/twitter/followers.js +9 -3
  28. package/clis/twitter/following.js +11 -5
  29. package/clis/twitter/hide-reply.js +24 -5
  30. package/clis/twitter/hide-reply.test.js +76 -0
  31. package/clis/twitter/like.js +21 -11
  32. package/clis/twitter/like.test.js +73 -0
  33. package/clis/twitter/likes.js +8 -6
  34. package/clis/twitter/list-add.js +4 -4
  35. package/clis/twitter/list-remove.js +4 -4
  36. package/clis/twitter/list-tweets.js +6 -4
  37. package/clis/twitter/lists.js +3 -3
  38. package/clis/twitter/notifications.js +2 -2
  39. package/clis/twitter/profile.js +4 -3
  40. package/clis/twitter/quote.js +167 -0
  41. package/clis/twitter/quote.test.js +194 -0
  42. package/clis/twitter/reply.js +24 -178
  43. package/clis/twitter/reply.test.js +29 -11
  44. package/clis/twitter/retweet.js +94 -0
  45. package/clis/twitter/retweet.test.js +73 -0
  46. package/clis/twitter/search.js +175 -23
  47. package/clis/twitter/search.test.js +266 -1
  48. package/clis/twitter/shared.js +81 -0
  49. package/clis/twitter/shared.test.js +134 -1
  50. package/clis/twitter/thread.js +6 -4
  51. package/clis/twitter/timeline.js +8 -6
  52. package/clis/twitter/tweets.js +5 -3
  53. package/clis/twitter/unbookmark.js +13 -6
  54. package/clis/twitter/unbookmark.test.js +73 -0
  55. package/clis/twitter/unlike.js +80 -0
  56. package/clis/twitter/unlike.test.js +75 -0
  57. package/clis/twitter/unretweet.js +94 -0
  58. package/clis/twitter/unretweet.test.js +73 -0
  59. package/clis/twitter/utils.js +286 -0
  60. package/clis/twitter/utils.test.js +169 -0
  61. package/dist/src/browser/ax-snapshot.d.ts +37 -0
  62. package/dist/src/browser/ax-snapshot.js +217 -0
  63. package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
  64. package/dist/src/browser/ax-snapshot.test.js +91 -0
  65. package/dist/src/browser/base-page.d.ts +51 -0
  66. package/dist/src/browser/base-page.js +545 -2
  67. package/dist/src/browser/base-page.test.js +520 -4
  68. package/dist/src/browser/bridge.js +47 -45
  69. package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
  70. package/dist/src/browser/cdp-click-fixture.test.js +87 -0
  71. package/dist/src/browser/cdp.js +5 -0
  72. package/dist/src/browser/cdp.test.js +1 -0
  73. package/dist/src/browser/daemon-client.d.ts +3 -1
  74. package/dist/src/browser/find.d.ts +9 -1
  75. package/dist/src/browser/find.js +219 -0
  76. package/dist/src/browser/find.test.js +61 -1
  77. package/dist/src/browser/page.d.ts +2 -1
  78. package/dist/src/browser/page.js +13 -0
  79. package/dist/src/browser/page.test.js +28 -0
  80. package/dist/src/browser/target-errors.d.ts +3 -1
  81. package/dist/src/browser/target-errors.js +2 -0
  82. package/dist/src/browser/target-resolver.d.ts +14 -0
  83. package/dist/src/browser/target-resolver.js +28 -0
  84. package/dist/src/browser/visual-refs.d.ts +11 -0
  85. package/dist/src/browser/visual-refs.js +108 -0
  86. package/dist/src/browser.test.js +18 -0
  87. package/dist/src/build-manifest.d.ts +23 -0
  88. package/dist/src/build-manifest.js +34 -0
  89. package/dist/src/build-manifest.test.js +108 -1
  90. package/dist/src/cli.js +560 -58
  91. package/dist/src/cli.test.js +689 -1
  92. package/dist/src/commanderAdapter.js +23 -4
  93. package/dist/src/help.d.ts +36 -0
  94. package/dist/src/help.js +301 -5
  95. package/dist/src/types.d.ts +82 -0
  96. package/package.json +1 -1
  97. package/scripts/typed-error-lint-baseline.json +18 -18
@@ -12,10 +12,9 @@
12
12
  import { log } from './logger.js';
13
13
  import yaml from 'js-yaml';
14
14
  import { fullName, getRegistry } from './registry.js';
15
- import { formatRegistryHelpText } from './serialization.js';
16
15
  import { render as renderOutput } from './output.js';
17
16
  import { executeCommand, prepareCommandArgs } from './execution.js';
18
- import { commandHelpData, formatSiteCommandDescription, installStructuredHelp, siteHelpData, } from './help.js';
17
+ import { commandHelpData, formatCommandHelpText, formatCommandListTerm, formatSiteCommandDescription, formatSiteHelpText, getRequestedHelpFormat, renderStructuredHelp, siteHelpData, } from './help.js';
19
18
  import { CliError, EXIT_CODES, toEnvelope, } from './errors.js';
20
19
  /**
21
20
  * Register a single CliCommand as a Commander subcommand.
@@ -49,7 +48,16 @@ export function registerCommandToProgram(siteCmd, cmd) {
49
48
  .option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
50
49
  .option('--trace <mode>', 'Trace capture: off, on, retain-on-failure', 'off')
51
50
  .option('-v, --verbose', 'Debug output', false);
52
- installStructuredHelp(subCmd, () => commandHelpData(cmd), () => formatRegistryHelpText(cmd));
51
+ const originalHelpInformation = subCmd.helpInformation.bind(subCmd);
52
+ subCmd.helpInformation = ((contextOptions) => {
53
+ const format = getRequestedHelpFormat();
54
+ if (format)
55
+ return renderStructuredHelp(commandHelpData(cmd), format);
56
+ // Keep a fallback reference so future Commander upgrades still initialize
57
+ // internal help state before we render the cleaner grouped command help.
58
+ void originalHelpInformation(contextOptions);
59
+ return formatCommandHelpText(cmd);
60
+ });
53
61
  subCmd.action(async (...actionArgs) => {
54
62
  const actionOpts = actionArgs[positionalArgs.length] ?? {};
55
63
  const optionsRecord = typeof actionOpts === 'object' && actionOpts !== null ? actionOpts : {};
@@ -174,7 +182,18 @@ export function registerAllCommands(program, siteGroups) {
174
182
  for (const cmd of commands) {
175
183
  registerCommandToProgram(siteCmd, cmd);
176
184
  }
177
- installStructuredHelp(siteCmd, () => siteHelpData(site, commands));
185
+ const commandTerms = new Map(commands.map(cmd => [cmd.name, formatCommandListTerm(cmd)]));
186
+ siteCmd.configureHelp({
187
+ subcommandTerm: command => commandTerms.get(command.name()) ?? command.name(),
188
+ });
189
+ const originalSiteHelpInformation = siteCmd.helpInformation.bind(siteCmd);
190
+ siteCmd.helpInformation = ((contextOptions) => {
191
+ const format = getRequestedHelpFormat();
192
+ if (format)
193
+ return renderStructuredHelp(siteHelpData(site, commands), format);
194
+ void originalSiteHelpInformation(contextOptions);
195
+ return formatSiteHelpText(site, commands);
196
+ });
178
197
  }
179
198
  return [...commandsBySite.keys()].sort((a, b) => a.localeCompare(b));
180
199
  }
@@ -1,6 +1,24 @@
1
1
  import { Command } from 'commander';
2
2
  import type { CliCommand } from './registry.js';
3
3
  export type StructuredHelpFormat = 'yaml' | 'json';
4
+ export interface ArgSpec {
5
+ name: string;
6
+ required?: true;
7
+ variadic?: true;
8
+ help?: string;
9
+ default?: unknown;
10
+ choices?: string[];
11
+ }
12
+ export interface OptionSpec {
13
+ name: string;
14
+ flags: string;
15
+ help?: string;
16
+ takes_value?: 'required' | 'optional';
17
+ required?: true;
18
+ default?: unknown;
19
+ choices?: string[];
20
+ negate?: true;
21
+ }
4
22
  export declare function getRequestedHelpFormat(argv?: readonly string[]): StructuredHelpFormat | undefined;
5
23
  export declare function renderStructuredHelp(data: unknown, format: StructuredHelpFormat): string;
6
24
  export declare function wrapCommaList(items: readonly string[], opts?: {
@@ -29,8 +47,26 @@ export interface RootAdapterGroups {
29
47
  sites: readonly string[];
30
48
  }
31
49
  export declare function formatRootAdapterHelpText(groups: RootAdapterGroups): string;
50
+ export declare function commanderNamespaceHelpData(namespaceRoot: Command, opts?: {
51
+ globalCommand?: Command;
52
+ description?: string;
53
+ }): Record<string, unknown>;
54
+ export declare function commanderCommandHelpData(namespaceRoot: Command, command: Command, opts?: {
55
+ globalCommand?: Command;
56
+ }): Record<string, unknown>;
57
+ export declare function commanderGroupHelpData(namespaceRoot: Command, groupCommand: Command, opts?: {
58
+ globalCommand?: Command;
59
+ }): Record<string, unknown>;
60
+ export declare function installCommanderNamespaceStructuredHelp(namespaceRoot: Command, opts?: {
61
+ globalCommand?: Command;
62
+ description?: string;
63
+ }): void;
64
+ export declare function formatCommandListTerm(cmd: CliCommand): string;
32
65
  export declare function rootHelpData(program: Command, groups: RootAdapterGroups): Record<string, unknown>;
33
66
  export declare function siteHelpData(site: string, commands: readonly CliCommand[]): Record<string, unknown>;
34
67
  export declare function commandHelpData(cmd: CliCommand): Record<string, unknown>;
68
+ export declare function formatCommonOptionsHelpText(): string;
69
+ export declare function formatSiteHelpText(site: string, commands: readonly CliCommand[]): string;
70
+ export declare function formatCommandHelpText(cmd: CliCommand): string;
35
71
  export declare function installStructuredHelp(command: Command, data: () => unknown, textSuffix?: string | (() => string)): void;
36
72
  export declare function formatSiteCommandDescription(cmd: CliCommand): string;
package/dist/src/help.js CHANGED
@@ -1,6 +1,33 @@
1
1
  import yaml from 'js-yaml';
2
2
  import { fullName } from './registry.js';
3
3
  import { formatCommandExample } from './serialization.js';
4
+ const COMMON_OPTIONS = [
5
+ {
6
+ flags: '-f, --format <fmt>',
7
+ name: 'format',
8
+ help: 'Output format: table, plain, json, yaml, md, csv',
9
+ default: 'table',
10
+ choices: ['table', 'plain', 'json', 'yaml', 'md', 'csv'],
11
+ },
12
+ {
13
+ flags: '--trace <mode>',
14
+ name: 'trace',
15
+ help: 'Trace capture: off, on, retain-on-failure',
16
+ default: 'off',
17
+ choices: ['off', 'on', 'retain-on-failure'],
18
+ },
19
+ {
20
+ flags: '-v, --verbose',
21
+ name: 'verbose',
22
+ help: 'Debug output',
23
+ default: false,
24
+ },
25
+ {
26
+ flags: '-h, --help',
27
+ name: 'help',
28
+ help: 'display help for command',
29
+ },
30
+ ];
4
31
  function normalizeStructuredHelpFormat(value) {
5
32
  const normalized = value?.toLowerCase();
6
33
  if (normalized === 'yaml' || normalized === 'yml')
@@ -73,7 +100,7 @@ export function formatRootAdapterHelpText(groups) {
73
100
  lines.push(...formatGroupSection('App adapters', groups.apps));
74
101
  lines.push(...formatGroupSection('Site adapters', groups.sites));
75
102
  lines.push("Run 'opencli list' for full command details, or 'opencli <site> --help' to inspect one site.");
76
- lines.push("Agent tip: use 'opencli <site> --help -f yaml' for structured commands, args, access, and examples.");
103
+ lines.push("Agent tip: use 'opencli <site> --help -f yaml' for all command args/options in one structured response.");
77
104
  lines.push('');
78
105
  return lines.join('\n');
79
106
  }
@@ -89,18 +116,201 @@ function compactArg(arg) {
89
116
  ...(arg.help ? { help: arg.help } : {}),
90
117
  };
91
118
  }
92
- function compactCommand(cmd, opts = {}) {
119
+ function compactCommonOption(option) {
120
+ return {
121
+ name: option.name,
122
+ flags: option.flags,
123
+ help: option.help,
124
+ ...('default' in option ? { default: option.default } : {}),
125
+ ...('choices' in option ? { choices: option.choices } : {}),
126
+ };
127
+ }
128
+ function compactCommanderArgument(arg) {
129
+ return {
130
+ name: arg.name(),
131
+ ...(arg.required ? { required: true } : {}),
132
+ ...(arg.variadic ? { variadic: true } : {}),
133
+ ...(arg.description ? { help: arg.description } : {}),
134
+ ...(arg.defaultValue !== undefined ? { default: arg.defaultValue } : {}),
135
+ ...(arg.argChoices?.length ? { choices: [...arg.argChoices] } : {}),
136
+ };
137
+ }
138
+ function compactCommanderOption(option) {
139
+ if (option.hidden)
140
+ return null;
141
+ return {
142
+ name: option.attributeName(),
143
+ flags: option.flags,
144
+ ...(option.description ? { help: option.description } : {}),
145
+ ...(option.required ? { takes_value: 'required' } : {}),
146
+ ...(option.optional ? { takes_value: 'optional' } : {}),
147
+ ...(option.mandatory ? { required: true } : {}),
148
+ ...(option.defaultValue !== undefined ? { default: option.defaultValue } : {}),
149
+ ...(option.argChoices?.length ? { choices: [...option.argChoices] } : {}),
150
+ ...(option.negate ? { negate: true } : {}),
151
+ };
152
+ }
153
+ function compactCommanderOptions(options) {
154
+ return options
155
+ .map(compactCommanderOption)
156
+ .filter((option) => option !== null);
157
+ }
158
+ function commanderPath(command) {
159
+ const parts = [];
160
+ let current = command;
161
+ while (current) {
162
+ const name = current.name();
163
+ if (name)
164
+ parts.push(name);
165
+ current = current.parent;
166
+ }
167
+ return parts.reverse();
168
+ }
169
+ function commandPathFromRoot(namespaceRoot, command) {
170
+ const rootPath = commanderPath(namespaceRoot);
171
+ const commandPath = commanderPath(command);
172
+ return commandPath.slice(rootPath.length);
173
+ }
174
+ function collectLeafCommands(command) {
175
+ if (command.commands.length === 0)
176
+ return [command];
177
+ return command.commands.flatMap(child => collectLeafCommands(child));
178
+ }
179
+ function collectDescendantCommands(command) {
180
+ return command.commands.flatMap(child => [child, ...collectDescendantCommands(child)]);
181
+ }
182
+ function formatCommanderPositionals(args) {
183
+ return args
184
+ .map(arg => {
185
+ const name = `${arg.name()}${arg.variadic ? '...' : ''}`;
186
+ return arg.required ? `<${name}>` : `[${name}]`;
187
+ })
188
+ .join(' ');
189
+ }
190
+ function formatCommanderUsage(command, opts = {}) {
191
+ const path = commanderPath(command).join(' ');
192
+ const positionalText = formatCommanderPositionals(command.registeredArguments);
193
+ const hasOptions = compactCommanderOptions(command.options).length > 0
194
+ || (opts.namespaceRoot ? compactCommanderOptions(opts.namespaceRoot.options).length > 0 : false)
195
+ || (opts.globalCommand ? compactCommanderOptions(opts.globalCommand.options).length > 0 : false);
196
+ const optionText = hasOptions ? ' [options]' : '';
197
+ return `${path}${positionalText ? ` ${positionalText}` : ''}${optionText}`;
198
+ }
199
+ function compactCommanderCommand(namespaceRoot, command, opts = {}) {
200
+ const relativePath = commandPathFromRoot(namespaceRoot, command);
201
+ return {
202
+ name: relativePath.join(' '),
203
+ command: commanderPath(command).join(' '),
204
+ usage: formatCommanderUsage(command, { namespaceRoot, globalCommand: opts.globalCommand }),
205
+ description: command.description(),
206
+ ...(command.aliases().length ? { aliases: command.aliases() } : {}),
207
+ positionals: command.registeredArguments.map(compactCommanderArgument),
208
+ command_options: compactCommanderOptions(command.options),
209
+ };
210
+ }
211
+ export function commanderNamespaceHelpData(namespaceRoot, opts = {}) {
212
+ const leaves = collectLeafCommands(namespaceRoot)
213
+ .filter(command => command !== namespaceRoot)
214
+ .sort((a, b) => commandPathFromRoot(namespaceRoot, a).join(' ').localeCompare(commandPathFromRoot(namespaceRoot, b).join(' ')));
215
+ return {
216
+ namespace: namespaceRoot.name(),
217
+ command: commanderPath(namespaceRoot).join(' '),
218
+ usage: `${commanderPath(namespaceRoot).join(' ')} <command> [args] [options]`,
219
+ description: opts.description ?? namespaceRoot.description(),
220
+ command_count: leaves.length,
221
+ commands: leaves.map(command => compactCommanderCommand(namespaceRoot, command, opts)),
222
+ namespace_options: compactCommanderOptions(namespaceRoot.options),
223
+ ...(opts.globalCommand ? { global_options: compactCommanderOptions(opts.globalCommand.options) } : {}),
224
+ structured_help: {
225
+ formats: ['yaml', 'json'],
226
+ usage: `${commanderPath(namespaceRoot).join(' ')} --help -f yaml`,
227
+ },
228
+ };
229
+ }
230
+ export function commanderCommandHelpData(namespaceRoot, command, opts = {}) {
231
+ return {
232
+ namespace: namespaceRoot.name(),
233
+ ...compactCommanderCommand(namespaceRoot, command, opts),
234
+ namespace_options: compactCommanderOptions(namespaceRoot.options),
235
+ ...(opts.globalCommand ? { global_options: compactCommanderOptions(opts.globalCommand.options) } : {}),
236
+ structured_help: {
237
+ formats: ['yaml', 'json'],
238
+ usage: `${commanderPath(command).join(' ')} --help -f yaml`,
239
+ },
240
+ };
241
+ }
242
+ export function commanderGroupHelpData(namespaceRoot, groupCommand, opts = {}) {
243
+ const leaves = collectLeafCommands(groupCommand)
244
+ .filter(command => command !== groupCommand)
245
+ .sort((a, b) => commandPathFromRoot(namespaceRoot, a).join(' ').localeCompare(commandPathFromRoot(namespaceRoot, b).join(' ')));
246
+ return {
247
+ namespace: namespaceRoot.name(),
248
+ group: commandPathFromRoot(namespaceRoot, groupCommand).join(' '),
249
+ command: commanderPath(groupCommand).join(' '),
250
+ usage: `${commanderPath(groupCommand).join(' ')} <command> [args] [options]`,
251
+ description: groupCommand.description(),
252
+ command_count: leaves.length,
253
+ commands: leaves.map(command => compactCommanderCommand(namespaceRoot, command, opts)),
254
+ namespace_options: compactCommanderOptions(namespaceRoot.options),
255
+ ...(opts.globalCommand ? { global_options: compactCommanderOptions(opts.globalCommand.options) } : {}),
256
+ structured_help: {
257
+ formats: ['yaml', 'json'],
258
+ usage: `${commanderPath(groupCommand).join(' ')} --help -f yaml`,
259
+ },
260
+ };
261
+ }
262
+ export function installCommanderNamespaceStructuredHelp(namespaceRoot, opts = {}) {
263
+ installStructuredHelp(namespaceRoot, () => commanderNamespaceHelpData(namespaceRoot, opts));
264
+ for (const command of collectDescendantCommands(namespaceRoot)) {
265
+ if (command.commands.length > 0) {
266
+ installStructuredHelp(command, () => commanderGroupHelpData(namespaceRoot, command, opts));
267
+ }
268
+ else {
269
+ installStructuredHelp(command, () => commanderCommandHelpData(namespaceRoot, command, opts));
270
+ }
271
+ }
272
+ }
273
+ function positionals(cmd) {
274
+ return cmd.args.filter(arg => arg.positional);
275
+ }
276
+ function commandOptions(cmd) {
277
+ return cmd.args.filter(arg => !arg.positional);
278
+ }
279
+ function formatPositionals(args) {
280
+ return args
281
+ .map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`)
282
+ .join(' ');
283
+ }
284
+ function formatCommandOptionTerm(arg) {
285
+ if (arg.required || arg.valueRequired)
286
+ return `--${arg.name} <value>`;
287
+ return `--${arg.name} [value]`;
288
+ }
289
+ export function formatCommandListTerm(cmd) {
290
+ const positionalText = formatPositionals(positionals(cmd));
291
+ const optionText = commandOptions(cmd).length > 0 ? ' [options]' : '';
292
+ return `${cmd.name}${positionalText ? ` ${positionalText}` : ''}${optionText}`;
293
+ }
294
+ function formatUsage(cmd) {
295
+ const positionalText = formatPositionals(positionals(cmd));
296
+ return `opencli ${cmd.site} ${cmd.name}${positionalText ? ` ${positionalText}` : ''} [options]`;
297
+ }
298
+ function compactCommand(cmd) {
93
299
  return {
94
300
  name: cmd.name,
95
301
  command: `opencli ${cmd.site} ${cmd.name}`,
302
+ usage: formatUsage(cmd),
96
303
  access: cmd.access,
97
304
  description: cmd.description,
305
+ browser: !!cmd.browser,
306
+ ...(cmd.domain ? { domain: cmd.domain } : {}),
98
307
  ...(cmd.aliases?.length ? { aliases: cmd.aliases } : {}),
99
- args: cmd.args.map(compactArg),
308
+ positionals: positionals(cmd).map(compactArg),
309
+ command_options: commandOptions(cmd).map(compactArg),
100
310
  example: formatCommandExample(cmd),
101
311
  ...(cmd.browserSession ? { browserSession: cmd.browserSession } : {}),
102
312
  ...(cmd.defaultFormat ? { defaultFormat: cmd.defaultFormat } : {}),
103
- ...(opts.includeColumns && cmd.columns?.length ? { columns: cmd.columns } : {}),
313
+ ...(cmd.columns?.length ? { columns: cmd.columns } : {}),
104
314
  };
105
315
  }
106
316
  export function rootHelpData(program, groups) {
@@ -142,6 +352,7 @@ export function siteHelpData(site, commands) {
142
352
  site,
143
353
  command_count: unique.length,
144
354
  commands: unique.map(cmd => compactCommand(cmd)),
355
+ common_options: COMMON_OPTIONS.map(compactCommonOption),
145
356
  next: [
146
357
  `opencli ${site} <command> --help -f yaml`,
147
358
  `opencli ${site} <command> -f yaml`,
@@ -151,10 +362,95 @@ export function siteHelpData(site, commands) {
151
362
  export function commandHelpData(cmd) {
152
363
  return {
153
364
  site: cmd.site,
154
- ...compactCommand(cmd, { includeColumns: true }),
365
+ ...compactCommand(cmd),
366
+ common_options: COMMON_OPTIONS.map(compactCommonOption),
155
367
  output_formats: ['table', 'plain', 'yaml', 'json', 'md', 'csv'],
156
368
  };
157
369
  }
370
+ function formatRows(rows) {
371
+ if (rows.length === 0)
372
+ return [];
373
+ const width = Math.min(Math.max(...rows.map(([left]) => left.length)), 34);
374
+ return rows.map(([left, right]) => ` ${left.padEnd(width + 2)}${right}`);
375
+ }
376
+ function formatArgHelp(arg) {
377
+ const parts = [];
378
+ if (arg.help)
379
+ parts.push(arg.help);
380
+ if (arg.default !== undefined)
381
+ parts.push(`default: ${arg.default}`);
382
+ if (arg.choices?.length)
383
+ parts.push(`choices: ${arg.choices.join(', ')}`);
384
+ return parts.join(' ');
385
+ }
386
+ export function formatCommonOptionsHelpText() {
387
+ const rows = COMMON_OPTIONS.map(option => {
388
+ const details = [option.help];
389
+ if ('default' in option)
390
+ details.push(`default: ${option.default}`);
391
+ if ('choices' in option)
392
+ details.push(`choices: ${option.choices.join(', ')}`);
393
+ return [option.flags, details.join(' ')];
394
+ });
395
+ return ['Common options:', ...formatRows(rows)].join('\n');
396
+ }
397
+ export function formatSiteHelpText(site, commands) {
398
+ const unique = [...new Map(commands.map(cmd => [fullName(cmd), cmd])).values()]
399
+ .sort((a, b) => a.name.localeCompare(b.name));
400
+ const lines = [
401
+ `Usage: opencli ${site} <command> [args] [options]`,
402
+ '',
403
+ wrapCommaList(unique.map(cmd => cmd.name), { indent: '' }),
404
+ '',
405
+ 'Commands:',
406
+ ...formatRows(unique.map(cmd => [formatCommandListTerm(cmd), formatSiteCommandDescription(cmd)])),
407
+ '',
408
+ formatCommonOptionsHelpText(),
409
+ '',
410
+ `Agent tip: use 'opencli ${site} --help -f yaml' to get all command args/options in one structured response.`,
411
+ '',
412
+ ];
413
+ return lines.join('\n');
414
+ }
415
+ export function formatCommandHelpText(cmd) {
416
+ const lines = [
417
+ `Usage: ${formatUsage(cmd)}`,
418
+ '',
419
+ cmd.description,
420
+ '',
421
+ ];
422
+ const positionalRows = positionals(cmd).map(arg => [
423
+ arg.name,
424
+ formatArgHelp(arg),
425
+ ]);
426
+ if (positionalRows.length) {
427
+ lines.push('Arguments:', ...formatRows(positionalRows), '');
428
+ }
429
+ const optionRows = commandOptions(cmd).map(arg => [
430
+ formatCommandOptionTerm(arg),
431
+ formatArgHelp(arg),
432
+ ]);
433
+ if (optionRows.length) {
434
+ lines.push('Command options:', ...formatRows(optionRows), '');
435
+ }
436
+ lines.push(formatCommonOptionsHelpText(), '');
437
+ const meta = [];
438
+ meta.push(`Access: ${cmd.access}`);
439
+ meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
440
+ if (cmd.domain)
441
+ meta.push(`Domain: ${cmd.domain}`);
442
+ if (cmd.defaultFormat)
443
+ meta.push(`Default format: ${cmd.defaultFormat}`);
444
+ if (cmd.aliases?.length)
445
+ meta.push(`Aliases: ${cmd.aliases.join(', ')}`);
446
+ lines.push(meta.join(' | '));
447
+ lines.push(`Example: ${formatCommandExample(cmd)}`);
448
+ if (cmd.columns?.length)
449
+ lines.push(`Output columns: ${cmd.columns.join(', ')}`);
450
+ lines.push("Agent tip: use '--help -f yaml' for structured args/options.");
451
+ lines.push('');
452
+ return lines.join('\n');
453
+ }
158
454
  export function installStructuredHelp(command, data, textSuffix) {
159
455
  const original = command.helpInformation.bind(command);
160
456
  command.helpInformation = ((contextOptions) => {
@@ -20,6 +20,8 @@ export interface SnapshotOptions {
20
20
  raw?: boolean;
21
21
  viewportExpand?: number;
22
22
  maxTextLength?: number;
23
+ /** Observation backend. `dom` is the stable default; `ax` is an opt-in prototype. */
24
+ source?: 'dom' | 'ax';
23
25
  }
24
26
  export interface WaitOptions {
25
27
  text?: string;
@@ -27,10 +29,25 @@ export interface WaitOptions {
27
29
  time?: number;
28
30
  timeout?: number;
29
31
  }
32
+ export interface BrowserDownloadWaitResult {
33
+ downloaded: boolean;
34
+ id?: number;
35
+ filename?: string;
36
+ url?: string;
37
+ finalUrl?: string;
38
+ mime?: string;
39
+ totalBytes?: number;
40
+ state?: string;
41
+ danger?: string;
42
+ error?: string;
43
+ elapsedMs: number;
44
+ }
30
45
  export interface ScreenshotOptions {
31
46
  format?: 'png' | 'jpeg';
32
47
  quality?: number;
33
48
  fullPage?: boolean;
49
+ /** Overlay current browser-state refs on visible interactive elements. */
50
+ annotate?: boolean;
34
51
  /** Override viewport width in CSS pixels for the screenshot only (cleared after). */
35
52
  width?: number;
36
53
  /** Override viewport height in CSS pixels for the screenshot only (ignored when fullPage). */
@@ -84,6 +101,69 @@ export interface IPage {
84
101
  matches_n: number;
85
102
  match_level: 'exact' | 'stable' | 'reidentified';
86
103
  }>;
104
+ dblClick?(ref: string, opts?: {
105
+ nth?: number;
106
+ firstOnMulti?: boolean;
107
+ }): Promise<{
108
+ matches_n: number;
109
+ match_level: 'exact' | 'stable' | 'reidentified';
110
+ }>;
111
+ hover?(ref: string, opts?: {
112
+ nth?: number;
113
+ firstOnMulti?: boolean;
114
+ }): Promise<{
115
+ matches_n: number;
116
+ match_level: 'exact' | 'stable' | 'reidentified';
117
+ }>;
118
+ focus?(ref: string, opts?: {
119
+ nth?: number;
120
+ firstOnMulti?: boolean;
121
+ }): Promise<{
122
+ focused: boolean;
123
+ matches_n: number;
124
+ match_level: 'exact' | 'stable' | 'reidentified';
125
+ }>;
126
+ setChecked?(ref: string, checked: boolean, opts?: {
127
+ nth?: number;
128
+ firstOnMulti?: boolean;
129
+ }): Promise<{
130
+ checked: boolean;
131
+ changed: boolean;
132
+ matches_n: number;
133
+ match_level: 'exact' | 'stable' | 'reidentified';
134
+ kind?: string;
135
+ }>;
136
+ uploadFiles?(ref: string, files: string[], opts?: {
137
+ nth?: number;
138
+ firstOnMulti?: boolean;
139
+ }): Promise<{
140
+ uploaded: boolean;
141
+ files: number;
142
+ file_names: string[];
143
+ target: string;
144
+ matches_n: number;
145
+ match_level: 'exact' | 'stable' | 'reidentified';
146
+ multiple?: boolean;
147
+ accept?: string;
148
+ }>;
149
+ drag?(source: string, target: string, opts?: {
150
+ from?: {
151
+ nth?: number;
152
+ firstOnMulti?: boolean;
153
+ };
154
+ to?: {
155
+ nth?: number;
156
+ firstOnMulti?: boolean;
157
+ };
158
+ }): Promise<{
159
+ dragged: boolean;
160
+ source: string;
161
+ target: string;
162
+ source_matches_n: number;
163
+ target_matches_n: number;
164
+ source_match_level: 'exact' | 'stable' | 'reidentified';
165
+ target_match_level: 'exact' | 'stable' | 'reidentified';
166
+ }>;
87
167
  typeText(ref: string, text: string, opts?: {
88
168
  nth?: number;
89
169
  firstOnMulti?: boolean;
@@ -111,6 +191,7 @@ export interface IPage {
111
191
  }): Promise<any>;
112
192
  getFormState(): Promise<any>;
113
193
  wait(options: number | WaitOptions): Promise<void>;
194
+ waitForDownload?(pattern?: string, timeoutMs?: number): Promise<BrowserDownloadWaitResult>;
114
195
  tabs(): Promise<any>;
115
196
  closeTab?(target?: number | string): Promise<void>;
116
197
  newTab?(url?: string): Promise<string | undefined>;
@@ -126,6 +207,7 @@ export interface IPage {
126
207
  getInterceptedRequests(): Promise<any[]>;
127
208
  waitForCapture(timeout?: number): Promise<void>;
128
209
  screenshot(options?: ScreenshotOptions): Promise<string>;
210
+ annotatedScreenshot?(options?: ScreenshotOptions): Promise<string>;
129
211
  startNetworkCapture?(pattern?: string): Promise<boolean>;
130
212
  readNetworkCapture?(): Promise<unknown[]>;
131
213
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.7.13",
3
+ "version": "1.7.15",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },