@vforsh/argus 0.1.19 → 0.1.21

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 (57) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/argus.js +642 -191
  3. package/dist/cli/register/extensionCommands.d.ts.map +1 -1
  4. package/dist/cli/register/extensionCommands.js +28 -3
  5. package/dist/cli/register/extensionCommands.js.map +1 -1
  6. package/dist/cli/register/keydownCommand.d.ts.map +1 -1
  7. package/dist/cli/register/keydownCommand.js +15 -2
  8. package/dist/cli/register/keydownCommand.js.map +1 -1
  9. package/dist/cli/register/netCommands.d.ts +5 -0
  10. package/dist/cli/register/netCommands.d.ts.map +1 -1
  11. package/dist/cli/register/netCommands.js +23 -6
  12. package/dist/cli/register/netCommands.js.map +1 -1
  13. package/dist/commands/domKeydown.d.ts +8 -1
  14. package/dist/commands/domKeydown.d.ts.map +1 -1
  15. package/dist/commands/domKeydown.js +35 -6
  16. package/dist/commands/domKeydown.js.map +1 -1
  17. package/dist/commands/extension/extensionId.d.ts +27 -0
  18. package/dist/commands/extension/extensionId.d.ts.map +1 -0
  19. package/dist/commands/extension/extensionId.js +37 -0
  20. package/dist/commands/extension/extensionId.js.map +1 -0
  21. package/dist/commands/extension/extensionPath.d.ts +15 -0
  22. package/dist/commands/extension/extensionPath.d.ts.map +1 -0
  23. package/dist/commands/extension/extensionPath.js +61 -0
  24. package/dist/commands/extension/extensionPath.js.map +1 -0
  25. package/dist/commands/extension/install.d.ts +16 -0
  26. package/dist/commands/extension/install.d.ts.map +1 -0
  27. package/dist/commands/extension/install.js +154 -0
  28. package/dist/commands/extension/install.js.map +1 -0
  29. package/dist/commands/extension/setup.d.ts +2 -1
  30. package/dist/commands/extension/setup.d.ts.map +1 -1
  31. package/dist/commands/extension/setup.js +5 -2
  32. package/dist/commands/extension/setup.js.map +1 -1
  33. package/dist/commands/netMock.d.ts +2 -0
  34. package/dist/commands/netMock.d.ts.map +1 -1
  35. package/dist/commands/netMock.js +22 -1
  36. package/dist/commands/netMock.js.map +1 -1
  37. package/dist/extension/dist/background/auth-header-monitor.js +77 -0
  38. package/dist/extension/dist/background/bridge-client.js +120 -0
  39. package/dist/extension/dist/background/cdp-proxy.js +201 -0
  40. package/dist/extension/dist/background/debugger-manager.js +296 -0
  41. package/dist/extension/dist/background/service-worker.js +2004 -0
  42. package/dist/extension/dist/background/service-worker.js.map +7 -0
  43. package/dist/extension/dist/popup/popup.js +730 -0
  44. package/dist/extension/dist/popup/popup.js.map +7 -0
  45. package/dist/extension/dist/popup/tabMarkup.js +104 -0
  46. package/dist/extension/dist/popup/viewState.js +60 -0
  47. package/dist/extension/dist/types/messages.js +5 -0
  48. package/dist/extension/icons/icon-128.png +0 -0
  49. package/dist/extension/icons/icon-16.png +0 -0
  50. package/dist/extension/icons/icon-48.png +0 -0
  51. package/dist/extension/manifest.json +26 -0
  52. package/dist/extension/src/popup/popup.html +756 -0
  53. package/dist/skill/argus/SKILL.md +6 -1
  54. package/dist/skill/argus/reference/EXTENSION.md +13 -8
  55. package/dist/skill/argus/reference/INSPECT.md +3 -1
  56. package/dist/skill/argus/reference/NET.md +4 -1
  57. package/package.json +2 -2
package/dist/argus.js CHANGED
@@ -2144,7 +2144,7 @@ var {
2144
2144
  // package.json
2145
2145
  var package_default = {
2146
2146
  name: "@vforsh/argus",
2147
- version: "0.1.19",
2147
+ version: "0.1.21",
2148
2148
  repository: {
2149
2149
  type: "git",
2150
2150
  url: "git+https://github.com/vforsh/argus.git",
@@ -2180,7 +2180,7 @@ var package_default = {
2180
2180
  scripts: {
2181
2181
  clean: "node ../../scripts/clean-dist.mjs",
2182
2182
  build: "npm run clean && tsc -b && bun run bundle",
2183
- bundle: "bun build dist/bin.js --outfile dist/argus.js --target node --external esbuild && node ../../scripts/copy-argus-skill.mjs",
2183
+ bundle: "bun build dist/bin.js --outfile dist/argus.js --target node --external esbuild && node ../../scripts/copy-argus-skill.mjs && node ../../scripts/copy-argus-extension.mjs",
2184
2184
  typecheck: "tsc -b --pretty false",
2185
2185
  "chrome:start": "bun src/bin.ts chrome start"
2186
2186
  }
@@ -6852,6 +6852,9 @@ function validateAddRequest(body) {
6852
6852
  if (payload.times !== undefined && (!Number.isInteger(payload.times) || payload.times < 1)) {
6853
6853
  return "times must be an integer >= 1";
6854
6854
  }
6855
+ if (payload.scope !== undefined && payload.scope !== "page" && payload.scope !== "selected") {
6856
+ return "scope must be one of: page, selected";
6857
+ }
6855
6858
  return null;
6856
6859
  }
6857
6860
  var validateMatch = (match) => {
@@ -9372,10 +9375,12 @@ var route29 = defineJsonRoute({
9372
9375
  });
9373
9376
 
9374
9377
  // ../argus-watcher/dist/cdp/keyboard.js
9375
- var buildKeyMap = () => {
9376
- const map = new Map;
9378
+ var buildKeyMaps = () => {
9379
+ const byKey = new Map;
9380
+ const byCode = new Map;
9377
9381
  const put = (lookup, def) => {
9378
- map.set(lookup.toLowerCase(), def);
9382
+ byKey.set(lookup.toLowerCase(), def);
9383
+ byCode.set(def.code.toLowerCase(), def);
9379
9384
  };
9380
9385
  for (let i = 0;i < 26; i++) {
9381
9386
  const lower = String.fromCharCode(97 + i);
@@ -9410,11 +9415,14 @@ var buildKeyMap = () => {
9410
9415
  const name = `F${i}`;
9411
9416
  put(name, { key: name, code: name, keyCode: 111 + i });
9412
9417
  }
9413
- return map;
9418
+ return { byKey, byCode };
9414
9419
  };
9415
- var KEY_MAP = buildKeyMap();
9420
+ var KEY_MAPS = buildKeyMaps();
9416
9421
  var resolveKeyDefinition = (key) => {
9417
- return KEY_MAP.get(key.toLowerCase());
9422
+ return KEY_MAPS.byKey.get(key.toLowerCase());
9423
+ };
9424
+ var resolveCodeDefinition = (code) => {
9425
+ return KEY_MAPS.byCode.get(code.toLowerCase());
9418
9426
  };
9419
9427
  var MODIFIER_BITS = {
9420
9428
  alt: 1,
@@ -9444,8 +9452,61 @@ var parseModifiers = (input) => {
9444
9452
  }
9445
9453
  return mask;
9446
9454
  };
9455
+ var resolveKeyboardEvent = (options) => {
9456
+ const modifiers = options.modifiers ?? 0;
9457
+ const keyInput = normalizeOptionalString(options.key);
9458
+ const codeInput = normalizeOptionalString(options.code);
9459
+ if (!keyInput && !codeInput) {
9460
+ throw new Error("key or code is required");
9461
+ }
9462
+ const keyDef = keyInput ? resolveKeyDefinition(keyInput) : undefined;
9463
+ const codeDef = codeInput ? resolveCodeDefinition(codeInput) : undefined;
9464
+ const baseDef = codeDef ?? keyDef;
9465
+ if (!baseDef) {
9466
+ if (codeInput && !keyInput) {
9467
+ throw new Error(`Unknown code: "${codeInput}"`);
9468
+ }
9469
+ throw new Error(`Unknown key: "${keyInput}"`);
9470
+ }
9471
+ const key = resolveEventKey(keyInput, baseDef, modifiers);
9472
+ const text = resolveEventText(key, baseDef);
9473
+ const event = {
9474
+ key,
9475
+ code: codeInput ?? baseDef.code,
9476
+ keyCode: baseDef.keyCode,
9477
+ modifiers,
9478
+ altKey: (modifiers & MODIFIER_BITS.alt) !== 0,
9479
+ ctrlKey: (modifiers & MODIFIER_BITS.ctrl) !== 0,
9480
+ metaKey: (modifiers & MODIFIER_BITS.meta) !== 0,
9481
+ shiftKey: (modifiers & MODIFIER_BITS.shift) !== 0
9482
+ };
9483
+ if (text !== undefined) {
9484
+ event.text = text;
9485
+ }
9486
+ return event;
9487
+ };
9488
+ var normalizeOptionalString = (value) => {
9489
+ const normalized = value?.trim();
9490
+ return normalized ? normalized : undefined;
9491
+ };
9492
+ var resolveEventKey = (keyInput, def, modifiers) => {
9493
+ const semanticKey = keyInput ?? def.key;
9494
+ if (isSingleLetter(semanticKey)) {
9495
+ return isShift(modifiers) ? semanticKey.toUpperCase() : semanticKey.toLowerCase();
9496
+ }
9497
+ return semanticKey;
9498
+ };
9499
+ var resolveEventText = (key, def) => {
9500
+ if (key.length === 1) {
9501
+ return key;
9502
+ }
9503
+ return def.text;
9504
+ };
9505
+ var isSingleLetter = (value) => /^[a-z]$/i.test(value);
9506
+ var isShift = (modifiers) => (modifiers & MODIFIER_BITS.shift) !== 0;
9447
9507
  var dispatchKeydown = async (session, options) => {
9448
- const { key, selector, modifiers = 0 } = options;
9508
+ const { selector } = options;
9509
+ const event = resolveKeyboardEvent(options);
9449
9510
  let focused = false;
9450
9511
  if (selector) {
9451
9512
  const { allNodeIds, nodeIds } = await resolveDomSelectorMatches(session, selector, false);
@@ -9461,31 +9522,23 @@ var dispatchKeydown = async (session, options) => {
9461
9522
  await session.sendAndWait("DOM.focus", { nodeId });
9462
9523
  focused = true;
9463
9524
  }
9464
- const def = resolveKeyDefinition(key);
9465
- if (!def) {
9466
- throw new Error(`Unknown key: "${key}"`);
9467
- }
9468
- let text = def.text;
9469
- if (text && text.length === 1 && (modifiers & 8) !== 0) {
9470
- text = text.toUpperCase();
9471
- }
9472
- const isPrintable = text != null && text !== "\r";
9525
+ const isPrintable = event.text != null && event.text !== "\r";
9473
9526
  const baseParams = {
9474
- code: def.code,
9475
- key: def.key,
9476
- windowsVirtualKeyCode: def.keyCode,
9477
- nativeVirtualKeyCode: def.keyCode,
9478
- modifiers
9527
+ code: event.code,
9528
+ key: event.key,
9529
+ windowsVirtualKeyCode: event.keyCode,
9530
+ nativeVirtualKeyCode: event.keyCode,
9531
+ modifiers: event.modifiers
9479
9532
  };
9480
9533
  if (isPrintable) {
9481
- await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "keyDown", text });
9482
- await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "char", text });
9534
+ await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "keyDown", text: event.text });
9535
+ await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "char", text: event.text });
9483
9536
  await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "keyUp" });
