@agentrhq/webcmd 0.3.0 → 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 (181) hide show
  1. package/README.md +13 -2
  2. package/cli-manifest.json +217 -13
  3. package/clis/chatgpt/ask.js +1 -1
  4. package/clis/chatgpt/ask.test.js +11 -0
  5. package/clis/chatgpt/commands.test.js +102 -1
  6. package/clis/chatgpt/deep-research-result.js +45 -3
  7. package/clis/chatgpt/image.js +22 -2
  8. package/clis/chatgpt/project-file-add.js +24 -1
  9. package/clis/chatgpt/utils.js +258 -52
  10. package/clis/chatgpt/utils.test.js +259 -1
  11. package/clis/claude/ask.js +21 -1
  12. package/clis/facebook/feed.js +110 -24
  13. package/clis/facebook/feed.test.js +62 -0
  14. package/clis/facebook/search.js +185 -38
  15. package/clis/facebook/search.test.js +95 -50
  16. package/clis/instagram/explore.js +30 -12
  17. package/clis/instagram/explore.test.js +77 -0
  18. package/clis/instagram/post.js +14 -1
  19. package/clis/instagram/reel.js +13 -1
  20. package/clis/linkedin/company.js +153 -0
  21. package/clis/linkedin/company.test.js +111 -0
  22. package/clis/linkedin/connections.js +135 -0
  23. package/clis/linkedin/connections.test.js +141 -0
  24. package/clis/mercury/reimbursement-draft.js +12 -1
  25. package/clis/twitter/article-evaluate.test.js +27 -0
  26. package/clis/twitter/article.js +73 -16
  27. package/clis/twitter/article.test.js +209 -0
  28. package/clis/twitter/download.js +6 -1
  29. package/clis/twitter/post.js +14 -1
  30. package/clis/twitter/profile.js +1 -1
  31. package/clis/twitter/profile.test.js +8 -0
  32. package/clis/twitter/quote.js +11 -1
  33. package/clis/twitter/reply.js +11 -1
  34. package/dist/src/browser/base-page.d.ts +26 -0
  35. package/dist/src/browser/base-page.js +23 -8
  36. package/dist/src/browser/base-page.test.js +45 -12
  37. package/dist/src/browser/command-catalog.d.ts +6 -0
  38. package/dist/src/browser/command-catalog.js +259 -0
  39. package/dist/src/browser/command-catalog.test.d.ts +1 -0
  40. package/dist/src/browser/command-catalog.test.js +79 -0
  41. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.d.ts +17 -0
  42. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.js +106 -0
  43. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.d.ts +1 -0
  44. package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.js +65 -0
  45. package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +7 -0
  46. package/dist/src/browser/runtime/local-cloak/session-manager.js +23 -9
  47. package/dist/src/browser/runtime/local-cloak/session-manager.test.js +36 -0
  48. package/dist/src/browser/target-resolver.d.ts +1 -0
  49. package/dist/src/browser/target-resolver.js +99 -4
  50. package/dist/src/browser/target-resolver.test.js +141 -1
  51. package/dist/src/build-manifest.d.ts +6 -0
  52. package/dist/src/build-manifest.js +18 -1
  53. package/dist/src/build-manifest.test.js +57 -1
  54. package/dist/src/builtin-command-surface.d.ts +9 -0
  55. package/dist/src/builtin-command-surface.js +16 -0
  56. package/dist/src/check-hosted-contract.test.d.ts +1 -0
  57. package/dist/src/check-hosted-contract.test.js +56 -0
  58. package/dist/src/cli.js +58 -106
  59. package/dist/src/cli.test.js +71 -1
  60. package/dist/src/command-presentation.d.ts +95 -0
  61. package/dist/src/command-presentation.js +486 -0
  62. package/dist/src/command-presentation.test.d.ts +1 -0
  63. package/dist/src/command-presentation.test.js +97 -0
  64. package/dist/src/command-surface.d.ts +43 -0
  65. package/dist/src/command-surface.js +205 -0
  66. package/dist/src/command-surface.test.d.ts +1 -0
  67. package/dist/src/command-surface.test.js +406 -0
  68. package/dist/src/commanderAdapter.d.ts +5 -1
  69. package/dist/src/commanderAdapter.js +12 -55
  70. package/dist/src/commanderAdapter.test.js +15 -3
  71. package/dist/src/community-plugin-sync.d.ts +11 -0
  72. package/dist/src/community-plugin-sync.js +138 -0
  73. package/dist/src/community-plugin-sync.test.d.ts +1 -0
  74. package/dist/src/community-plugin-sync.test.js +123 -0
  75. package/dist/src/completion-fast.d.ts +4 -4
  76. package/dist/src/completion-fast.js +15 -34
  77. package/dist/src/completion-shared.d.ts +4 -0
  78. package/dist/src/completion-shared.js +38 -0
  79. package/dist/src/completion.js +3 -27
  80. package/dist/src/completion.test.js +31 -2
  81. package/dist/src/docs-sync-review-cli.test.d.ts +1 -0
  82. package/dist/src/docs-sync-review-cli.test.js +322 -0
  83. package/dist/src/docs-sync-review.d.ts +117 -0
  84. package/dist/src/docs-sync-review.js +475 -0
  85. package/dist/src/docs-sync-review.test.d.ts +1 -0
  86. package/dist/src/docs-sync-review.test.js +324 -0
  87. package/dist/src/errors.d.ts +12 -3
  88. package/dist/src/errors.js +11 -7
  89. package/dist/src/errors.test.js +14 -1
  90. package/dist/src/execution.d.ts +1 -2
  91. package/dist/src/execution.js +4 -45
  92. package/dist/src/generate-release-notes-cli.test.js +7 -2
  93. package/dist/src/help.d.ts +4 -0
  94. package/dist/src/help.js +50 -255
  95. package/dist/src/help.test.js +27 -1
  96. package/dist/src/hosted/args.d.ts +2 -7
  97. package/dist/src/hosted/args.js +2 -99
  98. package/dist/src/hosted/args.test.js +15 -1
  99. package/dist/src/hosted/availability.d.ts +13 -0
  100. package/dist/src/hosted/availability.js +16 -0
  101. package/dist/src/hosted/availability.test.d.ts +1 -0
  102. package/dist/src/hosted/availability.test.js +180 -0
  103. package/dist/src/hosted/browser-args.d.ts +18 -0
  104. package/dist/src/hosted/browser-args.js +152 -0
  105. package/dist/src/hosted/browser-args.test.d.ts +1 -0
  106. package/dist/src/hosted/browser-args.test.js +182 -0
  107. package/dist/src/hosted/client.d.ts +31 -2
  108. package/dist/src/hosted/client.js +441 -53
  109. package/dist/src/hosted/client.test.js +651 -32
  110. package/dist/src/hosted/config.d.ts +7 -2
  111. package/dist/src/hosted/config.js +26 -4
  112. package/dist/src/hosted/config.test.js +34 -1
  113. package/dist/src/hosted/contract.d.ts +94 -0
  114. package/dist/src/hosted/contract.js +208 -0
  115. package/dist/src/hosted/contract.test.d.ts +1 -0
  116. package/dist/src/hosted/contract.test.js +361 -0
  117. package/dist/src/hosted/credentials.d.ts +38 -0
  118. package/dist/src/hosted/credentials.js +248 -0
  119. package/dist/src/hosted/credentials.test.d.ts +1 -0
  120. package/dist/src/hosted/credentials.test.js +93 -0
  121. package/dist/src/hosted/file-contract.test.d.ts +1 -0
  122. package/dist/src/hosted/file-contract.test.js +174 -0
  123. package/dist/src/hosted/files.d.ts +29 -0
  124. package/dist/src/hosted/files.js +296 -0
  125. package/dist/src/hosted/files.test.d.ts +1 -0
  126. package/dist/src/hosted/files.test.js +231 -0
  127. package/dist/src/hosted/main-lifecycle.test.d.ts +1 -0
  128. package/dist/src/hosted/main-lifecycle.test.js +213 -0
  129. package/dist/src/hosted/manifest.d.ts +5 -0
  130. package/dist/src/hosted/manifest.js +21 -66
  131. package/dist/src/hosted/manifest.test.js +120 -2
  132. package/dist/src/hosted/output-parity.test.d.ts +1 -0
  133. package/dist/src/hosted/output-parity.test.js +108 -0
  134. package/dist/src/hosted/root-command-surface.test.d.ts +1 -0
  135. package/dist/src/hosted/root-command-surface.test.js +646 -0
  136. package/dist/src/hosted/runner.d.ts +7 -0
  137. package/dist/src/hosted/runner.js +554 -253
  138. package/dist/src/hosted/runner.test.js +1471 -53
  139. package/dist/src/hosted/setup.d.ts +3 -2
  140. package/dist/src/hosted/setup.js +45 -15
  141. package/dist/src/hosted/setup.test.js +133 -6
  142. package/dist/src/hosted/types.d.ts +101 -23
  143. package/dist/src/main.js +120 -108
  144. package/dist/src/manifest-types.d.ts +2 -0
  145. package/dist/src/output.d.ts +17 -2
  146. package/dist/src/output.js +88 -81
  147. package/dist/src/output.test.js +141 -51
  148. package/dist/src/pipeline/executor.test.js +1 -0
  149. package/dist/src/pipeline/steps/download.test.js +1 -0
  150. package/dist/src/plugin-create-cli.test.d.ts +1 -0
  151. package/dist/src/plugin-create-cli.test.js +37 -0
  152. package/dist/src/plugin-manifest.d.ts +12 -0
  153. package/dist/src/plugin-manifest.js +16 -0
  154. package/dist/src/plugin-manifest.test.js +19 -1
  155. package/dist/src/plugin-scaffold.d.ts +4 -1
  156. package/dist/src/plugin-scaffold.js +4 -1
  157. package/dist/src/plugin-scaffold.test.js +23 -8
  158. package/dist/src/plugin.js +4 -1
  159. package/dist/src/plugin.test.js +13 -0
  160. package/dist/src/registry.d.ts +2 -0
  161. package/dist/src/release-notes.js +12 -4
  162. package/dist/src/release-notes.test.js +27 -15
  163. package/dist/src/root-command-surface.d.ts +31 -0
  164. package/dist/src/root-command-surface.js +106 -0
  165. package/dist/src/serialization.d.ts +1 -16
  166. package/dist/src/serialization.js +5 -55
  167. package/dist/src/stream-write.d.ts +12 -0
  168. package/dist/src/stream-write.js +91 -0
  169. package/dist/src/stream-write.test.d.ts +1 -0
  170. package/dist/src/stream-write.test.js +186 -0
  171. package/dist/src/types.d.ts +5 -0
  172. package/dist/src/utils.d.ts +1 -1
  173. package/dist/src/utils.js +2 -8
  174. package/dist/src/utils.test.js +50 -0
  175. package/hosted-contract.json +36748 -0
  176. package/package.json +6 -1
  177. package/scripts/check-hosted-contract.mjs +108 -0
  178. package/scripts/docs-sync-review.ts +332 -0
  179. package/scripts/sync-community-plugins.ts +14 -0
  180. package/skills/webcmd-autofix/SKILL.md +26 -37
  181. package/skills/webcmd-usage/SKILL.md +7 -0
