dirpush 0.1.6 → 0.1.8
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/.agents/skills/dirpush/SKILL.md +1 -1
- package/package.json +1 -1
- package/scripts/ab +8 -1
- package/scripts/google-login +1 -1
- package/src/browser.ts +6 -2
- package/src/checks.ts +30 -36
|
@@ -23,7 +23,7 @@ You are a **worker**, not the source of truth. SQLite (via the `dirpush` CLI) ow
|
|
|
23
23
|
Something the human should do that doesn't stop the job (verify an email later, check a listing) → `dirpush todo add --job <job> --message "…" [--url …]`. It shows in the dashboard's Actions tab.
|
|
24
24
|
10. **Hold your lease.** Checkpoints extend it; on long pages call `dirpush work heartbeat <job>` at least every 10 minutes. Stopping cleanly: `dirpush work release <job>`.
|
|
25
25
|
11. **Browser = `dirpush ab <agent-browser command>`.** It drives the agents' own Chrome profile (logged into the human's Google account; Gmail, Drive and account pages blocked), never the human's everyday browser. Any agent that can run shell commands can use it. Never call `agent-browser` directly on that profile, never `network unroute`, never another browser tool or extension. Google sign-in rules in [authentication.md](references/authentication.md) are hard rules.
|
|
26
|
-
Habits: `dirpush ab open <url>`, `dirpush ab snapshot -i` (refs like `@e3`), `dirpush ab fill @e3 "text"`, `dirpush ab select`, `dirpush ab check`, `dirpush ab click`. Uploads: `dirpush ab upload '<input[type=file] selector>' <absolute path>`, never a button ref; sign in before uploading. Every call has a 90 s limit (`AB_TIMEOUT`); exit 142 = it hung: `dirpush ab close
|
|
26
|
+
Habits: `dirpush ab open <url>`, `dirpush ab snapshot -i` (refs like `@e3`), `dirpush ab fill @e3 "text"`, `dirpush ab select`, `dirpush ab check`, `dirpush ab click`. Uploads: `dirpush ab upload '<input[type=file] selector>' <absolute path>`, never a button ref; sign in before uploading. Every call has a 90 s limit (`AB_TIMEOUT`); exit 142 = it hung: close that tab (`dirpush ab tab close`), reopen, continue from your last checkpoint. If every call hangs while one site is open (some pages freeze any automated browser on purpose), close that tab and `work block --type bot_protection` saying the human must submit it in their own browser; never close the whole browser for this.
|
|
27
27
|
12. **Bot checks: wait, don't fight.** Cloudflare / Turnstile usually clears by itself in this Chrome: wait ~10 s and re-snapshot. Never click a CAPTCHA or Turnstile checkbox yourself and never use a solving service. Still there → `work block --type captcha` with the page left open. A plain arithmetic question in a form is part of the form: answer it.
|
|
28
28
|
13. **Close your browser** (`dirpush ab close`) when you stop, unless a job of yours is blocked on the human in that window (CAPTCHA, login) — leave it for them. A throwaway profile (`AB_PROFILE`) loses the filled form when closed: in the block message, list exactly what to re-enter.
|
|
29
29
|
|
package/package.json
CHANGED
package/scripts/ab
CHANGED
|
@@ -19,7 +19,14 @@ AB="agent-browser --headed --session ${AB_SESSION:-default} --profile ${AB_PROFI
|
|
|
19
19
|
# `launch browser start` already runs this profile (Google services blocked by its PAC script): attach to it
|
|
20
20
|
# instead of launching a second Chrome on the same profile.
|
|
21
21
|
PORT="${LAUNCH_BROWSER_PORT:-9222}"
|
|
22
|
-
|
|
22
|
+
up() { curl -s -m 1 "http://127.0.0.1:$PORT/json/version" >/dev/null 2>&1; }
|
|
23
|
+
# The real profile always runs as that dedicated Chrome. Launching it through agent-browser instead relaunches Chrome
|
|
24
|
+
# on every call (losing tabs and filled forms), so start it here when it's down (e.g. after a Google sign-in).
|
|
25
|
+
if [ -z "$AB_PROFILE" ] && ! up && [ "$1" != "close" ]; then
|
|
26
|
+
agent-browser --session "${AB_SESSION:-default}" close >/dev/null 2>&1 || true
|
|
27
|
+
bun "$PKG/src/cli.ts" browser start >/dev/null 2>&1 || { echo "couldn't start the agents' Chrome: run 'dirpush browser start' and read its error" >&2; exit 1; }
|
|
28
|
+
fi
|
|
29
|
+
if [ -z "$AB_PROFILE" ] && up; then
|
|
23
30
|
exec perl -e 'alarm shift; exec @ARGV or die "cannot run $ARGV[0]: $!\n"' "${AB_TIMEOUT:-90}" agent-browser --session "${AB_SESSION:-default}" --cdp "$PORT" "$@"
|
|
24
31
|
fi
|
|
25
32
|
for host in mail.google.com gmail.com www.gmail.com inbox.google.com \
|
package/scripts/google-login
CHANGED
|
@@ -13,5 +13,5 @@ bun "$PKG/src/cli.ts" browser stop >/dev/null 2>&1
|
|
|
13
13
|
# so `dirpush checks` can carry on. A separate --user-data-dir is its own Chrome process; the human's Chrome is untouched.
|
|
14
14
|
for i in 1 2 3 4 5 6 7 8 9 10; do [ -L data/browser-profile/SingletonLock ] || break; sleep 0.5; done
|
|
15
15
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --user-data-dir="$PWD/data/browser-profile" \
|
|
16
|
-
--use-mock-keychain --password-store=basic --no-first-run --no-default-browser-check \
|
|
16
|
+
--use-mock-keychain --password-store=basic --no-first-run --no-default-browser-check --disable-extensions --disable-sync \
|
|
17
17
|
"https://accounts.google.com/ServiceLogin?continue=https%3A%2F%2Fwww.google.com%2F" >/dev/null 2>&1
|
package/src/browser.ts
CHANGED
|
@@ -46,6 +46,9 @@ export async function browserStatus() {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** Flags every Chrome on the agents' profile gets (scripts/google-login repeats them). */
|
|
50
|
+
export const AGENT_CHROME_FLAGS = ["--use-mock-keychain", "--password-store=basic", "--no-first-run", "--no-default-browser-check", "--disable-extensions", "--disable-sync"];
|
|
51
|
+
|
|
49
52
|
export async function startBrowser() {
|
|
50
53
|
const now = await browserStatus();
|
|
51
54
|
if (now.running) return { ...now, started: false };
|
|
@@ -53,8 +56,9 @@ export async function startBrowser() {
|
|
|
53
56
|
const pac = `data:application/x-ns-proxy-autoconfig;base64,${Buffer.from(pacScript()).toString("base64")}`;
|
|
54
57
|
// Plain Chrome, headed, no automation flags: Google sign-in and bot checks see a normal browser. The keychain
|
|
55
58
|
// flags match agent-browser and scripts/google-login: the profile's cookies are encrypted with that key, so
|
|
56
|
-
// without them every saved session would be unreadable.
|
|
57
|
-
|
|
59
|
+
// without them every saved session would be unreadable. No extensions and no sync: signing into Google here turns on
|
|
60
|
+
// Chrome sync, which copies the human's own extensions in (one with debugger access hijacked every agent session).
|
|
61
|
+
Bun.spawn([CHROME, `--user-data-dir=${PROFILE}`, `--remote-debugging-port=${BROWSER_PORT}`, `--proxy-pac-url=${pac}`, ...AGENT_CHROME_FLAGS], {
|
|
58
62
|
stdout: "ignore", stderr: "ignore", stdin: "ignore",
|
|
59
63
|
}).unref();
|
|
60
64
|
for (let i = 0; i < 40; i++) {
|
package/src/checks.ts
CHANGED
|
@@ -42,11 +42,6 @@ async function ask(prompt: string) {
|
|
|
42
42
|
try { return (await rl.question(prompt)).trim(); } finally { rl.close(); }
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const WHAT: Record<string, string> = {
|
|
46
|
-
captcha: "Tick the CAPTCHA, then do whatever the note above asks.",
|
|
47
|
-
bot_protection: "Wait for the “checking your browser” page to pass, or tick its box if it shows one.",
|
|
48
|
-
};
|
|
49
|
-
|
|
50
45
|
export async function checks(db: Db, opts: { product?: string; types: string[] }) {
|
|
51
46
|
if (!process.stdin.isTTY) throw new Error("checks is interactive: run `dirpush checks` yourself in a terminal");
|
|
52
47
|
const blocked = (types: string[]) => db.select().from(jobs)
|
|
@@ -58,36 +53,35 @@ export async function checks(db: Db, opts: { product?: string; types: string[] }
|
|
|
58
53
|
let signedIn = false;
|
|
59
54
|
|
|
60
55
|
const summary = async () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (byYou.length) console.log(`${green("✓")} Submitted by you: ${byYou.join(", ")}`);
|
|
64
|
-
if (skipped.length) console.log(`${dim("·")} Skipped, still waiting: ${skipped.join(", ")}`);
|
|
56
|
+
const parts = [handedBack.length && `${handedBack.length} back to the agents`, byYou.length && `${byYou.length} done by you`, skipped.length && `${skipped.length} skipped`].filter(Boolean);
|
|
57
|
+
console.log(`\n${green("✓")} ${bold("Done.")} ${parts.join(" · ") || "Nothing changed."}`);
|
|
65
58
|
if (handedBack.length) await startAgent(handedBack.length);
|
|
66
59
|
};
|
|
67
60
|
|
|
68
61
|
const googleSignIn = async () => {
|
|
69
62
|
const busy = db.select().from(jobs).where(and(eq(jobs.status, "running"), gt(jobs.leaseExpiresAt, Date.now()))).all();
|
|
70
63
|
if (busy.length) {
|
|
71
|
-
console.log(yellow(`An agent is using the browser
|
|
72
|
-
if ((await key(
|
|
64
|
+
console.log(yellow(` An agent is using the browser (${busy.map((j) => j.subject).join(", ")}); signing in closes it.`));
|
|
65
|
+
if ((await key(` ${cyan("enter")} sign in anyway ${cyan("n")} not now `, "\rn")) === "n") return false;
|
|
73
66
|
}
|
|
74
|
-
console.log(
|
|
75
|
-
console.log(`Sign in, then ${bold("quit that window with ⌘Q")} to come back here.`);
|
|
67
|
+
console.log(dim(" Waiting for you to quit that Chrome window…"));
|
|
76
68
|
script("google-login"); // returns when that Chrome quits
|
|
77
|
-
const ok = (await key(`Signed in? ${cyan("
|
|
69
|
+
const ok = (await key(` Signed in? ${cyan("y")} yes ${cyan("n")} no `, "yn")) === "y";
|
|
78
70
|
if (ok) signedIn = true;
|
|
79
|
-
else console.log(dim("No problem: run `dirpush checks` again whenever you want to retry."));
|
|
80
71
|
return ok;
|
|
81
72
|
};
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
if (!
|
|
85
|
-
console.log(
|
|
74
|
+
const total = queue.length + (google.length ? 1 : 0);
|
|
75
|
+
if (!total) return console.log("Nothing is waiting on you.");
|
|
76
|
+
console.log(`${bold("dirpush checks")} ${dim(`· ${total} to clear · an agent finishes each one after you`)}`);
|
|
77
|
+
const host = (u: string) => u.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, "");
|
|
78
|
+
const item = (n: number, name: string, where: string, todo: string) =>
|
|
79
|
+
console.log(`\n${dim(`${n}/${total}`)} ${bold(name)} ${dim(where)}\n ${todo}`);
|
|
80
|
+
const keys = (pairs: [string, string][]) => ` ${pairs.map(([k, l]) => `${cyan(k)} ${l}`).join(" ")} `;
|
|
86
81
|
|
|
87
82
|
if (google.length) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const k = await key(`${cyan("[enter]")} sign in now ${cyan("[n]")} later ${cyan("[q]")} quit `, "\rnq");
|
|
83
|
+
item(1, "Google sign-in", google.length === 1 ? "1 directory" : `${google.length} directories`, "A Chrome window opens: sign in, then quit it with ⌘Q.");
|
|
84
|
+
const k = await key(keys([["enter", "sign in"], ["n", "later"], ["q", "quit"]]), "\rnq");
|
|
91
85
|
if (k === "q") return await summary();
|
|
92
86
|
if (k === "\r" && (await googleSignIn())) {
|
|
93
87
|
for (const j of google) { J.unblock(db, j.id, "human signed into Google in the agents' Chrome profile"); handedBack.push(j.subject); }
|
|
@@ -97,20 +91,21 @@ export async function checks(db: Db, opts: { product?: string; types: string[] }
|
|
|
97
91
|
for (const [i, job] of queue.entries()) {
|
|
98
92
|
const dir = await loadDirectory(job.subject).then((d) => d.directory).catch(() => null);
|
|
99
93
|
const url = job.blockMessage?.match(/https?:\/\/[^\s)'"”]+/)?.[0]?.replace(/[.,;:]$/, "") ?? dir?.submission_url ?? dir?.website;
|
|
94
|
+
// Sites that break under automation: the human does them in their own browser (the agent's note says what to do).
|
|
95
|
+
const yours = /your own browser/i.test(job.blockMessage ?? "");
|
|
100
96
|
const open = () => {
|
|
101
|
-
if (!url) return
|
|
102
|
-
if (
|
|
103
|
-
else
|
|
97
|
+
if (!url) return;
|
|
98
|
+
if (yours) Bun.spawnSync(["open", url]);
|
|
99
|
+
else if (script("ab", "open", url) === 0) script("show-browser");
|
|
100
|
+
else console.log(yellow(` Couldn't open the agents' Chrome; open ${url} yourself.`));
|
|
104
101
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(dim("Asked to sign in with Google? Press g: Google refuses sign-in in the agents' window."));
|
|
110
|
-
console.log(dim("Check never passes there? Submit it from your own browser instead, then press s."));
|
|
102
|
+
const todo = yours
|
|
103
|
+
? `Do this one in your own browser: ${(job.blockMessage ?? "").split(/your own browser:?/i)[1]?.trim().replace(/,? then press s in dirpush checks\.?$/i, "") ?? "submit rdyrct"}.`
|
|
104
|
+
: "Tick the box in the window that opened. No box? Just press enter.";
|
|
105
|
+
item(i + 1 + (google.length ? 1 : 0), dir?.name ?? job.subject, url ? host(url) : "", todo);
|
|
111
106
|
open();
|
|
112
107
|
for (;;) {
|
|
113
|
-
const k = await key(
|
|
108
|
+
const k = await key(keys(yours ? [["s", "done"], ["n", "skip"], ["q", "quit"]] : [["enter", "done"], ["s", "I submitted it myself"], ["n", "skip"], ["q", "quit"]]), yours ? "sognq" : "\rsognq");
|
|
114
109
|
if (k === "o") { open(); continue; }
|
|
115
110
|
if (k === "g") { if (!signedIn) await googleSignIn(); open(); continue; }
|
|
116
111
|
if (k === "q") return await summary();
|
|
@@ -119,7 +114,7 @@ export async function checks(db: Db, opts: { product?: string; types: string[] }
|
|
|
119
114
|
J.unblock(db, job.id, "human cleared the check in the agents' Chrome; continue in the same browser session");
|
|
120
115
|
handedBack.push(job.subject);
|
|
121
116
|
} else {
|
|
122
|
-
const link = await ask(`Listing
|
|
117
|
+
const link = await ask(` Listing link ${dim("(enter to skip)")}: `);
|
|
123
118
|
J.recordManual(db, job.id, "pending", link || undefined);
|
|
124
119
|
byYou.push(job.subject);
|
|
125
120
|
}
|
|
@@ -140,12 +135,11 @@ async function startAgent(n: number) {
|
|
|
140
135
|
const agent = known.find((a) => caps?.checked_by?.startsWith(a.id)) ?? known[0];
|
|
141
136
|
const prompt = "continue my launch";
|
|
142
137
|
if (!agent || process.platform !== "darwin") {
|
|
143
|
-
console.log(
|
|
138
|
+
console.log(`Next: tell your agent ${bold(`“${prompt}”`)} to finish them.`);
|
|
144
139
|
return;
|
|
145
140
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (k === "n") return console.log(dim(`Whenever you're ready: open your agent and say “${prompt}”.`));
|
|
141
|
+
const k = await key(` ${cyan("enter")} open ${agent.name} to finish them ${cyan("n")} later `, "\rn");
|
|
142
|
+
if (k === "n") return console.log(dim(` Later: tell your agent “${prompt}”.`));
|
|
149
143
|
// A new Terminal window, so the human watches it work and approves what it asks for. Nothing runs unattended.
|
|
150
144
|
const cmd = `cd ${JSON.stringify(process.cwd())} && ${agent.bin} ${JSON.stringify(prompt)}`;
|
|
151
145
|
const ok = Bun.spawnSync(["osascript", "-e", `tell application "Terminal" to do script ${JSON.stringify(cmd)}`, "-e", `tell application "Terminal" to activate`], { stdin: "ignore", stdout: "ignore", stderr: "ignore" }).exitCode === 0;
|