opendevbrowser 0.0.12 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +216 -28
- package/dist/chunk-JVBMT2O5.js +7173 -0
- package/dist/chunk-JVBMT2O5.js.map +1 -0
- package/dist/cli/index.js +2486 -589
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1057 -194
- package/dist/index.js.map +1 -1
- package/dist/opendevbrowser.js +1057 -194
- package/dist/opendevbrowser.js.map +1 -1
- package/extension/dist/annotate-content.css +237 -0
- package/extension/dist/annotate-content.js +934 -0
- package/extension/dist/background.js +1194 -32
- package/extension/dist/logging.js +50 -0
- package/extension/dist/ops/dom-bridge.js +355 -0
- package/extension/dist/ops/ops-runtime.js +1249 -0
- package/extension/dist/ops/ops-session-store.js +189 -0
- package/extension/dist/ops/redaction.js +52 -0
- package/extension/dist/ops/snapshot-builder.js +4 -0
- package/extension/dist/ops/snapshot-shared.js +220 -0
- package/extension/dist/popup.js +370 -25
- package/extension/dist/relay-settings.js +1 -0
- package/extension/dist/services/CDPRouter.js +501 -103
- package/extension/dist/services/ConnectionManager.js +464 -57
- package/extension/dist/services/NativePortManager.js +182 -0
- package/extension/dist/services/RelayClient.js +227 -26
- package/extension/dist/services/TabManager.js +81 -0
- package/extension/dist/services/TargetSessionMap.js +146 -0
- package/extension/dist/services/cdp-router-commands.js +203 -0
- package/extension/dist/services/url-restrictions.js +41 -0
- package/extension/dist/types.js +3 -1
- package/extension/manifest.json +17 -3
- package/extension/popup.html +144 -0
- package/package.json +2 -2
- package/skills/AGENTS.md +34 -62
- package/skills/data-extraction/SKILL.md +95 -103
- package/skills/form-testing/SKILL.md +75 -82
- package/skills/login-automation/SKILL.md +76 -66
- package/skills/opendevbrowser-best-practices/SKILL.md +90 -49
- package/skills/opendevbrowser-continuity-ledger/SKILL.md +57 -23
- package/dist/chunk-WTFSMBVH.js +0 -2815
- package/dist/chunk-WTFSMBVH.js.map +0 -1
- package/extension/dist/popup.jsx +0 -150
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export class TargetSessionMap {
|
|
2
|
+
tabTargets = new Map();
|
|
3
|
+
sessionsById = new Map();
|
|
4
|
+
sessionByTarget = new Map();
|
|
5
|
+
rootWaiters = new Map();
|
|
6
|
+
registerRootTab(tabId, targetInfo, sessionId) {
|
|
7
|
+
const record = { tabId, targetInfo, rootSessionId: sessionId };
|
|
8
|
+
this.tabTargets.set(tabId, record);
|
|
9
|
+
const session = {
|
|
10
|
+
kind: "root",
|
|
11
|
+
sessionId,
|
|
12
|
+
tabId,
|
|
13
|
+
targetId: targetInfo.targetId,
|
|
14
|
+
debuggerSession: { tabId },
|
|
15
|
+
targetInfo
|
|
16
|
+
};
|
|
17
|
+
this.sessionsById.set(sessionId, session);
|
|
18
|
+
this.sessionByTarget.set(targetInfo.targetId, sessionId);
|
|
19
|
+
this.resolveRootWaiters(tabId, session);
|
|
20
|
+
return session;
|
|
21
|
+
}
|
|
22
|
+
registerChildSession(tabId, targetInfo, sessionId) {
|
|
23
|
+
const session = {
|
|
24
|
+
kind: "child",
|
|
25
|
+
sessionId,
|
|
26
|
+
tabId,
|
|
27
|
+
targetId: targetInfo.targetId,
|
|
28
|
+
debuggerSession: { tabId, sessionId },
|
|
29
|
+
targetInfo
|
|
30
|
+
};
|
|
31
|
+
this.sessionsById.set(sessionId, session);
|
|
32
|
+
this.sessionByTarget.set(targetInfo.targetId, sessionId);
|
|
33
|
+
return session;
|
|
34
|
+
}
|
|
35
|
+
getBySessionId(sessionId) {
|
|
36
|
+
return this.sessionsById.get(sessionId) ?? null;
|
|
37
|
+
}
|
|
38
|
+
hasSession(sessionId) {
|
|
39
|
+
return this.sessionsById.has(sessionId);
|
|
40
|
+
}
|
|
41
|
+
getByTargetId(targetId) {
|
|
42
|
+
const sessionId = this.sessionByTarget.get(targetId);
|
|
43
|
+
if (!sessionId) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return this.sessionsById.get(sessionId) ?? null;
|
|
47
|
+
}
|
|
48
|
+
getByTabId(tabId) {
|
|
49
|
+
return this.tabTargets.get(tabId) ?? null;
|
|
50
|
+
}
|
|
51
|
+
async waitForRootSession(tabId, timeoutMs = 2000) {
|
|
52
|
+
const existing = this.getByTabId(tabId);
|
|
53
|
+
if (existing) {
|
|
54
|
+
const session = this.sessionsById.get(existing.rootSessionId);
|
|
55
|
+
if (session) {
|
|
56
|
+
return session;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return await new Promise((resolve, reject) => {
|
|
60
|
+
const timeoutId = setTimeout(() => {
|
|
61
|
+
this.rejectRootWaiter(tabId, timeoutId);
|
|
62
|
+
reject(new Error("Target attach timeout"));
|
|
63
|
+
}, timeoutMs);
|
|
64
|
+
const entry = { resolve, reject, timeoutId };
|
|
65
|
+
const waiters = this.rootWaiters.get(tabId) ?? [];
|
|
66
|
+
waiters.push(entry);
|
|
67
|
+
this.rootWaiters.set(tabId, waiters);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
listTargetInfos() {
|
|
71
|
+
const rootTargets = Array.from(this.tabTargets.values()).map((record) => record.targetInfo);
|
|
72
|
+
const childTargets = Array.from(this.sessionsById.values())
|
|
73
|
+
.filter((session) => session.kind === "child" && session.targetInfo)
|
|
74
|
+
.map((session) => session.targetInfo);
|
|
75
|
+
return [...rootTargets, ...childTargets];
|
|
76
|
+
}
|
|
77
|
+
listTabIds() {
|
|
78
|
+
return Array.from(this.tabTargets.keys());
|
|
79
|
+
}
|
|
80
|
+
listSessionIds() {
|
|
81
|
+
return Array.from(this.sessionsById.keys());
|
|
82
|
+
}
|
|
83
|
+
removeByTabId(tabId) {
|
|
84
|
+
const record = this.tabTargets.get(tabId) ?? null;
|
|
85
|
+
if (!record) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
for (const [sessionId, session] of this.sessionsById.entries()) {
|
|
89
|
+
if (session.tabId === tabId) {
|
|
90
|
+
this.sessionsById.delete(sessionId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const [targetId, sessionId] of this.sessionByTarget.entries()) {
|
|
94
|
+
const session = this.sessionsById.get(sessionId);
|
|
95
|
+
if (!session || session.tabId === tabId) {
|
|
96
|
+
this.sessionByTarget.delete(targetId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
this.tabTargets.delete(tabId);
|
|
100
|
+
return record;
|
|
101
|
+
}
|
|
102
|
+
removeBySessionId(sessionId) {
|
|
103
|
+
const session = this.sessionsById.get(sessionId) ?? null;
|
|
104
|
+
if (!session) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (session.kind === "root") {
|
|
108
|
+
this.removeByTabId(session.tabId);
|
|
109
|
+
return session;
|
|
110
|
+
}
|
|
111
|
+
this.sessionsById.delete(sessionId);
|
|
112
|
+
this.sessionByTarget.delete(session.targetId);
|
|
113
|
+
return session;
|
|
114
|
+
}
|
|
115
|
+
removeByTargetId(targetId) {
|
|
116
|
+
const sessionId = this.sessionByTarget.get(targetId);
|
|
117
|
+
if (!sessionId) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return this.removeBySessionId(sessionId);
|
|
121
|
+
}
|
|
122
|
+
resolveRootWaiters(tabId, session) {
|
|
123
|
+
const waiters = this.rootWaiters.get(tabId);
|
|
124
|
+
if (!waiters || waiters.length === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.rootWaiters.delete(tabId);
|
|
128
|
+
for (const waiter of waiters) {
|
|
129
|
+
clearTimeout(waiter.timeoutId);
|
|
130
|
+
waiter.resolve(session);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
rejectRootWaiter(tabId, timeoutId) {
|
|
134
|
+
const waiters = this.rootWaiters.get(tabId);
|
|
135
|
+
if (!waiters || waiters.length === 0) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const remaining = waiters.filter((waiter) => waiter.timeoutId !== timeoutId);
|
|
139
|
+
if (remaining.length === 0) {
|
|
140
|
+
this.rootWaiters.delete(tabId);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
this.rootWaiters.set(tabId, remaining);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export async function handleSetDiscoverTargets(ctx, commandId, params) {
|
|
2
|
+
const discover = params.discover === true;
|
|
3
|
+
const shouldEmit = discover && !ctx.discoverTargets;
|
|
4
|
+
ctx.setDiscoverTargets(discover);
|
|
5
|
+
if (shouldEmit) {
|
|
6
|
+
for (const targetInfo of ctx.sessions.listTargetInfos()) {
|
|
7
|
+
ctx.emitTargetCreated(targetInfo);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
ctx.respond(commandId, {});
|
|
11
|
+
}
|
|
12
|
+
export async function handleSetAutoAttach(ctx, commandId, params, sessionId) {
|
|
13
|
+
if (params.flatten === false) {
|
|
14
|
+
ctx.respondError(commandId, ctx.flatSessionError, sessionId);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (sessionId) {
|
|
18
|
+
ctx.respond(commandId, {}, sessionId);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const autoAttach = params.autoAttach === true;
|
|
22
|
+
const waitForDebuggerOnStart = params.waitForDebuggerOnStart === true;
|
|
23
|
+
ctx.setAutoAttachOptions({ autoAttach, waitForDebuggerOnStart, flatten: true, filter: params.filter });
|
|
24
|
+
if (autoAttach && !sessionId) {
|
|
25
|
+
ctx.resetRootAttached();
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
for (const debuggee of ctx.debuggees.values()) {
|
|
29
|
+
await ctx.applyAutoAttach(debuggee);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
ctx.respondError(commandId, getErrorMessage(error));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!autoAttach) {
|
|
37
|
+
ctx.emitRootDetached();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
for (const targetInfo of ctx.sessions.listTargetInfos()) {
|
|
41
|
+
ctx.emitRootAttached(targetInfo);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
ctx.respond(commandId, {});
|
|
45
|
+
}
|
|
46
|
+
export async function handleCreateTarget(ctx, commandId, params) {
|
|
47
|
+
const url = typeof params.url === "string" ? params.url : undefined;
|
|
48
|
+
const background = params.background === true;
|
|
49
|
+
let createdTabId = null;
|
|
50
|
+
try {
|
|
51
|
+
const tab = await ctx.tabManager.createTab(url, !background);
|
|
52
|
+
if (typeof tab.id !== "number") {
|
|
53
|
+
throw new Error("Target.createTarget did not yield a tab id");
|
|
54
|
+
}
|
|
55
|
+
createdTabId = tab.id;
|
|
56
|
+
await ctx.tabManager.waitForTabComplete(tab.id);
|
|
57
|
+
await ctx.attach(tab.id);
|
|
58
|
+
await ctx.sessions.waitForRootSession(tab.id);
|
|
59
|
+
await ctx.sendCommand({ tabId: tab.id }, "Target.getTargets", {});
|
|
60
|
+
const targetInfo = await ctx.registerRootTab(tab.id);
|
|
61
|
+
if (ctx.discoverTargets) {
|
|
62
|
+
ctx.emitTargetCreated(targetInfo);
|
|
63
|
+
}
|
|
64
|
+
if (ctx.autoAttachOptions.autoAttach) {
|
|
65
|
+
ctx.emitRootAttached(targetInfo);
|
|
66
|
+
}
|
|
67
|
+
if (!background) {
|
|
68
|
+
ctx.updatePrimaryTab(tab.id);
|
|
69
|
+
}
|
|
70
|
+
ctx.respond(commandId, { targetId: targetInfo.targetId });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (createdTabId !== null) {
|
|
74
|
+
const debuggee = ctx.debuggees.get(createdTabId) ?? null;
|
|
75
|
+
ctx.detachTabState(createdTabId);
|
|
76
|
+
if (debuggee) {
|
|
77
|
+
await ctx.safeDetach(debuggee);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await ctx.tabManager.closeTab(createdTabId);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Best-effort cleanup for partially created targets.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
ctx.respondError(commandId, getErrorMessage(error));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function handleCloseTarget(ctx, commandId, params) {
|
|
90
|
+
const targetId = typeof params.targetId === "string" ? params.targetId : null;
|
|
91
|
+
if (!targetId) {
|
|
92
|
+
ctx.respondError(commandId, "Missing targetId");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const session = ctx.sessions.getByTargetId(targetId);
|
|
96
|
+
if (!session || session.kind !== "root") {
|
|
97
|
+
ctx.respondError(commandId, "Target not found");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const debuggee = ctx.debuggees.get(session.tabId) ?? null;
|
|
102
|
+
ctx.detachTabState(session.tabId);
|
|
103
|
+
if (debuggee) {
|
|
104
|
+
await ctx.safeDetach(debuggee);
|
|
105
|
+
}
|
|
106
|
+
await ctx.tabManager.closeTab(session.tabId);
|
|
107
|
+
ctx.respond(commandId, { success: true });
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
ctx.respondError(commandId, getErrorMessage(error));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function handleActivateTarget(ctx, commandId, params) {
|
|
114
|
+
const targetId = typeof params.targetId === "string" ? params.targetId : null;
|
|
115
|
+
if (!targetId) {
|
|
116
|
+
ctx.respondError(commandId, "Missing targetId");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const session = ctx.sessions.getByTargetId(targetId);
|
|
120
|
+
if (!session || session.kind !== "root") {
|
|
121
|
+
ctx.respondError(commandId, "Target not found");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await ctx.tabManager.activateTab(session.tabId);
|
|
126
|
+
ctx.updatePrimaryTab(session.tabId);
|
|
127
|
+
ctx.respond(commandId, {});
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
ctx.respondError(commandId, getErrorMessage(error));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export async function handleAttachToTarget(ctx, commandId, params, sessionId) {
|
|
134
|
+
const targetId = typeof params.targetId === "string" ? params.targetId : null;
|
|
135
|
+
if (!targetId) {
|
|
136
|
+
ctx.respondError(commandId, "Missing targetId", sessionId);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (params.flatten === false) {
|
|
140
|
+
ctx.respondError(commandId, ctx.flatSessionError, sessionId);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const targetSession = ctx.sessions.getByTargetId(targetId);
|
|
144
|
+
if (targetSession && targetSession.kind === "root") {
|
|
145
|
+
ctx.respond(commandId, { sessionId: targetSession.sessionId }, sessionId);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const session = sessionId ? ctx.sessions.getBySessionId(sessionId) : null;
|
|
149
|
+
if (sessionId && !session) {
|
|
150
|
+
ctx.respondError(commandId, `Unknown sessionId: ${sessionId}`, sessionId);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const debuggee = session?.debuggerSession ?? ctx.getPrimaryDebuggee();
|
|
154
|
+
if (!debuggee) {
|
|
155
|
+
ctx.respondError(commandId, "No tab attached", sessionId);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const result = await ctx.sendCommand(debuggee, "Target.attachToTarget", { targetId, flatten: true });
|
|
160
|
+
const record = isRecord(result) ? result : {};
|
|
161
|
+
const childSessionId = typeof record.sessionId === "string" ? record.sessionId : null;
|
|
162
|
+
if (childSessionId) {
|
|
163
|
+
const targetInfo = {
|
|
164
|
+
targetId,
|
|
165
|
+
type: "page",
|
|
166
|
+
browserContextId: "default"
|
|
167
|
+
};
|
|
168
|
+
ctx.sessions.registerChildSession(debuggee.tabId, targetInfo, childSessionId);
|
|
169
|
+
}
|
|
170
|
+
ctx.respond(commandId, result);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
ctx.respondError(commandId, getErrorMessage(error), sessionId);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export async function handleRoutedCommand(ctx, commandId, method, params, sessionId) {
|
|
177
|
+
const session = sessionId ? ctx.sessions.getBySessionId(sessionId) : null;
|
|
178
|
+
if (sessionId && !session) {
|
|
179
|
+
ctx.respondError(commandId, `Unknown sessionId: ${sessionId}`, sessionId);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const debuggee = session?.debuggerSession ?? ctx.getPrimaryDebuggee();
|
|
183
|
+
if (!debuggee) {
|
|
184
|
+
ctx.respondError(commandId, "No tab attached", sessionId);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const result = await ctx.sendCommand(debuggee, method, params);
|
|
189
|
+
ctx.respond(commandId, result, sessionId);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
ctx.respondError(commandId, getErrorMessage(error), sessionId);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const isRecord = (value) => {
|
|
196
|
+
return typeof value === "object" && value !== null;
|
|
197
|
+
};
|
|
198
|
+
const getErrorMessage = (error) => {
|
|
199
|
+
if (error instanceof Error) {
|
|
200
|
+
return error.message;
|
|
201
|
+
}
|
|
202
|
+
return "Unknown error";
|
|
203
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const RESTRICTED_PROTOCOLS = new Set([
|
|
2
|
+
"chrome:",
|
|
3
|
+
"chrome-extension:",
|
|
4
|
+
"chrome-search:",
|
|
5
|
+
"chrome-untrusted:",
|
|
6
|
+
"devtools:",
|
|
7
|
+
"chrome-devtools:",
|
|
8
|
+
"edge:",
|
|
9
|
+
"brave:"
|
|
10
|
+
]);
|
|
11
|
+
const isWebStoreUrl = (url) => {
|
|
12
|
+
if (url.hostname === "chromewebstore.google.com") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
export const getRestrictionMessage = (url) => {
|
|
21
|
+
if (RESTRICTED_PROTOCOLS.has(url.protocol)) {
|
|
22
|
+
return "Active tab uses a restricted URL scheme. Focus a normal http(s) tab and retry.";
|
|
23
|
+
}
|
|
24
|
+
if (isWebStoreUrl(url)) {
|
|
25
|
+
return "Chrome Web Store tabs cannot be debugged. Open a normal tab and retry.";
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
export const isRestrictedUrl = (rawUrl) => {
|
|
30
|
+
try {
|
|
31
|
+
const url = new URL(rawUrl);
|
|
32
|
+
const message = getRestrictionMessage(url);
|
|
33
|
+
if (message) {
|
|
34
|
+
return { restricted: true, message };
|
|
35
|
+
}
|
|
36
|
+
return { restricted: false };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { restricted: true, message: "Unable to parse tab URL." };
|
|
40
|
+
}
|
|
41
|
+
};
|
package/extension/dist/types.js
CHANGED
package/extension/manifest.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "OpenDevBrowser Relay",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.15",
|
|
5
5
|
"description": "Optional bridge to reuse existing Chrome tabs with OpenDevBrowser.",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"debugger",
|
|
8
|
+
"alarms",
|
|
8
9
|
"tabs",
|
|
9
|
-
"storage"
|
|
10
|
+
"storage",
|
|
11
|
+
"scripting",
|
|
12
|
+
"activeTab",
|
|
13
|
+
"nativeMessaging"
|
|
10
14
|
],
|
|
11
15
|
"host_permissions": [
|
|
12
16
|
"http://127.0.0.1/*",
|
|
13
|
-
"http://localhost/*"
|
|
17
|
+
"http://localhost/*",
|
|
18
|
+
"<all_urls>"
|
|
14
19
|
],
|
|
15
20
|
"icons": {
|
|
16
21
|
"16": "icons/icon16.png",
|
|
@@ -30,5 +35,14 @@
|
|
|
30
35
|
"background": {
|
|
31
36
|
"service_worker": "dist/background.js",
|
|
32
37
|
"type": "module"
|
|
38
|
+
},
|
|
39
|
+
"commands": {
|
|
40
|
+
"toggle-annotation": {
|
|
41
|
+
"suggested_key": {
|
|
42
|
+
"default": "Ctrl+Shift+P",
|
|
43
|
+
"mac": "Command+Shift+P"
|
|
44
|
+
},
|
|
45
|
+
"description": "Toggle annotation UI"
|
|
46
|
+
}
|
|
33
47
|
}
|
|
34
48
|
}
|
package/extension/popup.html
CHANGED
|
@@ -285,12 +285,97 @@
|
|
|
285
285
|
transform: translateY(0);
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
.secondary {
|
|
289
|
+
width: 100%;
|
|
290
|
+
padding: 9px 10px;
|
|
291
|
+
border-radius: 12px;
|
|
292
|
+
border: 1px solid var(--stroke);
|
|
293
|
+
background: var(--panel-strong);
|
|
294
|
+
color: var(--text);
|
|
295
|
+
font-size: 11px;
|
|
296
|
+
font-weight: 600;
|
|
297
|
+
letter-spacing: 0.02em;
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.secondary:hover {
|
|
303
|
+
transform: translateY(-1px);
|
|
304
|
+
border-color: rgba(32, 213, 198, 0.4);
|
|
305
|
+
box-shadow: 0 10px 22px rgba(32, 213, 198, 0.12);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.secondary:active {
|
|
309
|
+
transform: translateY(0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.secondary:disabled {
|
|
313
|
+
opacity: 0.6;
|
|
314
|
+
cursor: not-allowed;
|
|
315
|
+
box-shadow: none;
|
|
316
|
+
transform: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
288
319
|
.field-note {
|
|
289
320
|
font-size: 10px;
|
|
290
321
|
color: var(--muted);
|
|
291
322
|
margin-top: 6px;
|
|
292
323
|
}
|
|
293
324
|
|
|
325
|
+
.panel-title {
|
|
326
|
+
font-size: 10px;
|
|
327
|
+
text-transform: uppercase;
|
|
328
|
+
letter-spacing: 0.24em;
|
|
329
|
+
color: var(--muted);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.health-grid {
|
|
333
|
+
display: grid;
|
|
334
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
335
|
+
gap: 8px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.health-item {
|
|
339
|
+
padding: 8px 10px;
|
|
340
|
+
border-radius: 10px;
|
|
341
|
+
border: 1px solid var(--stroke);
|
|
342
|
+
background: var(--panel-strong);
|
|
343
|
+
display: flex;
|
|
344
|
+
flex-direction: column;
|
|
345
|
+
gap: 4px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.health-label {
|
|
349
|
+
font-size: 9px;
|
|
350
|
+
text-transform: uppercase;
|
|
351
|
+
letter-spacing: 0.2em;
|
|
352
|
+
color: var(--muted);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.health-value {
|
|
356
|
+
font-size: 12px;
|
|
357
|
+
font-weight: 600;
|
|
358
|
+
color: var(--text);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.health-value[data-tone="ok"] {
|
|
362
|
+
color: var(--accent);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.health-value[data-tone="warn"] {
|
|
366
|
+
color: #f6c56f;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.health-value[data-tone="off"] {
|
|
370
|
+
color: var(--muted);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.button-row {
|
|
374
|
+
display: grid;
|
|
375
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
376
|
+
gap: 10px;
|
|
377
|
+
}
|
|
378
|
+
|
|
294
379
|
@media (prefers-reduced-motion: reduce) {
|
|
295
380
|
* {
|
|
296
381
|
transition: none !important;
|
|
@@ -332,6 +417,17 @@
|
|
|
332
417
|
</label>
|
|
333
418
|
</div>
|
|
334
419
|
|
|
420
|
+
<div class="row">
|
|
421
|
+
<div>
|
|
422
|
+
<div class="row-title">Native fallback (experimental)</div>
|
|
423
|
+
<div class="row-subtitle">Use native messaging host when relay is down</div>
|
|
424
|
+
</div>
|
|
425
|
+
<label class="switch">
|
|
426
|
+
<input id="nativeEnabled" type="checkbox" />
|
|
427
|
+
<span class="slider"></span>
|
|
428
|
+
</label>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
335
431
|
<div class="row">
|
|
336
432
|
<div>
|
|
337
433
|
<div class="row-title">Auto-pair</div>
|
|
@@ -361,6 +457,54 @@
|
|
|
361
457
|
</div>
|
|
362
458
|
</section>
|
|
363
459
|
|
|
460
|
+
<section class="panel">
|
|
461
|
+
<div class="panel-title">Diagnostics</div>
|
|
462
|
+
<div class="health-grid">
|
|
463
|
+
<div class="health-item">
|
|
464
|
+
<div class="health-label">Relay</div>
|
|
465
|
+
<div id="healthRelay" class="health-value" data-tone="off">Unknown</div>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="health-item">
|
|
468
|
+
<div class="health-label">Handshake</div>
|
|
469
|
+
<div id="healthHandshake" class="health-value" data-tone="off">Unknown</div>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="health-item">
|
|
472
|
+
<div class="health-label">Annotate</div>
|
|
473
|
+
<div id="healthAnnotation" class="health-value" data-tone="off">Unknown</div>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="health-item">
|
|
476
|
+
<div class="health-label">Injected</div>
|
|
477
|
+
<div id="healthInjected" class="health-value" data-tone="off">Unknown</div>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="health-item">
|
|
480
|
+
<div class="health-label">CDP</div>
|
|
481
|
+
<div id="healthCdp" class="health-value" data-tone="off">Unknown</div>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="health-item">
|
|
484
|
+
<div class="health-label">Pairing</div>
|
|
485
|
+
<div id="healthPairing" class="health-value" data-tone="off">Unknown</div>
|
|
486
|
+
</div>
|
|
487
|
+
<div class="health-item">
|
|
488
|
+
<div class="health-label">Native</div>
|
|
489
|
+
<div id="healthNative" class="health-value" data-tone="off">Unknown</div>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
<div id="healthNote" class="field-note">Health check pending.</div>
|
|
493
|
+
</section>
|
|
494
|
+
|
|
495
|
+
<section class="panel">
|
|
496
|
+
<div class="panel-title">Annotation</div>
|
|
497
|
+
<div class="field">
|
|
498
|
+
<label for="annotationContext">Request</label>
|
|
499
|
+
<input id="annotationContext" type="text" placeholder="Optional context for this review" />
|
|
500
|
+
<div id="annotationNote" class="field-note">No annotations captured yet.</div>
|
|
501
|
+
</div>
|
|
502
|
+
<div class="button-row">
|
|
503
|
+
<button id="annotationStart" class="secondary">Annotate</button>
|
|
504
|
+
<button id="annotationCopy" class="secondary" disabled>Copy payload</button>
|
|
505
|
+
</div>
|
|
506
|
+
</section>
|
|
507
|
+
|
|
364
508
|
<button id="toggle" class="primary">Connect</button>
|
|
365
509
|
</div>
|
|
366
510
|
<script type="module" src="dist/popup.js"></script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opendevbrowser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "OpenCode plugin for browser automation via CDP with snapshot-refs-actions workflow",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"lint": "eslint \"{src,tests}/**/*.ts\"",
|
|
42
42
|
"test": "vitest run --coverage",
|
|
43
43
|
"extension:sync": "node scripts/sync-extension-version.mjs",
|
|
44
|
-
"extension:build": "npm run extension:sync && tsc -p extension/tsconfig.json",
|
|
44
|
+
"extension:build": "npm run extension:sync && tsc -p extension/tsconfig.json && node scripts/copy-extension-assets.mjs",
|
|
45
45
|
"extension:pack": "cd extension && zip -r ../opendevbrowser-extension.zip manifest.json popup.html dist/ icons/",
|
|
46
46
|
"version:check": "node scripts/verify-versions.mjs",
|
|
47
47
|
"prepack": "npm run version:check && npm run build && npm run extension:build"
|