dirpush 0.1.0 → 0.1.2
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/README.md +2 -1
- package/package.json +1 -1
- package/scripts/ab +2 -2
- package/scripts/google-login +7 -3
- package/src/checks.ts +130 -0
- package/src/cli.ts +5 -0
|
@@ -61,7 +61,7 @@ Workers read `dirpush capabilities` at the start of a session: email verificatio
|
|
|
61
61
|
- **"Set up dirpush"** / **"set up this product"** / **"add a product"**: `dirpush init <product-id>`, run the Setup check above if `dirpush capabilities` says it hasn't been done, then help the user fill `workspace/products/<id>/product.yaml` (and the shared `workspace/identity.yaml`, `permissions.yaml` the first time). Validate with `dirpush product show --product <id>`.
|
|
62
62
|
- **"Find N free directories I haven't submitted to and submit"**: `dirpush directories recommend --free --unsubmitted --limit N --product <id>`, show the list, `dirpush submissions enqueue <dir-id…> --product <id>`, then run the loop.
|
|
63
63
|
- **"Continue my launch"**: the whole routine, no other instructions needed (nothing from a previous conversation is either):
|
|
64
|
-
1. `dirpush actions` — tell the human what's waiting on them.
|
|
64
|
+
1. `dirpush actions` — tell the human what's waiting on them. Google sign-in, CAPTCHAs and bot checks: tell them to run `dirpush checks` in their terminal (it walks them through each one and hands the jobs back); never solve them yourself.
|
|
65
65
|
2. `dirpush submissions check` — marks pending listings live (skip if run in the last day).
|
|
66
66
|
3. Run the loop until `work next` is empty. Jobs still showing another worker with an expired lease are abandoned: `work next` hands them to you; continue from their last step.
|
|
67
67
|
4. Queue empty → `dirpush directories recommend --free --unsubmitted --limit 10 --product <id>`, show the list, enqueue only what the human approves.
|
package/README.md
CHANGED
|
@@ -24,7 +24,8 @@ dirpush init acme # sets up ~/.dirpush and installs the agent ski
|
|
|
24
24
|
| `dirpush directories recommend --free --unsubmitted --product <id>` | Directories that fit and haven't been tried |
|
|
25
25
|
| `dirpush submissions list --product <id>` | Every submission and its outcome |
|
|
26
26
|
| `dirpush submissions check` | Re-check pending listings and mark live ones |
|
|
27
|
-
| `dirpush
|
|
27
|
+
| `dirpush actions` | What needs you: CAPTCHAs, verification emails, questions |
|
|
28
|
+
| `dirpush checks` | Your part, guided: sign into Google, then clear CAPTCHAs and bot checks one by one; agents pick each job back up |
|
|
28
29
|
| `dirpush registry updates [--apply]` | Update your registry from the published one on dirpush.com |
|
|
29
30
|
| `dirpush registry audit` | Measure link type, noindex and bot checks on real listing pages |
|
|
30
31
|
| `dirpush submissions add <id or URL>` | Record a listing you made yourself |
|
package/package.json
CHANGED
package/scripts/ab
CHANGED
|
@@ -20,7 +20,7 @@ AB="agent-browser --headed --session ${AB_SESSION:-default} --profile ${AB_PROFI
|
|
|
20
20
|
# instead of launching a second Chrome on the same profile.
|
|
21
21
|
PORT="${LAUNCH_BROWSER_PORT:-9222}"
|
|
22
22
|
if [ -z "$AB_PROFILE" ] && curl -s -m 1 "http://127.0.0.1:$PORT/json/version" >/dev/null 2>&1; then
|
|
23
|
-
exec perl -e 'alarm shift; exec @ARGV' "${AB_TIMEOUT:-90}" agent-browser --session "${AB_SESSION:-default}" --cdp "$PORT" "$@"
|
|
23
|
+
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
24
|
fi
|
|
25
25
|
for host in mail.google.com gmail.com www.gmail.com inbox.google.com \
|
|
26
26
|
drive.google.com drive.usercontent.google.com docs.google.com sheets.google.com slides.google.com forms.google.com \
|
|
@@ -37,4 +37,4 @@ for path in "b/*/ManageAccount*" "ManageAccount*" "security*" "*/personal-info*"
|
|
|
37
37
|
done
|
|
38
38
|
# Per-call time limit (AB_TIMEOUT seconds, default 90): a hung call (seen on file uploads) must not freeze a worker.
|
|
39
39
|
# Exit 142 = timed out; then run `dirpush ab close` and start over from the last checkpoint.
|
|
40
|
-
exec perl -e 'alarm shift; exec @ARGV' "${AB_TIMEOUT:-90}" $AB "$CHROME" "$@"
|
|
40
|
+
exec perl -e 'alarm shift; exec @ARGV or die "cannot run $ARGV[0]: $!\n"' "${AB_TIMEOUT:-90}" $AB "$CHROME" "$@"
|
package/scripts/google-login
CHANGED
|
@@ -7,7 +7,11 @@ PKG="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
7
7
|
if [ -n "$LAUNCH_HOME" ]; then H="$LAUNCH_HOME"; elif [ -d "$PKG/.git" ]; then H="$PKG"; else H="$HOME/.dirpush"; fi
|
|
8
8
|
mkdir -p "$H/data" && cd "$H"
|
|
9
9
|
agent-browser close >/dev/null 2>&1
|
|
10
|
-
# The agents' Chrome (`
|
|
10
|
+
# The agents' Chrome (`dirpush browser start`) holds the same profile; close it so this window owns it.
|
|
11
11
|
bun "$PKG/src/cli.ts" browser stop >/dev/null 2>&1
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# Wait for the profile lock to go, then run Chrome in the foreground: this returns when the human quits it (⌘Q),
|
|
13
|
+
# so `dirpush checks` can carry on. A separate --user-data-dir is its own Chrome process; the human's Chrome is untouched.
|
|
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
|
+
"/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 \
|
|
17
|
+
"https://accounts.google.com/ServiceLogin?continue=https%3A%2F%2Fwww.google.com%2F" >/dev/null 2>&1
|
package/src/checks.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dirpush checks`: the human's part of a launch, in the terminal. Google sign-in first, in plain Chrome with no
|
|
3
|
+
* automation attached (Google refuses sign-in in a window software is driving); then each CAPTCHA or bot check,
|
|
4
|
+
* opened in the agents' Chrome. Agents never solve these: this hands each job back once the human has.
|
|
5
|
+
*/
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { and, eq, gt, inArray } from "drizzle-orm";
|
|
9
|
+
import { HOME, PKG, type Db } from "./db";
|
|
10
|
+
import { jobs } from "./db/schema";
|
|
11
|
+
import * as J from "./jobs";
|
|
12
|
+
import { loadDirectory } from "./registry";
|
|
13
|
+
|
|
14
|
+
const color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
15
|
+
const paint = (code: string) => (s: string) => (color ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
16
|
+
const bold = paint("1"), dim = paint("2"), green = paint("32"), yellow = paint("33"), cyan = paint("36");
|
|
17
|
+
const env = { ...process.env, LAUNCH_HOME: HOME };
|
|
18
|
+
const script = (name: string, ...args: string[]) =>
|
|
19
|
+
Bun.spawnSync([join(PKG, "scripts", name), ...args], { env, stdin: "ignore", stdout: "ignore", stderr: "ignore" }).exitCode;
|
|
20
|
+
|
|
21
|
+
/** One keypress, no Enter needed. Enter comes back as "\r"; Ctrl-C quits. */
|
|
22
|
+
async function key(prompt: string, keys: string): Promise<string> {
|
|
23
|
+
process.stdout.write(prompt);
|
|
24
|
+
process.stdin.setRawMode(true);
|
|
25
|
+
process.stdin.resume();
|
|
26
|
+
try {
|
|
27
|
+
for (;;) {
|
|
28
|
+
const k = await new Promise<string>((r) => process.stdin.once("data", (b: Buffer) => r(b.toString())));
|
|
29
|
+
if (k === "\x03") { process.stdout.write("\n"); process.exit(130); }
|
|
30
|
+
const ch = k === "\r" || k === "\n" ? "\r" : k.toLowerCase();
|
|
31
|
+
if (keys.includes(ch)) { process.stdout.write(ch === "\r" ? "\n" : `${ch}\n`); return ch; }
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
process.stdin.setRawMode(false);
|
|
35
|
+
process.stdin.pause();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function ask(prompt: string) {
|
|
40
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
41
|
+
try { return (await rl.question(prompt)).trim(); } finally { rl.close(); }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const WHAT: Record<string, string> = {
|
|
45
|
+
captcha: "Tick the CAPTCHA, then do whatever the note above asks.",
|
|
46
|
+
bot_protection: "Wait for the “checking your browser” page to pass, or tick its box if it shows one.",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export async function checks(db: Db, opts: { product?: string; types: string[] }) {
|
|
50
|
+
if (!process.stdin.isTTY) throw new Error("checks is interactive: run `dirpush checks` yourself in a terminal");
|
|
51
|
+
const blocked = (types: string[]) => db.select().from(jobs)
|
|
52
|
+
.where(and(eq(jobs.kind, "submission"), eq(jobs.status, "blocked"), inArray(jobs.blockType, types))).all()
|
|
53
|
+
.filter((j) => !opts.product || j.productId === opts.product);
|
|
54
|
+
const google = blocked(["google_login"]);
|
|
55
|
+
const queue = blocked(opts.types);
|
|
56
|
+
const handedBack: string[] = [], byYou: string[] = [], skipped: string[] = [];
|
|
57
|
+
let signedIn = false;
|
|
58
|
+
|
|
59
|
+
const summary = () => {
|
|
60
|
+
console.log(`\n${bold("Done for now.")}`);
|
|
61
|
+
if (handedBack.length) console.log(`${green("✓")} Back to the agents: ${handedBack.join(", ")}`);
|
|
62
|
+
if (byYou.length) console.log(`${green("✓")} Submitted by you: ${byYou.join(", ")}`);
|
|
63
|
+
if (skipped.length) console.log(`${dim("·")} Skipped, still waiting: ${skipped.join(", ")}`);
|
|
64
|
+
if (handedBack.length) console.log(`\nNext: ask your agent to ${bold("“continue my launch”")}.`);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const googleSignIn = async () => {
|
|
68
|
+
const busy = db.select().from(jobs).where(and(eq(jobs.status, "running"), gt(jobs.leaseExpiresAt, Date.now()))).all();
|
|
69
|
+
if (busy.length) {
|
|
70
|
+
console.log(yellow(`An agent is using the browser right now (${busy.map((j) => j.subject).join(", ")}). Signing in closes that window.`));
|
|
71
|
+
if ((await key(`${cyan("[enter]")} close it and sign in ${cyan("[n]")} not now `, "\rn")) === "n") return false;
|
|
72
|
+
}
|
|
73
|
+
console.log(`A normal Chrome window opens on Google's sign-in page (the agents' own profile, nothing automated).`);
|
|
74
|
+
console.log(`Sign in, then ${bold("quit that window with ⌘Q")} to come back here.`);
|
|
75
|
+
script("google-login"); // returns when that Chrome quits
|
|
76
|
+
const ok = (await key(`Signed in? ${cyan("[y]")} yes ${cyan("[n]")} no `, "yn")) === "y";
|
|
77
|
+
if (ok) signedIn = true;
|
|
78
|
+
else console.log(dim("No problem: run `dirpush checks` again whenever you want to retry."));
|
|
79
|
+
return ok;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
console.log(`${bold("dirpush checks")} ${dim(`${google.length} need Google sign-in · ${queue.length} CAPTCHAs or bot checks`)}`);
|
|
83
|
+
if (!google.length && !queue.length) return console.log("Nothing is waiting on you.");
|
|
84
|
+
console.log(dim("Agents never solve these; you do, and each job goes back to them."));
|
|
85
|
+
|
|
86
|
+
if (google.length) {
|
|
87
|
+
console.log(`\n${bold("1. Sign into Google")} ${dim(google.map((j) => j.subject).join(", "))}`);
|
|
88
|
+
console.log("These directories use “Sign in with Google”. Sign in once and the agents can use it from then on.");
|
|
89
|
+
const k = await key(`${cyan("[enter]")} sign in now ${cyan("[n]")} later ${cyan("[q]")} quit `, "\rnq");
|
|
90
|
+
if (k === "q") return summary();
|
|
91
|
+
if (k === "\r" && (await googleSignIn())) {
|
|
92
|
+
for (const j of google) { J.unblock(db, j.id, "human signed into Google in the agents' Chrome profile"); handedBack.push(j.subject); }
|
|
93
|
+
} else skipped.push(...google.map((j) => j.subject));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const [i, job] of queue.entries()) {
|
|
97
|
+
const dir = await loadDirectory(job.subject).then((d) => d.directory).catch(() => null);
|
|
98
|
+
const url = job.blockMessage?.match(/https?:\/\/[^\s)'"”]+/)?.[0]?.replace(/[.,;:]$/, "") ?? dir?.submission_url ?? dir?.website;
|
|
99
|
+
const open = () => {
|
|
100
|
+
if (!url) return console.log(yellow("No page saved for this one; open the directory yourself."));
|
|
101
|
+
if (script("ab", "open", url) === 0) script("show-browser");
|
|
102
|
+
else console.log(yellow(`Couldn't open the agents' Chrome. Open it yourself: ${url}`));
|
|
103
|
+
};
|
|
104
|
+
console.log(`\n${bold(`${google.length ? i + 2 : i + 1}. ${dir?.name ?? job.subject}`)} ${dim(`${i + 1} of ${queue.length}`)}`);
|
|
105
|
+
if (job.blockMessage) console.log(dim(job.blockMessage));
|
|
106
|
+
if (url) console.log(`${dim("Page:")} ${url}`);
|
|
107
|
+
console.log(WHAT[job.blockType ?? ""] ?? "Do what the note above asks.");
|
|
108
|
+
console.log(dim("Asked to sign in with Google? Press g: Google refuses sign-in in the agents' window."));
|
|
109
|
+
console.log(dim("Check never passes there? Submit it from your own browser instead, then press s."));
|
|
110
|
+
open();
|
|
111
|
+
for (;;) {
|
|
112
|
+
const k = await key(`${cyan("enter")} done ${cyan("s")} I submitted it ${cyan("o")} reopen ${cyan("g")} Google sign-in ${cyan("n")} skip ${cyan("q")} quit › `, "\rsognq");
|
|
113
|
+
if (k === "o") { open(); continue; }
|
|
114
|
+
if (k === "g") { if (!signedIn) await googleSignIn(); open(); continue; }
|
|
115
|
+
if (k === "q") return summary();
|
|
116
|
+
if (k === "n") skipped.push(job.subject);
|
|
117
|
+
else if (k === "\r") {
|
|
118
|
+
J.unblock(db, job.id, "human cleared the check in the agents' Chrome; continue in the same browser session");
|
|
119
|
+
handedBack.push(job.subject);
|
|
120
|
+
} else {
|
|
121
|
+
const link = await ask(`Listing URL, if there is one ${dim("(enter to skip)")}: `);
|
|
122
|
+
J.recordManual(db, job.id, "pending", link || undefined);
|
|
123
|
+
byYou.push(job.subject);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
summary();
|
|
129
|
+
}
|
|
130
|
+
|
package/src/cli.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { auditDirectory, auditDirectoryBrowser } from "./audit";
|
|
|
19
19
|
import { browserStatus, newPassword, startBrowser, stopBrowser, vaultLogin } from "./browser";
|
|
20
20
|
import { accountsFor, backfillAccounts, listAccounts } from "./accounts";
|
|
21
21
|
import { detect, loadCapabilities, saveCapabilities } from "./capabilities";
|
|
22
|
+
import { checks } from "./checks";
|
|
22
23
|
import { ratingDomain, ratingHistory, refreshRatings } from "./ratings";
|
|
23
24
|
import { fetchRegistryFile, registrySource, registryUpdates } from "./registry-updates";
|
|
24
25
|
import { loadIdentity, loadPermissions, loadProduct, permission, productIds, resolveProductId, PRODUCTS, utm, WORKSPACE } from "./workspace";
|
|
@@ -523,6 +524,9 @@ const commands: Record<string, (() => Promise<unknown> | unknown) | undefined> =
|
|
|
523
524
|
.orderBy(desc(productApprovals.id)).get() ?? null;
|
|
524
525
|
},
|
|
525
526
|
"assets capture-complete": () => consumeProductApproval(db, Number(arg(0, "approval-id"))),
|
|
527
|
+
/** The human's part, interactive: Google sign-in, then CAPTCHAs and bot checks one by one (src/checks.ts). */
|
|
528
|
+
"checks": async () => { await checks(db, { product: flags.product, types: (flags.type ?? "captcha,bot_protection").split(",").map((t) => t.trim()) }); process.exit(0); },
|
|
529
|
+
|
|
526
530
|
/** Everything waiting on the human, grouped. --product narrows it. */
|
|
527
531
|
"actions": () => {
|
|
528
532
|
const mine = <T extends { productId: string }>(xs: T[]) => xs.filter((x) => !flags.product || !x.productId || x.productId === flags.product);
|
|
@@ -661,6 +665,7 @@ const HELP: [string, [string, string][]][] = [
|
|
|
661
665
|
["products list", "all products in this workspace"],
|
|
662
666
|
["permission <group.action>", "automatic | confirm | never for an action, e.g. accounts.oauth_google"],
|
|
663
667
|
["actions [--product id]", "everything waiting on the human, grouped"],
|
|
668
|
+
["checks [--type captcha,bot_protection] [--product id]", "for the human: sign into Google, then clear CAPTCHAs and bot checks one by one; each job goes back to the agents"],
|
|
664
669
|
]],
|
|
665
670
|
["Work loop", [
|
|
666
671
|
["work next [--claim]", "next submission job (unknown ones first); --claim leases it to you"],
|