@vforsh/argus 0.1.14 → 0.1.16

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 (175) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/argus.js +1861 -1171
  3. package/dist/cli/defineWatcherCommand.d.ts +17 -0
  4. package/dist/cli/defineWatcherCommand.d.ts.map +1 -1
  5. package/dist/cli/defineWatcherCommand.js +19 -2
  6. package/dist/cli/defineWatcherCommand.js.map +1 -1
  7. package/dist/cli/plugins/pluginAliases.d.ts +6 -0
  8. package/dist/cli/plugins/pluginAliases.d.ts.map +1 -0
  9. package/dist/cli/plugins/pluginAliases.js +10 -0
  10. package/dist/cli/plugins/pluginAliases.js.map +1 -0
  11. package/dist/cli/plugins/pluginHost.d.ts +3 -0
  12. package/dist/cli/plugins/pluginHost.d.ts.map +1 -0
  13. package/dist/cli/plugins/pluginHost.js +30 -0
  14. package/dist/cli/plugins/pluginHost.js.map +1 -0
  15. package/dist/cli/plugins/registerPlugins.d.ts +32 -1
  16. package/dist/cli/plugins/registerPlugins.d.ts.map +1 -1
  17. package/dist/cli/plugins/registerPlugins.js +102 -12
  18. package/dist/cli/plugins/registerPlugins.js.map +1 -1
  19. package/dist/cli/program.d.ts.map +1 -1
  20. package/dist/cli/program.js +2 -0
  21. package/dist/cli/program.js.map +1 -1
  22. package/dist/cli/register/index.d.ts.map +1 -1
  23. package/dist/cli/register/index.js +2 -0
  24. package/dist/cli/register/index.js.map +1 -1
  25. package/dist/cli/register/registerAuth.d.ts.map +1 -1
  26. package/dist/cli/register/registerAuth.js +13 -8
  27. package/dist/cli/register/registerAuth.js.map +1 -1
  28. package/dist/cli/register/registerEval.d.ts.map +1 -1
  29. package/dist/cli/register/registerEval.js +12 -0
  30. package/dist/cli/register/registerEval.js.map +1 -1
  31. package/dist/cli/register/registerExtension.d.ts.map +1 -1
  32. package/dist/cli/register/registerExtension.js +24 -1
  33. package/dist/cli/register/registerExtension.js.map +1 -1
  34. package/dist/cli/register/registerPlugin.d.ts +3 -0
  35. package/dist/cli/register/registerPlugin.d.ts.map +1 -0
  36. package/dist/cli/register/registerPlugin.js +34 -0
  37. package/dist/cli/register/registerPlugin.js.map +1 -0
  38. package/dist/cli/register/registerWatcher.d.ts.map +1 -1
  39. package/dist/cli/register/registerWatcher.js +1 -0
  40. package/dist/cli/register/registerWatcher.js.map +1 -1
  41. package/dist/commands/auth.d.ts.map +1 -1
  42. package/dist/commands/auth.js +4 -9
  43. package/dist/commands/auth.js.map +1 -1
  44. package/dist/commands/authCookieSupport.d.ts.map +1 -1
  45. package/dist/commands/authCookieSupport.js +4 -6
  46. package/dist/commands/authCookieSupport.js.map +1 -1
  47. package/dist/commands/authCookies.d.ts +4 -4
  48. package/dist/commands/authCookies.d.ts.map +1 -1
  49. package/dist/commands/authCookies.js +85 -105
  50. package/dist/commands/authCookies.js.map +1 -1
  51. package/dist/commands/code.js +3 -25
  52. package/dist/commands/code.js.map +1 -1
  53. package/dist/commands/codeEdit.js +3 -3
  54. package/dist/commands/codeEdit.js.map +1 -1
  55. package/dist/commands/configInit.d.ts.map +1 -1
  56. package/dist/commands/configInit.js +1 -0
  57. package/dist/commands/configInit.js.map +1 -1
  58. package/dist/commands/domAdd.d.ts +1 -1
  59. package/dist/commands/domAdd.d.ts.map +1 -1
  60. package/dist/commands/domAdd.js +26 -30
  61. package/dist/commands/domAdd.js.map +1 -1
  62. package/dist/commands/domInfo.d.ts +1 -1
  63. package/dist/commands/domInfo.d.ts.map +1 -1
  64. package/dist/commands/domInfo.js +18 -23
  65. package/dist/commands/domInfo.js.map +1 -1
  66. package/dist/commands/domKeydown.d.ts +1 -1
  67. package/dist/commands/domKeydown.d.ts.map +1 -1
  68. package/dist/commands/domKeydown.js +22 -43
  69. package/dist/commands/domKeydown.js.map +1 -1
  70. package/dist/commands/domModify.js +27 -30
  71. package/dist/commands/domModify.js.map +1 -1
  72. package/dist/commands/domScroll.d.ts +1 -1
  73. package/dist/commands/domScroll.d.ts.map +1 -1
  74. package/dist/commands/domScroll.js +18 -24
  75. package/dist/commands/domScroll.js.map +1 -1
  76. package/dist/commands/domScrollTo.d.ts +1 -1
  77. package/dist/commands/domScrollTo.d.ts.map +1 -1
  78. package/dist/commands/domScrollTo.js +15 -24
  79. package/dist/commands/domScrollTo.js.map +1 -1
  80. package/dist/commands/domTree.d.ts +1 -1
  81. package/dist/commands/domTree.d.ts.map +1 -1
  82. package/dist/commands/domTree.js +18 -24
  83. package/dist/commands/domTree.js.map +1 -1
  84. package/dist/commands/eval.d.ts +4 -0
  85. package/dist/commands/eval.d.ts.map +1 -1
  86. package/dist/commands/eval.js +43 -62
  87. package/dist/commands/eval.js.map +1 -1
  88. package/dist/commands/evalBundle.d.ts +17 -0
  89. package/dist/commands/evalBundle.d.ts.map +1 -0
  90. package/dist/commands/evalBundle.js +119 -0
  91. package/dist/commands/evalBundle.js.map +1 -0
  92. package/dist/commands/evalPolling.d.ts +55 -0
  93. package/dist/commands/evalPolling.d.ts.map +1 -0
  94. package/dist/commands/evalPolling.js +59 -0
  95. package/dist/commands/evalPolling.js.map +1 -0
  96. package/dist/commands/evalShared.d.ts +14 -0
  97. package/dist/commands/evalShared.d.ts.map +1 -1
  98. package/dist/commands/evalShared.js +75 -10
  99. package/dist/commands/evalShared.js.map +1 -1
  100. package/dist/commands/evalUntil.d.ts +4 -0
  101. package/dist/commands/evalUntil.d.ts.map +1 -1
  102. package/dist/commands/evalUntil.js +47 -70
  103. package/dist/commands/evalUntil.js.map +1 -1
  104. package/dist/commands/extension/attach.d.ts +12 -0
  105. package/dist/commands/extension/attach.d.ts.map +1 -0
  106. package/dist/commands/extension/attach.js +175 -0
  107. package/dist/commands/extension/attach.js.map +1 -0
  108. package/dist/commands/extension/info.d.ts.map +1 -1
  109. package/dist/commands/extension/info.js +13 -33
  110. package/dist/commands/extension/info.js.map +1 -1
  111. package/dist/commands/extension/nativeHost.d.ts +35 -5
  112. package/dist/commands/extension/nativeHost.d.ts.map +1 -1
  113. package/dist/commands/extension/nativeHost.js +95 -12
  114. package/dist/commands/extension/nativeHost.js.map +1 -1
  115. package/dist/commands/extension/remove.d.ts.map +1 -1
  116. package/dist/commands/extension/remove.js +18 -38
  117. package/dist/commands/extension/remove.js.map +1 -1
  118. package/dist/commands/extension/resolveExtensionWatcher.d.ts +1 -1
  119. package/dist/commands/extension/resolveExtensionWatcher.d.ts.map +1 -1
  120. package/dist/commands/extension/resolveExtensionWatcher.js +15 -29
  121. package/dist/commands/extension/resolveExtensionWatcher.js.map +1 -1
  122. package/dist/commands/extension/setup.d.ts.map +1 -1
  123. package/dist/commands/extension/setup.js +20 -35
  124. package/dist/commands/extension/setup.js.map +1 -1
  125. package/dist/commands/extension/status.d.ts.map +1 -1
  126. package/dist/commands/extension/status.js +12 -38
  127. package/dist/commands/extension/status.js.map +1 -1
  128. package/dist/commands/locate.js +2 -2
  129. package/dist/commands/locate.js.map +1 -1
  130. package/dist/commands/logs.d.ts +1 -1
  131. package/dist/commands/logs.d.ts.map +1 -1
  132. package/dist/commands/logs.js +18 -27
  133. package/dist/commands/logs.js.map +1 -1
  134. package/dist/commands/net.d.ts +1 -1
  135. package/dist/commands/net.d.ts.map +1 -1
  136. package/dist/commands/net.js +14 -19
  137. package/dist/commands/net.js.map +1 -1
  138. package/dist/commands/netClear.d.ts +1 -1
  139. package/dist/commands/netClear.d.ts.map +1 -1
  140. package/dist/commands/netClear.js +7 -19
  141. package/dist/commands/netClear.js.map +1 -1
  142. package/dist/commands/netSse.js +2 -2
  143. package/dist/commands/netSse.js.map +1 -1
  144. package/dist/commands/netWebSocket.js +3 -3
  145. package/dist/commands/netWebSocket.js.map +1 -1
  146. package/dist/commands/pageEmulation.d.ts +3 -3
  147. package/dist/commands/pageEmulation.d.ts.map +1 -1
  148. package/dist/commands/pageEmulation.js +72 -143
  149. package/dist/commands/pageEmulation.js.map +1 -1
  150. package/dist/commands/pluginConfig.d.ts +7 -0
  151. package/dist/commands/pluginConfig.d.ts.map +1 -0
  152. package/dist/commands/pluginConfig.js +199 -0
  153. package/dist/commands/pluginConfig.js.map +1 -0
  154. package/dist/commands/pluginList.d.ts +5 -0
  155. package/dist/commands/pluginList.d.ts.map +1 -0
  156. package/dist/commands/pluginList.js +26 -0
  157. package/dist/commands/pluginList.js.map +1 -0
  158. package/dist/commands/screenshot.d.ts +1 -1
  159. package/dist/commands/screenshot.d.ts.map +1 -1
  160. package/dist/commands/screenshot.js +12 -18
  161. package/dist/commands/screenshot.js.map +1 -1
  162. package/dist/commands/watcherNativeHost.d.ts +1 -0
  163. package/dist/commands/watcherNativeHost.d.ts.map +1 -1
  164. package/dist/commands/watcherNativeHost.js +8 -2
  165. package/dist/commands/watcherNativeHost.js.map +1 -1
  166. package/dist/commands/watcherStatus.d.ts +1 -1
  167. package/dist/commands/watcherStatus.d.ts.map +1 -1
  168. package/dist/commands/watcherStatus.js +8 -21
  169. package/dist/commands/watcherStatus.js.map +1 -1
  170. package/dist/config/argusConfig.d.ts +5 -0
  171. package/dist/config/argusConfig.d.ts.map +1 -1
  172. package/dist/config/argusConfig.js +33 -2
  173. package/dist/config/argusConfig.js.map +1 -1
  174. package/package.json +3 -2
  175. package/schemas/argus.config.schema.json +16 -0
