@vforsh/argus 0.1.14 → 0.1.15

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 (137) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/argus.js +1199 -915
  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 +6 -0
  30. package/dist/cli/register/registerEval.js.map +1 -1
  31. package/dist/cli/register/registerPlugin.d.ts +3 -0
  32. package/dist/cli/register/registerPlugin.d.ts.map +1 -0
  33. package/dist/cli/register/registerPlugin.js +34 -0
  34. package/dist/cli/register/registerPlugin.js.map +1 -0
  35. package/dist/commands/auth.d.ts.map +1 -1
  36. package/dist/commands/auth.js +4 -9
  37. package/dist/commands/auth.js.map +1 -1
  38. package/dist/commands/authCookieSupport.d.ts.map +1 -1
  39. package/dist/commands/authCookieSupport.js +4 -6
  40. package/dist/commands/authCookieSupport.js.map +1 -1
  41. package/dist/commands/authCookies.d.ts +4 -4
  42. package/dist/commands/authCookies.d.ts.map +1 -1
  43. package/dist/commands/authCookies.js +85 -105
  44. package/dist/commands/authCookies.js.map +1 -1
  45. package/dist/commands/code.js +3 -25
  46. package/dist/commands/code.js.map +1 -1
  47. package/dist/commands/codeEdit.js +3 -3
  48. package/dist/commands/codeEdit.js.map +1 -1
  49. package/dist/commands/configInit.d.ts.map +1 -1
  50. package/dist/commands/configInit.js +1 -0
  51. package/dist/commands/configInit.js.map +1 -1
  52. package/dist/commands/domAdd.d.ts +1 -1
  53. package/dist/commands/domAdd.d.ts.map +1 -1
  54. package/dist/commands/domAdd.js +26 -30
  55. package/dist/commands/domAdd.js.map +1 -1
  56. package/dist/commands/domInfo.d.ts +1 -1
  57. package/dist/commands/domInfo.d.ts.map +1 -1
  58. package/dist/commands/domInfo.js +18 -23
  59. package/dist/commands/domInfo.js.map +1 -1
  60. package/dist/commands/domKeydown.d.ts +1 -1
  61. package/dist/commands/domKeydown.d.ts.map +1 -1
  62. package/dist/commands/domKeydown.js +22 -43
  63. package/dist/commands/domKeydown.js.map +1 -1
  64. package/dist/commands/domModify.js +27 -30
  65. package/dist/commands/domModify.js.map +1 -1
  66. package/dist/commands/domScroll.d.ts +1 -1
  67. package/dist/commands/domScroll.d.ts.map +1 -1
  68. package/dist/commands/domScroll.js +18 -24
  69. package/dist/commands/domScroll.js.map +1 -1
  70. package/dist/commands/domScrollTo.d.ts +1 -1
  71. package/dist/commands/domScrollTo.d.ts.map +1 -1
  72. package/dist/commands/domScrollTo.js +15 -24
  73. package/dist/commands/domScrollTo.js.map +1 -1
  74. package/dist/commands/domTree.d.ts +1 -1
  75. package/dist/commands/domTree.d.ts.map +1 -1
  76. package/dist/commands/domTree.js +18 -24
  77. package/dist/commands/domTree.js.map +1 -1
  78. package/dist/commands/eval.d.ts +2 -0
  79. package/dist/commands/eval.d.ts.map +1 -1
  80. package/dist/commands/eval.js +43 -62
  81. package/dist/commands/eval.js.map +1 -1
  82. package/dist/commands/evalPolling.d.ts +55 -0
  83. package/dist/commands/evalPolling.d.ts.map +1 -0
  84. package/dist/commands/evalPolling.js +59 -0
  85. package/dist/commands/evalPolling.js.map +1 -0
  86. package/dist/commands/evalShared.d.ts +13 -0
  87. package/dist/commands/evalShared.d.ts.map +1 -1
  88. package/dist/commands/evalShared.js +54 -7
  89. package/dist/commands/evalShared.js.map +1 -1
  90. package/dist/commands/evalUntil.d.ts +2 -0
  91. package/dist/commands/evalUntil.d.ts.map +1 -1
  92. package/dist/commands/evalUntil.js +47 -70
  93. package/dist/commands/evalUntil.js.map +1 -1
  94. package/dist/commands/locate.js +2 -2
  95. package/dist/commands/locate.js.map +1 -1
  96. package/dist/commands/logs.d.ts +1 -1
  97. package/dist/commands/logs.d.ts.map +1 -1
  98. package/dist/commands/logs.js +18 -27
  99. package/dist/commands/logs.js.map +1 -1
  100. package/dist/commands/net.d.ts +1 -1
  101. package/dist/commands/net.d.ts.map +1 -1
  102. package/dist/commands/net.js +14 -19
  103. package/dist/commands/net.js.map +1 -1
  104. package/dist/commands/netClear.d.ts +1 -1
  105. package/dist/commands/netClear.d.ts.map +1 -1
  106. package/dist/commands/netClear.js +7 -19
  107. package/dist/commands/netClear.js.map +1 -1
  108. package/dist/commands/netSse.js +2 -2
  109. package/dist/commands/netSse.js.map +1 -1
  110. package/dist/commands/netWebSocket.js +3 -3
  111. package/dist/commands/netWebSocket.js.map +1 -1
  112. package/dist/commands/pageEmulation.d.ts +3 -3
  113. package/dist/commands/pageEmulation.d.ts.map +1 -1
  114. package/dist/commands/pageEmulation.js +72 -143
  115. package/dist/commands/pageEmulation.js.map +1 -1
  116. package/dist/commands/pluginConfig.d.ts +7 -0
  117. package/dist/commands/pluginConfig.d.ts.map +1 -0
  118. package/dist/commands/pluginConfig.js +199 -0
  119. package/dist/commands/pluginConfig.js.map +1 -0
  120. package/dist/commands/pluginList.d.ts +5 -0
  121. package/dist/commands/pluginList.d.ts.map +1 -0
  122. package/dist/commands/pluginList.js +26 -0
  123. package/dist/commands/pluginList.js.map +1 -0
  124. package/dist/commands/screenshot.d.ts +1 -1
  125. package/dist/commands/screenshot.d.ts.map +1 -1
  126. package/dist/commands/screenshot.js +12 -18
  127. package/dist/commands/screenshot.js.map +1 -1
  128. package/dist/commands/watcherStatus.d.ts +1 -1
  129. package/dist/commands/watcherStatus.d.ts.map +1 -1
  130. package/dist/commands/watcherStatus.js +8 -21
  131. package/dist/commands/watcherStatus.js.map +1 -1
  132. package/dist/config/argusConfig.d.ts +5 -0
  133. package/dist/config/argusConfig.d.ts.map +1 -1
  134. package/dist/config/argusConfig.js +33 -2
  135. package/dist/config/argusConfig.js.map +1 -1
  136. package/package.json +1 -1
  137. package/schemas/argus.config.schema.json +16 -0