9484
9537
  } else {
9485
- await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "rawKeyDown", text: def.text });
9538
+ await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "rawKeyDown", text: event.text });
9486
9539
  await session.sendAndWait("Input.dispatchKeyEvent", { ...baseParams, type: "keyUp" });
9487
9540
  }
9488
- return { key: def.key, modifiers, focused };
9541
+ return { key: event.key, code: event.code, modifiers: event.modifiers, focused, event };
9489
9542
  };
9490
9543
 
9491
9544
  // ../argus-watcher/dist/http/routes/postDomKeydown.js
@@ -9495,12 +9548,23 @@ var route30 = defineJsonRoute({
9495
9548
  parseBody: true,
9496
9549
  endpoint: "dom/keydown",
9497
9550
  validate: (payload) => {
9498
- if (!payload.key || typeof payload.key !== "string") {
9499
- return "key is required";
9551
+ const hasKey = typeof payload.key === "string" && payload.key.trim() !== "";
9552
+ const hasCode = typeof payload.code === "string" && payload.code.trim() !== "";
9553
+ if (payload.key != null && !hasKey) {
9554
+ return "key must be a non-empty string";
9555
+ }
9556
+ if (payload.code != null && !hasCode) {
9557
+ return "code must be a non-empty string";
9558
+ }
9559
+ if (!hasKey && !hasCode) {
9560
+ return "key or code is required";
9500
9561
  }
9501
9562
  if (payload.selector != null && (typeof payload.selector !== "string" || payload.selector.trim() === "")) {
9502
9563
  return "selector must be a non-empty string";
9503
9564
  }
9565
+ if (payload.modifiers != null && typeof payload.modifiers !== "string") {
9566
+ return "modifiers must be a string";
9567
+ }
9504
9568
  try {
9505
9569
  parseModifiers(payload.modifiers);
9506
9570
  } catch (error) {
@@ -9511,10 +9575,18 @@ var route30 = defineJsonRoute({
9511
9575
  handle: async ({ ctx, body: payload }) => {
9512
9576
  const result = await dispatchKeydown(ctx.cdpSession, {
9513
9577
  key: payload.key,
9578
+ code: payload.code,
9514
9579
  selector: payload.selector,
9515
9580
  modifiers: parseModifiers(payload.modifiers)
9516
9581
  });
9517
- return { ok: true, key: result.key, modifiers: result.modifiers, focused: result.focused };
9582
+ return {
9583
+ ok: true,
9584
+ key: result.key,
9585
+ code: result.code,
9586
+ modifiers: result.modifiers,
9587
+ focused: result.focused,
9588
+ event: result.event
9589
+ };
9518
9590
  }
9519
9591
  });
9520
9592
 
@@ -11919,73 +11991,169 @@ var createThrottleController = () => {
11919
11991
  return { getStatus, setDesired, clearDesired, onAttach };
11920
11992
  };
11921
11993
 
11922
- // ../argus-watcher/dist/net/NetMockController.js
11923
- var createNetMockController = () => {
11924
- let session = null;
11925
- let bound = false;
11926
- let enabled = false;
11927
- let lastError = null;
11928
- let nextRuleId = 1;
11929
- const rules = [];
11930
- const hasActiveRules = () => rules.some(isRuleActive);
11931
- const recordError = (error) => {
11932
- lastError = { message: error instanceof Error ? error.message : String(error) };
11933
- const code = error?.code;
11934
- if (typeof code === "string") {
11935
- lastError.code = code;
11994
+ // ../argus-watcher/dist/net/NetMockInterception.js
11995
+ var PAGE_TARGET = { key: "page", sessionId: null };
11996
+
11997
+ class NetMockInterception {
11998
+ onPaused;
11999
+ onError;
12000
+ binding = null;
12001
+ bound = false;
12002
+ enabledTargets = new Map;
12003
+ reconcileTail = Promise.resolve();
12004
+ constructor(onPaused, onError) {
12005
+ this.onPaused = onPaused;
12006
+ this.onError = onError;
12007
+ }
12008
+ bind(binding) {
12009
+ this.binding = binding;
12010
+ if (this.bound) {
12011
+ return;
11936
12012
  }
11937
- };
11938
- const enableInterception = async () => {
11939
- if (!session || !session.isAttached()) {
11940
- enabled = false;
11941
- return false;
12013
+ this.bound = true;
12014
+ binding.pageSession.onEvent("Fetch.requestPaused", (params, meta) => {
12015
+ const request = parsePausedRequest(params, meta);
12016
+ if (request) {
12017
+ this.onPaused(request);
12018
+ }
12019
+ });
12020
+ }
12021
+ get enabled() {
12022
+ return this.enabledTargets.size > 0;
12023
+ }
12024
+ matchesScope(scope, request) {
12025
+ if (scope === "page") {
12026
+ return request.sessionId === null;
11942
12027
  }
11943
- try {
11944
- await session.sendAndWait("Fetch.enable", { patterns: [{ urlPattern: "*", requestStage: "Request" }] });
11945
- enabled = true;
11946
- return true;
11947
- } catch (error) {
11948
- enabled = false;
11949
- recordError(error);
12028
+ const selected = this.binding?.getSelectedTarget() ?? null;
12029
+ if (selected?.sessionId) {
12030
+ return request.sessionId === selected.sessionId;
12031
+ }
12032
+ if (request.sessionId !== null) {
11950
12033
  return false;
11951
12034
  }
11952
- };
11953
- const disableInterception = async () => {
11954
- if (!enabled) {
12035
+ const selectedFrameId = selected?.frameId ?? null;
12036
+ const selectedIsChildFrame = selectedFrameId !== null && selectedFrameId !== selected?.topFrameId;
12037
+ return !selectedIsChildFrame || request.frameId === selectedFrameId;
12038
+ }
12039
+ async sendRequestCommand(request, method, params) {
12040
+ await this.sendCommand({ key: request.sessionId ? `session:${request.sessionId}` : "page", sessionId: request.sessionId }, method, params);
12041
+ }
12042
+ reconcile(scopes, reset = false) {
12043
+ const requestedScopes = new Set(scopes);
12044
+ const operation = this.reconcileTail.then(() => this.applyReconcile(requestedScopes, reset));
12045
+ this.reconcileTail = operation.then(() => {
11955
12046
  return;
11956
- }
11957
- enabled = false;
11958
- if (!session || !session.isAttached()) {
12047
+ }, () => {
11959
12048
  return;
12049
+ });
12050
+ return operation;
12051
+ }
12052
+ async applyReconcile(scopes, reset) {
12053
+ if (reset) {
12054
+ this.enabledTargets.clear();
12055
+ }
12056
+ const session = this.binding?.pageSession;
12057
+ if (!session?.isAttached()) {
12058
+ this.enabledTargets.clear();
12059
+ return false;
12060
+ }
12061
+ const desiredTargets = this.getDesiredTargets(scopes);
12062
+ for (const [key, target] of this.enabledTargets) {
12063
+ if (!desiredTargets.has(key)) {
12064
+ await this.sendCommand(target, "Fetch.disable");
12065
+ this.enabledTargets.delete(key);
12066
+ }
12067
+ }
12068
+ for (const [key, target] of desiredTargets) {
12069
+ if (this.enabledTargets.has(key)) {
12070
+ continue;
12071
+ }
12072
+ const enabled = await this.sendCommand(target, "Fetch.enable", { patterns: [{ urlPattern: "*", requestStage: "Request" }] });
12073
+ if (enabled) {
12074
+ this.enabledTargets.set(key, target);
12075
+ }
12076
+ }
12077
+ return this.enabled;
12078
+ }
12079
+ onDetach() {
12080
+ this.enabledTargets.clear();
12081
+ }
12082
+ getDesiredTargets(scopes) {
12083
+ const targets = new Map;
12084
+ if (scopes.has("page")) {
12085
+ targets.set(PAGE_TARGET.key, PAGE_TARGET);
12086
+ }
12087
+ if (!scopes.has("selected")) {
12088
+ return targets;
12089
+ }
12090
+ const sessionId = this.binding?.getSelectedTarget()?.sessionId ?? null;
12091
+ const selectedTarget = sessionId ? { key: `session:${sessionId}`, sessionId } : PAGE_TARGET;
12092
+ targets.set(selectedTarget.key, selectedTarget);
12093
+ return targets;
12094
+ }
12095
+ async sendCommand(target, method, params = {}) {
12096
+ const session = this.binding?.pageSession;
12097
+ if (!session?.isAttached()) {
12098
+ return false;
11960
12099
  }
11961
12100
  try {
11962
- await session.sendAndWait("Fetch.disable");
12101
+ await session.sendAndWait(method, params, target.sessionId ? { sessionId: target.sessionId } : undefined);
12102
+ return true;
11963
12103
  } catch (error) {
11964
- recordError(error);
12104
+ if (!isBenignInterceptionError(error)) {
12105
+ this.onError(error);
12106
+ }
12107
+ return false;
11965
12108
  }
12109
+ }
12110
+ }
12111
+ var parsePausedRequest = (params, meta) => {
12112
+ const paused = params;
12113
+ const requestId = paused?.requestId;
12114
+ if (typeof requestId !== "string" || requestId === "") {
12115
+ return null;
12116
+ }
12117
+ return {
12118
+ requestId,
12119
+ url: paused.request?.url ?? "",
12120
+ method: paused.request?.method ?? "GET",
12121
+ resourceType: paused.resourceType ?? "",
12122
+ frameId: paused.frameId ?? null,
12123
+ sessionId: meta.sessionId ?? null,
12124
+ headers: paused.request?.headers ?? {}
11966
12125
  };
11967
- const bind = (nextSession) => {
11968
- session = nextSession;
11969
- if (bound) {
11970
- return;
12126
+ };
12127
+ var isBenignInterceptionError = (error) => {
12128
+ const code = error?.code;
12129
+ if (code === "cdp_not_attached") {
12130
+ return true;
12131
+ }
12132
+ const message = error instanceof Error ? error.message : String(error);
12133
+ return message.includes("Invalid InterceptionId") || message.includes("Inspected target navigated or closed") || message.includes("Session with given id not found") || message.includes("No target with given id");
12134
+ };
12135
+
12136
+ // ../argus-watcher/dist/net/NetMockController.js
12137
+ var createNetMockController = () => {
12138
+ let lastError = null;
12139
+ let nextRuleId = 1;
12140
+ const rules = [];
12141
+ const recordError = (error) => {
12142
+ lastError = { message: error instanceof Error ? error.message : String(error) };
12143
+ const code = error?.code;
12144
+ if (typeof code === "string") {
12145
+ lastError.code = code;
11971
12146
  }
11972
- bound = true;
11973
- nextSession.onEvent("Fetch.requestPaused", (params) => {
11974
- handleRequestPaused(params);
11975
- });
11976
12147
  };
11977
- const handleRequestPaused = async (params) => {
11978
- const paused = params;
11979
- const requestId = paused?.requestId;
11980
- if (typeof requestId !== "string" || requestId === "") {
11981
- return;
11982
- }
11983
- const url = paused.request?.url ?? "";
11984
- const method = paused.request?.method ?? "GET";
11985
- const resourceType = paused.resourceType ?? "";
11986
- const rule = rules.find((candidate) => ruleMatches(candidate, url, method, resourceType));
12148
+ const interception = new NetMockInterception((request) => void handleRequestPaused(request), recordError);
12149
+ const bind = (binding) => {
12150
+ interception.bind(binding);
12151
+ interception.reconcile(getActiveScopes(rules));
12152
+ };
12153
+ const handleRequestPaused = async (paused) => {
12154
+ const rule = rules.find((candidate) => interception.matchesScope(candidate.scope ?? "page", paused) && ruleMatches(candidate, paused.url, paused.method, paused.resourceType));
11987
12155
  if (!rule) {
11988
- await sendAction("Fetch.continueRequest", { requestId });
12156
+ await sendAction(paused, "Fetch.continueRequest", { requestId: paused.requestId });
11989
12157
  return;
11990
12158
  }
11991
12159
  rule.hits += 1;
@@ -11994,53 +12162,41 @@ var createNetMockController = () => {
11994
12162
  }
11995
12163
  const action = rule.action;
11996
12164
  if (action.kind === "block") {
11997
- await sendAction("Fetch.failRequest", { requestId, errorReason: "BlockedByClient" });
12165
+ await sendAction(paused, "Fetch.failRequest", { requestId: paused.requestId, errorReason: "BlockedByClient" });
11998
12166
  return;
11999
12167
  }
12000
12168
  if (action.kind === "fail") {
12001
- await sendAction("Fetch.failRequest", { requestId, errorReason: action.reason });
12169
+ await sendAction(paused, "Fetch.failRequest", { requestId: paused.requestId, errorReason: action.reason });
12002
12170
  return;
12003
12171
  }
12004
12172
  if (action.kind === "fulfill") {
12005
- const fulfillParams = { requestId, responseCode: action.status };
12173
+ const fulfillParams = { requestId: paused.requestId, responseCode: action.status };
12006
12174
  if (action.headers && action.headers.length > 0) {
12007
12175
  fulfillParams.responseHeaders = action.headers;
12008
12176
  }
12009
12177
  if (action.bodyBase64 != null) {
12010
12178
  fulfillParams.body = action.bodyBase64;
12011
12179
  }
12012
- await sendAction("Fetch.fulfillRequest", fulfillParams);
12180
+ await sendAction(paused, "Fetch.fulfillRequest", fulfillParams);
12013
12181
  return;
12014
12182
  }
12015
- const continueParams = { requestId };
12183
+ const continueParams = { requestId: paused.requestId };
12016
12184
  if (action.rewriteHost) {
12017
- const rewritten = rewriteUrlHost(url, action.rewriteHost);
12185
+ const rewritten = rewriteUrlHost(paused.url, action.rewriteHost);
12018
12186
  if (rewritten) {
12019
12187
  continueParams.url = rewritten;
12020
12188
  }
12021
12189
  }
12022
12190
  if (action.setHeaders && action.setHeaders.length > 0) {
12023
- continueParams.headers = mergeHeaders(paused.request?.headers ?? {}, action.setHeaders);
12024
- }
12025
- await sendAction("Fetch.continueRequest", continueParams);
12026
- };
12027
- const sendAction = async (cdpMethod, params) => {
12028
- if (!session || !session.isAttached()) {
12029
- return;
12030
- }
12031
- try {
12032
- await session.sendAndWait(cdpMethod, params);
12033
- } catch (error) {
12034
- if (isBenignInterceptionError(error)) {
12035
- return;
12036
- }
12037
- recordError(error);
12191
+ continueParams.headers = mergeHeaders(paused.headers, action.setHeaders);
12038
12192
  }
12193
+ await sendAction(paused, "Fetch.continueRequest", continueParams);
12039
12194
  };
12195
+ const sendAction = async (request, cdpMethod, params) => interception.sendRequestCommand(request, cdpMethod, params);
12040
12196
  const getStatus = (ctx) => ({
12041
12197
  ok: true,
12042
12198
  attached: ctx.attached,
12043
- enabled,
12199
+ enabled: interception.enabled,
12044
12200
  rules: rules.map(toPublicRule),
12045
12201
  lastError
12046
12202
  });
@@ -12048,6 +12204,7 @@ var createNetMockController = () => {
12048
12204
  lastError = null;
12049
12205
  const rule = {
12050
12206
  id: nextRuleId++,
12207
+ scope: input.scope ?? "page",
12051
12208
  match: input.match,
12052
12209
  action: input.action,
12053
12210
  delayMs: input.delayMs,
@@ -12060,7 +12217,7 @@ var createNetMockController = () => {
12060
12217
  if (!attached) {
12061
12218
  return { ok: true, attached: false, enabled: false, rule: toPublicRule(rule) };
12062
12219
  }
12063
- const applied = enabled || await enableInterception();
12220
+ const applied = await interception.reconcile(getActiveScopes(rules));
12064
12221
  return {
12065
12222
  ok: true,
12066
12223
  attached: true,
@@ -12076,32 +12233,33 @@ var createNetMockController = () => {
12076
12233
  rules.splice(index, 1);
12077
12234
  }
12078
12235
  if (rules.length === 0) {
12079
- await disableInterception();
12236
+ await interception.reconcile(new Set);
12237
+ } else {
12238
+ await interception.reconcile(getActiveScopes(rules));
12080
12239
  }
12081
- return { ok: true, removed, enabled };
12240
+ return { ok: true, removed, enabled: interception.enabled };
12082
12241
  };
12083
12242
  const clearRules = async () => {
12084
12243
  const removed = rules.length;
12085
12244
  rules.length = 0;
12086
- await disableInterception();
12087
- return { ok: true, removed, enabled };
12245
+ await interception.reconcile(new Set);
12246
+ return { ok: true, removed, enabled: interception.enabled };
12088
12247
  };
12089
- const onAttach = async (nextSession) => {
12090
- session = nextSession;
12091
- enabled = false;
12092
- if (!hasActiveRules()) {
12093
- return;
12094
- }
12095
- const applied = await enableInterception();
12248
+ const onAttach = async () => {
12249
+ const applied = await interception.reconcile(getActiveScopes(rules), true);
12096
12250
  if (!applied && lastError) {
12097
12251
  console.warn(`[NetMock] Failed to re-enable interception on attach: ${lastError.message}`);
12098
12252
  }
12099
12253
  };
12254
+ const onTargetChanged = async () => {
12255
+ await interception.reconcile(getActiveScopes(rules));
12256
+ };
12100
12257
  const onDetach = () => {
12101
- enabled = false;
12258
+ interception.onDetach();
12102
12259
  };
12103
- return { bind, getStatus, addRule, removeRule, clearRules, onAttach, onDetach };
12260
+ return { bind, getStatus, addRule, removeRule, clearRules, onAttach, onTargetChanged, onDetach };
12104
12261
  };
12262
+ var getActiveScopes = (rules) => new Set(rules.filter(isRuleActive).map((rule) => rule.scope ?? "page"));
12105
12263
  var isRuleActive = (rule) => rule.times == null || rule.hits < rule.times;
12106
12264
  var ruleMatches = (rule, url, method, resourceType) => {
12107
12265
  if (!isRuleActive(rule)) {
@@ -12145,16 +12303,9 @@ var mergeHeaders = (original, overrides) => {
12145
12303
  }
12146
12304
  return [...merged.values()];
12147
12305
  };
12148
- var isBenignInterceptionError = (error) => {
12149
- const code = error?.code;
12150
- if (code === "cdp_not_attached") {
12151
- return true;
12152
- }
12153
- const message = error instanceof Error ? error.message : String(error);
12154
- return message.includes("Invalid InterceptionId") || message.includes("Inspected target navigated or closed");
12155
- };
12156
12306
  var toPublicRule = (rule) => ({
12157
12307
  id: rule.id,
12308
+ scope: rule.scope ?? "page",
12158
12309
  match: rule.match,
12159
12310
  action: rule.action,
12160
12311
  delayMs: rule.delayMs,
@@ -17067,7 +17218,7 @@ var createWatcherHandle = async (options, watcherId) => {
17067
17218
  await emulationController.onAttach(session);
17068
17219
  await throttleController.onAttach(session);
17069
17220
  await visibilityController.onAttach(sourceHandle.pageSession ?? session);
17070
- await netMockController.onAttach(sourceHandle.pageSession ?? session);
17221
+ await netMockController.onAttach();
17071
17222
  await networkCapture?.onAttached();
17072
17223
  onIndicatorAttach(session, target);
17073
17224
  await maybeInjectOnAttach(session, target);
@@ -17084,6 +17235,7 @@ var createWatcherHandle = async (options, watcherId) => {
17084
17235
  },
17085
17236
  reason: null
17086
17237
  });
17238
+ netMockController.onTargetChanged();
17087
17239
  onIndicatorAttach(session, target);
17088
17240
  };
17089
17241
  const handleSourceDetach = (reason) => {
@@ -17111,7 +17263,18 @@ var createWatcherHandle = async (options, watcherId) => {
17111
17263
  indicatorController?.setRecording(recording);
17112
17264
  }
17113
17265
  });
17114
- netMockController.bind(sourceHandle.pageSession ?? sourceHandle.session);
17266
+ netMockController.bind({
17267
+ pageSession: sourceHandle.pageSession ?? sourceHandle.session,
17268
+ getSelectedTarget: () => {
17269
+ const context = sourceHandle.getNetFilterContext?.() ?? null;
17270
+ const frameId = context?.selectedFrameId ?? null;
17271
+ return {
17272
+ frameId,
17273
+ topFrameId: context?.topFrameId ?? null,
17274
+ sessionId: frameId ? sourceHandle.getFrameSessionId?.(frameId) ?? null : null
17275
+ };
17276
+ }
17277
+ });
17115
17278
  const dialogSession = sourceHandle.pageSession ?? sourceHandle.session;
17116
17279
  dialogSession.onEvent("Page.javascriptDialogOpening", (params) => {
17117
17280
  const dialog = parseDialogStatus(params);
@@ -22115,6 +22278,10 @@ var buildAddRequest = async (options, output) => {
22115
22278
  if (!action) {
22116
22279
  return null;
22117
22280
  }
22281
+ const scope = resolveMockScope(options, output);
22282
+ if (!scope) {
22283
+ return null;
22284
+ }
22118
22285
  const match = { url: urlPattern };
22119
22286
  if (options.method) {
22120
22287
  match.method = options.method;
@@ -22122,7 +22289,21 @@ var buildAddRequest = async (options, output) => {
22122
22289
  if (options.resourceType) {
22123
22290
  match.resourceType = options.resourceType;
22124
22291
  }
22125
- return { match, action, delayMs, times };
22292
+ return { scope, match, action, delayMs, times };
22293
+ };
22294
+ var resolveMockScope = (options, output) => {
22295
+ if (options.scope && options.frame) {
22296
+ output.writeWarn("Cannot combine --scope and --frame. Use one or the other.");
22297
+ process.exitCode = 2;
22298
+ return null;
22299
+ }
22300
+ const value = (options.scope ?? options.frame ?? "page").trim().toLowerCase();
22301
+ if (value === "page" || value === "selected") {
22302
+ return value;
22303
+ }
22304
+ output.writeWarn(`Invalid mock scope: ${value}. Expected page or selected.`);
22305
+ process.exitCode = 2;
22306
+ return null;
22126
22307
  };
22127
22308
  var buildAction = async (options, responseHeaders, requestHeaders, delayMs, output) => {
22128
22309
  const invalid = (message) => {
@@ -22310,6 +22491,9 @@ var describeRule = (rule) => {
22310
22491
  if (rule.times != null) {
22311
22492
  extras.push(`times ${Math.min(rule.hits, rule.times)}/${rule.times}`);
22312
22493
  }
22494
+ if (rule.scope === "selected") {
22495
+ extras.push("scope selected");
22496
+ }
22313
22497
  extras.push(`hits ${rule.hits}`);
22314
22498
  return `#${rule.id} ${match} → ${describeAction(rule.action)} [${extras.join(", ")}]`;
22315
22499
  };
@@ -22429,6 +22613,8 @@ var netMockCommand = {
22429
22613
  { flags: "--url <pattern>", description: "URL wildcard pattern; substring match when it contains no *" },
22430
22614
  { flags: "--method <method>", description: "Only match this HTTP method" },
22431
22615
  { flags: "--resource-type <type>", description: "Only match this CDP resource type (Fetch, XHR, Document, ...)" },
22616
+ { flags: "--scope <scope>", description: "Intercept the top-level page or currently selected target (page|selected)" },
22617
+ { flags: "--frame <frame>", description: "Alias for --scope; accepts page or selected" },
22432
22618
  { flags: "--block", description: "Abort matching requests as BlockedByClient" },
22433
22619
  { flags: "--fail <reason>", description: "Abort with a network error (TimedOut, ConnectionRefused, ...)" },
22434
22620
  { flags: "--status <code>", description: "Stub a response with this HTTP status (default 200 when a body is given)" },
@@ -22455,12 +22641,13 @@ var netMockCommand = {
22455
22641
  'argus net mock add app --url "*/analytics/*" --block',
22456
22642
  'argus net mock add app --url "*/api/save" --fail ConnectionRefused --times 1',
22457
22643
  'argus net mock add app --url "*/api/config" --status 200 --body-file ./fixtures/config.json',
22644
+ 'argus net mock add extension --scope selected --url "*/api/config" --status 200 --body-file ./fixtures/config.json',
22458
22645
  `argus net mock add app --url "*/game/init" --status 500 --body '{"error":"maintenance"}'`,
22459
22646
  'argus net mock add app --url "*/api/*" --delay 2s --method POST',
22460
22647
  'argus net mock add app --url "cdn.prod.com" --rewrite-host localhost:3000'
22461
22648
  ],
22462
22649
  action: async (id, options) => {
22463
- await runNetMockAdd(id, options);
22650
+ await runNetMockAdd(id, resolveNetMockAddOptions(options));
22464
22651
  }
22465
22652
  },
22466
22653
  {
@@ -22471,7 +22658,7 @@ var netMockCommand = {
22471
22658
  options: [jsonOption],
22472
22659
  examples: ["argus net mock ls app", "argus net mock ls app --json"],
22473
22660
  action: async (id, options) => {
22474
- await runNetMockList(id, options);
22661
+ await runNetMockList(id, resolveCommandOptions(options));
22475
22662
  }
22476
22663
  },
22477
22664
  {
@@ -22485,7 +22672,7 @@ var netMockCommand = {
22485
22672
  options: [jsonOption],
22486
22673
  examples: ["argus net mock rm 2 app", "argus net mock rm 2 app --json"],
22487
22674
  action: async (rule, id, options) => {
22488
- await runNetMockRemove(id, rule, options);
22675
+ await runNetMockRemove(id, rule, resolveCommandOptions(options));
22489
22676
  }
22490
22677
  },
22491
22678
  {
@@ -22495,7 +22682,7 @@ var netMockCommand = {
22495
22682
  options: [jsonOption],
22496
22683
  examples: ["argus net mock clear app", "argus net mock clear app --json"],
22497
22684
  action: async (id, options) => {
22498
- await runNetMockClear(id, options);
22685
+ await runNetMockClear(id, resolveCommandOptions(options));
22499
22686
  }
22500
22687
  }
22501
22688
  ]
@@ -22718,8 +22905,8 @@ Examples:
22718
22905
  ]
22719
22906
  }
22720
22907
  ];
22721
- var resolveCommandOptions = (value) => {
22722
- const fallback = parseNetArgv(process.argv.slice(2));
22908
+ var resolveCommandOptions = (value, argv = process.argv.slice(2)) => {
22909
+ const fallback = parseNetArgv(argv);
22723
22910
  if (value && typeof value === "object" && "opts" in value && typeof value.opts === "function") {
22724
22911
  return {
22725
22912
  ...value.opts(),
@@ -22731,6 +22918,16 @@ var resolveCommandOptions = (value) => {
22731
22918
  ...fallback
22732
22919
  };
22733
22920
  };
22921
+ var resolveNetMockAddOptions = (value, argv = process.argv.slice(2)) => {
22922
+ const resolved = resolveCommandOptions(value, argv);
22923
+ for (const key of ["method", "status", "resourceType"]) {
22924
+ const option = resolved[key];
22925
+ if (Array.isArray(option)) {
22926
+ resolved[key] = option.at(-1);
22927
+ }
22928
+ }
22929
+ return resolved;
22930
+ };
22734
22931
  var parseNetArgv = (argv) => {
22735
22932
  const parsed = {};
22736
22933
  const readValue = (flag) => {
@@ -47431,10 +47628,34 @@ var domCommands = [
47431
47628
  ];
47432
47629
 
47433
47630
  // dist/commands/domKeydown.js
47631
+ var normalizeOptionalString2 = (value) => {
47632
+ const normalized = value?.trim();
47633
+ return normalized ? normalized : undefined;
47634
+ };
47635
+ var mergeModifierOptions = (options) => {
47636
+ const modifiers = new Set;
47637
+ for (const part of (options.modifiers ?? "").split(",")) {
47638
+ const name = part.trim().toLowerCase();
47639
+ if (name) {
47640
+ modifiers.add(name);
47641
+ }
47642
+ }
47643
+ if (options.shift)
47644
+ modifiers.add("shift");
47645
+ if (options.ctrl)
47646
+ modifiers.add("ctrl");
47647
+ if (options.alt)
47648
+ modifiers.add("alt");
47649
+ if (options.meta || options.cmd)
47650
+ modifiers.add("meta");
47651
+ return modifiers.size > 0 ? Array.from(modifiers).join(",") : undefined;
47652
+ };
47434
47653
  var runDomKeydown = defineWatcherCommand({
47435
47654
  build: (_args, options, output) => {
47436
- if (!options.key || options.key.trim() === "") {
47437
- output.writeWarn("--key is required");
47655
+ const key = normalizeOptionalString2(options.key);
47656
+ const code = normalizeOptionalString2(options.code);
47657
+ if (!key && !code) {
47658
+ output.writeWarn("--key or --code is required");
47438
47659
  process.exitCode = 2;
47439
47660
  return null;
47440
47661
  }
@@ -47442,15 +47663,20 @@ var runDomKeydown = defineWatcherCommand({
47442
47663
  path: "/dom/keydown",
47443
47664
  method: "POST",
47444
47665
  body: {
47445
- key: options.key,
47666
+ key,
47667
+ code,
47446
47668
  selector: options.selector,
47447
- modifiers: options.modifiers
47669
+ modifiers: mergeModifierOptions(options)
47448
47670
  },
47449
47671
  timeoutMs: 30000
47450
47672
  };
47451
47673
  },
47452
- formatHuman: (response, { output }) => {
47453
- output.writeHuman(`Dispatched keydown: ${response.key}`);
47674
+ formatHuman: (response, { options, output }) => {
47675
+ if (options.printEvent) {
47676
+ output.writeHuman(`Dispatched keydown event: ${JSON.stringify(response.event)}`);
47677
+ return;
47678
+ }
47679
+ output.writeHuman(`Dispatched keydown: ${response.key} (code=${response.code})`);
47454
47680
  }
47455
47681
  });
47456
47682
 
@@ -47460,13 +47686,26 @@ var keydownCommand = {
47460
47686
  description: "Dispatch a keyboard event to the connected page",
47461
47687
  arguments: [{ flags: "[id]", description: "Watcher id to query" }],
47462
47688
  options: [
47463
- { flags: "--key <name>", description: "Key name (e.g. Enter, a, ArrowUp)", required: true },
47689
+ { flags: "--key <name>", description: "KeyboardEvent.key value (e.g. Enter, a, ArrowUp)" },
47690
+ { flags: "--code <code>", description: "KeyboardEvent.code value (e.g. KeyG, Digit1)" },
47464
47691
  { flags: "--selector <css>", description: "Focus element before dispatching" },
47465
47692
  { flags: "--testid <id>", description: `Shorthand for --selector "[data-testid='<id>']"` },
47466
47693
  { flags: "--modifiers <list>", description: "Comma-separated modifiers: shift,ctrl,alt,meta" },
47694
+ { flags: "--shift", description: "Shortcut for --modifiers shift" },
47695
+ { flags: "--ctrl", description: "Shortcut for --modifiers ctrl" },
47696
+ { flags: "--alt", description: "Shortcut for --modifiers alt" },
47697
+ { flags: "--meta", description: "Shortcut for --modifiers meta" },
47698
+ { flags: "--cmd", description: "Alias for --meta" },
47699
+ { flags: "--print-event", description: "Print resolved key/code/modifier event details" },
47467
47700
  { flags: "--json", description: "Output JSON for automation" }
47468
47701
  ],
47469
- examples: ["argus keydown app --key Enter", 'argus keydown app --key a --selector "#input"', "argus keydown app --key a --modifiers shift,ctrl"],
47702
+ examples: [
47703
+ "argus keydown app --key Enter",
47704
+ "argus keydown app --key G",
47705
+ "argus keydown app --code KeyG",
47706
+ 'argus keydown app --key a --selector "#input"',
47707
+ "argus keydown app --key a --shift --ctrl"
47708
+ ],
47470
47709
  action: async (id, options) => {
47471
47710
  if (!resolveTestId(options))
47472
47711
  return;
@@ -48729,7 +48968,7 @@ var extractPlugin = (mod) => {
48729
48968
  return null;
48730
48969
  return plugin;
48731
48970
  };
48732
- var normalizeOptionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
48971
+ var normalizeOptionalString3 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
48733
48972
  var normalizeCommands = (value) => {
48734
48973
  if (!Array.isArray(value))
48735
48974
  return [];
@@ -48751,11 +48990,11 @@ var createLoadedEntry = (entry, plugin, url) => ({
48751
48990
  alias: entry.alias,
48752
48991
  status: "loaded",
48753
48992
  name: plugin.name,
48754
- version: normalizeOptionalString(plugin.version),
48755
- description: normalizeOptionalString(plugin.description),
48993
+ version: normalizeOptionalString3(plugin.version),
48994
+ description: normalizeOptionalString3(plugin.description),
48756
48995
  commands: normalizeCommands(plugin.commands),
48757
- homepage: normalizeOptionalString(plugin.homepage),
48758
- minArgusVersion: normalizeOptionalString(plugin.minArgusVersion),
48996
+ homepage: normalizeOptionalString3(plugin.homepage),
48997
+ minArgusVersion: normalizeOptionalString3(plugin.minArgusVersion),
48759
48998
  url
48760
48999
  });
48761
49000
  var registerPlugins = async (program2, argv = process.argv.slice(2)) => {
@@ -49110,9 +49349,63 @@ var pluginCommands = [
49110
49349
  }
49111
49350
  ];
49112
49351
 
49113
- // dist/commands/extension/nativeHost.js
49352
+ // dist/commands/extension/install.js
49353
+ import { spawn as spawn3 } from "node:child_process";
49354
+
49355
+ // dist/commands/extension/extensionId.js
49356
+ var ARGUS_EXTENSION_ID = "ibhecpfiodfmkpflcnnkkgnjifheefhb";
49357
+
49358
+ // dist/commands/extension/extensionPath.js
49114
49359
  import fs17 from "node:fs";
49115
49360
  import path22 from "node:path";
49361
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
49362
+ var runExtensionPath = (options) => {
49363
+ const output = createOutput(options);
49364
+ const extensionDir = resolveExtensionDir();
49365
+ if (!extensionDir) {
49366
+ const message = `Packaged Argus extension not found. Checked:
49367
+ ${resolveExtensionCandidates().join(`
49368
+ `)}`;
49369
+ if (options.json) {
49370
+ output.writeJson({ ok: false, error: { message } });
49371
+ } else {
49372
+ console.error(message);
49373
+ }
49374
+ process.exitCode = 2;
49375
+ return;
49376
+ }
49377
+ if (options.json) {
49378
+ output.writeJson({ ok: true, path: extensionDir });
49379
+ return;
49380
+ }
49381
+ output.writeHuman(extensionDir);
49382
+ };
49383
+ var resolveExtensionDir = () => {
49384
+ for (const candidate of resolveExtensionCandidates()) {
49385
+ if (isLoadableExtensionDir(candidate)) {
49386
+ return candidate;
49387
+ }
49388
+ }
49389
+ return null;
49390
+ };
49391
+ var isLoadableExtensionDir = (dir) => {
49392
+ return fs17.existsSync(path22.join(dir, "manifest.json")) && fs17.existsSync(path22.join(dir, "dist", "background", "service-worker.js"));
49393
+ };
49394
+ var resolveExtensionCandidates = () => {
49395
+ const moduleDir = path22.dirname(fileURLToPath2(import.meta.url));
49396
+ const candidates = [
49397
+ path22.resolve(moduleDir, "extension"),
49398
+ path22.resolve(moduleDir, "..", "..", "extension"),
49399
+ path22.resolve(moduleDir, "..", "extension"),
49400
+ path22.resolve(moduleDir, "..", "..", "..", "argus-extension"),
49401
+ path22.resolve(moduleDir, "..", "..", "..", "..", "argus-extension")
49402
+ ];
49403
+ return [...new Set(candidates)];
49404
+ };
49405
+
49406
+ // dist/commands/extension/nativeHost.js
49407
+ import fs18 from "node:fs";
49408
+ import path23 from "node:path";
49116
49409
  import os8 from "node:os";
49117
49410
  import { execSync } from "node:child_process";
49118
49411
  var BRIDGE_HOST_NAME = "com.vforsh.argus.bridge";
@@ -49122,21 +49415,21 @@ var HOST_NAMES = [BRIDGE_HOST_NAME, CONTROL_HOST_NAME];
49122
49415
  var getManifestDir = (platform) => {
49123
49416
  switch (platform) {
49124
49417
  case "darwin":
49125
- return path22.join(os8.homedir(), "Library/Application Support/Google/Chrome/NativeMessagingHosts");
49418
+ return path23.join(os8.homedir(), "Library/Application Support/Google/Chrome/NativeMessagingHosts");
49126
49419
  case "linux":
49127
- return path22.join(os8.homedir(), ".config/google-chrome/NativeMessagingHosts");
49420
+ return path23.join(os8.homedir(), ".config/google-chrome/NativeMessagingHosts");
49128
49421
  case "win32":
49129
- return path22.join(process.env.LOCALAPPDATA ?? os8.homedir(), "Google/Chrome/User Data/NativeMessagingHosts");
49422
+ return path23.join(process.env.LOCALAPPDATA ?? os8.homedir(), "Google/Chrome/User Data/NativeMessagingHosts");
49130
49423
  default:
49131
49424
  throw new Error(`Unsupported platform: ${platform}`);
49132
49425
  }
49133
49426
  };
49134
49427
  var getManifestPath = (platform, hostName = BRIDGE_HOST_NAME) => {
49135
- return path22.join(getManifestDir(platform), `${hostName}.json`);
49428
+ return path23.join(getManifestDir(platform), `${hostName}.json`);
49136
49429
  };
49137
49430
  var getWrapperPath = (platform, hostName = BRIDGE_HOST_NAME) => {
49138
49431
  const filename = hostName === CONTROL_HOST_NAME ? "argus-native-control-host.sh" : "argus-native-host.sh";
49139
- return path22.join(getManifestDir(platform), filename);
49432
+ return path23.join(getManifestDir(platform), filename);
49140
49433
  };
49141
49434
  var getPlatform = () => {
49142
49435
  const platform = os8.platform();
@@ -49148,8 +49441,8 @@ var getPlatform = () => {
49148
49441
  var findArgusExecutable = () => {
49149
49442
  try {
49150
49443
  const npmGlobalPrefix = execSync("npm config get prefix", { encoding: "utf8" }).trim();
49151
- const globalBinPath = path22.join(npmGlobalPrefix, "bin", "argus");
49152
- if (fs17.existsSync(globalBinPath)) {
49444
+ const globalBinPath = path23.join(npmGlobalPrefix, "bin", "argus");
49445
+ if (fs18.existsSync(globalBinPath)) {
49153
49446
  return globalBinPath;
49154
49447
  }
49155
49448
  } catch {}
@@ -49162,7 +49455,7 @@ var findArgusExecutable = () => {
49162
49455
  throw new Error("Could not find argus executable. Please install argus globally (npm install -g @vforsh/argus) or ensure it is in your PATH.");
49163
49456
  };
49164
49457
  var findNodePath = () => {
49165
- return fs17.realpathSync(process.execPath);
49458
+ return fs18.realpathSync(process.execPath);
49166
49459
  };
49167
49460
  var createManifest = (extensionId, executablePath, hostName = BRIDGE_HOST_NAME) => {
49168
49461
  return {
@@ -49180,16 +49473,16 @@ var createWrapperScript = (platform, executablePath, hostName = BRIDGE_HOST_NAME
49180
49473
  const script = `#!/bin/bash
49181
49474
  exec "${nodePath}" "${executablePath}" ${args}
49182
49475
  `;
49183
- fs17.writeFileSync(wrapperPath, script, { mode: 493 });
49476
+ fs18.writeFileSync(wrapperPath, script, { mode: 493 });
49184
49477
  return wrapperPath;
49185
49478
  };
49186
49479
  var installNativeHosts = (platform, extensionId, executablePath) => {
49187
- fs17.mkdirSync(getManifestDir(platform), { recursive: true });
49480
+ fs18.mkdirSync(getManifestDir(platform), { recursive: true });
49188
49481
  return HOST_NAMES.map((hostName) => {
49189
49482
  const wrapperPath = createWrapperScript(platform, executablePath, hostName);
49190
49483
  const manifestPath = getManifestPath(platform, hostName);
49191
49484
  const manifest = createManifest(extensionId, wrapperPath, hostName);
49192
- fs17.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
49485
+ fs18.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
49193
49486
  registerWindowsNativeHost(platform, hostName, manifestPath);
49194
49487
  return { hostName, manifestPath, wrapperPath };
49195
49488
  });
@@ -49209,8 +49502,8 @@ var removeNativeHosts = (platform) => {
49209
49502
  var inspectNativeHost = (platform, hostName) => {
49210
49503
  const manifestPath = getManifestPath(platform, hostName);
49211
49504
  const wrapperPath = getWrapperPath(platform, hostName);
49212
- const manifestExists = fs17.existsSync(manifestPath);
49213
- const wrapperExists = fs17.existsSync(wrapperPath);
49505
+ const manifestExists = fs18.existsSync(manifestPath);
49506
+ const wrapperExists = fs18.existsSync(wrapperPath);
49214
49507
  const wrapperExecutable = wrapperExists && isWrapperExecutable(wrapperPath);
49215
49508
  const manifest = manifestExists ? readManifest(manifestPath) : null;
49216
49509
  const manifestValid = Boolean(manifest && manifest.name === hostName && manifest.type === "stdio" && Array.isArray(manifest.allowed_origins));
@@ -49231,7 +49524,7 @@ var inspectNativeHost = (platform, hostName) => {
49231
49524
  };
49232
49525
  var readManifest = (manifestPath) => {
49233
49526
  try {
49234
- const content = fs17.readFileSync(manifestPath, "utf8");
49527
+ const content = fs18.readFileSync(manifestPath, "utf8");
49235
49528
  return JSON.parse(content);
49236
49529
  } catch {
49237
49530
  return null;
@@ -49239,7 +49532,7 @@ var readManifest = (manifestPath) => {
49239
49532
  };
49240
49533
  var isWrapperExecutable = (wrapperPath) => {
49241
49534
  try {
49242
- fs17.accessSync(wrapperPath, fs17.constants.X_OK);
49535
+ fs18.accessSync(wrapperPath, fs18.constants.X_OK);
49243
49536
  return true;
49244
49537
  } catch {
49245
49538
  return false;
@@ -49260,10 +49553,10 @@ var extractExtensionId = (origin) => {
49260
49553
  return match?.[1] ?? null;
49261
49554
  };
49262
49555
  var removeFileIfExists = (filePath) => {
49263
- if (!fs17.existsSync(filePath)) {
49556
+ if (!fs18.existsSync(filePath)) {
49264
49557
  return false;
49265
49558
  }
49266
- fs17.unlinkSync(filePath);
49559
+ fs18.unlinkSync(filePath);
49267
49560
  return true;
49268
49561
  };
49269
49562
  var registerWindowsNativeHost = (platform, hostName, manifestPath) => {
@@ -49283,10 +49576,145 @@ var unregisterWindowsNativeHost = (platform, hostName) => {
49283
49576
  } catch {}
49284
49577
  };
49285
49578
 
49579
+ // dist/commands/extension/install.js
49580
+ var DEFAULT_TIMEOUT_SECONDS = 120;
49581
+ var POLL_INTERVAL_MS = 700;
49582
+ var runExtensionInstall = async (options) => {
49583
+ const output = createOutput(options);
49584
+ let platform;
49585
+ try {
49586
+ platform = getPlatform();
49587
+ } catch (error) {
49588
+ return fail(output, error.message);
49589
+ }
49590
+ const extensionDir = resolveExtensionDir();
49591
+ if (!extensionDir) {
49592
+ return fail(output, "Packaged Argus extension not found. Build the CLI (npm run build:packages) or run from a built checkout.");
49593
+ }
49594
+ let executablePath;
49595
+ try {
49596
+ executablePath = findArgusExecutable();
49597
+ } catch (error) {
49598
+ return fail(output, error.message);
49599
+ }
49600
+ let hosts;
49601
+ try {
49602
+ hosts = installNativeHosts(platform, ARGUS_EXTENSION_ID, executablePath);
49603
+ } catch (error) {
49604
+ return fail(output, `Failed to install native hosts: ${error.message}`);
49605
+ }
49606
+ await pruneRegistry();
49607
+ let connected = await isControlConnected();
49608
+ const shouldOpen = options.open !== false && !connected;
49609
+ const opened = shouldOpen ? openExtensionsPage() : false;
49610
+ if (!output.json) {
49611
+ printSummary(output, { extensionDir, hosts, connected, opened, shouldOpen });
49612
+ }
49613
+ const shouldWait = options.wait !== false && !connected;
49614
+ if (shouldWait) {
49615
+ connected = await waitForControl(parseTimeoutMs2(options.timeout), output);
49616
+ }
49617
+ if (output.json) {
49618
+ output.writeJson({ success: true, extensionId: ARGUS_EXTENSION_ID, extensionPath: extensionDir, hosts, connected });
49619
+ if (!connected && shouldWait) {
49620
+ process.exitCode = 1;
49621
+ }
49622
+ return;
49623
+ }
49624
+ printResult(output, connected, shouldWait);
49625
+ };
49626
+ var isControlConnected = async () => {
49627
+ const { registry: registry2 } = await readRegistry();
49628
+ return Boolean(registry2.watchers[CONTROL_WATCHER_ID]);
49629
+ };
49630
+ var openExtensionsPage = () => {
49631
+ const chromeBin = resolveChromeBin();
49632
+ if (!chromeBin) {
49633
+ return false;
49634
+ }
49635
+ try {
49636
+ const child = spawn3(chromeBin, ["chrome://extensions/"], { stdio: "ignore", detached: true });
49637
+ child.unref();
49638
+ return true;
49639
+ } catch {
49640
+ return false;
49641
+ }
49642
+ };
49643
+ var waitForControl = async (timeoutMs, output) => {
49644
+ if (!output.json) {
49645
+ output.writeHuman(`Waiting for the extension to connect (up to ${Math.round(timeoutMs / 1000)}s)...`);
49646
+ }
49647
+ const deadline = Date.now() + timeoutMs;
49648
+ while (Date.now() < deadline) {
49649
+ if (await isControlConnected()) {
49650
+ return true;
49651
+ }
49652
+ await delay7(POLL_INTERVAL_MS);
49653
+ }
49654
+ return isControlConnected();
49655
+ };
49656
+ var printSummary = (output, info) => {
49657
+ output.writeHuman("");
49658
+ output.writeHuman("Argus extension setup");
49659
+ output.writeHuman("");
49660
+ output.writeHuman(` Native hosts: installed (${info.hosts.map((host) => host.hostName).join(", ")})`);
49661
+ output.writeHuman(` Extension ID: ${ARGUS_EXTENSION_ID}`);
49662
+ output.writeHuman(` Load unpacked: ${info.extensionDir}`);
49663
+ output.writeHuman("");
49664
+ if (info.connected) {
49665
+ return;
49666
+ }
49667
+ if (info.opened) {
49668
+ output.writeHuman('Opened chrome://extensions. Click "Load unpacked" and select the folder above.');
49669
+ } else if (info.shouldOpen) {
49670
+ output.writeHuman('Open chrome://extensions, then click "Load unpacked" and select the folder above.');
49671
+ } else {
49672
+ output.writeHuman('Open chrome://extensions and "Load unpacked" the folder above.');
49673
+ }
49674
+ output.writeHuman('(Enable Developer mode if the "Load unpacked" button is hidden.)');
49675
+ output.writeHuman("");
49676
+ };
49677
+ var printResult = (output, connected, waited) => {
49678
+ if (connected) {
49679
+ output.writeHuman("Extension connected.");
49680
+ output.writeHuman("");
49681
+ output.writeHuman("Try it:");
49682
+ output.writeHuman(" argus ext tabs");
49683
+ output.writeHuman("");
49684
+ return;
49685
+ }
49686
+ if (waited) {
49687
+ output.writeHuman("Extension did not connect yet.");
49688
+ output.writeHuman(" - If it is already loaded, open its popup or reload it in chrome://extensions.");
49689
+ output.writeHuman(" - Then check `argus extension status` and `argus ext doctor`.");
49690
+ output.writeHuman("");
49691
+ process.exitCode = 1;
49692
+ return;
49693
+ }
49694
+ output.writeHuman("Once loaded, verify with `argus ext tabs`.");
49695
+ output.writeHuman("");
49696
+ };
49697
+ var parseTimeoutMs2 = (value) => {
49698
+ const seconds = value ? Number.parseInt(value, 10) : DEFAULT_TIMEOUT_SECONDS;
49699
+ if (!Number.isFinite(seconds) || seconds < 5) {
49700
+ return DEFAULT_TIMEOUT_SECONDS * 1000;
49701
+ }
49702
+ return seconds * 1000;
49703
+ };
49704
+ var fail = (output, message) => {
49705
+ if (output.json) {
49706
+ output.writeJson({ success: false, error: { message } });
49707
+ } else {
49708
+ console.error(message);
49709
+ }
49710
+ process.exitCode = 1;
49711
+ };
49712
+ var delay7 = (ms3) => new Promise((resolve2) => setTimeout(resolve2, ms3));
49713
+
49286
49714
  // dist/commands/extension/setup.js
49287
49715
  var runExtensionSetup = async (options) => {
49288
49716
  const output = createOutput(options);
49289
- const { extensionId } = options;
49717
+ const extensionId = options.extensionId ?? ARGUS_EXTENSION_ID;
49290
49718
  let platform;
49291
49719
  try {
49292
49720
  platform = getPlatform();
@@ -49342,9 +49770,11 @@ var runExtensionSetup = async (options) => {
49342
49770
  }
49343
49771
  output.writeHuman("");
49344
49772
  output.writeHuman("Next steps:");
49345
- output.writeHuman(" 1. Reload the extension in chrome://extensions");
49773
+ output.writeHuman(" 1. Load the unpacked extension from `argus extension path` (if not already loaded)");
49346
49774
  output.writeHuman(" 2. Run `argus ext tabs` or open the extension popup and attach a tab");
49347
49775
  output.writeHuman("");
49776
+ output.writeHuman("Tip: `argus extension install` does this end-to-end and waits for the extension to connect.");
49777
+ output.writeHuman("");
49348
49778
  }
49349
49779
  };
49350
49780
 
@@ -49733,7 +50163,7 @@ var waitForTabWatcher = async (controlWatcher, selector, tab, watcherId) => {
49733
50163
  if (watcher) {
49734
50164
  const status = await fetchStatus(watcher);
49735
50165
  if (!status || !statusMatchesTabWatcher(status, latestTab ?? tab, latestTab?.watcherId === explicitWatcherId)) {
49736
- await delay7(WATCHER_POLL_INTERVAL_MS);
50166
+ await delay8(WATCHER_POLL_INTERVAL_MS);
49737
50167
  continue;
49738
50168
  }
49739
50169
  return { ok: true, watcher, status, tab: latestTab ?? tab };
@@ -49743,7 +50173,7 @@ var waitForTabWatcher = async (controlWatcher, selector, tab, watcherId) => {
49743
50173
  if (lastResult.ok) {
49744
50174
  return lastResult;
49745
50175
  }
49746
- await delay7(WATCHER_POLL_INTERVAL_MS);
50176
+ await delay8(WATCHER_POLL_INTERVAL_MS);
49747
50177
  }
49748
50178
  return lastResult;
49749
50179
  };
@@ -49797,7 +50227,7 @@ var targetMatchesTab = (target, tab) => {
49797
50227
  }
49798
50228
  return Boolean(target.title && target.title === tab.title && target.url === tab.url);
49799
50229
  };
49800
- var delay7 = (ms3) => new Promise((resolve2) => setTimeout(resolve2, ms3));
50230
+ var delay8 = (ms3) => new Promise((resolve2) => setTimeout(resolve2, ms3));
49801
50231
  var formatError6 = (error) => error instanceof Error ? error.message : String(error);
49802
50232
 
49803
50233
  // dist/commands/extension/show.js
@@ -50176,19 +50606,19 @@ var waitForSelectedTarget = async (watcher, target, options = {}) => {
50176
50606
  const [status, targets] = await Promise.all([fetchWatcherStatus(watcher), fetchExtensionTargets(watcher)]);
50177
50607
  if (!status.ok) {
50178
50608
  lastError = status.error;
50179
- await delay8(intervalMs);
50609
+ await delay9(intervalMs);
50180
50610
  continue;
50181
50611
  }
50182
50612
  if (!targets.ok) {
50183
50613
  lastError = targets.error;
50184
- await delay8(intervalMs);
50614
+ await delay9(intervalMs);
50185
50615
  continue;
50186
50616
  }
50187
50617
  const selected = targets.targets.find((entry) => entry.id === target.id && entry.attached === true);
50188
50618
  if (selected && statusMatchesTarget(status.status, selected) && status.status.targetReady !== false) {
50189
50619
  return { ok: true, status: status.status, target: selected };
50190
50620
  }
50191
- await delay8(intervalMs);
50621
+ await delay9(intervalMs);
50192
50622
  }
50193
50623
  return { ok: false, error: lastError };
50194
50624
  };
@@ -50334,7 +50764,7 @@ var originOf = (url) => {
50334
50764
  }
50335
50765
  };
50336
50766
  var shortenId = (id) => id.length > 18 ? `${id.slice(0, 14)}...` : id;
50337
- var delay8 = (ms3) => new Promise((resolve2) => setTimeout(resolve2, ms3));
50767
+ var delay9 = (ms3) => new Promise((resolve2) => setTimeout(resolve2, ms3));
50338
50768
  var formatError8 = (error) => error instanceof Error ? error.message : String(error);
50339
50769
 
50340
50770
  // dist/commands/extension/use.js
@@ -50750,22 +51180,43 @@ var extensionCommands = [
50750
51180
  description: "Browser extension management",
50751
51181
  subcommands: [
50752
51182
  {
50753
- name: "setup <extensionId>",
50754
- description: "Install native messaging host for the browser extension",
51183
+ name: "install",
51184
+ description: "Set up the extension end-to-end: install hosts, open chrome://extensions, wait for connect",
51185
+ options: [
51186
+ { flags: "--no-open", description: "Do not open chrome://extensions automatically" },
51187
+ { flags: "--no-wait", description: "Do not wait for the extension to connect" },
51188
+ { flags: "--timeout <seconds>", description: "Seconds to wait for the extension to connect (default 120)" },
51189
+ jsonOption2
51190
+ ],
51191
+ examples: ["argus extension install", "argus extension install --no-open", "argus extension install --no-wait --json"],
51192
+ action: async (options) => {
51193
+ await runExtensionInstall(options);
51194
+ }
51195
+ },
51196
+ {
51197
+ name: "setup [extensionId]",
51198
+ description: "Install native messaging host (uses the pinned extension ID by default)",
50755
51199
  options: [jsonOption2],
50756
51200
  configure: (command) => {
50757
51201
  command.addHelpText("after", `
50758
- To get your extension ID:
50759
- 1. Open chrome://extensions
50760
- 2. Enable Developer mode
50761
- 3. Load argus-extension as unpacked
50762
- 4. Copy the ID from the extension card
51202
+ The Argus extension pins a stable ID, so no argument is needed.
51203
+ Prefer \`argus extension install\` for the guided, end-to-end flow.
51204
+ Pass an explicit id only when loading a differently-keyed build.
50763
51205
  `);
50764
51206
  },
50765
51207
  action: async (extensionId, options) => {
50766
51208
  await runExtensionSetup({ extensionId, ...options });
50767
51209
  }
50768
51210
  },
51211
+ {
51212
+ name: "path",
51213
+ description: 'Print the path to the unpacked extension for chrome://extensions "Load unpacked"',
51214
+ options: [jsonOption2],
51215
+ examples: ["argus extension path", "argus extension path --json"],
51216
+ action: (options) => {
51217
+ runExtensionPath(options);
51218
+ }
51219
+ },
50769
51220
  {
50770
51221
  name: "remove",
50771
51222
  description: "Uninstall native messaging host",
@@ -50923,9 +51374,9 @@ same sticky shown+focused lock as \`argus page show <watcherId>\`.
50923
51374
  ];
50924
51375
 
50925
51376
  // dist/commands/skill.js
50926
- import fs18 from "node:fs";
50927
- import path23 from "node:path";
50928
- import { fileURLToPath as fileURLToPath2 } from "node:url";
51377
+ import fs19 from "node:fs";
51378
+ import path24 from "node:path";
51379
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
50929
51380
  var SKILL_PATH_PARTS = ["skill", "argus", "SKILL.md"];
50930
51381
  var runSkill = (options) => {
50931
51382
  const output = createOutput(options);
@@ -50950,18 +51401,18 @@ ${resolveArgusSkillCandidates().join(`
50950
51401
  };
50951
51402
  var resolveArgusSkillPath = () => {
50952
51403
  for (const candidate of resolveArgusSkillCandidates()) {
50953
- if (fs18.existsSync(candidate)) {
51404
+ if (fs19.existsSync(candidate)) {
50954
51405
  return candidate;
50955
51406
  }
50956
51407
  }
50957
51408
  return null;
50958
51409
  };
50959
51410
  var resolveArgusSkillCandidates = () => {
50960
- const moduleDir = path23.dirname(fileURLToPath2(import.meta.url));
51411
+ const moduleDir = path24.dirname(fileURLToPath3(import.meta.url));
50961
51412
  return [
50962
- path23.resolve(moduleDir, ...SKILL_PATH_PARTS),
50963
- path23.resolve(moduleDir, "..", ...SKILL_PATH_PARTS),
50964
- path23.resolve(moduleDir, "..", "..", "..", "..", ...SKILL_PATH_PARTS)
51413
+ path24.resolve(moduleDir, ...SKILL_PATH_PARTS),
51414
+ path24.resolve(moduleDir, "..", ...SKILL_PATH_PARTS),
51415
+ path24.resolve(moduleDir, "..", "..", "..", "..", ...SKILL_PATH_PARTS)
50965
51416
  ];
50966
51417
  };
50967
51418