@vforsh/argus 0.1.18 → 0.1.20

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 (116) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/argus.js +3008 -572
  3. package/dist/cli/plugins/pluginHost.d.ts.map +1 -1
  4. package/dist/cli/plugins/pluginHost.js +1 -0
  5. package/dist/cli/plugins/pluginHost.js.map +1 -1
  6. package/dist/cli/register/extensionCommands.d.ts.map +1 -1
  7. package/dist/cli/register/extensionCommands.js +131 -6
  8. package/dist/cli/register/extensionCommands.js.map +1 -1
  9. package/dist/cli/register/index.d.ts.map +1 -1
  10. package/dist/cli/register/index.js +6 -0
  11. package/dist/cli/register/index.js.map +1 -1
  12. package/dist/cli/register/recordCommands.d.ts +3 -0
  13. package/dist/cli/register/recordCommands.d.ts.map +1 -0
  14. package/dist/cli/register/recordCommands.js +90 -0
  15. package/dist/cli/register/recordCommands.js.map +1 -0
  16. package/dist/cli/register/skillCommands.d.ts +3 -0
  17. package/dist/cli/register/skillCommands.d.ts.map +1 -0
  18. package/dist/cli/register/skillCommands.js +13 -0
  19. package/dist/cli/register/skillCommands.js.map +1 -0
  20. package/dist/commands/domDrag.d.ts +21 -0
  21. package/dist/commands/domDrag.d.ts.map +1 -0
  22. package/dist/commands/domDrag.js +179 -0
  23. package/dist/commands/domDrag.js.map +1 -0
  24. package/dist/commands/extension/attach.d.ts +2 -0
  25. package/dist/commands/extension/attach.d.ts.map +1 -1
  26. package/dist/commands/extension/attach.js +31 -6
  27. package/dist/commands/extension/attach.js.map +1 -1
  28. package/dist/commands/extension/doctor.d.ts +6 -0
  29. package/dist/commands/extension/doctor.d.ts.map +1 -0
  30. package/dist/commands/extension/doctor.js +185 -0
  31. package/dist/commands/extension/doctor.js.map +1 -0
  32. package/dist/commands/extension/extensionId.d.ts +27 -0
  33. package/dist/commands/extension/extensionId.d.ts.map +1 -0
  34. package/dist/commands/extension/extensionId.js +37 -0
  35. package/dist/commands/extension/extensionId.js.map +1 -0
  36. package/dist/commands/extension/extensionPath.d.ts +15 -0
  37. package/dist/commands/extension/extensionPath.d.ts.map +1 -0
  38. package/dist/commands/extension/extensionPath.js +61 -0
  39. package/dist/commands/extension/extensionPath.js.map +1 -0
  40. package/dist/commands/extension/install.d.ts +16 -0
  41. package/dist/commands/extension/install.d.ts.map +1 -0
  42. package/dist/commands/extension/install.js +154 -0
  43. package/dist/commands/extension/install.js.map +1 -0
  44. package/dist/commands/extension/select.d.ts +7 -0
  45. package/dist/commands/extension/select.d.ts.map +1 -0
  46. package/dist/commands/extension/select.js +73 -0
  47. package/dist/commands/extension/select.js.map +1 -0
  48. package/dist/commands/extension/setup.d.ts +2 -1
  49. package/dist/commands/extension/setup.d.ts.map +1 -1
  50. package/dist/commands/extension/setup.js +5 -2
  51. package/dist/commands/extension/setup.js.map +1 -1
  52. package/dist/commands/extension/show.d.ts +4 -0
  53. package/dist/commands/extension/show.d.ts.map +1 -1
  54. package/dist/commands/extension/show.js +12 -88
  55. package/dist/commands/extension/show.js.map +1 -1
  56. package/dist/commands/extension/tabAttach.d.ts +28 -0
  57. package/dist/commands/extension/tabAttach.d.ts.map +1 -0
  58. package/dist/commands/extension/tabAttach.js +106 -0
  59. package/dist/commands/extension/tabAttach.js.map +1 -0
  60. package/dist/commands/extension/tabWatcher.d.ts +22 -0
  61. package/dist/commands/extension/tabWatcher.d.ts.map +1 -0
  62. package/dist/commands/extension/tabWatcher.js +89 -0
  63. package/dist/commands/extension/tabWatcher.js.map +1 -0
  64. package/dist/commands/extension/targetSelection.d.ts +62 -0
  65. package/dist/commands/extension/targetSelection.d.ts.map +1 -0
  66. package/dist/commands/extension/targetSelection.js +210 -0
  67. package/dist/commands/extension/targetSelection.js.map +1 -0
  68. package/dist/commands/extension/targets.d.ts +7 -0
  69. package/dist/commands/extension/targets.d.ts.map +1 -0
  70. package/dist/commands/extension/targets.js +73 -0
  71. package/dist/commands/extension/targets.js.map +1 -0
  72. package/dist/commands/extension/use.d.ts +13 -0
  73. package/dist/commands/extension/use.d.ts.map +1 -0
  74. package/dist/commands/extension/use.js +66 -0
  75. package/dist/commands/extension/use.js.map +1 -0
  76. package/dist/commands/record.d.ts +35 -0
  77. package/dist/commands/record.d.ts.map +1 -0
  78. package/dist/commands/record.js +210 -0
  79. package/dist/commands/record.js.map +1 -0
  80. package/dist/commands/skill.d.ts +6 -0
  81. package/dist/commands/skill.d.ts.map +1 -0
  82. package/dist/commands/skill.js +42 -0
  83. package/dist/commands/skill.js.map +1 -0
  84. package/dist/commands/watcherNativeHost.js +48 -1
  85. package/dist/commands/watcherNativeHost.js.map +1 -1
  86. package/dist/extension/dist/background/auth-header-monitor.js +77 -0
  87. package/dist/extension/dist/background/bridge-client.js +120 -0
  88. package/dist/extension/dist/background/cdp-proxy.js +201 -0
  89. package/dist/extension/dist/background/debugger-manager.js +296 -0
  90. package/dist/extension/dist/background/service-worker.js +2004 -0
  91. package/dist/extension/dist/background/service-worker.js.map +7 -0
  92. package/dist/extension/dist/popup/popup.js +730 -0
  93. package/dist/extension/dist/popup/popup.js.map +7 -0
  94. package/dist/extension/dist/popup/tabMarkup.js +104 -0
  95. package/dist/extension/dist/popup/viewState.js +60 -0
  96. package/dist/extension/dist/types/messages.js +5 -0
  97. package/dist/extension/icons/icon-128.png +0 -0
  98. package/dist/extension/icons/icon-16.png +0 -0
  99. package/dist/extension/icons/icon-48.png +0 -0
  100. package/dist/extension/manifest.json +26 -0
  101. package/dist/extension/src/popup/popup.html +756 -0
  102. package/dist/skill/argus/SKILL.md +202 -0
  103. package/dist/skill/argus/reference/CAPTURE.md +77 -0
  104. package/dist/skill/argus/reference/DIALOG.md +31 -0
  105. package/dist/skill/argus/reference/EVAL.md +137 -0
  106. package/dist/skill/argus/reference/EXTENSION.md +43 -0
  107. package/dist/skill/argus/reference/EXTENSION_IFRAME_EVAL.md +58 -0
  108. package/dist/skill/argus/reference/IFRAMES.md +46 -0
  109. package/dist/skill/argus/reference/INJECT.md +145 -0
  110. package/dist/skill/argus/reference/INSPECT.md +175 -0
  111. package/dist/skill/argus/reference/NET.md +108 -0
  112. package/dist/skill/argus/reference/PLUGINS.md +172 -0
  113. package/dist/skill/argus/reference/RUNTIME_CODE.md +42 -0
  114. package/dist/skill/argus/reference/START.md +120 -0
  115. package/dist/skill/argus/start-watcher.ts +108 -0
  116. package/package.json +2 -2