package/dist/argus.js CHANGED
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -2130,7 +2144,7 @@ var {
2130
2144
  // package.json
2131
2145
  var package_default = {
2132
2146
  name: "@vforsh/argus",
2133
- version: "0.1.14",
2147
+ version: "0.1.15",
2134
2148
  repository: {
2135
2149
  type: "git",
2136
2150
  url: "git+https://github.com/vforsh/argus.git",
@@ -2172,7 +2186,7 @@ var package_default = {
2172
2186
  // dist/cli/program.js
2173
2187
  function createProgram() {
2174
2188
  const program2 = new Command;
2175
- program2.name("argus").description("Argus CLI for local watcher servers").version(package_default.version).configureOutput({
2189
+ 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
2190
  outputError: (str, write) => write(str)
2177
2191
  }).showSuggestionAfterError(true).exitOverride((error) => {
2178
2192
  if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
@@ -2183,6 +2197,7 @@ function createProgram() {
2183
2197
  });
2184
2198
  return program2;
2185
2199
  }
2200
+ var collectPluginOption = (value, previous) => [...previous, value];
2186
2201
 
2187
2202
  // ../argus-core/dist/protocol/logs.js
2188
2203
  var formatLogLevelTag = (level) => {
@@ -3311,11 +3326,20 @@ function writeResolveError(resolved, output) {
3311
3326
 
3312
3327
  // dist/cli/defineWatcherCommand.js
3313
3328
  var DEFAULT_TIMEOUT_MS = 30000;
3329
+ var requestWatcherCommandJson = async (input, output) => {
3330
+ const result = await requestWatcherJson(input);
3331
+ if (!result.ok) {
3332
+ writeRequestError(result, output);
3333
+ return null;
3334
+ }
3335
+ return result;
3336
+ };
3337
+ var requestWatcherCommandAction = requestWatcherAction;
3314
3338
  var defineWatcherCommand = (spec) => async (id, ...rest) => {
3315
3339
  const tail = rest.length - 1;
3316
3340
  const options = rest[tail];
3317
3341
  const args = rest.slice(0, tail);
3318
- const output = createOutput(options);
3342
+ const output = createOutput({ ...options, json: options.json === true || spec.isJson?.(options) === true });
3319
3343
  const plan = await spec.build(args, options, output);
3320
3344
  if (plan == null)
3321
3345
  return;
@@ -4368,20 +4392,15 @@ var loadAuthStateSnapshot = async (inputPath) => {
4368
4392
  return parseAuthStateSnapshot(parsed, `auth state ${source}`);
4369
4393
  };
4370
4394
  var requestAuthStateSnapshot = async (id, input, output) => {
4371
- const result = await requestWatcherJson({
4395
+ return requestWatcherCommandJson({
4372
4396
  id,
4373
4397
  path: "/auth/state",
4374
4398
  query: buildStateQuery(input),
4375
4399
  timeoutMs: 1e4
4376
- });
4377
- if (!result.ok) {
4378
- writeRequestError(result, output);
4379
- return null;
4380
- }
4381
- return result;
4400
+ }, output);
4382
4401
  };
4383
4402
  var loadAuthStateIntoWatcher = async (id, snapshot, input, output) => {
4384
- const result = await requestWatcherAction({
4403
+ const result = await requestWatcherCommandAction({
4385
4404
  id,
4386
4405
  path: "/auth/state/load",
4387
4406
  method: "POST",
@@ -6427,19 +6446,62 @@ var handle11 = (_req, res, url, ctx) => {
6427
6446
  respondJson(res, response);
6428
6447
  };
6429
6448
 
6449
+ // ../argus-watcher/dist/http/routes/defineRoute.js
6450
+ var defineJsonRoute = (input) => ({
6451
+ method: input.method,
6452
+ path: input.path,
6453
+ extensionOnly: input.extensionOnly,
6454
+ handler: async (req, res, url, ctx) => {
6455
+ const shouldReadBody = input.bodySchema != null || input.parseBody === true;
6456
+ const rawBody = shouldReadBody ? await readJsonBody(req, res) : undefined;
6457
+ if (shouldReadBody && rawBody == null) {
6458
+ return;
6459
+ }
6460
+ const parsedBody = input.bodySchema?.parse(rawBody);
6461
+ if (parsedBody && !parsedBody.ok) {
6462
+ return respondInvalidBody(res, formatProtocolValidationIssues(parsedBody.issues));
6463
+ }
6464
+ if (input.endpoint) {
6465
+ emitRequest(ctx, res, input.endpoint);
6466
+ }
6467
+ try {
6468
+ const response = await input.handle({
6469
+ req,
6470
+ res,
6471
+ url,
6472
+ ctx,
6473
+ body: parsedBody?.value ?? rawBody
6474
+ });
6475
+ if (response) {
6476
+ respondJson(res, response);
6477
+ }
6478
+ } catch (error) {
6479
+ if (input.handleError?.(res, error)) {
6480
+ return;
6481
+ }
6482
+ respondError(res, error);
6483
+ }
6484
+ }
6485
+ });
6486
+
6430
6487
  // ../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);
6488
+ var handle12 = defineJsonRoute({
6489
+ method: "POST",
6490
+ path: "/net/clear",
6491
+ handle: ({ res, ctx }) => {
6492
+ if (!ctx.netBuffer) {
6493
+ respondJson(res, { ok: false, error: { code: "net_disabled", message: "Network capture is disabled for this watcher" } }, 400);
6494
+ return;
6495
+ }
6496
+ emitRequest(ctx, res, "net/clear");
6497
+ const realtimeCleared = ctx.realtimeNetBuffer?.clear() ?? 0;
6498
+ const response = {
6499
+ ok: true,
6500
+ cleared: ctx.netBuffer.clear() + realtimeCleared
6501
+ };
6502
+ return response;
6434
6503
  }
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
- };
6504
+ }).handler;
6443
6505
 
6444
6506
  // ../argus-watcher/dist/cdp/auth.js
6445
6507
  var inspectAuthCookies = async (session, options) => {
@@ -7304,23 +7366,19 @@ var handle18 = async (req, res, _url, ctx) => {
7304
7366
  };
7305
7367
 
7306
7368
  // ../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);
7369
+ var handle19 = defineJsonRoute({
7370
+ method: "POST",
7371
+ path: "/screenshot",
7372
+ parseBody: true,
7373
+ handle: async ({ res, ctx, body: payload }) => {
7374
+ const validationError = validateScreenshotRequest(payload);
7375
+ if (validationError) {
7376
+ return respondInvalidBody(res, validationError);
7377
+ }
7378
+ emitRequest(ctx, res, "screenshot");
7379
+ return ctx.screenshotter.capture(payload);
7322
7380
  }
7323
- };
7381
+ }).handler;
7324
7382
  var validateScreenshotRequest = (payload) => {
7325
7383
  if (payload.selector != null && (typeof payload.selector !== "string" || !payload.selector.trim())) {
7326
7384
  return "selector must be a non-empty string";
@@ -7825,29 +7883,25 @@ var countNodes = (nodes) => {
7825
7883
  };
7826
7884
 
7827
7885
  // ../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, {
7886
+ var handle20 = defineJsonRoute({
7887
+ method: "POST",
7888
+ path: "/snapshot",
7889
+ parseBody: true,
7890
+ handle: async ({ res, ctx, body: payload }) => {
7891
+ if (payload.selector != null && (typeof payload.selector !== "string" || payload.selector.trim() === "")) {
7892
+ return respondInvalidBody(res, "selector must be a non-empty string");
7893
+ }
7894
+ if (payload.depth != null && (!Number.isFinite(payload.depth) || payload.depth < 0 || !Number.isInteger(payload.depth))) {
7895
+ return respondInvalidBody(res, "depth must be a non-negative integer");
7896
+ }
7897
+ emitRequest(ctx, res, "snapshot");
7898
+ return fetchAccessibilitySnapshot(ctx.cdpSession, {
7842
7899
  selector: payload.selector,
7843
7900
  depth: payload.depth,
7844
7901
  interactive: payload.interactive ?? false
7845
7902
  }, ctx.elementRefs);
7846
- respondJson(res, response);
7847
- } catch (error) {
7848
- respondError(res, error);
7849
7903
  }
7850
- };
7904
+ }).handler;
7851
7905
 
7852
7906
  // ../argus-watcher/dist/http/routes/domSelectorRoute.js
7853
7907
  var readDomTargetPayload = async (req, res) => {
@@ -8741,43 +8795,6 @@ var route = defineDomTargetRoute({
8741
8795
  }
8742
8796
  });
8743
8797
 
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
8798
  // ../argus-watcher/dist/http/routes/postDomClick.js
8782
8799
  var route2 = defineJsonRoute({
8783
8800
  method: "POST",
@@ -9427,78 +9444,75 @@ var handle37 = async (req, res, _url, ctx) => {
9427
9444
  };
9428
9445
 
9429
9446
  // ../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
- };
9447
+ var handle38 = defineJsonRoute({
9448
+ method: "GET",
9449
+ path: "/throttle",
9450
+ endpoint: "throttle",
9451
+ handle: ({ ctx }) => ctx.throttleController.getStatus({ attached: ctx.getCdpStatus().attached })
9452
+ }).handler;
9435
9453
 
9436
9454
  // ../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);
9455
+ var handle39 = defineJsonRoute({
9456
+ method: "POST",
9457
+ path: "/throttle",
9458
+ parseBody: true,
9459
+ handle: async ({ res, ctx, body: payload }) => {
9460
+ const validActions = ["set", "clear"];
9461
+ const action = payload.action;
9462
+ if (!action || !validActions.includes(action)) {
9463
+ return respondInvalidBody(res, `action must be one of: ${validActions.join(", ")}`);
9464
+ }
9465
+ emitRequest(ctx, res, "throttle");
9466
+ const session = ctx.cdpSession.isAttached() ? ctx.cdpSession : null;
9467
+ if (action === "clear") {
9468
+ return ctx.throttleController.clearDesired(session);
9455
9469
  }
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);
9470
+ const setPayload = payload;
9471
+ if (typeof setPayload.rate !== "number" || !Number.isFinite(setPayload.rate) || setPayload.rate < 1) {
9472
+ return respondInvalidBody(res, "rate must be a finite number >= 1");
9473
+ }
9474
+ return ctx.throttleController.setDesired(setPayload.rate, session);
9467
9475
  }
9468
- };
9476
+ }).handler;
9469
9477
 
9470
9478
  // ../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
- };
9479
+ var handle40 = defineJsonRoute({
9480
+ method: "GET",
9481
+ path: "/dialog",
9482
+ endpoint: "dialog/status",
9483
+ handle: ({ ctx }) => {
9484
+ const response = {
9485
+ ok: true,
9486
+ dialog: ctx.getDialog()
9487
+ };
9488
+ return response;
9489
+ }
9490
+ }).handler;
9479
9491
 
9480
9492
  // ../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 {
9493
+ var handle41 = defineJsonRoute({
9494
+ method: "POST",
9495
+ path: "/dialog",
9496
+ parseBody: true,
9497
+ endpoint: "dialog/handle",
9498
+ handle: async ({ res, ctx, body: payload }) => {
9499
+ const action = payload.action;
9500
+ if (action !== "accept" && action !== "dismiss") {
9501
+ respondInvalidBody(res, 'Dialog action must be "accept" or "dismiss"');
9502
+ return;
9503
+ }
9504
+ const dialog = ctx.getDialog();
9505
+ if (!dialog) {
9506
+ respondJson(res, { ok: false, error: { message: "No active browser dialog", code: "no_active_dialog" } }, 409);
9507
+ return;
9508
+ }
9509
+ if (payload.promptText != null && dialog.type !== "prompt") {
9510
+ respondJson(res, {
9511
+ ok: false,
9512
+ error: { message: "Prompt text can only be sent to prompt dialogs", code: "dialog_not_prompt" }
9513
+ }, 409);
9514
+ return;
9515
+ }
9502
9516
  await ctx.pageCdpSession.sendAndWait("Page.handleJavaScriptDialog", {
9503
9517
  accept: action === "accept",
9504
9518
  promptText: payload.promptText
@@ -9508,36 +9522,30 @@ var handle41 = async (req, res, _url, ctx) => {
9508
9522
  action,
9509
9523
  dialog
9510
9524
  };
9511
- respondJson(res, response);
9512
- } catch (error) {
9513
- respondError(res, error);
9525
+ return response;
9514
9526
  }
9515
- };
9527
+ }).handler;
9516
9528
 
9517
9529
  // ../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 {
9530
+ var handle42 = defineJsonRoute({
9531
+ method: "POST",
9532
+ path: "/visibility",
9533
+ parseBody: true,
9534
+ handle: async ({ res, ctx, body: payload }) => {
9535
+ const action = payload.action;
9536
+ if (action !== "show" && action !== "hide") {
9537
+ respondInvalidBody(res, 'Visibility action must be "show" or "hide"');
9538
+ return;
9539
+ }
9540
+ emitRequest(ctx, res, "visibility");
9541
+ const lock = action === "show" ? "shown" : "default";
9542
+ const session = ctx.pageCdpSession;
9543
+ const attached = session.isAttached();
9533
9544
  await ctx.visibilityController.setLock(attached ? session : null, lock);
9534
- } catch (error) {
9535
- respondError(res, error);
9536
- return;
9545
+ const response = { ok: true, attached, state: lock };
9546
+ return response;
9537
9547
  }
9538
- const response = { ok: true, attached, state: lock };
9539
- respondJson(res, response);
9540
- };
9548
+ }).handler;
9541
9549
 
9542
9550
  // ../argus-watcher/dist/cdp/storage.js
9543
9551
  var storageAccessors = {
@@ -9652,22 +9660,18 @@ var handle43 = createStorageHandler("local");
9652
9660
  var handle44 = createStorageHandler("session");
9653
9661
 
9654
9662
  // ../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 {
9663
+ var handle45 = defineJsonRoute({
9664
+ method: "POST",
9665
+ path: "/reload",
9666
+ parseBody: true,
9667
+ endpoint: "reload",
9668
+ handle: async ({ ctx, body: payload }) => {
9662
9669
  await ctx.pageCdpSession.sendAndWait("Page.reload", {
9663
9670
  ignoreCache: payload.ignoreCache ?? false
9664
9671
  });
9665
- const response = { ok: true };
9666
- respondJson(res, response);
9667
- } catch (error) {
9668
- respondError(res, error);
9672
+ return { ok: true };
9669
9673
  }
9670
- };
9674
+ }).handler;
9671
9675
 
9672
9676
  // ../argus-watcher/dist/http/routes/postShutdown.js
9673
9677
  var handle46 = (_req, res, _url, ctx) => {
@@ -15207,13 +15211,15 @@ var createExtensionSource = (options) => {
15207
15211
  if (state2.activeAttachedAt == null) {
15208
15212
  state2.activeAttachedAt = Date.now();
15209
15213
  }
15214
+ const targetReady = isTargetReady(session.tabId);
15210
15215
  emitStatus(target, null);
15211
15216
  messaging.send({
15212
15217
  type: "target_info",
15213
15218
  targetId: target.id,
15214
15219
  title: target.title,
15215
15220
  url: target.url,
15216
- attachedAt: state2.activeAttachedAt
15221
+ attachedAt: state2.activeAttachedAt,
15222
+ targetReady
15217
15223
  });
15218
15224
  events.onTargetChanged?.(session.handle, target);
15219
15225
  }
@@ -16085,7 +16091,7 @@ var runReload = defineWatcherCommand({
16085
16091
  import fs8 from "node:fs";
16086
16092
  import path10 from "node:path";
16087
16093
  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 } } } }.';
16094
+ 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
16095
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16090
16096
  var invalidConfig = (configPath, message) => {
16091
16097
  console.error(`Invalid Argus config at ${configPath}: ${message} ${EXPECTED_SHAPE_HINT}`);
@@ -16159,6 +16165,25 @@ var validateOptionalStringArray = (value, label) => {
16159
16165
  }
16160
16166
  return { ok: true, value };
16161
16167
  };
16168
+ var validateOptionalStringMap = (value, label) => {
16169
+ if (value === undefined) {
16170
+ return { ok: true };
16171
+ }
16172
+ if (!isRecord(value)) {
16173
+ return { ok: false, error: `${label} must be an object with string values.` };
16174
+ }
16175
+ const entries = [];
16176
+ for (const [key, item] of Object.entries(value)) {
16177
+ if (key.trim() === "") {
16178
+ return { ok: false, error: `${label} keys must be non-empty strings.` };
16179
+ }
16180
+ if (typeof item !== "string" || item.trim() === "") {
16181
+ return { ok: false, error: `${label}.${key} must be a non-empty string.` };
16182
+ }
16183
+ entries.push([key, item]);
16184
+ }
16185
+ return { ok: true, value: Object.fromEntries(entries) };
16186
+ };
16162
16187
  var validateOptionalSection = (value, label, validate) => {
16163
16188
  if (value === undefined) {
16164
16189
  return { ok: true };
@@ -16290,6 +16315,10 @@ var validateArgusConfig = (value) => {
16290
16315
  if (!pluginsResult.ok) {
16291
16316
  return pluginsResult;
16292
16317
  }
16318
+ const pluginAliasesResult = validateOptionalStringMap(value.pluginAliases, '"pluginAliases"');
16319
+ if (!pluginAliasesResult.ok) {
16320
+ return pluginAliasesResult;
16321
+ }
16293
16322
  const chromeResult = validateOptionalSection(value.chrome, '"chrome"', (section) => {
16294
16323
  if (section.start === undefined) {
16295
16324
  return { ok: true, value: {} };
@@ -16316,7 +16345,15 @@ var validateArgusConfig = (value) => {
16316
16345
  if (!watcherResult.ok) {
16317
16346
  return watcherResult;
16318
16347
  }
16319
- return { ok: true, value: { chrome: chromeResult.value, watcher: watcherResult.value, plugins: pluginsResult.value } };
16348
+ return {
16349
+ ok: true,
16350
+ value: {
16351
+ chrome: chromeResult.value,
16352
+ watcher: watcherResult.value,
16353
+ plugins: pluginsResult.value,
16354
+ pluginAliases: pluginAliasesResult.value
16355
+ }
16356
+ };
16320
16357
  };
16321
16358
  var resolveArgusConfigPath = ({ cliPath, cwd }) => {
16322
16359
  if (cliPath) {
@@ -16767,25 +16804,13 @@ var runWatcherStart = async (options) => {
16767
16804
  var generateWatcherId = () => crypto2.randomBytes(3).toString("hex");
16768
16805
 
16769
16806
  // 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;
16807
+ var runWatcherStatus = defineWatcherCommand({
16808
+ build: () => ({ path: "/status", timeoutMs: 2000 }),
16809
+ formatHuman: (status, { output, watcher }) => {
16810
+ const readinessSuffix = status.targetReady === false ? " targetReady=false" : "";
16811
+ output.writeHuman(`ok ${watcher.id} ${watcher.host}:${watcher.port} pid=${status.pid} attached=${status.attached}${readinessSuffix}`);
16784
16812
  }
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
- };
16813
+ });
16789
16814
 
16790
16815
  // dist/commands/watcherStop.js
16791
16816
  var runWatcherStop = async (id, _options) => {
@@ -17361,16 +17386,38 @@ var listPresetNames = () => {
17361
17386
  };
17362
17387
 
17363
17388
  // dist/commands/pageEmulation.js
17364
- var runPageEmulationSet = async (id, options) => {
17365
- const output = createOutput(options);
17389
+ var runPageEmulationSet = defineWatcherCommand({
17390
+ build: (_args, options, output) => buildPageEmulationSetPlan(options, output),
17391
+ formatHuman: (res, { output, options }) => {
17392
+ const state2 = buildEmulationState(options);
17393
+ const label = formatViewportLabel(state2.viewport, state2.touch.enabled, state2.userAgent.value);
17394
+ if (res.applied) {
17395
+ output.writeHuman(`Applied emulation: ${label}`);
17396
+ } else if (!res.attached) {
17397
+ output.writeHuman(`Queued emulation (watcher detached): ${label}`);
17398
+ } else {
17399
+ output.writeHuman(`Emulation set but not applied: ${label}`);
17400
+ if (res.error) {
17401
+ output.writeWarn(`Error: ${res.error.message}`);
17402
+ }
17403
+ }
17404
+ }
17405
+ });
17406
+ var buildPageEmulationSetPlan = (options, output) => {
17407
+ const state2 = buildEmulationState(options, output);
17408
+ if (!state2)
17409
+ return null;
17410
+ return { path: "/emulation", method: "POST", body: { action: "set", state: state2 }, timeoutMs: 1e4 };
17411
+ };
17412
+ var buildEmulationState = (options, output) => {
17366
17413
  let base = null;
17367
17414
  if (options.device) {
17368
17415
  const preset = resolvePreset(options.device);
17369
17416
  if (!preset) {
17370
- output.writeWarn(`Unknown device: ${options.device}`);
17371
- output.writeWarn(`Available: ${listPresetNames().join(", ")}`);
17417
+ output?.writeWarn(`Unknown device: ${options.device}`);
17418
+ output?.writeWarn(`Available: ${listPresetNames().join(", ")}`);
17372
17419
  process.exitCode = 2;
17373
- return;
17420
+ return null;
17374
17421
  }
17375
17422
  base = { viewport: preset.viewport, touch: preset.touch, userAgent: preset.userAgent };
17376
17423
  }
@@ -17378,34 +17425,34 @@ var runPageEmulationSet = async (id, options) => {
17378
17425
  const heightRaw = options.height != null ? Number(options.height) : null;
17379
17426
  const dprRaw = options.dpr != null ? Number(options.dpr) : null;
17380
17427
  if (widthRaw != null && (!Number.isInteger(widthRaw) || widthRaw <= 0)) {
17381
- output.writeWarn("--width must be a positive integer");
17428
+ output?.writeWarn("--width must be a positive integer");
17382
17429
  process.exitCode = 2;
17383
- return;
17430
+ return null;
17384
17431
  }
17385
17432
  if (heightRaw != null && (!Number.isInteger(heightRaw) || heightRaw <= 0)) {
17386
- output.writeWarn("--height must be a positive integer");
17433
+ output?.writeWarn("--height must be a positive integer");
17387
17434
  process.exitCode = 2;
17388
- return;
17435
+ return null;
17389
17436
  }
17390
17437
  if (dprRaw != null && (!Number.isFinite(dprRaw) || dprRaw <= 0)) {
17391
- output.writeWarn("--dpr must be a positive number");
17438
+ output?.writeWarn("--dpr must be a positive number");
17392
17439
  process.exitCode = 2;
17393
- return;
17440
+ return null;
17394
17441
  }
17395
17442
  const hasWidth = widthRaw != null;
17396
17443
  const hasHeight = heightRaw != null;
17397
17444
  if ((hasWidth || hasHeight) && !base) {
17398
17445
  if (!hasWidth || !hasHeight) {
17399
- output.writeWarn("Both --width and --height are required when not using --device");
17446
+ output?.writeWarn("Both --width and --height are required when not using --device");
17400
17447
  process.exitCode = 2;
17401
- return;
17448
+ return null;
17402
17449
  }
17403
17450
  }
17404
17451
  if (!base && !hasWidth) {
17405
- output.writeWarn("Provide --device or --width + --height");
17406
- output.writeWarn(`Available devices: ${listPresetNames().join(", ")}`);
17452
+ output?.writeWarn("Provide --device or --width + --height");
17453
+ output?.writeWarn(`Available devices: ${listPresetNames().join(", ")}`);
17407
17454
  process.exitCode = 2;
17408
- return;
17455
+ return null;
17409
17456
  }
17410
17457
  const baseViewport = base?.viewport ?? null;
17411
17458
  const width = widthRaw ?? baseViewport?.width ?? 0;
@@ -17415,137 +17462,48 @@ var runPageEmulationSet = async (id, options) => {
17415
17462
  const viewport = { width, height, deviceScaleFactor: dpr, mobile };
17416
17463
  const touchEnabled = options.touch ?? base?.touch ?? false;
17417
17464
  const uaValue = options.ua !== undefined ? options.ua : base?.userAgent ?? null;
17418
- const state2 = {
17465
+ return {
17419
17466
  viewport,
17420
17467
  touch: { enabled: touchEnabled },
17421
17468
  userAgent: { value: uaValue }
17422
17469
  };
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
17470
  };
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;
17471
+ var runPageEmulationClear = defineWatcherCommand({
17472
+ build: () => ({ path: "/emulation", method: "POST", body: { action: "clear" }, timeoutMs: 1e4 }),
17473
+ formatHuman: (_response, { output }) => {
17474
+ output.writeHuman("Cleared emulation");
17476
17475
  }
17477
- const response = result.data;
17478
- if (!response.ok) {
17479
- const err = response;
17480
- if (options.json) {
17481
- output.writeJson(response);
17476
+ });
17477
+ var runPageEmulationStatus = defineWatcherCommand({
17478
+ build: () => ({ path: "/emulation", method: "GET", timeoutMs: 5000 }),
17479
+ formatHuman: (res, { output }) => {
17480
+ const lines = [];
17481
+ lines.push(`attached: ${res.attached}`);
17482
+ lines.push(`applied: ${res.applied}`);
17483
+ if (res.state?.viewport) {
17484
+ const vp = res.state.viewport;
17485
+ lines.push(`viewport: ${vp.width}x${vp.height}@${vp.deviceScaleFactor} ${vp.mobile ? "mobile" : "desktop"}`);
17482
17486
  } else {
17483
- output.writeWarn(`Error: ${err.error.message}`);
17487
+ lines.push("viewport: none");
17484
17488
  }
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);
17489
+ lines.push(`touch: ${res.state?.touch?.enabled ? "enabled" : "disabled"}`);
17490
+ if (res.state?.userAgent?.value) {
17491
+ const ua = res.state.userAgent.value;
17492
+ lines.push(`ua: ${ua.length > 80 ? ua.slice(0, 77) + "..." : ua}`);
17512
17493
  } else {
17513
- output.writeWarn(`Error: ${err.error.message}`);
17494
+ lines.push("ua: default");
17514
17495
  }
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(`
17496
+ if (res.baseline.userAgent) {
17497
+ const bua = res.baseline.userAgent;
17498
+ lines.push(`baseline: ${bua.length > 80 ? bua.slice(0, 77) + "..." : bua}`);
17499
+ }
17500
+ if (res.lastError) {
17501
+ lines.push(`error: ${res.lastError.message}`);
17502
+ }
17503
+ output.writeHuman(lines.join(`
17547
17504
  `));
17548
- };
17505
+ }
17506
+ });
17549
17507
  var formatViewportLabel = (viewport, touch, ua) => {
17550
17508
  const parts = [`${viewport.width}x${viewport.height}@${viewport.deviceScaleFactor}`];
17551
17509
  if (viewport.mobile)
@@ -17915,46 +17873,38 @@ var appendRepeatedParams = (params, key, values) => {
17915
17873
  };
17916
17874
 
17917
17875
  // dist/commands/logs.js
17918
- var runLogs = async (id, options) => {
17919
- const output = createOutput(options);
17876
+ var runLogs = defineWatcherCommand({
17877
+ isJson: (options) => options.jsonFull === true,
17878
+ build: (_args, options, output) => buildLogsPlan(options, output),
17879
+ formatJson: (response, { options }) => options.jsonFull ? response.events : response.events.map((event) => previewLogEvent(event)),
17880
+ formatHuman: (response, { output, watcher }) => {
17881
+ for (const event of response.events) {
17882
+ output.writeHuman(formatLogEvent(event, {
17883
+ includeTimestamps: watcher.includeTimestamps
17884
+ }));
17885
+ }
17886
+ }
17887
+ });
17888
+ var buildLogsPlan = (options, output) => {
17920
17889
  const params = new URLSearchParams;
17921
17890
  appendAfterLimitParams(params, options);
17922
17891
  const filters = appendLogFilterParams(params, options);
17923
17892
  if (filters.error) {
17924
17893
  output.writeWarn(filters.error);
17925
17894
  process.exitCode = 2;
17926
- return;
17895
+ return null;
17927
17896
  }
17928
17897
  const since = appendSinceParam(params, options.since);
17929
17898
  if (since.error) {
17930
17899
  output.writeWarn(since.error);
17931
17900
  process.exitCode = 2;
17932
- return;
17901
+ return null;
17933
17902
  }
17934
- const result = await requestWatcherJson({
17935
- id,
17903
+ return {
17936
17904
  path: "/logs",
17937
17905
  query: params,
17938
17906
  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
- }
17907
+ };
17958
17908
  };
17959
17909
 
17960
17910
  // dist/commands/tail.js
@@ -18060,23 +18010,12 @@ Examples:
18060
18010
  }
18061
18011
 
18062
18012
  // 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;
18013
+ var runNetClear = defineWatcherCommand({
18014
+ build: () => ({ path: "/net/clear", method: "POST", timeoutMs: 5000 }),
18015
+ formatHuman: (response, { output, watcher }) => {
18016
+ output.writeHuman(`cleared ${response.cleared} requests from ${watcher.id}`);
18073
18017
  }
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
- };
18018
+ });
18080
18019
 
18081
18020
  // dist/commands/netExport.js
18082
18021
  import fs9 from "node:fs/promises";
@@ -18256,6 +18195,31 @@ var resolveDerivedNetFilters = (options) => {
18256
18195
 
18257
18196
  // dist/commands/evalShared.js
18258
18197
  import { readFile as readFile2 } from "node:fs/promises";
18198
+ var prepareEvalExpression = async (inline, options, output) => {
18199
+ const evalArgs = parseEvalArgs(options.arg);
18200
+ if (evalArgs.error) {
18201
+ output.writeWarn(evalArgs.error);
18202
+ return null;
18203
+ }
18204
+ const resolvedExpression = await resolveExpression(inline, options, output);
18205
+ if (resolvedExpression == null) {
18206
+ return null;
18207
+ }
18208
+ const args = evalArgs.value;
18209
+ if (!options.iframe) {
18210
+ return {
18211
+ expression: resolvedExpression,
18212
+ args: hasEvalArgs2(args) ? args : undefined
18213
+ };
18214
+ }
18215
+ return {
18216
+ expression: wrapForIframeEval(wrapExpressionWithArgs(resolvedExpression, args), {
18217
+ selector: options.iframe,
18218
+ namespace: options.iframeNamespace ?? "argus",
18219
+ timeoutMs: parseNumber(options.iframeTimeout) ?? 5000
18220
+ })
18221
+ };
18222
+ };
18259
18223
  var resolveExpression = async (inline, options, output) => {
18260
18224
  const wantsStdin = options.stdin === true || inline === "-";
18261
18225
  const hasInline = inline != null && inline !== "-";
@@ -18272,6 +18236,7 @@ var resolveExpression = async (inline, options, output) => {
18272
18236
  output.writeWarn("Cannot combine --file with stdin input");
18273
18237
  return null;
18274
18238
  }
18239
+ let expression;
18275
18240
  if (hasFile) {
18276
18241
  try {
18277
18242
  const content = await readFile2(options.file ?? "", "utf8");
@@ -18279,34 +18244,51 @@ var resolveExpression = async (inline, options, output) => {
18279
18244
  output.writeWarn(`File is empty: ${options.file}`);
18280
18245
  return null;
18281
18246
  }
18282
- return content;
18247
+ expression = content;
18283
18248
  } catch (error) {
18284
18249
  output.writeWarn(`Failed to read --file: ${formatError(error)}`);
18285
18250
  return null;
18286
18251
  }
18287
- }
18288
- if (wantsStdin) {
18252
+ } else if (wantsStdin) {
18289
18253
  try {
18290
18254
  const content = await readStdin();
18291
18255
  if (!content.trim()) {
18292
18256
  output.writeWarn("Stdin input is empty");
18293
18257
  return null;
18294
18258
  }
18295
- return content;
18259
+ expression = content;
18296
18260
  } catch (error) {
18297
18261
  output.writeWarn(`Failed to read stdin: ${formatError(error)}`);
18298
18262
  return null;
18299
18263
  }
18300
- }
18301
- if (hasInline) {
18264
+ } else if (hasInline) {
18302
18265
  if (!inline.trim()) {
18303
18266
  output.writeWarn("Expression is empty");
18304
18267
  return null;
18305
18268
  }
18306
- return inline;
18269
+ expression = inline;
18270
+ } else {
18271
+ output.writeWarn("Expression is required. Provide an inline expression, --file, or --stdin (or pass - as expression).");
18272
+ return null;
18273
+ }
18274
+ return await prependInjectSource(expression, options.inject, output);
18275
+ };
18276
+ var prependInjectSource = async (expression, injectPath, output) => {
18277
+ if (injectPath == null) {
18278
+ return expression;
18279
+ }
18280
+ try {
18281
+ const injectSource = await readFile2(injectPath, "utf8");
18282
+ if (!injectSource.trim()) {
18283
+ output.writeWarn(`Inject file is empty: ${injectPath}`);
18284
+ return null;
18285
+ }
18286
+ return `${injectSource}
18287
+ ${expression}`;
18288
+ } catch (error) {
18289
+ output.writeWarn(`Failed to read --inject: ${formatError(error)}`);
18290
+ return null;
18307
18291
  }
18308
- output.writeWarn("Expression is required. Provide an inline expression, --file, or --stdin (or pass - as expression).");
18309
- return null;
18310
18292
  };
18311
18293
  var readStdin = async () => new Promise((resolve2, reject) => {
18312
18294
  let data = "";
@@ -19380,32 +19362,28 @@ var writeInspectNoMatch = (output, pattern, settleMs, timedOut) => {
19380
19362
  };
19381
19363
 
19382
19364
  // dist/commands/net.js
19383
- var runNet = async (id, options) => {
19384
- const output = createOutput(options);
19365
+ var runNet = defineWatcherCommand({
19366
+ build: (_args, options, output) => buildNetPlan(options, output),
19367
+ formatJson: (response) => response.requests,
19368
+ formatHuman: (response, { output }) => {
19369
+ for (const request of response.requests) {
19370
+ output.writeHuman(formatNetworkRequest(request));
19371
+ }
19372
+ }
19373
+ });
19374
+ var buildNetPlan = (options, output) => {
19385
19375
  const params = new URLSearchParams;
19386
19376
  const query = appendNetCommandParams(params, options);
19387
19377
  if (query.error) {
19388
19378
  output.writeWarn(query.error);
19389
19379
  process.exitCode = 2;
19390
- return;
19380
+ return null;
19391
19381
  }
19392
- const result = await requestWatcherJson({
19393
- id,
19382
+ return {
19394
19383
  path: "/net",
19395
19384
  query: params,
19396
19385
  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
- }
19386
+ };
19409
19387
  };
19410
19388
 
19411
19389
  // dist/commands/netRequestTarget.js
@@ -19717,7 +19695,7 @@ var runNetSse = async (id, options) => {
19717
19695
  process.exitCode = 2;
19718
19696
  return;
19719
19697
  }
19720
- const result = await requestWatcherAction({ id, path: "/net/sse", query: params, timeoutMs: 5000 }, output);
19698
+ const result = await requestWatcherCommandAction({ id, path: "/net/sse", query: params, timeoutMs: 5000 }, output);
19721
19699
  if (!result) {
19722
19700
  return;
19723
19701
  }
@@ -19836,7 +19814,7 @@ var runNetWebSocket = async (id, options) => {
19836
19814
  process.exitCode = 2;
19837
19815
  return;
19838
19816
  }
19839
- const result = await requestWatcherAction({ id, path: "/net/ws", query: params, timeoutMs: 5000 }, output);
19817
+ const result = await requestWatcherCommandAction({ id, path: "/net/ws", query: params, timeoutMs: 5000 }, output);
19840
19818
  if (!result) {
19841
19819
  return;
19842
19820
  }
@@ -19854,7 +19832,7 @@ var runNetWebSocketShow = async (id, connection, options) => {
19854
19832
  if (!query) {
19855
19833
  return;
19856
19834
  }
19857
- const result = await requestWatcherAction({ id, path: "/net/ws/connection", query, timeoutMs: 5000 }, output);
19835
+ const result = await requestWatcherCommandAction({ id, path: "/net/ws/connection", query, timeoutMs: 5000 }, output);
19858
19836
  if (!result) {
19859
19837
  return;
19860
19838
  }
@@ -20142,16 +20120,14 @@ var toCamelCase = (flag) => flag.replace(/^--/, "").replace(/-([a-z])/g, (_match
20142
20120
  import { writeFile as writeFile2 } from "node:fs/promises";
20143
20121
  var COOKIE_EXPORT_FORMATS = new Set(["netscape", "json", "header"]);
20144
20122
  var fetchAuthCookies = async (id, input, output) => {
20145
- const result = await requestWatcherJson({
20123
+ const result = await requestWatcherCommandJson({
20146
20124
  id,
20147
20125
  path: "/auth/cookies",
20148
20126
  query: buildCookieQuery(input),
20149
20127
  timeoutMs: 1e4
20150
- });
20151
- if (!result.ok) {
20152
- writeRequestError(result, output);
20128
+ }, output);
20129
+ if (!result)
20153
20130
  return null;
20154
- }
20155
20131
  return result.data;
20156
20132
  };
20157
20133
  var normalizeCookieIdentityInput = (input, output) => {
@@ -20380,116 +20356,104 @@ var runAuthExportCookies = async (id, options) => {
20380
20356
  const serialized = serializeCookies(filterCookies(response.origin, response.cookies, options), format);
20381
20357
  await writeOutput2(serialized, options.out);
20382
20358
  };
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
20359
+ var runAuthCookieGet = defineWatcherCommand({
20360
+ build: ([name], options, output) => {
20361
+ const identity = normalizeCookieIdentityInput({ name, domain: options.domain, path: options.path }, output);
20362
+ if (!identity) {
20363
+ return null;
20396
20364
  }
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;
20365
+ return {
20366
+ path: "/auth/cookies/get",
20367
+ method: "POST",
20368
+ body: {
20369
+ ...identity,
20370
+ includeValue: options.showValue === true
20371
+ }
20372
+ };
20373
+ },
20374
+ formatHuman: (response, { output, options }) => {
20375
+ if (!response.cookie) {
20376
+ output.writeHuman("No cookie matched exact identity.");
20377
+ return;
20378
+ }
20379
+ output.writeHuman(formatCookieLine(response.cookie, options.showValue === true));
20462
20380
  }
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
20381
+ });
20382
+ var runAuthCookieSet = defineWatcherCommand({
20383
+ build: ([name, value], options, output) => {
20384
+ const cookie = parseCookieSetInput({ name, value, ...options }, output);
20385
+ if (!cookie) {
20386
+ return null;
20472
20387
  }
20473
- }, output);
20474
- if (!result) {
20475
- return;
20388
+ return {
20389
+ path: "/auth/cookies/set",
20390
+ method: "POST",
20391
+ body: { cookie }
20392
+ };
20393
+ },
20394
+ formatHuman: (response, { output }) => {
20395
+ output.writeHuman(`Set ${formatCookieLine(response.cookie, false)}`);
20476
20396
  }
20477
- if (options.json) {
20478
- output.writeJson(result.data);
20479
- return;
20397
+ });
20398
+ var runAuthCookieDelete = defineWatcherCommand({
20399
+ build: ([name], options, output) => {
20400
+ const identity = normalizeCookieIdentityInput({ name, domain: options.domain, path: options.path }, output);
20401
+ if (!identity) {
20402
+ return null;
20403
+ }
20404
+ return {
20405
+ path: "/auth/cookies/delete",
20406
+ method: "POST",
20407
+ body: identity
20408
+ };
20409
+ },
20410
+ formatHuman: (response, { output }) => {
20411
+ if (!response.deleted) {
20412
+ output.writeHuman("No cookie matched exact identity.");
20413
+ return;
20414
+ }
20415
+ output.writeHuman(`Deleted ${formatCookieIdentityLine(response.cookie)}`);
20480
20416
  }
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));
20417
+ });
20418
+ var runAuthCookieClear = defineWatcherCommand({
20419
+ build: (_args, options, output) => {
20420
+ const scope = resolveCookieClearScope(options, output);
20421
+ if (!scope) {
20422
+ return null;
20423
+ }
20424
+ return {
20425
+ path: "/auth/cookies/clear",
20426
+ method: "POST",
20427
+ body: {
20428
+ scope,
20429
+ domain: scope === "domain" ? options.domain : undefined,
20430
+ sessionOnly: options.sessionOnly === true,
20431
+ authOnly: options.authOnly === true
20432
+ }
20433
+ };
20434
+ },
20435
+ formatHuman: (response, { output }) => {
20436
+ const scopeSuffix = response.scopeValue ? ` (${response.scopeValue})` : "";
20437
+ output.writeHuman(`Cleared ${response.cleared} cookie(s) from ${response.scope}${scopeSuffix}`);
20438
+ for (const cookie of response.cookies) {
20439
+ output.writeHuman(formatCookieIdentityLine(cookie));
20440
+ }
20485
20441
  }
20486
- };
20442
+ });
20487
20443
 
20488
20444
  // dist/cli/register/registerAuth.js
20489
20445
  function registerAuth(program2) {
20490
20446
  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", `
20447
+ 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", `
20448
+ Examples:
20449
+ $ argus auth cookies app
20450
+ $ argus auth cookies app --for-origin --exclude-tracking
20451
+ $ argus auth cookies app --domain example.com
20452
+ $ argus auth cookies app --session-only --show-values
20453
+ `).action(async (id, options) => {
20454
+ await runAuthCookies(id, options);
20455
+ });
20456
+ 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
20457
  Examples:
20494
20458
  $ argus auth cookies list app
20495
20459
  $ argus auth cookies ls app --for-origin --exclude-tracking
@@ -20571,27 +20535,71 @@ Examples:
20571
20535
  });
20572
20536
  }
20573
20537
  var addCookieScopeOptions = (command, forOriginDescription) => command.option("--for-origin", forOriginDescription).option("--exclude-tracking", "Hide common analytics/tracking cookies such as _ga and _ym");
20538
+ 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");
20539
+
20540
+ // dist/commands/evalPolling.js
20541
+ var pollEval = async (input) => {
20542
+ let running = true;
20543
+ const stop = () => {
20544
+ running = false;
20545
+ };
20546
+ process.on("SIGINT", stop);
20547
+ process.on("SIGTERM", stop);
20548
+ try {
20549
+ const startTime = Date.now();
20550
+ let iteration = 0;
20551
+ while (running) {
20552
+ if (input.totalTimeoutMs != null) {
20553
+ const elapsed = Date.now() - startTime;
20554
+ if (elapsed >= input.totalTimeoutMs) {
20555
+ return { kind: "timeout", elapsedMs: elapsed };
20556
+ }
20557
+ }
20558
+ iteration += 1;
20559
+ const result = await evalWithRetries({
20560
+ watcher: input.watcher,
20561
+ expression: input.expression,
20562
+ args: input.args,
20563
+ awaitPromise: input.awaitPromise,
20564
+ returnByValue: input.returnByValue,
20565
+ timeoutMs: input.timeoutMs,
20566
+ failOnException: input.failOnException,
20567
+ retryCount: input.retryCount
20568
+ });
20569
+ if (!result.ok) {
20570
+ return { kind: "eval-error", failure: result };
20571
+ }
20572
+ const context = { response: result.response, iteration, attempt: result.attempt };
20573
+ input.onResult?.(result.response, context);
20574
+ const stopResult = input.shouldStop?.(context);
20575
+ if (stopResult) {
20576
+ if (!stopResult.ok) {
20577
+ return { kind: "condition-error", error: stopResult.error };
20578
+ }
20579
+ if (stopResult.matched) {
20580
+ return { kind: "matched", response: result.response, iteration, attempt: result.attempt };
20581
+ }
20582
+ }
20583
+ if (input.count != null && iteration >= input.count) {
20584
+ return { kind: "exhausted", iterations: iteration };
20585
+ }
20586
+ await sleep3(input.intervalMs);
20587
+ }
20588
+ return { kind: "interrupted" };
20589
+ } finally {
20590
+ process.off("SIGINT", stop);
20591
+ process.off("SIGTERM", stop);
20592
+ }
20593
+ };
20574
20594
 
20575
20595
  // dist/commands/eval.js
20576
20596
  var runEval = async (id, rawExpression, options) => {
20577
20597
  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) {
20598
+ const prepared = await prepareEvalExpression(rawExpression, options, output);
20599
+ if (prepared == null) {
20586
20600
  process.exitCode = 2;
20587
20601
  return;
20588
20602
  }
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
20603
  const retryCount = parseRetryCount(options.retry);
20596
20604
  if (retryCount.error) {
20597
20605
  output.writeWarn(retryCount.error);
@@ -20634,8 +20642,8 @@ var runEval = async (id, rawExpression, options) => {
20634
20642
  if (intervalMs.value == null) {
20635
20643
  const singleResult = await evalWithRetries({
20636
20644
  watcher,
20637
- expression,
20638
- args: requestArgs,
20645
+ expression: prepared.expression,
20646
+ args: prepared.args,
20639
20647
  awaitPromise: options.await ?? true,
20640
20648
  returnByValue: options.returnByValue ?? true,
20641
20649
  timeoutMs,
@@ -20650,51 +20658,45 @@ var runEval = async (id, rawExpression, options) => {
20650
20658
  printSuccess(singleResult.response, options, output, false);
20651
20659
  return;
20652
20660
  }
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) {
20661
+ const pollResult = await pollEval({
20662
+ watcher,
20663
+ expression: prepared.expression,
20664
+ args: prepared.args,
20665
+ awaitPromise: options.await ?? true,
20666
+ returnByValue: options.returnByValue ?? true,
20667
+ timeoutMs,
20668
+ failOnException: options.failOnException ?? true,
20669
+ retryCount: retryCount.value,
20670
+ intervalMs: intervalMs.value,
20671
+ count: countValue.value,
20672
+ onResult: (response) => {
20673
+ printSuccess(response, options, output, true);
20674
+ },
20675
+ shouldStop: (context) => {
20676
+ if (!untilEvaluator.evaluator) {
20677
+ return { ok: true, matched: false };
20678
+ }
20679
20679
  const untilResult = untilEvaluator.evaluator({
20680
- result: iterationResult.response.result,
20681
- exception: iterationResult.response.exception ?? null,
20682
- iteration,
20683
- attempt: iterationResult.attempt
20680
+ result: context.response.result,
20681
+ exception: context.response.exception ?? null,
20682
+ iteration: context.iteration,
20683
+ attempt: context.attempt
20684
20684
  });
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;
20685
+ return untilResult.ok ? { ok: true, matched: untilResult.matched } : { ok: false, error: untilResult.error };
20696
20686
  }
20697
- await sleep3(intervalMs.value);
20687
+ });
20688
+ if (pollResult.kind === "eval-error") {
20689
+ printError(pollResult.failure, options, output);
20690
+ process.exitCode = 1;
20691
+ return;
20692
+ }
20693
+ if (pollResult.kind === "condition-error") {
20694
+ printError({ kind: "until", error: pollResult.error }, options, output);
20695
+ process.exitCode = 1;
20696
+ return;
20697
+ }
20698
+ if (pollResult.kind === "interrupted") {
20699
+ process.exitCode = 130;
20698
20700
  }
20699
20701
  };
20700
20702
  var compileUntil = (condition) => {
@@ -20725,23 +20727,11 @@ var compileUntil = (condition) => {
20725
20727
  // dist/commands/evalUntil.js
20726
20728
  var runEvalUntil = async (id, rawExpression, options) => {
20727
20729
  const output = createOutput(options);
20728
- const evalArgs = parseEvalArgs(options.arg);
20729
- if (evalArgs.error) {
20730
- output.writeWarn(evalArgs.error);
20730
+ const prepared = await prepareEvalExpression(rawExpression, options, output);
20731
+ if (prepared == null) {
20731
20732
  process.exitCode = 2;
20732
20733
  return;
20733
20734
  }
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
20735
  const retryCount = parseRetryCount(options.retry);
20746
20736
  if (retryCount.error) {
20747
20737
  output.writeWarn(retryCount.error);
@@ -20772,57 +20762,50 @@ var runEvalUntil = async (id, rawExpression, options) => {
20772
20762
  return;
20773
20763
  const { watcher } = resolved;
20774
20764
  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;
20765
+ const pollResult = await pollEval({
20766
+ watcher,
20767
+ expression: prepared.expression,
20768
+ args: prepared.args,
20769
+ awaitPromise: options.await ?? true,
20770
+ returnByValue: options.returnByValue ?? true,
20771
+ timeoutMs,
20772
+ failOnException: options.failOnException ?? true,
20773
+ retryCount: retryCount.value,
20774
+ intervalMs: pollIntervalMs,
20775
+ count: countValue.value,
20776
+ totalTimeoutMs: totalTimeoutMs.value,
20777
+ onResult: (response) => {
20778
+ if (!response.result && options.verbose) {
20779
+ printSuccess(response, options, output, true);
20792
20780
  }
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);
20781
+ },
20782
+ shouldStop: ({ response }) => ({ ok: true, matched: Boolean(response.result) })
20783
+ });
20784
+ if (pollResult.kind === "matched") {
20785
+ printSuccess(pollResult.response, options, output, false);
20786
+ return;
20787
+ }
20788
+ if (pollResult.kind === "eval-error") {
20789
+ printError(pollResult.failure, options, output);
20790
+ process.exitCode = 1;
20791
+ return;
20792
+ }
20793
+ if (pollResult.kind === "timeout") {
20794
+ output.writeWarn(`Total timeout exceeded (${options.totalTimeout})`);
20795
+ process.exitCode = 1;
20796
+ return;
20797
+ }
20798
+ if (pollResult.kind === "exhausted") {
20799
+ output.writeWarn(`Exhausted after ${pollResult.iterations} iterations without a truthy result`);
20800
+ process.exitCode = 1;
20801
+ return;
20824
20802
  }
20825
- if (interrupted) {
20803
+ if (pollResult.kind === "condition-error") {
20804
+ output.writeWarn(pollResult.error);
20805
+ process.exitCode = 1;
20806
+ return;
20807
+ }
20808
+ if (pollResult.kind === "interrupted") {
20826
20809
  process.exitCode = 130;
20827
20810
  }
20828
20811
  };
@@ -20911,11 +20894,12 @@ ${script}})();
20911
20894
  // dist/cli/register/registerEval.js
20912
20895
  var collectValue = (value, previous = []) => [...previous, value];
20913
20896
  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", `
20897
+ 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("--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
20898
  Examples:
20916
20899
  $ argus eval app "location.href"
20917
20900
  $ argus eval app "await fetch('/ping').then(r => r.status)"
20918
20901
  $ argus eval app --file ./script.js
20902
+ $ argus eval app "window.store.getState()" --inject ./debug-hooks.js
20919
20903
  $ argus eval app --file ./script.js --arg level=10 --arg mode=fast
20920
20904
  $ cat script.js | argus eval app --stdin
20921
20905
  $ argus eval app - < script.js
@@ -20940,6 +20924,7 @@ Examples:
20940
20924
  until: options.until,
20941
20925
  file: options.file,
20942
20926
  stdin: options.stdin,
20927
+ inject: options.inject,
20943
20928
  iframe: options.iframe,
20944
20929
  iframeNamespace: options.iframeNamespace,
20945
20930
  iframeTimeout: options.iframeTimeout,
@@ -20955,7 +20940,7 @@ Examples:
20955
20940
  `).action(async (options) => {
20956
20941
  await runIframeHelper(options);
20957
20942
  });
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", `
20943
+ 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("--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
20944
  Examples:
20960
20945
  $ argus eval-until app "document.querySelector('#loaded')"
20961
20946
  $ argus eval-until app "await window.appReadyPromise"
@@ -20964,6 +20949,7 @@ Examples:
20964
20949
  $ argus eval-until app "window.data" --verbose
20965
20950
  $ argus eval-until app "window.data" --count 20 --interval 1s
20966
20951
  $ argus eval-until app --file ./check.js --total-timeout 1m
20952
+ $ argus wait app "window.appReady" --inject ./debug-hooks.js --total-timeout 20s
20967
20953
  $ argus wait app --file ./ready.js --arg level=10 --total-timeout 20s
20968
20954
  `).action(async (id, expression, options) => {
20969
20955
  await runEvalUntil(id, expression, {
@@ -20980,6 +20966,7 @@ Examples:
20980
20966
  verbose: options.verbose,
20981
20967
  file: options.file,
20982
20968
  stdin: options.stdin,
20969
+ inject: options.inject,
20983
20970
  iframe: options.iframe,
20984
20971
  iframeNamespace: options.iframeNamespace,
20985
20972
  iframeTimeout: options.iframeTimeout,
@@ -21072,26 +21059,35 @@ var escapeAttrValue = (value) => {
21072
21059
  };
21073
21060
 
21074
21061
  // dist/commands/domTree.js
21075
- var runDomTree = async (id, options) => {
21076
- const output = createOutput(options);
21062
+ var runDomTree = defineWatcherCommand({
21063
+ build: (_args, options, output) => buildDomTreePlan(options, output),
21064
+ formatHuman: (response, { output, options }) => {
21065
+ if (response.matches === 0) {
21066
+ writeNoElementFound(options.selector, output);
21067
+ return;
21068
+ }
21069
+ const formatted = formatDomTree(response.roots, response.truncated, response.truncatedReason);
21070
+ output.writeHuman(formatted);
21071
+ }
21072
+ });
21073
+ var buildDomTreePlan = (options, output) => {
21077
21074
  const selector = requireSelector(options, output);
21078
21075
  if (!selector) {
21079
- return;
21076
+ return null;
21080
21077
  }
21081
21078
  const depth = parsePositiveInt(options.depth, { allowZero: true });
21082
21079
  if (options.depth !== undefined && depth === undefined) {
21083
21080
  output.writeWarn("--depth must be a non-negative integer");
21084
21081
  process.exitCode = 2;
21085
- return;
21082
+ return null;
21086
21083
  }
21087
21084
  const maxNodes = parsePositiveInt(options.maxNodes);
21088
21085
  if (options.maxNodes !== undefined && maxNodes === undefined) {
21089
21086
  output.writeWarn("--max-nodes must be a positive integer");
21090
21087
  process.exitCode = 2;
21091
- return;
21088
+ return null;
21092
21089
  }
21093
- const result = await requestWatcherAction({
21094
- id,
21090
+ return {
21095
21091
  path: "/dom/tree",
21096
21092
  method: "POST",
21097
21093
  body: {
@@ -21102,38 +21098,34 @@ var runDomTree = async (id, options) => {
21102
21098
  text: options.text
21103
21099
  },
21104
21100
  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);
21101
+ };
21120
21102
  };
21121
21103
 
21122
21104
  // dist/commands/domInfo.js
21123
- var runDomInfo = async (id, options) => {
21124
- const output = createOutput(options);
21105
+ var runDomInfo = defineWatcherCommand({
21106
+ build: (_args, options, output) => buildDomInfoPlan(options, output),
21107
+ formatHuman: (response, { output, options }) => {
21108
+ const target = { selector: options.selector, ref: options.ref };
21109
+ if (response.matches === 0) {
21110
+ writeNoElementFound(target.selector ?? target.ref, output);
21111
+ return;
21112
+ }
21113
+ const formatted = formatDomInfo(response.elements);
21114
+ output.writeHuman(formatted);
21115
+ }
21116
+ });
21117
+ var buildDomInfoPlan = (options, output) => {
21125
21118
  const target = requireElementTarget({ selector: options.selector, ref: options.ref }, output);
21126
21119
  if (!target) {
21127
- return;
21120
+ return null;
21128
21121
  }
21129
21122
  const outerHtmlMaxChars = parsePositiveInt(options.outerHtmlMax);
21130
21123
  if (options.outerHtmlMax !== undefined && outerHtmlMaxChars === undefined) {
21131
21124
  output.writeWarn("--outer-html-max must be a positive integer");
21132
21125
  process.exitCode = 2;
21133
- return;
21126
+ return null;
21134
21127
  }
21135
- const result = await requestWatcherAction({
21136
- id,
21128
+ return {
21137
21129
  path: "/dom/info",
21138
21130
  method: "POST",
21139
21131
  body: {
@@ -21144,21 +21136,7 @@ var runDomInfo = async (id, options) => {
21144
21136
  text: options.text
21145
21137
  },
21146
21138
  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);
21139
+ };
21162
21140
  };
21163
21141
 
21164
21142
  // dist/commands/domFocus.js
@@ -21191,50 +21169,62 @@ var runDomFocus = defineWatcherCommand({
21191
21169
 
21192
21170
  // dist/commands/domAdd.js
21193
21171
  import { readFile as readFile3 } from "node:fs/promises";
21194
- var runDomAdd = async (id, options) => {
21195
- const output = createOutput(options);
21172
+ var runDomAdd = defineWatcherCommand({
21173
+ build: (_args, options, output) => buildDomAddPlan(options, output),
21174
+ formatHuman: (successResp, { output, options, watcher }) => {
21175
+ const selector = options.selector;
21176
+ const position = normalizePosition(options.position);
21177
+ if (successResp.matches === 0) {
21178
+ writeNoElementFound(selector, output, `Hint: run \`argus dom tree ${watcher.id} --selector "${selector}" --all\``);
21179
+ return;
21180
+ }
21181
+ const label = successResp.inserted === 1 ? "element" : "elements";
21182
+ const prefix = watcher.id ? `${watcher.id}: ` : "";
21183
+ output.writeHuman(`${prefix}Inserted at ${successResp.inserted}/${successResp.matches} ${label} (${position}) for selector: ${selector}`);
21184
+ }
21185
+ });
21186
+ var buildDomAddPlan = async (options, output) => {
21196
21187
  const selector = requireSelector(options, output);
21197
21188
  if (!selector) {
21198
- return;
21189
+ return null;
21199
21190
  }
21200
21191
  const position = normalizePosition(options.position);
21201
21192
  if (!position) {
21202
21193
  output.writeWarn("--position must be one of: beforebegin, afterbegin, beforeend, afterend (aliases: before, after, prepend, append)");
21203
21194
  process.exitCode = 2;
21204
- return;
21195
+ return null;
21205
21196
  }
21206
21197
  if (options.all && (options.nth != null || options.first)) {
21207
21198
  output.writeWarn("Cannot combine --all with --nth or --first");
21208
21199
  process.exitCode = 2;
21209
- return;
21200
+ return null;
21210
21201
  }
21211
21202
  if (options.nth != null && options.first) {
21212
21203
  output.writeWarn("Cannot combine --nth with --first");
21213
21204
  process.exitCode = 2;
21214
- return;
21205
+ return null;
21215
21206
  }
21216
21207
  const nth = options.first ? 0 : parseNonNegativeInt(options.nth, "--nth", output);
21217
21208
  if (options.nth != null && nth == null) {
21218
21209
  process.exitCode = 2;
21219
- return;
21210
+ return null;
21220
21211
  }
21221
21212
  const expect = parseNonNegativeInt(options.expect, "--expect", output);
21222
21213
  if (options.expect != null && expect == null) {
21223
21214
  process.exitCode = 2;
21224
- return;
21215
+ return null;
21225
21216
  }
21226
21217
  const html = await resolveHtmlInput(options, output);
21227
21218
  if (html == null) {
21228
21219
  process.exitCode = 2;
21229
- return;
21220
+ return null;
21230
21221
  }
21231
21222
  if (!html) {
21232
21223
  output.writeWarn("HTML content is empty. Provide non-empty --html, --html-file, or --html-stdin input.");
21233
21224
  process.exitCode = 2;
21234
- return;
21225
+ return null;
21235
21226
  }
21236
- const result = await requestWatcherAction({
21237
- id,
21227
+ return {
21238
21228
  path: "/dom/add",
21239
21229
  method: "POST",
21240
21230
  body: {
@@ -21247,22 +21237,7 @@ var runDomAdd = async (id, options) => {
21247
21237
  text: options.text ?? false
21248
21238
  },
21249
21239
  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}`);
21240
+ };
21266
21241
  };
21267
21242
  var normalizePosition = (value) => {
21268
21243
  if (!value) {
@@ -21591,25 +21566,28 @@ var buildSetFilePlan = (options, output) => {
21591
21566
  };
21592
21567
 
21593
21568
  // dist/commands/domScroll.js
21594
- var runDomScroll = async (id, options) => {
21595
- const output = createOutput(options);
21569
+ var runDomScroll = defineWatcherCommand({
21570
+ build: (_args, options, output) => buildDomScrollPlan(options, output),
21571
+ formatHuman: formatDomScrollHuman
21572
+ });
21573
+ var buildDomScrollPlan = (options, output) => {
21596
21574
  if (options.by == null) {
21597
21575
  output.writeWarn("--by is required (e.g. --by 0,300)");
21598
21576
  process.exitCode = 2;
21599
- return;
21577
+ return null;
21600
21578
  }
21601
21579
  const delta = parseXY(options.by);
21602
21580
  if (!delta) {
21603
21581
  output.writeWarn('--by must be in the format "x,y" (e.g. --by 0,300)');
21604
21582
  process.exitCode = 2;
21605
- return;
21583
+ return null;
21606
21584
  }
21607
21585
  const hasSelector = options.selector != null && options.selector.trim() !== "";
21608
21586
  const hasPos = options.pos != null;
21609
21587
  if (hasSelector && hasPos) {
21610
21588
  output.writeWarn("--selector and --pos are mutually exclusive");
21611
21589
  process.exitCode = 2;
21612
- return;
21590
+ return null;
21613
21591
  }
21614
21592
  let x;
21615
21593
  let y;
@@ -21618,7 +21596,7 @@ var runDomScroll = async (id, options) => {
21618
21596
  if (!parsed) {
21619
21597
  output.writeWarn('--pos must be in the format "x,y" (e.g. --pos 400,300)');
21620
21598
  process.exitCode = 2;
21621
- return;
21599
+ return null;
21622
21600
  }
21623
21601
  x = parsed.x;
21624
21602
  y = parsed.y;
@@ -21635,21 +21613,12 @@ var runDomScroll = async (id, options) => {
21635
21613
  body.x = x;
21636
21614
  body.y = y;
21637
21615
  }
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
- }
21616
+ return { path: "/dom/scroll", method: "POST", body, timeoutMs: 30000 };
21617
+ };
21618
+ function formatDomScrollHuman(successResp, { output, options }) {
21619
+ const delta = parseXY(options.by);
21620
+ const hasSelector = options.selector != null && options.selector.trim() !== "";
21621
+ const hasPos = options.pos != null;
21653
21622
  if (hasSelector) {
21654
21623
  if (successResp.matches === 0) {
21655
21624
  writeNoElementFound(options.selector, output);
@@ -21658,27 +21627,31 @@ var runDomScroll = async (id, options) => {
21658
21627
  const label = successResp.scrolled === 1 ? "element" : "elements";
21659
21628
  output.writeHuman(`Emulated scroll on ${successResp.scrolled} ${label} by (${delta.x}, ${delta.y})`);
21660
21629
  } else if (hasPos) {
21630
+ const { x, y } = parseXY(options.pos);
21661
21631
  output.writeHuman(`Emulated scroll at (${x}, ${y}) by (${delta.x}, ${delta.y})`);
21662
21632
  } else {
21663
21633
  output.writeHuman(`Emulated scroll at viewport center by (${delta.x}, ${delta.y})`);
21664
21634
  }
21665
- };
21635
+ }
21666
21636
 
21667
21637
  // dist/commands/domScrollTo.js
21668
- var runDomScrollTo = async (id, options) => {
21669
- const output = createOutput(options);
21638
+ var runDomScrollTo = defineWatcherCommand({
21639
+ build: (_args, options, output) => buildDomScrollToPlan(options, output),
21640
+ formatHuman: formatDomScrollToHuman
21641
+ });
21642
+ var buildDomScrollToPlan = (options, output) => {
21670
21643
  const hasSelector = options.selector != null && options.selector.trim() !== "";
21671
21644
  const hasTo = options.to != null;
21672
21645
  const hasBy = options.by != null;
21673
21646
  if (!hasSelector && !hasTo && !hasBy) {
21674
21647
  output.writeWarn("--selector, --to, or --by is required");
21675
21648
  process.exitCode = 2;
21676
- return;
21649
+ return null;
21677
21650
  }
21678
21651
  if (hasTo && hasBy) {
21679
21652
  output.writeWarn("--to and --by are mutually exclusive");
21680
21653
  process.exitCode = 2;
21681
- return;
21654
+ return null;
21682
21655
  }
21683
21656
  let to;
21684
21657
  let by;
@@ -21687,7 +21660,7 @@ var runDomScrollTo = async (id, options) => {
21687
21660
  if (!parsed) {
21688
21661
  output.writeWarn('--to must be in the format "x,y" (e.g. --to 0,1000)');
21689
21662
  process.exitCode = 2;
21690
- return;
21663
+ return null;
21691
21664
  }
21692
21665
  to = parsed;
21693
21666
  }
@@ -21696,7 +21669,7 @@ var runDomScrollTo = async (id, options) => {
21696
21669
  if (!parsed) {
21697
21670
  output.writeWarn('--by must be in the format "x,y" (e.g. --by 0,500)');
21698
21671
  process.exitCode = 2;
21699
- return;
21672
+ return null;
21700
21673
  }
21701
21674
  by = parsed;
21702
21675
  }
@@ -21714,21 +21687,10 @@ var runDomScrollTo = async (id, options) => {
21714
21687
  if (by) {
21715
21688
  body.by = by;
21716
21689
  }
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
- }
21690
+ return { path: "/dom/scroll-to", method: "POST", body, timeoutMs: 30000 };
21691
+ };
21692
+ function formatDomScrollToHuman(successResp, { output, options }) {
21693
+ const hasSelector = options.selector != null && options.selector.trim() !== "";
21732
21694
  if (hasSelector && successResp.matches === 0) {
21733
21695
  writeNoElementFound(options.selector, output);
21734
21696
  return;
@@ -21739,40 +21701,37 @@ var runDomScrollTo = async (id, options) => {
21739
21701
  } else {
21740
21702
  output.writeHuman(`Scrolled viewport (scrollX=${successResp.scrollX}, scrollY=${successResp.scrollY})`);
21741
21703
  }
21742
- };
21704
+ }
21743
21705
 
21744
21706
  // dist/commands/domModify.js
21745
21707
  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));
21708
+ await runDomModifyRequest(id, payload, successMessage, options);
21775
21709
  };
21710
+ var runDomModifyRequest = defineWatcherCommand({
21711
+ build: ([payload], options, output) => {
21712
+ const selector = requireSelector(options, output);
21713
+ if (!selector) {
21714
+ return null;
21715
+ }
21716
+ return {
21717
+ path: "/dom/modify",
21718
+ method: "POST",
21719
+ body: {
21720
+ ...payload,
21721
+ selector,
21722
+ ...options.text != null ? { text: options.text } : {}
21723
+ },
21724
+ timeoutMs: 30000
21725
+ };
21726
+ },
21727
+ formatHuman: (successResp, { output, options, args }) => {
21728
+ if (successResp.matches === 0) {
21729
+ writeNoElementFound(options.selector, output);
21730
+ return;
21731
+ }
21732
+ output.writeHuman(args[1](successResp));
21733
+ }
21734
+ });
21776
21735
  var parseAttrArgs = (attrs) => {
21777
21736
  const result = {};
21778
21737
  for (const attr of attrs) {
@@ -22150,47 +22109,28 @@ Examples:
22150
22109
  }
22151
22110
 
22152
22111
  // 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}`);
22112
+ var runDomKeydown = defineWatcherCommand({
22113
+ build: (_args, options, output) => {
22114
+ if (!options.key || options.key.trim() === "") {
22115
+ output.writeWarn("--key is required");
22116
+ process.exitCode = 2;
22117
+ return null;
22183
22118
  }
22184
- process.exitCode = 1;
22185
- return;
22186
- }
22187
- const successResp = response;
22188
- if (options.json) {
22189
- output.writeJson(successResp);
22190
- return;
22119
+ return {
22120
+ path: "/dom/keydown",
22121
+ method: "POST",
22122
+ body: {
22123
+ key: options.key,
22124
+ selector: options.selector,
22125
+ modifiers: options.modifiers
22126
+ },
22127
+ timeoutMs: 30000
22128
+ };
22129
+ },
22130
+ formatHuman: (response, { output }) => {
22131
+ output.writeHuman(`Dispatched keydown: ${response.key}`);
22191
22132
  }
22192
- output.writeHuman(`Dispatched keydown: ${successResp.key}`);
22193
- };
22133
+ });
22194
22134
 
22195
22135
  // dist/cli/register/registerKeydown.js
22196
22136
  function registerKeydown(program2) {
@@ -22531,19 +22471,23 @@ Examples:
22531
22471
 
22532
22472
  // dist/commands/screenshot.js
22533
22473
  var SCREENSHOT_REQUEST_TIMEOUT_MS = 45000;
22534
- var runScreenshot = async (id, options) => {
22535
- const output = createOutput(options);
22474
+ var runScreenshot = defineWatcherCommand({
22475
+ build: (_args, options, output) => buildScreenshotPlan(options, output),
22476
+ formatHuman: (response, { output }) => {
22477
+ output.writeHuman(`Screenshot saved: ${response.outFile}`);
22478
+ }
22479
+ });
22480
+ var buildScreenshotPlan = (options, output) => {
22536
22481
  const clip = parseScreenshotClip(options.clip, output);
22537
22482
  if (clip === null) {
22538
- return;
22483
+ return null;
22539
22484
  }
22540
22485
  const selector = normalizeSelector(options.selector);
22541
22486
  if (selector && clip) {
22542
22487
  writeScreenshotOptionError(output, "Cannot use both --selector and --clip");
22543
- return;
22488
+ return null;
22544
22489
  }
22545
- const result = await requestWatcherJson({
22546
- id,
22490
+ return {
22547
22491
  path: "/screenshot",
22548
22492
  method: "POST",
22549
22493
  body: {
@@ -22553,16 +22497,7 @@ var runScreenshot = async (id, options) => {
22553
22497
  format: "png"
22554
22498
  },
22555
22499
  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}`);
22500
+ };
22566
22501
  };
22567
22502
  var normalizeSelector = (selector) => {
22568
22503
  const trimmed = selector?.trim();
@@ -22850,6 +22785,7 @@ var DEFAULT_CONFIG_PATH = ".argus/config.json";
22850
22785
  var buildConfigTemplate = (schemaRef) => ({
22851
22786
  $schema: schemaRef,
22852
22787
  plugins: [],
22788
+ pluginAliases: {},
22853
22789
  chrome: {
22854
22790
  start: {
22855
22791
  url: "http://localhost:3000",
@@ -45252,30 +45188,9 @@ var writeCodeGrepWarnings = (skippedResources, output) => {
45252
45188
  }
45253
45189
  output.writeWarn(warning);
45254
45190
  };
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
45191
  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;
45192
+ const result = await requestWatcherCommandAction({ ...input, id, timeoutMs: 30000 }, output);
45193
+ return result?.data ?? null;
45279
45194
  };
45280
45195
 
45281
45196
  // dist/commands/codeEdit.js
@@ -45292,7 +45207,7 @@ var runCodeEdit = async (id, url, options) => {
45292
45207
  if (source === null) {
45293
45208
  return;
45294
45209
  }
45295
- const result = await requestWatcherAction({
45210
+ const result = await requestWatcherCommandAction({
45296
45211
  id,
45297
45212
  path: "/code/edit",
45298
45213
  method: "POST",
@@ -45403,7 +45318,7 @@ async function loadFullSource(id, url, output) {
45403
45318
  const chunks = [];
45404
45319
  let offset = 0;
45405
45320
  for (;; ) {
45406
- const result = await requestWatcherAction({
45321
+ const result = await requestWatcherCommandAction({
45407
45322
  id,
45408
45323
  path: "/code/read",
45409
45324
  method: "POST",
@@ -45554,7 +45469,7 @@ var runLocateRequest = async (id, path14, body, options) => {
45554
45469
  process.exitCode = 2;
45555
45470
  return;
45556
45471
  }
45557
- const result = await requestWatcherAction({
45472
+ const result = await requestWatcherCommandAction({
45558
45473
  id,
45559
45474
  path: path14,
45560
45475
  method: "POST",
@@ -45643,44 +45558,60 @@ Examples:
45643
45558
  });
45644
45559
  }
45645
45560
 
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
45561
  // dist/cli/plugins/registerPlugins.js
45677
45562
  import path14 from "node:path";
45563
+ import { existsSync as existsSync3 } from "node:fs";
45678
45564
  import { pathToFileURL as pathToFileURL2 } from "node:url";
45679
45565
 
45680
45566
  // ../argus-plugin-api/dist/index.js
45681
45567
  var ARGUS_PLUGIN_API_VERSION = 1;
45682
45568
 
45569
+ // dist/cli/plugins/pluginAliases.js
45570
+ var BUILTIN_PLUGIN_ALIASES = {
45571
+ gsheets: "@vforsh/argus-plugin-google-sheets",
45572
+ gs: "@vforsh/argus-plugin-google-sheets"
45573
+ };
45574
+ var resolvePluginAlias = (spec, aliases) => {
45575
+ const trimmed = spec.trim();
45576
+ const resolved = aliases[trimmed];
45577
+ return resolved ? { spec: resolved, alias: trimmed } : { spec: trimmed, alias: null };
45578
+ };
45579
+
45580
+ // dist/cli/plugins/pluginHost.js
45581
+ var DEFAULT_TIMEOUT_MS2 = 30000;
45582
+ var postWatcherJson = (id, path14, body, timeoutMs = DEFAULT_TIMEOUT_MS2) => requestWatcherJson({
45583
+ id,
45584
+ path: path14,
45585
+ method: "POST",
45586
+ body,
45587
+ timeoutMs
45588
+ });
45589
+ var argus = {
45590
+ eval: (id, request, options) => postWatcherJson(id, "/eval", request, options?.timeoutMs ?? request.timeoutMs ?? DEFAULT_TIMEOUT_MS2),
45591
+ dom: {
45592
+ click: (id, request, options) => postWatcherJson(id, "/dom/click", request, options?.timeoutMs),
45593
+ info: (id, request, options) => postWatcherJson(id, "/dom/info", request, options?.timeoutMs),
45594
+ keydown: (id, request, options) => postWatcherJson(id, "/dom/keydown", request, options?.timeoutMs)
45595
+ },
45596
+ screenshot: (id, request = {}, options) => postWatcherJson(id, "/screenshot", request, options?.timeoutMs)
45597
+ };
45598
+ var createPluginHost = () => ({
45599
+ createOutput,
45600
+ requestWatcherJson,
45601
+ writeRequestError,
45602
+ runChromeOpen,
45603
+ defineWatcherCommand,
45604
+ argus
45605
+ });
45606
+
45683
45607
  // dist/cli/plugins/registerPlugins.js
45608
+ var lastPluginLoadReport = {
45609
+ configPath: null,
45610
+ configDir: null,
45611
+ cwd: process.cwd(),
45612
+ entries: []
45613
+ };
45614
+ var getPluginLoadReport = () => lastPluginLoadReport;
45684
45615
  var parseEnvPlugins = () => {
45685
45616
  const raw = process.env.ARGUS_PLUGINS;
45686
45617
  if (!raw)
@@ -45688,6 +45619,30 @@ var parseEnvPlugins = () => {
45688
45619
  return raw.split(",").map((s) => s.trim()).filter(Boolean);
45689
45620
  };
45690
45621
  var uniq = (values) => Array.from(new Set(values));
45622
+ var createPluginInput = (source, spec, aliases) => {
45623
+ const resolved = resolvePluginAlias(spec, aliases);
45624
+ return { source, spec, resolvedSpec: resolved.spec, alias: resolved.alias };
45625
+ };
45626
+ var parseCliPlugins = (argv) => {
45627
+ const plugins = [];
45628
+ for (let i = 0;i < argv.length; i++) {
45629
+ const arg = argv[i];
45630
+ if (arg === "--plugin") {
45631
+ const value = argv[i + 1];
45632
+ if (value && !value.startsWith("-")) {
45633
+ plugins.push(value);
45634
+ i++;
45635
+ }
45636
+ continue;
45637
+ }
45638
+ if (arg.startsWith("--plugin=")) {
45639
+ const value = arg.slice("--plugin=".length).trim();
45640
+ if (value)
45641
+ plugins.push(value);
45642
+ }
45643
+ }
45644
+ return plugins;
45645
+ };
45691
45646
  var resolveWithImportMeta = (specifier, parentUrl) => {
45692
45647
  const resolver2 = import.meta.resolve;
45693
45648
  if (typeof resolver2 !== "function") {
@@ -45695,6 +45650,7 @@ var resolveWithImportMeta = (specifier, parentUrl) => {
45695
45650
  }
45696
45651
  return resolver2(specifier, parentUrl);
45697
45652
  };
45653
+ var isPathLikeSpecifier = (specifier) => specifier.startsWith(".") || specifier.startsWith("/");
45698
45654
  var resolvePluginModuleUrl = (specifier, baseDirs) => {
45699
45655
  const trimmed = specifier.trim();
45700
45656
  if (!trimmed) {
@@ -45704,6 +45660,23 @@ var resolvePluginModuleUrl = (specifier, baseDirs) => {
45704
45660
  return { ok: true, url: trimmed };
45705
45661
  }
45706
45662
  const errors = [];
45663
+ if (isPathLikeSpecifier(trimmed)) {
45664
+ for (const baseDir of baseDirs) {
45665
+ try {
45666
+ const resolvedPath = path14.resolve(baseDir, trimmed);
45667
+ if (existsSync3(resolvedPath)) {
45668
+ return { ok: true, url: pathToFileURL2(resolvedPath).href };
45669
+ }
45670
+ errors.push(`${baseDir}: ${resolvedPath} does not exist`);
45671
+ } catch (error) {
45672
+ const msg = error instanceof Error ? error.message : String(error);
45673
+ errors.push(`${baseDir}: ${msg}`);
45674
+ }
45675
+ }
45676
+ return { ok: false, error: `Failed to resolve plugin path "${specifier}". Tried:
45677
+ ${errors.map((e) => `- ${e}`).join(`
45678
+ `)}` };
45679
+ }
45707
45680
  try {
45708
45681
  return { ok: true, url: resolveWithImportMeta(trimmed, import.meta.url) };
45709
45682
  } catch (error) {
@@ -45739,26 +45712,62 @@ var extractPlugin = (mod) => {
45739
45712
  return null;
45740
45713
  return plugin;
45741
45714
  };
45715
+ var normalizeOptionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
45716
+ var normalizeCommands = (value) => {
45717
+ if (!Array.isArray(value))
45718
+ return [];
45719
+ const commands = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
45720
+ return Array.from(new Set(commands));
45721
+ };
45742
45722
  var warnPluginLoad = (source, spec, message) => {
45743
45723
  const output = createOutput({ json: false });
45744
45724
  output.writeWarn(`[plugins] Failed to load (${source}) "${spec}": ${message}`);
45745
45725
  };
45746
- var registerPlugins = async (program2) => {
45726
+ var recordPluginFailure = (entries, entry, error, url) => {
45727
+ entries.push({ source: entry.source, spec: entry.spec, resolvedSpec: entry.resolvedSpec, alias: entry.alias, status: "failed", url, error });
45728
+ warnPluginLoad(entry.source, entry.spec, error);
45729
+ };
45730
+ var createLoadedEntry = (entry, plugin, url) => ({
45731
+ source: entry.source,
45732
+ spec: entry.spec,
45733
+ resolvedSpec: entry.resolvedSpec,
45734
+ alias: entry.alias,
45735
+ status: "loaded",
45736
+ name: plugin.name,
45737
+ version: normalizeOptionalString(plugin.version),
45738
+ description: normalizeOptionalString(plugin.description),
45739
+ commands: normalizeCommands(plugin.commands),
45740
+ homepage: normalizeOptionalString(plugin.homepage),
45741
+ minArgusVersion: normalizeOptionalString(plugin.minArgusVersion),
45742
+ url
45743
+ });
45744
+ var registerPlugins = async (program2, argv = process.argv.slice(2)) => {
45747
45745
  const cwd = process.cwd();
45748
45746
  const configPath = resolveArgusConfigPath({ cwd });
45749
45747
  const configResult = configPath ? loadArgusConfig(configPath) : null;
45748
+ const aliases = { ...BUILTIN_PLUGIN_ALIASES, ...configResult?.config.pluginAliases ?? {} };
45750
45749
  const configPlugins = configResult?.config.plugins ?? [];
45751
45750
  const envPlugins = parseEnvPlugins();
45751
+ const cliPlugins = parseCliPlugins(argv);
45752
45752
  const all = [];
45753
45753
  for (const spec of configPlugins)
45754
- all.push({ source: "config", spec });
45754
+ all.push(createPluginInput("config", spec, aliases));
45755
45755
  for (const spec of envPlugins)
45756
- all.push({ source: "env", spec });
45756
+ all.push(createPluginInput("env", spec, aliases));
45757
+ for (const spec of cliPlugins)
45758
+ all.push(createPluginInput("cli", spec, aliases));
45759
+ const entries = [];
45760
+ lastPluginLoadReport = {
45761
+ configPath,
45762
+ configDir: configResult?.configDir ?? null,
45763
+ cwd,
45764
+ entries
45765
+ };
45757
45766
  if (all.length === 0)
45758
45767
  return;
45759
45768
  const seen = new Set;
45760
45769
  const ordered = all.filter((p2) => {
45761
- const key = p2.spec.trim();
45770
+ const key = p2.resolvedSpec.trim();
45762
45771
  if (!key)
45763
45772
  return false;
45764
45773
  if (seen.has(key))
@@ -45769,36 +45778,311 @@ var registerPlugins = async (program2) => {
45769
45778
  const baseDirs = uniq([configResult?.configDir, cwd].filter((v4) => Boolean(v4)));
45770
45779
  const ctxBase = {
45771
45780
  apiVersion: ARGUS_PLUGIN_API_VERSION,
45772
- host: { createOutput, requestWatcherJson, writeRequestError, runChromeOpen },
45781
+ host: createPluginHost(),
45773
45782
  cwd,
45774
45783
  configPath,
45775
45784
  configDir: configResult?.configDir ?? null
45776
45785
  };
45777
45786
  for (const entry of ordered) {
45778
- const resolved = resolvePluginModuleUrl(entry.spec, baseDirs);
45787
+ const resolved = resolvePluginModuleUrl(entry.resolvedSpec, baseDirs);
45779
45788
  if (!resolved.ok) {
45780
- warnPluginLoad(entry.source, entry.spec, resolved.error);
45789
+ recordPluginFailure(entries, entry, resolved.error);
45781
45790
  continue;
45782
45791
  }
45783
45792
  let mod;
45784
45793
  try {
45785
45794
  mod = await import(resolved.url);
45786
45795
  } catch (error) {
45787
- warnPluginLoad(entry.source, entry.spec, error instanceof Error ? error.message : String(error));
45796
+ recordPluginFailure(entries, entry, error instanceof Error ? error.message : String(error), resolved.url);
45788
45797
  continue;
45789
45798
  }
45790
45799
  const plugin = extractPlugin(mod);
45791
45800
  if (!plugin) {
45792
- warnPluginLoad(entry.source, entry.spec, "Invalid plugin export (expected default export with { apiVersion: 1, name, register() }).");
45801
+ recordPluginFailure(entries, entry, "Invalid plugin export (expected default export with { apiVersion: 1, name, register() }).", resolved.url);
45793
45802
  continue;
45794
45803
  }
45795
45804
  try {
45796
45805
  await plugin.register({ ...ctxBase, program: program2 });
45806
+ entries.push(createLoadedEntry(entry, plugin, resolved.url));
45797
45807
  } catch (error) {
45798
- warnPluginLoad(entry.source, entry.spec, error instanceof Error ? error.message : String(error));
45808
+ recordPluginFailure(entries, entry, error instanceof Error ? error.message : String(error), resolved.url);
45809
+ }
45810
+ }
45811
+ };
45812
+
45813
+ // dist/commands/pluginList.js
45814
+ var runPluginList = (options) => {
45815
+ const output = createOutput(options);
45816
+ const report = getPluginLoadReport();
45817
+ if (options.json) {
45818
+ output.writeJson(report);
45819
+ return;
45820
+ }
45821
+ if (report.entries.length === 0) {
45822
+ output.writeHuman("No plugins configured for this invocation.");
45823
+ return;
45824
+ }
45825
+ for (const entry of report.entries) {
45826
+ const spec = entry.alias ? `${entry.alias} -> ${entry.resolvedSpec}` : entry.spec;
45827
+ if (entry.status === "failed") {
45828
+ output.writeHuman(`${entry.source} ${spec} failed ${entry.error}`);
45829
+ continue;
45830
+ }
45831
+ const version2 = entry.version ? ` v${entry.version}` : "";
45832
+ const commands = entry.commands.length > 0 ? ` commands: ${entry.commands.join(", ")}` : "";
45833
+ const description = entry.description ? ` — ${entry.description}` : "";
45834
+ output.writeHuman(`${entry.source} ${entry.name}${version2} ${spec} loaded${commands}${description}`);
45835
+ }
45836
+ };
45837
+
45838
+ // dist/commands/pluginConfig.js
45839
+ import fs20 from "node:fs/promises";
45840
+ import { existsSync as existsSync4 } from "node:fs";
45841
+ import path15 from "node:path";
45842
+ var DEFAULT_CONFIG_PATH2 = ".argus/config.json";
45843
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
45844
+ var resolveTargetConfigPath = (cwd, cliPath) => {
45845
+ if (cliPath) {
45846
+ const resolved = path15.isAbsolute(cliPath) ? cliPath : path15.resolve(cwd, cliPath);
45847
+ return { path: resolved, exists: existsSync4(resolved) };
45848
+ }
45849
+ const discovered = resolveArgusConfigPath({ cwd });
45850
+ if (discovered)
45851
+ return { path: discovered, exists: true };
45852
+ if (process.exitCode)
45853
+ return null;
45854
+ return { path: path15.resolve(cwd, DEFAULT_CONFIG_PATH2), exists: false };
45855
+ };
45856
+ var readConfigObject = async (configPath, exists) => {
45857
+ if (!exists)
45858
+ return {};
45859
+ let raw;
45860
+ try {
45861
+ raw = await fs20.readFile(configPath, "utf8");
45862
+ } catch (error) {
45863
+ console.error(`Failed to read Argus config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
45864
+ process.exitCode = 2;
45865
+ return null;
45866
+ }
45867
+ try {
45868
+ const parsed = JSON.parse(raw);
45869
+ if (isRecord2(parsed))
45870
+ return parsed;
45871
+ console.error(`Invalid Argus config at ${configPath}: config root must be an object.`);
45872
+ process.exitCode = 2;
45873
+ return null;
45874
+ } catch (error) {
45875
+ console.error(`Invalid Argus config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
45876
+ process.exitCode = 2;
45877
+ return null;
45878
+ }
45879
+ };
45880
+ var getPluginSpecs = (config, configPath) => {
45881
+ const value = config.plugins;
45882
+ if (value === undefined)
45883
+ return [];
45884
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
45885
+ console.error(`Invalid Argus config at ${configPath}: "plugins" must be an array of non-empty strings.`);
45886
+ process.exitCode = 2;
45887
+ return null;
45888
+ }
45889
+ return value;
45890
+ };
45891
+ var getPluginAliases = (config, configPath) => {
45892
+ const value = config.pluginAliases;
45893
+ if (value === undefined)
45894
+ return {};
45895
+ if (!isRecord2(value)) {
45896
+ console.error(`Invalid Argus config at ${configPath}: "pluginAliases" must be an object with string values.`);
45897
+ process.exitCode = 2;
45898
+ return null;
45899
+ }
45900
+ const aliases = {};
45901
+ for (const [alias, spec] of Object.entries(value)) {
45902
+ if (alias.trim() === "" || typeof spec !== "string" || spec.trim() === "") {
45903
+ console.error(`Invalid Argus config at ${configPath}: "pluginAliases" keys and values must be non-empty strings.`);
45904
+ process.exitCode = 2;
45905
+ return null;
45799
45906
  }
45907
+ aliases[alias] = spec;
45800
45908
  }
45909
+ return aliases;
45910
+ };
45911
+ var writeConfigObject = async (configPath, config) => {
45912
+ try {
45913
+ await fs20.mkdir(path15.dirname(configPath), { recursive: true });
45914
+ await fs20.writeFile(configPath, `${JSON.stringify(config, null, "\t")}
45915
+ `, "utf8");
45916
+ return true;
45917
+ } catch (error) {
45918
+ console.error(`Failed to write Argus config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
45919
+ process.exitCode = 2;
45920
+ return false;
45921
+ }
45922
+ };
45923
+ var loadMutablePluginConfig = async (options) => {
45924
+ const target = resolveTargetConfigPath(process.cwd(), options.path);
45925
+ if (!target)
45926
+ return null;
45927
+ const config = await readConfigObject(target.path, target.exists);
45928
+ if (!config)
45929
+ return null;
45930
+ const plugins = getPluginSpecs(config, target.path);
45931
+ if (!plugins)
45932
+ return null;
45933
+ const pluginAliases = getPluginAliases(config, target.path);
45934
+ if (!pluginAliases)
45935
+ return null;
45936
+ return { path: target.path, config, plugins, pluginAliases };
45801
45937
  };
45938
+ var parsePluginAddTarget = (raw, aliases) => {
45939
+ const trimmed = raw.trim();
45940
+ if (!trimmed)
45941
+ return null;
45942
+ const separator = trimmed.indexOf("=");
45943
+ if (separator < 0) {
45944
+ const resolved = resolvePluginAlias(trimmed, aliases);
45945
+ return { spec: trimmed, alias: resolved.alias, resolvedSpec: resolved.spec };
45946
+ }
45947
+ const alias = trimmed.slice(0, separator).trim();
45948
+ const spec = trimmed.slice(separator + 1).trim();
45949
+ return alias && spec ? { spec: alias, alias, resolvedSpec: spec } : null;
45950
+ };
45951
+ var runPluginAdd = async (targetSpec, options) => {
45952
+ const output = createOutput(options);
45953
+ const loaded = await loadMutablePluginConfig(options);
45954
+ if (!loaded)
45955
+ return;
45956
+ const parsed = parsePluginAddTarget(targetSpec, { ...BUILTIN_PLUGIN_ALIASES, ...loaded.pluginAliases });
45957
+ if (!parsed) {
45958
+ output.writeWarn("Plugin specifier is required.");
45959
+ process.exitCode = 2;
45960
+ return;
45961
+ }
45962
+ const aliases = { ...BUILTIN_PLUGIN_ALIASES, ...loaded.pluginAliases };
45963
+ const hasSameResolvedPlugin = loaded.plugins.some((spec) => resolvePluginAlias(spec, aliases).spec === parsed.resolvedSpec);
45964
+ const changed = !loaded.plugins.includes(parsed.spec) && !hasSameResolvedPlugin;
45965
+ const aliasChanged = parsed.alias !== null && loaded.pluginAliases[parsed.alias] !== parsed.resolvedSpec;
45966
+ if (changed) {
45967
+ loaded.plugins = [...loaded.plugins, parsed.spec];
45968
+ loaded.config.plugins = loaded.plugins;
45969
+ }
45970
+ if (aliasChanged && parsed.alias) {
45971
+ loaded.pluginAliases[parsed.alias] = parsed.resolvedSpec;
45972
+ loaded.config.pluginAliases = loaded.pluginAliases;
45973
+ }
45974
+ if ((changed || aliasChanged) && !await writeConfigObject(loaded.path, loaded.config))
45975
+ return;
45976
+ const response = {
45977
+ configPath: loaded.path,
45978
+ spec: parsed.spec,
45979
+ alias: parsed.alias,
45980
+ resolvedSpec: parsed.resolvedSpec,
45981
+ changed: changed || aliasChanged,
45982
+ plugins: loaded.plugins,
45983
+ pluginAliases: loaded.pluginAliases
45984
+ };
45985
+ if (options.json)
45986
+ output.writeJson(response);
45987
+ else
45988
+ output.writeHuman(response.changed ? `Added plugin ${parsed.spec} to ${loaded.path}` : `Plugin already configured: ${parsed.spec}`);
45989
+ };
45990
+ var runPluginRemove = async (targetSpec, options) => {
45991
+ const output = createOutput(options);
45992
+ const normalized = targetSpec.trim();
45993
+ if (!normalized) {
45994
+ output.writeWarn("Plugin specifier or name is required.");
45995
+ process.exitCode = 2;
45996
+ return;
45997
+ }
45998
+ const loaded = await loadMutablePluginConfig(options);
45999
+ if (!loaded)
46000
+ return;
46001
+ const aliases = { ...BUILTIN_PLUGIN_ALIASES, ...loaded.pluginAliases };
46002
+ const next = loaded.plugins.filter((spec) => !matchesPluginSpecifier(spec, normalized, aliases));
46003
+ const nextAliases = Object.fromEntries(Object.entries(loaded.pluginAliases).filter(([alias, spec]) => !matchesPluginAlias(alias, spec, normalized)));
46004
+ const changed = next.length !== loaded.plugins.length;
46005
+ const aliasChanged = Object.keys(nextAliases).length !== Object.keys(loaded.pluginAliases).length;
46006
+ if (changed || aliasChanged) {
46007
+ loaded.config.plugins = next;
46008
+ loaded.config.pluginAliases = Object.keys(nextAliases).length > 0 ? nextAliases : undefined;
46009
+ if (!await writeConfigObject(loaded.path, loaded.config))
46010
+ return;
46011
+ }
46012
+ const response = { configPath: loaded.path, target: normalized, changed: changed || aliasChanged, plugins: next, pluginAliases: nextAliases };
46013
+ if (options.json)
46014
+ output.writeJson(response);
46015
+ else
46016
+ output.writeHuman(response.changed ? `Removed plugin ${normalized} from ${loaded.path}` : `Plugin not configured: ${normalized}`);
46017
+ };
46018
+ var matchesPluginSpecifier = (spec, target, aliases) => {
46019
+ const resolved = resolvePluginAlias(spec, aliases);
46020
+ return spec === target || resolved.spec === target || resolved.alias === target || pluginKey(spec) === pluginKey(target) || pluginKey(resolved.spec) === pluginKey(target);
46021
+ };
46022
+ var matchesPluginAlias = (alias, spec, target) => alias === target || spec === target || pluginKey(alias) === pluginKey(target) || pluginKey(spec) === pluginKey(target);
46023
+ var pluginKey = (value) => {
46024
+ const withoutPath = value.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? value;
46025
+ const withoutExt = withoutPath.replace(/\.(mjs|js|ts)$/, "");
46026
+ return withoutExt.replace(/^argus-plugin-/, "").replace(/^@[^/]+\/argus-plugin-/, "");
46027
+ };
46028
+
46029
+ // dist/cli/register/registerPlugin.js
46030
+ function registerPlugin(program2) {
46031
+ const plugin = program2.command("plugin").alias("plugins").description("Manage Argus CLI plugins");
46032
+ plugin.command("list").alias("ls").description("List plugins discovered for this invocation").option("--json", "Output JSON for automation").addHelpText("after", `
46033
+ Examples:
46034
+ $ argus plugin list
46035
+ $ argus --plugin ./plugins/foo.js plugin list --json
46036
+ `).action((options) => {
46037
+ runPluginList(options);
46038
+ });
46039
+ 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", `
46040
+ Examples:
46041
+ $ argus plugin add gsheets
46042
+ $ argus plugin add gs=@vforsh/argus-plugin-google-sheets
46043
+ $ argus plugin add ./plugins/foo.js
46044
+ `).action(async (specifier, options) => {
46045
+ await runPluginAdd(specifier, options);
46046
+ });
46047
+ 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", `
46048
+ Examples:
46049
+ $ argus plugin remove @vforsh/argus-plugin-google-sheets
46050
+ $ argus plugin remove google-sheets
46051
+ `).action(async (specifierOrName, options) => {
46052
+ await runPluginRemove(specifierOrName, options);
46053
+ });
46054
+ }
46055
+
46056
+ // dist/cli/register/index.js
46057
+ var registerDefinedCommands = (program2) => {
46058
+ defineCommands(program2, [domClickCommandDefinition]);
46059
+ };
46060
+ var coreProgramRegistrars = [
46061
+ registerQuickAccess,
46062
+ registerChrome,
46063
+ registerWatcher,
46064
+ registerPage,
46065
+ registerDialog,
46066
+ registerLogs,
46067
+ registerNet,
46068
+ registerAuth,
46069
+ registerEval,
46070
+ registerLocate,
46071
+ registerCode,
46072
+ registerDom,
46073
+ registerDefinedCommands,
46074
+ registerKeydown,
46075
+ registerFill,
46076
+ registerHover,
46077
+ registerScrollTo,
46078
+ registerStorage,
46079
+ registerThrottle,
46080
+ registerSnapshot,
46081
+ registerTrace,
46082
+ registerConfig,
46083
+ registerPlugin,
46084
+ registerExtension
46085
+ ];
45802
46086
 
45803
46087
  // dist/bin.js
45804
46088
  var program2 = createProgram();