@agentrhq/webcmd 0.2.5 → 0.3.1

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 (200) hide show
  1. package/README.md +13 -2
  2. package/cli-manifest.json +217 -13
  3. package/clis/_shared/site-auth.js +3 -3
  4. package/clis/_shared/site-auth.test.js +4 -4
  5. package/clis/chatgpt/ask.js +1 -1
  6. package/clis/chatgpt/ask.test.js +11 -0
  7. package/clis/chatgpt/commands.test.js +102 -1
  8. package/clis/chatgpt/deep-research-result.js +45 -3
  9. package/clis/chatgpt/image.js +22 -2
  10. package/clis/chatgpt/project-file-add.js +24 -1
  11. package/clis/chatgpt/utils.js +258 -52
  12. package/clis/chatgpt/utils.test.js +259 -1
  13. package/clis/claude/ask.js +21 -1
  14. package/clis/facebook/feed.js +110 -24
  15. package/clis/facebook/feed.test.js +62 -0
  16. package/clis/facebook/search.js +185 -38
  17. package/clis/facebook/search.test.js +95 -50
  18. package/clis/instagram/explore.js +30 -12
  19. package/clis/instagram/explore.test.js +77 -0
  20. package/clis/instagram/post.js +14 -1
  21. package/clis/instagram/reel.js +13 -1
  22. package/clis/linkedin/company.js +153 -0
  23. package/clis/linkedin/company.test.js +111 -0
  24. package/clis/linkedin/connections.js +135 -0
  25. package/clis/linkedin/connections.test.js +141 -0
  26. package/clis/mercury/reimbursement-draft.js +12 -1
  27. package/clis/slock/whoami.test.js +2 -2
  28. package/clis/twitter/article-evaluate.test.js +27 -0
  29. package/clis/twitter/article.js +73 -16
  30. package/clis/twitter/article.test.js +209 -0
  31. package/clis/twitter/download.js +6 -1
  32. package/clis/twitter/post.js +14 -1
  33. package/clis/twitter/profile.js +1 -1
  34. package/clis/twitter/profile.test.js +8 -0
  35. package/clis/twitter/quote.js +11 -1
  36. package/clis/twitter/reply.js +11 -1
  37. package/dist/src/browser/base-page.d.ts +26 -0
  38. package/dist/src/browser/base-page.js +23 -8
  39. package/dist/src/browser/base-page.test.js +45 -12
  40. package/dist/src/browser/command-catalog.d.ts +6 -0
  41. package/dist/src/browser/command-catalog.js +259 -0
  42. package/dist/src/browser/command-catalog.test.d.ts +1 -0
  43. package/dist/src/browser/command-catalog.test.js +79 -0
  44. package/dist/src/browser/daemon-client.d.ts +2 -1
  45. package/dist/src/browser/runtime/local-cloak/actions.js +4 -1
  46. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.d.ts +17 -0
  47. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.js +106 -0
  48. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.d.ts +1 -0
  49. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.js +65 -0
  50. package/dist/src/browser/runtime/local-cloak/provider.test.js +36 -0
  51. package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +10 -2
  52. package/dist/src/browser/runtime/local-cloak/session-manager.js +25 -9
  53. package/dist/src/browser/runtime/local-cloak/session-manager.test.js +36 -0
  54. package/dist/src/browser/target-resolver.d.ts +1 -0
  55. package/dist/src/browser/target-resolver.js +99 -4
  56. package/dist/src/browser/target-resolver.test.js +141 -1
  57. package/dist/src/build-manifest.d.ts +6 -0
  58. package/dist/src/build-manifest.js +18 -1
  59. package/dist/src/build-manifest.test.js +57 -1
  60. package/dist/src/builtin-command-surface.d.ts +9 -0
  61. package/dist/src/builtin-command-surface.js +16 -0
  62. package/dist/src/check-hosted-contract.test.d.ts +1 -0
  63. package/dist/src/check-hosted-contract.test.js +56 -0
  64. package/dist/src/cli.js +63 -110
  65. package/dist/src/cli.test.js +82 -6
  66. package/dist/src/command-presentation.d.ts +95 -0
  67. package/dist/src/command-presentation.js +486 -0
  68. package/dist/src/command-presentation.test.d.ts +1 -0
  69. package/dist/src/command-presentation.test.js +97 -0
  70. package/dist/src/command-surface.d.ts +43 -0
  71. package/dist/src/command-surface.js +205 -0
  72. package/dist/src/command-surface.test.d.ts +1 -0
  73. package/dist/src/command-surface.test.js +406 -0
  74. package/dist/src/commanderAdapter.d.ts +5 -1
  75. package/dist/src/commanderAdapter.js +12 -55
  76. package/dist/src/commanderAdapter.test.js +15 -3
  77. package/dist/src/commands/auth.js +3 -2
  78. package/dist/src/commands/auth.test.js +6 -6
  79. package/dist/src/community-plugin-sync.d.ts +11 -0
  80. package/dist/src/community-plugin-sync.js +138 -0
  81. package/dist/src/community-plugin-sync.test.d.ts +1 -0
  82. package/dist/src/community-plugin-sync.test.js +123 -0
  83. package/dist/src/completion-fast.d.ts +4 -4
  84. package/dist/src/completion-fast.js +15 -34
  85. package/dist/src/completion-shared.d.ts +4 -0
  86. package/dist/src/completion-shared.js +38 -0
  87. package/dist/src/completion.js +3 -27
  88. package/dist/src/completion.test.js +31 -2
  89. package/dist/src/docs-sync-review-cli.test.d.ts +1 -0
  90. package/dist/src/docs-sync-review-cli.test.js +322 -0
  91. package/dist/src/docs-sync-review.d.ts +117 -0
  92. package/dist/src/docs-sync-review.js +475 -0
  93. package/dist/src/docs-sync-review.test.d.ts +1 -0
  94. package/dist/src/docs-sync-review.test.js +324 -0
  95. package/dist/src/errors.d.ts +12 -3
  96. package/dist/src/errors.js +11 -7
  97. package/dist/src/errors.test.js +14 -1
  98. package/dist/src/execution.d.ts +1 -2
  99. package/dist/src/execution.js +4 -45
  100. package/dist/src/generate-release-notes-cli.test.js +11 -1
  101. package/dist/src/help.d.ts +4 -0
  102. package/dist/src/help.js +50 -255
  103. package/dist/src/help.test.js +27 -1
  104. package/dist/src/hosted/args.d.ts +4 -0
  105. package/dist/src/hosted/args.js +4 -0
  106. package/dist/src/hosted/args.test.d.ts +1 -0
  107. package/dist/src/hosted/args.test.js +49 -0
  108. package/dist/src/hosted/availability.d.ts +13 -0
  109. package/dist/src/hosted/availability.js +16 -0
  110. package/dist/src/hosted/availability.test.d.ts +1 -0
  111. package/dist/src/hosted/availability.test.js +180 -0
  112. package/dist/src/hosted/browser-args.d.ts +18 -0
  113. package/dist/src/hosted/browser-args.js +152 -0
  114. package/dist/src/hosted/browser-args.test.d.ts +1 -0
  115. package/dist/src/hosted/browser-args.test.js +182 -0
  116. package/dist/src/hosted/client.d.ts +59 -0
  117. package/dist/src/hosted/client.js +510 -0
  118. package/dist/src/hosted/client.test.d.ts +1 -0
  119. package/dist/src/hosted/client.test.js +738 -0
  120. package/dist/src/hosted/config.d.ts +55 -0
  121. package/dist/src/hosted/config.js +112 -0
  122. package/dist/src/hosted/config.test.d.ts +1 -0
  123. package/dist/src/hosted/config.test.js +81 -0
  124. package/dist/src/hosted/contract.d.ts +94 -0
  125. package/dist/src/hosted/contract.js +208 -0
  126. package/dist/src/hosted/contract.test.d.ts +1 -0
  127. package/dist/src/hosted/contract.test.js +361 -0
  128. package/dist/src/hosted/credentials.d.ts +38 -0
  129. package/dist/src/hosted/credentials.js +248 -0
  130. package/dist/src/hosted/credentials.test.d.ts +1 -0
  131. package/dist/src/hosted/credentials.test.js +93 -0
  132. package/dist/src/hosted/file-contract.test.d.ts +1 -0
  133. package/dist/src/hosted/file-contract.test.js +174 -0
  134. package/dist/src/hosted/files.d.ts +29 -0
  135. package/dist/src/hosted/files.js +296 -0
  136. package/dist/src/hosted/files.test.d.ts +1 -0
  137. package/dist/src/hosted/files.test.js +231 -0
  138. package/dist/src/hosted/main-lifecycle.test.d.ts +1 -0
  139. package/dist/src/hosted/main-lifecycle.test.js +213 -0
  140. package/dist/src/hosted/manifest.d.ts +14 -0
  141. package/dist/src/hosted/manifest.js +47 -0
  142. package/dist/src/hosted/manifest.test.d.ts +1 -0
  143. package/dist/src/hosted/manifest.test.js +164 -0
  144. package/dist/src/hosted/output-parity.test.d.ts +1 -0
  145. package/dist/src/hosted/output-parity.test.js +108 -0
  146. package/dist/src/hosted/root-command-surface.test.d.ts +1 -0
  147. package/dist/src/hosted/root-command-surface.test.js +646 -0
  148. package/dist/src/hosted/runner.d.ts +19 -0
  149. package/dist/src/hosted/runner.js +705 -0
  150. package/dist/src/hosted/runner.test.d.ts +1 -0
  151. package/dist/src/hosted/runner.test.js +1607 -0
  152. package/dist/src/hosted/setup.d.ts +10 -0
  153. package/dist/src/hosted/setup.js +79 -0
  154. package/dist/src/hosted/setup.test.d.ts +1 -0
  155. package/dist/src/hosted/setup.test.js +195 -0
  156. package/dist/src/hosted/types.d.ts +175 -0
  157. package/dist/src/hosted/types.js +1 -0
  158. package/dist/src/main.js +123 -97
  159. package/dist/src/manifest-types.d.ts +2 -0
  160. package/dist/src/output.d.ts +17 -2
  161. package/dist/src/output.js +88 -81
  162. package/dist/src/output.test.js +141 -51
  163. package/dist/src/pipeline/executor.test.js +1 -0
  164. package/dist/src/pipeline/steps/download.test.js +1 -0
  165. package/dist/src/plugin-create-cli.test.d.ts +1 -0
  166. package/dist/src/plugin-create-cli.test.js +37 -0
  167. package/dist/src/plugin-manifest.d.ts +12 -0
  168. package/dist/src/plugin-manifest.js +16 -0
  169. package/dist/src/plugin-manifest.test.js +19 -1
  170. package/dist/src/plugin-scaffold.d.ts +4 -1
  171. package/dist/src/plugin-scaffold.js +4 -1
  172. package/dist/src/plugin-scaffold.test.js +23 -8
  173. package/dist/src/plugin.js +4 -1
  174. package/dist/src/plugin.test.js +13 -0
  175. package/dist/src/registry.d.ts +2 -0
  176. package/dist/src/release-notes.d.ts +6 -1
  177. package/dist/src/release-notes.js +192 -4
  178. package/dist/src/release-notes.test.js +143 -4
  179. package/dist/src/root-command-surface.d.ts +31 -0
  180. package/dist/src/root-command-surface.js +106 -0
  181. package/dist/src/serialization.d.ts +1 -16
  182. package/dist/src/serialization.js +5 -55
  183. package/dist/src/stream-write.d.ts +12 -0
  184. package/dist/src/stream-write.js +91 -0
  185. package/dist/src/stream-write.test.d.ts +1 -0
  186. package/dist/src/stream-write.test.js +186 -0
  187. package/dist/src/types.d.ts +5 -0
  188. package/dist/src/utils.d.ts +1 -1
  189. package/dist/src/utils.js +2 -8
  190. package/dist/src/utils.test.js +50 -0
  191. package/hosted-contract.json +36748 -0
  192. package/package.json +6 -1
  193. package/scripts/check-hosted-contract.mjs +108 -0
  194. package/scripts/docs-sync-review.ts +332 -0
  195. package/scripts/generate-release-notes.ts +1 -1
  196. package/scripts/sync-community-plugins.ts +14 -0
  197. package/skills/smart-search/SKILL.md +14 -3
  198. package/skills/webcmd-autofix/SKILL.md +26 -37
  199. package/skills/webcmd-usage/SKILL.md +21 -7
  200. package/clis/antigravity/SKILL.md +0 -38
