@agentrhq/webcmd 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/cli-manifest.json +228 -24
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/ask.test.js +11 -0
- package/clis/chatgpt/commands.test.js +102 -1
- package/clis/chatgpt/deep-research-result.js +45 -3
- package/clis/chatgpt/image.js +22 -2
- package/clis/chatgpt/project-file-add.js +24 -1
- package/clis/chatgpt/utils.js +258 -52
- package/clis/chatgpt/utils.test.js +259 -1
- package/clis/claude/ask.js +21 -1
- package/clis/facebook/feed.js +110 -24
- package/clis/facebook/feed.test.js +62 -0
- package/clis/facebook/search.js +185 -38
- package/clis/facebook/search.test.js +95 -50
- package/clis/instagram/explore.js +30 -12
- package/clis/instagram/explore.test.js +77 -0
- package/clis/instagram/post.js +14 -1
- package/clis/instagram/reel.js +13 -1
- package/clis/linkedin/company.js +153 -0
- package/clis/linkedin/company.test.js +111 -0
- package/clis/linkedin/connections.js +135 -0
- package/clis/linkedin/connections.test.js +141 -0
- package/clis/mercury/reimbursement-draft.js +12 -1
- package/clis/producthunt/hot.js +1 -2
- package/clis/spotify/spotify.js +11 -11
- package/clis/twitter/article-evaluate.test.js +27 -0
- package/clis/twitter/article.js +73 -16
- package/clis/twitter/article.test.js +209 -0
- package/clis/twitter/download.js +6 -1
- package/clis/twitter/post.js +14 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/profile.test.js +8 -0
- package/clis/twitter/quote.js +11 -1
- package/clis/twitter/reply.js +11 -1
- package/dist/src/browser/base-page.d.ts +26 -0
- package/dist/src/browser/base-page.js +23 -8
- package/dist/src/browser/base-page.test.js +45 -12
- package/dist/src/browser/command-catalog.d.ts +6 -0
- package/dist/src/browser/command-catalog.js +259 -0
- package/dist/src/browser/command-catalog.test.d.ts +1 -0
- package/dist/src/browser/command-catalog.test.js +79 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.d.ts +17 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.js +106 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.js +65 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +7 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +23 -9
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +36 -0
- package/dist/src/browser/target-resolver.d.ts +1 -0
- package/dist/src/browser/target-resolver.js +99 -4
- package/dist/src/browser/target-resolver.test.js +141 -1
- package/dist/src/build-manifest.d.ts +6 -0
- package/dist/src/build-manifest.js +18 -1
- package/dist/src/build-manifest.test.js +57 -1
- package/dist/src/builtin-command-surface.d.ts +9 -0
- package/dist/src/builtin-command-surface.js +16 -0
- package/dist/src/check-hosted-contract.test.d.ts +1 -0
- package/dist/src/check-hosted-contract.test.js +56 -0
- package/dist/src/cli.js +58 -106
- package/dist/src/cli.test.js +71 -1
- package/dist/src/command-presentation.d.ts +95 -0
- package/dist/src/command-presentation.js +486 -0
- package/dist/src/command-presentation.test.d.ts +1 -0
- package/dist/src/command-presentation.test.js +97 -0
- package/dist/src/command-surface.d.ts +43 -0
- package/dist/src/command-surface.js +205 -0
- package/dist/src/command-surface.test.d.ts +1 -0
- package/dist/src/command-surface.test.js +406 -0
- package/dist/src/commanderAdapter.d.ts +5 -1
- package/dist/src/commanderAdapter.js +12 -55
- package/dist/src/commanderAdapter.test.js +15 -3
- package/dist/src/community-plugin-sync.d.ts +11 -0
- package/dist/src/community-plugin-sync.js +138 -0
- package/dist/src/community-plugin-sync.test.d.ts +1 -0
- package/dist/src/community-plugin-sync.test.js +123 -0
- package/dist/src/completion-fast.d.ts +4 -4
- package/dist/src/completion-fast.js +15 -34
- package/dist/src/completion-shared.d.ts +4 -0
- package/dist/src/completion-shared.js +38 -0
- package/dist/src/completion.js +3 -27
- package/dist/src/completion.test.js +31 -2
- package/dist/src/docs-sync-review-cli.test.d.ts +1 -0
- package/dist/src/docs-sync-review-cli.test.js +322 -0
- package/dist/src/docs-sync-review.d.ts +117 -0
- package/dist/src/docs-sync-review.js +475 -0
- package/dist/src/docs-sync-review.test.d.ts +1 -0
- package/dist/src/docs-sync-review.test.js +324 -0
- package/dist/src/errors.d.ts +12 -3
- package/dist/src/errors.js +11 -7
- package/dist/src/errors.test.js +14 -1
- package/dist/src/execution.d.ts +1 -2
- package/dist/src/execution.js +4 -45
- package/dist/src/generate-release-notes-cli.test.js +7 -2
- package/dist/src/help.d.ts +4 -0
- package/dist/src/help.js +50 -255
- package/dist/src/help.test.js +27 -1
- package/dist/src/hosted/args.d.ts +2 -7
- package/dist/src/hosted/args.js +2 -99
- package/dist/src/hosted/args.test.js +15 -1
- package/dist/src/hosted/availability.d.ts +13 -0
- package/dist/src/hosted/availability.js +16 -0
- package/dist/src/hosted/availability.test.d.ts +1 -0
- package/dist/src/hosted/availability.test.js +191 -0
- package/dist/src/hosted/browser-args.d.ts +18 -0
- package/dist/src/hosted/browser-args.js +152 -0
- package/dist/src/hosted/browser-args.test.d.ts +1 -0
- package/dist/src/hosted/browser-args.test.js +182 -0
- package/dist/src/hosted/client.d.ts +31 -2
- package/dist/src/hosted/client.js +441 -53
- package/dist/src/hosted/client.test.js +651 -32
- package/dist/src/hosted/config.d.ts +7 -2
- package/dist/src/hosted/config.js +26 -4
- package/dist/src/hosted/config.test.js +34 -1
- package/dist/src/hosted/contract.d.ts +94 -0
- package/dist/src/hosted/contract.js +208 -0
- package/dist/src/hosted/contract.test.d.ts +1 -0
- package/dist/src/hosted/contract.test.js +361 -0
- package/dist/src/hosted/credentials.d.ts +38 -0
- package/dist/src/hosted/credentials.js +248 -0
- package/dist/src/hosted/credentials.test.d.ts +1 -0
- package/dist/src/hosted/credentials.test.js +93 -0
- package/dist/src/hosted/file-contract.test.d.ts +1 -0
- package/dist/src/hosted/file-contract.test.js +174 -0
- package/dist/src/hosted/files.d.ts +29 -0
- package/dist/src/hosted/files.js +296 -0
- package/dist/src/hosted/files.test.d.ts +1 -0
- package/dist/src/hosted/files.test.js +231 -0
- package/dist/src/hosted/main-lifecycle.test.d.ts +1 -0
- package/dist/src/hosted/main-lifecycle.test.js +213 -0
- package/dist/src/hosted/manifest.d.ts +5 -0
- package/dist/src/hosted/manifest.js +21 -66
- package/dist/src/hosted/manifest.test.js +120 -2
- package/dist/src/hosted/output-parity.test.d.ts +1 -0
- package/dist/src/hosted/output-parity.test.js +108 -0
- package/dist/src/hosted/root-command-surface.test.d.ts +1 -0
- package/dist/src/hosted/root-command-surface.test.js +646 -0
- package/dist/src/hosted/runner.d.ts +7 -0
- package/dist/src/hosted/runner.js +554 -253
- package/dist/src/hosted/runner.test.js +1471 -53
- package/dist/src/hosted/setup.d.ts +3 -2
- package/dist/src/hosted/setup.js +45 -15
- package/dist/src/hosted/setup.test.js +133 -6
- package/dist/src/hosted/types.d.ts +101 -23
- package/dist/src/main.js +120 -108
- package/dist/src/manifest-types.d.ts +2 -0
- package/dist/src/output.d.ts +17 -2
- package/dist/src/output.js +88 -81
- package/dist/src/output.test.js +141 -51
- package/dist/src/pipeline/executor.test.js +1 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/plugin-create-cli.test.d.ts +1 -0
- package/dist/src/plugin-create-cli.test.js +37 -0
- package/dist/src/plugin-manifest.d.ts +12 -0
- package/dist/src/plugin-manifest.js +16 -0
- package/dist/src/plugin-manifest.test.js +19 -1
- package/dist/src/plugin-scaffold.d.ts +4 -1
- package/dist/src/plugin-scaffold.js +4 -1
- package/dist/src/plugin-scaffold.test.js +23 -8
- package/dist/src/plugin.js +4 -1
- package/dist/src/plugin.test.js +13 -0
- package/dist/src/registry.d.ts +2 -0
- package/dist/src/release-notes.js +12 -4
- package/dist/src/release-notes.test.js +27 -15
- package/dist/src/root-command-surface.d.ts +31 -0
- package/dist/src/root-command-surface.js +106 -0
- package/dist/src/serialization.d.ts +1 -16
- package/dist/src/serialization.js +5 -55
- package/dist/src/stream-write.d.ts +12 -0
- package/dist/src/stream-write.js +91 -0
- package/dist/src/stream-write.test.d.ts +1 -0
- package/dist/src/stream-write.test.js +186 -0
- package/dist/src/types.d.ts +5 -0
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.js +2 -8
- package/dist/src/utils.test.js +50 -0
- package/hosted-contract.json +36759 -0
- package/package.json +6 -1
- package/scripts/check-hosted-contract.mjs +108 -0
- package/scripts/docs-sync-review.ts +332 -0
- package/scripts/sync-community-plugins.ts +14 -0
- package/skills/webcmd-autofix/SKILL.md +26 -37
- package/skills/webcmd-usage/SKILL.md +7 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { CLI_COMMAND } from './brand.js';
|
|
2
|
+
const COMMON_OPTIONS = [
|
|
3
|
+
{
|
|
4
|
+
flags: '-f, --format <fmt>',
|
|
5
|
+
name: 'format',
|
|
6
|
+
help: 'Output format: table, plain, json, yaml, md, csv',
|
|
7
|
+
default: 'table',
|
|
8
|
+
choices: ['table', 'plain', 'json', 'yaml', 'md', 'csv'],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
flags: '--trace <mode>',
|
|
12
|
+
name: 'trace',
|
|
13
|
+
help: 'Trace capture: off, on, retain-on-failure',
|
|
14
|
+
default: 'off',
|
|
15
|
+
choices: ['off', 'on', 'retain-on-failure'],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
flags: '-v, --verbose',
|
|
19
|
+
name: 'verbose',
|
|
20
|
+
help: 'Debug output',
|
|
21
|
+
default: false,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
flags: '-h, --help',
|
|
25
|
+
name: 'help',
|
|
26
|
+
help: 'display help for command',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const BROWSER_COMMON_OPTIONS = [
|
|
30
|
+
{
|
|
31
|
+
flags: '--window <mode>',
|
|
32
|
+
name: 'window',
|
|
33
|
+
help: 'Browser window mode: foreground or background',
|
|
34
|
+
choices: ['foreground', 'background'],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
flags: '--site-session <mode>',
|
|
38
|
+
name: 'site-session',
|
|
39
|
+
help: 'Adapter site session lifecycle: ephemeral or persistent',
|
|
40
|
+
choices: ['ephemeral', 'persistent'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
flags: '--keep-tab <bool>',
|
|
44
|
+
name: 'keep-tab',
|
|
45
|
+
help: 'Keep the browser tab lease after the command finishes',
|
|
46
|
+
choices: ['true', 'false'],
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
export function toPresentableCommand(command) {
|
|
50
|
+
return {
|
|
51
|
+
site: command.site,
|
|
52
|
+
name: command.name,
|
|
53
|
+
aliases: [...(command.aliases ?? [])],
|
|
54
|
+
description: command.description,
|
|
55
|
+
access: command.access === 'write' ? 'write' : 'read',
|
|
56
|
+
strategy: (command.strategy ?? 'public').toLowerCase(),
|
|
57
|
+
browser: command.browser === true,
|
|
58
|
+
args: command.args.map((arg) => ({ ...arg, ...(arg.choices ? { choices: [...arg.choices] } : {}) })),
|
|
59
|
+
columns: [...(command.columns ?? [])],
|
|
60
|
+
...(command.defaultFormat ? { defaultFormat: command.defaultFormat } : {}),
|
|
61
|
+
...(command.domain ? { domain: command.domain } : {}),
|
|
62
|
+
...(command.example ? { example: command.example } : {}),
|
|
63
|
+
...(command.siteSession ? { siteSession: command.siteSession } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function formatRootHelp(presentation) {
|
|
67
|
+
if (presentation.baseText !== undefined) {
|
|
68
|
+
const baseText = presentation.baseText.replace(/\n+$/, '');
|
|
69
|
+
const groups = (presentation.groups ?? []).filter((group) => group.items.length > 0);
|
|
70
|
+
if (groups.length === 0)
|
|
71
|
+
return `${baseText}\n`;
|
|
72
|
+
const tail = [];
|
|
73
|
+
for (const group of groups) {
|
|
74
|
+
tail.push(`${group.label}:`, wrapCommaList(group.items), '');
|
|
75
|
+
}
|
|
76
|
+
if (presentation.footer?.length)
|
|
77
|
+
tail.push(...presentation.footer);
|
|
78
|
+
return `${baseText}\n\n${tail.join('\n')}\n`;
|
|
79
|
+
}
|
|
80
|
+
const usage = presentation.usage ?? [
|
|
81
|
+
`${CLI_COMMAND} <site> <command> [args] [options]`,
|
|
82
|
+
`${CLI_COMMAND} list [options]`,
|
|
83
|
+
];
|
|
84
|
+
const lines = [
|
|
85
|
+
'Usage:',
|
|
86
|
+
...usage.map((entry) => ` ${entry}`),
|
|
87
|
+
'',
|
|
88
|
+
presentation.description,
|
|
89
|
+
'',
|
|
90
|
+
'Options:',
|
|
91
|
+
...formatRows(presentation.options.map((option) => [option.flags, option.description])),
|
|
92
|
+
'',
|
|
93
|
+
'Commands:',
|
|
94
|
+
...formatRows(presentation.commands.map((command) => [command.name, command.description])),
|
|
95
|
+
];
|
|
96
|
+
if (presentation.localOnlyCommands?.length) {
|
|
97
|
+
lines.push('', 'Local-only commands:', ...formatRows(presentation.localOnlyCommands.map((command) => [command.name, command.description])));
|
|
98
|
+
if (presentation.localOnlyExplanation)
|
|
99
|
+
lines.push('', presentation.localOnlyExplanation);
|
|
100
|
+
}
|
|
101
|
+
for (const group of presentation.groups ?? []) {
|
|
102
|
+
if (group.items.length === 0)
|
|
103
|
+
continue;
|
|
104
|
+
lines.push('', `${group.label}:`, wrapCommaList(group.items));
|
|
105
|
+
}
|
|
106
|
+
if (presentation.footer?.length)
|
|
107
|
+
lines.push('', ...presentation.footer);
|
|
108
|
+
lines.push('');
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|
|
111
|
+
export function commandListRows(commands, structured) {
|
|
112
|
+
return uniqueCommands(commands).map((command) => {
|
|
113
|
+
if (structured) {
|
|
114
|
+
return {
|
|
115
|
+
command: commandFullName(command),
|
|
116
|
+
site: command.site,
|
|
117
|
+
name: command.name,
|
|
118
|
+
aliases: [...command.aliases],
|
|
119
|
+
description: command.description,
|
|
120
|
+
access: command.access,
|
|
121
|
+
strategy: command.strategy,
|
|
122
|
+
browser: command.browser,
|
|
123
|
+
args: command.args.map(serializePresentableArg),
|
|
124
|
+
columns: [...command.columns],
|
|
125
|
+
domain: command.domain ?? null,
|
|
126
|
+
example: formatPresentableCommandExample(command),
|
|
127
|
+
defaultFormat: command.defaultFormat ?? null,
|
|
128
|
+
siteSession: command.siteSession ?? null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
command: commandFullName(command),
|
|
133
|
+
site: command.site,
|
|
134
|
+
name: command.name,
|
|
135
|
+
aliases: command.aliases.join(', '),
|
|
136
|
+
description: command.description,
|
|
137
|
+
access: command.access,
|
|
138
|
+
strategy: command.strategy,
|
|
139
|
+
browser: command.browser,
|
|
140
|
+
args: formatArgumentSummary(command.args),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function commandListPresentation(commands, format, options = {}) {
|
|
145
|
+
const structured = format === 'json' || format === 'yaml';
|
|
146
|
+
const unique = uniqueCommands(commands);
|
|
147
|
+
return {
|
|
148
|
+
rows: commandListRows(unique, structured),
|
|
149
|
+
columns: [
|
|
150
|
+
'command',
|
|
151
|
+
'site',
|
|
152
|
+
'name',
|
|
153
|
+
'aliases',
|
|
154
|
+
'description',
|
|
155
|
+
'access',
|
|
156
|
+
'strategy',
|
|
157
|
+
'browser',
|
|
158
|
+
'args',
|
|
159
|
+
...(structured ? ['columns', 'domain'] : []),
|
|
160
|
+
],
|
|
161
|
+
structured,
|
|
162
|
+
...(format === 'table'
|
|
163
|
+
? { displayLines: formatGroupedCommandList(unique, options.externalClis ?? []) }
|
|
164
|
+
: {}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function isLocalIpDomain(domain) {
|
|
168
|
+
if (domain === '::1' || domain === '[::1]')
|
|
169
|
+
return true;
|
|
170
|
+
const parts = domain.split('.');
|
|
171
|
+
if (parts.length !== 4)
|
|
172
|
+
return false;
|
|
173
|
+
return parts.every((part) => /^\d+$/.test(part) && Number(part) >= 0 && Number(part) <= 255)
|
|
174
|
+
&& Number(parts[0]) === 127;
|
|
175
|
+
}
|
|
176
|
+
export function classifyAdapterDomain(domain) {
|
|
177
|
+
if (!domain)
|
|
178
|
+
return 'site';
|
|
179
|
+
if (isLocalIpDomain(domain))
|
|
180
|
+
return 'app';
|
|
181
|
+
return domain.includes('.') ? 'site' : 'app';
|
|
182
|
+
}
|
|
183
|
+
function formatGroupedCommandList(commands, externalClis) {
|
|
184
|
+
const appsBySite = new Map();
|
|
185
|
+
const sitesBySite = new Map();
|
|
186
|
+
for (const command of commands) {
|
|
187
|
+
const target = classifyAdapterDomain(command.domain) === 'app' ? appsBySite : sitesBySite;
|
|
188
|
+
const group = target.get(command.site) ?? [];
|
|
189
|
+
group.push(command);
|
|
190
|
+
target.set(command.site, group);
|
|
191
|
+
}
|
|
192
|
+
const lines = ['', ` ${CLI_COMMAND} — available commands`, ''];
|
|
193
|
+
const appendAdapterSection = (label, groups) => {
|
|
194
|
+
if (groups.size === 0)
|
|
195
|
+
return;
|
|
196
|
+
lines.push(` ${label}`, '');
|
|
197
|
+
for (const [site, siteCommands] of groups) {
|
|
198
|
+
lines.push(` ${site}`);
|
|
199
|
+
for (const command of siteCommands) {
|
|
200
|
+
const aliases = command.aliases.length > 0 ? ` (aliases: ${command.aliases.join(', ')})` : '';
|
|
201
|
+
lines.push(` ${command.name} [${command.strategy}]${aliases}`
|
|
202
|
+
+ `${command.description ? ` — ${command.description}` : ''}`);
|
|
203
|
+
}
|
|
204
|
+
lines.push('');
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
appendAdapterSection('App adapters', appsBySite);
|
|
208
|
+
appendAdapterSection('Site adapters', sitesBySite);
|
|
209
|
+
if (externalClis.length > 0) {
|
|
210
|
+
lines.push(' external CLIs');
|
|
211
|
+
for (const external of externalClis) {
|
|
212
|
+
const tag = external.installed ? '[installed]' : '[auto-install]';
|
|
213
|
+
lines.push(` ${external.label} ${tag}${external.description ? ` — ${external.description}` : ''}`);
|
|
214
|
+
}
|
|
215
|
+
lines.push('');
|
|
216
|
+
}
|
|
217
|
+
lines.push(` ${commands.length} built-in commands across ${appsBySite.size} apps + ${sitesBySite.size} sites, `
|
|
218
|
+
+ `${externalClis.length} external CLIs`, '');
|
|
219
|
+
return lines;
|
|
220
|
+
}
|
|
221
|
+
export function getCommandCompletionCandidates(commands, words, cursor, builtins) {
|
|
222
|
+
if (cursor <= 1) {
|
|
223
|
+
const sites = new Set(commands.map((command) => command.site));
|
|
224
|
+
return [...builtins, ...sites].sort();
|
|
225
|
+
}
|
|
226
|
+
const site = words[0];
|
|
227
|
+
if (!site || builtins.includes(site))
|
|
228
|
+
return [];
|
|
229
|
+
if (cursor !== 2)
|
|
230
|
+
return [];
|
|
231
|
+
return [...new Set(commands
|
|
232
|
+
.filter((command) => command.site === site)
|
|
233
|
+
.flatMap((command) => [command.name, ...(command.aliases ?? [])]))]
|
|
234
|
+
.sort();
|
|
235
|
+
}
|
|
236
|
+
export function formatCommandListTerm(command) {
|
|
237
|
+
const positionalText = formatPositionals(positionals(command));
|
|
238
|
+
const optionText = commandOptions(command).length > 0 ? ' [options]' : '';
|
|
239
|
+
return `${command.name}${positionalText ? ` ${positionalText}` : ''}${optionText}`;
|
|
240
|
+
}
|
|
241
|
+
export function siteHelpData(site, commands) {
|
|
242
|
+
const unique = commandsForSite(site, commands);
|
|
243
|
+
return {
|
|
244
|
+
site,
|
|
245
|
+
command_count: unique.length,
|
|
246
|
+
commands: unique.map(compactCommand),
|
|
247
|
+
common_options: COMMON_OPTIONS.map(compactCommonOption),
|
|
248
|
+
...(unique.some((command) => command.browser)
|
|
249
|
+
? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) }
|
|
250
|
+
: {}),
|
|
251
|
+
next: [
|
|
252
|
+
`${CLI_COMMAND} ${site} <command> --help -f yaml`,
|
|
253
|
+
`${CLI_COMMAND} ${site} <command> -f yaml`,
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
export function commandHelpData(command) {
|
|
258
|
+
return {
|
|
259
|
+
site: command.site,
|
|
260
|
+
...compactCommand(command),
|
|
261
|
+
common_options: COMMON_OPTIONS.map(compactCommonOption),
|
|
262
|
+
...(command.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
|
|
263
|
+
output_formats: ['table', 'plain', 'yaml', 'json', 'md', 'csv'],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
export function formatCommonOptionsHelp() {
|
|
267
|
+
const rows = COMMON_OPTIONS.map((option) => {
|
|
268
|
+
const details = [option.help];
|
|
269
|
+
if ('default' in option)
|
|
270
|
+
details.push(`default: ${option.default}`);
|
|
271
|
+
if ('choices' in option)
|
|
272
|
+
details.push(`choices: ${option.choices.join(', ')}`);
|
|
273
|
+
return [option.flags, details.join(' ')];
|
|
274
|
+
});
|
|
275
|
+
return ['Common options:', ...formatRows(rows)].join('\n');
|
|
276
|
+
}
|
|
277
|
+
export function formatBrowserCommonOptionsHelp() {
|
|
278
|
+
const rows = BROWSER_COMMON_OPTIONS.map((option) => {
|
|
279
|
+
const details = [option.help];
|
|
280
|
+
if ('choices' in option)
|
|
281
|
+
details.push(`choices: ${option.choices.join(', ')}`);
|
|
282
|
+
return [option.flags, details.join(' ')];
|
|
283
|
+
});
|
|
284
|
+
return ['Browser common options:', ...formatRows(rows)].join('\n');
|
|
285
|
+
}
|
|
286
|
+
export function formatSiteHelp(site, commands) {
|
|
287
|
+
const unique = commandsForSite(site, commands);
|
|
288
|
+
const lines = [
|
|
289
|
+
`Usage: ${CLI_COMMAND} ${site} <command> [args] [options]`,
|
|
290
|
+
'',
|
|
291
|
+
wrapCommaList(unique.map((command) => command.name), { indent: '' }),
|
|
292
|
+
'',
|
|
293
|
+
'Commands:',
|
|
294
|
+
...formatRows(unique.map((command) => [formatCommandListTerm(command), formatSiteCommandDescription(command)])),
|
|
295
|
+
'',
|
|
296
|
+
formatCommonOptionsHelp(),
|
|
297
|
+
...(unique.some((command) => command.browser) ? ['', formatBrowserCommonOptionsHelp()] : []),
|
|
298
|
+
'',
|
|
299
|
+
`Agent tip: use '${CLI_COMMAND} ${site} --help -f yaml' to get all command args/options in one structured response.`,
|
|
300
|
+
'',
|
|
301
|
+
];
|
|
302
|
+
return lines.join('\n');
|
|
303
|
+
}
|
|
304
|
+
export function formatCommandHelp(command) {
|
|
305
|
+
const lines = [
|
|
306
|
+
`Usage: ${formatUsage(command)}`,
|
|
307
|
+
'',
|
|
308
|
+
command.description,
|
|
309
|
+
'',
|
|
310
|
+
];
|
|
311
|
+
const positionalRows = positionals(command).map((arg) => [
|
|
312
|
+
arg.name,
|
|
313
|
+
formatArgHelp(arg),
|
|
314
|
+
]);
|
|
315
|
+
if (positionalRows.length)
|
|
316
|
+
lines.push('Arguments:', ...formatRows(positionalRows), '');
|
|
317
|
+
const optionRows = commandOptions(command).map((arg) => [
|
|
318
|
+
formatCommandOptionTerm(arg),
|
|
319
|
+
formatArgHelp(arg),
|
|
320
|
+
]);
|
|
321
|
+
if (optionRows.length)
|
|
322
|
+
lines.push('Command options:', ...formatRows(optionRows), '');
|
|
323
|
+
lines.push(formatCommonOptionsHelp(), '');
|
|
324
|
+
if (command.browser)
|
|
325
|
+
lines.push(formatBrowserCommonOptionsHelp(), '');
|
|
326
|
+
const meta = [
|
|
327
|
+
`Access: ${command.access}`,
|
|
328
|
+
`Browser: ${command.browser ? 'yes' : 'no'}`,
|
|
329
|
+
];
|
|
330
|
+
if (command.domain)
|
|
331
|
+
meta.push(`Domain: ${command.domain}`);
|
|
332
|
+
if (command.defaultFormat)
|
|
333
|
+
meta.push(`Default format: ${command.defaultFormat}`);
|
|
334
|
+
if (command.aliases.length)
|
|
335
|
+
meta.push(`Aliases: ${command.aliases.join(', ')}`);
|
|
336
|
+
lines.push(meta.join(' | '));
|
|
337
|
+
lines.push(`Example: ${formatPresentableCommandExample(command)}`);
|
|
338
|
+
if (command.columns.length)
|
|
339
|
+
lines.push(`Output columns: ${command.columns.join(', ')}`);
|
|
340
|
+
lines.push("Agent tip: use '--help -f yaml' for structured args/options.", '');
|
|
341
|
+
return lines.join('\n');
|
|
342
|
+
}
|
|
343
|
+
export function formatSiteCommandDescription(command) {
|
|
344
|
+
return `${command.access === 'write' ? '[write]' : '[read]'} ${command.description}`;
|
|
345
|
+
}
|
|
346
|
+
export function wrapCommaList(items, opts = {}) {
|
|
347
|
+
const width = Math.max(opts.width ?? process.stdout.columns ?? 100, 40);
|
|
348
|
+
const indent = opts.indent ?? ' ';
|
|
349
|
+
const sorted = [...items].sort((a, b) => a.localeCompare(b));
|
|
350
|
+
const lines = [];
|
|
351
|
+
let line = indent;
|
|
352
|
+
sorted.forEach((item, index) => {
|
|
353
|
+
const token = `${item}${index < sorted.length - 1 ? ',' : ''}`;
|
|
354
|
+
const prefix = line === indent ? '' : ' ';
|
|
355
|
+
if (line.length + prefix.length + token.length > width && line.trim()) {
|
|
356
|
+
lines.push(line);
|
|
357
|
+
line = `${indent}${token}`;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
line += `${prefix}${token}`;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
if (line.trim())
|
|
364
|
+
lines.push(line);
|
|
365
|
+
return lines.join('\n');
|
|
366
|
+
}
|
|
367
|
+
function commandFullName(command) {
|
|
368
|
+
return `${command.site}/${command.name}`;
|
|
369
|
+
}
|
|
370
|
+
function uniqueCommands(commands) {
|
|
371
|
+
return [...new Map(commands.map((command) => [commandFullName(command), command])).values()]
|
|
372
|
+
.sort((a, b) => commandFullName(a).localeCompare(commandFullName(b)));
|
|
373
|
+
}
|
|
374
|
+
function commandsForSite(site, commands) {
|
|
375
|
+
return uniqueCommands(commands.filter((command) => command.site === site))
|
|
376
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
377
|
+
}
|
|
378
|
+
function positionals(command) {
|
|
379
|
+
return command.args.filter((arg) => arg.positional);
|
|
380
|
+
}
|
|
381
|
+
function commandOptions(command) {
|
|
382
|
+
return command.args.filter((arg) => !arg.positional);
|
|
383
|
+
}
|
|
384
|
+
function formatPositionals(args) {
|
|
385
|
+
return args.map((arg) => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ');
|
|
386
|
+
}
|
|
387
|
+
function formatCommandOptionTerm(arg) {
|
|
388
|
+
return arg.required || arg.valueRequired ? `--${arg.name} <value>` : `--${arg.name} [value]`;
|
|
389
|
+
}
|
|
390
|
+
function formatUsage(command) {
|
|
391
|
+
const positionalText = formatPositionals(positionals(command));
|
|
392
|
+
return `${CLI_COMMAND} ${command.site} ${command.name}${positionalText ? ` ${positionalText}` : ''} [options]`;
|
|
393
|
+
}
|
|
394
|
+
function compactCommand(command) {
|
|
395
|
+
return {
|
|
396
|
+
name: command.name,
|
|
397
|
+
command: `${CLI_COMMAND} ${command.site} ${command.name}`,
|
|
398
|
+
usage: formatUsage(command),
|
|
399
|
+
access: command.access,
|
|
400
|
+
description: command.description,
|
|
401
|
+
browser: command.browser,
|
|
402
|
+
...(command.domain ? { domain: command.domain } : {}),
|
|
403
|
+
...(command.aliases.length ? { aliases: [...command.aliases] } : {}),
|
|
404
|
+
positionals: positionals(command).map(compactArg),
|
|
405
|
+
command_options: commandOptions(command).map(compactArg),
|
|
406
|
+
...(command.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
|
|
407
|
+
example: formatPresentableCommandExample(command),
|
|
408
|
+
...(command.siteSession ? { siteSession: command.siteSession } : {}),
|
|
409
|
+
...(command.defaultFormat ? { defaultFormat: command.defaultFormat } : {}),
|
|
410
|
+
...(command.columns.length ? { columns: [...command.columns] } : {}),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function compactArg(arg) {
|
|
414
|
+
return {
|
|
415
|
+
name: arg.name,
|
|
416
|
+
...(arg.type && arg.type !== 'string' ? { type: arg.type } : {}),
|
|
417
|
+
...(arg.positional ? { positional: true } : {}),
|
|
418
|
+
...(arg.required ? { required: true } : {}),
|
|
419
|
+
...(arg.valueRequired ? { valueRequired: true } : {}),
|
|
420
|
+
...(arg.default !== undefined ? { default: arg.default } : {}),
|
|
421
|
+
...(arg.choices?.length ? { choices: [...arg.choices] } : {}),
|
|
422
|
+
...(arg.help ? { help: arg.help } : {}),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function compactCommonOption(option) {
|
|
426
|
+
return {
|
|
427
|
+
name: option.name,
|
|
428
|
+
flags: option.flags,
|
|
429
|
+
help: option.help,
|
|
430
|
+
...('default' in option ? { default: option.default } : {}),
|
|
431
|
+
...('choices' in option ? { choices: [...option.choices] } : {}),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
export function serializePresentableArg(arg) {
|
|
435
|
+
return {
|
|
436
|
+
name: arg.name,
|
|
437
|
+
type: arg.type ?? 'string',
|
|
438
|
+
required: !!arg.required,
|
|
439
|
+
valueRequired: !!arg.valueRequired,
|
|
440
|
+
positional: !!arg.positional,
|
|
441
|
+
choices: [...(arg.choices ?? [])],
|
|
442
|
+
default: arg.default ?? null,
|
|
443
|
+
help: arg.help ?? '',
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
export function formatArgumentSummary(args) {
|
|
447
|
+
return args.map((arg) => {
|
|
448
|
+
if (arg.positional)
|
|
449
|
+
return arg.required ? `<${arg.name}>` : `[${arg.name}]`;
|
|
450
|
+
return arg.required ? `--${arg.name}` : `[--${arg.name}]`;
|
|
451
|
+
}).join(' ');
|
|
452
|
+
}
|
|
453
|
+
export function formatPresentableCommandExample(command) {
|
|
454
|
+
if (command.example?.trim())
|
|
455
|
+
return command.example.trim();
|
|
456
|
+
const parts = [CLI_COMMAND, command.site, command.name];
|
|
457
|
+
for (const arg of command.args) {
|
|
458
|
+
if (arg.positional && arg.required)
|
|
459
|
+
parts.push(`<${arg.name}>`);
|
|
460
|
+
}
|
|
461
|
+
for (const arg of command.args) {
|
|
462
|
+
if (arg.positional || !arg.required)
|
|
463
|
+
continue;
|
|
464
|
+
parts.push(`--${arg.name}`);
|
|
465
|
+
if (arg.type !== 'bool' && arg.type !== 'boolean')
|
|
466
|
+
parts.push(`<${arg.name}>`);
|
|
467
|
+
}
|
|
468
|
+
parts.push('-f', 'yaml');
|
|
469
|
+
return parts.join(' ');
|
|
470
|
+
}
|
|
471
|
+
function formatRows(rows) {
|
|
472
|
+
if (rows.length === 0)
|
|
473
|
+
return [];
|
|
474
|
+
const width = Math.min(Math.max(...rows.map(([left]) => left.length)), 34);
|
|
475
|
+
return rows.map(([left, right]) => ` ${left.padEnd(width + 2)}${right}`);
|
|
476
|
+
}
|
|
477
|
+
function formatArgHelp(arg) {
|
|
478
|
+
const parts = [];
|
|
479
|
+
if (arg.help)
|
|
480
|
+
parts.push(arg.help);
|
|
481
|
+
if (arg.default !== undefined)
|
|
482
|
+
parts.push(`default: ${arg.default}`);
|
|
483
|
+
if (arg.choices?.length)
|
|
484
|
+
parts.push(`choices: ${arg.choices.join(', ')}`);
|
|
485
|
+
return parts.join(' ');
|
|
486
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Strategy } from './registry.js';
|
|
3
|
+
import { commandListPresentation, commandListRows, formatCommandHelp, formatRootHelp, formatSiteHelp, getCommandCompletionCandidates, toPresentableCommand, } from './command-presentation.js';
|
|
4
|
+
const args = [
|
|
5
|
+
{ name: 'owner', positional: true, required: true, help: 'Repository owner' },
|
|
6
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Maximum issues' },
|
|
7
|
+
];
|
|
8
|
+
const localCommand = {
|
|
9
|
+
site: 'github',
|
|
10
|
+
name: 'issues',
|
|
11
|
+
aliases: ['issue-list'],
|
|
12
|
+
description: 'List repository issues',
|
|
13
|
+
access: 'read',
|
|
14
|
+
strategy: Strategy.COOKIE,
|
|
15
|
+
browser: true,
|
|
16
|
+
args: args.map((arg) => ({ ...arg })),
|
|
17
|
+
columns: ['number', 'title'],
|
|
18
|
+
domain: 'github.com',
|
|
19
|
+
defaultFormat: 'yaml',
|
|
20
|
+
};
|
|
21
|
+
const hostedCommand = {
|
|
22
|
+
site: 'github',
|
|
23
|
+
name: 'issues',
|
|
24
|
+
aliases: ['issue-list'],
|
|
25
|
+
command: 'github/issues',
|
|
26
|
+
description: 'List repository issues',
|
|
27
|
+
access: 'read',
|
|
28
|
+
strategy: 'COOKIE',
|
|
29
|
+
browser: true,
|
|
30
|
+
args: args.map((arg) => ({ ...arg })),
|
|
31
|
+
columns: ['number', 'title'],
|
|
32
|
+
domain: 'github.com',
|
|
33
|
+
defaultFormat: 'yaml',
|
|
34
|
+
};
|
|
35
|
+
describe('shared command presentation', () => {
|
|
36
|
+
it('renders byte-identical root help for equal local and hosted surfaces', () => {
|
|
37
|
+
const local = {
|
|
38
|
+
description: 'Make any website your CLI. Zero setup. AI-powered.',
|
|
39
|
+
commands: [
|
|
40
|
+
{ name: 'list', description: 'List all available CLI commands' },
|
|
41
|
+
{ name: 'setup', description: 'Configure local or hosted mode' },
|
|
42
|
+
],
|
|
43
|
+
options: [
|
|
44
|
+
{ flags: '--profile <name>', description: 'Browser profile/context alias' },
|
|
45
|
+
{ flags: '-h, --help', description: 'Display help for command' },
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
const hosted = JSON.parse(JSON.stringify(local));
|
|
49
|
+
expect(formatRootHelp(hosted)).toBe(formatRootHelp(local));
|
|
50
|
+
});
|
|
51
|
+
it('normalizes local and hosted metadata to byte-identical site and command help', () => {
|
|
52
|
+
const local = toPresentableCommand(localCommand);
|
|
53
|
+
const hosted = toPresentableCommand(hostedCommand);
|
|
54
|
+
expect(formatSiteHelp('github', [hosted])).toBe(formatSiteHelp('github', [local]));
|
|
55
|
+
expect(formatCommandHelp(hosted)).toBe(formatCommandHelp(local));
|
|
56
|
+
});
|
|
57
|
+
it('builds byte-identical structured and display list rows', () => {
|
|
58
|
+
const local = toPresentableCommand(localCommand);
|
|
59
|
+
const hosted = toPresentableCommand(hostedCommand);
|
|
60
|
+
expect(commandListRows([hosted], true)).toEqual(commandListRows([local], true));
|
|
61
|
+
expect(commandListRows([hosted], false)).toEqual(commandListRows([local], false));
|
|
62
|
+
});
|
|
63
|
+
it('builds byte-identical canonical grouped list displays', () => {
|
|
64
|
+
const local = toPresentableCommand(localCommand);
|
|
65
|
+
const hosted = toPresentableCommand(hostedCommand);
|
|
66
|
+
const externalClis = [{ label: 'gh', installed: true, description: 'GitHub CLI' }];
|
|
67
|
+
const expected = [
|
|
68
|
+
'',
|
|
69
|
+
' webcmd — available commands',
|
|
70
|
+
'',
|
|
71
|
+
' Site adapters',
|
|
72
|
+
'',
|
|
73
|
+
' github',
|
|
74
|
+
' issues [cookie] (aliases: issue-list) — List repository issues',
|
|
75
|
+
'',
|
|
76
|
+
' external CLIs',
|
|
77
|
+
' gh [installed] — GitHub CLI',
|
|
78
|
+
'',
|
|
79
|
+
' 1 built-in commands across 0 apps + 1 sites, 1 external CLIs',
|
|
80
|
+
'',
|
|
81
|
+
];
|
|
82
|
+
expect(commandListPresentation([hosted], 'table', { externalClis }).displayLines)
|
|
83
|
+
.toEqual(commandListPresentation([local], 'table', { externalClis }).displayLines);
|
|
84
|
+
expect(commandListPresentation([local], 'table', { externalClis }).displayLines).toEqual(expected);
|
|
85
|
+
});
|
|
86
|
+
it('builds byte-identical root, site, and alias completion candidates', () => {
|
|
87
|
+
const local = [toPresentableCommand(localCommand)];
|
|
88
|
+
const hosted = [toPresentableCommand(hostedCommand)];
|
|
89
|
+
const builtins = ['completion', 'list', 'setup'];
|
|
90
|
+
expect(getCommandCompletionCandidates(hosted, [], 1, builtins))
|
|
91
|
+
.toEqual(getCommandCompletionCandidates(local, [], 1, builtins));
|
|
92
|
+
expect(getCommandCompletionCandidates(hosted, ['github'], 2, builtins))
|
|
93
|
+
.toEqual(getCommandCompletionCandidates(local, ['github'], 2, builtins));
|
|
94
|
+
expect(getCommandCompletionCandidates(hosted, ['github'], 2, builtins))
|
|
95
|
+
.toEqual(['issue-list', 'issues']);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { ArgumentError } from './errors.js';
|
|
3
|
+
import type { Arg } from './registry.js';
|
|
4
|
+
export declare const OUTPUT_FORMATS: readonly ["table", "plain", "json", "yaml", "yml", "md", "markdown", "csv"];
|
|
5
|
+
export declare const TRACE_MODES: readonly ["off", "on", "retain-on-failure"];
|
|
6
|
+
export type OutputFormat = string;
|
|
7
|
+
export type TraceMode = typeof TRACE_MODES[number];
|
|
8
|
+
export interface CommandSurfaceMetadata {
|
|
9
|
+
args: readonly Arg[];
|
|
10
|
+
browser?: boolean;
|
|
11
|
+
defaultFormat?: string | null;
|
|
12
|
+
command?: string;
|
|
13
|
+
site?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ParsedCommandSurface {
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
optionSources: Record<string, 'cli' | 'default'>;
|
|
19
|
+
format: OutputFormat;
|
|
20
|
+
formatExplicit: boolean;
|
|
21
|
+
trace: TraceMode;
|
|
22
|
+
profile?: string;
|
|
23
|
+
verbose: boolean;
|
|
24
|
+
help: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Identifies the one parser failure whose public bytes are owned by Commander. */
|
|
27
|
+
export declare class MissingRequiredPositionalError extends ArgumentError {
|
|
28
|
+
readonly argumentName: string;
|
|
29
|
+
constructor(argumentName: string, help?: string);
|
|
30
|
+
}
|
|
31
|
+
/** Raw structural failure bytes/status owned by Commander. */
|
|
32
|
+
export declare class CommanderStructuralError extends Error {
|
|
33
|
+
readonly output: string;
|
|
34
|
+
readonly exitCode: number;
|
|
35
|
+
constructor(output: string, exitCode: number);
|
|
36
|
+
}
|
|
37
|
+
/** Register the adapter argument grammar and its shared execution options. */
|
|
38
|
+
export declare function configureCommandSurface(command: Command, metadata: CommandSurfaceMetadata): void;
|
|
39
|
+
/** Parse one adapter invocation without requiring a local Commander program. */
|
|
40
|
+
export declare function parseCommandSurface(metadata: CommandSurfaceMetadata, argv: string[]): ParsedCommandSurface;
|
|
41
|
+
/** Apply the adapter's required/default/type/choice contract to raw values. */
|
|
42
|
+
export declare function coerceCommandArguments(definitions: readonly Arg[], input: Record<string, unknown>): Record<string, unknown>;
|
|
43
|
+
export declare function parseOutputFormat(value: unknown): OutputFormat;
|