package/dist/src/main.js CHANGED
@@ -16,7 +16,7 @@ import * as fs from 'node:fs';
16
16
  import * as os from 'node:os';
17
17
  import * as path from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
- import { getCompletionsFromManifest, hasAllManifests, printCompletionScriptFast } from './completion-fast.js';
19
+ import { getCompletionScriptFast, getCompletionsFromManifest, hasAllManifests } from './completion-fast.js';
20
20
  import { findPackageRoot, getCliManifestPath } from './package-paths.js';
21
21
  import { PKG_VERSION } from './version.js';
22
22
  import { EXIT_CODES } from './errors.js';
@@ -42,44 +42,111 @@ if (typeof globalThis.Bun === 'undefined' && !isSupportedNodeVersion(process.ver
42
42
  }
43
43
  // Fast path: --version (only when it's the top-level intent, not passed to a subcommand)
44
44
  // e.g. `webcmd --version` or `webcmd -V`, but NOT `webcmd gh --version`
45
+ let fastPathHandled = false;
45
46
  if (argv[0] === '--version' || argv[0] === '-V') {
46
47
  process.stdout.write(PKG_VERSION + '\n');
47
48
  process.exit(EXIT_CODES.SUCCESS);
48
49
  }
49
50
  // Fast path: completion <shell> — print shell script without discovery
50
- if (argv[0] === 'completion' && argv.length >= 2) {
51
- if (printCompletionScriptFast(argv[1])) {
51
+ if (!fastPathHandled && argv[0] === 'completion' && argv.length >= 2) {
52
+ const script = getCompletionScriptFast(argv[1]);
53
+ if (script !== undefined) {
54
+ process.stdout.write(script);
52
55
  process.exit(EXIT_CODES.SUCCESS);
53
56
  }
54
57
  // Unknown shell — fall through to full path for proper error handling
55
58
  }
56
59
  // Hosted setup and hosted dispatch run before local adapter discovery. This is
57
60
  // the mode boundary: hosted mode must not read ~/.webcmd/clis or local site
58
- // memory just to decide what commands exist.
59
- if (argv[0] === 'setup') {
60
- const { runHostedSetup } = await import('./hosted/setup.js');
61
- process.exit(await runHostedSetup());
62
- }
63
- const { shouldUseHostedMode } = await import('./hosted/config.js');
64
- if (shouldUseHostedMode()) {
65
- const { runHostedCli } = await import('./hosted/runner.js');
66
- const result = await runHostedCli(argv);
67
- if (result.handled)
68
- process.exit(result.exitCode);
61
+ // memory just to decide what commands exist. Awaiting the selected branch and
62
+ // assigning exitCode lets Node flush pending stdout/stderr before shutdown.
63
+ if (!fastPathHandled) {
64
+ if (argv[0] === 'setup') {
65
+ const { runHostedSetup } = await import('./hosted/setup.js');
66
+ process.exitCode = await runHostedSetup();
67
+ }
68
+ else {
69
+ const { shouldUseHostedMode } = await import('./hosted/config.js');
70
+ if (shouldUseHostedMode()) {
71
+ const { runHostedCli } = await import('./hosted/runner.js');
72
+ const result = await runHostedCli(argv);
73
+ process.exitCode = result.exitCode;
74
+ }
75
+ else {
76
+ await runLocalMain();
77
+ }
78
+ }
69
79
  }
70
- // Fast path: --get-completions — read from manifest, skip discovery
71
- const getCompIdx = process.argv.indexOf('--get-completions');
72
- if (getCompIdx !== -1) {
73
- // Only include manifests that actually exist on disk.
74
- // With sparse override, the user clis dir may exist but have no manifest.
75
- const manifestPaths = [getCliManifestPath(BUILTIN_CLIS)];
76
- const userManifest = getCliManifestPath(USER_CLIS);
77
- try {
78
- fs.accessSync(userManifest);
79
- manifestPaths.push(userManifest);
80
+ async function runLocalMain() {
81
+ // Fast path: --get-completions — read from manifest, skip discovery
82
+ const getCompIdx = process.argv.indexOf('--get-completions');
83
+ if (getCompIdx !== -1) {
84
+ // Only include manifests that actually exist on disk.
85
+ // With sparse override, the user clis dir may exist but have no manifest.
86
+ const manifestPaths = [getCliManifestPath(BUILTIN_CLIS)];
87
+ const userManifest = getCliManifestPath(USER_CLIS);
88
+ try {
89
+ fs.accessSync(userManifest);
90
+ manifestPaths.push(userManifest);
91
+ }
92
+ catch { /* no user manifest */ }
93
+ if (hasAllManifests(manifestPaths)) {
94
+ const rest = process.argv.slice(getCompIdx + 1);
95
+ let cursor;
96
+ const words = [];
97
+ for (let i = 0; i < rest.length; i++) {
98
+ if (rest[i] === '--cursor' && i + 1 < rest.length) {
99
+ cursor = parseInt(rest[i + 1], 10);
100
+ i++;
101
+ }
102
+ else {
103
+ words.push(rest[i]);
104
+ }
105
+ }
106
+ if (cursor === undefined)
107
+ cursor = words.length;
108
+ const candidates = getCompletionsFromManifest(words, cursor, manifestPaths);
109
+ process.stdout.write(candidates.join('\n') + '\n');
110
+ process.exit(EXIT_CODES.SUCCESS);
111
+ }
112
+ // No manifest — fall through to full discovery path below
113
+ }
114
+ // ── Full startup path ───────────────────────────────────────────────────
115
+ // Dynamic imports: these are deferred so the fast path above never pays the cost.
116
+ const { discoverClis, discoverPlugins, ensureUserCliCompatShims, ensureUserAdapters } = await import('./discovery.js');
117
+ const { getCompletions } = await import('./completion.js');
118
+ const { runCli } = await import('./cli.js');
119
+ const { emitHook } = await import('./hooks.js');
120
+ const { installNodeNetwork } = await import('./node-network.js');
121
+ const { registerUpdateNoticeOnExit, checkForUpdateBackground } = await import('./update-check.js');
122
+ installNodeNetwork();
123
+ // Parallelise independent startup I/O:
124
+ // - Built-in adapter discovery has no dependency on user-dir setup.
125
+ // - ensureUserCliCompatShims and ensureUserAdapters operate on different paths
126
+ // (~/.webcmd/node_modules/ vs ~/.webcmd/clis/ + adapter-manifest.json).
127
+ // - registerCommand() overwrites on name collision (see registry.ts), so
128
+ // user-CLI discovery MUST run after built-in discovery to preserve the
129
+ // intended override order (user adapters override built-in ones).
130
+ // - discoverPlugins runs last: plugins may override both built-in and user CLIs.
131
+ const skipUserDiscovery = argv[0] === 'convention-audit';
132
+ if (skipUserDiscovery) {
133
+ await discoverClis(BUILTIN_CLIS);
134
+ }
135
+ else {
136
+ const [, ,] = await Promise.all([
137
+ ensureUserCliCompatShims(),
138
+ ensureUserAdapters(),
139
+ discoverClis(BUILTIN_CLIS),
140
+ ]);
141
+ await discoverClis(USER_CLIS);
142
+ await discoverPlugins();
80
143
  }
81
- catch { /* no user manifest */ }
82
- if (hasAllManifests(manifestPaths)) {
144
+ // Register exit hook: notice appears after command output (same as npm/gh/yarn)
145
+ registerUpdateNoticeOnExit();
146
+ // Kick off background fetch for next run (non-blocking)
147
+ checkForUpdateBackground();
148
+ // ── Fallback completion: manifest unavailable, use full registry ─────────
149
+ if (getCompIdx !== -1) {
83
150
  const rest = process.argv.slice(getCompIdx + 1);
84
151
  let cursor;
85
152
  const words = [];
@@ -94,93 +161,38 @@ if (getCompIdx !== -1) {
94
161
  }
95
162
  if (cursor === undefined)
96
163
  cursor = words.length;
97
- const candidates = getCompletionsFromManifest(words, cursor, manifestPaths);
164
+ const candidates = getCompletions(words, cursor);
98
165
  process.stdout.write(candidates.join('\n') + '\n');
99
166
  process.exit(EXIT_CODES.SUCCESS);
100
167
  }
101
- // No manifest fall through to full discovery path below
102
- }
103
- // ── Full startup path ───────────────────────────────────────────────────
104
- // Dynamic imports: these are deferred so the fast path above never pays the cost.
105
- const { discoverClis, discoverPlugins, ensureUserCliCompatShims, ensureUserAdapters } = await import('./discovery.js');
106
- const { getCompletions } = await import('./completion.js');
107
- const { runCli } = await import('./cli.js');
108
- const { emitHook } = await import('./hooks.js');
109
- const { installNodeNetwork } = await import('./node-network.js');
110
- const { registerUpdateNoticeOnExit, checkForUpdateBackground } = await import('./update-check.js');
111
- installNodeNetwork();
112
- // Parallelise independent startup I/O:
113
- // - Built-in adapter discovery has no dependency on user-dir setup.
114
- // - ensureUserCliCompatShims and ensureUserAdapters operate on different paths
115
- // (~/.webcmd/node_modules/ vs ~/.webcmd/clis/ + adapter-manifest.json).
116
- // - registerCommand() overwrites on name collision (see registry.ts), so
117
- // user-CLI discovery MUST run after built-in discovery to preserve the
118
- // intended override order (user adapters override built-in ones).
119
- // - discoverPlugins runs last: plugins may override both built-in and user CLIs.
120
- const skipUserDiscovery = argv[0] === 'convention-audit';
121
- if (skipUserDiscovery) {
122
- await discoverClis(BUILTIN_CLIS);
123
- }
124
- else {
125
- const [, ,] = await Promise.all([
126
- ensureUserCliCompatShims(),
127
- ensureUserAdapters(),
128
- discoverClis(BUILTIN_CLIS),
129
- ]);
130
- await discoverClis(USER_CLIS);
131
- await discoverPlugins();
132
- }
133
- // Register exit hook: notice appears after command output (same as npm/gh/yarn)
134
- registerUpdateNoticeOnExit();
135
- // Kick off background fetch for next run (non-blocking)
136
- checkForUpdateBackground();
137
- // ── Fallback completion: manifest unavailable, use full registry ─────────
138
- if (getCompIdx !== -1) {
139
- const rest = process.argv.slice(getCompIdx + 1);
140
- let cursor;
141
- const words = [];
142
- for (let i = 0; i < rest.length; i++) {
143
- if (rest[i] === '--cursor' && i + 1 < rest.length) {
144
- cursor = parseInt(rest[i + 1], 10);
145
- i++;
146
- }
147
- else {
148
- words.push(rest[i]);
149
- }
150
- }
151
- if (cursor === undefined)
152
- cursor = words.length;
153
- const candidates = getCompletions(words, cursor);
154
- process.stdout.write(candidates.join('\n') + '\n');
155
- process.exit(EXIT_CODES.SUCCESS);
156
- }
157
- // Rewrite `webcmd browser <session> <subcommand> ...` so commander (which
158
- // can't combine a parent positional with subcommand dispatch) sees the internal
159
- // `--session <name>` flag form. Also refuses the retired `webcmd browser
160
- // --session foo ...` user form with a friendly usage error.
161
- const { rewriteBrowserArgv, BrowserSessionArgvError, escapeLeadingDashPositional } = await import('./cli-argv-preprocess.js');
162
- try {
163
- let rewritten = rewriteBrowserArgv(process.argv.slice(2));
164
- // Insert a `--` separator before a required positional whose value starts
165
- // with `-` (e.g. opaque securityId tokens; #1160). Skipped when the
166
- // manifest is unavailable so the user-cli / dev paths still work.
168
+ // Rewrite `webcmd browser <session> <subcommand> ...` so commander (which
169
+ // can't combine a parent positional with subcommand dispatch) sees the internal
170
+ // `--session <name>` flag form. Also refuses the retired `webcmd browser
171
+ // --session foo ...` user form with a friendly usage error.
172
+ const { rewriteBrowserArgv, BrowserSessionArgvError, escapeLeadingDashPositional } = await import('./cli-argv-preprocess.js');
167
173
  try {
168
- const manifestPath = getCliManifestPath(BUILTIN_CLIS);
169
- if (fs.existsSync(manifestPath)) {
170
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
171
- if (Array.isArray(manifest))
172
- rewritten = escapeLeadingDashPositional(rewritten, manifest);
174
+ let rewritten = rewriteBrowserArgv(process.argv.slice(2));
175
+ // Insert a `--` separator before a required positional whose value starts
176
+ // with `-` (e.g. opaque securityId tokens; #1160). Skipped when the
177
+ // manifest is unavailable so the user-cli / dev paths still work.
178
+ try {
179
+ const manifestPath = getCliManifestPath(BUILTIN_CLIS);
180
+ if (fs.existsSync(manifestPath)) {
181
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
182
+ if (Array.isArray(manifest))
183
+ rewritten = escapeLeadingDashPositional(rewritten, manifest);
184
+ }
173
185
  }
186
+ catch { /* manifest unavailable; skip the dash escape */ }
187
+ process.argv.splice(2, process.argv.length - 2, ...rewritten);
174
188
  }
175
- catch { /* manifest unavailable; skip the dash escape */ }
176
- process.argv.splice(2, process.argv.length - 2, ...rewritten);
177
- }
178
- catch (err) {
179
- if (err instanceof BrowserSessionArgvError) {
180
- process.stderr.write(`error: ${err.message}\n`);
181
- process.exit(EXIT_CODES.GENERIC_ERROR);
189
+ catch (err) {
190
+ if (err instanceof BrowserSessionArgvError) {
191
+ process.stderr.write(`error: ${err.message}\n`);
192
+ process.exit(EXIT_CODES.GENERIC_ERROR);
193
+ }
194
+ throw err;
182
195
  }
183
- throw err;
196
+ await emitHook('onStartup', { command: '__startup__', args: {} });
197
+ runCli(BUILTIN_CLIS, USER_CLIS);
184
198
  }
185
- await emitHook('onStartup', { command: '__startup__', args: {} });
186
- runCli(BUILTIN_CLIS, USER_CLIS);
@@ -6,6 +6,7 @@
6
6
  * runtime code needs from build-manifest is the `ManifestEntry` type,
7
7
  * and that lives here.
8
8
  */
9
+ import type { FileArgumentContract } from './hosted/contract.js';
9
10
  export interface ManifestEntry {
10
11
  site: string;
11
12
  name: string;
@@ -25,6 +26,7 @@ export interface ManifestEntry {
25
26
  positional?: boolean;
26
27
  help?: string;
27
28
  choices?: string[];
29
+ file?: FileArgumentContract;
28
30
  }>;
29
31
  columns?: string[];
30
32
  pipeline?: Record<string, unknown>[];
@@ -1,14 +1,29 @@
1
1
  /**
2
2
  * Output formatting: table, JSON, Markdown, CSV, YAML.
3
3
  */
4
+ import type { ErrorEnvelope } from './errors.js';
4
5
  export interface RenderOptions {
5
6
  fmt?: string;
6
- /** True when the user explicitly passed -f on the command line */
7
+ /** True when the user explicitly passed -f on the command line. */
7
8
  fmtExplicit?: boolean;
9
+ /** TTY state used by the pure formatter. `render` derives this from stdout. */
10
+ isTTY?: boolean;
8
11
  columns?: string[];
9
12
  title?: string;
10
13
  elapsed?: number;
11
14
  source?: string;
12
15
  footerExtra?: string;
13
16
  }
14
- export declare function render(data: unknown, opts?: RenderOptions): void;
17
+ export interface ErrorRenderOptions {
18
+ cmdName?: string;
19
+ traceMode?: unknown;
20
+ }
21
+ export interface StreamRenderOptions extends RenderOptions {
22
+ stdout?: NodeJS.WritableStream;
23
+ }
24
+ /** Format output without writing to process-global streams. */
25
+ export declare function formatOutput(data: unknown, opts?: RenderOptions): string;
26
+ /** Render to an injected stream, with the legacy console path retained for local callers. */
27
+ export declare function render(data: unknown, opts?: StreamRenderOptions): Promise<void>;
28
+ /** Serialize the local error envelope without writing to process-global stderr. */
29
+ export declare function formatErrorEnvelope(envelope: ErrorEnvelope, opts?: ErrorRenderOptions): string;
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import Table from 'cli-table3';
5
5
  import yaml from 'js-yaml';
6
+ import { writeToStream } from './stream-write.js';
6
7
  function normalizeRows(data) {
7
8
  if (Array.isArray(data))
8
9
  return data;
@@ -13,140 +14,146 @@ function normalizeRows(data) {
13
14
  function resolveColumns(rows, opts) {
14
15
  return opts.columns ?? Object.keys(rows[0] ?? {});
15
16
  }
16
- export function render(data, opts = {}) {
17
+ /** Format output without writing to process-global streams. */
18
+ export function formatOutput(data, opts = {}) {
17
19
  let fmt = opts.fmt ?? 'table';
18
- // Non-TTY auto-downgrade only when format was NOT explicitly passed by user.
19
- if (!opts.fmtExplicit) {
20
- if (fmt === 'table' && !process.stdout.isTTY)
21
- fmt = 'yaml';
22
- }
23
- if (data === null || data === undefined) {
24
- console.log(data);
25
- return;
26
- }
20
+ if (!opts.fmtExplicit && fmt === 'table' && !opts.isTTY)
21
+ fmt = 'yaml';
22
+ if (data === null || data === undefined)
23
+ return `${String(data)}\n`;
27
24
  switch (fmt) {
28
- case 'json':
29
- renderJson(data);
30
- break;
31
- case 'plain':
32
- renderPlain(data, opts);
33
- break;
25
+ case 'json': return `${JSON.stringify(data, null, 2)}\n`;
26
+ case 'plain': return formatPlain(data);
34
27
  case 'md':
35
- case 'markdown':
36
- renderMarkdown(data, opts);
37
- break;
38
- case 'csv':
39
- renderCsv(data, opts);
40
- break;
28
+ case 'markdown': return formatMarkdown(data, opts);
29
+ case 'csv': return formatCsv(data, opts);
41
30
  case 'yaml':
42
- case 'yml':
43
- renderYaml(data);
44
- break;
45
- default:
46
- renderTable(data, opts);
47
- break;
31
+ case 'yml': return `${yaml.dump(data, { sortKeys: false, lineWidth: 120, noRefs: true })}\n`;
32
+ default: return formatTable(data, opts);
48
33
  }
49
34
  }
50
- function renderTable(data, opts) {
51
- const rows = normalizeRows(data);
52
- if (!rows.length) {
53
- console.log('(no data)');
35
+ /** Render to an injected stream, with the legacy console path retained for local callers. */
36
+ export async function render(data, opts = {}) {
37
+ const { stdout, ...formatOptions } = opts;
38
+ const targetIsTTY = formatOptions.isTTY
39
+ ?? (stdout ? stdout.isTTY === true : process.stdout.isTTY === true);
40
+ const output = formatOutput(data, { ...formatOptions, isTTY: targetIsTTY });
41
+ if (!output)
42
+ return;
43
+ if (stdout) {
44
+ await writeToStream(stdout, output);
54
45
  return;
55
46
  }
47
+ // Existing local command tests and embedders intercept console.log. Passing
48
+ // one string preserves the exact bytes console.log historically emitted.
49
+ console.log(output.endsWith('\n') ? output.slice(0, -1) : output);
50
+ }
51
+ /** Serialize the local error envelope without writing to process-global stderr. */
52
+ export function formatErrorEnvelope(envelope, opts = {}) {
53
+ let output = yaml.dump(envelope, { sortKeys: false, lineWidth: 120, noRefs: true });
54
+ const code = envelope.error.code;
55
+ if (opts.cmdName
56
+ && opts.traceMode !== 'on'
57
+ && opts.traceMode !== 'retain-on-failure'
58
+ && (code === 'SELECTOR' || code === 'EMPTY_RESULT' || code === 'ADAPTER_LOAD' || code === 'UNKNOWN')) {
59
+ const runnable = opts.cmdName.replace('/', ' ');
60
+ output += '# AutoFix: re-run with --trace=retain-on-failure for trace artifact\n';
61
+ output += `# webcmd ${runnable} --trace retain-on-failure\n`;
62
+ }
63
+ return output;
64
+ }
65
+ function formatTable(data, opts) {
66
+ const rows = normalizeRows(data);
67
+ if (!rows.length)
68
+ return '(no data)\n';
56
69
  const columns = resolveColumns(rows, opts);
57
- const header = columns.map(c => capitalize(c));
58
70
  const table = new Table({
59
- head: header.map(h => h),
71
+ head: columns.map(capitalize),
60
72
  style: { head: [], border: [] },
61
73
  wordWrap: true,
62
74
  wrapOnWordBoundary: true,
63
75
  });
64
76
  for (const row of rows) {
65
- table.push(columns.map(c => {
66
- const v = row[c];
67
- return v === null || v === undefined ? '' : String(v);
77
+ table.push(columns.map((column) => {
78
+ const value = row[column];
79
+ return value === null || value === undefined ? '' : String(value);
68
80
  }));
69
81
  }
70
- console.log();
82
+ const output = [''];
71
83
  if (opts.title)
72
- console.log(` ${opts.title}`);
73
- console.log(table.toString());
74
- const footer = [];
75
- footer.push(`${rows.length} items`);
84
+ output.push(` ${opts.title}`);
85
+ output.push(table.toString());
86
+ const footer = [`${rows.length} items`];
76
87
  if (opts.elapsed !== undefined)
77
88
  footer.push(`${opts.elapsed.toFixed(1)}s`);
78
89
  if (opts.source)
79
90
  footer.push(opts.source);
80
91
  if (opts.footerExtra)
81
92
  footer.push(opts.footerExtra);
82
- console.log(footer.join(' | '));
93
+ output.push(footer.join(' | '));
94
+ return `${output.join('\n')}\n`;
83
95
  }
84
- function renderJson(data) {
85
- console.log(JSON.stringify(data, null, 2));
86
- }
87
- function renderPlain(data, opts) {
96
+ function formatPlain(data) {
88
97
  const rows = normalizeRows(data);
89
98
  if (!rows.length)
90
- return;
91
- // Single-row single-field shortcuts for chat-style commands.
99
+ return '';
92
100
  if (rows.length === 1) {
93
- const row = rows[0];
94
- const entries = Object.entries(row);
101
+ const entries = Object.entries(rows[0]);
95
102
  if (entries.length === 1) {
96
103
  const [key, value] = entries[0];
97
104
  if (key === 'response' || key === 'content' || key === 'markdown' || key === 'text' || key === 'value') {
98
- console.log(String(value ?? ''));
99
- return;
105
+ return `${String(value ?? '')}\n`;
100
106
  }
101
107
  }
102
108
  }
109
+ const output = [];
103
110
  rows.forEach((row, index) => {
104
- const entries = Object.entries(row).filter(([, value]) => value !== undefined && value !== null && String(value) !== '');
105
- entries.forEach(([key, value]) => {
106
- console.log(`${key}: ${value}`);
107
- });
111
+ for (const [key, value] of Object.entries(row)) {
112
+ if (value === undefined || value === null || String(value) === '')
113
+ continue;
114
+ output.push(`${key}: ${value}`);
115
+ }
108
116
  if (index < rows.length - 1)
109
- console.log('');
117
+ output.push('');
110
118
  });
119
+ return output.length > 0 ? `${output.join('\n')}\n` : '';
111
120
  }
112
- function renderMarkdown(data, opts) {
121
+ function formatMarkdown(data, opts) {
113
122
  const rows = normalizeRows(data);
114
123
  if (!rows.length)
115
- return;
124
+ return '';
116
125
  if (rows.length === 1) {
117
126
  const entries = Object.entries(rows[0]);
118
127
  if (entries.length === 1) {
119
128
  const [key, value] = entries[0];
120
129
  if (key === 'content' || key === 'markdown' || key === 'text' || key === 'value') {
121
- console.log(String(value ?? ''));
122
- return;
130
+ return `${String(value ?? '')}\n`;
123
131
  }
124
132
  }
125
133
  }
126
134
  const columns = resolveColumns(rows, opts);
127
- console.log('| ' + columns.join(' | ') + ' |');
128
- console.log('| ' + columns.map(() => '---').join(' | ') + ' |');
129
- for (const row of rows) {
130
- console.log('| ' + columns.map(c => String(row[c] ?? '')).join(' | ') + ' |');
131
- }
135
+ const output = [
136
+ `| ${columns.join(' | ')} |`,
137
+ `| ${columns.map(() => '---').join(' | ')} |`,
138
+ ...rows.map(row => `| ${columns.map(column => String(row[column] ?? '')).join(' | ')} |`),
139
+ ];
140
+ return `${output.join('\n')}\n`;
132
141
  }
133
- function renderCsv(data, opts) {
142
+ function formatCsv(data, opts) {
134
143
  const rows = normalizeRows(data);
135
144
  if (!rows.length)
136
- return;
145
+ return '';
137
146
  const columns = resolveColumns(rows, opts);
138
- console.log(columns.join(','));
139
- for (const row of rows) {
140
- console.log(columns.map(c => {
141
- const v = String(row[c] ?? '');
142
- return v.includes(',') || v.includes('"') || v.includes('\n') || v.includes('\r')
143
- ? `"${v.replace(/"/g, '""')}"` : v;
144
- }).join(','));
145
- }
147
+ const output = [
148
+ columns.join(','),
149
+ ...rows.map(row => columns.map(column => csvCell(row[column])).join(',')),
150
+ ];
151
+ return `${output.join('\n')}\n`;
146
152
  }
147
- function renderYaml(data) {
148
- console.log(yaml.dump(data, { sortKeys: false, lineWidth: 120, noRefs: true }));
153
+ function csvCell(value) {
154
+ const text = String(value ?? '');
155
+ return /[,"\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
149
156
  }
150
- function capitalize(s) {
151
- return s.charAt(0).toUpperCase() + s.slice(1);
157
+ function capitalize(value) {
158
+ return value.charAt(0).toUpperCase() + value.slice(1);
152
159
  }