package/dist/src/help.js CHANGED
@@ -1,54 +1,6 @@
1
1
  import yaml from 'js-yaml';
2
- import { fullName } from './registry.js';
3
- import { formatCommandExample } from './serialization.js';
4
2
  import { CLI_COMMAND } from './brand.js';
5
- const COMMON_OPTIONS = [
6
- {
7
- flags: '-f, --format <fmt>',
8
- name: 'format',
9
- help: 'Output format: table, plain, json, yaml, md, csv',
10
- default: 'table',
11
- choices: ['table', 'plain', 'json', 'yaml', 'md', 'csv'],
12
- },
13
- {
14
- flags: '--trace <mode>',
15
- name: 'trace',
16
- help: 'Trace capture: off, on, retain-on-failure',
17
- default: 'off',
18
- choices: ['off', 'on', 'retain-on-failure'],
19
- },
20
- {
21
- flags: '-v, --verbose',
22
- name: 'verbose',
23
- help: 'Debug output',
24
- default: false,
25
- },
26
- {
27
- flags: '-h, --help',
28
- name: 'help',
29
- help: 'display help for command',
30
- },
31
- ];
32
- const BROWSER_COMMON_OPTIONS = [
33
- {
34
- flags: '--window <mode>',
35
- name: 'window',
36
- help: 'Browser window mode: foreground or background',
37
- choices: ['foreground', 'background'],
38
- },
39
- {
40
- flags: '--site-session <mode>',
41
- name: 'site-session',
42
- help: 'Adapter site session lifecycle: ephemeral or persistent',
43
- choices: ['ephemeral', 'persistent'],
44
- },
45
- {
46
- flags: '--keep-tab <bool>',
47
- name: 'keep-tab',
48
- help: 'Keep the browser tab lease after the command finishes',
49
- choices: ['true', 'false'],
50
- },
51
- ];
3
+ import { classifyAdapterDomain, commandHelpData as buildCommandHelpData, formatBrowserCommonOptionsHelp, formatCommandHelp, formatCommandListTerm as buildCommandListTerm, formatCommonOptionsHelp, formatSiteCommandDescription as buildSiteCommandDescription, formatSiteHelp, formatRootHelp, siteHelpData as buildSiteHelpData, toPresentableCommand, wrapCommaList as wrapPresentationCommaList, } from './command-presentation.js';
52
4
  function normalizeStructuredHelpFormat(value) {
53
5
  const normalized = value?.toLowerCase();
54
6
  if (normalized === 'yaml' || normalized === 'yml')
@@ -78,41 +30,49 @@ export function renderStructuredHelp(data, format) {
78
30
  return yaml.dump(data, { sortKeys: false, lineWidth: 120, noRefs: true });
79
31
  }
80
32
  export function wrapCommaList(items, opts = {}) {
81
- const width = Math.max(opts.width ?? process.stdout.columns ?? 100, 40);
82
- const indent = opts.indent ?? ' ';
83
- const sorted = [...items].sort((a, b) => a.localeCompare(b));
84
- const lines = [];
85
- let line = indent;
86
- sorted.forEach((item, index) => {
87
- const token = `${item}${index < sorted.length - 1 ? ',' : ''}`;
88
- const prefix = line === indent ? '' : ' ';
89
- if (line.length + prefix.length + token.length > width && line.trim()) {
90
- lines.push(line);
91
- line = `${indent}${token}`;
92
- }
93
- else {
94
- line += `${prefix}${token}`;
95
- }
96
- });
97
- if (line.trim())
98
- lines.push(line);
99
- return lines.join('\n');
100
- }
101
- function isLocalIpDomain(domain) {
102
- if (domain === '::1' || domain === '[::1]')
103
- return true;
104
- const parts = domain.split('.');
105
- if (parts.length !== 4)
106
- return false;
107
- return parts.every(part => /^\d+$/.test(part) && Number(part) >= 0 && Number(part) <= 255)
108
- && Number(parts[0]) === 127;
33
+ return wrapPresentationCommaList(items, opts);
109
34
  }
110
35
  export function classifyAdapter(domain) {
111
- if (!domain)
112
- return 'site';
113
- if (isLocalIpDomain(domain))
114
- return 'app';
115
- return domain.includes('.') ? 'site' : 'app';
36
+ return classifyAdapterDomain(domain);
37
+ }
38
+ const installedRootHelpPresentations = new WeakMap();
39
+ export function buildRootHelpPresentation(program, groups) {
40
+ const adapterNames = new Set([...groups.external.map((cli) => cli.name), ...groups.apps, ...groups.sites]);
41
+ const commanderHelp = program.createHelp();
42
+ const options = commanderHelp.visibleOptions(program).map((option) => ({
43
+ flags: option.flags,
44
+ description: option.description,
45
+ }));
46
+ return {
47
+ description: program.description(),
48
+ usage: [`${program.name()} [options] [command]`],
49
+ baseText: commanderHelp.formatHelp(program, commanderHelp),
50
+ options,
51
+ commands: program.commands
52
+ .filter((command) => !adapterNames.has(command.name()))
53
+ .map((command) => ({ name: command.name(), description: command.description() })),
54
+ groups: [
55
+ { label: `External CLIs (${groups.external.length})`, items: groups.external.map((cli) => cli.label) },
56
+ { label: `App adapters (${groups.apps.length})`, items: groups.apps },
57
+ { label: `Site adapters (${groups.sites.length})`, items: groups.sites },
58
+ ],
59
+ footer: [
60
+ `Run '${CLI_COMMAND} list' for full command details, or '${CLI_COMMAND} <site> --help' to inspect one site.`,
61
+ `Agent tip: use '${CLI_COMMAND} <site> --help -f yaml' for all command args/options in one structured response.`,
62
+ ],
63
+ };
64
+ }
65
+ export function getInstalledRootHelpPresentation(command) {
66
+ return installedRootHelpPresentations.get(command);
67
+ }
68
+ export function installRootPresentationHelp(command, data, presentation) {
69
+ installedRootHelpPresentations.set(command, presentation);
70
+ command.helpInformation = (() => {
71
+ const format = getRequestedHelpFormat();
72
+ if (format)
73
+ return renderStructuredHelp(data(), format);
74
+ return formatRootHelp(presentation);
75
+ });
116
76
  }
117
77
  function formatGroupSection(label, names) {
118
78
  if (names.length === 0)
@@ -136,27 +96,6 @@ export function formatRootAdapterHelpText(groups) {
136
96
  lines.push('');
137
97
  return lines.join('\n');
138
98
  }
139
- function compactArg(arg) {
140
- return {
141
- name: arg.name,
142
- ...(arg.type && arg.type !== 'string' ? { type: arg.type } : {}),
143
- ...(arg.positional ? { positional: true } : {}),
144
- ...(arg.required ? { required: true } : {}),
145
- ...(arg.valueRequired ? { valueRequired: true } : {}),
146
- ...(arg.default !== undefined ? { default: arg.default } : {}),
147
- ...(arg.choices?.length ? { choices: arg.choices } : {}),
148
- ...(arg.help ? { help: arg.help } : {}),
149
- };
150
- }
151
- function compactCommonOption(option) {
152
- return {
153
- name: option.name,
154
- flags: option.flags,
155
- help: option.help,
156
- ...('default' in option ? { default: option.default } : {}),
157
- ...('choices' in option ? { choices: option.choices } : {}),
158
- };
159
- }
160
99
  function compactCommanderArgument(arg) {
161
100
  return {
162
101
  name: arg.name(),
@@ -343,49 +282,8 @@ export function installCommanderNamespaceStructuredHelp(namespaceRoot, opts = {}
343
282
  }
344
283
  }
345
284
  }
346
- function positionals(cmd) {
347
- return cmd.args.filter(arg => arg.positional);
348
- }
349
- function commandOptions(cmd) {
350
- return cmd.args.filter(arg => !arg.positional);
351
- }
352
- function formatPositionals(args) {
353
- return args
354
- .map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`)
355
- .join(' ');
356
- }
357
- function formatCommandOptionTerm(arg) {
358
- if (arg.required || arg.valueRequired)
359
- return `--${arg.name} <value>`;
360
- return `--${arg.name} [value]`;
361
- }
362
285
  export function formatCommandListTerm(cmd) {
363
- const positionalText = formatPositionals(positionals(cmd));
364
- const optionText = commandOptions(cmd).length > 0 ? ' [options]' : '';
365
- return `${cmd.name}${positionalText ? ` ${positionalText}` : ''}${optionText}`;
366
- }
367
- function formatUsage(cmd) {
368
- const positionalText = formatPositionals(positionals(cmd));
369
- return `${CLI_COMMAND} ${cmd.site} ${cmd.name}${positionalText ? ` ${positionalText}` : ''} [options]`;
370
- }
371
- function compactCommand(cmd) {
372
- return {
373
- name: cmd.name,
374
- command: `${CLI_COMMAND} ${cmd.site} ${cmd.name}`,
375
- usage: formatUsage(cmd),
376
- access: cmd.access,
377
- description: cmd.description,
378
- browser: !!cmd.browser,
379
- ...(cmd.domain ? { domain: cmd.domain } : {}),
380
- ...(cmd.aliases?.length ? { aliases: cmd.aliases } : {}),
381
- positionals: positionals(cmd).map(compactArg),
382
- command_options: commandOptions(cmd).map(compactArg),
383
- ...(cmd.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
384
- example: formatCommandExample(cmd),
385
- ...(cmd.siteSession ? { siteSession: cmd.siteSession } : {}),
386
- ...(cmd.defaultFormat ? { defaultFormat: cmd.defaultFormat } : {}),
387
- ...(cmd.columns?.length ? { columns: cmd.columns } : {}),
388
- };
286
+ return buildCommandListTerm(toPresentableCommand(cmd));
389
287
  }
390
288
  export function rootHelpData(program, groups) {
391
289
  const adapterNames = new Set([...groups.external.map(cli => cli.name), ...groups.apps, ...groups.sites]);
@@ -421,124 +319,22 @@ export function rootHelpData(program, groups) {
421
319
  };
422
320
  }
423
321
  export function siteHelpData(site, commands) {
424
- const unique = [...new Map(commands.map(cmd => [fullName(cmd), cmd])).values()]
425
- .sort((a, b) => a.name.localeCompare(b.name));
426
- return {
427
- site,
428
- command_count: unique.length,
429
- commands: unique.map(cmd => compactCommand(cmd)),
430
- common_options: COMMON_OPTIONS.map(compactCommonOption),
431
- ...(unique.some(cmd => cmd.browser) ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
432
- next: [
433
- `${CLI_COMMAND} ${site} <command> --help -f yaml`,
434
- `${CLI_COMMAND} ${site} <command> -f yaml`,
435
- ],
436
- };
322
+ return buildSiteHelpData(site, commands.map(toPresentableCommand));
437
323
  }
438
324
  export function commandHelpData(cmd) {
439
- return {
440
- site: cmd.site,
441
- ...compactCommand(cmd),
442
- common_options: COMMON_OPTIONS.map(compactCommonOption),
443
- ...(cmd.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
444
- output_formats: ['table', 'plain', 'yaml', 'json', 'md', 'csv'],
445
- };
446
- }
447
- function formatRows(rows) {
448
- if (rows.length === 0)
449
- return [];
450
- const width = Math.min(Math.max(...rows.map(([left]) => left.length)), 34);
451
- return rows.map(([left, right]) => ` ${left.padEnd(width + 2)}${right}`);
452
- }
453
- function formatArgHelp(arg) {
454
- const parts = [];
455
- if (arg.help)
456
- parts.push(arg.help);
457
- if (arg.default !== undefined)
458
- parts.push(`default: ${arg.default}`);
459
- if (arg.choices?.length)
460
- parts.push(`choices: ${arg.choices.join(', ')}`);
461
- return parts.join(' ');
325
+ return buildCommandHelpData(toPresentableCommand(cmd));
462
326
  }
463
327
  export function formatCommonOptionsHelpText() {
464
- const rows = COMMON_OPTIONS.map(option => {
465
- const details = [option.help];
466
- if ('default' in option)
467
- details.push(`default: ${option.default}`);
468
- if ('choices' in option)
469
- details.push(`choices: ${option.choices.join(', ')}`);
470
- return [option.flags, details.join(' ')];
471
- });
472
- return ['Common options:', ...formatRows(rows)].join('\n');
328
+ return formatCommonOptionsHelp();
473
329
  }
474
330
  export function formatBrowserCommonOptionsHelpText() {
475
- const rows = BROWSER_COMMON_OPTIONS.map(option => {
476
- const details = [option.help];
477
- if ('choices' in option)
478
- details.push(`choices: ${option.choices.join(', ')}`);
479
- return [option.flags, details.join(' ')];
480
- });
481
- return ['Browser common options:', ...formatRows(rows)].join('\n');
331
+ return formatBrowserCommonOptionsHelp();
482
332
  }
483
333
  export function formatSiteHelpText(site, commands) {
484
- const unique = [...new Map(commands.map(cmd => [fullName(cmd), cmd])).values()]
485
- .sort((a, b) => a.name.localeCompare(b.name));
486
- const lines = [
487
- `Usage: ${CLI_COMMAND} ${site} <command> [args] [options]`,
488
- '',
489
- wrapCommaList(unique.map(cmd => cmd.name), { indent: '' }),
490
- '',
491
- 'Commands:',
492
- ...formatRows(unique.map(cmd => [formatCommandListTerm(cmd), formatSiteCommandDescription(cmd)])),
493
- '',
494
- formatCommonOptionsHelpText(),
495
- ...(unique.some(cmd => cmd.browser) ? ['', formatBrowserCommonOptionsHelpText()] : []),
496
- '',
497
- `Agent tip: use '${CLI_COMMAND} ${site} --help -f yaml' to get all command args/options in one structured response.`,
498
- '',
499
- ];
500
- return lines.join('\n');
334
+ return formatSiteHelp(site, commands.map(toPresentableCommand));
501
335
  }
502
336
  export function formatCommandHelpText(cmd) {
503
- const lines = [
504
- `Usage: ${formatUsage(cmd)}`,
505
- '',
506
- cmd.description,
507
- '',
508
- ];
509
- const positionalRows = positionals(cmd).map(arg => [
510
- arg.name,
511
- formatArgHelp(arg),
512
- ]);
513
- if (positionalRows.length) {
514
- lines.push('Arguments:', ...formatRows(positionalRows), '');
515
- }
516
- const optionRows = commandOptions(cmd).map(arg => [
517
- formatCommandOptionTerm(arg),
518
- formatArgHelp(arg),
519
- ]);
520
- if (optionRows.length) {
521
- lines.push('Command options:', ...formatRows(optionRows), '');
522
- }
523
- lines.push(formatCommonOptionsHelpText(), '');
524
- if (cmd.browser)
525
- lines.push(formatBrowserCommonOptionsHelpText(), '');
526
- const meta = [];
527
- meta.push(`Access: ${cmd.access}`);
528
- meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
529
- if (cmd.domain)
530
- meta.push(`Domain: ${cmd.domain}`);
531
- if (cmd.defaultFormat)
532
- meta.push(`Default format: ${cmd.defaultFormat}`);
533
- if (cmd.aliases?.length)
534
- meta.push(`Aliases: ${cmd.aliases.join(', ')}`);
535
- lines.push(meta.join(' | '));
536
- lines.push(`Example: ${formatCommandExample(cmd)}`);
537
- if (cmd.columns?.length)
538
- lines.push(`Output columns: ${cmd.columns.join(', ')}`);
539
- lines.push("Agent tip: use '--help -f yaml' for structured args/options.");
540
- lines.push('');
541
- return lines.join('\n');
337
+ return formatCommandHelp(toPresentableCommand(cmd));
542
338
  }
543
339
  export function installStructuredHelp(command, data, textSuffix) {
544
340
  const original = command.helpInformation.bind(command);
@@ -551,6 +347,5 @@ export function installStructuredHelp(command, data, textSuffix) {
551
347
  });
552
348
  }
553
349
  export function formatSiteCommandDescription(cmd) {
554
- const access = cmd.access === 'write' ? '[write]' : '[read]';
555
- return `${access} ${cmd.description}`;
350
+ return buildSiteCommandDescription(toPresentableCommand(cmd));
556
351
  }
@@ -1,5 +1,19 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { classifyAdapter, formatRootAdapterHelpText } from './help.js';
2
+ import { classifyAdapter, commandHelpData, formatCommandHelpText, formatRootAdapterHelpText, formatSiteHelpText, siteHelpData, } from './help.js';
3
+ import { commandHelpData as sharedCommandHelpData, formatCommandHelp, formatSiteHelp, siteHelpData as sharedSiteHelpData, toPresentableCommand, } from './command-presentation.js';
4
+ import { Strategy } from './registry.js';
5
+ const presentableFixture = {
6
+ site: 'github',
7
+ name: 'issues',
8
+ aliases: ['issue-list'],
9
+ description: 'List repository issues',
10
+ access: 'read',
11
+ strategy: Strategy.COOKIE,
12
+ browser: true,
13
+ args: [{ name: 'owner', positional: true, required: true, help: 'Repository owner' }],
14
+ columns: ['number', 'title'],
15
+ domain: 'github.com',
16
+ };
3
17
  describe('classifyAdapter', () => {
4
18
  it('classifies DNS-style domains as site', () => {
5
19
  expect(classifyAdapter('www.youtube.com')).toBe('site');
@@ -56,3 +70,15 @@ describe('formatRootAdapterHelpText', () => {
56
70
  expect(text).toContain("'webcmd <site> --help -f yaml'");
57
71
  });
58
72
  });
73
+ describe('shared presentation delegation', () => {
74
+ it('keeps local site and command text byte-identical to the pure model', () => {
75
+ const presentable = toPresentableCommand(presentableFixture);
76
+ expect(formatSiteHelpText('github', [presentableFixture])).toBe(formatSiteHelp('github', [presentable]));
77
+ expect(formatCommandHelpText(presentableFixture)).toBe(formatCommandHelp(presentable));
78
+ });
79
+ it('keeps local structured help byte-identical to the pure model', () => {
80
+ const presentable = toPresentableCommand(presentableFixture);
81
+ expect(siteHelpData('github', [presentableFixture])).toEqual(sharedSiteHelpData('github', [presentable]));
82
+ expect(commandHelpData(presentableFixture)).toEqual(sharedCommandHelpData(presentable));
83
+ });
84
+ });
@@ -0,0 +1,4 @@
1
+ import { type ParsedCommandSurface } from '../command-surface.js';
2
+ import type { HostedCommand } from './types.js';
3
+ export type ParsedHostedInvocation = ParsedCommandSurface;
4
+ export declare function parseHostedInvocation(command: HostedCommand, argv: string[]): ParsedHostedInvocation;
@@ -0,0 +1,4 @@
1
+ import { parseCommandSurface } from '../command-surface.js';
2
+ export function parseHostedInvocation(command, argv) {
3
+ return parseCommandSurface(command, argv);
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseHostedInvocation } from './args.js';
3
+ const command = {
4
+ site: 'github',
5
+ name: 'search',
6
+ command: 'github/search',
7
+ description: 'Search GitHub',
8
+ access: 'read',
9
+ strategy: 'PUBLIC',
10
+ browser: false,
11
+ args: [
12
+ { name: 'query', positional: true, required: true, type: 'string' },
13
+ { name: 'limit', type: 'int', default: 10 },
14
+ { name: 'include-forks', type: 'boolean', default: false },
15
+ { name: 'sort', type: 'string', choices: ['updated', 'stars'] },
16
+ ],
17
+ columns: ['name'],
18
+ };
19
+ describe('parseHostedInvocation', () => {
20
+ it('parses positional args, value flags, boolean flags, and output options', () => {
21
+ expect(parseHostedInvocation(command, ['webcmd', '--limit', '5', '--include-forks', '-f', 'json', '--trace', 'on']))
22
+ .toEqual({
23
+ args: {
24
+ query: 'webcmd',
25
+ limit: 5,
26
+ 'include-forks': true,
27
+ },
28
+ optionSources: {
29
+ query: 'cli',
30
+ limit: 'cli',
31
+ 'include-forks': 'cli',
32
+ },
33
+ format: 'json',
34
+ formatExplicit: true,
35
+ trace: 'on',
36
+ verbose: false,
37
+ help: false,
38
+ });
39
+ });
40
+ it('rejects missing required positional args', () => {
41
+ expect(() => parseHostedInvocation(command, [])).toThrow("error: missing required argument 'query'");
42
+ });
43
+ it.each([
44
+ { name: 'partial integers', argv: ['webcmd', '--limit', '12x'], message: /limit.*number/i },
45
+ { name: 'invalid choices', argv: ['webcmd', '--sort', 'recent'], message: /sort.*one of.*updated.*stars/i },
46
+ ])('rejects $name', ({ argv, message }) => {
47
+ expect(() => parseHostedInvocation(command, argv)).toThrow(message);
48
+ });
49
+ });
@@ -0,0 +1,13 @@
1
+ import { Strategy } from '../registry.js';
2
+ export type HostedAvailability = {
3
+ mode: 'hosted';
4
+ } | {
5
+ mode: 'local-only';
6
+ reason: 'desktop-app' | 'local-tool' | 'browser-bind';
7
+ };
8
+ export interface HostedAvailabilityMetadata {
9
+ strategy?: Strategy | string;
10
+ domain?: string;
11
+ }
12
+ export declare function deriveHostedAvailability(command: HostedAvailabilityMetadata): HostedAvailability;
13
+ export declare function deriveBrowserAvailability(command: string): HostedAvailability;
@@ -0,0 +1,16 @@
1
+ import { classifyAdapter } from '../help.js';
2
+ import { Strategy } from '../registry.js';
3
+ export function deriveHostedAvailability(command) {
4
+ if (String(command.strategy).toLowerCase() === Strategy.LOCAL) {
5
+ return { mode: 'local-only', reason: 'local-tool' };
6
+ }
7
+ if (classifyAdapter(command.domain) === 'app') {
8
+ return { mode: 'local-only', reason: 'desktop-app' };
9
+ }
10
+ return { mode: 'hosted' };
11
+ }
12
+ export function deriveBrowserAvailability(command) {
13
+ return command === 'bind'
14
+ ? { mode: 'local-only', reason: 'browser-bind' }
15
+ : { mode: 'hosted' };
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,180 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { deriveBrowserAvailability, deriveHostedAvailability, } from './availability.js';
6
+ const EXPECTED_LOCAL_TOOLS = [
7
+ 'antigravity/recent-paths',
8
+ 'antigravity/settings-read',
9
+ 'antigravity/state-get',
10
+ 'antigravity/state-keys',
11
+ 'antigravity/workspaces-list',
12
+ 'mercury/reimbursement-plan',
13
+ 'trae-solo/extensions-list',
14
+ 'trae-solo/recent-workspaces',
15
+ 'trae-solo/settings-read',
16
+ 'trae-solo/skill-fs-installed',
17
+ 'trae-solo/skill-fs-list',
18
+ 'trae-solo/skill-fs-show',
19
+ 'trae-solo/state-get',
20
+ 'trae-solo/state-keys',
21
+ 'trae-solo/task-fs-list',
22
+ 'trae-solo/task-fs-show',
23
+ 'trae-solo/task-fs-turns',
24
+ 'trae-solo/user-rules',
25
+ 'trae-solo/workspaces-list',
26
+ ];
27
+ const EXPECTED_DESKTOP_APPS = [
28
+ 'antigravity/add-context',
29
+ 'antigravity/cookies',
30
+ 'antigravity/copy-code',
31
+ 'antigravity/copy-message',
32
+ 'antigravity/delete',
33
+ 'antigravity/display-options',
34
+ 'antigravity/dump',
35
+ 'antigravity/extract-code',
36
+ 'antigravity/history',
37
+ 'antigravity/idb-list',
38
+ 'antigravity/mark-read',
39
+ 'antigravity/model',
40
+ 'antigravity/nav',
41
+ 'antigravity/new',
42
+ 'antigravity/react',
43
+ 'antigravity/read',
44
+ 'antigravity/rename',
45
+ 'antigravity/revert',
46
+ 'antigravity/send',
47
+ 'antigravity/settings',
48
+ 'antigravity/sidebar-toggle',
49
+ 'antigravity/status',
50
+ 'antigravity/storage-get',
51
+ 'antigravity/storage-keys',
52
+ 'antigravity/toggle-aux',
53
+ 'antigravity/watch',
54
+ 'chatgpt-app/ask',
55
+ 'chatgpt-app/model',
56
+ 'chatgpt-app/new',
57
+ 'chatgpt-app/read',
58
+ 'chatgpt-app/send',
59
+ 'chatgpt-app/status',
60
+ 'chatwise/ask',
61
+ 'chatwise/export',
62
+ 'chatwise/history',
63
+ 'chatwise/model',
64
+ 'chatwise/new',
65
+ 'chatwise/read',
66
+ 'chatwise/screenshot',
67
+ 'chatwise/send',
68
+ 'chatwise/status',
69
+ 'codex/archive',
70
+ 'codex/ask',
71
+ 'codex/dump',
72
+ 'codex/export',
73
+ 'codex/extract-diff',
74
+ 'codex/history',
75
+ 'codex/model',
76
+ 'codex/new',
77
+ 'codex/pin',
78
+ 'codex/projects',
79
+ 'codex/read',
80
+ 'codex/rename',
81
+ 'codex/screenshot',
82
+ 'codex/send',
83
+ 'codex/status',
84
+ 'codex/unpin',
85
+ 'cursor/ask',
86
+ 'cursor/composer',
87
+ 'cursor/dump',
88
+ 'cursor/export',
89
+ 'cursor/extract-code',
90
+ 'cursor/history',
91
+ 'cursor/model',
92
+ 'cursor/new',
93
+ 'cursor/read',
94
+ 'cursor/screenshot',
95
+ 'cursor/send',
96
+ 'cursor/status',
97
+ 'discord-app/channels',
98
+ 'discord-app/delete',
99
+ 'discord-app/goto',
100
+ 'discord-app/members',
101
+ 'discord-app/read',
102
+ 'discord-app/search',
103
+ 'discord-app/send',
104
+ 'discord-app/servers',
105
+ 'discord-app/status',
106
+ 'discord-app/thread-read',
107
+ 'discord-app/threads',
108
+ 'qoder/account',
109
+ 'qoder/add-workspace',
110
+ 'qoder/ask',
111
+ 'qoder/credits',
112
+ 'qoder/history',
113
+ 'qoder/knowledge',
114
+ 'qoder/marketplace',
115
+ 'qoder/more-actions',
116
+ 'qoder/new',
117
+ 'qoder/open-editor',
118
+ 'qoder/open-panel',
119
+ 'qoder/prompt-enhance',
120
+ 'qoder/read',
121
+ 'qoder/search',
122
+ 'qoder/send',
123
+ 'qoder/settings',
124
+ 'qoder/sidebar-toggle',
125
+ 'qoder/status',
126
+ 'qoder/view-all',
127
+ 'trae-solo/automation-list',
128
+ 'trae-solo/cookies',
129
+ 'trae-solo/history',
130
+ 'trae-solo/idb-list',
131
+ 'trae-solo/mode',
132
+ 'trae-solo/model',
133
+ 'trae-solo/skill-category',
134
+ 'trae-solo/skill-list',
135
+ 'trae-solo/skill-search',
136
+ 'trae-solo/status',
137
+ 'trae-solo/storage-get',
138
+ 'trae-solo/storage-keys',
139
+ ];
140
+ function exceptionDiff(actual, expected) {
141
+ const actualSet = new Set(actual);
142
+ const expectedSet = new Set(expected);
143
+ return {
144
+ added: actual.filter(name => !expectedSet.has(name)),
145
+ missing: expected.filter(name => !actualSet.has(name)),
146
+ };
147
+ }
148
+ describe('hosted availability', () => {
149
+ it('derives decisions only from normalized strategy and domain metadata', () => {
150
+ expect(deriveHostedAvailability({ strategy: 'local', domain: 'localhost' }))
151
+ .toEqual({ mode: 'local-only', reason: 'local-tool' });
152
+ expect(deriveHostedAvailability({ strategy: 'ui', domain: 'localhost' }))
153
+ .toEqual({ mode: 'local-only', reason: 'desktop-app' });
154
+ expect(deriveHostedAvailability({ strategy: 'cookie', domain: 'example.com' }))
155
+ .toEqual({ mode: 'hosted' });
156
+ expect(deriveBrowserAvailability('bind'))
157
+ .toEqual({ mode: 'local-only', reason: 'browser-bind' });
158
+ expect(deriveBrowserAvailability('open')).toEqual({ mode: 'hosted' });
159
+ });
160
+ it('matches the reviewed local-only adapter exception sets exactly', () => {
161
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
162
+ const entries = JSON.parse(fs.readFileSync(path.join(root, 'cli-manifest.json'), 'utf8'));
163
+ const byReason = new Map([
164
+ ['local-tool', []],
165
+ ['desktop-app', []],
166
+ ]);
167
+ for (const entry of entries) {
168
+ const availability = deriveHostedAvailability(entry);
169
+ if (availability.mode === 'local-only') {
170
+ byReason.get(availability.reason)?.push(`${entry.site}/${entry.name}`);
171
+ }
172
+ }
173
+ const localTools = (byReason.get('local-tool') ?? []).sort();
174
+ const desktopApps = (byReason.get('desktop-app') ?? []).sort();
175
+ expect(exceptionDiff(localTools, EXPECTED_LOCAL_TOOLS)).toEqual({ added: [], missing: [] });
176
+ expect(exceptionDiff(desktopApps, EXPECTED_DESKTOP_APPS)).toEqual({ added: [], missing: [] });
177
+ expect(localTools).toHaveLength(19);
178
+ expect(desktopApps).toHaveLength(111);
179
+ });
180
+ });