package/dist/argus.js CHANGED
@@ -2130,7 +2130,7 @@ var {
2130
2130
  // package.json
2131
2131
  var package_default = {
2132
2132
  name: "@vforsh/argus",
2133
- version: "0.1.14",
2133
+ version: "0.1.16",
2134
2134
  repository: {
2135
2135
  type: "git",
2136
2136
  url: "git+https://github.com/vforsh/argus.git",
@@ -2158,12 +2158,13 @@ var package_default = {
2158
2158
  dependencies: {
2159
2159
  "@vforsh/argus-plugin-api": "workspace:*",
2160
2160
  commander: "14.0.2",
2161
+ esbuild: "0.25.12",
2161
2162
  prettier: "3.7.4"
2162
2163
  },
2163
2164
  scripts: {
2164
2165
  clean: "node ../../scripts/clean-dist.mjs",
2165
2166
  build: "npm run clean && tsc -b && bun run bundle",
2166
- bundle: "bun build dist/bin.js --outfile dist/argus.js --target node",
2167
+ bundle: "bun build dist/bin.js --outfile dist/argus.js --target node --external esbuild",
2167
2168
  typecheck: "tsc -b --pretty false",
2168
2169
  "chrome:start": "bun src/bin.ts chrome start"
2169
2170
  }
@@ -2172,7 +2173,7 @@ var package_default = {
2172
2173
  // dist/cli/program.js
2173
2174
  function createProgram() {
2174
2175
  const program2 = new Command;
2175
- program2.name("argus").description("Argus CLI for local watcher servers").version(package_default.version).configureOutput({
2176
+ program2.name("argus").description("Argus CLI for local watcher servers").version(package_default.version).option("--plugin <specifier>", "Load an extra CLI plugin for this invocation", collectPluginOption, []).configureOutput({
2176
2177
  outputError: (str, write) => write(str)
2177
2178
  }).showSuggestionAfterError(true).exitOverride((error) => {
2178
2179
  if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
@@ -2183,6 +2184,7 @@ function createProgram() {
2183
2184
  });
2184
2185
  return program2;
2185
2186
  }
2187
+ var collectPluginOption = (value, previous) => [...previous, value];
2186
2188
 
2187
2189
  // ../argus-core/dist/protocol/logs.js
2188
2190
  var formatLogLevelTag = (level) => {
@@ -3311,11 +3313,20 @@ function writeResolveError(resolved, output) {
3311
3313
 
3312
3314
  // dist/cli/defineWatcherCommand.js
3313
3315
  var DEFAULT_TIMEOUT_MS = 30000;
3316
+ var requestWatcherCommandJson = async (input, output) => {
3317
+ const result = await requestWatcherJson(input);
3318
+ if (!result.ok) {
3319
+ writeRequestError(result, output);
3320
+ return null;
3321
+ }
3322
+ return result;
3323
+ };
3324
+ var requestWatcherCommandAction = requestWatcherAction;
3314
3325
  var defineWatcherCommand = (spec) => async (id, ...rest) => {
3315
3326
  const tail = rest.length - 1;
3316
3327
  const options = rest[tail];
3317
3328
  const args = rest.slice(0, tail);
3318
- const output = createOutput(options);
3329
+ const output = createOutput({ ...options, json: options.json === true || spec.isJson?.(options) === true });
3319
3330
  const plan = await spec.build(args, options, output);
3320
3331
  if (plan == null)
3321
3332
  return;
@@ -4368,20 +4379,15 @@ var loadAuthStateSnapshot = async (inputPath) => {
4368
4379
  return parseAuthStateSnapshot(parsed, `auth state ${source}`);
4369
4380
  };
4370
4381
  var requestAuthStateSnapshot = async (id, input, output) => {
4371
- const result = await requestWatcherJson({
4382
+ return requestWatcherCommandJson({
4372
4383
  id,
4373
4384
  path: "/auth/state",
4374
4385
  query: buildStateQuery(input),
4375
4386
  timeoutMs: 1e4
4376
- });
4377
- if (!result.ok) {
4378
- writeRequestError(result, output);
4379
- return null;
4380
- }
4381
- return result;
4387
+ }, output);
4382
4388
  };
4383
4389
  var loadAuthStateIntoWatcher = async (id, snapshot, input, output) => {
4384
- const result = await requestWatcherAction({
4390
+ const result = await requestWatcherCommandAction({
4385
4391
  id,
4386
4392
  path: "/auth/state/load",
4387
4393
  method: "POST",
@@ -6427,19 +6433,62 @@ var handle11 = (_req, res, url, ctx) => {
6427
6433
  respondJson(res, response);
6428
6434
  };
6429
6435
 
6436
+ // ../argus-watcher/dist/http/routes/defineRoute.js
6437
+ var defineJsonRoute = (input) => ({
6438
+ method: input.method,
6439
+ path: input.path,
6440
+ extensionOnly: input.extensionOnly,
6441
+ handler: async (req, res, url, ctx) => {
6442
+ const shouldReadBody = input.bodySchema != null || input.parseBody === true;
6443
+ const rawBody = shouldReadBody ? await readJsonBody(req, res) : undefined;
6444
+ if (shouldReadBody && rawBody == null) {
6445
+ return;
6446
+ }
6447
+ const parsedBody = input.bodySchema?.parse(rawBody);
6448
+ if (parsedBody && !parsedBody.ok) {
6449
+ return respondInvalidBody(res, formatProtocolValidationIssues(parsedBody.issues));
6450
+ }
6451
+ if (input.endpoint) {
6452
+ emitRequest(ctx, res, input.endpoint);
6453
+ }
6454
+ try {
6455
+ const response = await input.handle({
6456
+ req,
6457
+ res,
6458
+ url,
6459
+ ctx,
6460
+ body: parsedBody?.value ?? rawBody
6461
+ });
6462
+ if (response) {
6463
+ respondJson(res, response);
6464
+ }
6465
+ } catch (error) {
6466
+ if (input.handleError?.(res, error)) {
6467
+ return;
6468
+ }
6469
+ respondError(res, error);
6470
+ }
6471
+ }
6472
+ });
6473
+
6430
6474
  // ../argus-watcher/dist/http/routes/postNetClear.js
6431
- var handle12 = (_req, res, _url, ctx) => {
6432
- if (!ctx.netBuffer) {
6433
- return respondJson(res, { ok: false, error: { code: "net_disabled", message: "Network capture is disabled for this watcher" } }, 400);
6475
+ var handle12 = defineJsonRoute({
6476
+ method: "POST",
6477
+ path: "/net/clear",
6478
+ handle: ({ res, ctx }) => {
6479
+ if (!ctx.netBuffer) {
6480
+ respondJson(res, { ok: false, error: { code: "net_disabled", message: "Network capture is disabled for this watcher" } }, 400);
6481
+ return;
6482
+ }
6483
+ emitRequest(ctx, res, "net/clear");
6484
+ const realtimeCleared = ctx.realtimeNetBuffer?.clear() ?? 0;
6485
+ const response = {
6486
+ ok: true,
6487
+ cleared: ctx.netBuffer.clear() + realtimeCleared
6488
+ };
6489
+ return response;
6434
6490
  }
6435
- emitRequest(ctx, res, "net/clear");
6436
- const realtimeCleared = ctx.realtimeNetBuffer?.clear() ?? 0;
6437
- const response = {
6438
- ok: true,
6439
- cleared: ctx.netBuffer.clear() + realtimeCleared
6440
- };
6441
- respondJson(res, response);
6442
- };
6491
+ }).handler;
6443
6492
 
6444
6493
  // ../argus-watcher/dist/cdp/auth.js
6445
6494
  var inspectAuthCookies = async (session, options) => {
@@ -7304,23 +7353,19 @@ var handle18 = async (req, res, _url, ctx) => {
7304
7353
  };
7305
7354
 
7306
7355
  // ../argus-watcher/dist/http/routes/postScreenshot.js
7307
- var handle19 = async (req, res, _url, ctx) => {
7308
- const payload = await readJsonBody(req, res);
7309
- if (!payload) {
7310
- return;
7311
- }
7312
- const validationError = validateScreenshotRequest(payload);
7313
- if (validationError) {
7314
- return respondInvalidBody(res, validationError);
7315
- }
7316
- emitRequest(ctx, res, "screenshot");
7317
- try {
7318
- const response = await ctx.screenshotter.capture(payload);
7319
- respondJson(res, response);
7320
- } catch (error) {
7321
- respondError(res, error);
7356
+ var handle19 = defineJsonRoute({
7357
+ method: "POST",
7358
+ path: "/screenshot",
7359
+ parseBody: true,
7360
+ handle: async ({ res, ctx, body: payload }) => {
7361
+ const validationError = validateScreenshotRequest(payload);
7362
+ if (validationError) {
7363
+ return respondInvalidBody(res, validationError);
7364
+ }
7365
+ emitRequest(ctx, res, "screenshot");
7366
+ return ctx.screenshotter.capture(payload);
7322
7367
  }
7323
- };
7368
+ }).handler;
7324
7369
  var validateScreenshotRequest = (payload) => {
7325
7370
  if (payload.selector != null && (typeof payload.selector !== "string" || !payload.selector.trim())) {
7326
7371
  return "selector must be a non-empty string";
@@ -7825,29 +7870,25 @@ var countNodes = (nodes) => {
7825
7870
  };
7826
7871
 
7827
7872
  // ../argus-watcher/dist/http/routes/postSnapshot.js
7828
- var handle20 = async (req, res, _url, ctx) => {
7829
- const payload = await readJsonBody(req, res);
7830
- if (!payload) {
7831
- return;
7832
- }
7833
- if (payload.selector != null && (typeof payload.selector !== "string" || payload.selector.trim() === "")) {
7834
- return respondInvalidBody(res, "selector must be a non-empty string");
7835
- }
7836
- if (payload.depth != null && (!Number.isFinite(payload.depth) || payload.depth < 0 || !Number.isInteger(payload.depth))) {
7837
- return respondInvalidBody(res, "depth must be a non-negative integer");
7838
- }
7839
- emitRequest(ctx, res, "snapshot");
7840
- try {
7841
- const response = await fetchAccessibilitySnapshot(ctx.cdpSession, {
7873
+ var handle20 = defineJsonRoute({
7874
+ method: "POST",
7875
+ path: "/snapshot",
7876
+ parseBody: true,
7877
+ handle: async ({ res, ctx, body: payload }) => {
7878
+ if (payload.selector != null && (typeof payload.selector !== "string" || payload.selector.trim() === "")) {
7879
+ return respondInvalidBody(res, "selector must be a non-empty string");
7880
+ }
7881
+ if (payload.depth != null && (!Number.isFinite(payload.depth) || payload.depth < 0 || !Number.isInteger(payload.depth))) {
7882
+ return respondInvalidBody(res, "depth must be a non-negative integer");
7883
+ }
7884
+ emitRequest(ctx, res, "snapshot");
7885
+ return fetchAccessibilitySnapshot(ctx.cdpSession, {
7842
7886
  selector: payload.selector,
7843
7887
  depth: payload.depth,
7844
7888
  interactive: payload.interactive ?? false
7845
7889
  }, ctx.elementRefs);
7846
- respondJson(res, response);
7847
- } catch (error) {
7848
- respondError(res, error);
7849
7890
  }
7850
- };
7891
+ }).handler;
7851
7892
 
7852
7893
  // ../argus-watcher/dist/http/routes/domSelectorRoute.js
7853
7894
  var readDomTargetPayload = async (req, res) => {
@@ -8741,43 +8782,6 @@ var route = defineDomTargetRoute({
8741
8782
  }
8742
8783
  });
8743
8784
 
8744
- // ../argus-watcher/dist/http/routes/defineRoute.js
8745
- var defineJsonRoute = (input) => ({
8746
- method: input.method,
8747
- path: input.path,
8748
- extensionOnly: input.extensionOnly,
8749
- handler: async (req, res, url, ctx) => {
8750
- const rawBody = input.bodySchema ? await readJsonBody(req, res) : undefined;
8751
- if (input.bodySchema && rawBody == null) {
8752
- return;
8753
- }
8754
- const parsedBody = input.bodySchema?.parse(rawBody);
8755
- if (parsedBody && !parsedBody.ok) {
8756
- return respondInvalidBody(res, formatProtocolValidationIssues(parsedBody.issues));
8757
- }
8758
- if (input.endpoint) {
8759
- emitRequest(ctx, res, input.endpoint);
8760
- }
8761
- try {
8762
- const response = await input.handle({
8763
- req,
8764
- res,
8765
- url,
8766
- ctx,
8767
- body: parsedBody?.value
8768
- });
8769
- if (response) {
8770
- respondJson(res, response);
8771
- }
8772
- } catch (error) {
8773
- if (input.handleError?.(res, error)) {
8774
- return;
8775
- }
8776
- respondError(res, error);
8777
- }
8778
- }
8779
- });
8780
-
8781
8785
  // ../argus-watcher/dist/http/routes/postDomClick.js
8782
8786
  var route2 = defineJsonRoute({
8783
8787
  method: "POST",
@@ -9427,78 +9431,75 @@ var handle37 = async (req, res, _url, ctx) => {
9427
9431
  };
9428
9432
 
9429
9433
  // ../argus-watcher/dist/http/routes/getThrottle.js
9430
- var handle38 = (_req, res, _url, ctx) => {
9431
- emitRequest(ctx, res, "throttle");
9432
- const status = ctx.throttleController.getStatus({ attached: ctx.getCdpStatus().attached });
9433
- respondJson(res, status);
9434
- };
9434
+ var handle38 = defineJsonRoute({
9435
+ method: "GET",
9436
+ path: "/throttle",
9437
+ endpoint: "throttle",
9438
+ handle: ({ ctx }) => ctx.throttleController.getStatus({ attached: ctx.getCdpStatus().attached })
9439
+ }).handler;
9435
9440
 
9436
9441
  // ../argus-watcher/dist/http/routes/postThrottle.js
9437
- var handle39 = async (req, res, _url, ctx) => {
9438
- const payload = await readJsonBody(req, res);
9439
- if (!payload) {
9440
- return;
9441
- }
9442
- const validActions = ["set", "clear"];
9443
- const action = payload.action;
9444
- if (!action || !validActions.includes(action)) {
9445
- return respondInvalidBody(res, `action must be one of: ${validActions.join(", ")}`);
9446
- }
9447
- emitRequest(ctx, res, "throttle");
9448
- const session = ctx.cdpSession.isAttached() ? ctx.cdpSession : null;
9449
- if (action === "clear") {
9450
- try {
9451
- const response = await ctx.throttleController.clearDesired(session);
9452
- respondJson(res, response);
9453
- } catch (error) {
9454
- respondError(res, error);
9442
+ var handle39 = defineJsonRoute({
9443
+ method: "POST",
9444
+ path: "/throttle",
9445
+ parseBody: true,
9446
+ handle: async ({ res, ctx, body: payload }) => {
9447
+ const validActions = ["set", "clear"];
9448
+ const action = payload.action;
9449
+ if (!action || !validActions.includes(action)) {
9450
+ return respondInvalidBody(res, `action must be one of: ${validActions.join(", ")}`);
9451
+ }
9452
+ emitRequest(ctx, res, "throttle");
9453
+ const session = ctx.cdpSession.isAttached() ? ctx.cdpSession : null;
9454
+ if (action === "clear") {
9455
+ return ctx.throttleController.clearDesired(session);
9455
9456
  }
9456
- return;
9457
- }
9458
- const setPayload = payload;
9459
- if (typeof setPayload.rate !== "number" || !Number.isFinite(setPayload.rate) || setPayload.rate < 1) {
9460
- return respondInvalidBody(res, "rate must be a finite number >= 1");
9461
- }
9462
- try {
9463
- const response = await ctx.throttleController.setDesired(setPayload.rate, session);
9464
- respondJson(res, response);
9465
- } catch (error) {
9466
- respondError(res, error);
9457
+ const setPayload = payload;
9458
+ if (typeof setPayload.rate !== "number" || !Number.isFinite(setPayload.rate) || setPayload.rate < 1) {
9459
+ return respondInvalidBody(res, "rate must be a finite number >= 1");
9460
+ }
9461
+ return ctx.throttleController.setDesired(setPayload.rate, session);
9467
9462
  }
9468
- };
9463
+ }).handler;
9469
9464
 
9470
9465
  // ../argus-watcher/dist/http/routes/getDialog.js
9471
- var handle40 = (_req, res, _url, ctx) => {
9472
- emitRequest(ctx, res, "dialog/status");
9473
- const response = {
9474
- ok: true,
9475
- dialog: ctx.getDialog()
9476
- };
9477
- respondJson(res, response);
9478
- };
9466
+ var handle40 = defineJsonRoute({
9467
+ method: "GET",
9468
+ path: "/dialog",
9469
+ endpoint: "dialog/status",
9470
+ handle: ({ ctx }) => {
9471
+ const response = {
9472
+ ok: true,
9473
+ dialog: ctx.getDialog()
9474
+ };
9475
+ return response;
9476
+ }
9477
+ }).handler;
9479
9478
 
9480
9479
  // ../argus-watcher/dist/http/routes/postDialog.js
9481
- var handle41 = async (req, res, _url, ctx) => {
9482
- const payload = await readJsonBody(req, res);
9483
- if (!payload) {
9484
- return;
9485
- }
9486
- emitRequest(ctx, res, "dialog/handle");
9487
- const action = payload.action;
9488
- if (action !== "accept" && action !== "dismiss") {
9489
- respondInvalidBody(res, 'Dialog action must be "accept" or "dismiss"');
9490
- return;
9491
- }
9492
- const dialog = ctx.getDialog();
9493
- if (!dialog) {
9494
- respondJson(res, { ok: false, error: { message: "No active browser dialog", code: "no_active_dialog" } }, 409);
9495
- return;
9496
- }
9497
- if (payload.promptText != null && dialog.type !== "prompt") {
9498
- respondJson(res, { ok: false, error: { message: "Prompt text can only be sent to prompt dialogs", code: "dialog_not_prompt" } }, 409);
9499
- return;
9500
- }
9501
- try {
9480
+ var handle41 = defineJsonRoute({
9481
+ method: "POST",
9482
+ path: "/dialog",
9483
+ parseBody: true,
9484
+ endpoint: "dialog/handle",
9485
+ handle: async ({ res, ctx, body: payload }) => {
9486
+ const action = payload.action;
9487
+ if (action !== "accept" && action !== "dismiss") {
9488
+ respondInvalidBody(res, 'Dialog action must be "accept" or "dismiss"');
9489
+ return;
9490
+ }
9491
+ const dialog = ctx.getDialog();
9492
+ if (!dialog) {
9493
+ respondJson(res, { ok: false, error: { message: "No active browser dialog", code: "no_active_dialog" } }, 409);
9494
+ return;
9495
+ }
9496
+ if (payload.promptText != null && dialog.type !== "prompt") {
9497
+ respondJson(res, {
9498
+ ok: false,
9499
+ error: { message: "Prompt text can only be sent to prompt dialogs", code: "dialog_not_prompt" }
9500
+ }, 409);
9501
+ return;
9502
+ }
9502
9503
  await ctx.pageCdpSession.sendAndWait("Page.handleJavaScriptDialog", {
9503
9504
  accept: action === "accept",
9504
9505
  promptText: payload.promptText
@@ -9508,36 +9509,30 @@ var handle41 = async (req, res, _url, ctx) => {
9508
9509
  action,
9509
9510
  dialog
9510
9511
  };
9511
- respondJson(res, response);
9512
- } catch (error) {
9513
- respondError(res, error);
9512
+ return response;
9514
9513
  }
9515
- };
9514
+ }).handler;
9516
9515
 
9517
9516
  // ../argus-watcher/dist/http/routes/postVisibility.js
9518
- var handle42 = async (req, res, _url, ctx) => {
9519
- const payload = await readJsonBody(req, res);
9520
- if (!payload) {
9521
- return;
9522
- }
9523
- emitRequest(ctx, res, "visibility");
9524
- const action = payload.action;
9525
- if (action !== "show" && action !== "hide") {
9526
- respondInvalidBody(res, 'Visibility action must be "show" or "hide"');
9527
- return;
9528
- }
9529
- const lock = action === "show" ? "shown" : "default";
9530
- const session = ctx.pageCdpSession;
9531
- const attached = session.isAttached();
9532
- try {
9517
+ var handle42 = defineJsonRoute({
9518
+ method: "POST",
9519
+ path: "/visibility",
9520
+ parseBody: true,
9521
+ handle: async ({ res, ctx, body: payload }) => {
9522
+ const action = payload.action;
9523
+ if (action !== "show" && action !== "hide") {
9524
+ respondInvalidBody(res, 'Visibility action must be "show" or "hide"');
9525
+ return;
9526
+ }
9527
+ emitRequest(ctx, res, "visibility");
9528
+ const lock = action === "show" ? "shown" : "default";
9529
+ const session = ctx.pageCdpSession;
9530
+ const attached = session.isAttached();
9533
9531
  await ctx.visibilityController.setLock(attached ? session : null, lock);
9534
- } catch (error) {
9535
- respondError(res, error);
9536
- return;
9532
+ const response = { ok: true, attached, state: lock };
9533
+ return response;
9537
9534
  }
9538
- const response = { ok: true, attached, state: lock };
9539
- respondJson(res, response);
9540
- };
9535
+ }).handler;
9541
9536
 
9542
9537
  // ../argus-watcher/dist/cdp/storage.js
9543
9538
  var storageAccessors = {
@@ -9652,22 +9647,18 @@ var handle43 = createStorageHandler("local");
9652
9647
  var handle44 = createStorageHandler("session");
9653
9648
 
9654
9649
  // ../argus-watcher/dist/http/routes/postReload.js
9655
- var handle45 = async (req, res, _url, ctx) => {
9656
- const payload = await readJsonBody(req, res);
9657
- if (!payload) {
9658
- return;
9659
- }
9660
- emitRequest(ctx, res, "reload");
9661
- try {
9650
+ var handle45 = defineJsonRoute({
9651
+ method: "POST",
9652
+ path: "/reload",
9653
+ parseBody: true,
9654
+ endpoint: "reload",
9655
+ handle: async ({ ctx, body: payload }) => {
9662
9656
  await ctx.pageCdpSession.sendAndWait("Page.reload", {
9663
9657
  ignoreCache: payload.ignoreCache ?? false
9664
9658
  });
9665
- const response = { ok: true };
9666
- respondJson(res, response);
9667
- } catch (error) {
9668
- respondError(res, error);
9659
+ return { ok: true };
9669
9660
  }
9670
- };
9661
+ }).handler;
9671
9662
 
9672
9663
  // ../argus-watcher/dist/http/routes/postShutdown.js
9673
9664
  var handle46 = (_req, res, _url, ctx) => {
@@ -11316,6 +11307,7 @@ var createVisibilityController = () => {
11316
11307
  // ../argus-watcher/dist/runtime/watcherSetup.js
11317
11308
  var normalizeWatcherSetup = (options, watcherId) => {
11318
11309
  const sourceMode = options.source ?? "cdp";
11310
+ const extensionRole = options.extensionRole ?? "tab";
11319
11311
  const host = options.host ?? "127.0.0.1";
11320
11312
  const port = options.port ?? 0;
11321
11313
  const chrome = options.chrome ?? { host: "127.0.0.1", port: 9222 };
@@ -11359,6 +11351,7 @@ var normalizeWatcherSetup = (options, watcherId) => {
11359
11351
  };
11360
11352
  return {
11361
11353
  sourceMode,
11354
+ extensionRole,
11362
11355
  host,
11363
11356
  port,
11364
11357
  chrome,
@@ -14064,6 +14057,57 @@ var createNativeMessaging = () => {
14064
14057
  };
14065
14058
  };
14066
14059
 
14060
+ // ../argus-watcher/dist/native-messaging/control-session-manager.js
14061
+ class ControlSessionManager {
14062
+ messaging;
14063
+ pendingTabsRequest = null;
14064
+ constructor(messaging) {
14065
+ this.messaging = messaging;
14066
+ this.messaging.onMessage((message) => {
14067
+ this.handleMessage(message);
14068
+ });
14069
+ }
14070
+ attachTabWatcher(tabId) {
14071
+ this.messaging.send({
14072
+ type: "attach_tab_watcher",
14073
+ tabId
14074
+ });
14075
+ }
14076
+ detachTabWatcher(tabId) {
14077
+ this.messaging.send({
14078
+ type: "detach_tab_watcher",
14079
+ tabId
14080
+ });
14081
+ }
14082
+ async listTabs(filter) {
14083
+ return new Promise((resolve2, reject) => {
14084
+ this.pendingTabsRequest = { resolve: resolve2, reject };
14085
+ this.messaging.send({
14086
+ type: "list_tabs",
14087
+ filter
14088
+ });
14089
+ setTimeout(() => {
14090
+ if (!this.pendingTabsRequest) {
14091
+ return;
14092
+ }
14093
+ this.pendingTabsRequest.reject(new Error("List tabs request timed out"));
14094
+ this.pendingTabsRequest = null;
14095
+ }, 5000);
14096
+ });
14097
+ }
14098
+ handleMessage(message) {
14099
+ switch (message.type) {
14100
+ case "list_tabs_response":
14101
+ this.handleListTabsResponse(message);
14102
+ return;
14103
+ }
14104
+ }
14105
+ handleListTabsResponse(message) {
14106
+ this.pendingTabsRequest?.resolve(message.tabs);
14107
+ this.pendingTabsRequest = null;
14108
+ }
14109
+ }
14110
+
14067
14111
  // ../argus-watcher/dist/native-messaging/session-manager.js
14068
14112
  var nextRequestId = 1;
14069
14113
 
@@ -14073,7 +14117,6 @@ class SessionManager {
14073
14117
  pendingRequests = new Map;
14074
14118
  eventHandlers = new Map;
14075
14119
  events;
14076
- pendingTabsRequest = null;
14077
14120
  constructor(messaging, events) {
14078
14121
  this.messaging = messaging;
14079
14122
  this.events = events;
@@ -14101,9 +14144,6 @@ class SessionManager {
14101
14144
  case "cookie_query_response":
14102
14145
  this.handleCookieQueryResponse(message);
14103
14146
  break;
14104
- case "list_tabs_response":
14105
- this.handleListTabsResponse(message);
14106
- break;
14107
14147
  case "target_selected":
14108
14148
  this.events.onTargetSelected(message.tabId, message.frameId ?? null);
14109
14149
  break;
@@ -14156,13 +14196,6 @@ class SessionManager {
14156
14196
  pending.resolve(result);
14157
14197
  }
14158
14198
  }
14159
- handleListTabsResponse(message) {
14160
- if (this.pendingTabsRequest) {
14161
- this.pendingTabsRequest.resolve(message.tabs);
14162
- this.pendingTabsRequest = null;
14163
- }
14164
- this.events.onTabsUpdated(message.tabs);
14165
- }
14166
14199
  createSession(message) {
14167
14200
  const { tabId, url, title, faviconUrl } = message;
14168
14201
  const enabledDomains = new Set;
@@ -14243,22 +14276,6 @@ class SessionManager {
14243
14276
  };
14244
14277
  this.messaging.send(message);
14245
14278
  }
14246
- async listTabs(filter) {
14247
- return new Promise((resolve2, reject) => {
14248
- this.pendingTabsRequest = { resolve: resolve2, reject };
14249
- const message = {
14250
- type: "list_tabs",
14251
- filter
14252
- };
14253
- this.messaging.send(message);
14254
- setTimeout(() => {
14255
- if (this.pendingTabsRequest) {
14256
- this.pendingTabsRequest.reject(new Error("List tabs request timed out"));
14257
- this.pendingTabsRequest = null;
14258
- }
14259
- }, 5000);
14260
- });
14261
- }
14262
14279
  getSession(tabId) {
14263
14280
  return this.sessions.get(tabId);
14264
14281
  }
@@ -14919,7 +14936,87 @@ var TARGET_RECOVERY_INTERVAL_MS = 500;
14919
14936
  var TARGET_RECOVERY_TIMEOUT_MS = 30000;
14920
14937
  var FRAME_COMMAND_READY_TIMEOUT_MS = 3000;
14921
14938
  var FRAME_COMMAND_READY_POLL_MS = 100;
14939
+ var createDetachedSession = (message) => ({
14940
+ isAttached: () => false,
14941
+ sendAndWait: async () => {
14942
+ const error = new Error(message);
14943
+ error.code = "cdp_not_attached";
14944
+ throw error;
14945
+ },
14946
+ onEvent: () => () => {}
14947
+ });
14948
+ var createControlExtensionSource = (options) => {
14949
+ const { events, watcherId, watcherHost, watcherPort } = options;
14950
+ const messaging = createNativeMessaging();
14951
+ const controlSession = new ControlSessionManager(messaging);
14952
+ const hostInfo = {
14953
+ watcherId: watcherId ?? "extension-control",
14954
+ watcherHost: watcherHost ?? "127.0.0.1",
14955
+ watcherPort: watcherPort ?? 0,
14956
+ watcherPid: process.pid
14957
+ };
14958
+ let stopping = false;
14959
+ messaging.start();
14960
+ messaging.send({ type: "host_ready" });
14961
+ sendHostInfo();
14962
+ events.onStatus({
14963
+ attached: false,
14964
+ target: null,
14965
+ reason: "control_watcher"
14966
+ });
14967
+ messaging.onDisconnect(() => {
14968
+ console.error("[ExtensionControlSource] Extension disconnected");
14969
+ if (!stopping) {
14970
+ events.onStatus({ attached: false, target: null, reason: "extension_disconnected" });
14971
+ events.onDetach?.("extension_disconnected");
14972
+ }
14973
+ });
14974
+ return {
14975
+ session: createDetachedSession("Extension control watcher does not expose a CDP tab session"),
14976
+ syncWatcherInfo: (info) => {
14977
+ hostInfo.watcherId = info.watcherId;
14978
+ hostInfo.watcherHost = info.watcherHost;
14979
+ hostInfo.watcherPort = info.watcherPort;
14980
+ hostInfo.watcherPid = info.watcherPid;
14981
+ sendHostInfo();
14982
+ },
14983
+ stop: async () => {
14984
+ stopping = true;
14985
+ messaging.stop();
14986
+ },
14987
+ listTargets: async () => [],
14988
+ listTabs: async (filter) => await controlSession.listTabs(filter),
14989
+ attachTarget: (targetId) => {
14990
+ controlSession.attachTabWatcher(parseControlTabTarget(targetId, "attach"));
14991
+ },
14992
+ detachTarget: (targetId) => {
14993
+ controlSession.detachTabWatcher(parseControlTabTarget(targetId, "detach"));
14994
+ }
14995
+ };
14996
+ function sendHostInfo() {
14997
+ messaging.send({
14998
+ type: "host_info",
14999
+ watcherId: hostInfo.watcherId,
15000
+ watcherHost: hostInfo.watcherHost,
15001
+ watcherPort: hostInfo.watcherPort,
15002
+ pid: hostInfo.watcherPid
15003
+ });
15004
+ }
15005
+ };
15006
+ var parseControlTabTarget = (targetId, action) => {
15007
+ const target = parseExtensionTargetId(targetId);
15008
+ if (!target.frameId) {
15009
+ return target.tabId;
15010
+ }
15011
+ if (action === "attach") {
15012
+ throw new Error(`Cannot attach iframe ${target.frameId} from extension-control before tab ${target.tabId} has a tab watcher`);
15013
+ }
15014
+ throw new Error(`Cannot detach iframe ${target.frameId} from extension-control. Detach tab ${target.tabId} instead.`);
15015
+ };
14922
15016
  var createExtensionSource = (options) => {
15017
+ if (options.role === "control") {
15018
+ return createControlExtensionSource(options);
15019
+ }
14923
15020
  const { events, ignoreMatcher, stripUrlPrefixes: stripUrlPrefixes2, watcherId, watcherHost, watcherPort } = options;
14924
15021
  const messaging = createNativeMessaging();
14925
15022
  const hostInfo = {
@@ -14958,7 +15055,6 @@ var createExtensionSource = (options) => {
14958
15055
  emitStatus(null, reason);
14959
15056
  events.onDetach?.(reason);
14960
15057
  },
14961
- onTabsUpdated: () => {},
14962
15058
  onTargetSelected: (tabId, frameId) => {
14963
15059
  const session = currentSession;
14964
15060
  if (!session || session.tabId !== tabId) {
@@ -14998,6 +15094,7 @@ var createExtensionSource = (options) => {
14998
15094
  });
14999
15095
  delegatingSessions.add(pageController);
15000
15096
  messaging.start();
15097
+ messaging.send({ type: "host_ready" });
15001
15098
  sendHostInfo();
15002
15099
  messaging.onDisconnect(() => {
15003
15100
  console.error("[ExtensionSource] Extension disconnected");
@@ -15030,13 +15127,26 @@ var createExtensionSource = (options) => {
15030
15127
  };
15031
15128
  const attachTarget = (targetId) => {
15032
15129
  const target = parseExtensionTargetId(targetId);
15033
- const session = requireOwnedSession(target.tabId);
15130
+ const session = currentSession;
15131
+ if (!session) {
15132
+ if (target.frameId) {
15133
+ throw new Error(`Cannot attach iframe ${target.frameId} before tab ${target.tabId} is attached`);
15134
+ }
15135
+ throw new Error(`Watcher ${hostInfo.watcherId} is a tab watcher. Use extension-control to attach tab ${target.tabId}.`);
15136
+ }
15137
+ if (session.tabId !== target.tabId) {
15138
+ throw new Error(`Watcher ${hostInfo.watcherId} is pinned to tab ${session.tabId}, not tab ${target.tabId}`);
15139
+ }
15034
15140
  requestTargetSelection(session, target.frameId);
15035
15141
  };
15036
15142
  const detachTarget = (targetId) => {
15037
15143
  const target = parseExtensionTargetId(targetId);
15038
- const session = requireOwnedSession(target.tabId);
15039
- sessionManager.detachTab(session.tabId);
15144
+ const session = currentSession;
15145
+ if (!session || session.tabId === target.tabId) {
15146
+ sessionManager.detachTab(target.tabId);
15147
+ return;
15148
+ }
15149
+ throw new Error(`Watcher ${hostInfo.watcherId} is pinned to tab ${session.tabId}, not tab ${target.tabId}`);
15040
15150
  };
15041
15151
  return {
15042
15152
  session: proxySession,
@@ -15080,9 +15190,6 @@ var createExtensionSource = (options) => {
15080
15190
  },
15081
15191
  stop,
15082
15192
  listTargets,
15083
- listTabs: async (filter) => {
15084
- return await sessionManager.listTabs(filter);
15085
- },
15086
15193
  attachTarget,
15087
15194
  detachTarget
15088
15195
  };
@@ -15095,16 +15202,6 @@ var createExtensionSource = (options) => {
15095
15202
  pid: hostInfo.watcherPid
15096
15203
  });
15097
15204
  }
15098
- function requireOwnedSession(tabId) {
15099
- const session = currentSession;
15100
- if (!session) {
15101
- throw createNotAttachedError();
15102
- }
15103
- if (session.tabId !== tabId) {
15104
- throw new Error(`Watcher ${hostInfo.watcherId} is pinned to tab ${session.tabId}, not tab ${tabId}`);
15105
- }
15106
- return session;
15107
- }
15108
15205
  function rebindDelegatingSessions() {
15109
15206
  for (const controller of delegatingSessions) {
15110
15207
  controller.rebind();
@@ -15207,13 +15304,15 @@ var createExtensionSource = (options) => {
15207
15304
  if (state2.activeAttachedAt == null) {
15208
15305
  state2.activeAttachedAt = Date.now();
15209
15306
  }
15307
+ const targetReady = isTargetReady(session.tabId);
15210
15308
  emitStatus(target, null);
15211
15309
  messaging.send({
15212
15310
  type: "target_info",
15213
15311
  targetId: target.id,
15214
15312
  title: target.title,
15215
15313
  url: target.url,
15216
- attachedAt: state2.activeAttachedAt
15314
+ attachedAt: state2.activeAttachedAt,
15315
+ targetReady
15217
15316
  });
15218
15317
  events.onTargetChanged?.(session.handle, target);
15219
15318
  }
@@ -15354,7 +15453,7 @@ function delay4(ms) {
15354
15453
 
15355
15454
  // ../argus-watcher/dist/runtime/watcherServices.js
15356
15455
  var createWatcherRuntimeServices = (options, setup, callbacks) => {
15357
- const { sourceMode, chrome, artifactsBaseDir, netBuffer, realtimeNetBuffer, sessionHandle, ignoreMatcher, stripUrlPrefixes: stripUrlPrefixes2, watcherId, host, record } = setup;
15456
+ const { sourceMode, chrome, artifactsBaseDir, netBuffer, realtimeNetBuffer, sessionHandle, ignoreMatcher, stripUrlPrefixes: stripUrlPrefixes2, watcherId, host, record, extensionRole } = setup;
15358
15457
  if (sourceMode === "extension") {
15359
15458
  const sourceHandle2 = createExtensionSource({
15360
15459
  events: {
@@ -15372,6 +15471,7 @@ var createWatcherRuntimeServices = (options, setup, callbacks) => {
15372
15471
  watcherId,
15373
15472
  watcherHost: host,
15374
15473
  watcherPort: record.port,
15474
+ role: extensionRole,
15375
15475
  ignoreMatcher: ignoreMatcher ? (url) => ignoreMatcher.matches(url) : null,
15376
15476
  stripUrlPrefixes: stripUrlPrefixes2
15377
15477
  });
@@ -16085,7 +16185,7 @@ var runReload = defineWatcherCommand({
16085
16185
  import fs8 from "node:fs";
16086
16186
  import path10 from "node:path";
16087
16187
  var AUTO_CONFIG_CANDIDATES = [".argus/config.json", ".config/argus.json", "argus.config.json", "argus/config.json"];
16088
- var EXPECTED_SHAPE_HINT = 'Expected shape: { chrome?: { start?: { url?: string, watcherId?: string, profile?: "temp"|"default-full"|"default-medium"|"default-lite", devTools?: boolean, headless?: boolean } }, watcher?: { start?: { id?: string, url?: string, chromeHost?: string, chromePort?: number, artifacts?: string, pageIndicator?: boolean, pageConsoleLogging?: "none"|"minimal"|"full", inject?: { file: string, exposeArgus?: boolean } } } }.';
16188
+ var EXPECTED_SHAPE_HINT = 'Expected shape: { plugins?: string[], pluginAliases?: Record<string, string>, chrome?: { start?: { url?: string, watcherId?: string, profile?: "temp"|"default-full"|"default-medium"|"default-lite", devTools?: boolean, headless?: boolean } }, watcher?: { start?: { id?: string, url?: string, chromeHost?: string, chromePort?: number, artifacts?: string, pageIndicator?: boolean, pageConsoleLogging?: "none"|"minimal"|"full", inject?: { file: string, exposeArgus?: boolean } } } }.';
16089
16189
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16090
16190
  var invalidConfig = (configPath, message) => {
16091
16191
  console.error(`Invalid Argus config at ${configPath}: ${message} ${EXPECTED_SHAPE_HINT}`);
@@ -16159,6 +16259,25 @@ var validateOptionalStringArray = (value, label) => {
16159
16259
  }
16160
16260
  return { ok: true, value };
16161
16261
  };
16262
+ var validateOptionalStringMap = (value, label) => {
16263
+ if (value === undefined) {
16264
+ return { ok: true };
16265
+ }
16266
+ if (!isRecord(value)) {
16267
+ return { ok: false, error: `${label} must be an object with string values.` };
16268
+ }
16269
+ const entries = [];
16270
+ for (const [key, item] of Object.entries(value)) {
16271
+ if (key.trim() === "") {
16272
+ return { ok: false, error: `${label} keys must be non-empty strings.` };
16273
+ }
16274
+ if (typeof item !== "string" || item.trim() === "") {
16275
+ return { ok: false, error: `${label}.${key} must be a non-empty string.` };
16276
+ }
16277
+ entries.push([key, item]);
16278
+ }
16279
+ return { ok: true, value: Object.fromEntries(entries) };
16280
+ };
16162
16281
  var validateOptionalSection = (value, label, validate) => {
16163
16282
  if (value === undefined) {
16164
16283
  return { ok: true };
@@ -16290,6 +16409,10 @@ var validateArgusConfig = (value) => {
16290
16409
  if (!pluginsResult.ok) {
16291
16410
  return pluginsResult;
16292
16411
  }
16412
+ const pluginAliasesResult = validateOptionalStringMap(value.pluginAliases, '"pluginAliases"');
16413
+ if (!pluginAliasesResult.ok) {
16414
+ return pluginAliasesResult;
16415
+ }
16293
16416
  const chromeResult = validateOptionalSection(value.chrome, '"chrome"', (section) => {
16294
16417
  if (section.start === undefined) {
16295
16418
  return { ok: true, value: {} };
@@ -16316,7 +16439,15 @@ var validateArgusConfig = (value) => {
16316
16439
  if (!watcherResult.ok) {
16317
16440
  return watcherResult;
16318
16441
  }
16319
- return { ok: true, value: { chrome: chromeResult.value, watcher: watcherResult.value, plugins: pluginsResult.value } };
16442
+ return {
16443
+ ok: true,
16444
+ value: {
16445
+ chrome: chromeResult.value,
16446
+ watcher: watcherResult.value,
16447
+ plugins: pluginsResult.value,
16448
+ pluginAliases: pluginAliasesResult.value
16449
+ }
16450
+ };
16320
16451
  };
16321
16452
  var resolveArgusConfigPath = ({ cliPath, cwd }) => {
16322
16453
  if (cliPath) {
@@ -16767,25 +16898,13 @@ var runWatcherStart = async (options) => {
16767
16898
  var generateWatcherId = () => crypto2.randomBytes(3).toString("hex");
16768
16899
 
16769
16900
  // dist/commands/watcherStatus.js
16770
- var runWatcherStatus = async (id, options) => {
16771
- const output = createOutput(options);
16772
- const result = await requestWatcherJson({
16773
- id,
16774
- path: "/status",
16775
- timeoutMs: 2000
16776
- });
16777
- if (!result.ok) {
16778
- writeRequestError(result, output);
16779
- return;
16780
- }
16781
- if (options.json) {
16782
- output.writeJson(result.data);
16783
- return;
16901
+ var runWatcherStatus = defineWatcherCommand({
16902
+ build: () => ({ path: "/status", timeoutMs: 2000 }),
16903
+ formatHuman: (status, { output, watcher }) => {
16904
+ const readinessSuffix = status.targetReady === false ? " targetReady=false" : "";
16905
+ output.writeHuman(`ok ${watcher.id} ${watcher.host}:${watcher.port} pid=${status.pid} attached=${status.attached}${readinessSuffix}`);
16784
16906
  }
16785
- const { watcher, data: status } = result;
16786
- const readinessSuffix = status.targetReady === false ? " targetReady=false" : "";
16787
- output.writeHuman(`ok ${watcher.id} ${watcher.host}:${watcher.port} pid=${status.pid} attached=${status.attached}${readinessSuffix}`);
16788
- };
16907
+ });
16789
16908
 
16790
16909
  // dist/commands/watcherStop.js
16791
16910
  var runWatcherStop = async (id, _options) => {
@@ -16943,21 +17062,27 @@ var checkWatcherReachable = async (watcher) => {
16943
17062
  // dist/commands/watcherNativeHost.js
16944
17063
  var runWatcherNativeHost = async (options) => {
16945
17064
  const watcherId = options.id?.trim() || "extension";
17065
+ const role = options.role?.trim() || "tab";
17066
+ if (role !== "tab" && role !== "control") {
17067
+ console.error(`Invalid native host role: ${role}. Expected "tab" or "control".`);
17068
+ process.exit(2);
17069
+ }
16946
17070
  let handle51;
16947
17071
  try {
16948
17072
  handle51 = await startWatcher({
16949
17073
  id: watcherId,
16950
17074
  source: "extension",
17075
+ extensionRole: role,
16951
17076
  host: "127.0.0.1",
16952
17077
  port: 0,
16953
17078
  net: { enabled: true },
16954
- pageIndicator: { enabled: true }
17079
+ pageIndicator: { enabled: role === "tab" }
16955
17080
  });
16956
17081
  } catch (error) {
16957
17082
  console.error(`Failed to start watcher: ${error instanceof Error ? error.message : error}`);
16958
17083
  process.exit(1);
16959
17084
  }
16960
- console.error(`[NativeHost] Watcher started: id=${handle51.watcher.id} port=${handle51.watcher.port}`);
17085
+ console.error(`[NativeHost] Watcher started: id=${handle51.watcher.id} role=${role} port=${handle51.watcher.port}`);
16961
17086
  const cleanup = async () => {
16962
17087
  try {
16963
17088
  await handle51.close();
@@ -17080,7 +17205,7 @@ Examples:
17080
17205
  `).action(async (options) => {
17081
17206
  await runWatcherPrune(options);
17082
17207
  });
17083
- watcher.command("native-host").description("[internal] Start as Native Messaging host for Chrome extension").option("--id <watcherId>", "Watcher id (default: extension)").option("--json", "Output JSON for automation").action(async (options) => {
17208
+ watcher.command("native-host").description("[internal] Start as Native Messaging host for Chrome extension").option("--id <watcherId>", "Watcher id (default: extension)").option("--role <role>", "Native host role: tab or control (default: tab)").option("--json", "Output JSON for automation").action(async (options) => {
17084
17209
  await runWatcherNativeHost(options);
17085
17210
  });
17086
17211
  }
@@ -17361,16 +17486,38 @@ var listPresetNames = () => {
17361
17486
  };
17362
17487
 
17363
17488
  // dist/commands/pageEmulation.js
17364
- var runPageEmulationSet = async (id, options) => {
17365
- const output = createOutput(options);
17489
+ var runPageEmulationSet = defineWatcherCommand({
17490
+ build: (_args, options, output) => buildPageEmulationSetPlan(options, output),
17491
+ formatHuman: (res, { output, options }) => {
17492
+ const state2 = buildEmulationState(options);
17493
+ const label = formatViewportLabel(state2.viewport, state2.touch.enabled, state2.userAgent.value);
17494
+ if (res.applied) {
17495
+ output.writeHuman(`Applied emulation: ${label}`);
17496
+ } else if (!res.attached) {
17497
+ output.writeHuman(`Queued emulation (watcher detached): ${label}`);
17498
+ } else {
17499
+ output.writeHuman(`Emulation set but not applied: ${label}`);
17500
+ if (res.error) {
17501
+ output.writeWarn(`Error: ${res.error.message}`);
17502
+ }
17503
+ }
17504
+ }
17505
+ });
17506
+ var buildPageEmulationSetPlan = (options, output) => {
17507
+ const state2 = buildEmulationState(options, output);
17508
+ if (!state2)
17509
+ return null;
17510
+ return { path: "/emulation", method: "POST", body: { action: "set", state: state2 }, timeoutMs: 1e4 };
17511
+ };
17512
+ var buildEmulationState = (options, output) => {
17366
17513
  let base = null;
17367
17514
  if (options.device) {
17368
17515
  const preset = resolvePreset(options.device);
17369
17516
  if (!preset) {
17370
- output.writeWarn(`Unknown device: ${options.device}`);
17371
- output.writeWarn(`Available: ${listPresetNames().join(", ")}`);
17517
+ output?.writeWarn(`Unknown device: ${options.device}`);
17518
+ output?.writeWarn(`Available: ${listPresetNames().join(", ")}`);
17372
17519
  process.exitCode = 2;
17373
- return;
17520
+ return null;
17374
17521
  }
17375
17522
  base = { viewport: preset.viewport, touch: preset.touch, userAgent: preset.userAgent };
17376
17523
  }
@@ -17378,34 +17525,34 @@ var runPageEmulationSet = async (id, options) => {
17378
17525
  const heightRaw = options.height != null ? Number(options.height) : null;
17379
17526
  const dprRaw = options.dpr != null ? Number(options.dpr) : null;
17380
17527
  if (widthRaw != null && (!Number.isInteger(widthRaw) || widthRaw <= 0)) {
17381
- output.writeWarn("--width must be a positive integer");
17528
+ output?.writeWarn("--width must be a positive integer");
17382
17529
  process.exitCode = 2;
17383
- return;
17530
+ return null;
17384
17531
  }
17385
17532
  if (heightRaw != null && (!Number.isInteger(heightRaw) || heightRaw <= 0)) {
17386
- output.writeWarn("--height must be a positive integer");
17533
+ output?.writeWarn("--height must be a positive integer");
17387
17534
  process.exitCode = 2;
17388
- return;
17535
+ return null;
17389
17536
  }
17390
17537
  if (dprRaw != null && (!Number.isFinite(dprRaw) || dprRaw <= 0)) {
17391
- output.writeWarn("--dpr must be a positive number");
17538
+ output?.writeWarn("--dpr must be a positive number");
17392
17539
  process.exitCode = 2;
17393
- return;
17540
+ return null;
17394
17541
  }
17395
17542
  const hasWidth = widthRaw != null;
17396
17543
  const hasHeight = heightRaw != null;
17397
17544
  if ((hasWidth || hasHeight) && !base) {
17398
17545
  if (!hasWidth || !hasHeight) {
17399
- output.writeWarn("Both --width and --height are required when not using --device");
17546
+ output?.writeWarn("Both --width and --height are required when not using --device");
17400
17547
  process.exitCode = 2;
17401
- return;
17548
+ return null;
17402
17549
  }
17403
17550
  }
17404
17551
  if (!base && !hasWidth) {
17405
- output.writeWarn("Provide --device or --width + --height");
17406
- output.writeWarn(`Available devices: ${listPresetNames().join(", ")}`);
17552
+ output?.writeWarn("Provide --device or --width + --height");
17553
+ output?.writeWarn(`Available devices: ${listPresetNames().join(", ")}`);
17407
17554
  process.exitCode = 2;
17408
- return;
17555
+ return null;
17409
17556
  }
17410
17557
  const baseViewport = base?.viewport ?? null;
17411
17558
  const width = widthRaw ?? baseViewport?.width ?? 0;
@@ -17415,137 +17562,48 @@ var runPageEmulationSet = async (id, options) => {
17415
17562
  const viewport = { width, height, deviceScaleFactor: dpr, mobile };
17416
17563
  const touchEnabled = options.touch ?? base?.touch ?? false;
17417
17564
  const uaValue = options.ua !== undefined ? options.ua : base?.userAgent ?? null;
17418
- const state2 = {
17565
+ return {
17419
17566
  viewport,
17420
17567
  touch: { enabled: touchEnabled },
17421
17568
  userAgent: { value: uaValue }
17422
17569
  };
17423
- const result = await requestWatcherJson({
17424
- id,
17425
- path: "/emulation",
17426
- method: "POST",
17427
- body: { action: "set", state: state2 },
17428
- timeoutMs: 1e4,
17429
- returnErrorResponse: true
17430
- });
17431
- if (!result.ok) {
17432
- writeRequestError(result, output);
17433
- return;
17434
- }
17435
- const response = result.data;
17436
- if (!response.ok) {
17437
- const err = response;
17438
- if (options.json) {
17439
- output.writeJson(response);
17440
- } else {
17441
- output.writeWarn(`Error: ${err.error.message}`);
17442
- }
17443
- process.exitCode = 1;
17444
- return;
17445
- }
17446
- if (options.json) {
17447
- output.writeJson(response);
17448
- return;
17449
- }
17450
- const res = response;
17451
- const label = formatViewportLabel(viewport, touchEnabled, uaValue);
17452
- if (res.applied) {
17453
- output.writeHuman(`Applied emulation: ${label}`);
17454
- } else if (!res.attached) {
17455
- output.writeHuman(`Queued emulation (watcher detached): ${label}`);
17456
- } else {
17457
- output.writeHuman(`Emulation set but not applied: ${label}`);
17458
- if (res.error) {
17459
- output.writeWarn(`Error: ${res.error.message}`);
17460
- }
17461
- }
17462
17570
  };
17463
- var runPageEmulationClear = async (id, options) => {
17464
- const output = createOutput(options);
17465
- const result = await requestWatcherJson({
17466
- id,
17467
- path: "/emulation",
17468
- method: "POST",
17469
- body: { action: "clear" },
17470
- timeoutMs: 1e4,
17471
- returnErrorResponse: true
17472
- });
17473
- if (!result.ok) {
17474
- writeRequestError(result, output);
17475
- return;
17571
+ var runPageEmulationClear = defineWatcherCommand({
17572
+ build: () => ({ path: "/emulation", method: "POST", body: { action: "clear" }, timeoutMs: 1e4 }),
17573
+ formatHuman: (_response, { output }) => {
17574
+ output.writeHuman("Cleared emulation");
17476
17575
  }
17477
- const response = result.data;
17478
- if (!response.ok) {
17479
- const err = response;
17480
- if (options.json) {
17481
- output.writeJson(response);
17576
+ });
17577
+ var runPageEmulationStatus = defineWatcherCommand({
17578
+ build: () => ({ path: "/emulation", method: "GET", timeoutMs: 5000 }),
17579
+ formatHuman: (res, { output }) => {
17580
+ const lines = [];
17581
+ lines.push(`attached: ${res.attached}`);
17582
+ lines.push(`applied: ${res.applied}`);
17583
+ if (res.state?.viewport) {
17584
+ const vp = res.state.viewport;
17585
+ lines.push(`viewport: ${vp.width}x${vp.height}@${vp.deviceScaleFactor} ${vp.mobile ? "mobile" : "desktop"}`);
17482
17586
  } else {
17483
- output.writeWarn(`Error: ${err.error.message}`);
17587
+ lines.push("viewport: none");
17484
17588
  }
17485
- process.exitCode = 1;
17486
- return;
17487
- }
17488
- if (options.json) {
17489
- output.writeJson(response);
17490
- return;
17491
- }
17492
- output.writeHuman("Cleared emulation");
17493
- };
17494
- var runPageEmulationStatus = async (id, options) => {
17495
- const output = createOutput(options);
17496
- const result = await requestWatcherJson({
17497
- id,
17498
- path: "/emulation",
17499
- method: "GET",
17500
- timeoutMs: 5000,
17501
- returnErrorResponse: true
17502
- });
17503
- if (!result.ok) {
17504
- writeRequestError(result, output);
17505
- return;
17506
- }
17507
- const response = result.data;
17508
- if (!response.ok) {
17509
- const err = response;
17510
- if (options.json) {
17511
- output.writeJson(response);
17589
+ lines.push(`touch: ${res.state?.touch?.enabled ? "enabled" : "disabled"}`);
17590
+ if (res.state?.userAgent?.value) {
17591
+ const ua = res.state.userAgent.value;
17592
+ lines.push(`ua: ${ua.length > 80 ? ua.slice(0, 77) + "..." : ua}`);
17512
17593
  } else {
17513
- output.writeWarn(`Error: ${err.error.message}`);
17594
+ lines.push("ua: default");
17514
17595
  }
17515
- process.exitCode = 1;
17516
- return;
17517
- }
17518
- if (options.json) {
17519
- output.writeJson(response);
17520
- return;
17521
- }
17522
- const res = response;
17523
- const lines = [];
17524
- lines.push(`attached: ${res.attached}`);
17525
- lines.push(`applied: ${res.applied}`);
17526
- if (res.state?.viewport) {
17527
- const vp = res.state.viewport;
17528
- lines.push(`viewport: ${vp.width}x${vp.height}@${vp.deviceScaleFactor} ${vp.mobile ? "mobile" : "desktop"}`);
17529
- } else {
17530
- lines.push("viewport: none");
17531
- }
17532
- lines.push(`touch: ${res.state?.touch?.enabled ? "enabled" : "disabled"}`);
17533
- if (res.state?.userAgent?.value) {
17534
- const ua = res.state.userAgent.value;
17535
- lines.push(`ua: ${ua.length > 80 ? ua.slice(0, 77) + "..." : ua}`);
17536
- } else {
17537
- lines.push("ua: default");
17538
- }
17539
- if (res.baseline.userAgent) {
17540
- const bua = res.baseline.userAgent;
17541
- lines.push(`baseline: ${bua.length > 80 ? bua.slice(0, 77) + "..." : bua}`);
17542
- }
17543
- if (res.lastError) {
17544
- lines.push(`error: ${res.lastError.message}`);
17545
- }
17546
- output.writeHuman(lines.join(`
17596
+ if (res.baseline.userAgent) {
17597
+ const bua = res.baseline.userAgent;
17598
+ lines.push(`baseline: ${bua.length > 80 ? bua.slice(0, 77) + "..." : bua}`);
17599
+ }
17600
+ if (res.lastError) {
17601
+ lines.push(`error: ${res.lastError.message}`);
17602
+ }
17603
+ output.writeHuman(lines.join(`
17547
17604
  `));
17548
- };
17605
+ }
17606
+ });
17549
17607
  var formatViewportLabel = (viewport, touch, ua) => {
17550
17608
  const parts = [`${viewport.width}x${viewport.height}@${viewport.deviceScaleFactor}`];
17551
17609
  if (viewport.mobile)
@@ -17915,46 +17973,38 @@ var appendRepeatedParams = (params, key, values) => {
17915
17973
  };
17916
17974
 
17917
17975
  // dist/commands/logs.js
17918
- var runLogs = async (id, options) => {
17919
- const output = createOutput(options);
17976
+ var runLogs = defineWatcherCommand({
17977
+ isJson: (options) => options.jsonFull === true,
17978
+ build: (_args, options, output) => buildLogsPlan(options, output),
17979
+ formatJson: (response, { options }) => options.jsonFull ? response.events : response.events.map((event) => previewLogEvent(event)),
17980
+ formatHuman: (response, { output, watcher }) => {
17981
+ for (const event of response.events) {
17982
+ output.writeHuman(formatLogEvent(event, {
17983
+ includeTimestamps: watcher.includeTimestamps
17984
+ }));
17985
+ }
17986
+ }
17987
+ });
17988
+ var buildLogsPlan = (options, output) => {
17920
17989
  const params = new URLSearchParams;
17921
17990
  appendAfterLimitParams(params, options);
17922
17991
  const filters = appendLogFilterParams(params, options);
17923
17992
  if (filters.error) {
17924
17993
  output.writeWarn(filters.error);
17925
17994
  process.exitCode = 2;
17926
- return;
17995
+ return null;
17927
17996
  }
17928
17997
  const since = appendSinceParam(params, options.since);
17929
17998
  if (since.error) {
17930
17999
  output.writeWarn(since.error);
17931
18000
  process.exitCode = 2;
17932
- return;
18001
+ return null;
17933
18002
  }
17934
- const result = await requestWatcherJson({
17935
- id,
18003
+ return {
17936
18004
  path: "/logs",
17937
18005
  query: params,
17938
18006
  timeoutMs: 5000
17939
- });
17940
- if (!result.ok) {
17941
- writeRequestError(result, output);
17942
- return;
17943
- }
17944
- if (options.jsonFull) {
17945
- output.writeJson(result.data.events);
17946
- return;
17947
- }
17948
- if (options.json) {
17949
- const previewEvents = result.data.events.map((event) => previewLogEvent(event));
17950
- output.writeJson(previewEvents);
17951
- return;
17952
- }
17953
- for (const event of result.data.events) {
17954
- output.writeHuman(formatLogEvent(event, {
17955
- includeTimestamps: result.watcher.includeTimestamps
17956
- }));
17957
- }
18007
+ };
17958
18008
  };
17959
18009
 
17960
18010
  // dist/commands/tail.js
@@ -18060,27 +18110,16 @@ Examples:
18060
18110
  }
18061
18111
 
18062
18112
  // dist/commands/netClear.js
18063
- var runNetClear = async (id, options) => {
18064
- const output = createOutput(options);
18065
- const result = await requestWatcherAction({
18066
- id,
18067
- path: "/net/clear",
18068
- method: "POST",
18069
- timeoutMs: 5000
18070
- }, output);
18071
- if (!result) {
18072
- return;
18113
+ var runNetClear = defineWatcherCommand({
18114
+ build: () => ({ path: "/net/clear", method: "POST", timeoutMs: 5000 }),
18115
+ formatHuman: (response, { output, watcher }) => {
18116
+ output.writeHuman(`cleared ${response.cleared} requests from ${watcher.id}`);
18073
18117
  }
18074
- if (options.json) {
18075
- output.writeJson(result.data);
18076
- return;
18077
- }
18078
- output.writeHuman(`cleared ${result.data.cleared} requests from ${result.watcher.id}`);
18079
- };
18118
+ });
18080
18119
 
18081
18120
  // dist/commands/netExport.js
18082
18121
  import fs9 from "node:fs/promises";
18083
- import path11 from "node:path";
18122
+ import path12 from "node:path";
18084
18123
 
18085
18124
  // dist/eval/evalClient.js
18086
18125
  var evalOnce = async (input) => {
@@ -18256,10 +18295,147 @@ var resolveDerivedNetFilters = (options) => {
18256
18295
 
18257
18296
  // dist/commands/evalShared.js
18258
18297
  import { readFile as readFile2 } from "node:fs/promises";
18298
+
18299
+ // dist/commands/evalBundle.js
18300
+ import { realpath } from "node:fs/promises";
18301
+ import path11 from "node:path";
18302
+ var esbuildModulePromise;
18303
+ var nodeModulesSegment = `${path11.sep}node_modules${path11.sep}`;
18304
+ var loadEsbuild = () => esbuildModulePromise ??= import("esbuild");
18305
+ var bundleEvalEntry = async (entryPath) => {
18306
+ let entryPoint;
18307
+ try {
18308
+ entryPoint = await realpath(path11.resolve(entryPath));
18309
+ } catch {
18310
+ return { ok: false, error: `Failed to read entry file: ${entryPath}` };
18311
+ }
18312
+ const bundleRoot = path11.dirname(entryPoint);
18313
+ try {
18314
+ const esbuild = await loadEsbuild();
18315
+ const result = await esbuild.build({
18316
+ absWorkingDir: bundleRoot,
18317
+ entryPoints: [entryPoint],
18318
+ bundle: true,
18319
+ write: false,
18320
+ splitting: false,
18321
+ format: "esm",
18322
+ platform: "browser",
18323
+ target: "es2022",
18324
+ sourcemap: false,
18325
+ logLevel: "silent",
18326
+ plugins: [createLocalFilesPlugin(bundleRoot)]
18327
+ });
18328
+ const output = result.outputFiles[0];
18329
+ if (output == null) {
18330
+ return { ok: false, error: "Bundle produced no output." };
18331
+ }
18332
+ const code = `${output.text.trimEnd()}
18333
+ //# sourceURL=argus-file://${entryPoint}
18334
+ `;
18335
+ return { ok: true, code };
18336
+ } catch (error) {
18337
+ return { ok: false, error: formatBundleFailure(error) };
18338
+ }
18339
+ };
18340
+ var createLocalFilesPlugin = (bundleRoot) => {
18341
+ const bundleRootPrefix = bundleRoot.endsWith(path11.sep) ? bundleRoot : `${bundleRoot}${path11.sep}`;
18342
+ return {
18343
+ name: "argus-eval-bundle",
18344
+ setup(build) {
18345
+ build.onResolve({ filter: /.*/ }, (args) => {
18346
+ if (args.kind === "entry-point") {
18347
+ return;
18348
+ }
18349
+ if (args.path.startsWith("node:")) {
18350
+ return bundleError(`Node built-in import ${JSON.stringify(args.path)} is not allowed in page eval.`);
18351
+ }
18352
+ if (!args.path.startsWith(".") && !path11.isAbsolute(args.path)) {
18353
+ return bundleError(`Import ${JSON.stringify(args.path)} is not allowed. Use relative local files under ${bundleRoot}.`);
18354
+ }
18355
+ return;
18356
+ });
18357
+ build.onLoad({ filter: /.*/ }, async (args) => {
18358
+ let resolvedPath;
18359
+ try {
18360
+ resolvedPath = await realpath(args.path);
18361
+ } catch {
18362
+ return bundleError(`Failed to read file: ${args.path}`);
18363
+ }
18364
+ if (resolvedPath !== bundleRoot && !resolvedPath.startsWith(bundleRootPrefix)) {
18365
+ return bundleError(`Import escapes the entry directory: ${args.path}`);
18366
+ }
18367
+ if (resolvedPath.includes(nodeModulesSegment)) {
18368
+ return bundleError(`Import from node_modules is not allowed: ${args.path}`);
18369
+ }
18370
+ return;
18371
+ });
18372
+ build.onEnd((result) => {
18373
+ if (result.errors.length > 0) {
18374
+ return;
18375
+ }
18376
+ const outputs = result.outputFiles ?? [];
18377
+ if (outputs.length !== 1) {
18378
+ return bundleError(`Bundle produced ${outputs.length} outputs; expected exactly one script.`);
18379
+ }
18380
+ if (/\bexport\b/.test(outputs[0]?.text ?? "")) {
18381
+ return bundleError("Bundled script contains top-level export statements, which page eval cannot run. Remove exports from the entry file (helpers may still export symbols for import).");
18382
+ }
18383
+ });
18384
+ }
18385
+ };
18386
+ };
18387
+ var bundleError = (text) => ({ errors: [{ text }] });
18388
+ var formatBundleFailure = (error) => {
18389
+ if (isEsbuildFailure(error)) {
18390
+ return error.errors.map(formatEsbuildMessage).join(`
18391
+ `);
18392
+ }
18393
+ return error instanceof Error ? error.message : String(error);
18394
+ };
18395
+ var isEsbuildFailure = (error) => error instanceof Error && ("errors" in error) && Array.isArray(error.errors) && error.errors.length > 0;
18396
+ var formatEsbuildMessage = (issue) => {
18397
+ const message = issue.text || "Bundle failed.";
18398
+ const { file, line, column } = issue.location ?? {};
18399
+ if (!file) {
18400
+ return message;
18401
+ }
18402
+ if (line == null || column == null) {
18403
+ return `${file}: ${message}`;
18404
+ }
18405
+ return `${file}:${line}:${column}: ${message}`;
18406
+ };
18407
+
18408
+ // dist/commands/evalShared.js
18409
+ var prepareEvalExpression = async (inline, options, output) => {
18410
+ const evalArgs = parseEvalArgs(options.arg);
18411
+ if (evalArgs.error) {
18412
+ output.writeWarn(evalArgs.error);
18413
+ return null;
18414
+ }
18415
+ const resolvedExpression = await resolveExpression(inline, options, output);
18416
+ if (resolvedExpression == null) {
18417
+ return null;
18418
+ }
18419
+ const args = evalArgs.value;
18420
+ if (!options.iframe) {
18421
+ return {
18422
+ expression: resolvedExpression,
18423
+ args: hasEvalArgs2(args) ? args : undefined
18424
+ };
18425
+ }
18426
+ return {
18427
+ expression: wrapForIframeEval(wrapExpressionWithArgs(resolvedExpression, args), {
18428
+ selector: options.iframe,
18429
+ namespace: options.iframeNamespace ?? "argus",
18430
+ timeoutMs: parseNumber(options.iframeTimeout) ?? 5000
18431
+ })
18432
+ };
18433
+ };
18259
18434
  var resolveExpression = async (inline, options, output) => {
18260
18435
  const wantsStdin = options.stdin === true || inline === "-";
18261
18436
  const hasInline = inline != null && inline !== "-";
18262
- const hasFile = options.file != null;
18437
+ const filePath = options.file;
18438
+ const hasFile = filePath != null;
18263
18439
  if (hasInline && hasFile) {
18264
18440
  output.writeWarn("Cannot combine an inline expression with --file");
18265
18441
  return null;
@@ -18272,41 +18448,74 @@ var resolveExpression = async (inline, options, output) => {
18272
18448
  output.writeWarn("Cannot combine --file with stdin input");
18273
18449
  return null;
18274
18450
  }
18275
- if (hasFile) {
18451
+ if (options.bundle && !hasFile) {
18452
+ output.writeWarn("--bundle requires --file");
18453
+ return null;
18454
+ }
18455
+ let expression;
18456
+ if (hasFile && options.bundle) {
18457
+ const bundled = await bundleEvalEntry(filePath);
18458
+ if (!bundled.ok) {
18459
+ output.writeWarn(`Failed to bundle --file: ${bundled.error}`);
18460
+ return null;
18461
+ }
18462
+ if (!bundled.code.trim()) {
18463
+ output.writeWarn(`Bundled file is empty: ${options.file}`);
18464
+ return null;
18465
+ }
18466
+ expression = bundled.code;
18467
+ } else if (hasFile) {
18276
18468
  try {
18277
- const content = await readFile2(options.file ?? "", "utf8");
18469
+ const content = await readFile2(filePath, "utf8");
18278
18470
  if (!content.trim()) {
18279
18471
  output.writeWarn(`File is empty: ${options.file}`);
18280
18472
  return null;
18281
18473
  }
18282
- return content;
18474
+ expression = content;
18283
18475
  } catch (error) {
18284
18476
  output.writeWarn(`Failed to read --file: ${formatError(error)}`);
18285
18477
  return null;
18286
18478
  }
18287
- }
18288
- if (wantsStdin) {
18479
+ } else if (wantsStdin) {
18289
18480
  try {
18290
18481
  const content = await readStdin();
18291
18482
  if (!content.trim()) {
18292
18483
  output.writeWarn("Stdin input is empty");
18293
18484
  return null;
18294
18485
  }
18295
- return content;
18486
+ expression = content;
18296
18487
  } catch (error) {
18297
18488
  output.writeWarn(`Failed to read stdin: ${formatError(error)}`);
18298
18489
  return null;
18299
18490
  }
18300
- }
18301
- if (hasInline) {
18491
+ } else if (hasInline) {
18302
18492
  if (!inline.trim()) {
18303
18493
  output.writeWarn("Expression is empty");
18304
18494
  return null;
18305
18495
  }
18306
- return inline;
18496
+ expression = inline;
18497
+ } else {
18498
+ output.writeWarn("Expression is required. Provide an inline expression, --file, or --stdin (or pass - as expression).");
18499
+ return null;
18500
+ }
18501
+ return await prependInjectSource(expression, options.inject, output);
18502
+ };
18503
+ var prependInjectSource = async (expression, injectPath, output) => {
18504
+ if (injectPath == null) {
18505
+ return expression;
18506
+ }
18507
+ try {
18508
+ const injectSource = await readFile2(injectPath, "utf8");
18509
+ if (!injectSource.trim()) {
18510
+ output.writeWarn(`Inject file is empty: ${injectPath}`);
18511
+ return null;
18512
+ }
18513
+ return `${injectSource}
18514
+ ${expression}`;
18515
+ } catch (error) {
18516
+ output.writeWarn(`Failed to read --inject: ${formatError(error)}`);
18517
+ return null;
18307
18518
  }
18308
- output.writeWarn("Expression is required. Provide an inline expression, --file, or --stdin (or pass - as expression).");
18309
- return null;
18310
18519
  };
18311
18520
  var readStdin = async () => new Promise((resolve2, reject) => {
18312
18521
  let data = "";
@@ -18822,7 +19031,7 @@ var validateNetExportOptions = (options) => {
18822
19031
  return { value: { out, format: "har" } };
18823
19032
  };
18824
19033
  var writeHarFile = async (outPath, har) => {
18825
- await fs9.mkdir(path11.dirname(outPath), { recursive: true });
19034
+ await fs9.mkdir(path12.dirname(outPath), { recursive: true });
18826
19035
  await fs9.writeFile(outPath, `${JSON.stringify(har, null, 2)}
18827
19036
  `, "utf8");
18828
19037
  };
@@ -19169,10 +19378,10 @@ var fetchNetRequestBody = async (watcher, query, part, output, config = {}) => {
19169
19378
  bodyQuery.set("part", part);
19170
19379
  return await fetchNetRequestRoute(watcher, "/net/request/body", bodyQuery, NET_REQUEST_BODY_TIMEOUT_MS, output, config);
19171
19380
  };
19172
- var fetchNetRequestRoute = async (watcher, path12, query, timeoutMs, output, config = {}) => {
19381
+ var fetchNetRequestRoute = async (watcher, path13, query, timeoutMs, output, config = {}) => {
19173
19382
  try {
19174
19383
  const response = await fetchWatcherJson(watcher, {
19175
- path: path12,
19384
+ path: path13,
19176
19385
  query,
19177
19386
  timeoutMs,
19178
19387
  returnErrorResponse: true
@@ -19380,32 +19589,28 @@ var writeInspectNoMatch = (output, pattern, settleMs, timedOut) => {
19380
19589
  };
19381
19590
 
19382
19591
  // dist/commands/net.js
19383
- var runNet = async (id, options) => {
19384
- const output = createOutput(options);
19592
+ var runNet = defineWatcherCommand({
19593
+ build: (_args, options, output) => buildNetPlan(options, output),
19594
+ formatJson: (response) => response.requests,
19595
+ formatHuman: (response, { output }) => {
19596
+ for (const request of response.requests) {
19597
+ output.writeHuman(formatNetworkRequest(request));
19598
+ }
19599
+ }
19600
+ });
19601
+ var buildNetPlan = (options, output) => {
19385
19602
  const params = new URLSearchParams;
19386
19603
  const query = appendNetCommandParams(params, options);
19387
19604
  if (query.error) {
19388
19605
  output.writeWarn(query.error);
19389
19606
  process.exitCode = 2;
19390
- return;
19607
+ return null;
19391
19608
  }
19392
- const result = await requestWatcherJson({
19393
- id,
19609
+ return {
19394
19610
  path: "/net",
19395
19611
  query: params,
19396
19612
  timeoutMs: 5000
19397
- });
19398
- if (!result.ok) {
19399
- writeRequestError(result, output);
19400
- return;
19401
- }
19402
- if (options.json) {
19403
- output.writeJson(result.data.requests);
19404
- return;
19405
- }
19406
- for (const request of result.data.requests) {
19407
- output.writeHuman(formatNetworkRequest(request));
19408
- }
19613
+ };
19409
19614
  };
19410
19615
 
19411
19616
  // dist/commands/netRequestTarget.js
@@ -19717,7 +19922,7 @@ var runNetSse = async (id, options) => {
19717
19922
  process.exitCode = 2;
19718
19923
  return;
19719
19924
  }
19720
- const result = await requestWatcherAction({ id, path: "/net/sse", query: params, timeoutMs: 5000 }, output);
19925
+ const result = await requestWatcherCommandAction({ id, path: "/net/sse", query: params, timeoutMs: 5000 }, output);
19721
19926
  if (!result) {
19722
19927
  return;
19723
19928
  }
@@ -19836,7 +20041,7 @@ var runNetWebSocket = async (id, options) => {
19836
20041
  process.exitCode = 2;
19837
20042
  return;
19838
20043
  }
19839
- const result = await requestWatcherAction({ id, path: "/net/ws", query: params, timeoutMs: 5000 }, output);
20044
+ const result = await requestWatcherCommandAction({ id, path: "/net/ws", query: params, timeoutMs: 5000 }, output);
19840
20045
  if (!result) {
19841
20046
  return;
19842
20047
  }
@@ -19854,7 +20059,7 @@ var runNetWebSocketShow = async (id, connection, options) => {
19854
20059
  if (!query) {
19855
20060
  return;
19856
20061
  }
19857
- const result = await requestWatcherAction({ id, path: "/net/ws/connection", query, timeoutMs: 5000 }, output);
20062
+ const result = await requestWatcherCommandAction({ id, path: "/net/ws/connection", query, timeoutMs: 5000 }, output);
19858
20063
  if (!result) {
19859
20064
  return;
19860
20065
  }
@@ -20142,35 +20347,33 @@ var toCamelCase = (flag) => flag.replace(/^--/, "").replace(/-([a-z])/g, (_match
20142
20347
  import { writeFile as writeFile2 } from "node:fs/promises";
20143
20348
  var COOKIE_EXPORT_FORMATS = new Set(["netscape", "json", "header"]);
20144
20349
  var fetchAuthCookies = async (id, input, output) => {
20145
- const result = await requestWatcherJson({
20350
+ const result = await requestWatcherCommandJson({
20146
20351
  id,
20147
20352
  path: "/auth/cookies",
20148
20353
  query: buildCookieQuery(input),
20149
20354
  timeoutMs: 1e4
20150
- });
20151
- if (!result.ok) {
20152
- writeRequestError(result, output);
20355
+ }, output);
20356
+ if (!result)
20153
20357
  return null;
20154
- }
20155
20358
  return result.data;
20156
20359
  };
20157
20360
  var normalizeCookieIdentityInput = (input, output) => {
20158
20361
  const name = input.name.trim();
20159
20362
  const domain = input.domain.trim();
20160
- const path12 = input.path.trim();
20363
+ const path13 = input.path.trim();
20161
20364
  if (!name) {
20162
20365
  return writeCookieInputError(output, "name is required");
20163
20366
  }
20164
20367
  if (!domain) {
20165
20368
  return writeCookieInputError(output, "domain is required");
20166
20369
  }
20167
- if (!path12) {
20370
+ if (!path13) {
20168
20371
  return writeCookieInputError(output, "path is required");
20169
20372
  }
20170
- if (!path12.startsWith("/")) {
20373
+ if (!path13.startsWith("/")) {
20171
20374
  return writeCookieInputError(output, 'path must start with "/"');
20172
20375
  }
20173
- return { name, domain, path: path12 };
20376
+ return { name, domain, path: path13 };
20174
20377
  };
20175
20378
  var parseCookieSetInput = (input, output) => {
20176
20379
  const identity = normalizeCookieIdentityInput(input, output);
@@ -20380,116 +20583,104 @@ var runAuthExportCookies = async (id, options) => {
20380
20583
  const serialized = serializeCookies(filterCookies(response.origin, response.cookies, options), format);
20381
20584
  await writeOutput2(serialized, options.out);
20382
20585
  };
20383
- var runAuthCookieGet = async (id, name, options) => {
20384
- const output = createOutput(options);
20385
- const identity = normalizeCookieIdentityInput({ name, domain: options.domain, path: options.path }, output);
20386
- if (!identity) {
20387
- return;
20388
- }
20389
- const result = await requestWatcherAction({
20390
- id,
20391
- path: "/auth/cookies/get",
20392
- method: "POST",
20393
- body: {
20394
- ...identity,
20395
- includeValue: options.showValue === true
20586
+ var runAuthCookieGet = defineWatcherCommand({
20587
+ build: ([name], options, output) => {
20588
+ const identity = normalizeCookieIdentityInput({ name, domain: options.domain, path: options.path }, output);
20589
+ if (!identity) {
20590
+ return null;
20396
20591
  }
20397
- }, output);
20398
- if (!result) {
20399
- return;
20400
- }
20401
- if (options.json) {
20402
- output.writeJson(result.data);
20403
- return;
20404
- }
20405
- if (!result.data.cookie) {
20406
- output.writeHuman("No cookie matched exact identity.");
20407
- return;
20408
- }
20409
- output.writeHuman(formatCookieLine(result.data.cookie, options.showValue === true));
20410
- };
20411
- var runAuthCookieSet = async (id, name, value, options) => {
20412
- const output = createOutput(options);
20413
- const cookie = parseCookieSetInput({ name, value, ...options }, output);
20414
- if (!cookie) {
20415
- return;
20416
- }
20417
- const result = await requestWatcherAction({
20418
- id,
20419
- path: "/auth/cookies/set",
20420
- method: "POST",
20421
- body: { cookie }
20422
- }, output);
20423
- if (!result) {
20424
- return;
20425
- }
20426
- if (options.json) {
20427
- output.writeJson(result.data);
20428
- return;
20429
- }
20430
- output.writeHuman(`Set ${formatCookieLine(result.data.cookie, false)}`);
20431
- };
20432
- var runAuthCookieDelete = async (id, name, options) => {
20433
- const output = createOutput(options);
20434
- const identity = normalizeCookieIdentityInput({ name, domain: options.domain, path: options.path }, output);
20435
- if (!identity) {
20436
- return;
20437
- }
20438
- const result = await requestWatcherAction({
20439
- id,
20440
- path: "/auth/cookies/delete",
20441
- method: "POST",
20442
- body: identity
20443
- }, output);
20444
- if (!result) {
20445
- return;
20446
- }
20447
- if (options.json) {
20448
- output.writeJson(result.data);
20449
- return;
20450
- }
20451
- if (!result.data.deleted) {
20452
- output.writeHuman("No cookie matched exact identity.");
20453
- return;
20454
- }
20455
- output.writeHuman(`Deleted ${formatCookieIdentityLine(result.data.cookie)}`);
20456
- };
20457
- var runAuthCookieClear = async (id, options) => {
20458
- const output = createOutput(options);
20459
- const scope = resolveCookieClearScope(options, output);
20460
- if (!scope) {
20461
- return;
20592
+ return {
20593
+ path: "/auth/cookies/get",
20594
+ method: "POST",
20595
+ body: {
20596
+ ...identity,
20597
+ includeValue: options.showValue === true
20598
+ }
20599
+ };
20600
+ },
20601
+ formatHuman: (response, { output, options }) => {
20602
+ if (!response.cookie) {
20603
+ output.writeHuman("No cookie matched exact identity.");
20604
+ return;
20605
+ }
20606
+ output.writeHuman(formatCookieLine(response.cookie, options.showValue === true));
20462
20607
  }
20463
- const result = await requestWatcherAction({
20464
- id,
20465
- path: "/auth/cookies/clear",
20466
- method: "POST",
20467
- body: {
20468
- scope,
20469
- domain: scope === "domain" ? options.domain : undefined,
20470
- sessionOnly: options.sessionOnly === true,
20471
- authOnly: options.authOnly === true
20608
+ });
20609
+ var runAuthCookieSet = defineWatcherCommand({
20610
+ build: ([name, value], options, output) => {
20611
+ const cookie = parseCookieSetInput({ name, value, ...options }, output);
20612
+ if (!cookie) {
20613
+ return null;
20472
20614
  }
20473
- }, output);
20474
- if (!result) {
20475
- return;
20615
+ return {
20616
+ path: "/auth/cookies/set",
20617
+ method: "POST",
20618
+ body: { cookie }
20619
+ };
20620
+ },
20621
+ formatHuman: (response, { output }) => {
20622
+ output.writeHuman(`Set ${formatCookieLine(response.cookie, false)}`);
20476
20623
  }
20477
- if (options.json) {
20478
- output.writeJson(result.data);
20479
- return;
20624
+ });
20625
+ var runAuthCookieDelete = defineWatcherCommand({
20626
+ build: ([name], options, output) => {
20627
+ const identity = normalizeCookieIdentityInput({ name, domain: options.domain, path: options.path }, output);
20628
+ if (!identity) {
20629
+ return null;
20630
+ }
20631
+ return {
20632
+ path: "/auth/cookies/delete",
20633
+ method: "POST",
20634
+ body: identity
20635
+ };
20636
+ },
20637
+ formatHuman: (response, { output }) => {
20638
+ if (!response.deleted) {
20639
+ output.writeHuman("No cookie matched exact identity.");
20640
+ return;
20641
+ }
20642
+ output.writeHuman(`Deleted ${formatCookieIdentityLine(response.cookie)}`);
20480
20643
  }
20481
- const scopeSuffix = result.data.scopeValue ? ` (${result.data.scopeValue})` : "";
20482
- output.writeHuman(`Cleared ${result.data.cleared} cookie(s) from ${result.data.scope}${scopeSuffix}`);
20483
- for (const cookie of result.data.cookies) {
20484
- output.writeHuman(formatCookieIdentityLine(cookie));
20644
+ });
20645
+ var runAuthCookieClear = defineWatcherCommand({
20646
+ build: (_args, options, output) => {
20647
+ const scope = resolveCookieClearScope(options, output);
20648
+ if (!scope) {
20649
+ return null;
20650
+ }
20651
+ return {
20652
+ path: "/auth/cookies/clear",
20653
+ method: "POST",
20654
+ body: {
20655
+ scope,
20656
+ domain: scope === "domain" ? options.domain : undefined,
20657
+ sessionOnly: options.sessionOnly === true,
20658
+ authOnly: options.authOnly === true
20659
+ }
20660
+ };
20661
+ },
20662
+ formatHuman: (response, { output }) => {
20663
+ const scopeSuffix = response.scopeValue ? ` (${response.scopeValue})` : "";
20664
+ output.writeHuman(`Cleared ${response.cleared} cookie(s) from ${response.scope}${scopeSuffix}`);
20665
+ for (const cookie of response.cookies) {
20666
+ output.writeHuman(formatCookieIdentityLine(cookie));
20667
+ }
20485
20668
  }
20486
- };
20669
+ });
20487
20670
 
20488
20671
  // dist/cli/register/registerAuth.js
20489
20672
  function registerAuth(program2) {
20490
20673
  const auth = program2.command("auth").description("Inspect, export, and load browser auth state");
20491
- const cookies2 = auth.command("cookies").description("Inspect and mutate browser cookies");
20492
- addCookieScopeOptions(cookies2.command("list").alias("ls"), "Only include first-party cookies for the attached page origin").argument("[id]", "Watcher id").description("List cookies for the attached page").option("--domain <domain>", "Filter cookies by domain suffix").option("--session-only", "Show only session cookies").option("--http-only", "Show only HttpOnly cookies").option("--secure", "Show only Secure cookies").option("--show-values", "Reveal raw cookie values instead of previews").option("--json", "Output JSON for automation").addHelpText("after", `
20674
+ const cookies2 = addCookieListOptions(auth.command("cookies").argument("[id]", "Watcher id").description("Inspect and mutate browser cookies"), "Only include first-party cookies for the attached page origin").addHelpText("after", `
20675
+ Examples:
20676
+ $ argus auth cookies app
20677
+ $ argus auth cookies app --for-origin --exclude-tracking
20678
+ $ argus auth cookies app --domain example.com
20679
+ $ argus auth cookies app --session-only --show-values
20680
+ `).action(async (id, options) => {
20681
+ await runAuthCookies(id, options);
20682
+ });
20683
+ addCookieListOptions(cookies2.command("list").alias("ls"), "Only include first-party cookies for the attached page origin").argument("[id]", "Watcher id").description("List cookies for the attached page").addHelpText("after", `
20493
20684
  Examples:
20494
20685
  $ argus auth cookies list app
20495
20686
  $ argus auth cookies ls app --for-origin --exclude-tracking
@@ -20571,27 +20762,71 @@ Examples:
20571
20762
  });
20572
20763
  }
20573
20764
  var addCookieScopeOptions = (command, forOriginDescription) => command.option("--for-origin", forOriginDescription).option("--exclude-tracking", "Hide common analytics/tracking cookies such as _ga and _ym");
20765
+ var addCookieListOptions = (command, forOriginDescription) => addCookieScopeOptions(command, forOriginDescription).option("--domain <domain>", "Filter cookies by domain suffix").option("--session-only", "Show only session cookies").option("--http-only", "Show only HttpOnly cookies").option("--secure", "Show only Secure cookies").option("--show-values", "Reveal raw cookie values instead of previews").option("--json", "Output JSON for automation");
20766
+
20767
+ // dist/commands/evalPolling.js
20768
+ var pollEval = async (input) => {
20769
+ let running = true;
20770
+ const stop = () => {
20771
+ running = false;
20772
+ };
20773
+ process.on("SIGINT", stop);
20774
+ process.on("SIGTERM", stop);
20775
+ try {
20776
+ const startTime = Date.now();
20777
+ let iteration = 0;
20778
+ while (running) {
20779
+ if (input.totalTimeoutMs != null) {
20780
+ const elapsed = Date.now() - startTime;
20781
+ if (elapsed >= input.totalTimeoutMs) {
20782
+ return { kind: "timeout", elapsedMs: elapsed };
20783
+ }
20784
+ }
20785
+ iteration += 1;
20786
+ const result = await evalWithRetries({
20787
+ watcher: input.watcher,
20788
+ expression: input.expression,
20789
+ args: input.args,
20790
+ awaitPromise: input.awaitPromise,
20791
+ returnByValue: input.returnByValue,
20792
+ timeoutMs: input.timeoutMs,
20793
+ failOnException: input.failOnException,
20794
+ retryCount: input.retryCount
20795
+ });
20796
+ if (!result.ok) {
20797
+ return { kind: "eval-error", failure: result };
20798
+ }
20799
+ const context = { response: result.response, iteration, attempt: result.attempt };
20800
+ input.onResult?.(result.response, context);
20801
+ const stopResult = input.shouldStop?.(context);
20802
+ if (stopResult) {
20803
+ if (!stopResult.ok) {
20804
+ return { kind: "condition-error", error: stopResult.error };
20805
+ }
20806
+ if (stopResult.matched) {
20807
+ return { kind: "matched", response: result.response, iteration, attempt: result.attempt };
20808
+ }
20809
+ }
20810
+ if (input.count != null && iteration >= input.count) {
20811
+ return { kind: "exhausted", iterations: iteration };
20812
+ }
20813
+ await sleep3(input.intervalMs);
20814
+ }
20815
+ return { kind: "interrupted" };
20816
+ } finally {
20817
+ process.off("SIGINT", stop);
20818
+ process.off("SIGTERM", stop);
20819
+ }
20820
+ };
20574
20821
 
20575
20822
  // dist/commands/eval.js
20576
20823
  var runEval = async (id, rawExpression, options) => {
20577
20824
  const output = createOutput(options);
20578
- const evalArgs = parseEvalArgs(options.arg);
20579
- if (evalArgs.error) {
20580
- output.writeWarn(evalArgs.error);
20581
- process.exitCode = 2;
20582
- return;
20583
- }
20584
- const resolvedExpression = await resolveExpression(rawExpression, options, output);
20585
- if (resolvedExpression == null) {
20825
+ const prepared = await prepareEvalExpression(rawExpression, options, output);
20826
+ if (prepared == null) {
20586
20827
  process.exitCode = 2;
20587
20828
  return;
20588
20829
  }
20589
- const requestArgs = !options.iframe && hasEvalArgs2(evalArgs.value) ? evalArgs.value : undefined;
20590
- const expression = options.iframe ? wrapForIframeEval(wrapExpressionWithArgs(resolvedExpression, evalArgs.value), {
20591
- selector: options.iframe,
20592
- namespace: options.iframeNamespace ?? "argus",
20593
- timeoutMs: parseNumber(options.iframeTimeout) ?? 5000
20594
- }) : resolvedExpression;
20595
20830
  const retryCount = parseRetryCount(options.retry);
20596
20831
  if (retryCount.error) {
20597
20832
  output.writeWarn(retryCount.error);
@@ -20634,8 +20869,8 @@ var runEval = async (id, rawExpression, options) => {
20634
20869
  if (intervalMs.value == null) {
20635
20870
  const singleResult = await evalWithRetries({
20636
20871
  watcher,
20637
- expression,
20638
- args: requestArgs,
20872
+ expression: prepared.expression,
20873
+ args: prepared.args,
20639
20874
  awaitPromise: options.await ?? true,
20640
20875
  returnByValue: options.returnByValue ?? true,
20641
20876
  timeoutMs,
@@ -20650,51 +20885,45 @@ var runEval = async (id, rawExpression, options) => {
20650
20885
  printSuccess(singleResult.response, options, output, false);
20651
20886
  return;
20652
20887
  }
20653
- let running = true;
20654
- const stop = () => {
20655
- running = false;
20656
- };
20657
- process.on("SIGINT", stop);
20658
- process.on("SIGTERM", stop);
20659
- let iteration = 0;
20660
- while (running) {
20661
- iteration += 1;
20662
- const iterationResult = await evalWithRetries({
20663
- watcher,
20664
- expression,
20665
- args: requestArgs,
20666
- awaitPromise: options.await ?? true,
20667
- returnByValue: options.returnByValue ?? true,
20668
- timeoutMs,
20669
- failOnException: options.failOnException ?? true,
20670
- retryCount: retryCount.value
20671
- });
20672
- if (!iterationResult.ok) {
20673
- printError(iterationResult, options, output);
20674
- process.exitCode = 1;
20675
- return;
20676
- }
20677
- printSuccess(iterationResult.response, options, output, true);
20678
- if (untilEvaluator.evaluator) {
20888
+ const pollResult = await pollEval({
20889
+ watcher,
20890
+ expression: prepared.expression,
20891
+ args: prepared.args,
20892
+ awaitPromise: options.await ?? true,
20893
+ returnByValue: options.returnByValue ?? true,
20894
+ timeoutMs,
20895
+ failOnException: options.failOnException ?? true,
20896
+ retryCount: retryCount.value,
20897
+ intervalMs: intervalMs.value,
20898
+ count: countValue.value,
20899
+ onResult: (response) => {
20900
+ printSuccess(response, options, output, true);
20901
+ },
20902
+ shouldStop: (context) => {
20903
+ if (!untilEvaluator.evaluator) {
20904
+ return { ok: true, matched: false };
20905
+ }
20679
20906
  const untilResult = untilEvaluator.evaluator({
20680
- result: iterationResult.response.result,
20681
- exception: iterationResult.response.exception ?? null,
20682
- iteration,
20683
- attempt: iterationResult.attempt
20907
+ result: context.response.result,
20908
+ exception: context.response.exception ?? null,
20909
+ iteration: context.iteration,
20910
+ attempt: context.attempt
20684
20911
  });
20685
- if (!untilResult.ok) {
20686
- printError({ kind: "until", error: untilResult.error }, options, output);
20687
- process.exitCode = 1;
20688
- return;
20689
- }
20690
- if (untilResult.matched) {
20691
- return;
20692
- }
20693
- }
20694
- if (countValue.value != null && iteration >= countValue.value) {
20695
- return;
20912
+ return untilResult.ok ? { ok: true, matched: untilResult.matched } : { ok: false, error: untilResult.error };
20696
20913
  }
20697
- await sleep3(intervalMs.value);
20914
+ });
20915
+ if (pollResult.kind === "eval-error") {
20916
+ printError(pollResult.failure, options, output);
20917
+ process.exitCode = 1;
20918
+ return;
20919
+ }
20920
+ if (pollResult.kind === "condition-error") {
20921
+ printError({ kind: "until", error: pollResult.error }, options, output);
20922
+ process.exitCode = 1;
20923
+ return;
20924
+ }
20925
+ if (pollResult.kind === "interrupted") {
20926
+ process.exitCode = 130;
20698
20927
  }
20699
20928
  };
20700
20929
  var compileUntil = (condition) => {
@@ -20725,23 +20954,11 @@ var compileUntil = (condition) => {
20725
20954
  // dist/commands/evalUntil.js
20726
20955
  var runEvalUntil = async (id, rawExpression, options) => {
20727
20956
  const output = createOutput(options);
20728
- const evalArgs = parseEvalArgs(options.arg);
20729
- if (evalArgs.error) {
20730
- output.writeWarn(evalArgs.error);
20957
+ const prepared = await prepareEvalExpression(rawExpression, options, output);
20958
+ if (prepared == null) {
20731
20959
  process.exitCode = 2;
20732
20960
  return;
20733
20961
  }
20734
- const resolvedExpression = await resolveExpression(rawExpression, options, output);
20735
- if (resolvedExpression == null) {
20736
- process.exitCode = 2;
20737
- return;
20738
- }
20739
- const requestArgs = !options.iframe && hasEvalArgs2(evalArgs.value) ? evalArgs.value : undefined;
20740
- const expression = options.iframe ? wrapForIframeEval(wrapExpressionWithArgs(resolvedExpression, evalArgs.value), {
20741
- selector: options.iframe,
20742
- namespace: options.iframeNamespace ?? "argus",
20743
- timeoutMs: parseNumber(options.iframeTimeout) ?? 5000
20744
- }) : resolvedExpression;
20745
20962
  const retryCount = parseRetryCount(options.retry);
20746
20963
  if (retryCount.error) {
20747
20964
  output.writeWarn(retryCount.error);
@@ -20772,57 +20989,50 @@ var runEvalUntil = async (id, rawExpression, options) => {
20772
20989
  return;
20773
20990
  const { watcher } = resolved;
20774
20991
  const timeoutMs = parseNumber(options.timeout);
20775
- let running = true;
20776
- let interrupted = false;
20777
- const stop = () => {
20778
- running = false;
20779
- interrupted = true;
20780
- };
20781
- process.on("SIGINT", stop);
20782
- process.on("SIGTERM", stop);
20783
- const startTime = Date.now();
20784
- let iteration = 0;
20785
- while (running) {
20786
- if (totalTimeoutMs.value != null) {
20787
- const elapsed = Date.now() - startTime;
20788
- if (elapsed >= totalTimeoutMs.value) {
20789
- output.writeWarn(`Total timeout exceeded (${options.totalTimeout})`);
20790
- process.exitCode = 1;
20791
- return;
20992
+ const pollResult = await pollEval({
20993
+ watcher,
20994
+ expression: prepared.expression,
20995
+ args: prepared.args,
20996
+ awaitPromise: options.await ?? true,
20997
+ returnByValue: options.returnByValue ?? true,
20998
+ timeoutMs,
20999
+ failOnException: options.failOnException ?? true,
21000
+ retryCount: retryCount.value,
21001
+ intervalMs: pollIntervalMs,
21002
+ count: countValue.value,
21003
+ totalTimeoutMs: totalTimeoutMs.value,
21004
+ onResult: (response) => {
21005
+ if (!response.result && options.verbose) {
21006
+ printSuccess(response, options, output, true);
20792
21007
  }
20793
- }
20794
- iteration += 1;
20795
- const iterationResult = await evalWithRetries({
20796
- watcher,
20797
- expression,
20798
- args: requestArgs,
20799
- awaitPromise: options.await ?? true,
20800
- returnByValue: options.returnByValue ?? true,
20801
- timeoutMs,
20802
- failOnException: options.failOnException ?? true,
20803
- retryCount: retryCount.value
20804
- });
20805
- if (!iterationResult.ok) {
20806
- printError(iterationResult, options, output);
20807
- process.exitCode = 1;
20808
- return;
20809
- }
20810
- const isTruthy = Boolean(iterationResult.response.result);
20811
- if (isTruthy) {
20812
- printSuccess(iterationResult.response, options, output, false);
20813
- return;
20814
- }
20815
- if (options.verbose) {
20816
- printSuccess(iterationResult.response, options, output, true);
20817
- }
20818
- if (countValue.value != null && iteration >= countValue.value) {
20819
- output.writeWarn(`Exhausted after ${iteration} iterations without a truthy result`);
20820
- process.exitCode = 1;
20821
- return;
20822
- }
20823
- await sleep3(pollIntervalMs);
21008
+ },
21009
+ shouldStop: ({ response }) => ({ ok: true, matched: Boolean(response.result) })
21010
+ });
21011
+ if (pollResult.kind === "matched") {
21012
+ printSuccess(pollResult.response, options, output, false);
21013
+ return;
21014
+ }
21015
+ if (pollResult.kind === "eval-error") {
21016
+ printError(pollResult.failure, options, output);
21017
+ process.exitCode = 1;
21018
+ return;
21019
+ }
21020
+ if (pollResult.kind === "timeout") {
21021
+ output.writeWarn(`Total timeout exceeded (${options.totalTimeout})`);
21022
+ process.exitCode = 1;
21023
+ return;
20824
21024
  }
20825
- if (interrupted) {
21025
+ if (pollResult.kind === "exhausted") {
21026
+ output.writeWarn(`Exhausted after ${pollResult.iterations} iterations without a truthy result`);
21027
+ process.exitCode = 1;
21028
+ return;
21029
+ }
21030
+ if (pollResult.kind === "condition-error") {
21031
+ output.writeWarn(pollResult.error);
21032
+ process.exitCode = 1;
21033
+ return;
21034
+ }
21035
+ if (pollResult.kind === "interrupted") {
20826
21036
  process.exitCode = 130;
20827
21037
  }
20828
21038
  };
@@ -20911,11 +21121,13 @@ ${script}})();
20911
21121
  // dist/cli/register/registerEval.js
20912
21122
  var collectValue = (value, previous = []) => [...previous, value];
20913
21123
  function registerEval(program2) {
20914
- const evalCmd = program2.command("eval").alias("js").argument("[id]", "Watcher id to query").argument("[expression]", "JS expression to evaluate (or use --file / --stdin)").description("Evaluate a JS expression in the connected page").option("--no-await", "Do not await promises").option("--timeout <ms>", "Eval timeout in milliseconds").option("--json", "Output JSON for automation").option("--no-return-by-value", "Disable returnByValue (use preview)").option("--no-fail-on-exception", "Do not exit with code 1 when the evaluation throws").option("--retry <n>", "Retry failed evaluations up to N times").option("-q, --silent", "Suppress success output; only emit output on error").option("--interval <ms|duration>", "Re-evaluate every interval (e.g. 500, 3s)").option("--count <n>", "Stop after N iterations (requires --interval)").option("--until <condition>", "Stop when local condition becomes truthy (requires --interval)").option("-f, --file <path>", "Read expression from a file").option("--stdin", "Read expression from stdin").option("--iframe <selector>", "Eval in iframe via postMessage (requires helper script)").option("--iframe-namespace <name>", "Message type prefix for iframe eval (default: argus)").option("--iframe-timeout <ms>", "Timeout for iframe postMessage response (default: 5000)").option("--arg <key=value>", "Argument exposed to eval scripts as args[key]", collectValue, []).addHelpText("after", `
21124
+ const evalCmd = program2.command("eval").alias("js").argument("[id]", "Watcher id to query").argument("[expression]", "JS expression to evaluate (or use --file / --stdin)").description("Evaluate a JS expression in the connected page").option("--no-await", "Do not await promises").option("--timeout <ms>", "Eval timeout in milliseconds").option("--json", "Output JSON for automation").option("--no-return-by-value", "Disable returnByValue (use preview)").option("--no-fail-on-exception", "Do not exit with code 1 when the evaluation throws").option("--retry <n>", "Retry failed evaluations up to N times").option("-q, --silent", "Suppress success output; only emit output on error").option("--interval <ms|duration>", "Re-evaluate every interval (e.g. 500, 3s)").option("--count <n>", "Stop after N iterations (requires --interval)").option("--until <condition>", "Stop when local condition becomes truthy (requires --interval)").option("-f, --file <path>", "Read expression from a file").option("--bundle", "Bundle local imports from --file into one script before eval").option("--stdin", "Read expression from stdin").option("--inject <path>", "Read setup code from a file and run it before the expression").option("--iframe <selector>", "Eval in iframe via postMessage (requires helper script)").option("--iframe-namespace <name>", "Message type prefix for iframe eval (default: argus)").option("--iframe-timeout <ms>", "Timeout for iframe postMessage response (default: 5000)").option("--arg <key=value>", "Argument exposed to eval scripts as args[key]", collectValue, []).addHelpText("after", `
20915
21125
  Examples:
20916
21126
  $ argus eval app "location.href"
20917
21127
  $ argus eval app "await fetch('/ping').then(r => r.status)"
20918
21128
  $ argus eval app --file ./script.js
21129
+ $ argus eval app --file ./script.js --bundle
21130
+ $ argus eval app "window.store.getState()" --inject ./debug-hooks.js
20919
21131
  $ argus eval app --file ./script.js --arg level=10 --arg mode=fast
20920
21132
  $ cat script.js | argus eval app --stdin
20921
21133
  $ argus eval app - < script.js
@@ -20939,7 +21151,9 @@ Examples:
20939
21151
  count: options.count,
20940
21152
  until: options.until,
20941
21153
  file: options.file,
21154
+ bundle: options.bundle,
20942
21155
  stdin: options.stdin,
21156
+ inject: options.inject,
20943
21157
  iframe: options.iframe,
20944
21158
  iframeNamespace: options.iframeNamespace,
20945
21159
  iframeTimeout: options.iframeTimeout,
@@ -20955,7 +21169,7 @@ Examples:
20955
21169
  `).action(async (options) => {
20956
21170
  await runIframeHelper(options);
20957
21171
  });
20958
- program2.command("eval-until").alias("wait").argument("[id]", "Watcher id to query").argument("[expression]", "JS expression to poll until truthy (or use --file / --stdin)").description("Poll a JS expression until it returns a truthy value").option("--no-await", "Do not await promises").option("--timeout <ms>", "Per-eval timeout in milliseconds").option("--json", "Output JSON for automation").option("--no-return-by-value", "Disable returnByValue (use preview)").option("--no-fail-on-exception", "Do not exit with code 1 when the evaluation throws").option("--retry <n>", "Retry failed evaluations up to N times").option("-q, --silent", "Suppress success output; only emit output on error").option("--interval <ms|duration>", "Polling interval (default: 250ms)").option("--count <n>", "Stop after N iterations").option("--total-timeout <duration>", "Max wall-clock time (e.g. 30s, 2m)").option("--verbose", "Print intermediate (falsy) results").option("-f, --file <path>", "Read expression from a file").option("--stdin", "Read expression from stdin").option("--iframe <selector>", "Eval in iframe via postMessage (requires helper script)").option("--iframe-namespace <name>", "Message type prefix for iframe eval (default: argus)").option("--iframe-timeout <ms>", "Timeout for iframe postMessage response (default: 5000)").option("--arg <key=value>", "Argument exposed to eval scripts as args[key]", collectValue, []).addHelpText("after", `
21172
+ program2.command("eval-until").alias("wait").argument("[id]", "Watcher id to query").argument("[expression]", "JS expression to poll until truthy (or use --file / --stdin)").description("Poll a JS expression until it returns a truthy value").option("--no-await", "Do not await promises").option("--timeout <ms>", "Per-eval timeout in milliseconds").option("--json", "Output JSON for automation").option("--no-return-by-value", "Disable returnByValue (use preview)").option("--no-fail-on-exception", "Do not exit with code 1 when the evaluation throws").option("--retry <n>", "Retry failed evaluations up to N times").option("-q, --silent", "Suppress success output; only emit output on error").option("--interval <ms|duration>", "Polling interval (default: 250ms)").option("--count <n>", "Stop after N iterations").option("--total-timeout <duration>", "Max wall-clock time (e.g. 30s, 2m)").option("--verbose", "Print intermediate (falsy) results").option("-f, --file <path>", "Read expression from a file").option("--bundle", "Bundle local imports from --file into one script before eval").option("--stdin", "Read expression from stdin").option("--inject <path>", "Read setup code from a file and run it before the expression").option("--iframe <selector>", "Eval in iframe via postMessage (requires helper script)").option("--iframe-namespace <name>", "Message type prefix for iframe eval (default: argus)").option("--iframe-timeout <ms>", "Timeout for iframe postMessage response (default: 5000)").option("--arg <key=value>", "Argument exposed to eval scripts as args[key]", collectValue, []).addHelpText("after", `
20959
21173
  Examples:
20960
21174
  $ argus eval-until app "document.querySelector('#loaded')"
20961
21175
  $ argus eval-until app "await window.appReadyPromise"
@@ -20964,6 +21178,8 @@ Examples:
20964
21178
  $ argus eval-until app "window.data" --verbose
20965
21179
  $ argus eval-until app "window.data" --count 20 --interval 1s
20966
21180
  $ argus eval-until app --file ./check.js --total-timeout 1m
21181
+ $ argus eval-until app --file ./check.js --bundle --total-timeout 1m
21182
+ $ argus wait app "window.appReady" --inject ./debug-hooks.js --total-timeout 20s
20967
21183
  $ argus wait app --file ./ready.js --arg level=10 --total-timeout 20s
20968
21184
  `).action(async (id, expression, options) => {
20969
21185
  await runEvalUntil(id, expression, {
@@ -20979,7 +21195,9 @@ Examples:
20979
21195
  totalTimeout: options.totalTimeout,
20980
21196
  verbose: options.verbose,
20981
21197
  file: options.file,
21198
+ bundle: options.bundle,
20982
21199
  stdin: options.stdin,
21200
+ inject: options.inject,
20983
21201
  iframe: options.iframe,
20984
21202
  iframeNamespace: options.iframeNamespace,
20985
21203
  iframeTimeout: options.iframeTimeout,
@@ -21072,26 +21290,35 @@ var escapeAttrValue = (value) => {
21072
21290
  };
21073
21291
 
21074
21292
  // dist/commands/domTree.js
21075
- var runDomTree = async (id, options) => {
21076
- const output = createOutput(options);
21293
+ var runDomTree = defineWatcherCommand({
21294
+ build: (_args, options, output) => buildDomTreePlan(options, output),
21295
+ formatHuman: (response, { output, options }) => {
21296
+ if (response.matches === 0) {
21297
+ writeNoElementFound(options.selector, output);
21298
+ return;
21299
+ }
21300
+ const formatted = formatDomTree(response.roots, response.truncated, response.truncatedReason);
21301
+ output.writeHuman(formatted);
21302
+ }
21303
+ });
21304
+ var buildDomTreePlan = (options, output) => {
21077
21305
  const selector = requireSelector(options, output);
21078
21306
  if (!selector) {
21079
- return;
21307
+ return null;
21080
21308
  }
21081
21309
  const depth = parsePositiveInt(options.depth, { allowZero: true });
21082
21310
  if (options.depth !== undefined && depth === undefined) {
21083
21311
  output.writeWarn("--depth must be a non-negative integer");
21084
21312
  process.exitCode = 2;
21085
- return;
21313
+ return null;
21086
21314
  }
21087
21315
  const maxNodes = parsePositiveInt(options.maxNodes);
21088
21316
  if (options.maxNodes !== undefined && maxNodes === undefined) {
21089
21317
  output.writeWarn("--max-nodes must be a positive integer");
21090
21318
  process.exitCode = 2;
21091
- return;
21319
+ return null;
21092
21320
  }
21093
- const result = await requestWatcherAction({
21094
- id,
21321
+ return {
21095
21322
  path: "/dom/tree",
21096
21323
  method: "POST",
21097
21324
  body: {
@@ -21102,38 +21329,34 @@ var runDomTree = async (id, options) => {
21102
21329
  text: options.text
21103
21330
  },
21104
21331
  timeoutMs: 30000
21105
- }, output);
21106
- if (!result) {
21107
- return;
21108
- }
21109
- const successResp = result.data;
21110
- if (options.json) {
21111
- output.writeJson(successResp);
21112
- return;
21113
- }
21114
- if (successResp.matches === 0) {
21115
- writeNoElementFound(selector, output);
21116
- return;
21117
- }
21118
- const formatted = formatDomTree(successResp.roots, successResp.truncated, successResp.truncatedReason);
21119
- output.writeHuman(formatted);
21332
+ };
21120
21333
  };
21121
21334
 
21122
21335
  // dist/commands/domInfo.js
21123
- var runDomInfo = async (id, options) => {
21124
- const output = createOutput(options);
21336
+ var runDomInfo = defineWatcherCommand({
21337
+ build: (_args, options, output) => buildDomInfoPlan(options, output),
21338
+ formatHuman: (response, { output, options }) => {
21339
+ const target = { selector: options.selector, ref: options.ref };
21340
+ if (response.matches === 0) {
21341
+ writeNoElementFound(target.selector ?? target.ref, output);
21342
+ return;
21343
+ }
21344
+ const formatted = formatDomInfo(response.elements);
21345
+ output.writeHuman(formatted);
21346
+ }
21347
+ });
21348
+ var buildDomInfoPlan = (options, output) => {
21125
21349
  const target = requireElementTarget({ selector: options.selector, ref: options.ref }, output);
21126
21350
  if (!target) {
21127
- return;
21351
+ return null;
21128
21352
  }
21129
21353
  const outerHtmlMaxChars = parsePositiveInt(options.outerHtmlMax);
21130
21354
  if (options.outerHtmlMax !== undefined && outerHtmlMaxChars === undefined) {
21131
21355
  output.writeWarn("--outer-html-max must be a positive integer");
21132
21356
  process.exitCode = 2;
21133
- return;
21357
+ return null;
21134
21358
  }
21135
- const result = await requestWatcherAction({
21136
- id,
21359
+ return {
21137
21360
  path: "/dom/info",
21138
21361
  method: "POST",
21139
21362
  body: {
@@ -21144,21 +21367,7 @@ var runDomInfo = async (id, options) => {
21144
21367
  text: options.text
21145
21368
  },
21146
21369
  timeoutMs: 30000
21147
- }, output);
21148
- if (!result) {
21149
- return;
21150
- }
21151
- const successResp = result.data;
21152
- if (options.json) {
21153
- output.writeJson(successResp);
21154
- return;
21155
- }
21156
- if (successResp.matches === 0) {
21157
- writeNoElementFound(target.selector ?? target.ref, output);
21158
- return;
21159
- }
21160
- const formatted = formatDomInfo(successResp.elements);
21161
- output.writeHuman(formatted);
21370
+ };
21162
21371
  };
21163
21372
 
21164
21373
  // dist/commands/domFocus.js
@@ -21191,50 +21400,62 @@ var runDomFocus = defineWatcherCommand({
21191
21400
 
21192
21401
  // dist/commands/domAdd.js
21193
21402
  import { readFile as readFile3 } from "node:fs/promises";
21194
- var runDomAdd = async (id, options) => {
21195
- const output = createOutput(options);
21403
+ var runDomAdd = defineWatcherCommand({
21404
+ build: (_args, options, output) => buildDomAddPlan(options, output),
21405
+ formatHuman: (successResp, { output, options, watcher }) => {
21406
+ const selector = options.selector;
21407
+ const position = normalizePosition(options.position);
21408
+ if (successResp.matches === 0) {
21409
+ writeNoElementFound(selector, output, `Hint: run \`argus dom tree ${watcher.id} --selector "${selector}" --all\``);
21410
+ return;
21411
+ }
21412
+ const label = successResp.inserted === 1 ? "element" : "elements";
21413
+ const prefix = watcher.id ? `${watcher.id}: ` : "";
21414
+ output.writeHuman(`${prefix}Inserted at ${successResp.inserted}/${successResp.matches} ${label} (${position}) for selector: ${selector}`);
21415
+ }
21416
+ });
21417
+ var buildDomAddPlan = async (options, output) => {
21196
21418
  const selector = requireSelector(options, output);
21197
21419
  if (!selector) {
21198
- return;
21420
+ return null;
21199
21421
  }
21200
21422
  const position = normalizePosition(options.position);
21201
21423
  if (!position) {
21202
21424
  output.writeWarn("--position must be one of: beforebegin, afterbegin, beforeend, afterend (aliases: before, after, prepend, append)");
21203
21425
  process.exitCode = 2;
21204
- return;
21426
+ return null;
21205
21427
  }
21206
21428
  if (options.all && (options.nth != null || options.first)) {
21207
21429
  output.writeWarn("Cannot combine --all with --nth or --first");
21208
21430
  process.exitCode = 2;
21209
- return;
21431
+ return null;
21210
21432
  }
21211
21433
  if (options.nth != null && options.first) {
21212
21434
  output.writeWarn("Cannot combine --nth with --first");
21213
21435
  process.exitCode = 2;
21214
- return;
21436
+ return null;
21215
21437
  }
21216
21438
  const nth = options.first ? 0 : parseNonNegativeInt(options.nth, "--nth", output);
21217
21439
  if (options.nth != null && nth == null) {
21218
21440
  process.exitCode = 2;
21219
- return;
21441
+ return null;
21220
21442
  }
21221
21443
  const expect = parseNonNegativeInt(options.expect, "--expect", output);
21222
21444
  if (options.expect != null && expect == null) {
21223
21445
  process.exitCode = 2;
21224
- return;
21446
+ return null;
21225
21447
  }
21226
21448
  const html = await resolveHtmlInput(options, output);
21227
21449
  if (html == null) {
21228
21450
  process.exitCode = 2;
21229
- return;
21451
+ return null;
21230
21452
  }
21231
21453
  if (!html) {
21232
21454
  output.writeWarn("HTML content is empty. Provide non-empty --html, --html-file, or --html-stdin input.");
21233
21455
  process.exitCode = 2;
21234
- return;
21456
+ return null;
21235
21457
  }
21236
- const result = await requestWatcherAction({
21237
- id,
21458
+ return {
21238
21459
  path: "/dom/add",
21239
21460
  method: "POST",
21240
21461
  body: {
@@ -21247,22 +21468,7 @@ var runDomAdd = async (id, options) => {
21247
21468
  text: options.text ?? false
21248
21469
  },
21249
21470
  timeoutMs: 30000
21250
- }, output);
21251
- if (!result) {
21252
- return;
21253
- }
21254
- const successResp = result.data;
21255
- if (options.json) {
21256
- output.writeJson(successResp);
21257
- return;
21258
- }
21259
- if (successResp.matches === 0) {
21260
- writeNoElementFound(selector, output, `Hint: run \`argus dom tree${id ? ` ${id}` : ""} --selector "${selector}" --all\``);
21261
- return;
21262
- }
21263
- const label = successResp.inserted === 1 ? "element" : "elements";
21264
- const prefix = result.watcher.id ? `${result.watcher.id}: ` : "";
21265
- output.writeHuman(`${prefix}Inserted at ${successResp.inserted}/${successResp.matches} ${label} (${position}) for selector: ${selector}`);
21471
+ };
21266
21472
  };
21267
21473
  var normalizePosition = (value) => {
21268
21474
  if (!value) {
@@ -21591,25 +21797,28 @@ var buildSetFilePlan = (options, output) => {
21591
21797
  };
21592
21798
 
21593
21799
  // dist/commands/domScroll.js
21594
- var runDomScroll = async (id, options) => {
21595
- const output = createOutput(options);
21800
+ var runDomScroll = defineWatcherCommand({
21801
+ build: (_args, options, output) => buildDomScrollPlan(options, output),
21802
+ formatHuman: formatDomScrollHuman
21803
+ });
21804
+ var buildDomScrollPlan = (options, output) => {
21596
21805
  if (options.by == null) {
21597
21806
  output.writeWarn("--by is required (e.g. --by 0,300)");
21598
21807
  process.exitCode = 2;
21599
- return;
21808
+ return null;
21600
21809
  }
21601
21810
  const delta = parseXY(options.by);
21602
21811
  if (!delta) {
21603
21812
  output.writeWarn('--by must be in the format "x,y" (e.g. --by 0,300)');
21604
21813
  process.exitCode = 2;
21605
- return;
21814
+ return null;
21606
21815
  }
21607
21816
  const hasSelector = options.selector != null && options.selector.trim() !== "";
21608
21817
  const hasPos = options.pos != null;
21609
21818
  if (hasSelector && hasPos) {
21610
21819
  output.writeWarn("--selector and --pos are mutually exclusive");
21611
21820
  process.exitCode = 2;
21612
- return;
21821
+ return null;
21613
21822
  }
21614
21823
  let x;
21615
21824
  let y;
@@ -21618,7 +21827,7 @@ var runDomScroll = async (id, options) => {
21618
21827
  if (!parsed) {
21619
21828
  output.writeWarn('--pos must be in the format "x,y" (e.g. --pos 400,300)');
21620
21829
  process.exitCode = 2;
21621
- return;
21830
+ return null;
21622
21831
  }
21623
21832
  x = parsed.x;
21624
21833
  y = parsed.y;
@@ -21635,21 +21844,12 @@ var runDomScroll = async (id, options) => {
21635
21844
  body.x = x;
21636
21845
  body.y = y;
21637
21846
  }
21638
- const result = await requestWatcherAction({
21639
- id,
21640
- path: "/dom/scroll",
21641
- method: "POST",
21642
- body,
21643
- timeoutMs: 30000
21644
- }, output);
21645
- if (!result) {
21646
- return;
21647
- }
21648
- const successResp = result.data;
21649
- if (options.json) {
21650
- output.writeJson(successResp);
21651
- return;
21652
- }
21847
+ return { path: "/dom/scroll", method: "POST", body, timeoutMs: 30000 };
21848
+ };
21849
+ function formatDomScrollHuman(successResp, { output, options }) {
21850
+ const delta = parseXY(options.by);
21851
+ const hasSelector = options.selector != null && options.selector.trim() !== "";
21852
+ const hasPos = options.pos != null;
21653
21853
  if (hasSelector) {
21654
21854
  if (successResp.matches === 0) {
21655
21855
  writeNoElementFound(options.selector, output);
@@ -21658,27 +21858,31 @@ var runDomScroll = async (id, options) => {
21658
21858
  const label = successResp.scrolled === 1 ? "element" : "elements";
21659
21859
  output.writeHuman(`Emulated scroll on ${successResp.scrolled} ${label} by (${delta.x}, ${delta.y})`);
21660
21860
  } else if (hasPos) {
21861
+ const { x, y } = parseXY(options.pos);
21661
21862
  output.writeHuman(`Emulated scroll at (${x}, ${y}) by (${delta.x}, ${delta.y})`);
21662
21863
  } else {
21663
21864
  output.writeHuman(`Emulated scroll at viewport center by (${delta.x}, ${delta.y})`);
21664
21865
  }
21665
- };
21866
+ }
21666
21867
 
21667
21868
  // dist/commands/domScrollTo.js
21668
- var runDomScrollTo = async (id, options) => {
21669
- const output = createOutput(options);
21869
+ var runDomScrollTo = defineWatcherCommand({
21870
+ build: (_args, options, output) => buildDomScrollToPlan(options, output),
21871
+ formatHuman: formatDomScrollToHuman
21872
+ });
21873
+ var buildDomScrollToPlan = (options, output) => {
21670
21874
  const hasSelector = options.selector != null && options.selector.trim() !== "";
21671
21875
  const hasTo = options.to != null;
21672
21876
  const hasBy = options.by != null;
21673
21877
  if (!hasSelector && !hasTo && !hasBy) {
21674
21878
  output.writeWarn("--selector, --to, or --by is required");
21675
21879
  process.exitCode = 2;
21676
- return;
21880
+ return null;
21677
21881
  }
21678
21882
  if (hasTo && hasBy) {
21679
21883
  output.writeWarn("--to and --by are mutually exclusive");
21680
21884
  process.exitCode = 2;
21681
- return;
21885
+ return null;
21682
21886
  }
21683
21887
  let to;
21684
21888
  let by;
@@ -21687,7 +21891,7 @@ var runDomScrollTo = async (id, options) => {
21687
21891
  if (!parsed) {
21688
21892
  output.writeWarn('--to must be in the format "x,y" (e.g. --to 0,1000)');
21689
21893
  process.exitCode = 2;
21690
- return;
21894
+ return null;
21691
21895
  }
21692
21896
  to = parsed;
21693
21897
  }
@@ -21696,7 +21900,7 @@ var runDomScrollTo = async (id, options) => {
21696
21900
  if (!parsed) {
21697
21901
  output.writeWarn('--by must be in the format "x,y" (e.g. --by 0,500)');
21698
21902
  process.exitCode = 2;
21699
- return;
21903
+ return null;
21700
21904
  }
21701
21905
  by = parsed;
21702
21906
  }
@@ -21714,21 +21918,10 @@ var runDomScrollTo = async (id, options) => {
21714
21918
  if (by) {
21715
21919
  body.by = by;
21716
21920
  }
21717
- const result = await requestWatcherAction({
21718
- id,
21719
- path: "/dom/scroll-to",
21720
- method: "POST",
21721
- body,
21722
- timeoutMs: 30000
21723
- }, output);
21724
- if (!result) {
21725
- return;
21726
- }
21727
- const successResp = result.data;
21728
- if (options.json) {
21729
- output.writeJson(successResp);
21730
- return;
21731
- }
21921
+ return { path: "/dom/scroll-to", method: "POST", body, timeoutMs: 30000 };
21922
+ };
21923
+ function formatDomScrollToHuman(successResp, { output, options }) {
21924
+ const hasSelector = options.selector != null && options.selector.trim() !== "";
21732
21925
  if (hasSelector && successResp.matches === 0) {
21733
21926
  writeNoElementFound(options.selector, output);
21734
21927
  return;
@@ -21739,40 +21932,37 @@ var runDomScrollTo = async (id, options) => {
21739
21932
  } else {
21740
21933
  output.writeHuman(`Scrolled viewport (scrollX=${successResp.scrollX}, scrollY=${successResp.scrollY})`);
21741
21934
  }
21742
- };
21935
+ }
21743
21936
 
21744
21937
  // dist/commands/domModify.js
21745
21938
  var executeModify = async (id, options, payload, successMessage) => {
21746
- const output = createOutput(options);
21747
- const selector = requireSelector(options, output);
21748
- if (!selector) {
21749
- return;
21750
- }
21751
- const result = await requestWatcherAction({
21752
- id,
21753
- path: "/dom/modify",
21754
- method: "POST",
21755
- body: {
21756
- ...payload,
21757
- selector,
21758
- ...options.text != null ? { text: options.text } : {}
21759
- },
21760
- timeoutMs: 30000
21761
- }, output);
21762
- if (!result) {
21763
- return;
21764
- }
21765
- const successResp = result.data;
21766
- if (options.json) {
21767
- output.writeJson(successResp);
21768
- return;
21769
- }
21770
- if (successResp.matches === 0) {
21771
- writeNoElementFound(selector, output);
21772
- return;
21773
- }
21774
- output.writeHuman(successMessage(successResp));
21939
+ await runDomModifyRequest(id, payload, successMessage, options);
21775
21940
  };
21941
+ var runDomModifyRequest = defineWatcherCommand({
21942
+ build: ([payload], options, output) => {
21943
+ const selector = requireSelector(options, output);
21944
+ if (!selector) {
21945
+ return null;
21946
+ }
21947
+ return {
21948
+ path: "/dom/modify",
21949
+ method: "POST",
21950
+ body: {
21951
+ ...payload,
21952
+ selector,
21953
+ ...options.text != null ? { text: options.text } : {}
21954
+ },
21955
+ timeoutMs: 30000
21956
+ };
21957
+ },
21958
+ formatHuman: (successResp, { output, options, args }) => {
21959
+ if (successResp.matches === 0) {
21960
+ writeNoElementFound(options.selector, output);
21961
+ return;
21962
+ }
21963
+ output.writeHuman(args[1](successResp));
21964
+ }
21965
+ });
21776
21966
  var parseAttrArgs = (attrs) => {
21777
21967
  const result = {};
21778
21968
  for (const attr of attrs) {
@@ -22150,47 +22340,28 @@ Examples:
22150
22340
  }
22151
22341
 
22152
22342
  // dist/commands/domKeydown.js
22153
- var runDomKeydown = async (id, options) => {
22154
- const output = createOutput(options);
22155
- if (!options.key || options.key.trim() === "") {
22156
- output.writeWarn("--key is required");
22157
- process.exitCode = 2;
22158
- return;
22159
- }
22160
- const result = await requestWatcherJson({
22161
- id,
22162
- path: "/dom/keydown",
22163
- method: "POST",
22164
- body: {
22165
- key: options.key,
22166
- selector: options.selector,
22167
- modifiers: options.modifiers
22168
- },
22169
- timeoutMs: 30000,
22170
- returnErrorResponse: true
22171
- });
22172
- if (!result.ok) {
22173
- writeRequestError(result, output);
22174
- return;
22175
- }
22176
- const response = result.data;
22177
- if (!response.ok) {
22178
- const errorResp = response;
22179
- if (options.json) {
22180
- output.writeJson(response);
22181
- } else {
22182
- output.writeWarn(`Error: ${errorResp.error.message}`);
22343
+ var runDomKeydown = defineWatcherCommand({
22344
+ build: (_args, options, output) => {
22345
+ if (!options.key || options.key.trim() === "") {
22346
+ output.writeWarn("--key is required");
22347
+ process.exitCode = 2;
22348
+ return null;
22183
22349
  }
22184
- process.exitCode = 1;
22185
- return;
22186
- }
22187
- const successResp = response;
22188
- if (options.json) {
22189
- output.writeJson(successResp);
22190
- return;
22350
+ return {
22351
+ path: "/dom/keydown",
22352
+ method: "POST",
22353
+ body: {
22354
+ key: options.key,
22355
+ selector: options.selector,
22356
+ modifiers: options.modifiers
22357
+ },
22358
+ timeoutMs: 30000
22359
+ };
22360
+ },
22361
+ formatHuman: (response, { output }) => {
22362
+ output.writeHuman(`Dispatched keydown: ${response.key}`);
22191
22363
  }
22192
- output.writeHuman(`Dispatched keydown: ${successResp.key}`);
22193
- };
22364
+ });
22194
22365
 
22195
22366
  // dist/cli/register/registerKeydown.js
22196
22367
  function registerKeydown(program2) {
@@ -22531,19 +22702,23 @@ Examples:
22531
22702
 
22532
22703
  // dist/commands/screenshot.js
22533
22704
  var SCREENSHOT_REQUEST_TIMEOUT_MS = 45000;
22534
- var runScreenshot = async (id, options) => {
22535
- const output = createOutput(options);
22705
+ var runScreenshot = defineWatcherCommand({
22706
+ build: (_args, options, output) => buildScreenshotPlan(options, output),
22707
+ formatHuman: (response, { output }) => {
22708
+ output.writeHuman(`Screenshot saved: ${response.outFile}`);
22709
+ }
22710
+ });
22711
+ var buildScreenshotPlan = (options, output) => {
22536
22712
  const clip = parseScreenshotClip(options.clip, output);
22537
22713
  if (clip === null) {
22538
- return;
22714
+ return null;
22539
22715
  }
22540
22716
  const selector = normalizeSelector(options.selector);
22541
22717
  if (selector && clip) {
22542
22718
  writeScreenshotOptionError(output, "Cannot use both --selector and --clip");
22543
- return;
22719
+ return null;
22544
22720
  }
22545
- const result = await requestWatcherJson({
22546
- id,
22721
+ return {
22547
22722
  path: "/screenshot",
22548
22723
  method: "POST",
22549
22724
  body: {
@@ -22553,16 +22728,7 @@ var runScreenshot = async (id, options) => {
22553
22728
  format: "png"
22554
22729
  },
22555
22730
  timeoutMs: SCREENSHOT_REQUEST_TIMEOUT_MS
22556
- });
22557
- if (!result.ok) {
22558
- writeRequestError(result, output);
22559
- return;
22560
- }
22561
- if (options.json) {
22562
- output.writeJson(result.data);
22563
- return;
22564
- }
22565
- output.writeHuman(`Screenshot saved: ${result.data.outFile}`);
22731
+ };
22566
22732
  };
22567
22733
  var normalizeSelector = (selector) => {
22568
22734
  const trimmed = selector?.trim();
@@ -22844,12 +23010,13 @@ function resolveActionOptions(options, command) {
22844
23010
 
22845
23011
  // dist/commands/configInit.js
22846
23012
  import fs12 from "node:fs/promises";
22847
- import path12 from "node:path";
23013
+ import path13 from "node:path";
22848
23014
  import { fileURLToPath, pathToFileURL } from "node:url";
22849
23015
  var DEFAULT_CONFIG_PATH = ".argus/config.json";
22850
23016
  var buildConfigTemplate = (schemaRef) => ({
22851
23017
  $schema: schemaRef,
22852
23018
  plugins: [],
23019
+ pluginAliases: {},
22853
23020
  chrome: {
22854
23021
  start: {
22855
23022
  url: "http://localhost:3000",
@@ -22870,12 +23037,12 @@ var buildConfigTemplate = (schemaRef) => ({
22870
23037
  });
22871
23038
  var resolveConfigPath = (cwd, targetPath) => {
22872
23039
  if (!targetPath) {
22873
- return path12.resolve(cwd, DEFAULT_CONFIG_PATH);
23040
+ return path13.resolve(cwd, DEFAULT_CONFIG_PATH);
22874
23041
  }
22875
- return path12.isAbsolute(targetPath) ? targetPath : path12.resolve(cwd, targetPath);
23042
+ return path13.isAbsolute(targetPath) ? targetPath : path13.resolve(cwd, targetPath);
22876
23043
  };
22877
23044
  var ensureParentDir2 = async (filePath) => {
22878
- const dir = path12.dirname(filePath);
23045
+ const dir = path13.dirname(filePath);
22879
23046
  try {
22880
23047
  await fs12.mkdir(dir, { recursive: true });
22881
23048
  return true;
@@ -22954,33 +23121,33 @@ Examples:
22954
23121
  });
22955
23122
  }
22956
23123
 
22957
- // dist/commands/extension/setup.js
22958
- import fs14 from "node:fs";
22959
- import { execSync as execSync2 } from "node:child_process";
22960
-
22961
23124
  // dist/commands/extension/nativeHost.js
22962
23125
  import fs13 from "node:fs";
22963
- import path13 from "node:path";
23126
+ import path14 from "node:path";
22964
23127
  import os4 from "node:os";
22965
23128
  import { execSync } from "node:child_process";
22966
- var HOST_NAME = "com.vforsh.argus.bridge";
23129
+ var BRIDGE_HOST_NAME = "com.vforsh.argus.bridge";
23130
+ var CONTROL_HOST_NAME = "com.vforsh.argus.control";
23131
+ var CONTROL_WATCHER_ID = "extension-control";
23132
+ var HOST_NAMES = [BRIDGE_HOST_NAME, CONTROL_HOST_NAME];
22967
23133
  var getManifestDir = (platform) => {
22968
23134
  switch (platform) {
22969
23135
  case "darwin":
22970
- return path13.join(os4.homedir(), "Library/Application Support/Google/Chrome/NativeMessagingHosts");
23136
+ return path14.join(os4.homedir(), "Library/Application Support/Google/Chrome/NativeMessagingHosts");
22971
23137
  case "linux":
22972
- return path13.join(os4.homedir(), ".config/google-chrome/NativeMessagingHosts");
23138
+ return path14.join(os4.homedir(), ".config/google-chrome/NativeMessagingHosts");
22973
23139
  case "win32":
22974
- return path13.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Google/Chrome/User Data/NativeMessagingHosts");
23140
+ return path14.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Google/Chrome/User Data/NativeMessagingHosts");
22975
23141
  default:
22976
23142
  throw new Error(`Unsupported platform: ${platform}`);
22977
23143
  }
22978
23144
  };
22979
- var getManifestPath = (platform) => {
22980
- return path13.join(getManifestDir(platform), `${HOST_NAME}.json`);
23145
+ var getManifestPath = (platform, hostName = BRIDGE_HOST_NAME) => {
23146
+ return path14.join(getManifestDir(platform), `${hostName}.json`);
22981
23147
  };
22982
- var getWrapperPath = (platform) => {
22983
- return path13.join(getManifestDir(platform), "argus-native-host.sh");
23148
+ var getWrapperPath = (platform, hostName = BRIDGE_HOST_NAME) => {
23149
+ const filename = hostName === CONTROL_HOST_NAME ? "argus-native-control-host.sh" : "argus-native-host.sh";
23150
+ return path14.join(getManifestDir(platform), filename);
22984
23151
  };
22985
23152
  var getPlatform = () => {
22986
23153
  const platform = os4.platform();
@@ -22992,7 +23159,7 @@ var getPlatform = () => {
22992
23159
  var findArgusExecutable = () => {
22993
23160
  try {
22994
23161
  const npmGlobalPrefix = execSync("npm config get prefix", { encoding: "utf8" }).trim();
22995
- const globalBinPath = path13.join(npmGlobalPrefix, "bin", "argus");
23162
+ const globalBinPath = path14.join(npmGlobalPrefix, "bin", "argus");
22996
23163
  if (fs13.existsSync(globalBinPath)) {
22997
23164
  return globalBinPath;
22998
23165
  }
@@ -23008,25 +23175,71 @@ var findArgusExecutable = () => {
23008
23175
  var findNodePath = () => {
23009
23176
  return fs13.realpathSync(process.execPath);
23010
23177
  };
23011
- var createManifest = (extensionId, executablePath) => {
23178
+ var createManifest = (extensionId, executablePath, hostName = BRIDGE_HOST_NAME) => {
23012
23179
  return {
23013
- name: HOST_NAME,
23014
- description: "Argus Watcher Native Messaging Host",
23180
+ name: hostName,
23181
+ description: hostName === CONTROL_HOST_NAME ? "Argus Extension Control Native Messaging Host" : "Argus Watcher Native Messaging Host",
23015
23182
  path: executablePath,
23016
23183
  type: "stdio",
23017
23184
  allowed_origins: [`chrome-extension://${extensionId}/`]
23018
23185
  };
23019
23186
  };
23020
- var createWrapperScript = (platform, executablePath) => {
23021
- const wrapperDir = getManifestDir(platform);
23022
- const wrapperPath = path13.join(wrapperDir, "argus-native-host.sh");
23187
+ var createWrapperScript = (platform, executablePath, hostName = BRIDGE_HOST_NAME) => {
23188
+ const wrapperPath = getWrapperPath(platform, hostName);
23023
23189
  const nodePath = findNodePath();
23190
+ const args = hostName === CONTROL_HOST_NAME ? `watcher native-host --role control --id ${CONTROL_WATCHER_ID}` : "watcher native-host --role tab";
23024
23191
  const script = `#!/bin/bash
23025
- exec "${nodePath}" "${executablePath}" watcher native-host
23192
+ exec "${nodePath}" "${executablePath}" ${args}
23026
23193
  `;
23027
23194
  fs13.writeFileSync(wrapperPath, script, { mode: 493 });
23028
23195
  return wrapperPath;
23029
23196
  };
23197
+ var installNativeHosts = (platform, extensionId, executablePath) => {
23198
+ fs13.mkdirSync(getManifestDir(platform), { recursive: true });
23199
+ return HOST_NAMES.map((hostName) => {
23200
+ const wrapperPath = createWrapperScript(platform, executablePath, hostName);
23201
+ const manifestPath = getManifestPath(platform, hostName);
23202
+ const manifest = createManifest(extensionId, wrapperPath, hostName);
23203
+ fs13.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
23204
+ registerWindowsNativeHost(platform, hostName, manifestPath);
23205
+ return { hostName, manifestPath, wrapperPath };
23206
+ });
23207
+ };
23208
+ var inspectNativeHosts = (platform) => {
23209
+ return HOST_NAMES.map((hostName) => inspectNativeHost(platform, hostName));
23210
+ };
23211
+ var removeNativeHosts = (platform) => {
23212
+ return HOST_NAMES.map((hostName) => {
23213
+ const host = inspectNativeHost(platform, hostName);
23214
+ const manifestRemoved = removeFileIfExists(host.manifestPath);
23215
+ const wrapperRemoved = removeFileIfExists(host.wrapperPath);
23216
+ unregisterWindowsNativeHost(platform, hostName);
23217
+ return { ...host, manifestRemoved, wrapperRemoved };
23218
+ });
23219
+ };
23220
+ var inspectNativeHost = (platform, hostName) => {
23221
+ const manifestPath = getManifestPath(platform, hostName);
23222
+ const wrapperPath = getWrapperPath(platform, hostName);
23223
+ const manifestExists = fs13.existsSync(manifestPath);
23224
+ const wrapperExists = fs13.existsSync(wrapperPath);
23225
+ const wrapperExecutable = wrapperExists && isWrapperExecutable(wrapperPath);
23226
+ const manifest = manifestExists ? readManifest(manifestPath) : null;
23227
+ const manifestValid = Boolean(manifest && manifest.name === hostName && manifest.type === "stdio" && Array.isArray(manifest.allowed_origins));
23228
+ const extensionId = extractExtensionId(manifest?.allowed_origins?.[0]);
23229
+ return {
23230
+ hostName,
23231
+ manifestPath,
23232
+ wrapperPath,
23233
+ manifestExists,
23234
+ manifestValid,
23235
+ wrapperExists,
23236
+ wrapperExecutable,
23237
+ configured: manifestExists && manifestValid && wrapperExists && wrapperExecutable,
23238
+ installed: manifestExists && wrapperExists,
23239
+ extensionId,
23240
+ argusPath: manifest?.path ?? null
23241
+ };
23242
+ };
23030
23243
  var readManifest = (manifestPath) => {
23031
23244
  try {
23032
23245
  const content = fs13.readFileSync(manifestPath, "utf8");
@@ -23050,6 +23263,36 @@ var shortenPath = (filePath) => {
23050
23263
  }
23051
23264
  return filePath;
23052
23265
  };
23266
+ var extractExtensionId = (origin) => {
23267
+ if (!origin) {
23268
+ return null;
23269
+ }
23270
+ const match = origin.match(/^chrome-extension:\/\/([^/]+)\/$/);
23271
+ return match?.[1] ?? null;
23272
+ };
23273
+ var removeFileIfExists = (filePath) => {
23274
+ if (!fs13.existsSync(filePath)) {
23275
+ return false;
23276
+ }
23277
+ fs13.unlinkSync(filePath);
23278
+ return true;
23279
+ };
23280
+ var registerWindowsNativeHost = (platform, hostName, manifestPath) => {
23281
+ if (platform !== "win32") {
23282
+ return;
23283
+ }
23284
+ const regKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`;
23285
+ execSync(`reg add "${regKey}" /ve /t REG_SZ /d "${manifestPath}" /f`, { stdio: "pipe" });
23286
+ };
23287
+ var unregisterWindowsNativeHost = (platform, hostName) => {
23288
+ if (platform !== "win32") {
23289
+ return;
23290
+ }
23291
+ const regKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`;
23292
+ try {
23293
+ execSync(`reg delete "${regKey}" /f`, { stdio: "pipe" });
23294
+ } catch {}
23295
+ };
23053
23296
 
23054
23297
  // dist/commands/extension/setup.js
23055
23298
  var runExtensionSetup = async (options) => {
@@ -23067,10 +23310,6 @@ var runExtensionSetup = async (options) => {
23067
23310
  process.exitCode = 1;
23068
23311
  return;
23069
23312
  }
23070
- const manifestDir = getManifestDir(platform);
23071
- const manifestPath = getManifestPath(platform);
23072
- const wrapperPath = getWrapperPath(platform);
23073
- fs14.mkdirSync(manifestDir, { recursive: true });
23074
23313
  let executablePath;
23075
23314
  try {
23076
23315
  executablePath = findArgusExecutable();
@@ -23083,51 +23322,44 @@ var runExtensionSetup = async (options) => {
23083
23322
  process.exitCode = 1;
23084
23323
  return;
23085
23324
  }
23086
- createWrapperScript(platform, executablePath);
23087
- const manifest = createManifest(extensionId, wrapperPath);
23088
- fs14.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
23089
- if (platform === "win32") {
23090
- const regKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`;
23091
- try {
23092
- execSync2(`reg add "${regKey}" /ve /t REG_SZ /d "${manifestPath}" /f`, { stdio: "pipe" });
23093
- } catch (error) {
23094
- if (options.json) {
23095
- output.writeJson({ success: false, error: `Failed to add registry key: ${error.message}` });
23096
- } else {
23097
- console.error("Failed to add registry key:", error);
23098
- }
23099
- process.exitCode = 1;
23100
- return;
23325
+ let installedHosts;
23326
+ try {
23327
+ installedHosts = installNativeHosts(platform, extensionId, executablePath);
23328
+ } catch (error) {
23329
+ if (options.json) {
23330
+ output.writeJson({ success: false, error: `Failed to install native hosts: ${error.message}` });
23331
+ } else {
23332
+ console.error("Failed to install native hosts:", error);
23101
23333
  }
23334
+ process.exitCode = 1;
23335
+ return;
23102
23336
  }
23103
23337
  if (options.json) {
23104
23338
  output.writeJson({
23105
23339
  success: true,
23106
- hostName: HOST_NAME,
23107
23340
  extensionId,
23108
- manifestPath,
23109
- wrapperPath,
23341
+ hosts: installedHosts,
23110
23342
  argusPath: executablePath
23111
23343
  });
23112
23344
  } else {
23113
23345
  output.writeHuman("");
23114
- output.writeHuman("Native messaging host installed");
23346
+ output.writeHuman("Native messaging hosts installed");
23115
23347
  output.writeHuman("");
23116
- output.writeHuman(` Host name: ${HOST_NAME}`);
23117
23348
  output.writeHuman(` Extension ID: ${extensionId}`);
23118
- output.writeHuman(` Manifest: ${shortenPath(manifestPath)}`);
23119
- output.writeHuman(` Wrapper: ${shortenPath(wrapperPath)}`);
23349
+ for (const host of installedHosts) {
23350
+ output.writeHuman(` Host name: ${host.hostName}`);
23351
+ output.writeHuman(` Manifest: ${shortenPath(host.manifestPath)}`);
23352
+ output.writeHuman(` Wrapper: ${shortenPath(host.wrapperPath)}`);
23353
+ }
23120
23354
  output.writeHuman("");
23121
23355
  output.writeHuman("Next steps:");
23122
23356
  output.writeHuman(" 1. Reload the extension in chrome://extensions");
23123
- output.writeHuman(" 2. Open the extension popup and attach a tab");
23357
+ output.writeHuman(" 2. Run `argus ext tabs` or open the extension popup and attach a tab");
23124
23358
  output.writeHuman("");
23125
23359
  }
23126
23360
  };
23127
23361
 
23128
23362
  // dist/commands/extension/remove.js
23129
- import fs15 from "node:fs";
23130
- import { execSync as execSync3 } from "node:child_process";
23131
23363
  var runExtensionRemove = async (options) => {
23132
23364
  const output = createOutput(options);
23133
23365
  let platform;
@@ -23142,55 +23374,42 @@ var runExtensionRemove = async (options) => {
23142
23374
  process.exitCode = 1;
23143
23375
  return;
23144
23376
  }
23145
- const manifestPath = getManifestPath(platform);
23146
- const wrapperPath = getWrapperPath(platform);
23147
- let manifestRemoved = false;
23148
- let wrapperRemoved = false;
23149
- if (fs15.existsSync(manifestPath)) {
23150
- fs15.unlinkSync(manifestPath);
23151
- manifestRemoved = true;
23152
- }
23153
- if (fs15.existsSync(wrapperPath)) {
23154
- fs15.unlinkSync(wrapperPath);
23155
- wrapperRemoved = true;
23156
- }
23157
- if (platform === "win32") {
23158
- const regKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`;
23159
- try {
23160
- execSync3(`reg delete "${regKey}" /f`, { stdio: "pipe" });
23161
- } catch {}
23162
- }
23377
+ const removedHosts = removeNativeHosts(platform);
23378
+ const manifestRemoved = removedHosts.some((host) => host.manifestRemoved);
23379
+ const wrapperRemoved = removedHosts.some((host) => host.wrapperRemoved);
23163
23380
  if (options.json) {
23164
23381
  output.writeJson({
23165
23382
  success: true,
23166
23383
  manifestRemoved,
23167
23384
  wrapperRemoved,
23168
- manifestPath,
23169
- wrapperPath
23385
+ hosts: removedHosts
23170
23386
  });
23171
23387
  } else {
23172
23388
  output.writeHuman("");
23173
23389
  if (manifestRemoved || wrapperRemoved) {
23174
- output.writeHuman("Native messaging host removed");
23390
+ output.writeHuman("Native messaging hosts removed");
23175
23391
  output.writeHuman("");
23176
- if (manifestRemoved) {
23177
- output.writeHuman(` Removed manifest: ${shortenPath(manifestPath)}`);
23178
- }
23179
- if (wrapperRemoved) {
23180
- output.writeHuman(` Removed wrapper: ${shortenPath(wrapperPath)}`);
23392
+ for (const host of removedHosts) {
23393
+ if (host.manifestRemoved) {
23394
+ output.writeHuman(` Removed manifest: ${shortenPath(host.manifestPath)}`);
23395
+ }
23396
+ if (host.wrapperRemoved) {
23397
+ output.writeHuman(` Removed wrapper: ${shortenPath(host.wrapperPath)}`);
23398
+ }
23181
23399
  }
23182
23400
  } else {
23183
- output.writeHuman("Native messaging host was not installed");
23401
+ output.writeHuman("Native messaging hosts were not installed");
23184
23402
  output.writeHuman("");
23185
- output.writeHuman(` Manifest not found: ${shortenPath(manifestPath)}`);
23186
- output.writeHuman(` Wrapper not found: ${shortenPath(wrapperPath)}`);
23403
+ for (const host of removedHosts) {
23404
+ output.writeHuman(` Manifest not found: ${shortenPath(host.manifestPath)}`);
23405
+ output.writeHuman(` Wrapper not found: ${shortenPath(host.wrapperPath)}`);
23406
+ }
23187
23407
  }
23188
23408
  output.writeHuman("");
23189
23409
  }
23190
23410
  };
23191
23411
 
23192
23412
  // dist/commands/extension/status.js
23193
- import fs16 from "node:fs";
23194
23413
  var runExtensionStatus = async (options) => {
23195
23414
  const output = createOutput(options);
23196
23415
  let platform;
@@ -23205,40 +23424,14 @@ var runExtensionStatus = async (options) => {
23205
23424
  process.exitCode = 1;
23206
23425
  return;
23207
23426
  }
23208
- const manifestPath = getManifestPath(platform);
23209
- const wrapperPath = getWrapperPath(platform);
23210
- const manifestExists = fs16.existsSync(manifestPath);
23211
- const wrapperExists = fs16.existsSync(wrapperPath);
23212
- const wrapperExecutable = wrapperExists && isWrapperExecutable(wrapperPath);
23213
- let manifest = null;
23214
- let manifestValid = false;
23215
- let extensionId = null;
23216
- if (manifestExists) {
23217
- manifest = readManifest(manifestPath);
23218
- if (manifest && manifest.name === HOST_NAME && manifest.type === "stdio" && Array.isArray(manifest.allowed_origins)) {
23219
- manifestValid = true;
23220
- const origin = manifest.allowed_origins[0];
23221
- if (origin) {
23222
- const match = origin.match(/^chrome-extension:\/\/([^/]+)\/$/);
23223
- if (match) {
23224
- extensionId = match[1];
23225
- }
23226
- }
23227
- }
23228
- }
23229
- const configured = manifestExists && manifestValid && wrapperExists && wrapperExecutable;
23427
+ const hosts = inspectNativeHosts(platform);
23428
+ const configured = hosts.every((host) => host.configured);
23429
+ const extensionId = hosts.find((host) => host.extensionId)?.extensionId ?? null;
23230
23430
  if (options.json) {
23231
23431
  output.writeJson({
23232
23432
  configured,
23233
- hostName: HOST_NAME,
23234
- manifestPath,
23235
- wrapperPath,
23236
- manifestExists,
23237
- manifestValid,
23238
- wrapperExists,
23239
- wrapperExecutable,
23240
- extensionId,
23241
- argusPath: manifest?.path ?? null
23433
+ hosts,
23434
+ extensionId
23242
23435
  });
23243
23436
  if (!configured) {
23244
23437
  process.exitCode = 1;
@@ -23247,16 +23440,17 @@ var runExtensionStatus = async (options) => {
23247
23440
  }
23248
23441
  output.writeHuman("");
23249
23442
  if (configured) {
23250
- output.writeHuman("Native messaging host is configured");
23443
+ output.writeHuman("Native messaging hosts are configured");
23251
23444
  output.writeHuman("");
23252
- output.writeHuman(` Manifest: exists`);
23253
- output.writeHuman(` Wrapper: exists, executable`);
23254
23445
  output.writeHuman(` Extension ID: ${extensionId}`);
23255
23446
  } else {
23256
- output.writeHuman("Native messaging host not configured");
23447
+ output.writeHuman("Native messaging hosts not configured");
23257
23448
  output.writeHuman("");
23258
- output.writeHuman(` Manifest: ${manifestExists ? manifestValid ? "exists" : "exists, invalid" : "not found"}`);
23259
- output.writeHuman(` Wrapper: ${wrapperExists ? wrapperExecutable ? "exists, executable" : "exists, not executable" : "not found"}`);
23449
+ for (const host of hosts) {
23450
+ output.writeHuman(` ${host.hostName}`);
23451
+ output.writeHuman(` Manifest: ${host.manifestExists ? host.manifestValid ? "exists" : "exists, invalid" : "not found"}`);
23452
+ output.writeHuman(` Wrapper: ${host.wrapperExists ? host.wrapperExecutable ? "exists, executable" : "exists, not executable" : "not found"}`);
23453
+ }
23260
23454
  if (extensionId) {
23261
23455
  output.writeHuman(` Extension ID: ${extensionId}`);
23262
23456
  }
@@ -23268,7 +23462,6 @@ var runExtensionStatus = async (options) => {
23268
23462
  };
23269
23463
 
23270
23464
  // dist/commands/extension/info.js
23271
- import fs17 from "node:fs";
23272
23465
  var runExtensionInfo = async (options) => {
23273
23466
  const output = createOutput(options);
23274
23467
  let platform;
@@ -23283,51 +23476,32 @@ var runExtensionInfo = async (options) => {
23283
23476
  process.exitCode = 1;
23284
23477
  return;
23285
23478
  }
23286
- const manifestPath = getManifestPath(platform);
23287
- const wrapperPath = getWrapperPath(platform);
23288
- const manifestExists = fs17.existsSync(manifestPath);
23289
- const wrapperExists = fs17.existsSync(wrapperPath);
23290
- let extensionId = null;
23291
- let argusPath = null;
23292
- if (manifestExists) {
23293
- const manifest = readManifest(manifestPath);
23294
- if (manifest) {
23295
- argusPath = manifest.path;
23296
- const origin = manifest.allowed_origins?.[0];
23297
- if (origin) {
23298
- const match = origin.match(/^chrome-extension:\/\/([^/]+)\/$/);
23299
- if (match) {
23300
- extensionId = match[1];
23301
- }
23302
- }
23303
- }
23304
- }
23305
- const installed = manifestExists && wrapperExists;
23479
+ const hosts = inspectNativeHosts(platform);
23480
+ const installed = hosts.every((host) => host.installed);
23306
23481
  if (options.json) {
23307
23482
  output.writeJson({
23308
- hostName: HOST_NAME,
23309
23483
  platform,
23310
- manifestPath,
23311
- wrapperPath,
23312
23484
  installed,
23313
- extensionId,
23314
- argusPath
23485
+ hosts
23315
23486
  });
23316
23487
  return;
23317
23488
  }
23318
23489
  output.writeHuman("");
23319
23490
  output.writeHuman("Native Messaging Host Info");
23320
23491
  output.writeHuman("");
23321
- output.writeHuman(` Host name: ${HOST_NAME}`);
23322
23492
  output.writeHuman(` Platform: ${platform}`);
23323
- output.writeHuman(` Manifest path: ${shortenPath(manifestPath)}`);
23324
- output.writeHuman(` Wrapper path: ${shortenPath(wrapperPath)}`);
23493
+ for (const host of hosts) {
23494
+ output.writeHuman(` Host name: ${host.hostName}`);
23495
+ output.writeHuman(` Manifest path: ${shortenPath(host.manifestPath)}`);
23496
+ output.writeHuman(` Wrapper path: ${shortenPath(host.wrapperPath)}`);
23497
+ }
23325
23498
  output.writeHuman("");
23326
23499
  output.writeHuman("Current configuration:");
23327
23500
  output.writeHuman(` Installed: ${installed ? "yes" : "no"}`);
23328
- if (installed) {
23329
- output.writeHuman(` Extension ID: ${extensionId ?? "(unknown)"}`);
23330
- output.writeHuman(` Argus path: ${argusPath ?? "(unknown)"}`);
23501
+ for (const host of hosts.filter((entry) => entry.installed)) {
23502
+ output.writeHuman(` ${host.hostName}`);
23503
+ output.writeHuman(` Extension ID: ${host.extensionId ?? "(unknown)"}`);
23504
+ output.writeHuman(` Argus path: ${host.argusPath ?? "(unknown)"}`);
23331
23505
  }
23332
23506
  output.writeHuman("");
23333
23507
  };
@@ -23355,38 +23529,23 @@ var resolveExtensionWatcher = async (input) => {
23355
23529
  if (extensionWatchers.length === 0) {
23356
23530
  return {
23357
23531
  ok: false,
23358
- error: "No extension-backed watchers found. Attach a tab in the extension popup first.",
23532
+ error: "No extension-backed watchers found. Reload the extension after `argus extension setup`, or attach a tab in the extension popup.",
23359
23533
  exitCode: 2
23360
23534
  };
23361
23535
  }
23362
- const reachability = await Promise.all(extensionWatchers.map(async (watcher) => ({
23363
- watcher,
23364
- status: await checkWatcherStatus3(watcher)
23365
- })));
23366
- const attached = reachability.filter((entry) => entry.status.ok && entry.status.status.attached);
23367
- if (attached.length === 1) {
23368
- return { ok: true, watcher: attached[0].watcher, registry: registry2 };
23369
- }
23370
- const cwd = process.cwd();
23371
- const cwdMatches = extensionWatchers.filter((watcher) => watcher.cwd === cwd);
23372
- const attachedCwdMatches = attached.filter((entry) => entry.watcher.cwd === cwd);
23373
- if (attachedCwdMatches.length === 1) {
23374
- return { ok: true, watcher: attachedCwdMatches[0].watcher, registry: registry2 };
23375
- }
23376
- if (attached.length > 1) {
23377
- return { ok: false, error: "Watcher id required.", exitCode: 2, candidates: attached.map((entry) => entry.watcher) };
23378
- }
23379
- if (cwdMatches.length === 1) {
23380
- return { ok: true, watcher: cwdMatches[0], registry: registry2 };
23381
- }
23382
- if (cwdMatches.length > 1) {
23383
- return { ok: false, error: "Watcher id required.", exitCode: 2, candidates: cwdMatches };
23384
- }
23385
- const reachable = reachability.filter((entry) => entry.status.ok);
23386
- if (reachable.length === 1) {
23387
- return { ok: true, watcher: reachable[0].watcher, registry: registry2 };
23536
+ const controlWatcher = registry2.watchers[CONTROL_WATCHER_ID];
23537
+ if (controlWatcher?.source === "extension") {
23538
+ const status = await checkWatcherStatus3(controlWatcher);
23539
+ if (status.ok) {
23540
+ return { ok: true, watcher: controlWatcher, registry: registry2 };
23541
+ }
23388
23542
  }
23389
- return { ok: false, error: "Watcher id required.", exitCode: 2, candidates: extensionWatchers };
23543
+ return {
23544
+ ok: false,
23545
+ error: "extension-control watcher is unavailable. Reload the extension after `argus extension setup`.",
23546
+ exitCode: 2,
23547
+ candidates: extensionWatchers
23548
+ };
23390
23549
  };
23391
23550
  var getExtensionWatchers = (watchers) => watchers.filter((watcher) => watcher.source === "extension");
23392
23551
  var checkWatcherStatus3 = async (watcher) => {
@@ -23479,6 +23638,173 @@ var writeCommandError = (output, options, humanMessage, jsonPayload) => {
23479
23638
  };
23480
23639
  var formatError4 = (error) => error instanceof Error ? error.message : String(error);
23481
23640
 
23641
+ // dist/commands/extension/attach.js
23642
+ var runExtensionAttach = async (options) => {
23643
+ await runExtensionTabAction("attach", options);
23644
+ };
23645
+ var runExtensionDetach = async (options) => {
23646
+ await runExtensionTabAction("detach", options);
23647
+ };
23648
+ var runExtensionTabAction = async (action, options) => {
23649
+ const output = createOutput(options);
23650
+ const resolved = await resolveExtensionWatcher({});
23651
+ if (!resolved.ok) {
23652
+ writeResolveFailure2(output, options, resolved);
23653
+ return;
23654
+ }
23655
+ const selector = parseTabSelector(options);
23656
+ if (!selector.ok) {
23657
+ writeTabResolutionFailure(output, options, selector);
23658
+ return;
23659
+ }
23660
+ const tabs = await fetchTabs(resolved.watcher, selector.selector, output, options);
23661
+ if (!tabs) {
23662
+ return;
23663
+ }
23664
+ const tab = resolveTab(tabs, selector.selector);
23665
+ if (!tab.ok) {
23666
+ writeTabResolutionFailure(output, options, tab);
23667
+ return;
23668
+ }
23669
+ let response;
23670
+ try {
23671
+ response = await fetchWatcherJson(resolved.watcher, {
23672
+ path: action === "attach" ? "/attach" : "/detach",
23673
+ method: "POST",
23674
+ body: { tabId: tab.tab.tabId },
23675
+ timeoutMs: 5000,
23676
+ returnErrorResponse: true
23677
+ });
23678
+ } catch (error) {
23679
+ const message = error instanceof Error ? error.message : String(error);
23680
+ writeCommandError2(output, options, `${resolved.watcher.id}: failed to ${action} tab (${message})`, message);
23681
+ return;
23682
+ }
23683
+ if (!response.ok) {
23684
+ writeCommandError2(output, options, `Error: ${response.error.message}`, response);
23685
+ return;
23686
+ }
23687
+ if (options.json) {
23688
+ output.writeJson({
23689
+ ok: true,
23690
+ tab: tab.tab,
23691
+ watcherId: resolved.watcher.id
23692
+ });
23693
+ return;
23694
+ }
23695
+ const verb = action === "attach" ? "Attach" : "Detach";
23696
+ output.writeHuman(`${verb} request sent via ${resolved.watcher.id}`);
23697
+ output.writeHuman(` ${formatExtensionTabLine2(tab.tab)}`);
23698
+ };
23699
+ var fetchTabs = async (watcher, selector, output, options) => {
23700
+ const query = buildTabsQuery2(selector);
23701
+ try {
23702
+ const response = await fetchWatcherJson(watcher, {
23703
+ path: "/tabs",
23704
+ query,
23705
+ timeoutMs: 5000,
23706
+ returnErrorResponse: true
23707
+ });
23708
+ if (!response.ok) {
23709
+ writeCommandError2(output, options, `Error: ${response.error.message}`, response);
23710
+ return null;
23711
+ }
23712
+ return response.tabs;
23713
+ } catch (error) {
23714
+ const message = error instanceof Error ? error.message : String(error);
23715
+ writeCommandError2(output, options, `${watcher.id}: failed to list extension tabs (${message})`, message);
23716
+ return null;
23717
+ }
23718
+ };
23719
+ var parseTabSelector = (options) => {
23720
+ const tabId = parseTabId(options.tab);
23721
+ const url = options.url?.trim();
23722
+ const title = options.title?.trim();
23723
+ if (tabId !== null && (url || title)) {
23724
+ return { ok: false, reason: "Use --tab by itself, or resolve by --url/--title.", exitCode: 2 };
23725
+ }
23726
+ if (tabId === null && !url && !title) {
23727
+ return { ok: false, reason: "Specify --tab <tabId>, --url <substring>, or --title <substring>.", exitCode: 2 };
23728
+ }
23729
+ if (options.tab != null && tabId === null) {
23730
+ return { ok: false, reason: `Invalid --tab value: ${options.tab}`, exitCode: 2 };
23731
+ }
23732
+ if (tabId !== null) {
23733
+ return { ok: true, selector: { kind: "tab", tabId } };
23734
+ }
23735
+ return { ok: true, selector: { kind: "query", url, title } };
23736
+ };
23737
+ var resolveTab = (tabs, selector) => {
23738
+ const matches = selector.kind === "tab" ? tabs.filter((tab) => tab.tabId === selector.tabId) : tabs;
23739
+ if (matches.length === 0) {
23740
+ return { ok: false, reason: "No extension tab matched.", exitCode: 2 };
23741
+ }
23742
+ if (matches.length > 1) {
23743
+ return { ok: false, reason: "Multiple extension tabs matched. Use --tab to pick one.", exitCode: 2, matches };
23744
+ }
23745
+ return { ok: true, tab: matches[0] };
23746
+ };
23747
+ var parseTabId = (value) => {
23748
+ if (value === undefined) {
23749
+ return null;
23750
+ }
23751
+ const tabId = typeof value === "number" ? value : Number(value);
23752
+ return Number.isInteger(tabId) ? tabId : null;
23753
+ };
23754
+ var buildTabsQuery2 = (selector) => {
23755
+ const query = new URLSearchParams;
23756
+ if (selector.kind === "tab") {
23757
+ return query;
23758
+ }
23759
+ if (selector.url) {
23760
+ query.set("url", selector.url);
23761
+ }
23762
+ if (selector.title) {
23763
+ query.set("title", selector.title);
23764
+ }
23765
+ return query;
23766
+ };
23767
+ var writeTabResolutionFailure = (output, options, result) => {
23768
+ const matches = "matches" in result ? result.matches : undefined;
23769
+ if (options.json) {
23770
+ output.writeJson({ ok: false, error: result.reason, matches: matches ?? [] });
23771
+ } else {
23772
+ output.writeWarn(result.reason);
23773
+ if (matches) {
23774
+ for (const tab of matches) {
23775
+ output.writeWarn(` ${formatExtensionTabLine2(tab)}`);
23776
+ }
23777
+ }
23778
+ }
23779
+ process.exitCode = result.exitCode;
23780
+ };
23781
+ var writeResolveFailure2 = (output, options, resolved) => {
23782
+ if (options.json) {
23783
+ output.writeJson({ ok: false, error: resolved.error, candidates: resolved.candidates?.map((watcher) => watcher.id) ?? [] });
23784
+ } else {
23785
+ output.writeWarn(resolved.error);
23786
+ if (resolved.candidates && resolved.candidates.length > 0) {
23787
+ for (const watcher of resolved.candidates) {
23788
+ output.writeWarn(formatWatcherLine(watcher));
23789
+ }
23790
+ }
23791
+ }
23792
+ process.exitCode = resolved.exitCode;
23793
+ };
23794
+ var writeCommandError2 = (output, options, humanMessage, jsonPayload) => {
23795
+ if (options.json) {
23796
+ output.writeJson(typeof jsonPayload === "string" ? { ok: false, error: jsonPayload } : jsonPayload);
23797
+ } else {
23798
+ output.writeWarn(humanMessage);
23799
+ }
23800
+ process.exitCode = 1;
23801
+ };
23802
+ var formatExtensionTabLine2 = (tab) => {
23803
+ const state2 = tab.attached ? "attached" : "available";
23804
+ const label = tab.title || "(untitled)";
23805
+ return `${tab.tabId} [${state2}] ${label} - ${tab.url}`;
23806
+ };
23807
+
23482
23808
  // dist/cli/register/registerExtension.js
23483
23809
  function registerExtension(program2) {
23484
23810
  const extension = program2.command("extension").alias("ext").description("Browser extension management");
@@ -23500,7 +23826,7 @@ To get your extension ID:
23500
23826
  extension.command("info").description("Show native messaging host paths and configuration").option("--json", "Output JSON for automation").action(async (options) => {
23501
23827
  await runExtensionInfo(options);
23502
23828
  });
23503
- extension.command("tabs").description("List browser tabs visible to the extension transport").option("--id <watcherId>", "Extension-backed watcher id to use as the transport").option("--url <substring>", "Filter tabs by URL substring").option("--title <substring>", "Filter tabs by title substring").option("--json", "Output JSON for automation").addHelpText("after", `
23829
+ extension.command("tabs").description("List browser tabs visible to the extension transport").option("--id <watcherId>", "Extension-backed watcher id to use as the transport (default: extension-control)").option("--url <substring>", "Filter tabs by URL substring").option("--title <substring>", "Filter tabs by title substring").option("--json", "Output JSON for automation").addHelpText("after", `
23504
23830
  Examples:
23505
23831
  $ argus ext tabs
23506
23832
  $ argus ext tabs --url localhost
@@ -23509,6 +23835,22 @@ Examples:
23509
23835
  `).action(async (options) => {
23510
23836
  await runExtensionTabs(options);
23511
23837
  });
23838
+ extension.command("attach").description("Ask the extension control watcher to attach a browser tab").option("--tab <tabId>", "Browser tab id to attach").option("--url <substring>", "Resolve tab by URL substring").option("--title <substring>", "Resolve tab by title substring").option("--json", "Output JSON for automation").addHelpText("after", `
23839
+ Examples:
23840
+ $ argus ext attach --tab 123
23841
+ $ argus ext attach --url localhost
23842
+ $ argus ext attach --title Docs --json
23843
+ `).action(async (options) => {
23844
+ await runExtensionAttach(options);
23845
+ });
23846
+ extension.command("detach").description("Ask the extension control watcher to detach a browser tab").option("--tab <tabId>", "Browser tab id to detach").option("--url <substring>", "Resolve tab by URL substring").option("--title <substring>", "Resolve tab by title substring").option("--json", "Output JSON for automation").addHelpText("after", `
23847
+ Examples:
23848
+ $ argus ext detach --tab 123
23849
+ $ argus ext detach --url localhost
23850
+ $ argus ext detach --title Docs --json
23851
+ `).action(async (options) => {
23852
+ await runExtensionDetach(options);
23853
+ });
23512
23854
  }
23513
23855
 
23514
23856
  // dist/output/code.js
@@ -33771,7 +34113,7 @@ function hr2(a2, t, e) {
33771
34113
  }
33772
34114
  return t;
33773
34115
  }
33774
- var fs18 = hr2;
34116
+ var fs14 = hr2;
33775
34117
  function cr2(a2, t) {
33776
34118
  return t === false ? false : a2.charAt(t) === "/" && a2.charAt(t + 1) === "/" ? ls(a2, t) : t;
33777
34119
  }
@@ -33779,7 +34121,7 @@ var ds = cr2;
33779
34121
  function lr2(a2, t) {
33780
34122
  let e = null, s = t;
33781
34123
  for (;s !== e; )
33782
- e = s, s = cs(a2, s), s = ps(a2, s), s = ds(a2, s), s = fs18(a2, s);
34124
+ e = s, s = cs(a2, s), s = ps(a2, s), s = ds(a2, s), s = fs14(a2, s);
33783
34125
  return s;
33784
34126
  }
33785
34127
  var ms = lr2;
@@ -35779,13 +36121,13 @@ function kc({ node: e, parent: t }) {
35779
36121
  let r = /^[fx]?(?:describe|it|test)$/u;
35780
36122
  return t.type === "TaggedTemplateExpression" && t.quasi === e && t.tag.type === "MemberExpression" && t.tag.property.type === "Identifier" && t.tag.property.name === "each" && (t.tag.object.type === "Identifier" && r.test(t.tag.object.name) || t.tag.object.type === "MemberExpression" && t.tag.object.property.type === "Identifier" && (t.tag.object.property.name === "only" || t.tag.object.property.name === "skip") && t.tag.object.object.type === "Identifier" && r.test(t.tag.object.object.name));
35781
36123
  }
35782
- var fs19 = [(e, t) => e.type === "ObjectExpression" && t === "properties", (e, t) => e.type === "CallExpression" && e.callee.type === "Identifier" && e.callee.name === "Component" && t === "arguments", (e, t) => e.type === "Decorator" && t === "expression"];
36124
+ var fs15 = [(e, t) => e.type === "ObjectExpression" && t === "properties", (e, t) => e.type === "CallExpression" && e.callee.type === "Identifier" && e.callee.name === "Component" && t === "arguments", (e, t) => e.type === "Decorator" && t === "expression"];
35783
36125
  function oo2(e) {
35784
36126
  let t = (n) => n.type === "TemplateLiteral", r = (n, s) => Oe3(n) && !n.computed && n.key.type === "Identifier" && n.key.name === "styles" && s === "value";
35785
- return e.match(t, (n, s) => q3(n) && s === "elements", r, ...fs19) || e.match(t, r, ...fs19);
36127
+ return e.match(t, (n, s) => q3(n) && s === "elements", r, ...fs15) || e.match(t, r, ...fs15);
35786
36128
  }
35787
36129
  function ys2(e) {
35788
- return e.match((t) => t.type === "TemplateLiteral", (t, r) => Oe3(t) && !t.computed && t.key.type === "Identifier" && t.key.name === "template" && r === "value", ...fs19);
36130
+ return e.match((t) => t.type === "TemplateLiteral", (t, r) => Oe3(t) && !t.computed && t.key.type === "Identifier" && t.key.name === "template" && r === "value", ...fs15);
35789
36131
  }
35790
36132
  function Ds2(e, t) {
35791
36133
  return T3(e, x2.Block | x2.Leading, ({ value: r }) => r === ` ${t} `);
@@ -39692,7 +40034,7 @@ var ft4 = w4((gx, Mi3) => {
39692
40034
  ct4.default = ct4;
39693
40035
  });
39694
40036
  var fe3 = w4((wx, zi3) => {
39695
- var Bi3 = Me4(), Ui3 = ft4(), Fc2 = ut4(), { isClean: Fi3, my: $i3 } = Ht4(), fs20, Wi3, Gi3, ps3;
40037
+ var Bi3 = Me4(), Ui3 = ft4(), Fc2 = ut4(), { isClean: Fi3, my: $i3 } = Ht4(), fs16, Wi3, Gi3, ps3;
39696
40038
  function Yi3(t) {
39697
40039
  return t.map((e) => (e.nodes && (e.nodes = Yi3(e.nodes)), delete e.source, e));
39698
40040
  }
@@ -39791,7 +40133,7 @@ var fe3 = w4((wx, zi3) => {
39791
40133
  } else if (e.selector || e.selectors)
39792
40134
  e = [new ps3(e)];
39793
40135
  else if (e.name)
39794
- e = [new fs20(e)];
40136
+ e = [new fs16(e)];
39795
40137
  else if (e.text)
39796
40138
  e = [new Bi3(e)];
39797
40139
  else
@@ -39893,7 +40235,7 @@ var fe3 = w4((wx, zi3) => {
39893
40235
  ps3 = t;
39894
40236
  };
39895
40237
  z3.registerAtRule = (t) => {
39896
- fs20 = t;
40238
+ fs16 = t;
39897
40239
  };
39898
40240
  z3.registerRoot = (t) => {
39899
40241
  Gi3 = t;
@@ -39901,7 +40243,7 @@ var fe3 = w4((wx, zi3) => {
39901
40243
  zi3.exports = z3;
39902
40244
  z3.default = z3;
39903
40245
  z3.rebuild = (t) => {
39904
- t.type === "atrule" ? Object.setPrototypeOf(t, fs20.prototype) : t.type === "rule" ? Object.setPrototypeOf(t, ps3.prototype) : t.type === "decl" ? Object.setPrototypeOf(t, Ui3.prototype) : t.type === "comment" ? Object.setPrototypeOf(t, Bi3.prototype) : t.type === "root" && Object.setPrototypeOf(t, Gi3.prototype), t[$i3] = true, t.nodes && t.nodes.forEach((e) => {
40246
+ t.type === "atrule" ? Object.setPrototypeOf(t, fs16.prototype) : t.type === "rule" ? Object.setPrototypeOf(t, ps3.prototype) : t.type === "decl" ? Object.setPrototypeOf(t, Ui3.prototype) : t.type === "comment" ? Object.setPrototypeOf(t, Bi3.prototype) : t.type === "root" && Object.setPrototypeOf(t, Gi3.prototype), t[$i3] = true, t.nodes && t.nodes.forEach((e) => {
39905
40247
  z3.rebuild(e);
39906
40248
  });
39907
40249
  };
@@ -45252,30 +45594,9 @@ var writeCodeGrepWarnings = (skippedResources, output) => {
45252
45594
  }
45253
45595
  output.writeWarn(warning);
45254
45596
  };
45255
- var writeErrorResponse2 = (response, output) => {
45256
- if (output.json) {
45257
- output.writeJson(response);
45258
- } else {
45259
- output.writeWarn(`Error: ${response.error.message}`);
45260
- }
45261
- process.exitCode = 1;
45262
- };
45263
45597
  var requestCodeResponse = async (id, output, input) => {
45264
- const result = await requestWatcherJson({
45265
- ...input,
45266
- id,
45267
- timeoutMs: 30000,
45268
- returnErrorResponse: true
45269
- });
45270
- if (!result.ok) {
45271
- writeRequestError(result, output);
45272
- return null;
45273
- }
45274
- if (!result.data.ok) {
45275
- writeErrorResponse2(result.data, output);
45276
- return null;
45277
- }
45278
- return result.data;
45598
+ const result = await requestWatcherCommandAction({ ...input, id, timeoutMs: 30000 }, output);
45599
+ return result?.data ?? null;
45279
45600
  };
45280
45601
 
45281
45602
  // dist/commands/codeEdit.js
@@ -45292,7 +45613,7 @@ var runCodeEdit = async (id, url, options) => {
45292
45613
  if (source === null) {
45293
45614
  return;
45294
45615
  }
45295
- const result = await requestWatcherAction({
45616
+ const result = await requestWatcherCommandAction({
45296
45617
  id,
45297
45618
  path: "/code/edit",
45298
45619
  method: "POST",
@@ -45327,9 +45648,9 @@ async function resolveEditSource(id, url, options, output) {
45327
45648
  }
45328
45649
  return readStdinSource(output);
45329
45650
  }
45330
- async function readFileSource(path14, output) {
45651
+ async function readFileSource(path15, output) {
45331
45652
  try {
45332
- return { text: await readFile6(path14, "utf8") };
45653
+ return { text: await readFile6(path15, "utf8") };
45333
45654
  } catch (error) {
45334
45655
  output.writeWarn(`Failed to read file: ${error instanceof Error ? error.message : error}`);
45335
45656
  process.exitCode = 1;
@@ -45403,7 +45724,7 @@ async function loadFullSource(id, url, output) {
45403
45724
  const chunks = [];
45404
45725
  let offset = 0;
45405
45726
  for (;; ) {
45406
- const result = await requestWatcherAction({
45727
+ const result = await requestWatcherCommandAction({
45407
45728
  id,
45408
45729
  path: "/code/read",
45409
45730
  method: "POST",
@@ -45538,7 +45859,7 @@ var runLocateLabel = async (id, label, options) => {
45538
45859
  exact: options.exact
45539
45860
  }, options);
45540
45861
  };
45541
- var runLocateRequest = async (id, path14, body, options) => {
45862
+ var runLocateRequest = async (id, path15, body, options) => {
45542
45863
  const output = createOutput(options);
45543
45864
  const action = normalizeLocateAction(options.action, output);
45544
45865
  if (action == null && options.action) {
@@ -45554,9 +45875,9 @@ var runLocateRequest = async (id, path14, body, options) => {
45554
45875
  process.exitCode = 2;
45555
45876
  return;
45556
45877
  }
45557
- const result = await requestWatcherAction({
45878
+ const result = await requestWatcherCommandAction({
45558
45879
  id,
45559
- path: path14,
45880
+ path: path15,
45560
45881
  method: "POST",
45561
45882
  body,
45562
45883
  timeoutMs: 30000
@@ -45643,44 +45964,60 @@ Examples:
45643
45964
  });
45644
45965
  }
45645
45966
 
45646
- // dist/cli/register/index.js
45647
- var registerDefinedCommands = (program2) => {
45648
- defineCommands(program2, [domClickCommandDefinition]);
45649
- };
45650
- var coreProgramRegistrars = [
45651
- registerQuickAccess,
45652
- registerChrome,
45653
- registerWatcher,
45654
- registerPage,
45655
- registerDialog,
45656
- registerLogs,
45657
- registerNet,
45658
- registerAuth,
45659
- registerEval,
45660
- registerLocate,
45661
- registerCode,
45662
- registerDom,
45663
- registerDefinedCommands,
45664
- registerKeydown,
45665
- registerFill,
45666
- registerHover,
45667
- registerScrollTo,
45668
- registerStorage,
45669
- registerThrottle,
45670
- registerSnapshot,
45671
- registerTrace,
45672
- registerConfig,
45673
- registerExtension
45674
- ];
45675
-
45676
45967
  // dist/cli/plugins/registerPlugins.js
45677
- import path14 from "node:path";
45968
+ import path15 from "node:path";
45969
+ import { existsSync as existsSync3 } from "node:fs";
45678
45970
  import { pathToFileURL as pathToFileURL2 } from "node:url";
45679
45971
 
45680
45972
  // ../argus-plugin-api/dist/index.js
45681
45973
  var ARGUS_PLUGIN_API_VERSION = 1;
45682
45974
 
45975
+ // dist/cli/plugins/pluginAliases.js
45976
+ var BUILTIN_PLUGIN_ALIASES = {
45977
+ gsheets: "@vforsh/argus-plugin-google-sheets",
45978
+ gs: "@vforsh/argus-plugin-google-sheets"
45979
+ };
45980
+ var resolvePluginAlias = (spec, aliases) => {
45981
+ const trimmed = spec.trim();
45982
+ const resolved = aliases[trimmed];
45983
+ return resolved ? { spec: resolved, alias: trimmed } : { spec: trimmed, alias: null };
45984
+ };
45985
+
45986
+ // dist/cli/plugins/pluginHost.js
45987
+ var DEFAULT_TIMEOUT_MS2 = 30000;
45988
+ var postWatcherJson = (id, path15, body, timeoutMs = DEFAULT_TIMEOUT_MS2) => requestWatcherJson({
45989
+ id,
45990
+ path: path15,
45991
+ method: "POST",
45992
+ body,
45993
+ timeoutMs
45994
+ });
45995
+ var argus = {
45996
+ eval: (id, request, options) => postWatcherJson(id, "/eval", request, options?.timeoutMs ?? request.timeoutMs ?? DEFAULT_TIMEOUT_MS2),
45997
+ dom: {
45998
+ click: (id, request, options) => postWatcherJson(id, "/dom/click", request, options?.timeoutMs),
45999
+ info: (id, request, options) => postWatcherJson(id, "/dom/info", request, options?.timeoutMs),
46000
+ keydown: (id, request, options) => postWatcherJson(id, "/dom/keydown", request, options?.timeoutMs)
46001
+ },
46002
+ screenshot: (id, request = {}, options) => postWatcherJson(id, "/screenshot", request, options?.timeoutMs)
46003
+ };
46004
+ var createPluginHost = () => ({
46005
+ createOutput,
46006
+ requestWatcherJson,
46007
+ writeRequestError,
46008
+ runChromeOpen,
46009
+ defineWatcherCommand,
46010
+ argus
46011
+ });
46012
+
45683
46013
  // dist/cli/plugins/registerPlugins.js
46014
+ var lastPluginLoadReport = {
46015
+ configPath: null,
46016
+ configDir: null,
46017
+ cwd: process.cwd(),
46018
+ entries: []
46019
+ };
46020
+ var getPluginLoadReport = () => lastPluginLoadReport;
45684
46021
  var parseEnvPlugins = () => {
45685
46022
  const raw = process.env.ARGUS_PLUGINS;
45686
46023
  if (!raw)
@@ -45688,6 +46025,30 @@ var parseEnvPlugins = () => {
45688
46025
  return raw.split(",").map((s) => s.trim()).filter(Boolean);
45689
46026
  };
45690
46027
  var uniq = (values) => Array.from(new Set(values));
46028
+ var createPluginInput = (source, spec, aliases) => {
46029
+ const resolved = resolvePluginAlias(spec, aliases);
46030
+ return { source, spec, resolvedSpec: resolved.spec, alias: resolved.alias };
46031
+ };
46032
+ var parseCliPlugins = (argv) => {
46033
+ const plugins = [];
46034
+ for (let i = 0;i < argv.length; i++) {
46035
+ const arg = argv[i];
46036
+ if (arg === "--plugin") {
46037
+ const value = argv[i + 1];
46038
+ if (value && !value.startsWith("-")) {
46039
+ plugins.push(value);
46040
+ i++;
46041
+ }
46042
+ continue;
46043
+ }
46044
+ if (arg.startsWith("--plugin=")) {
46045
+ const value = arg.slice("--plugin=".length).trim();
46046
+ if (value)
46047
+ plugins.push(value);
46048
+ }
46049
+ }
46050
+ return plugins;
46051
+ };
45691
46052
  var resolveWithImportMeta = (specifier, parentUrl) => {
45692
46053
  const resolver2 = import.meta.resolve;
45693
46054
  if (typeof resolver2 !== "function") {
@@ -45695,6 +46056,7 @@ var resolveWithImportMeta = (specifier, parentUrl) => {
45695
46056
  }
45696
46057
  return resolver2(specifier, parentUrl);
45697
46058
  };
46059
+ var isPathLikeSpecifier = (specifier) => specifier.startsWith(".") || specifier.startsWith("/");
45698
46060
  var resolvePluginModuleUrl = (specifier, baseDirs) => {
45699
46061
  const trimmed = specifier.trim();
45700
46062
  if (!trimmed) {
@@ -45704,6 +46066,23 @@ var resolvePluginModuleUrl = (specifier, baseDirs) => {
45704
46066
  return { ok: true, url: trimmed };
45705
46067
  }
45706
46068
  const errors = [];
46069
+ if (isPathLikeSpecifier(trimmed)) {
46070
+ for (const baseDir of baseDirs) {
46071
+ try {
46072
+ const resolvedPath = path15.resolve(baseDir, trimmed);
46073
+ if (existsSync3(resolvedPath)) {
46074
+ return { ok: true, url: pathToFileURL2(resolvedPath).href };
46075
+ }
46076
+ errors.push(`${baseDir}: ${resolvedPath} does not exist`);
46077
+ } catch (error) {
46078
+ const msg = error instanceof Error ? error.message : String(error);
46079
+ errors.push(`${baseDir}: ${msg}`);
46080
+ }
46081
+ }
46082
+ return { ok: false, error: `Failed to resolve plugin path "${specifier}". Tried:
46083
+ ${errors.map((e) => `- ${e}`).join(`
46084
+ `)}` };
46085
+ }
45707
46086
  try {
45708
46087
  return { ok: true, url: resolveWithImportMeta(trimmed, import.meta.url) };
45709
46088
  } catch (error) {
@@ -45712,7 +46091,7 @@ var resolvePluginModuleUrl = (specifier, baseDirs) => {
45712
46091
  }
45713
46092
  for (const baseDir of baseDirs) {
45714
46093
  try {
45715
- const baseUrl = pathToFileURL2(path14.join(baseDir, "noop.js")).href;
46094
+ const baseUrl = pathToFileURL2(path15.join(baseDir, "noop.js")).href;
45716
46095
  return { ok: true, url: resolveWithImportMeta(trimmed, baseUrl) };
45717
46096
  } catch (error) {
45718
46097
  const msg = error instanceof Error ? error.message : String(error);
@@ -45739,26 +46118,62 @@ var extractPlugin = (mod) => {
45739
46118
  return null;
45740
46119
  return plugin;
45741
46120
  };
46121
+ var normalizeOptionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
46122
+ var normalizeCommands = (value) => {
46123
+ if (!Array.isArray(value))
46124
+ return [];
46125
+ const commands = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
46126
+ return Array.from(new Set(commands));
46127
+ };
45742
46128
  var warnPluginLoad = (source, spec, message) => {
45743
46129
  const output = createOutput({ json: false });
45744
46130
  output.writeWarn(`[plugins] Failed to load (${source}) "${spec}": ${message}`);
45745
46131
  };
45746
- var registerPlugins = async (program2) => {
46132
+ var recordPluginFailure = (entries, entry, error, url) => {
46133
+ entries.push({ source: entry.source, spec: entry.spec, resolvedSpec: entry.resolvedSpec, alias: entry.alias, status: "failed", url, error });
46134
+ warnPluginLoad(entry.source, entry.spec, error);
46135
+ };
46136
+ var createLoadedEntry = (entry, plugin, url) => ({
46137
+ source: entry.source,
46138
+ spec: entry.spec,
46139
+ resolvedSpec: entry.resolvedSpec,
46140
+ alias: entry.alias,
46141
+ status: "loaded",
46142
+ name: plugin.name,
46143
+ version: normalizeOptionalString(plugin.version),
46144
+ description: normalizeOptionalString(plugin.description),
46145
+ commands: normalizeCommands(plugin.commands),
46146
+ homepage: normalizeOptionalString(plugin.homepage),
46147
+ minArgusVersion: normalizeOptionalString(plugin.minArgusVersion),
46148
+ url
46149
+ });
46150
+ var registerPlugins = async (program2, argv = process.argv.slice(2)) => {
45747
46151
  const cwd = process.cwd();
45748
46152
  const configPath = resolveArgusConfigPath({ cwd });
45749
46153
  const configResult = configPath ? loadArgusConfig(configPath) : null;
46154
+ const aliases = { ...BUILTIN_PLUGIN_ALIASES, ...configResult?.config.pluginAliases ?? {} };
45750
46155
  const configPlugins = configResult?.config.plugins ?? [];
45751
46156
  const envPlugins = parseEnvPlugins();
46157
+ const cliPlugins = parseCliPlugins(argv);
45752
46158
  const all = [];
45753
46159
  for (const spec of configPlugins)
45754
- all.push({ source: "config", spec });
46160
+ all.push(createPluginInput("config", spec, aliases));
45755
46161
  for (const spec of envPlugins)
45756
- all.push({ source: "env", spec });
46162
+ all.push(createPluginInput("env", spec, aliases));
46163
+ for (const spec of cliPlugins)
46164
+ all.push(createPluginInput("cli", spec, aliases));
46165
+ const entries = [];
46166
+ lastPluginLoadReport = {
46167
+ configPath,
46168
+ configDir: configResult?.configDir ?? null,
46169
+ cwd,
46170
+ entries
46171
+ };
45757
46172
  if (all.length === 0)
45758
46173
  return;
45759
46174
  const seen = new Set;
45760
46175
  const ordered = all.filter((p2) => {
45761
- const key = p2.spec.trim();
46176
+ const key = p2.resolvedSpec.trim();
45762
46177
  if (!key)
45763
46178
  return false;
45764
46179
  if (seen.has(key))
@@ -45769,37 +46184,312 @@ var registerPlugins = async (program2) => {
45769
46184
  const baseDirs = uniq([configResult?.configDir, cwd].filter((v4) => Boolean(v4)));
45770
46185
  const ctxBase = {
45771
46186
  apiVersion: ARGUS_PLUGIN_API_VERSION,
45772
- host: { createOutput, requestWatcherJson, writeRequestError, runChromeOpen },
46187
+ host: createPluginHost(),
45773
46188
  cwd,
45774
46189
  configPath,
45775
46190
  configDir: configResult?.configDir ?? null
45776
46191
  };
45777
46192
  for (const entry of ordered) {
45778
- const resolved = resolvePluginModuleUrl(entry.spec, baseDirs);
46193
+ const resolved = resolvePluginModuleUrl(entry.resolvedSpec, baseDirs);
45779
46194
  if (!resolved.ok) {
45780
- warnPluginLoad(entry.source, entry.spec, resolved.error);
46195
+ recordPluginFailure(entries, entry, resolved.error);
45781
46196
  continue;
45782
46197
  }
45783
46198
  let mod;
45784
46199
  try {
45785
46200
  mod = await import(resolved.url);
45786
46201
  } catch (error) {
45787
- warnPluginLoad(entry.source, entry.spec, error instanceof Error ? error.message : String(error));
46202
+ recordPluginFailure(entries, entry, error instanceof Error ? error.message : String(error), resolved.url);
45788
46203
  continue;
45789
46204
  }
45790
46205
  const plugin = extractPlugin(mod);
45791
46206
  if (!plugin) {
45792
- warnPluginLoad(entry.source, entry.spec, "Invalid plugin export (expected default export with { apiVersion: 1, name, register() }).");
46207
+ recordPluginFailure(entries, entry, "Invalid plugin export (expected default export with { apiVersion: 1, name, register() }).", resolved.url);
45793
46208
  continue;
45794
46209
  }
45795
46210
  try {
45796
46211
  await plugin.register({ ...ctxBase, program: program2 });
46212
+ entries.push(createLoadedEntry(entry, plugin, resolved.url));
45797
46213
  } catch (error) {
45798
- warnPluginLoad(entry.source, entry.spec, error instanceof Error ? error.message : String(error));
46214
+ recordPluginFailure(entries, entry, error instanceof Error ? error.message : String(error), resolved.url);
45799
46215
  }
45800
46216
  }
45801
46217
  };
45802
46218
 
46219
+ // dist/commands/pluginList.js
46220
+ var runPluginList = (options) => {
46221
+ const output = createOutput(options);
46222
+ const report = getPluginLoadReport();
46223
+ if (options.json) {
46224
+ output.writeJson(report);
46225
+ return;
46226
+ }
46227
+ if (report.entries.length === 0) {
46228
+ output.writeHuman("No plugins configured for this invocation.");
46229
+ return;
46230
+ }
46231
+ for (const entry of report.entries) {
46232
+ const spec = entry.alias ? `${entry.alias} -> ${entry.resolvedSpec}` : entry.spec;
46233
+ if (entry.status === "failed") {
46234
+ output.writeHuman(`${entry.source} ${spec} failed ${entry.error}`);
46235
+ continue;
46236
+ }
46237
+ const version2 = entry.version ? ` v${entry.version}` : "";
46238
+ const commands = entry.commands.length > 0 ? ` commands: ${entry.commands.join(", ")}` : "";
46239
+ const description = entry.description ? ` — ${entry.description}` : "";
46240
+ output.writeHuman(`${entry.source} ${entry.name}${version2} ${spec} loaded${commands}${description}`);
46241
+ }
46242
+ };
46243
+
46244
+ // dist/commands/pluginConfig.js
46245
+ import fs16 from "node:fs/promises";
46246
+ import { existsSync as existsSync4 } from "node:fs";
46247
+ import path16 from "node:path";
46248
+ var DEFAULT_CONFIG_PATH2 = ".argus/config.json";
46249
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
46250
+ var resolveTargetConfigPath = (cwd, cliPath) => {
46251
+ if (cliPath) {
46252
+ const resolved = path16.isAbsolute(cliPath) ? cliPath : path16.resolve(cwd, cliPath);
46253
+ return { path: resolved, exists: existsSync4(resolved) };
46254
+ }
46255
+ const discovered = resolveArgusConfigPath({ cwd });
46256
+ if (discovered)
46257
+ return { path: discovered, exists: true };
46258
+ if (process.exitCode)
46259
+ return null;
46260
+ return { path: path16.resolve(cwd, DEFAULT_CONFIG_PATH2), exists: false };
46261
+ };
46262
+ var readConfigObject = async (configPath, exists) => {
46263
+ if (!exists)
46264
+ return {};
46265
+ let raw;
46266
+ try {
46267
+ raw = await fs16.readFile(configPath, "utf8");
46268
+ } catch (error) {
46269
+ console.error(`Failed to read Argus config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
46270
+ process.exitCode = 2;
46271
+ return null;
46272
+ }
46273
+ try {
46274
+ const parsed = JSON.parse(raw);
46275
+ if (isRecord2(parsed))
46276
+ return parsed;
46277
+ console.error(`Invalid Argus config at ${configPath}: config root must be an object.`);
46278
+ process.exitCode = 2;
46279
+ return null;
46280
+ } catch (error) {
46281
+ console.error(`Invalid Argus config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
46282
+ process.exitCode = 2;
46283
+ return null;
46284
+ }
46285
+ };
46286
+ var getPluginSpecs = (config, configPath) => {
46287
+ const value = config.plugins;
46288
+ if (value === undefined)
46289
+ return [];
46290
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
46291
+ console.error(`Invalid Argus config at ${configPath}: "plugins" must be an array of non-empty strings.`);
46292
+ process.exitCode = 2;
46293
+ return null;
46294
+ }
46295
+ return value;
46296
+ };
46297
+ var getPluginAliases = (config, configPath) => {
46298
+ const value = config.pluginAliases;
46299
+ if (value === undefined)
46300
+ return {};
46301
+ if (!isRecord2(value)) {
46302
+ console.error(`Invalid Argus config at ${configPath}: "pluginAliases" must be an object with string values.`);
46303
+ process.exitCode = 2;
46304
+ return null;
46305
+ }
46306
+ const aliases = {};
46307
+ for (const [alias, spec] of Object.entries(value)) {
46308
+ if (alias.trim() === "" || typeof spec !== "string" || spec.trim() === "") {
46309
+ console.error(`Invalid Argus config at ${configPath}: "pluginAliases" keys and values must be non-empty strings.`);
46310
+ process.exitCode = 2;
46311
+ return null;
46312
+ }
46313
+ aliases[alias] = spec;
46314
+ }
46315
+ return aliases;
46316
+ };
46317
+ var writeConfigObject = async (configPath, config) => {
46318
+ try {
46319
+ await fs16.mkdir(path16.dirname(configPath), { recursive: true });
46320
+ await fs16.writeFile(configPath, `${JSON.stringify(config, null, "\t")}
46321
+ `, "utf8");
46322
+ return true;
46323
+ } catch (error) {
46324
+ console.error(`Failed to write Argus config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
46325
+ process.exitCode = 2;
46326
+ return false;
46327
+ }
46328
+ };
46329
+ var loadMutablePluginConfig = async (options) => {
46330
+ const target = resolveTargetConfigPath(process.cwd(), options.path);
46331
+ if (!target)
46332
+ return null;
46333
+ const config = await readConfigObject(target.path, target.exists);
46334
+ if (!config)
46335
+ return null;
46336
+ const plugins = getPluginSpecs(config, target.path);
46337
+ if (!plugins)
46338
+ return null;
46339
+ const pluginAliases = getPluginAliases(config, target.path);
46340
+ if (!pluginAliases)
46341
+ return null;
46342
+ return { path: target.path, config, plugins, pluginAliases };
46343
+ };
46344
+ var parsePluginAddTarget = (raw, aliases) => {
46345
+ const trimmed = raw.trim();
46346
+ if (!trimmed)
46347
+ return null;
46348
+ const separator = trimmed.indexOf("=");
46349
+ if (separator < 0) {
46350
+ const resolved = resolvePluginAlias(trimmed, aliases);
46351
+ return { spec: trimmed, alias: resolved.alias, resolvedSpec: resolved.spec };
46352
+ }
46353
+ const alias = trimmed.slice(0, separator).trim();
46354
+ const spec = trimmed.slice(separator + 1).trim();
46355
+ return alias && spec ? { spec: alias, alias, resolvedSpec: spec } : null;
46356
+ };
46357
+ var runPluginAdd = async (targetSpec, options) => {
46358
+ const output = createOutput(options);
46359
+ const loaded = await loadMutablePluginConfig(options);
46360
+ if (!loaded)
46361
+ return;
46362
+ const parsed = parsePluginAddTarget(targetSpec, { ...BUILTIN_PLUGIN_ALIASES, ...loaded.pluginAliases });
46363
+ if (!parsed) {
46364
+ output.writeWarn("Plugin specifier is required.");
46365
+ process.exitCode = 2;
46366
+ return;
46367
+ }
46368
+ const aliases = { ...BUILTIN_PLUGIN_ALIASES, ...loaded.pluginAliases };
46369
+ const hasSameResolvedPlugin = loaded.plugins.some((spec) => resolvePluginAlias(spec, aliases).spec === parsed.resolvedSpec);
46370
+ const changed = !loaded.plugins.includes(parsed.spec) && !hasSameResolvedPlugin;
46371
+ const aliasChanged = parsed.alias !== null && loaded.pluginAliases[parsed.alias] !== parsed.resolvedSpec;
46372
+ if (changed) {
46373
+ loaded.plugins = [...loaded.plugins, parsed.spec];
46374
+ loaded.config.plugins = loaded.plugins;
46375
+ }
46376
+ if (aliasChanged && parsed.alias) {
46377
+ loaded.pluginAliases[parsed.alias] = parsed.resolvedSpec;
46378
+ loaded.config.pluginAliases = loaded.pluginAliases;
46379
+ }
46380
+ if ((changed || aliasChanged) && !await writeConfigObject(loaded.path, loaded.config))
46381
+ return;
46382
+ const response = {
46383
+ configPath: loaded.path,
46384
+ spec: parsed.spec,
46385
+ alias: parsed.alias,
46386
+ resolvedSpec: parsed.resolvedSpec,
46387
+ changed: changed || aliasChanged,
46388
+ plugins: loaded.plugins,
46389
+ pluginAliases: loaded.pluginAliases
46390
+ };
46391
+ if (options.json)
46392
+ output.writeJson(response);
46393
+ else
46394
+ output.writeHuman(response.changed ? `Added plugin ${parsed.spec} to ${loaded.path}` : `Plugin already configured: ${parsed.spec}`);
46395
+ };
46396
+ var runPluginRemove = async (targetSpec, options) => {
46397
+ const output = createOutput(options);
46398
+ const normalized = targetSpec.trim();
46399
+ if (!normalized) {
46400
+ output.writeWarn("Plugin specifier or name is required.");
46401
+ process.exitCode = 2;
46402
+ return;
46403
+ }
46404
+ const loaded = await loadMutablePluginConfig(options);
46405
+ if (!loaded)
46406
+ return;
46407
+ const aliases = { ...BUILTIN_PLUGIN_ALIASES, ...loaded.pluginAliases };
46408
+ const next = loaded.plugins.filter((spec) => !matchesPluginSpecifier(spec, normalized, aliases));
46409
+ const nextAliases = Object.fromEntries(Object.entries(loaded.pluginAliases).filter(([alias, spec]) => !matchesPluginAlias(alias, spec, normalized)));
46410
+ const changed = next.length !== loaded.plugins.length;
46411
+ const aliasChanged = Object.keys(nextAliases).length !== Object.keys(loaded.pluginAliases).length;
46412
+ if (changed || aliasChanged) {
46413
+ loaded.config.plugins = next;
46414
+ loaded.config.pluginAliases = Object.keys(nextAliases).length > 0 ? nextAliases : undefined;
46415
+ if (!await writeConfigObject(loaded.path, loaded.config))
46416
+ return;
46417
+ }
46418
+ const response = { configPath: loaded.path, target: normalized, changed: changed || aliasChanged, plugins: next, pluginAliases: nextAliases };
46419
+ if (options.json)
46420
+ output.writeJson(response);
46421
+ else
46422
+ output.writeHuman(response.changed ? `Removed plugin ${normalized} from ${loaded.path}` : `Plugin not configured: ${normalized}`);
46423
+ };
46424
+ var matchesPluginSpecifier = (spec, target, aliases) => {
46425
+ const resolved = resolvePluginAlias(spec, aliases);
46426
+ return spec === target || resolved.spec === target || resolved.alias === target || pluginKey(spec) === pluginKey(target) || pluginKey(resolved.spec) === pluginKey(target);
46427
+ };
46428
+ var matchesPluginAlias = (alias, spec, target) => alias === target || spec === target || pluginKey(alias) === pluginKey(target) || pluginKey(spec) === pluginKey(target);
46429
+ var pluginKey = (value) => {
46430
+ const withoutPath = value.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? value;
46431
+ const withoutExt = withoutPath.replace(/\.(mjs|js|ts)$/, "");
46432
+ return withoutExt.replace(/^argus-plugin-/, "").replace(/^@[^/]+\/argus-plugin-/, "");
46433
+ };
46434
+
46435
+ // dist/cli/register/registerPlugin.js
46436
+ function registerPlugin(program2) {
46437
+ const plugin = program2.command("plugin").alias("plugins").description("Manage Argus CLI plugins");
46438
+ plugin.command("list").alias("ls").description("List plugins discovered for this invocation").option("--json", "Output JSON for automation").addHelpText("after", `
46439
+ Examples:
46440
+ $ argus plugin list
46441
+ $ argus --plugin ./plugins/foo.js plugin list --json
46442
+ `).action((options) => {
46443
+ runPluginList(options);
46444
+ });
46445
+ plugin.command("add <specifier>").description("Add a plugin specifier or alias to the Argus config").option("--path <file>", "Config file to update (default: discovered config or .argus/config.json)").option("--json", "Output JSON for automation").addHelpText("after", `
46446
+ Examples:
46447
+ $ argus plugin add gsheets
46448
+ $ argus plugin add gs=@vforsh/argus-plugin-google-sheets
46449
+ $ argus plugin add ./plugins/foo.js
46450
+ `).action(async (specifier, options) => {
46451
+ await runPluginAdd(specifier, options);
46452
+ });
46453
+ plugin.command("remove <specifierOrName>").alias("rm").description("Remove a plugin from the Argus config").option("--path <file>", "Config file to update (default: discovered config or .argus/config.json)").option("--json", "Output JSON for automation").addHelpText("after", `
46454
+ Examples:
46455
+ $ argus plugin remove @vforsh/argus-plugin-google-sheets
46456
+ $ argus plugin remove google-sheets
46457
+ `).action(async (specifierOrName, options) => {
46458
+ await runPluginRemove(specifierOrName, options);
46459
+ });
46460
+ }
46461
+
46462
+ // dist/cli/register/index.js
46463
+ var registerDefinedCommands = (program2) => {
46464
+ defineCommands(program2, [domClickCommandDefinition]);
46465
+ };
46466
+ var coreProgramRegistrars = [
46467
+ registerQuickAccess,
46468
+ registerChrome,
46469
+ registerWatcher,
46470
+ registerPage,
46471
+ registerDialog,
46472
+ registerLogs,
46473
+ registerNet,
46474
+ registerAuth,
46475
+ registerEval,
46476
+ registerLocate,
46477
+ registerCode,
46478
+ registerDom,
46479
+ registerDefinedCommands,
46480
+ registerKeydown,
46481
+ registerFill,
46482
+ registerHover,
46483
+ registerScrollTo,
46484
+ registerStorage,
46485
+ registerThrottle,
46486
+ registerSnapshot,
46487
+ registerTrace,
46488
+ registerConfig,
46489
+ registerPlugin,
46490
+ registerExtension
46491
+ ];
46492
+
45803
46493
  // dist/bin.js
45804
46494
  var program2 = createProgram();
45805
46495
  for (const registerProgramPart of coreProgramRegistrars) {