tau-all-agent 0.1.0
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 +45 -0
- package/extensions/answer.ts +601 -0
- package/extensions/branch-term.ts +405 -0
- package/extensions/btw.ts +444 -0
- package/extensions/ghostty.ts +301 -0
- package/extensions/git-diff-stats.ts +277 -0
- package/extensions/git-pr-status.ts +286 -0
- package/extensions/insights.ts +2367 -0
- package/extensions/interlude.ts +144 -0
- package/extensions/loop.ts +529 -0
- package/extensions/memory.ts +1889 -0
- package/extensions/notify.ts +161 -0
- package/extensions/openai-fast.ts +227 -0
- package/extensions/openai-verbosity.ts +223 -0
- package/extensions/review.ts +4347 -0
- package/extensions/sandbox/index.ts +2578 -0
- package/extensions/telegram/README.md +81 -0
- package/extensions/telegram/daemon.mjs +1942 -0
- package/extensions/telegram/index.ts +1073 -0
- package/extensions/telegram/message-text.d.mts +1 -0
- package/extensions/telegram/message-text.mjs +16 -0
- package/extensions/usage/anthropic.ts +198 -0
- package/extensions/usage/github-copilot.ts +204 -0
- package/extensions/usage/google-gemini-cli.ts +232 -0
- package/extensions/usage/index.ts +2310 -0
- package/extensions/usage/minimax.ts +208 -0
- package/extensions/usage/openai-codex.ts +180 -0
- package/extensions/usage/openrouter.ts +168 -0
- package/extensions/usage/providers.ts +26 -0
- package/extensions/usage/shared.ts +156 -0
- package/extensions/usage/types.ts +53 -0
- package/extensions/usage/zai.ts +186 -0
- package/extensions/websearch/README.md +68 -0
- package/extensions/websearch/browser/chromium.ts +330 -0
- package/extensions/websearch/browser/discovery.ts +75 -0
- package/extensions/websearch/browser/firefox.ts +150 -0
- package/extensions/websearch/browser/sqlite.ts +38 -0
- package/extensions/websearch/config.ts +76 -0
- package/extensions/websearch/index.ts +312 -0
- package/extensions/websearch/normalize.ts +155 -0
- package/extensions/websearch/providers/anthropic.pi.ts +139 -0
- package/extensions/websearch/providers/gemini.browser.ts +185 -0
- package/extensions/websearch/providers/gemini.pi.ts +108 -0
- package/extensions/websearch/providers/openai-codex.browser.ts +77 -0
- package/extensions/websearch/providers/openai-codex.pi.ts +49 -0
- package/extensions/websearch/providers/openai-codex.shared.ts +123 -0
- package/extensions/websearch/providers/pi-model.shared.ts +86 -0
- package/extensions/websearch/providers/search-prompt.shared.ts +17 -0
- package/extensions/websearch/providers/shared.ts +76 -0
- package/extensions/websearch/types.ts +52 -0
- package/extensions/worktree.ts +2223 -0
- package/package.json +76 -0
- package/skills/browser-tools/SKILL.md +253 -0
- package/skills/browser-tools/scripts/browser-content.js +100 -0
- package/skills/browser-tools/scripts/browser-cookies.js +33 -0
- package/skills/browser-tools/scripts/browser-dismiss-cookies.js +455 -0
- package/skills/browser-tools/scripts/browser-eval.js +75 -0
- package/skills/browser-tools/scripts/browser-logs-tail.js +89 -0
- package/skills/browser-tools/scripts/browser-nav.js +28 -0
- package/skills/browser-tools/scripts/browser-net-summary.js +115 -0
- package/skills/browser-tools/scripts/browser-pick.js +133 -0
- package/skills/browser-tools/scripts/browser-screenshot.js +18 -0
- package/skills/browser-tools/scripts/browser-start.js +281 -0
- package/skills/browser-tools/scripts/browser-watch.js +321 -0
- package/skills/browser-tools/scripts/utils.js +60 -0
- package/skills/git-clean-history/SKILL.md +45 -0
- package/skills/git-commit/SKILL.md +55 -0
- package/skills/homeassistant-ops/SKILL.md +184 -0
- package/skills/homeassistant-ops/references/api.md +64 -0
- package/skills/homeassistant-ops/references/id_conventions.md +71 -0
- package/skills/homeassistant-ops/references/playbook.md +150 -0
- package/skills/homeassistant-ops/scripts/ha_ops.js +2942 -0
- package/skills/openscad/SKILL.md +149 -0
- package/skills/openscad/examples/parametric_box.scad +92 -0
- package/skills/openscad/examples/phone_stand.scad +95 -0
- package/skills/openscad/scripts/common.sh +49 -0
- package/skills/openscad/scripts/export-stl.sh +56 -0
- package/skills/openscad/scripts/extract-params.sh +147 -0
- package/skills/openscad/scripts/multi-preview.sh +68 -0
- package/skills/openscad/scripts/preview.sh +74 -0
- package/skills/openscad/scripts/render-with-params.sh +91 -0
- package/skills/openscad/scripts/validate.sh +46 -0
- package/skills/oracle/SKILL.md +64 -0
- package/skills/oracle/scripts/oracle +249 -0
- package/skills/oracle/scripts/oracle-bundle +227 -0
- package/skills/sentry/SKILL.md +198 -0
- package/skills/sentry/lib/auth.js +99 -0
- package/skills/sentry/scripts/fetch-event.js +325 -0
- package/skills/sentry/scripts/fetch-issue.js +368 -0
- package/skills/sentry/scripts/list-issues.js +245 -0
- package/skills/sentry/scripts/search-events.js +291 -0
- package/skills/sentry/scripts/search-logs.js +234 -0
- package/skills/update-changelog/SKILL.md +135 -0
- package/skills/web-design/SKILL.md +117 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Summarize network activity from browser-watch JSONL logs.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* browser-net-summary.js # summarize latest log
|
|
8
|
+
* browser-net-summary.js --file <path>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_TMP = process.platform === "win32" ? tmpdir() : "/tmp";
|
|
16
|
+
|
|
17
|
+
const LOG_ROOT =
|
|
18
|
+
process.env.BROWSER_TOOLS_LOG_ROOT || join(DEFAULT_TMP, "agent-browser-tools", "logs");
|
|
19
|
+
|
|
20
|
+
function statSafe(path) {
|
|
21
|
+
try {
|
|
22
|
+
return statSync(path);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findLatestFile() {
|
|
29
|
+
if (!existsSync(LOG_ROOT)) return null;
|
|
30
|
+
|
|
31
|
+
const dirs = readdirSync(LOG_ROOT)
|
|
32
|
+
.filter((name) => /^\d{4}-\d{2}-\d{2}$/.test(name))
|
|
33
|
+
.map((name) => join(LOG_ROOT, name))
|
|
34
|
+
.filter((p) => statSafe(p)?.isDirectory())
|
|
35
|
+
.sort();
|
|
36
|
+
|
|
37
|
+
if (dirs.length === 0) return null;
|
|
38
|
+
|
|
39
|
+
const latestDir = dirs[dirs.length - 1];
|
|
40
|
+
const files = readdirSync(latestDir)
|
|
41
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
42
|
+
.map((name) => join(latestDir, name))
|
|
43
|
+
.map((p) => ({ path: p, mtime: statSafe(p)?.mtimeMs || 0 }))
|
|
44
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
45
|
+
|
|
46
|
+
return files[0]?.path || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const args = process.argv.slice(2);
|
|
50
|
+
let filePath = null;
|
|
51
|
+
const fileIdx = args.indexOf("--file");
|
|
52
|
+
if (fileIdx !== -1) filePath = args[fileIdx + 1];
|
|
53
|
+
if (!filePath) filePath = findLatestFile();
|
|
54
|
+
|
|
55
|
+
if (!filePath) {
|
|
56
|
+
console.error("✗ No log file found");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const statusCounts = new Map();
|
|
61
|
+
const failures = [];
|
|
62
|
+
let totalResponses = 0;
|
|
63
|
+
let totalRequests = 0;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const data = readFileSync(filePath, "utf8");
|
|
67
|
+
const lines = data.split("\n").filter(Boolean);
|
|
68
|
+
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
let entry;
|
|
71
|
+
try {
|
|
72
|
+
entry = JSON.parse(line);
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (entry.type === "network.request") {
|
|
78
|
+
totalRequests += 1;
|
|
79
|
+
} else if (entry.type === "network.response") {
|
|
80
|
+
totalResponses += 1;
|
|
81
|
+
const status = String(entry.status ?? "unknown");
|
|
82
|
+
statusCounts.set(status, (statusCounts.get(status) || 0) + 1);
|
|
83
|
+
} else if (entry.type === "network.failure") {
|
|
84
|
+
failures.push({
|
|
85
|
+
requestId: entry.requestId,
|
|
86
|
+
errorText: entry.errorText,
|
|
87
|
+
url: entry.url,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error("✗ summary failed:", e.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`file: ${filePath}`);
|
|
97
|
+
console.log(`requests: ${totalRequests}`);
|
|
98
|
+
console.log(`responses: ${totalResponses}`);
|
|
99
|
+
|
|
100
|
+
const statuses = Array.from(statusCounts.entries()).sort((a, b) => Number(a[0]) - Number(b[0]));
|
|
101
|
+
for (const [status, count] of statuses) {
|
|
102
|
+
console.log(`status ${status}: ${count}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (failures.length > 0) {
|
|
106
|
+
console.log("failures:");
|
|
107
|
+
for (const failure of failures.slice(0, 10)) {
|
|
108
|
+
console.log(
|
|
109
|
+
`- ${failure.errorText || "unknown"} (${failure.requestId || "?"}) ${failure.url || ""}`.trim(),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (failures.length > 10) {
|
|
113
|
+
console.log(`- ... ${failures.length - 10} more`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { connectBrowser, getActivePage, printResult } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
const message = process.argv.slice(2).join(" ");
|
|
6
|
+
if (!message) {
|
|
7
|
+
console.log("Usage: browser-pick.js 'message'");
|
|
8
|
+
console.log("\nExample:");
|
|
9
|
+
console.log(' browser-pick.js "Click the submit button"');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const browser = await connectBrowser();
|
|
14
|
+
const page = await getActivePage(browser);
|
|
15
|
+
|
|
16
|
+
// Inject pick() helper into current page
|
|
17
|
+
await page.evaluate(() => {
|
|
18
|
+
if (!window.pick) {
|
|
19
|
+
window.pick = async (message) => {
|
|
20
|
+
if (!message) {
|
|
21
|
+
throw new Error("pick() requires a message parameter");
|
|
22
|
+
}
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const selections = [];
|
|
25
|
+
const selectedElements = new Set();
|
|
26
|
+
|
|
27
|
+
const overlay = document.createElement("div");
|
|
28
|
+
overlay.style.cssText =
|
|
29
|
+
"position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;pointer-events:none";
|
|
30
|
+
|
|
31
|
+
const highlight = document.createElement("div");
|
|
32
|
+
highlight.style.cssText =
|
|
33
|
+
"position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);transition:all 0.1s";
|
|
34
|
+
overlay.appendChild(highlight);
|
|
35
|
+
|
|
36
|
+
const banner = document.createElement("div");
|
|
37
|
+
banner.style.cssText =
|
|
38
|
+
"position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1f2937;color:white;padding:12px 24px;border-radius:8px;font:14px sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.3);pointer-events:auto;z-index:2147483647";
|
|
39
|
+
|
|
40
|
+
const updateBanner = () => {
|
|
41
|
+
banner.textContent = `${message} (${selections.length} selected, Cmd/Ctrl+click to add, Enter to finish, ESC to cancel)`;
|
|
42
|
+
};
|
|
43
|
+
updateBanner();
|
|
44
|
+
|
|
45
|
+
document.body.append(banner, overlay);
|
|
46
|
+
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
document.removeEventListener("mousemove", onMove, true);
|
|
49
|
+
document.removeEventListener("click", onClick, true);
|
|
50
|
+
document.removeEventListener("keydown", onKey, true);
|
|
51
|
+
overlay.remove();
|
|
52
|
+
banner.remove();
|
|
53
|
+
selectedElements.forEach((el) => {
|
|
54
|
+
el.style.outline = "";
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const onMove = (e) => {
|
|
59
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
60
|
+
if (!el || overlay.contains(el) || banner.contains(el)) return;
|
|
61
|
+
const r = el.getBoundingClientRect();
|
|
62
|
+
highlight.style.cssText = `position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);top:${r.top}px;left:${r.left}px;width:${r.width}px;height:${r.height}px`;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const buildElementInfo = (el) => {
|
|
66
|
+
const parents = [];
|
|
67
|
+
let current = el.parentElement;
|
|
68
|
+
while (current && current !== document.body) {
|
|
69
|
+
const parentInfo = current.tagName.toLowerCase();
|
|
70
|
+
const id = current.id ? `#${current.id}` : "";
|
|
71
|
+
const cls = current.className
|
|
72
|
+
? `.${current.className.trim().split(/\s+/).join(".")}`
|
|
73
|
+
: "";
|
|
74
|
+
parents.push(parentInfo + id + cls);
|
|
75
|
+
current = current.parentElement;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
tag: el.tagName.toLowerCase(),
|
|
80
|
+
id: el.id || null,
|
|
81
|
+
class: el.className || null,
|
|
82
|
+
text: el.textContent?.trim().slice(0, 200) || null,
|
|
83
|
+
html: el.outerHTML.slice(0, 500),
|
|
84
|
+
parents: parents.join(" > "),
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const onClick = (e) => {
|
|
89
|
+
if (banner.contains(e.target)) return;
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
e.stopPropagation();
|
|
92
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
93
|
+
if (!el || overlay.contains(el) || banner.contains(el)) return;
|
|
94
|
+
|
|
95
|
+
if (e.metaKey || e.ctrlKey) {
|
|
96
|
+
if (!selectedElements.has(el)) {
|
|
97
|
+
selectedElements.add(el);
|
|
98
|
+
el.style.outline = "3px solid #10b981";
|
|
99
|
+
selections.push(buildElementInfo(el));
|
|
100
|
+
updateBanner();
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
cleanup();
|
|
104
|
+
const info = buildElementInfo(el);
|
|
105
|
+
resolve(selections.length > 0 ? selections : info);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const onKey = (e) => {
|
|
110
|
+
if (e.key === "Escape") {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
cleanup();
|
|
113
|
+
resolve(null);
|
|
114
|
+
} else if (e.key === "Enter" && selections.length > 0) {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
cleanup();
|
|
117
|
+
resolve(selections);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
document.addEventListener("mousemove", onMove, true);
|
|
122
|
+
document.addEventListener("click", onClick, true);
|
|
123
|
+
document.addEventListener("keydown", onKey, true);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const result = await page.evaluate((msg) => window.pick(msg), message);
|
|
130
|
+
|
|
131
|
+
printResult(result);
|
|
132
|
+
|
|
133
|
+
await browser.disconnect();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { connectBrowser, getActivePage } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
const browser = await connectBrowser();
|
|
8
|
+
const page = await getActivePage(browser);
|
|
9
|
+
|
|
10
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
11
|
+
const filename = `screenshot-${timestamp}.png`;
|
|
12
|
+
const filepath = join(tmpdir(), filename);
|
|
13
|
+
|
|
14
|
+
await page.screenshot({ path: filepath });
|
|
15
|
+
|
|
16
|
+
console.log(filepath);
|
|
17
|
+
|
|
18
|
+
await browser.disconnect();
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn, execSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import puppeteer from "puppeteer-core";
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
let useProfile = false;
|
|
11
|
+
let startWatch = false;
|
|
12
|
+
let browserChoice = "auto"; // auto|chromium|chrome
|
|
13
|
+
let executableOverride = undefined;
|
|
14
|
+
let profileSrcOverride = undefined;
|
|
15
|
+
|
|
16
|
+
const usage = () => {
|
|
17
|
+
console.log(
|
|
18
|
+
"Usage: browser-start.js [--profile] [--watch] [--browser <chromium|chrome>] [--executable <path>]",
|
|
19
|
+
);
|
|
20
|
+
console.log("\nOptions:");
|
|
21
|
+
console.log(" --profile Copy your browser profile (cookies, logins)");
|
|
22
|
+
console.log(" --watch Start browser-watch.js in the background (JSONL logs)");
|
|
23
|
+
console.log(" --browser <name> Select browser: chromium, chrome (default: auto)");
|
|
24
|
+
console.log(" --executable <path> Explicit browser executable path");
|
|
25
|
+
console.log("\nEnv:");
|
|
26
|
+
console.log(" BROWSER_TOOLS_BROWSER chromium|chrome (overrides auto)");
|
|
27
|
+
console.log(" BROWSER_TOOLS_EXECUTABLE Explicit executable path (overrides auto)");
|
|
28
|
+
console.log(" BROWSER_TOOLS_PROFILE_SRC Profile directory to rsync from (overrides auto)");
|
|
29
|
+
console.log(
|
|
30
|
+
" BROWSER_TOOLS_LOG_ROOT Directory for browser-watch logs (defaults to /tmp/agent-browser-tools/logs)",
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < args.length; i++) {
|
|
35
|
+
const arg = args[i];
|
|
36
|
+
if (arg === "--profile") {
|
|
37
|
+
useProfile = true;
|
|
38
|
+
} else if (arg === "--watch") {
|
|
39
|
+
startWatch = true;
|
|
40
|
+
} else if (arg === "--browser") {
|
|
41
|
+
browserChoice = args[++i] ?? "";
|
|
42
|
+
} else if (arg.startsWith("--browser=")) {
|
|
43
|
+
browserChoice = arg.split("=", 2)[1] ?? "";
|
|
44
|
+
} else if (arg === "--executable") {
|
|
45
|
+
executableOverride = args[++i];
|
|
46
|
+
} else if (arg.startsWith("--executable=")) {
|
|
47
|
+
executableOverride = arg.split("=", 2)[1];
|
|
48
|
+
} else {
|
|
49
|
+
usage();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
browserChoice = process.env.BROWSER_TOOLS_BROWSER || browserChoice;
|
|
55
|
+
executableOverride = process.env.BROWSER_TOOLS_EXECUTABLE || executableOverride;
|
|
56
|
+
profileSrcOverride = process.env.BROWSER_TOOLS_PROFILE_SRC || profileSrcOverride;
|
|
57
|
+
|
|
58
|
+
if (!["auto", "chromium", "chrome"].includes(browserChoice)) {
|
|
59
|
+
usage();
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const SCRAPING_DIR = `${process.env.HOME}/.cache/browser-tools`;
|
|
64
|
+
|
|
65
|
+
const startWatcher = () => {
|
|
66
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
67
|
+
const watcherPath = join(scriptDir, "browser-watch.js");
|
|
68
|
+
spawn(process.execPath, [watcherPath], { detached: true, stdio: "ignore" }).unref();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const connectToRunningBrowser = async () => {
|
|
72
|
+
try {
|
|
73
|
+
return await puppeteer.connect({
|
|
74
|
+
browserURL: "http://127.0.0.1:9222",
|
|
75
|
+
defaultViewport: null,
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const runningBrowser = await connectToRunningBrowser();
|
|
83
|
+
if (runningBrowser) {
|
|
84
|
+
await runningBrowser.disconnect();
|
|
85
|
+
if (startWatch) startWatcher();
|
|
86
|
+
console.log(`✓ Browser already running on :9222${startWatch ? " (watch enabled)" : ""}`);
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const findExecutable = (candidates) => {
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
if (!candidate) continue;
|
|
93
|
+
if (candidate.includes("/")) {
|
|
94
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const found = execSync(`command -v "${candidate}"`, {
|
|
99
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
100
|
+
})
|
|
101
|
+
.toString()
|
|
102
|
+
.trim();
|
|
103
|
+
if (found) return found;
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getDefaultProfileSrc = (kind) =>
|
|
110
|
+
kind === "chrome"
|
|
111
|
+
? process.platform === "darwin"
|
|
112
|
+
? `${process.env.HOME}/Library/Application Support/Google/Chrome/`
|
|
113
|
+
: `${process.env.HOME}/.config/google-chrome/`
|
|
114
|
+
: process.platform === "darwin"
|
|
115
|
+
? `${process.env.HOME}/Library/Application Support/Chromium/`
|
|
116
|
+
: `${process.env.HOME}/.config/chromium/`;
|
|
117
|
+
|
|
118
|
+
const buildBrowserConfig = ({ label, candidates, profileSrc }) => ({
|
|
119
|
+
label,
|
|
120
|
+
candidates,
|
|
121
|
+
executable: findExecutable(candidates),
|
|
122
|
+
profileSrc,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const getBrowserSelection = () => {
|
|
126
|
+
if (executableOverride) {
|
|
127
|
+
const label =
|
|
128
|
+
browserChoice === "chrome" ? "Chrome" : browserChoice === "chromium" ? "Chromium" : "Browser";
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
selected: buildBrowserConfig({
|
|
132
|
+
label,
|
|
133
|
+
candidates: [executableOverride],
|
|
134
|
+
profileSrc:
|
|
135
|
+
profileSrcOverride ||
|
|
136
|
+
(browserChoice === "auto" ? null : getDefaultProfileSrc(browserChoice)),
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const chromium = buildBrowserConfig({
|
|
142
|
+
label: "Chromium",
|
|
143
|
+
candidates:
|
|
144
|
+
process.platform === "darwin"
|
|
145
|
+
? [
|
|
146
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
147
|
+
`${process.env.HOME}/Applications/Chromium.app/Contents/MacOS/Chromium`,
|
|
148
|
+
]
|
|
149
|
+
: ["chromium", "chromium-browser"],
|
|
150
|
+
profileSrc: profileSrcOverride || getDefaultProfileSrc("chromium"),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const chrome = buildBrowserConfig({
|
|
154
|
+
label: "Chrome",
|
|
155
|
+
candidates:
|
|
156
|
+
process.platform === "darwin"
|
|
157
|
+
? [
|
|
158
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
159
|
+
`${process.env.HOME}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
|
|
160
|
+
]
|
|
161
|
+
: ["google-chrome", "google-chrome-stable", "chrome"],
|
|
162
|
+
profileSrc: profileSrcOverride || getDefaultProfileSrc("chrome"),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
selected:
|
|
167
|
+
browserChoice === "chromium"
|
|
168
|
+
? chromium
|
|
169
|
+
: browserChoice === "chrome"
|
|
170
|
+
? chrome
|
|
171
|
+
: chromium.executable
|
|
172
|
+
? chromium
|
|
173
|
+
: chrome,
|
|
174
|
+
all: { chromium, chrome },
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const printMissingBrowserError = ({ selected, all }) => {
|
|
179
|
+
if (executableOverride) {
|
|
180
|
+
console.error(`✗ ${selected.label} executable not found: ${executableOverride}`);
|
|
181
|
+
console.error(" Pass a valid path or command via --executable or BROWSER_TOOLS_EXECUTABLE.");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (browserChoice === "auto" && all) {
|
|
186
|
+
console.error("✗ No Chromium or Chrome executable found.");
|
|
187
|
+
console.error(` Chromium checked: ${all.chromium.candidates.join(", ")}`);
|
|
188
|
+
console.error(` Chrome checked: ${all.chrome.candidates.join(", ")}`);
|
|
189
|
+
console.error(" Install one of them or pass --executable <path>.");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.error(`✗ ${selected.label} executable not found.`);
|
|
194
|
+
console.error(` Checked: ${selected.candidates.join(", ")}`);
|
|
195
|
+
console.error(" Install it, use --browser auto, or pass --executable <path>.");
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const browserSelection = getBrowserSelection();
|
|
199
|
+
const browserConfig = browserSelection.selected;
|
|
200
|
+
if (!browserConfig.executable) {
|
|
201
|
+
printMissingBrowserError(browserSelection);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Setup profile directory
|
|
206
|
+
execSync(`mkdir -p "${SCRAPING_DIR}"`, { stdio: "ignore" });
|
|
207
|
+
|
|
208
|
+
// Remove SingletonLock to allow new instance
|
|
209
|
+
try {
|
|
210
|
+
execSync(
|
|
211
|
+
`rm -f "${SCRAPING_DIR}/SingletonLock" "${SCRAPING_DIR}/SingletonSocket" "${SCRAPING_DIR}/SingletonCookie"`,
|
|
212
|
+
{ stdio: "ignore" },
|
|
213
|
+
);
|
|
214
|
+
} catch {}
|
|
215
|
+
|
|
216
|
+
if (useProfile) {
|
|
217
|
+
if (!browserConfig.profileSrc) {
|
|
218
|
+
console.error("✗ Cannot infer a profile directory for --executable in auto mode.");
|
|
219
|
+
console.error(" Also pass --browser <chromium|chrome> or set BROWSER_TOOLS_PROFILE_SRC.");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log("Syncing profile...");
|
|
224
|
+
try {
|
|
225
|
+
execSync(
|
|
226
|
+
`rsync -a --delete \
|
|
227
|
+
--exclude='SingletonLock' \
|
|
228
|
+
--exclude='SingletonSocket' \
|
|
229
|
+
--exclude='SingletonCookie' \
|
|
230
|
+
--exclude='*/Sessions/*' \
|
|
231
|
+
--exclude='*/Current Session' \
|
|
232
|
+
--exclude='*/Current Tabs' \
|
|
233
|
+
--exclude='*/Last Session' \
|
|
234
|
+
--exclude='*/Last Tabs' \
|
|
235
|
+
"${browserConfig.profileSrc}" "${SCRAPING_DIR}/"`,
|
|
236
|
+
{ stdio: "pipe" },
|
|
237
|
+
);
|
|
238
|
+
} catch {
|
|
239
|
+
console.error(`✗ Failed to sync profile from: ${browserConfig.profileSrc}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Start browser with flags to force new instance
|
|
245
|
+
spawn(
|
|
246
|
+
browserConfig.executable,
|
|
247
|
+
[
|
|
248
|
+
"--remote-debugging-port=9222",
|
|
249
|
+
`--user-data-dir=${SCRAPING_DIR}`,
|
|
250
|
+
"--no-first-run",
|
|
251
|
+
"--no-default-browser-check",
|
|
252
|
+
],
|
|
253
|
+
{ detached: true, stdio: "ignore" },
|
|
254
|
+
).unref();
|
|
255
|
+
|
|
256
|
+
// Wait for the browser to be ready
|
|
257
|
+
let connected = false;
|
|
258
|
+
for (let i = 0; i < 30; i++) {
|
|
259
|
+
try {
|
|
260
|
+
const browser = await puppeteer.connect({
|
|
261
|
+
browserURL: "http://127.0.0.1:9222",
|
|
262
|
+
defaultViewport: null,
|
|
263
|
+
});
|
|
264
|
+
await browser.disconnect();
|
|
265
|
+
connected = true;
|
|
266
|
+
break;
|
|
267
|
+
} catch {
|
|
268
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!connected) {
|
|
273
|
+
console.error("✗ Failed to connect to browser");
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (startWatch) startWatcher();
|
|
278
|
+
|
|
279
|
+
console.log(
|
|
280
|
+
`✓ ${browserConfig.label} started on :9222${useProfile ? " with your profile" : ""}${startWatch ? " (watch enabled)" : ""}`,
|
|
281
|
+
);
|