@@ -0,0 +1,730 @@
1
+ // src/popup/classify-target.ts
2
+ var LOW_INTEREST_URL_RULES = [
3
+ { host: "accounts.google.com", pathPrefix: "/RotateCookiesPage" },
4
+ { host: "clients6.google.com", pathPrefix: "/static/proxy.html" },
5
+ { host: "contacts.google.com", pathPrefix: "/u/2/widget/hovercard/" },
6
+ { host: "docs.google.com", pathPrefix: "/_/og/bscframe" },
7
+ { host: "docs.google.com", pathPrefix: "/offline/iframeapi" },
8
+ { host: "doubleclick.net" },
9
+ { host: "googlesyndication.com" },
10
+ { host: "google.com", pathPrefix: "/recaptcha" },
11
+ { host: "googletagmanager.com" },
12
+ { host: "google-analytics.com" },
13
+ { host: "googleadservices.com" },
14
+ { host: "connect.facebook.net" },
15
+ { host: "facebook.com", pathPrefix: "/tr" },
16
+ { host: "ozone-project.com" },
17
+ { host: "amazon-adsystem.com" },
18
+ { host: "adnxs.com" },
19
+ { host: "adsrvr.org" },
20
+ { host: "criteo.com" },
21
+ { host: "inmobi.com" },
22
+ { host: "rubiconproject.com" },
23
+ { host: "pubmatic.com" }
24
+ ];
25
+ var LOW_INTEREST_TITLE_PATTERNS = [
26
+ /^__\w+__$/,
27
+ // __pb_locator__, __tcfapiLocator
28
+ /^googlefc/i
29
+ // googlefcPresent, googlefcInactive, googlefcLoaded
30
+ ];
31
+ function isLowInterestTarget(target) {
32
+ if (target.type === "page") return false;
33
+ const url = target.url;
34
+ if (!url || url === "about:blank" || url === "about:srcdoc") return true;
35
+ if (matchesLowInterestUrl(url)) return true;
36
+ const title = target.title;
37
+ if (title && LOW_INTEREST_TITLE_PATTERNS.some((pattern) => pattern.test(title))) return true;
38
+ return false;
39
+ }
40
+ function matchesLowInterestUrl(url) {
41
+ try {
42
+ const parsed = new URL(url);
43
+ return LOW_INTEREST_URL_RULES.some((rule) => matchesUrlRule(parsed, rule));
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ function matchesUrlRule(url, rule) {
49
+ if (!matchesHost(url.hostname, rule.host)) return false;
50
+ if (!rule.pathPrefix) return true;
51
+ return url.pathname.startsWith(rule.pathPrefix);
52
+ }
53
+ function matchesHost(hostname, host) {
54
+ return hostname === host || hostname.endsWith(`.${host}`);
55
+ }
56
+
57
+ // src/popup/target-list.ts
58
+ var HIDE_TARGET_ICON = `
59
+ <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
60
+ <path d="M10.7 5.1A10.7 10.7 0 0 1 12 5c5 0 8.5 4.5 9.5 6a13.4 13.4 0 0 1-2.7 3.2"></path>
61
+ <path d="M6.6 6.7A13.4 13.4 0 0 0 2.5 11c1 1.5 4.5 6 9.5 6a8.7 8.7 0 0 0 4.2-1.1"></path>
62
+ <path d="M14.1 14.1a3 3 0 0 1-4.2-4.2"></path>
63
+ <path d="M3 3l18 18"></path>
64
+ </svg>
65
+ `;
66
+ var SHOW_TARGET_ICON = `
67
+ <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
68
+ <path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6z"></path>
69
+ <circle cx="12" cy="12" r="3"></circle>
70
+ </svg>
71
+ `;
72
+ function renderTargetList(tab) {
73
+ const targets = tab.targets ?? [];
74
+ const hiddenTargets = tab.hiddenTargets ?? [];
75
+ if (targets.length <= 1 && hiddenTargets.length === 0) return "";
76
+ const primaryTargets = [];
77
+ const autoHiddenTargets = [];
78
+ for (const target of targets) {
79
+ if (isLowInterestTarget(target)) {
80
+ autoHiddenTargets.push(target);
81
+ } else {
82
+ primaryTargets.push(target);
83
+ }
84
+ }
85
+ const primaryHtml = renderTargetItems(tab, primaryTargets, "visible");
86
+ const hiddenFramesHtml = renderHiddenFramesSection(tab, autoHiddenTargets, hiddenTargets);
87
+ return `
88
+ <div class="target-list">
89
+ ${primaryHtml}
90
+ ${hiddenFramesHtml}
91
+ </div>
92
+ `;
93
+ }
94
+ function renderHiddenFramesSection(tab, autoHiddenTargets, manuallyHiddenTargets) {
95
+ const count = autoHiddenTargets.length + manuallyHiddenTargets.length;
96
+ if (count === 0) {
97
+ return "";
98
+ }
99
+ const hasSelectedAutoHiddenTarget = autoHiddenTargets.some((target) => isTargetSelected(tab.selectedFrameId, target.frameId));
100
+ const autoHiddenHtml = renderTargetItems(tab, autoHiddenTargets, "visible");
101
+ const manuallyHiddenHtml = renderTargetItems(tab, manuallyHiddenTargets, "hidden");
102
+ const expanded = hasSelectedAutoHiddenTarget ? "open" : "";
103
+ return `
104
+ <details class="target-collapsed target-hidden-list" ${expanded}>
105
+ <summary class="target-collapsed-toggle">
106
+ ${count} hidden frame${count === 1 ? "" : "s"}
107
+ </summary>
108
+ <div class="target-collapsed-list">
109
+ ${autoHiddenHtml}
110
+ ${manuallyHiddenHtml}
111
+ </div>
112
+ </details>
113
+ `;
114
+ }
115
+ function renderTargetItems(tab, targets, visibility) {
116
+ return targets.map((target) => renderTargetItem(tab, target, visibility)).join("");
117
+ }
118
+ function renderTargetItem(tab, target, visibility) {
119
+ const isSelected = isTargetSelected(tab.selectedFrameId, target.frameId);
120
+ const kindLabel = target.type === "page" ? "Page" : "Iframe";
121
+ const title = target.title || kindLabel;
122
+ const url = target.url || "(no url)";
123
+ const visibilityAction = renderTargetVisibilityAction(tab.tabId, target, visibility);
124
+ return `
125
+ <div class="target-item ${isSelected ? "selected" : ""} ${visibility === "hidden" ? "hidden-target" : ""}">
126
+ <button
127
+ class="target-select-action"
128
+ data-action="select-target"
129
+ data-tab-id="${tab.tabId}"
130
+ data-frame-id="${escapeHtml(target.frameId ?? "")}"
131
+ type="button"
132
+ ${visibility === "hidden" ? "disabled" : ""}
133
+ >
134
+ <span class="target-kind">${kindLabel}</span>
135
+ <span class="target-meta">
136
+ <span class="target-title" title="${escapeHtml(title)}">${escapeHtml(title)}</span>
137
+ <span class="target-url" title="${escapeHtml(url)}">${escapeHtml(url)}</span>
138
+ </span>
139
+ </button>
140
+ ${visibilityAction}
141
+ </div>
142
+ `;
143
+ }
144
+ function renderTargetVisibilityAction(tabId, target, visibility) {
145
+ if (target.type !== "iframe" || !target.frameId) {
146
+ return "";
147
+ }
148
+ const action = visibility === "hidden" ? "show-target" : "hide-target";
149
+ const label = visibility === "hidden" ? "Show iframe target" : "Hide iframe target";
150
+ const icon = visibility === "hidden" ? SHOW_TARGET_ICON : HIDE_TARGET_ICON;
151
+ return `
152
+ <button
153
+ class="target-visibility-action"
154
+ data-action="${action}"
155
+ data-tab-id="${tabId}"
156
+ data-frame-id="${escapeHtml(target.frameId)}"
157
+ type="button"
158
+ title="${label}"
159
+ aria-label="${label}"
160
+ >
161
+ ${icon}
162
+ </button>
163
+ `;
164
+ }
165
+ function isTargetSelected(selectedFrameId, targetFrameId) {
166
+ return selectedFrameId !== void 0 && (selectedFrameId ?? null) === (targetFrameId ?? null);
167
+ }
168
+ function escapeHtml(text) {
169
+ const div = document.createElement("div");
170
+ div.textContent = text;
171
+ return div.innerHTML;
172
+ }
173
+
174
+ // src/popup/popup.ts
175
+ var COPY_ICON = `
176
+ <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
177
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect>
178
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path>
179
+ </svg>
180
+ `;
181
+ var CHECK_ICON = `
182
+ <svg viewBox="0 0 16 16" fill="none" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
183
+ <path d="M3 8.5l3.1 3.1L13 4.75"></path>
184
+ </svg>
185
+ `;
186
+ var DETACH_ICON = `
187
+ <svg viewBox="0 0 16 16" fill="none" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
188
+ <path d="M5.4 1.75h5.2l3.65 3.65v5.2l-3.65 3.65H5.4l-3.65-3.65V5.4z"></path>
189
+ <path d="M6 6l4 4"></path>
190
+ <path d="M10 6l-4 4"></path>
191
+ </svg>
192
+ `;
193
+ var statusIndicator = document.getElementById("statusIndicator");
194
+ var statusText = document.getElementById("statusText");
195
+ var attachedSection = document.getElementById("attachedSection");
196
+ var attachedContent = document.getElementById("attachedContent");
197
+ var availableSection = document.getElementById("availableSection");
198
+ var availableContent = document.getElementById("availableContent");
199
+ var attachedCount = document.getElementById("attachedCount");
200
+ var errorBanner = document.getElementById("errorBanner");
201
+ var healthSummary = document.getElementById("healthSummary");
202
+ var healthNativeHost = document.getElementById("healthNativeHost");
203
+ var healthWatcherReady = document.getElementById("healthWatcherReady");
204
+ var healthWatcherId = document.getElementById("healthWatcherId");
205
+ var healthAttachedCount = document.getElementById("healthAttachedCount");
206
+ var healthSelectedTarget = document.getElementById("healthSelectedTarget");
207
+ var healthLastMessage = document.getElementById("healthLastMessage");
208
+ var healthPid = document.getElementById("healthPid");
209
+ var copyAllButton = document.getElementById("copyAllButton");
210
+ var detachAllButton = document.getElementById("detachAllButton");
211
+ var prevStateHash = "";
212
+ var currentError = null;
213
+ var latestCurrentWatcher = null;
214
+ var latestWatchers = [];
215
+ copyAllButton.addEventListener("click", () => {
216
+ void copyAllWatchersInfo().catch((error) => {
217
+ showError(error instanceof Error ? error.message : "Copy failed");
218
+ });
219
+ });
220
+ detachAllButton.addEventListener("click", () => {
221
+ void detachAllWatchers();
222
+ });
223
+ document.querySelector(".health-summary-actions")?.addEventListener("click", (event) => {
224
+ event.stopPropagation();
225
+ });
226
+ async function sendMessage(message) {
227
+ return new Promise((resolve) => {
228
+ chrome.runtime.sendMessage(message, resolve);
229
+ });
230
+ }
231
+ function setEmptyState(icon, message) {
232
+ availableSection.classList.remove("hidden");
233
+ attachedSection.classList.add("hidden");
234
+ availableContent.innerHTML = `
235
+ <div class="empty-state">
236
+ <div class="icon">${escapeHtml2(icon)}</div>
237
+ <div>${escapeHtml2(message)}</div>
238
+ </div>
239
+ `;
240
+ }
241
+ function updateStatus(connected, tabCount) {
242
+ statusIndicator.classList.toggle("connected", connected);
243
+ statusText.textContent = connected ? "Bridge connected" : "Bridge disconnected";
244
+ attachedCount.textContent = `${tabCount} attached`;
245
+ }
246
+ function showError(message) {
247
+ currentError = message;
248
+ errorBanner.textContent = message ?? "";
249
+ errorBanner.classList.toggle("hidden", !message);
250
+ }
251
+ function updateHealth(status, watcher) {
252
+ const target = watcher?.currentTarget ?? null;
253
+ const tabCount = status?.attachedTabs.length ?? 0;
254
+ const nativeHostConnected = watcher?.nativeHostConnected ?? status?.bridgeConnected ?? false;
255
+ const watcherReady = watcher?.watcherReady ?? false;
256
+ const targetState = watcher?.targetState ?? "not-selected";
257
+ healthSummary.textContent = buildHealthSummaryText({
258
+ nativeHostConnected,
259
+ watcherReady,
260
+ targetState,
261
+ watcherId: watcher?.watcherId ?? null,
262
+ tabCount
263
+ });
264
+ setHealthValueState(healthNativeHost, nativeHostConnected ? "Connected" : "Disconnected", nativeHostConnected ? "connected" : "disconnected");
265
+ setHealthValueState(healthWatcherReady, watcherReady ? "Ready" : "Not ready", watcherReady ? "connected" : "disconnected");
266
+ healthWatcherId.textContent = watcher?.watcherId ?? "-";
267
+ healthAttachedCount.textContent = String(tabCount);
268
+ setHealthValueState(healthSelectedTarget, formatTargetState(target, targetState), getTargetStateClass(targetState));
269
+ healthLastMessage.textContent = formatRelativeTimestamp(watcher?.lastMessageAt ?? null);
270
+ healthPid.textContent = watcher?.nativeHostPid ? String(watcher.nativeHostPid) : "-";
271
+ const hasWatchers = latestWatchers.length > 0;
272
+ copyAllButton.disabled = !hasWatchers;
273
+ detachAllButton.disabled = !hasWatchers;
274
+ }
275
+ function buildHealthSummaryText(input) {
276
+ const parts = [];
277
+ if (input.watcherId) parts.push(input.watcherId);
278
+ parts.push(input.nativeHostConnected ? "Host connected" : "Host disconnected");
279
+ parts.push(input.watcherReady ? "Watcher ready" : "Watcher not ready");
280
+ if (input.targetState === "rebinding") parts.push("Target rebinding");
281
+ if (input.tabCount > 0) parts.push(`${input.tabCount} tab${input.tabCount === 1 ? "" : "s"}`);
282
+ return parts.join(" \xB7 ");
283
+ }
284
+ function setHealthValueState(element, text, stateClass) {
285
+ element.textContent = text;
286
+ element.classList.toggle("connected", stateClass === "connected");
287
+ element.classList.toggle("disconnected", stateClass === "disconnected");
288
+ element.classList.toggle("warning", stateClass === "warning");
289
+ }
290
+ function formatTargetState(target, state) {
291
+ if (!target || state === "not-selected") {
292
+ return "-";
293
+ }
294
+ const label = target.type === "page" ? "Page" : "Iframe";
295
+ return state === "ready" ? `${label} ready` : `${label} rebinding`;
296
+ }
297
+ function getTargetStateClass(state) {
298
+ if (state === "ready") {
299
+ return "connected";
300
+ }
301
+ if (state === "rebinding") {
302
+ return "warning";
303
+ }
304
+ return null;
305
+ }
306
+ function renderTabs(tabs, currentTabId) {
307
+ if (tabs.length === 0) {
308
+ setEmptyState("\u{1F50D}", "No tabs available");
309
+ return;
310
+ }
311
+ const attachedTabs = tabs.filter((tab) => tab.attached);
312
+ let availableTabs = tabs.filter((tab) => !tab.attached);
313
+ if (currentTabId !== void 0) {
314
+ const currentTabIndex = availableTabs.findIndex((tab) => tab.tabId === currentTabId);
315
+ if (currentTabIndex > 0) {
316
+ const [currentTab] = availableTabs.splice(currentTabIndex, 1);
317
+ availableTabs.unshift(currentTab);
318
+ }
319
+ }
320
+ renderTabGroup(attachedSection, attachedContent, attachedTabs, true);
321
+ renderTabGroup(availableSection, availableContent, availableTabs, false);
322
+ }
323
+ function renderTabItem(tab, showTargets) {
324
+ const favicon = tab.faviconUrl ? `<img class="tab-favicon" src="${escapeHtml2(tab.faviconUrl)}" alt="">` : `<div class="tab-favicon" style="background: #e0e0e0"></div>`;
325
+ const watcherSuffix = tab.attached && tab.watcher?.watcherId ? ` (${tab.watcher.watcherId})` : "";
326
+ const actions = tab.attached ? renderAttachedTabActions(tab.tabId) : renderAttachButton(tab.tabId);
327
+ const fullTitle = (tab.title || "Untitled") + watcherSuffix;
328
+ return `
329
+ <div class="tab-item ${tab.attached ? "attached" : ""}" data-tab-id="${tab.tabId}">
330
+ ${favicon}
331
+ <div class="tab-info">
332
+ <div class="tab-title" title="${escapeHtml2(fullTitle)}">${escapeHtml2(fullTitle)}</div>
333
+ <div class="tab-url" title="${escapeHtml2(tab.url)}">${escapeHtml2(tab.url)}</div>
334
+ </div>
335
+ ${actions}
336
+ </div>
337
+ ${showTargets ? renderTargetList(tab) : ""}
338
+ `;
339
+ }
340
+ function renderTabGroup(section, content, tabs, showTargets) {
341
+ if (tabs.length === 0) {
342
+ section.classList.add("hidden");
343
+ content.innerHTML = "";
344
+ return;
345
+ }
346
+ section.classList.remove("hidden");
347
+ content.innerHTML = `<div class="tab-list">${tabs.map((tab) => renderTabItem(tab, showTargets)).join("")}</div>`;
348
+ bindTabInteractions(content);
349
+ }
350
+ function bindTabInteractions(root) {
351
+ root.querySelectorAll(".tab-action").forEach((button) => {
352
+ button.addEventListener("click", handleTabAction);
353
+ });
354
+ root.querySelectorAll('[data-action="select-target"]').forEach((button) => {
355
+ button.addEventListener("click", handleTargetSelection);
356
+ });
357
+ root.querySelectorAll('[data-action="hide-target"], [data-action="show-target"]').forEach((button) => {
358
+ button.addEventListener("click", handleTargetVisibility);
359
+ });
360
+ root.querySelectorAll(".tab-item").forEach((item) => {
361
+ item.addEventListener("click", handleTabItemClick);
362
+ });
363
+ }
364
+ function renderAttachedTabActions(tabId) {
365
+ return `
366
+ <div class="tab-actions">
367
+ ${renderIconActionButton(tabId, "copy-info", "copy", "Copy watcher info", COPY_ICON)}
368
+ ${renderIconActionButton(tabId, "detach", "detach", "Detach", DETACH_ICON)}
369
+ </div>
370
+ `;
371
+ }
372
+ function renderAttachButton(tabId) {
373
+ return `<button class="tab-action attach" data-tab-id="${tabId}" data-action="attach" type="button">Attach</button>`;
374
+ }
375
+ function renderIconActionButton(tabId, action, variant, label, icon) {
376
+ return `
377
+ <button
378
+ class="tab-action icon-only ${variant}"
379
+ data-tab-id="${tabId}"
380
+ data-action="${action}"
381
+ type="button"
382
+ title="${label}"
383
+ aria-label="${label}"
384
+ >
385
+ <span class="tab-action-icon" aria-hidden="true">${icon}</span>
386
+ </button>
387
+ `;
388
+ }
389
+ async function handleTabItemClick(event) {
390
+ const target = event.target;
391
+ if (target.closest(".tab-action")) {
392
+ return;
393
+ }
394
+ const tabItem = event.currentTarget;
395
+ const tabId = getTabId(tabItem);
396
+ const action = getTabItemAction(tabItem);
397
+ tabItem.style.opacity = "0.6";
398
+ tabItem.style.pointerEvents = "none";
399
+ try {
400
+ await runPopupAction(action, { tabId });
401
+ if (action === "attach") {
402
+ await refreshTabs(true);
403
+ }
404
+ } catch (error) {
405
+ showError(error instanceof Error ? error.message : `${capitalize(action)} failed`);
406
+ tabItem.style.opacity = "1";
407
+ tabItem.style.pointerEvents = "auto";
408
+ }
409
+ }
410
+ function getTabItemAction(tabItem) {
411
+ return tabItem.classList.contains("attached") ? "focusTab" : "attach";
412
+ }
413
+ async function handleTabAction(event) {
414
+ const button = event.target.closest(".tab-action");
415
+ if (!button) {
416
+ return;
417
+ }
418
+ const tabId = getTabId(button);
419
+ const action = button.dataset.action;
420
+ if (action === "copy-info") {
421
+ try {
422
+ await copyWatcherInfo(tabId, button);
423
+ } catch (error) {
424
+ showError(error instanceof Error ? error.message : "Copy failed");
425
+ }
426
+ return;
427
+ }
428
+ const restoreButton = setBusyButtonState(button, action);
429
+ try {
430
+ await runPopupAction(action, { tabId });
431
+ await refreshTabs(true);
432
+ } catch (error) {
433
+ showError(error instanceof Error ? error.message : `${capitalize(action)} failed`);
434
+ restoreButton();
435
+ }
436
+ }
437
+ async function handleTargetSelection(event) {
438
+ const button = event.currentTarget;
439
+ const tabId = getTabId(button);
440
+ const frameId = button.dataset.frameId || null;
441
+ button.disabled = true;
442
+ try {
443
+ await runPopupAction("selectTarget", { tabId, frameId });
444
+ await refreshTabs(true);
445
+ } catch (error) {
446
+ showError(error instanceof Error ? error.message : "Target selection failed");
447
+ button.disabled = false;
448
+ }
449
+ }
450
+ async function handleTargetVisibility(event) {
451
+ event.stopPropagation();
452
+ const button = event.currentTarget;
453
+ const tabId = getTabId(button);
454
+ const frameId = button.dataset.frameId || null;
455
+ const action = button.dataset.action === "show-target" ? "showTarget" : "hideTarget";
456
+ button.disabled = true;
457
+ try {
458
+ await runPopupAction(action, { tabId, frameId });
459
+ await refreshTabs(true);
460
+ } catch (error) {
461
+ showError(error instanceof Error ? error.message : action === "hideTarget" ? "Target hide failed" : "Target restore failed");
462
+ button.disabled = false;
463
+ }
464
+ }
465
+ async function runPopupAction(action, args) {
466
+ const response = await sendMessage({ action, ...args });
467
+ if (!response.success) {
468
+ throw new Error(response.error ?? `${capitalize(action)} failed`);
469
+ }
470
+ showError(null);
471
+ }
472
+ async function refreshTabs(forceRender = false) {
473
+ try {
474
+ const [statusResponse, tabsResponse, activeTab] = await Promise.all([
475
+ sendMessage({ action: "getStatus" }),
476
+ sendMessage({ action: "getTargets" }),
477
+ getCurrentTab()
478
+ ]);
479
+ if (!statusResponse.success) {
480
+ throw new Error(statusResponse.error ?? "Failed to load bridge status");
481
+ }
482
+ const status = statusResponse.status;
483
+ latestWatchers = status?.watchers ?? [];
484
+ latestCurrentWatcher = selectCurrentWatcher(status ?? null, tabsResponse.tabs ?? [], activeTab?.id);
485
+ const stateHash = JSON.stringify({
486
+ status,
487
+ tabs: tabsResponse.tabs,
488
+ currentTabId: activeTab?.id,
489
+ error: currentError ?? tabsResponse.error ?? null
490
+ });
491
+ if (!forceRender && stateHash === prevStateHash) {
492
+ return;
493
+ }
494
+ prevStateHash = stateHash;
495
+ updateStatus(status?.bridgeConnected ?? false, status?.attachedTabs.length ?? 0);
496
+ updateHealth(status, latestCurrentWatcher);
497
+ if (tabsResponse.success && tabsResponse.tabs) {
498
+ renderTabs(tabsResponse.tabs, activeTab?.id);
499
+ showError(currentError);
500
+ return;
501
+ }
502
+ showError(tabsResponse.error ?? "Failed to load tabs");
503
+ setEmptyState("\u26A0\uFE0F", tabsResponse.error ?? "Failed to load tabs");
504
+ } catch (error) {
505
+ const message = error instanceof Error ? error.message : "Failed to refresh popup";
506
+ showError(message);
507
+ updateStatus(false, 0);
508
+ latestWatchers = [];
509
+ latestCurrentWatcher = null;
510
+ updateHealth(void 0, null);
511
+ setEmptyState("\u26A0\uFE0F", message);
512
+ }
513
+ }
514
+ function formatRelativeTimestamp(timestamp) {
515
+ if (!timestamp) {
516
+ return "-";
517
+ }
518
+ const elapsedMs = Math.max(0, Date.now() - timestamp);
519
+ if (elapsedMs < 1e3) {
520
+ return "Just now";
521
+ }
522
+ const elapsedSeconds = Math.floor(elapsedMs / 1e3);
523
+ if (elapsedSeconds < 60) {
524
+ return `${elapsedSeconds}s ago`;
525
+ }
526
+ const elapsedMinutes = Math.floor(elapsedSeconds / 60);
527
+ if (elapsedMinutes < 60) {
528
+ return `${elapsedMinutes}m ago`;
529
+ }
530
+ const elapsedHours = Math.floor(elapsedMinutes / 60);
531
+ return `${elapsedHours}h ago`;
532
+ }
533
+ async function copyWatcherInfo(tabId, button) {
534
+ const watcher = findWatcherByTabId(latestWatchers, tabId);
535
+ const text = buildWatcherInfoText(watcher);
536
+ if (!text) {
537
+ return;
538
+ }
539
+ await copyTextToClipboard(text);
540
+ showError(null);
541
+ restoreButtonFeedback(button, "Copied!", 1500, CHECK_ICON);
542
+ }
543
+ async function copyAllWatchersInfo() {
544
+ const text = buildAllWatchersInfoText(latestWatchers);
545
+ if (!text) {
546
+ return;
547
+ }
548
+ await copyTextToClipboard(text);
549
+ showError(null);
550
+ restoreButtonFeedback(copyAllButton, "Copied!");
551
+ }
552
+ async function copyTextToClipboard(text) {
553
+ try {
554
+ await navigator.clipboard.writeText(text);
555
+ return;
556
+ } catch {
557
+ }
558
+ const textarea = document.createElement("textarea");
559
+ textarea.value = text;
560
+ textarea.setAttribute("readonly", "true");
561
+ textarea.style.position = "fixed";
562
+ textarea.style.opacity = "0";
563
+ textarea.style.pointerEvents = "none";
564
+ document.body.appendChild(textarea);
565
+ textarea.select();
566
+ textarea.setSelectionRange(0, textarea.value.length);
567
+ try {
568
+ const copied = document.execCommand("copy");
569
+ if (!copied) {
570
+ throw new Error("Browser denied clipboard write");
571
+ }
572
+ } finally {
573
+ textarea.remove();
574
+ }
575
+ }
576
+ async function detachAllWatchers() {
577
+ if (latestWatchers.length === 0) {
578
+ return;
579
+ }
580
+ const restoreBusyState = setButtonFeedback(detachAllButton, "Detaching...");
581
+ const failures = await detachWatchers(latestWatchers.map((watcher) => watcher.tabId));
582
+ restoreBusyState();
583
+ await refreshTabs(true);
584
+ if (failures.length > 0) {
585
+ showError(buildDetachAllError(failures.length));
586
+ return;
587
+ }
588
+ showError(null);
589
+ restoreButtonFeedback(detachAllButton, "Detached!");
590
+ }
591
+ async function detachWatchers(tabIds) {
592
+ const failures = [];
593
+ for (const tabId of tabIds) {
594
+ try {
595
+ await runPopupAction("detach", { tabId });
596
+ } catch {
597
+ failures.push(tabId);
598
+ }
599
+ }
600
+ return failures;
601
+ }
602
+ function setBusyButtonState(button, action) {
603
+ const previousMarkup = button.innerHTML;
604
+ const restoreFeedback = setButtonFeedback(button, action === "attach" ? "Attaching..." : "Detaching...");
605
+ if (action === "attach") {
606
+ button.textContent = "Attaching...";
607
+ }
608
+ return () => {
609
+ button.innerHTML = previousMarkup;
610
+ restoreFeedback();
611
+ };
612
+ }
613
+ function restoreButtonFeedback(button, label, timeoutMs = 1500, iconMarkup) {
614
+ const restore = setButtonFeedback(button, label, iconMarkup);
615
+ setTimeout(restore, timeoutMs);
616
+ }
617
+ function setButtonFeedback(button, label, iconMarkup) {
618
+ const previousTitle = button.title;
619
+ const previousLabel = button.getAttribute("aria-label");
620
+ const previousText = getButtonLabel(button);
621
+ const previousIcon = getButtonIconMarkup(button);
622
+ const previousDisabled = button.disabled;
623
+ button.disabled = true;
624
+ button.title = label;
625
+ button.setAttribute("aria-label", label);
626
+ setButtonLabel(button, label);
627
+ if (iconMarkup) {
628
+ setButtonIconMarkup(button, iconMarkup);
629
+ }
630
+ return () => {
631
+ button.disabled = previousDisabled;
632
+ button.title = previousTitle;
633
+ button.setAttribute("aria-label", previousLabel ?? previousTitle);
634
+ setButtonLabel(button, previousText);
635
+ setButtonIconMarkup(button, previousIcon);
636
+ };
637
+ }
638
+ function buildWatcherInfoText(watcher) {
639
+ if (!canCopyWatcherInfo(watcher)) {
640
+ return null;
641
+ }
642
+ const readyWatcher = watcher;
643
+ const target = readyWatcher.currentTarget;
644
+ const attached = target.attachedAt ? new Date(target.attachedAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
645
+ const fields = [
646
+ ["ID", readyWatcher.watcherId],
647
+ ["Host", `${readyWatcher.watcherHost}:${readyWatcher.watcherPort}`],
648
+ ["PID", String(readyWatcher.nativeHostPid)],
649
+ ["Target", target.title || "(no title)"],
650
+ ["URL", target.url || "(no url)"],
651
+ ["Attached", attached]
652
+ ];
653
+ return `Argus Watcher Info
654
+ ${fields.map(([label, value]) => `${label}: ${value}`).join("\n")}`;
655
+ }
656
+ function buildAllWatchersInfoText(watchers) {
657
+ const watcherInfo = watchers.map((watcher) => buildWatcherInfoText(watcher)).filter((text) => Boolean(text));
658
+ if (watcherInfo.length === 0) {
659
+ return null;
660
+ }
661
+ return watcherInfo.join("\n\n");
662
+ }
663
+ function buildDetachAllError(failureCount) {
664
+ return failureCount === 1 ? "Failed to detach 1 watcher" : `Failed to detach ${failureCount} watchers`;
665
+ }
666
+ function capitalize(text) {
667
+ return text.charAt(0).toUpperCase() + text.slice(1);
668
+ }
669
+ function selectCurrentWatcher(status, tabs, activeTabId) {
670
+ if (!status) {
671
+ return null;
672
+ }
673
+ if (activeTabId !== void 0) {
674
+ const activeWatcher = findWatcherByTabId(status.watchers, activeTabId);
675
+ if (activeWatcher) {
676
+ return activeWatcher;
677
+ }
678
+ }
679
+ for (const tab of tabs) {
680
+ if (!tab.attached) {
681
+ continue;
682
+ }
683
+ const watcher = findWatcherByTabId(status.watchers, tab.tabId);
684
+ if (watcher) {
685
+ return watcher;
686
+ }
687
+ }
688
+ return status.watchers[0] ?? null;
689
+ }
690
+ function canCopyWatcherInfo(watcher) {
691
+ return Boolean(watcher?.watcherId && watcher?.watcherHost && watcher?.watcherPort != null && watcher?.nativeHostPid && watcher?.currentTarget);
692
+ }
693
+ function findWatcherByTabId(watchers, tabId) {
694
+ return watchers.find((watcher) => watcher.tabId === tabId) ?? null;
695
+ }
696
+ function getTabId(element) {
697
+ return parseInt(element.dataset.tabId ?? "0", 10);
698
+ }
699
+ function getButtonLabel(button) {
700
+ return button.querySelector("[data-button-label]")?.textContent ?? "";
701
+ }
702
+ function setButtonLabel(button, label) {
703
+ const labelNode = button.querySelector("[data-button-label]");
704
+ if (labelNode) {
705
+ labelNode.textContent = label;
706
+ }
707
+ }
708
+ function getButtonIconMarkup(button) {
709
+ return button.querySelector(".tab-action-icon")?.innerHTML ?? "";
710
+ }
711
+ function setButtonIconMarkup(button, iconMarkup) {
712
+ const iconNode = button.querySelector(".tab-action-icon");
713
+ if (iconNode) {
714
+ iconNode.innerHTML = iconMarkup;
715
+ }
716
+ }
717
+ function escapeHtml2(text) {
718
+ const div = document.createElement("div");
719
+ div.textContent = text;
720
+ return div.innerHTML;
721
+ }
722
+ async function getCurrentTab() {
723
+ const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
724
+ return tabs[0];
725
+ }
726
+ void refreshTabs();
727
+ setInterval(() => {
728
+ void refreshTabs();
729
+ }, 2e3);
730
+ //# sourceMappingURL=popup.js.map