@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
@@ -0,0 +1,10 @@
1
+ import { type ConfigIo } from './config.js';
2
+ import { type HostedCredentialIo } from './credentials.js';
3
+ export interface SetupIo extends ConfigIo, HostedCredentialIo {
4
+ input?: NodeJS.ReadableStream;
5
+ output?: NodeJS.WritableStream;
6
+ fetchImpl?: typeof fetch;
7
+ question?: (prompt: string) => Promise<string>;
8
+ write?: (message: string) => void | Promise<void>;
9
+ }
10
+ export declare function runHostedSetup(io?: SetupIo): Promise<number>;
@@ -0,0 +1,79 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { stdin as defaultInput, stdout as defaultOutput } from 'node:process';
3
+ import { writeToStream } from '../stream-write.js';
4
+ import { HostedClient } from './client.js';
5
+ import { defaultHostedApiBaseUrl, makeLocalConfig, saveWebcmdConfig, } from './config.js';
6
+ import { makeStoredHostedConfig, storeHostedApiKey, } from './credentials.js';
7
+ export async function runHostedSetup(io = {}) {
8
+ const write = io.write
9
+ ? async (message) => { await io.write(message); }
10
+ : async (message) => writeToStream(io.output ?? defaultOutput, message);
11
+ const ownedReadline = io.question ? undefined : createInterface({
12
+ input: io.input ?? defaultInput,
13
+ output: io.output ?? defaultOutput,
14
+ });
15
+ const ask = io.question ?? ((prompt) => ownedReadline.question(prompt));
16
+ try {
17
+ await write('Webcmd setup\n');
18
+ const mode = await ask('Use hosted Webcmd Cloud or local Webcmd? [hosted/local] ');
19
+ if (mode.trim().toLowerCase().startsWith('l')) {
20
+ saveWebcmdConfig(makeLocalConfig(io.now?.() ?? new Date()), io);
21
+ await write('Webcmd is now configured for local mode.\n');
22
+ return 0;
23
+ }
24
+ const apiBaseUrl = defaultHostedApiBaseUrl(io.env ?? process.env);
25
+ const apiKey = (await ask('Webcmd API key: ')).trim();
26
+ if (!apiKey) {
27
+ await write('A Webcmd API key is required for hosted mode.\n');
28
+ return 2;
29
+ }
30
+ let accountLabel;
31
+ try {
32
+ const me = await new HostedClient({
33
+ apiBaseUrl,
34
+ apiKey,
35
+ fetchImpl: io.fetchImpl,
36
+ }).getMe();
37
+ accountLabel = hostedAccountLabel(me);
38
+ }
39
+ catch (err) {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ await write(`Warning: could not verify API key yet: ${message}\n`);
42
+ }
43
+ const credential = await storeHostedApiKey(apiKey, io);
44
+ const config = makeStoredHostedConfig({
45
+ apiBaseUrl,
46
+ apiKeyRef: credential.apiKeyRef,
47
+ credentialBackend: credential.credentialBackend,
48
+ now: io.now?.() ?? new Date(),
49
+ });
50
+ saveWebcmdConfig(config, io);
51
+ if (accountLabel)
52
+ await write(`Verified Webcmd Cloud account: ${accountLabel}\n`);
53
+ if (credential.credentialBackend === 'file-fallback') {
54
+ await write('Warning: OS credential storage was unavailable; API key stored in a protected Webcmd credentials file.\n');
55
+ }
56
+ await write(`Credential backend: ${credentialBackendLabel(credential.credentialBackend)}.\n`);
57
+ await write('Webcmd is now configured for hosted mode.\n');
58
+ return 0;
59
+ }
60
+ finally {
61
+ ownedReadline?.close();
62
+ }
63
+ }
64
+ function hostedAccountLabel(body) {
65
+ if (!body || typeof body !== 'object' || Array.isArray(body))
66
+ return undefined;
67
+ const user = body.user;
68
+ if (!user || typeof user !== 'object' || Array.isArray(user))
69
+ return undefined;
70
+ const record = user;
71
+ if (typeof record.email === 'string' && record.email.trim())
72
+ return record.email.trim();
73
+ if (typeof record.id === 'string' && record.id.trim())
74
+ return record.id.trim();
75
+ return undefined;
76
+ }
77
+ function credentialBackendLabel(backend) {
78
+ return backend === 'os' ? 'OS credential store' : 'protected file fallback';
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,195 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import path, { join } from 'node:path';
5
+ import { Writable } from 'node:stream';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { afterEach, describe, expect, it, vi } from 'vitest';
8
+ import { getConfigPath } from './config.js';
9
+ import { getHostedCredentialPath } from './credentials.js';
10
+ import { runHostedSetup } from './setup.js';
11
+ let tempDir;
12
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
13
+ afterEach(async () => {
14
+ if (tempDir)
15
+ await rm(tempDir, { recursive: true, force: true });
16
+ tempDir = undefined;
17
+ });
18
+ describe('webcmd setup', () => {
19
+ it('writes local mode from interactive answer', async () => {
20
+ tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-'));
21
+ const answers = ['local'];
22
+ const messages = [];
23
+ const env = { WEBCMD_CONFIG_DIR: tempDir };
24
+ const code = await runHostedSetup({
25
+ env,
26
+ platform: 'linux',
27
+ now: () => new Date('2026-07-08T00:00:00.000Z'),
28
+ question: async () => answers.shift() ?? '',
29
+ write: (message) => { messages.push(message); },
30
+ });
31
+ expect(code).toBe(0);
32
+ expect(JSON.parse(await readFile(getConfigPath({ env }), 'utf8'))).toEqual({
33
+ mode: 'local',
34
+ updatedAt: '2026-07-08T00:00:00.000Z',
35
+ });
36
+ expect(messages.join('')).toContain('local mode');
37
+ });
38
+ it('writes hosted mode and validates with /v1/me', async () => {
39
+ tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-'));
40
+ const answers = ['hosted', 'wcmd_live_test'];
41
+ const env = {
42
+ WEBCMD_CONFIG_DIR: tempDir,
43
+ WEBCMD_CREDENTIAL_BACKEND: 'file',
44
+ };
45
+ const requests = [];
46
+ const prompts = [];
47
+ const messages = [];
48
+ const code = await runHostedSetup({
49
+ env,
50
+ platform: 'linux',
51
+ now: () => new Date('2026-07-08T00:00:00.000Z'),
52
+ question: async (prompt) => {
53
+ prompts.push(prompt);
54
+ return answers.shift() ?? '';
55
+ },
56
+ fetchImpl: async (url, init) => {
57
+ requests.push({
58
+ url: String(url),
59
+ authorization: new Headers(init?.headers).get('authorization'),
60
+ });
61
+ return new Response(JSON.stringify({ ok: true, user: { id: 'user_demo' } }), { status: 200 });
62
+ },
63
+ write: (message) => { messages.push(message); },
64
+ });
65
+ expect(code).toBe(0);
66
+ expect(prompts).toEqual([
67
+ 'Use hosted Webcmd Cloud or local Webcmd? [hosted/local] ',
68
+ 'Webcmd API key: ',
69
+ ]);
70
+ expect(requests).toEqual([{ url: 'https://api.webcmd.dev/v1/me', authorization: 'Bearer wcmd_live_test' }]);
71
+ expect(JSON.parse(await readFile(getConfigPath({ env }), 'utf8'))).toMatchObject({
72
+ mode: 'hosted',
73
+ hosted: {
74
+ apiBaseUrl: 'https://api.webcmd.dev',
75
+ apiKeyRef: expect.stringMatching(/^wcmd_cred_/),
76
+ credentialBackend: 'file-fallback',
77
+ },
78
+ });
79
+ expect(await readFile(getConfigPath({ env }), 'utf8')).not.toContain('wcmd_live_test');
80
+ expect(await readFile(getHostedCredentialPath({ env }), 'utf8')).toContain('wcmd_live_test');
81
+ expect(messages.join('')).toContain('Verified Webcmd Cloud account: user_demo');
82
+ expect(messages.join('')).toContain('Credential backend: protected file fallback.');
83
+ });
84
+ it('persists interactive local setup before the real CLI process completes', async () => {
85
+ tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-process-'));
86
+ const child = spawn(process.execPath, ['--import', 'tsx', 'src/main.ts', 'setup'], {
87
+ cwd: packageRoot,
88
+ env: { ...process.env, WEBCMD_CONFIG_DIR: tempDir, WEBCMD_NO_UPDATE_CHECK: '1' },
89
+ stdio: ['pipe', 'pipe', 'pipe'],
90
+ });
91
+ const stdout = [];
92
+ const stderr = [];
93
+ child.stdout.on('data', chunk => stdout.push(Buffer.from(chunk)));
94
+ child.stderr.on('data', chunk => stderr.push(Buffer.from(chunk)));
95
+ child.stdin.end('local\n');
96
+ const status = await new Promise((resolve, reject) => {
97
+ child.once('error', reject);
98
+ child.once('close', resolve);
99
+ });
100
+ expect(status).toBe(0);
101
+ expect(Buffer.concat(stderr).toString('utf8')).toBe('');
102
+ expect(Buffer.concat(stdout).toString('utf8')).toContain('Webcmd is now configured for local mode.');
103
+ expect(JSON.parse(await readFile(getConfigPath({ env: { WEBCMD_CONFIG_DIR: tempDir } }), 'utf8')))
104
+ .toMatchObject({ mode: 'local' });
105
+ }, 20_000);
106
+ it('does not resolve until all caller-owned output writes complete', async () => {
107
+ tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-slow-output-'));
108
+ const output = new SetupControlledWritable();
109
+ let settled = false;
110
+ const run = runHostedSetup({
111
+ env: { WEBCMD_CONFIG_DIR: tempDir },
112
+ output,
113
+ question: async () => 'local',
114
+ }).then(code => {
115
+ settled = true;
116
+ return code;
117
+ });
118
+ await Promise.resolve();
119
+ expect(settled).toBe(false);
120
+ expect(output.pendingCount()).toBe(1);
121
+ output.release();
122
+ await new Promise(resolve => setImmediate(resolve));
123
+ expect(settled).toBe(false);
124
+ expect(output.pendingCount()).toBe(1);
125
+ output.release();
126
+ await expect(run).resolves.toBe(0);
127
+ expect(output.text()).toBe('Webcmd setup\nWebcmd is now configured for local mode.\n');
128
+ });
129
+ it('rejects caller-owned stream errors without ending the stream', async () => {
130
+ tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-output-error-'));
131
+ const output = new Writable({
132
+ write(_chunk, _encoding, callback) {
133
+ callback(new Error('setup output failed'));
134
+ },
135
+ });
136
+ const end = vi.spyOn(output, 'end');
137
+ await expect(runHostedSetup({
138
+ env: { WEBCMD_CONFIG_DIR: tempDir },
139
+ output,
140
+ question: async () => 'local',
141
+ })).rejects.toThrow('setup output failed');
142
+ expect(end).not.toHaveBeenCalled();
143
+ });
144
+ it('rejects within a bound when caller-owned output closes before its callback', async () => {
145
+ tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-output-close-'));
146
+ const output = new SetupCloseBeforeCallbackWritable();
147
+ const end = vi.spyOn(output, 'end');
148
+ await expect(within(runHostedSetup({
149
+ env: { WEBCMD_CONFIG_DIR: tempDir },
150
+ output,
151
+ question: async () => 'local',
152
+ }))).rejects.toThrow('closed before the write completed');
153
+ expect(end).not.toHaveBeenCalled();
154
+ });
155
+ });
156
+ async function within(promise, milliseconds = 500) {
157
+ let timer;
158
+ try {
159
+ return await Promise.race([
160
+ promise,
161
+ new Promise((_resolve, reject) => {
162
+ timer = setTimeout(() => reject(new Error(`promise did not settle within ${milliseconds}ms`)), milliseconds);
163
+ }),
164
+ ]);
165
+ }
166
+ finally {
167
+ if (timer)
168
+ clearTimeout(timer);
169
+ }
170
+ }
171
+ class SetupCloseBeforeCallbackWritable extends Writable {
172
+ _write(_chunk, _encoding, _callback) {
173
+ this.destroy();
174
+ }
175
+ }
176
+ class SetupControlledWritable extends Writable {
177
+ chunks = [];
178
+ releases = [];
179
+ _write(chunk, _encoding, callback) {
180
+ this.chunks.push(Buffer.from(chunk));
181
+ this.releases.push(callback);
182
+ }
183
+ pendingCount() {
184
+ return this.releases.length;
185
+ }
186
+ release() {
187
+ const callback = this.releases.shift();
188
+ if (!callback)
189
+ throw new Error('No setup write is pending');
190
+ callback();
191
+ }
192
+ text() {
193
+ return Buffer.concat(this.chunks).toString('utf8');
194
+ }
195
+ }
@@ -0,0 +1,175 @@
1
+ import type { CommandSurfaceMetadata } from '../command-surface.js';
2
+ import type { Arg } from '../registry.js';
3
+ export type HostedCommandStrategy = 'PUBLIC' | 'COOKIE' | 'INTERCEPT' | 'UI' | 'LOCAL' | string;
4
+ export interface HostedCommandArg extends Arg {
5
+ }
6
+ export interface HostedFileArgument {
7
+ name: string;
8
+ direction: 'input' | 'output';
9
+ pathKind: 'file' | 'directory';
10
+ multiple: boolean;
11
+ required: boolean;
12
+ separator?: ',';
13
+ contentTypes?: string[];
14
+ contentType?: string;
15
+ maxBytes?: number;
16
+ defaultPath?: string;
17
+ }
18
+ export interface HostedCommand extends CommandSurfaceMetadata {
19
+ site: string;
20
+ name: string;
21
+ aliases?: string[];
22
+ command: string;
23
+ description: string;
24
+ access: 'read' | 'write' | string;
25
+ strategy: HostedCommandStrategy;
26
+ browser: boolean;
27
+ args: HostedCommandArg[];
28
+ columns: string[];
29
+ domain?: string | null;
30
+ defaultFormat?: string | null;
31
+ }
32
+ export interface HostedManifest {
33
+ userId: string;
34
+ metadata: {
35
+ contractSchemaVersion: number;
36
+ webcmdPackageVersion: string;
37
+ generatedAt: string;
38
+ };
39
+ commands: HostedCommand[];
40
+ }
41
+ export interface HostedPublicProfile {
42
+ name: string;
43
+ default: boolean;
44
+ status: 'available';
45
+ createdAt: string;
46
+ lastUsedAt: string;
47
+ }
48
+ export interface HostedProfilesResponse {
49
+ ok: true;
50
+ profiles: HostedPublicProfile[];
51
+ }
52
+ export interface HostedExecution {
53
+ id: string;
54
+ command: string;
55
+ status: 'succeeded' | 'failed' | 'timed_out';
56
+ }
57
+ export interface HostedTraceReceipt {
58
+ receipt: string;
59
+ executionId: string;
60
+ artifactsUrl?: string;
61
+ liveViewUrl?: string;
62
+ replayUrl?: string;
63
+ }
64
+ export interface HostedExecuteResponse {
65
+ ok: true;
66
+ result: unknown;
67
+ columns?: string[];
68
+ footerExtra?: string;
69
+ execution: HostedExecution;
70
+ trace?: HostedTraceReceipt;
71
+ artifacts?: HostedArtifactReceipt[];
72
+ }
73
+ export interface HostedPreparedExecution {
74
+ id: string;
75
+ command: string;
76
+ status: 'queued';
77
+ }
78
+ export interface HostedPrepareExecutionResponse {
79
+ ok: true;
80
+ execution: HostedPreparedExecution;
81
+ fileArguments: HostedFileArgument[];
82
+ }
83
+ export interface HostedArtifactReceipt {
84
+ artifactId: string;
85
+ argument: string;
86
+ direction: 'input' | 'output';
87
+ pathKind: 'file' | 'directory';
88
+ filename: string;
89
+ contentType: string;
90
+ byteSize: number;
91
+ sha256?: string;
92
+ relativePath?: string;
93
+ expiresAt: string;
94
+ }
95
+ export interface HostedArtifactReference {
96
+ $webcmdArtifact: {
97
+ id?: string;
98
+ direction?: 'input' | 'output';
99
+ filename?: string;
100
+ contentType?: string;
101
+ };
102
+ }
103
+ export interface HostedUploadArtifactResponse {
104
+ ok: true;
105
+ artifact: HostedArtifactReceipt;
106
+ reference: HostedArtifactReference;
107
+ }
108
+ export type HostedBrowserActionName = 'analyze' | 'back' | 'check' | 'click' | 'close-window' | 'console' | 'dblclick' | 'dialog-accept' | 'dialog-dismiss' | 'drag' | 'exec' | 'extract' | 'fill' | 'find' | 'focus' | 'frames' | 'get-attributes' | 'get-html' | 'get-text' | 'get-title' | 'get-url' | 'get-value' | 'hover' | 'init' | 'insert-text' | 'navigate' | 'network' | 'press-key' | 'screenshot' | 'scroll' | 'select' | 'set-file-input' | 'snapshot' | 'tabs' | 'type' | 'uncheck' | 'verify' | 'wait';
109
+ export interface HostedBrowserRunRequest {
110
+ command: string;
111
+ args: Record<string, unknown>;
112
+ profile?: string;
113
+ windowMode?: 'foreground' | 'background';
114
+ trace?: string;
115
+ }
116
+ export interface HostedBrowserRunResponse {
117
+ ok: true;
118
+ run: {
119
+ executionId: string;
120
+ session: string;
121
+ profile: {
122
+ id: string;
123
+ displayName: string;
124
+ };
125
+ liveViewUrl?: string;
126
+ };
127
+ }
128
+ export interface HostedBrowserActionRequest {
129
+ action: HostedBrowserActionName;
130
+ args: Record<string, unknown>;
131
+ profile?: string;
132
+ }
133
+ export interface HostedBrowserActionResponse {
134
+ ok: true;
135
+ result: unknown;
136
+ columns: string[];
137
+ trace: HostedBrowserActionTrace | null;
138
+ }
139
+ export interface HostedBrowserActionTrace {
140
+ id: string;
141
+ receipt: string;
142
+ kind: string;
143
+ contentType?: string;
144
+ byteSize?: number;
145
+ storagePath?: string;
146
+ }
147
+ export interface HostedBrowserFinishRequest {
148
+ status: 'succeeded' | 'failed' | 'timed_out';
149
+ errorCode?: string;
150
+ profile?: string;
151
+ }
152
+ export interface HostedBrowserFinishResponse {
153
+ ok: true;
154
+ execution: {
155
+ id: string;
156
+ status: 'succeeded' | 'failed' | 'timed_out';
157
+ };
158
+ }
159
+ export interface HostedBrowserRunActionInput extends HostedBrowserRunRequest, HostedBrowserActionRequest {
160
+ }
161
+ export interface HostedBrowserRunActionResponse extends HostedBrowserActionResponse {
162
+ run: HostedBrowserRunResponse['run'];
163
+ execution: HostedBrowserFinishResponse['execution'];
164
+ }
165
+ export interface HostedErrorResponse {
166
+ ok: false;
167
+ error: {
168
+ code: string;
169
+ message: string;
170
+ help?: string;
171
+ exitCode?: number;
172
+ };
173
+ execution?: HostedExecution;
174
+ trace?: HostedTraceReceipt;
175
+ }
@@ -0,0 +1 @@
1
+ export {};