@tomflow/chatloop-extension 0.0.0-stage → 0.3.28
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/README.md +172 -2
- package/bin/cli.mjs +88 -0
- package/bin/native-host.mjs +29 -0
- package/extension/acceptance.js +389 -0
- package/extension/actions/clock.js +29 -0
- package/extension/actions/common.js +137 -0
- package/extension/actions/continue.js +264 -0
- package/extension/actions/dom.js +25 -0
- package/extension/actions/handoff.js +302 -0
- package/extension/actions/reload.js +20 -0
- package/extension/actions/screenshot.js +69 -0
- package/extension/actions/text.js +25 -0
- package/extension/background.js +946 -0
- package/extension/clock.html +4 -0
- package/extension/content.js +40 -0
- package/extension/continue.html +9 -0
- package/extension/dom.html +17 -0
- package/extension/handoff.html +9 -0
- package/extension/loop/core.js +37 -0
- package/extension/loop/observer.js +440 -0
- package/extension/manifest.json +38 -0
- package/extension/popup.html +26 -0
- package/extension/popup.js +70 -0
- package/extension/reload.html +17 -0
- package/extension/screenshot.html +17 -0
- package/extension/text.html +17 -0
- package/lib/bridge.mjs +104 -0
- package/lib/mcp-tools.json +26 -0
- package/package.json +23 -4
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Screenshot Action: capture only the selected ChatGPT tab; wait for download completion.
|
|
2
|
+
(() => {
|
|
3
|
+
const {show, fail, findTarget, done, requireOwnTab,
|
|
4
|
+
machineBinding, machineTarget, machineResult, machineFailure} = globalThis.ChatLoopActionCommonV1;
|
|
5
|
+
async function saveScreenshot(tab,machine) {
|
|
6
|
+
await chrome.windows.update(tab.windowId, {focused: true});
|
|
7
|
+
await chrome.tabs.update(tab.id, {active: true});
|
|
8
|
+
// captureVisibleTab may otherwise return a stale compositor frame from an
|
|
9
|
+
// unfocused window even when tabs.query already reports the requested tab.
|
|
10
|
+
const [paint] = await chrome.scripting.executeScript({target: {tabId: tab.id},
|
|
11
|
+
func: async () => {
|
|
12
|
+
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
13
|
+
return {visible: document.visibilityState === "visible", url: location.href};
|
|
14
|
+
}});
|
|
15
|
+
if (paint?.result?.visible !== true || paint.result.url !== tab.url)
|
|
16
|
+
throw Error("TARGET_PAINT_UNVERIFIED");
|
|
17
|
+
await new Promise(resolve => setTimeout(resolve, 600));
|
|
18
|
+
const [active] = await chrome.tabs.query({windowId: tab.windowId, active: true});
|
|
19
|
+
if (active?.id !== tab.id) throw Error("TARGET_NOT_VISIBLE");
|
|
20
|
+
const current = await chrome.tabs.get(tab.id);
|
|
21
|
+
if (current.url !== tab.url) throw Error("TARGET_CHANGED_BEFORE_CAPTURE");
|
|
22
|
+
const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {format: "png"});
|
|
23
|
+
if (!dataUrl?.startsWith("data:image/png;base64,")) throw Error("CAPTURE_FAILED");
|
|
24
|
+
if(machine) { await machineResult(machine,dataUrl.slice("data:image/png;base64,".length)); return; }
|
|
25
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
26
|
+
const filename = "ChatLoop/eye/chat-" + tab.id + "-" + stamp + ".png";
|
|
27
|
+
const downloadId = await chrome.downloads.download({
|
|
28
|
+
url: dataUrl, filename, saveAs: false, conflictAction: "uniquify"
|
|
29
|
+
});
|
|
30
|
+
if (!Number.isInteger(downloadId)) throw Error("DOWNLOAD_NOT_STARTED");
|
|
31
|
+
await new Promise((resolve, reject) => {
|
|
32
|
+
let settled = false;
|
|
33
|
+
const timer = setTimeout(() => finish("DOWNLOAD_TIMEOUT"), 30000);
|
|
34
|
+
function finish(error) {
|
|
35
|
+
if (settled) return;
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
chrome.downloads.onChanged.removeListener(onChanged);
|
|
39
|
+
if (error) reject(Error(error));
|
|
40
|
+
else resolve();
|
|
41
|
+
}
|
|
42
|
+
function onChanged(delta) {
|
|
43
|
+
if (delta.id !== downloadId) return;
|
|
44
|
+
if (delta.error?.current) return finish("DOWNLOAD_FAILED: " + delta.error.current);
|
|
45
|
+
if (delta.state?.current === "complete") return finish();
|
|
46
|
+
if (delta.state?.current === "interrupted") return finish("DOWNLOAD_INTERRUPTED");
|
|
47
|
+
}
|
|
48
|
+
chrome.downloads.onChanged.addListener(onChanged);
|
|
49
|
+
void chrome.downloads.search({id: downloadId}).then(items => {
|
|
50
|
+
if (items[0]?.state === "complete") finish();
|
|
51
|
+
if (items[0]?.state === "interrupted") finish("DOWNLOAD_INTERRUPTED");
|
|
52
|
+
}).catch(() => finish("DOWNLOAD_STATUS_UNVERIFIED"));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let machine;
|
|
56
|
+
async function run() {
|
|
57
|
+
machine = await machineBinding("chat_screenshot");
|
|
58
|
+
const own = requireOwnTab(await chrome.tabs.getCurrent());
|
|
59
|
+
const target = machine ? await machineTarget(machine) : await findTarget(own);
|
|
60
|
+
show("RUNNING", "screenshot → ChatGPT tab " + target.id);
|
|
61
|
+
await saveScreenshot(target,machine);
|
|
62
|
+
await done(own, "screenshot", target);
|
|
63
|
+
}
|
|
64
|
+
run().catch(async error => {
|
|
65
|
+
await machineFailure("chat_screenshot",error);
|
|
66
|
+
fail(error.message || "UNEXPECTED_ERROR",
|
|
67
|
+
"操作未完成;动作页已保留,未对其他 ChatGPT 标签页进行操作。");
|
|
68
|
+
});
|
|
69
|
+
})();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// TEXT Action: read one selected ChatGPT tab and copy its content.
|
|
2
|
+
(() => {
|
|
3
|
+
const {show, fail, findTarget, readChat, copyText, done, requireOwnTab,
|
|
4
|
+
machineBinding, machineTarget, machineResult, machineFailure}
|
|
5
|
+
= globalThis.ChatLoopActionCommonV1;
|
|
6
|
+
let machine;
|
|
7
|
+
async function run() {
|
|
8
|
+
machine = await machineBinding("chat_text");
|
|
9
|
+
const own = requireOwnTab(await chrome.tabs.getCurrent());
|
|
10
|
+
const target = machine ? await machineTarget(machine) : await findTarget(own);
|
|
11
|
+
show("RUNNING", "text → ChatGPT tab " + target.id);
|
|
12
|
+
const value = await readChat(target, "text");
|
|
13
|
+
if (!value.length) throw Error("EMPTY_CONTENT");
|
|
14
|
+
if(machine) await machineResult(machine,value);
|
|
15
|
+
else copyText(value);
|
|
16
|
+
// Let Chrome publish selections to macOS pasteboard before closing.
|
|
17
|
+
if(!machine) await new Promise(resolve => setTimeout(resolve, 250));
|
|
18
|
+
await done(own, "text", target);
|
|
19
|
+
}
|
|
20
|
+
run().catch(async error => {
|
|
21
|
+
await machineFailure("chat_text",error);
|
|
22
|
+
fail(error.message || "UNEXPECTED_ERROR",
|
|
23
|
+
"操作未完成;动作页已保留,未对其他 ChatGPT 标签页进行操作。");
|
|
24
|
+
});
|
|
25
|
+
})();
|