@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.
- package/README.md +13 -2
- package/cli-manifest.json +217 -13
- package/clis/_shared/site-auth.js +3 -3
- package/clis/_shared/site-auth.test.js +4 -4
- 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/slock/whoami.test.js +2 -2
- 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/daemon-client.d.ts +2 -1
- package/dist/src/browser/runtime/local-cloak/actions.js +4 -1
- 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/provider.test.js +36 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +10 -2
- package/dist/src/browser/runtime/local-cloak/session-manager.js +25 -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 +63 -110
- package/dist/src/cli.test.js +82 -6
- 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/commands/auth.js +3 -2
- package/dist/src/commands/auth.test.js +6 -6
- 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 +11 -1
- 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 +4 -0
- package/dist/src/hosted/args.js +4 -0
- package/dist/src/hosted/args.test.d.ts +1 -0
- package/dist/src/hosted/args.test.js +49 -0
- 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 +180 -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 +59 -0
- package/dist/src/hosted/client.js +510 -0
- package/dist/src/hosted/client.test.d.ts +1 -0
- package/dist/src/hosted/client.test.js +738 -0
- package/dist/src/hosted/config.d.ts +55 -0
- package/dist/src/hosted/config.js +112 -0
- package/dist/src/hosted/config.test.d.ts +1 -0
- package/dist/src/hosted/config.test.js +81 -0
- 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 +14 -0
- package/dist/src/hosted/manifest.js +47 -0
- package/dist/src/hosted/manifest.test.d.ts +1 -0
- package/dist/src/hosted/manifest.test.js +164 -0
- 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 +19 -0
- package/dist/src/hosted/runner.js +705 -0
- package/dist/src/hosted/runner.test.d.ts +1 -0
- package/dist/src/hosted/runner.test.js +1607 -0
- package/dist/src/hosted/setup.d.ts +10 -0
- package/dist/src/hosted/setup.js +79 -0
- package/dist/src/hosted/setup.test.d.ts +1 -0
- package/dist/src/hosted/setup.test.js +195 -0
- package/dist/src/hosted/types.d.ts +175 -0
- package/dist/src/hosted/types.js +1 -0
- package/dist/src/main.js +123 -97
- 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.d.ts +6 -1
- package/dist/src/release-notes.js +192 -4
- package/dist/src/release-notes.test.js +143 -4
- 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 +36748 -0
- package/package.json +6 -1
- package/scripts/check-hosted-contract.mjs +108 -0
- package/scripts/docs-sync-review.ts +332 -0
- package/scripts/generate-release-notes.ts +1 -1
- package/scripts/sync-community-plugins.ts +14 -0
- package/skills/smart-search/SKILL.md +14 -3
- package/skills/webcmd-autofix/SKILL.md +26 -37
- package/skills/webcmd-usage/SKILL.md +21 -7
- package/clis/antigravity/SKILL.md +0 -38
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { Command, CommanderError } from 'commander';
|
|
5
|
+
import { configureCompletionCommandSurface, configureListCommandSurface } from '../builtin-command-surface.js';
|
|
6
|
+
import { BrowserSessionArgvError, rewriteBrowserArgv } from '../cli-argv-preprocess.js';
|
|
7
|
+
import { CommanderStructuralError, MissingRequiredPositionalError } from '../command-surface.js';
|
|
8
|
+
import { formatRootHelp, getCommandCompletionCandidates } from '../command-presentation.js';
|
|
9
|
+
import { HOSTED_BUILTIN_COMMANDS, HOSTED_ROOT_HELP, LOCAL_ONLY_COMMAND_HELP, } from '../completion-shared.js';
|
|
10
|
+
import { CliError, ConfigError, EXIT_CODES, toEnvelope } from '../errors.js';
|
|
11
|
+
import { getRequestedHelpFormat, renderStructuredHelp } from '../help.js';
|
|
12
|
+
import { findPackageRoot } from '../package-paths.js';
|
|
13
|
+
import { formatErrorEnvelope, render as renderOutput } from '../output.js';
|
|
14
|
+
import { StreamWriteError, writeToStream } from '../stream-write.js';
|
|
15
|
+
import { PKG_VERSION } from '../version.js';
|
|
16
|
+
import { getCompletionScriptFast } from '../completion-fast.js';
|
|
17
|
+
import { browserCommandCatalog } from '../browser/command-catalog.js';
|
|
18
|
+
import { HostedClient, HostedClientError } from './client.js';
|
|
19
|
+
import { parseHostedInvocation } from './args.js';
|
|
20
|
+
import { HostedBrowserHelp, parseHostedBrowserStructure } from './browser-args.js';
|
|
21
|
+
import { materializeHostedOutputs, prepareHostedFiles, rewriteHostedOutputResultPaths } from './files.js';
|
|
22
|
+
import { findHostedCommand, hostedCommandHelpData, hostedCommands, hostedListPresentation, hostedSiteHelpData, isLocalOnlyHostedCommand, renderHostedCommandHelp, renderHostedSiteHelp, } from './manifest.js';
|
|
23
|
+
import { isHostedConfig, loadWebcmdConfig } from './config.js';
|
|
24
|
+
import { resolveHostedApiKey } from './credentials.js';
|
|
25
|
+
import { parseHostedRootCommandSurface } from '../root-command-surface.js';
|
|
26
|
+
class CommanderCompatibleError extends Error {
|
|
27
|
+
output;
|
|
28
|
+
exitCode;
|
|
29
|
+
stdoutOutput;
|
|
30
|
+
constructor(output, exitCode, stdoutOutput) {
|
|
31
|
+
super(output.trimEnd());
|
|
32
|
+
this.output = output;
|
|
33
|
+
this.exitCode = exitCode;
|
|
34
|
+
this.stdoutOutput = stdoutOutput;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const hostedBrowserCommandsByPath = new Map(browserCommandCatalog.map(command => [command.command, command]));
|
|
38
|
+
export async function runHostedCli(argv, opts = {}) {
|
|
39
|
+
const config = opts.config ?? loadWebcmdConfig({ env: opts.env, homeDir: opts.homeDir });
|
|
40
|
+
if (!isHostedConfig(config))
|
|
41
|
+
return { handled: false, exitCode: EXIT_CODES.SUCCESS };
|
|
42
|
+
const stdout = opts.stdout ?? process.stdout;
|
|
43
|
+
const stderr = opts.stderr ?? process.stderr;
|
|
44
|
+
try {
|
|
45
|
+
const credential = await resolveHostedApiKey(config, {
|
|
46
|
+
credentialStore: opts.credentialStore,
|
|
47
|
+
env: opts.env,
|
|
48
|
+
homeDir: opts.homeDir,
|
|
49
|
+
platform: opts.platform,
|
|
50
|
+
randomUUID: opts.randomUUID,
|
|
51
|
+
migrate: opts.config === undefined,
|
|
52
|
+
});
|
|
53
|
+
const client = new HostedClient({
|
|
54
|
+
apiBaseUrl: config.hosted.apiBaseUrl,
|
|
55
|
+
apiKey: credential.apiKey,
|
|
56
|
+
fetchImpl: opts.fetchImpl,
|
|
57
|
+
});
|
|
58
|
+
await dispatchHosted(argv, client, stdout, stderr, opts.now ?? Date.now);
|
|
59
|
+
return { handled: true, exitCode: EXIT_CODES.SUCCESS };
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (err instanceof StreamWriteError)
|
|
63
|
+
throw err;
|
|
64
|
+
if (err instanceof CliError && err.code === 'UNSUPPORTED_SHELL')
|
|
65
|
+
throw err;
|
|
66
|
+
if (err instanceof CommanderStructuralError) {
|
|
67
|
+
await writeToStream(stderr, err.output);
|
|
68
|
+
return { handled: true, exitCode: err.exitCode };
|
|
69
|
+
}
|
|
70
|
+
if (err instanceof CommanderCompatibleError) {
|
|
71
|
+
await writeToStream(stderr, err.output);
|
|
72
|
+
if (err.stdoutOutput)
|
|
73
|
+
await writeToStream(stdout, err.stdoutOutput);
|
|
74
|
+
return { handled: true, exitCode: err.exitCode };
|
|
75
|
+
}
|
|
76
|
+
if (err instanceof MissingRequiredPositionalError) {
|
|
77
|
+
await writeToStream(stderr, `error: missing required argument '${err.argumentName}'\n`);
|
|
78
|
+
return { handled: true, exitCode: EXIT_CODES.GENERIC_ERROR };
|
|
79
|
+
}
|
|
80
|
+
await writeToStream(stderr, formatErrorEnvelope(toEnvelope(err), {
|
|
81
|
+
cmdName: hostedCommandName(argv),
|
|
82
|
+
traceMode: hostedTraceMode(argv),
|
|
83
|
+
}));
|
|
84
|
+
return {
|
|
85
|
+
handled: true,
|
|
86
|
+
exitCode: errorExitCode(err),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function dispatchHosted(argv, client, stdout, stderr, now) {
|
|
91
|
+
const normalized = parseHostedRootCommandSurface(argv);
|
|
92
|
+
if (normalized.kind === 'help') {
|
|
93
|
+
const help = formatRootHelp(HOSTED_ROOT_HELP);
|
|
94
|
+
if (normalized.exitCode !== EXIT_CODES.SUCCESS) {
|
|
95
|
+
throw new CommanderCompatibleError(help, normalized.exitCode);
|
|
96
|
+
}
|
|
97
|
+
await writeToStream(stdout, help);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (normalized.kind === 'version') {
|
|
101
|
+
await writeToStream(stdout, normalized.output);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (normalized.kind === 'completion') {
|
|
105
|
+
const manifest = await client.getManifest();
|
|
106
|
+
validateManifestContractIdentity(manifest);
|
|
107
|
+
await writeToStream(stdout, hostedCompletions(manifest, normalized.argv).join('\n') + '\n');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const args = normalized.argv;
|
|
111
|
+
if (args[0] === 'completion') {
|
|
112
|
+
const parsed = parseHostedCompletionSurface(args.slice(1), normalized.literal);
|
|
113
|
+
if (parsed.kind === 'help') {
|
|
114
|
+
await writeToStream(stdout, parsed.output);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const script = getCompletionScriptFast(parsed.shell);
|
|
118
|
+
if (script === undefined) {
|
|
119
|
+
// Run the canonical local Commander action for this rare error path so
|
|
120
|
+
// hosted mode preserves even the historical public stack bytes.
|
|
121
|
+
const { createProgram } = await import('../cli.js');
|
|
122
|
+
await createProgram('', '').parseAsync(argv, { from: 'user' });
|
|
123
|
+
throw new CliError('UNSUPPORTED_SHELL', `Unsupported shell: ${parsed.shell}. Supported: bash, zsh, fish`);
|
|
124
|
+
}
|
|
125
|
+
await writeToStream(stdout, script);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (args[0] === 'daemon') {
|
|
129
|
+
throw new ConfigError('webcmd daemon is local-only. Hosted mode has no local daemon.', LOCAL_ONLY_COMMAND_HELP);
|
|
130
|
+
}
|
|
131
|
+
if (args[0] === 'browser') {
|
|
132
|
+
const invocation = parseHostedBrowserInvocation(args, normalized.profile);
|
|
133
|
+
const manifest = await client.getManifest();
|
|
134
|
+
validateManifestContractIdentity(manifest);
|
|
135
|
+
await dispatchHostedBrowser(invocation, client, stdout);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (args[0] === 'list') {
|
|
139
|
+
const parsed = parseHostedListSurface(args.slice(1), normalized.literal);
|
|
140
|
+
if (parsed.kind === 'help') {
|
|
141
|
+
await writeToStream(stdout, parsed.output);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const manifest = await client.getManifest();
|
|
145
|
+
validateManifestContractIdentity(manifest);
|
|
146
|
+
await renderHostedList(manifest, parsed.format, parsed.formatExplicit, stdout);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const manifest = await client.getManifest();
|
|
150
|
+
validateManifestContractIdentity(manifest);
|
|
151
|
+
const site = args[0];
|
|
152
|
+
const commandName = args[1];
|
|
153
|
+
const siteExists = manifest.commands.some(command => command.site === site);
|
|
154
|
+
if (!siteExists) {
|
|
155
|
+
const unknownRoot = parseUnknownSiteRootOptions(args, normalized.literal);
|
|
156
|
+
if (unknownRoot.version) {
|
|
157
|
+
await writeToStream(stdout, `${PKG_VERSION}\n`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (unknownRoot.help) {
|
|
161
|
+
await writeToStream(stdout, formatRootHelp(HOSTED_ROOT_HELP));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
throw new CommanderCompatibleError(`error: unknown command '${site}'\n`, EXIT_CODES.USAGE_ERROR, formatRootHelp(HOSTED_ROOT_HELP));
|
|
165
|
+
}
|
|
166
|
+
if (!commandName || commandName === '--help' || commandName === '-h') {
|
|
167
|
+
const data = hostedSiteHelpData(manifest, site);
|
|
168
|
+
if (!data) {
|
|
169
|
+
throw new CommanderCompatibleError(`error: unknown command '${site}'\n`, EXIT_CODES.USAGE_ERROR, formatRootHelp(HOSTED_ROOT_HELP));
|
|
170
|
+
}
|
|
171
|
+
await writeHostedHelp(stdout, args, data, renderHostedSiteHelp(manifest, site));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const command = findHostedCommand(manifest, site, commandName);
|
|
175
|
+
if (!command) {
|
|
176
|
+
if (!normalized.literal && hasTerminalBeforeSeparator(args.slice(1), token => token === '--help' || token === '-h')) {
|
|
177
|
+
const data = hostedSiteHelpData(manifest, site);
|
|
178
|
+
if (!data) {
|
|
179
|
+
throw new CommanderCompatibleError(`error: unknown command '${site}'\n`, EXIT_CODES.USAGE_ERROR, formatRootHelp(HOSTED_ROOT_HELP));
|
|
180
|
+
}
|
|
181
|
+
await writeHostedHelp(stdout, args, data, renderHostedSiteHelp(manifest, site));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
throw new CommanderCompatibleError(`error: unknown command '${commandName}'\n`, EXIT_CODES.GENERIC_ERROR);
|
|
185
|
+
}
|
|
186
|
+
if (isLocalOnlyHostedCommand(command)) {
|
|
187
|
+
throw new ConfigError(`Command ${command.command} is local-only and is not available in hosted mode.`, LOCAL_ONLY_COMMAND_HELP);
|
|
188
|
+
}
|
|
189
|
+
const parsed = parseHostedInvocation(command, args.slice(2));
|
|
190
|
+
if (parsed.help) {
|
|
191
|
+
await writeHostedHelp(stdout, args, hostedCommandHelpData(command), renderHostedCommandHelp(command));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const startTime = now();
|
|
195
|
+
const response = hasPresentFileArgument(command, parsed.args)
|
|
196
|
+
? await executeHostedFileCommand({
|
|
197
|
+
client,
|
|
198
|
+
command,
|
|
199
|
+
args: parsed.args,
|
|
200
|
+
format: parsed.format,
|
|
201
|
+
trace: parsed.trace,
|
|
202
|
+
profile: parsed.profile ?? normalized.profile,
|
|
203
|
+
})
|
|
204
|
+
: await client.execute({
|
|
205
|
+
command: command.command,
|
|
206
|
+
args: parsed.args,
|
|
207
|
+
format: parsed.format,
|
|
208
|
+
trace: parsed.trace,
|
|
209
|
+
profile: parsed.profile ?? normalized.profile,
|
|
210
|
+
});
|
|
211
|
+
let format = parsed.format;
|
|
212
|
+
if (!parsed.formatExplicit && format === 'table' && command.defaultFormat) {
|
|
213
|
+
format = command.defaultFormat;
|
|
214
|
+
}
|
|
215
|
+
const elapsed = (now() - startTime) / 1000;
|
|
216
|
+
if (response.result !== null && response.result !== undefined) {
|
|
217
|
+
await renderOutput(response.result, {
|
|
218
|
+
fmt: format,
|
|
219
|
+
fmtExplicit: parsed.formatExplicit,
|
|
220
|
+
columns: response.columns ?? command.columns,
|
|
221
|
+
title: command.command,
|
|
222
|
+
elapsed,
|
|
223
|
+
source: command.command,
|
|
224
|
+
footerExtra: response.footerExtra,
|
|
225
|
+
stdout,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (parsed.trace === 'on' && response.trace) {
|
|
229
|
+
await writeToStream(stderr, `Webcmd trace artifact: ${response.trace.receipt}\n`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function hasPresentFileArgument(command, args) {
|
|
233
|
+
return command.args.some((arg) => {
|
|
234
|
+
if (!arg.file)
|
|
235
|
+
return false;
|
|
236
|
+
const value = args[arg.name] ?? arg.default;
|
|
237
|
+
return value !== undefined && value !== null && value !== '';
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function executeHostedFileCommand(input) {
|
|
241
|
+
const prepared = await prepareHostedFiles({
|
|
242
|
+
client: input.client,
|
|
243
|
+
command: input.command,
|
|
244
|
+
args: input.args,
|
|
245
|
+
});
|
|
246
|
+
const response = await input.client.runPreparedExecution({
|
|
247
|
+
executionId: prepared.executionId,
|
|
248
|
+
command: input.command.command,
|
|
249
|
+
args: prepared.args,
|
|
250
|
+
format: input.format,
|
|
251
|
+
trace: input.trace,
|
|
252
|
+
...(input.profile !== undefined ? { profile: input.profile } : {}),
|
|
253
|
+
});
|
|
254
|
+
const materialized = await materializeHostedOutputs({
|
|
255
|
+
client: input.client,
|
|
256
|
+
response,
|
|
257
|
+
outputs: prepared.outputs,
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
...response,
|
|
261
|
+
result: rewriteHostedOutputResultPaths(response.result, materialized),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async function dispatchHostedBrowser(invocation, client, stdout) {
|
|
265
|
+
const args = invocation.action === 'set-file-input'
|
|
266
|
+
? materializeHostedBrowserUploadArgs(invocation.args)
|
|
267
|
+
: invocation.args;
|
|
268
|
+
const response = await client.runBrowserAction(invocation.session, {
|
|
269
|
+
command: invocation.command,
|
|
270
|
+
action: invocation.action,
|
|
271
|
+
args,
|
|
272
|
+
...(invocation.profile !== undefined ? { profile: invocation.profile } : {}),
|
|
273
|
+
...(invocation.windowMode !== undefined ? { windowMode: invocation.windowMode } : {}),
|
|
274
|
+
trace: 'off',
|
|
275
|
+
});
|
|
276
|
+
await renderHostedBrowserResponse(stdout, invocation, response);
|
|
277
|
+
}
|
|
278
|
+
function materializeHostedBrowserUploadArgs(args) {
|
|
279
|
+
const files = args.files;
|
|
280
|
+
if (!Array.isArray(files))
|
|
281
|
+
return args;
|
|
282
|
+
return {
|
|
283
|
+
...args,
|
|
284
|
+
files: files.map((file) => {
|
|
285
|
+
if (typeof file !== 'string')
|
|
286
|
+
return file;
|
|
287
|
+
const body = readFileSync(file);
|
|
288
|
+
return {
|
|
289
|
+
$webcmdBrowserUpload: {
|
|
290
|
+
filename: path.basename(file),
|
|
291
|
+
contentType: contentTypeForUpload(file),
|
|
292
|
+
base64: Buffer.from(body).toString('base64'),
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function contentTypeForUpload(filePath) {
|
|
299
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
300
|
+
case '.gif':
|
|
301
|
+
return 'image/gif';
|
|
302
|
+
case '.jpg':
|
|
303
|
+
case '.jpeg':
|
|
304
|
+
return 'image/jpeg';
|
|
305
|
+
case '.json':
|
|
306
|
+
return 'application/json';
|
|
307
|
+
case '.pdf':
|
|
308
|
+
return 'application/pdf';
|
|
309
|
+
case '.png':
|
|
310
|
+
return 'image/png';
|
|
311
|
+
case '.txt':
|
|
312
|
+
return 'text/plain';
|
|
313
|
+
case '.webp':
|
|
314
|
+
return 'image/webp';
|
|
315
|
+
default:
|
|
316
|
+
return 'application/octet-stream';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function parseHostedBrowserInvocation(argv, profile) {
|
|
320
|
+
let rewritten;
|
|
321
|
+
try {
|
|
322
|
+
rewritten = rewriteBrowserArgv(argv);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (error instanceof BrowserSessionArgvError) {
|
|
326
|
+
throw new ConfigError(error.message, 'Use: webcmd browser <session> <command>');
|
|
327
|
+
}
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
let structure;
|
|
331
|
+
try {
|
|
332
|
+
structure = parseHostedBrowserStructure(rewritten);
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
if (error instanceof HostedBrowserHelp)
|
|
336
|
+
throw new CommanderCompatibleError('', 0, error.output);
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
if (rewritten[0] !== 'browser') {
|
|
340
|
+
throw new ConfigError('Hosted browser invocation must start with browser.');
|
|
341
|
+
}
|
|
342
|
+
if (!structure.session) {
|
|
343
|
+
throw new ConfigError('<session> is required for hosted browser commands.', 'Use: webcmd browser <session> <command>');
|
|
344
|
+
}
|
|
345
|
+
if (!structure.commandName) {
|
|
346
|
+
throw new ConfigError('Hosted browser command is required.', 'Use: webcmd browser <session> open <url>, state, screenshot, tab list, or eval <js>.');
|
|
347
|
+
}
|
|
348
|
+
const windowMode = structure.window === undefined ? undefined : parseWindowMode(structure.window);
|
|
349
|
+
const parsed = parseBrowserLeaf(structure.commandName, structure.positionals, structure.options);
|
|
350
|
+
return {
|
|
351
|
+
session: structure.session,
|
|
352
|
+
command: `browser/${parsed.commandName}`,
|
|
353
|
+
action: parsed.action,
|
|
354
|
+
args: parsed.args,
|
|
355
|
+
...(parsed.localPath !== undefined ? { localPath: parsed.localPath } : {}),
|
|
356
|
+
...(profile !== undefined ? { profile } : {}),
|
|
357
|
+
...(windowMode !== undefined ? { windowMode } : {}),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function parseWindowMode(value) {
|
|
361
|
+
if (value === 'foreground' || value === 'background')
|
|
362
|
+
return value;
|
|
363
|
+
throw new ConfigError('--window must be one of: foreground, background.');
|
|
364
|
+
}
|
|
365
|
+
function parseBrowserLeaf(leaf, positionals, options) {
|
|
366
|
+
const contract = hostedBrowserCommandsByPath.get(leaf);
|
|
367
|
+
if (!contract || !contract.action) {
|
|
368
|
+
if (leaf === 'bind' || contract?.sessionPolicy === 'local-only') {
|
|
369
|
+
throw new ConfigError('Browser bind is not supported in hosted mode.', 'Use browser state or browser tabs to inspect the active hosted page.');
|
|
370
|
+
}
|
|
371
|
+
throw new ConfigError(`Hosted browser command is not supported yet: ${leaf}`);
|
|
372
|
+
}
|
|
373
|
+
const localPath = leaf === 'screenshot' ? positionals[0] : undefined;
|
|
374
|
+
const args = browserActionArgs(contract, positionals, options);
|
|
375
|
+
return {
|
|
376
|
+
commandName: leaf,
|
|
377
|
+
action: contract.action,
|
|
378
|
+
args,
|
|
379
|
+
...(localPath !== undefined ? { localPath } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function browserActionArgs(contract, positionals, options) {
|
|
383
|
+
const args = compactRecord({ ...options });
|
|
384
|
+
let index = 0;
|
|
385
|
+
for (const positional of contract.positionals) {
|
|
386
|
+
if (positional.variadic) {
|
|
387
|
+
const rest = positionals.slice(index);
|
|
388
|
+
if (rest.length)
|
|
389
|
+
args[positional.name] = rest;
|
|
390
|
+
index = positionals.length;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const value = positionals[index];
|
|
394
|
+
if (value !== undefined)
|
|
395
|
+
args[positional.name] = value;
|
|
396
|
+
index += 1;
|
|
397
|
+
}
|
|
398
|
+
switch (contract.command) {
|
|
399
|
+
case 'screenshot':
|
|
400
|
+
delete args.path;
|
|
401
|
+
return args;
|
|
402
|
+
case 'tab/list':
|
|
403
|
+
return { ...args, op: 'list' };
|
|
404
|
+
case 'tab/new':
|
|
405
|
+
return {
|
|
406
|
+
...withoutKeys(args, ['url']),
|
|
407
|
+
op: 'new',
|
|
408
|
+
...(typeof args.url === 'string' && args.url ? { url: args.url } : {}),
|
|
409
|
+
};
|
|
410
|
+
case 'tab/select':
|
|
411
|
+
return {
|
|
412
|
+
...withoutKeys(args, ['targetId']),
|
|
413
|
+
op: 'select',
|
|
414
|
+
...(typeof args.targetId === 'string' && args.targetId ? { target: args.targetId } : {}),
|
|
415
|
+
};
|
|
416
|
+
case 'tab/close':
|
|
417
|
+
return {
|
|
418
|
+
...withoutKeys(args, ['targetId']),
|
|
419
|
+
op: 'close',
|
|
420
|
+
...(typeof args.targetId === 'string' && args.targetId ? { target: args.targetId } : {}),
|
|
421
|
+
};
|
|
422
|
+
case 'type':
|
|
423
|
+
return rewriteTextTargetArgs(args, options, 'targetOrText', 'text');
|
|
424
|
+
case 'fill':
|
|
425
|
+
return rewriteTextTargetArgs(args, options, 'targetOrText', 'text');
|
|
426
|
+
case 'select':
|
|
427
|
+
return rewriteTextTargetArgs(args, options, 'targetOrOption', 'option');
|
|
428
|
+
case 'upload':
|
|
429
|
+
return rewriteUploadArgs(args, options);
|
|
430
|
+
default:
|
|
431
|
+
return args;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function rewriteTextTargetArgs(args, options, firstPositionalName, valueName) {
|
|
435
|
+
const first = args[firstPositionalName];
|
|
436
|
+
const value = args[valueName];
|
|
437
|
+
const next = withoutKeys(args, [firstPositionalName, valueName]);
|
|
438
|
+
if (hasSemanticLocator(options)) {
|
|
439
|
+
return {
|
|
440
|
+
...next,
|
|
441
|
+
...(typeof first === 'string' ? { [valueName]: first } : {}),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
...next,
|
|
446
|
+
...(typeof first === 'string' ? { target: first } : {}),
|
|
447
|
+
...(typeof value === 'string' ? { [valueName]: value } : {}),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function rewriteUploadArgs(args, options) {
|
|
451
|
+
const targetOrFile = args.targetOrFile;
|
|
452
|
+
const files = Array.isArray(args.files) ? args.files.filter((entry) => typeof entry === 'string') : [];
|
|
453
|
+
const next = withoutKeys(args, ['targetOrFile', 'files']);
|
|
454
|
+
if (hasSemanticLocator(options)) {
|
|
455
|
+
return {
|
|
456
|
+
...next,
|
|
457
|
+
files: [
|
|
458
|
+
...(typeof targetOrFile === 'string' ? [targetOrFile] : []),
|
|
459
|
+
...files,
|
|
460
|
+
],
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
...next,
|
|
465
|
+
selector: typeof targetOrFile === 'string' ? targetOrFile : 'input[type="file"]',
|
|
466
|
+
files,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function hasSemanticLocator(args) {
|
|
470
|
+
return ['role', 'name', 'label', 'text', 'testid'].some(key => args[key] !== undefined);
|
|
471
|
+
}
|
|
472
|
+
function compactRecord(input) {
|
|
473
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
474
|
+
}
|
|
475
|
+
function withoutKeys(input, keys) {
|
|
476
|
+
const blocked = new Set(keys);
|
|
477
|
+
return Object.fromEntries(Object.entries(input).filter(([key, value]) => !blocked.has(key) && value !== undefined));
|
|
478
|
+
}
|
|
479
|
+
async function renderHostedBrowserResponse(stdout, invocation, response) {
|
|
480
|
+
const result = response.result;
|
|
481
|
+
if (invocation.action === 'snapshot' && result && typeof result === 'object') {
|
|
482
|
+
const record = result;
|
|
483
|
+
await writeToStream(stdout, `URL: ${typeof record.url === 'string' ? record.url : ''}\n\n`);
|
|
484
|
+
await writeToStream(stdout, `${typeof record.snapshot === 'string' ? record.snapshot : JSON.stringify(record.snapshot, null, 2)}\n`);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (invocation.action === 'screenshot' && result && typeof result === 'object') {
|
|
488
|
+
const base64 = result.base64;
|
|
489
|
+
if (typeof base64 === 'string' && invocation.localPath) {
|
|
490
|
+
writeFileSync(invocation.localPath, Buffer.from(base64, 'base64'));
|
|
491
|
+
await writeToStream(stdout, `Screenshot saved to: ${invocation.localPath}\n`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (typeof base64 === 'string') {
|
|
495
|
+
await writeToStream(stdout, `${base64}\n`);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (typeof result === 'string') {
|
|
500
|
+
await writeToStream(stdout, `${result}\n`);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
await writeToStream(stdout, `${JSON.stringify(result, null, 2)}\n`);
|
|
504
|
+
}
|
|
505
|
+
async function renderHostedList(manifest, fmt, explicit, stdout) {
|
|
506
|
+
const presentation = hostedListPresentation(manifest, fmt);
|
|
507
|
+
if (presentation.displayLines) {
|
|
508
|
+
for (const line of presentation.displayLines)
|
|
509
|
+
await writeToStream(stdout, `${line}\n`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
await renderOutput(presentation.rows, {
|
|
513
|
+
fmt,
|
|
514
|
+
fmtExplicit: explicit,
|
|
515
|
+
columns: presentation.columns,
|
|
516
|
+
stdout,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
async function writeHostedHelp(stdout, argv, data, text) {
|
|
520
|
+
const format = getRequestedHelpFormat(argv);
|
|
521
|
+
await writeToStream(stdout, format ? renderStructuredHelp(data, format) : text);
|
|
522
|
+
}
|
|
523
|
+
function parseHostedListSurface(argv, literal) {
|
|
524
|
+
let stdout = '';
|
|
525
|
+
let stderr = '';
|
|
526
|
+
let parsedFormat = 'table';
|
|
527
|
+
let formatExplicit = false;
|
|
528
|
+
let actionRan = false;
|
|
529
|
+
const root = new Command('webcmd');
|
|
530
|
+
const list = configureListCommandSurface(root.command('list'));
|
|
531
|
+
const output = {
|
|
532
|
+
writeOut: (value) => { stdout += value; },
|
|
533
|
+
writeErr: (value) => { stderr += value; },
|
|
534
|
+
};
|
|
535
|
+
root.exitOverride().configureOutput(output);
|
|
536
|
+
list.exitOverride().configureOutput(output).action((options) => {
|
|
537
|
+
actionRan = true;
|
|
538
|
+
parsedFormat = options.format;
|
|
539
|
+
formatExplicit = list.getOptionValueSource('format') === 'cli';
|
|
540
|
+
});
|
|
541
|
+
try {
|
|
542
|
+
root.parse(literal ? ['--', 'list', ...argv] : ['list', ...argv], { from: 'user' });
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
if (!(error instanceof CommanderError))
|
|
546
|
+
throw error;
|
|
547
|
+
if (error.code === 'commander.helpDisplayed')
|
|
548
|
+
return { kind: 'help', output: stdout };
|
|
549
|
+
throw new CommanderStructuralError(stderr || `${error.message}\n`, error.exitCode);
|
|
550
|
+
}
|
|
551
|
+
if (!actionRan)
|
|
552
|
+
throw new CommanderStructuralError("error: command 'list' did not run\n", 1);
|
|
553
|
+
return { kind: 'run', format: parsedFormat, formatExplicit };
|
|
554
|
+
}
|
|
555
|
+
function parseHostedCompletionSurface(argv, literal) {
|
|
556
|
+
let stdout = '';
|
|
557
|
+
let stderr = '';
|
|
558
|
+
let shell;
|
|
559
|
+
const root = new Command('webcmd');
|
|
560
|
+
const completion = configureCompletionCommandSurface(root.command('completion'));
|
|
561
|
+
const output = {
|
|
562
|
+
writeOut: (value) => { stdout += value; },
|
|
563
|
+
writeErr: (value) => { stderr += value; },
|
|
564
|
+
};
|
|
565
|
+
root.exitOverride().configureOutput(output);
|
|
566
|
+
completion.exitOverride().configureOutput(output).action((value) => {
|
|
567
|
+
shell = value;
|
|
568
|
+
});
|
|
569
|
+
try {
|
|
570
|
+
root.parse(literal ? ['--', 'completion', ...argv] : ['completion', ...argv], { from: 'user' });
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (!(error instanceof CommanderError))
|
|
574
|
+
throw error;
|
|
575
|
+
if (error.code === 'commander.helpDisplayed')
|
|
576
|
+
return { kind: 'help', output: stdout };
|
|
577
|
+
throw new CommanderStructuralError(stderr || `${error.message}\n`, error.exitCode);
|
|
578
|
+
}
|
|
579
|
+
if (shell === undefined) {
|
|
580
|
+
throw new CommanderStructuralError("error: missing required argument 'shell'\n", 1);
|
|
581
|
+
}
|
|
582
|
+
return { kind: 'run', shell };
|
|
583
|
+
}
|
|
584
|
+
function parseUnknownSiteRootOptions(argv, literal) {
|
|
585
|
+
if (literal)
|
|
586
|
+
return { help: false, version: false };
|
|
587
|
+
let profile;
|
|
588
|
+
let help = false;
|
|
589
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
590
|
+
const token = argv[i];
|
|
591
|
+
if (token === '--')
|
|
592
|
+
break;
|
|
593
|
+
if (token === '--profile') {
|
|
594
|
+
const value = argv[i + 1];
|
|
595
|
+
if (value === undefined) {
|
|
596
|
+
throw new CommanderStructuralError("error: option '--profile <name>' argument missing\n", 1);
|
|
597
|
+
}
|
|
598
|
+
profile = value;
|
|
599
|
+
i += 1;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (token.startsWith('--profile=')) {
|
|
603
|
+
profile = token.slice('--profile='.length);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (token === '--version' || token.startsWith('-V')) {
|
|
607
|
+
return { help: false, version: true, ...(profile !== undefined ? { profile } : {}) };
|
|
608
|
+
}
|
|
609
|
+
if (token === '--help' || token === '-h')
|
|
610
|
+
help = true;
|
|
611
|
+
}
|
|
612
|
+
return { help, version: false, ...(profile !== undefined ? { profile } : {}) };
|
|
613
|
+
}
|
|
614
|
+
function hasTerminalBeforeSeparator(argv, predicate) {
|
|
615
|
+
for (const token of argv) {
|
|
616
|
+
if (token === '--')
|
|
617
|
+
return false;
|
|
618
|
+
if (predicate(token))
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
function hostedCompletions(manifest, argv) {
|
|
624
|
+
const index = argv.indexOf('--get-completions');
|
|
625
|
+
const rest = index === -1 ? argv : argv.slice(index + 1);
|
|
626
|
+
const words = [];
|
|
627
|
+
let cursor;
|
|
628
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
629
|
+
if (rest[i] === '--cursor' && i + 1 < rest.length) {
|
|
630
|
+
cursor = Number.parseInt(rest[++i], 10);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
words.push(rest[i]);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return getCommandCompletionCandidates(hostedCommands(manifest), words, Number.isFinite(cursor) ? cursor : words.length, HOSTED_BUILTIN_COMMANDS);
|
|
637
|
+
}
|
|
638
|
+
function errorExitCode(err) {
|
|
639
|
+
if (err instanceof ConfigError)
|
|
640
|
+
return err.exitCode;
|
|
641
|
+
if (err && typeof err === 'object' && 'exitCode' in err && typeof err.exitCode === 'number') {
|
|
642
|
+
return err.exitCode;
|
|
643
|
+
}
|
|
644
|
+
return EXIT_CODES.GENERIC_ERROR;
|
|
645
|
+
}
|
|
646
|
+
function validateManifestContractIdentity(manifest) {
|
|
647
|
+
const installed = readInstalledHostedContractIdentity();
|
|
648
|
+
const installedLine = hostedContractCompatibilityLine(installed.webcmdVersion);
|
|
649
|
+
const manifestLine = hostedContractCompatibilityLine(manifest.metadata.webcmdPackageVersion);
|
|
650
|
+
if (manifest.metadata.contractSchemaVersion !== installed.schemaVersion
|
|
651
|
+
|| !installedLine
|
|
652
|
+
|| !manifestLine
|
|
653
|
+
|| manifestLine !== installedLine) {
|
|
654
|
+
throw new HostedClientError('HOSTED_PROTOCOL', 'Webcmd Cloud manifest does not match this installed Webcmd hosted contract.');
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function hostedContractCompatibilityLine(version) {
|
|
658
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
|
|
659
|
+
if (!match)
|
|
660
|
+
return undefined;
|
|
661
|
+
return `${match[1]}.${match[2]}.0`;
|
|
662
|
+
}
|
|
663
|
+
function readInstalledHostedContractIdentity() {
|
|
664
|
+
try {
|
|
665
|
+
const moduleFile = fileURLToPath(import.meta.url);
|
|
666
|
+
const packageRoot = findPackageRoot(moduleFile);
|
|
667
|
+
const value = JSON.parse(readFileSync(path.join(packageRoot, 'hosted-contract.json'), 'utf-8'));
|
|
668
|
+
if (!value
|
|
669
|
+
|| typeof value !== 'object'
|
|
670
|
+
|| typeof value.schemaVersion !== 'number'
|
|
671
|
+
|| typeof value.webcmdVersion !== 'string') {
|
|
672
|
+
throw new Error('invalid hosted contract identity');
|
|
673
|
+
}
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
throw new HostedClientError('HOSTED_PROTOCOL', 'The installed Webcmd hosted contract could not be validated.');
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function hostedCommandName(argv) {
|
|
681
|
+
const positionals = [];
|
|
682
|
+
const valueOptions = new Set(['--profile', '-f', '--format', '--trace']);
|
|
683
|
+
for (let i = 0; i < argv.length && positionals.length < 2; i += 1) {
|
|
684
|
+
const token = argv[i];
|
|
685
|
+
if (valueOptions.has(token)) {
|
|
686
|
+
i += 1;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (token.startsWith('-'))
|
|
690
|
+
continue;
|
|
691
|
+
positionals.push(token);
|
|
692
|
+
}
|
|
693
|
+
if (positionals.length < 2)
|
|
694
|
+
return positionals[0];
|
|
695
|
+
return `${positionals[0]}/${positionals[1]}`;
|
|
696
|
+
}
|
|
697
|
+
function hostedTraceMode(argv) {
|
|
698
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
699
|
+
if (argv[i] === '--trace')
|
|
700
|
+
return argv[i + 1];
|
|
701
|
+
if (argv[i]?.startsWith('--trace='))
|
|
702
|
+
return argv[i].slice('--trace='.length);
|
|
703
|
+
}
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|