demobite 1.9.0 → 1.10.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/launcher/index.mjs +15 -4
- package/package.json +1 -1
- package/skill/scripts/login.mjs +51 -37
- package/skill/scripts/signin.mjs +25 -0
package/launcher/index.mjs
CHANGED
|
@@ -190,9 +190,12 @@ if (arg === "listen") {
|
|
|
190
190
|
const baseFlag = argvRest[argvRest.indexOf("--base") + 1];
|
|
191
191
|
let cfg = readCfg();
|
|
192
192
|
const base = (argvRest.includes("--base") && baseFlag ? baseFlag : cfg?.base || "https://app.demobites.com").replace(/\/+$/, "");
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
// --connect <code>: the line the Agentic Capture chat gave — already approved
|
|
194
|
+
// for the signed-in person; connects this machine without a page or a click.
|
|
195
|
+
const connectCode = argvRest.includes("--connect") ? (argvRest[argvRest.indexOf("--connect") + 1] || "") : "";
|
|
196
|
+
if (connectCode || !cfg?.api_key || (argvRest.includes("--base") && cfg?.base && cfg.base.replace(/\/+$/, "") !== base)) {
|
|
197
|
+
console.log(`\n Connecting this ${connectCode ? "machine" : "project"} to your DemoBites account first…\n`);
|
|
198
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "login.mjs"), base, ...(connectCode ? ["--connect", connectCode] : [])], { stdio: "inherit", cwd: process.cwd() });
|
|
196
199
|
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
197
200
|
cfg = readCfg();
|
|
198
201
|
}
|
|
@@ -207,10 +210,18 @@ if (arg === "listen") {
|
|
|
207
210
|
}
|
|
208
211
|
const name = (argvRest[argvRest.indexOf("--name") + 1] && argvRest.includes("--name")) ? argvRest[argvRest.indexOf("--name") + 1] : os.hostname().replace(/\.local$/, "");
|
|
209
212
|
const agentBin = (() => { const r = spawnSync(process.platform === "win32" ? "where" : "which", ["claude"], { encoding: "utf8" }); return r.status === 0 ? r.stdout.trim().split("\n")[0] : null; })();
|
|
213
|
+
// The repo this listener was started in (the Code signal on the rail): the
|
|
214
|
+
// git remote's owner/name, or null when the folder is not a repo.
|
|
215
|
+
const repo = (() => {
|
|
216
|
+
const r = spawnSync("git", ["config", "--get", "remote.origin.url"], { encoding: "utf8", cwd: process.cwd() });
|
|
217
|
+
if (r.status !== 0) return null;
|
|
218
|
+
const m = r.stdout.trim().match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
|
|
219
|
+
return m ? m[1] : null;
|
|
220
|
+
})();
|
|
210
221
|
const readiness = () => {
|
|
211
222
|
let signins = [];
|
|
212
223
|
try { signins = Object.keys(JSON.parse(fs.readFileSync(path.join(process.cwd(), ".recorder", "signins.json"), "utf8")).origins || {}); } catch { /* none */ }
|
|
213
|
-
return { agent: agentBin ? "Claude Code" : null, chrome: haveChrome, recorder: pkg.version, signins, cwd: process.cwd(), platform: process.platform };
|
|
224
|
+
return { agent: agentBin ? "Claude Code" : null, chrome: haveChrome, recorder: pkg.version, signins, cwd: process.cwd(), platform: process.platform, repo };
|
|
214
225
|
};
|
|
215
226
|
const api = async (body) => {
|
|
216
227
|
try {
|
package/package.json
CHANGED
package/skill/scripts/login.mjs
CHANGED
|
@@ -27,7 +27,8 @@ let cfgParseFailed = false;
|
|
|
27
27
|
try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch (e) {
|
|
28
28
|
cfgParseFailed = fs.existsSync(cfgPath);
|
|
29
29
|
}
|
|
30
|
-
const
|
|
30
|
+
const connectIdx = process.argv.indexOf("--connect");
|
|
31
|
+
const argv = process.argv.slice(2).filter((a, i) => a !== "--logout" && a !== "--connect" && i + 2 !== connectIdx + 1);
|
|
31
32
|
const wantLogout = process.argv.includes("--logout");
|
|
32
33
|
// For LOGOUT the key's own home wins: the key was minted on cfg.base, so a
|
|
33
34
|
// CLI base argument must not point the revoke at a different host (review
|
|
@@ -75,43 +76,56 @@ if (wantLogout) {
|
|
|
75
76
|
|
|
76
77
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
console.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
// --connect <code> (Agentic Capture chat, 2026-10-15): the app already approved
|
|
80
|
+
// this code for the signed-in person, so there is no page to open and nothing
|
|
81
|
+
// to click — poll straight away and the key arrives.
|
|
82
|
+
const connectAt = process.argv.indexOf("--connect");
|
|
83
|
+
const connectCode = connectAt >= 0 ? (process.argv[connectAt + 1] || "").trim() : "";
|
|
84
|
+
let device_code, expires_in, interval;
|
|
85
|
+
if (connectCode) {
|
|
86
|
+
device_code = connectCode; expires_in = 600; interval = 2;
|
|
87
|
+
console.log("Connecting this machine to your DemoBites account…");
|
|
88
|
+
} else {
|
|
89
|
+
let start;
|
|
90
|
+
try {
|
|
91
|
+
start = await fetch(`${base}/api/recorder/device`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: "{}",
|
|
95
|
+
});
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error(`Could not reach ${base}: ${e.message}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if (!start.ok) {
|
|
101
|
+
console.error(`Device link start failed: ${start.status} ${await start.text().catch(() => "")}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const started = await start.json();
|
|
105
|
+
device_code = started.device_code; expires_in = started.expires_in; interval = started.interval;
|
|
106
|
+
const { user_code, verification_url } = started;
|
|
107
|
+
if (!device_code || !user_code || !verification_url) {
|
|
108
|
+
console.error("Device link response missing fields.");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const verifyUrl = new URL(verification_url, base).toString();
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(` Open: ${verifyUrl}`);
|
|
114
|
+
console.log(` Code: ${user_code}`);
|
|
115
|
+
console.log("");
|
|
116
|
+
console.log("Approve the link in your browser. If a sign-in page appears first,");
|
|
117
|
+
console.log("sign in — the approval page follows with the same code.");
|
|
118
|
+
// A real pause before the page appears (founder 2026-10-14: "we'll open a
|
|
119
|
+
// browser now to connect, in 3 seconds"): the human is told, then has three
|
|
120
|
+
// counted seconds, then the page opens. Never silent, never a question.
|
|
121
|
+
process.stdout.write("Opening the approval page in your browser in 3");
|
|
122
|
+
for (const n of [2, 1]) { await new Promise((r) => setTimeout(r, 1000)); process.stdout.write(`… ${n}`); }
|
|
123
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
124
|
+
console.log("… now. Waiting for the approval...");
|
|
125
|
+
if (process.platform === "darwin") spawnSync("open", [verifyUrl], { stdio: "ignore" });
|
|
126
|
+
else if (process.platform === "win32") spawnSync("cmd", ["/c", "start", "", verifyUrl], { stdio: "ignore" });
|
|
127
|
+
else spawnSync("xdg-open", [verifyUrl], { stdio: "ignore" });
|
|
97
128
|
}
|
|
98
|
-
const verifyUrl = new URL(verification_url, base).toString();
|
|
99
|
-
console.log("");
|
|
100
|
-
console.log(` Open: ${verifyUrl}`);
|
|
101
|
-
console.log(` Code: ${user_code}`);
|
|
102
|
-
console.log("");
|
|
103
|
-
console.log("Approve the link in your browser. If a sign-in page appears first,");
|
|
104
|
-
console.log("sign in — the approval page follows with the same code.");
|
|
105
|
-
// A real pause before the page appears (founder 2026-10-14: "we'll open a
|
|
106
|
-
// browser now to connect, in 3 seconds"): the human is told, then has three
|
|
107
|
-
// counted seconds, then the page opens. Never silent, never a question.
|
|
108
|
-
process.stdout.write("Opening the approval page in your browser in 3");
|
|
109
|
-
for (const n of [2, 1]) { await new Promise((r) => setTimeout(r, 1000)); process.stdout.write(`… ${n}`); }
|
|
110
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
111
|
-
console.log("… now. Waiting for the approval...");
|
|
112
|
-
if (process.platform === "darwin") spawnSync("open", [verifyUrl], { stdio: "ignore" });
|
|
113
|
-
else if (process.platform === "win32") spawnSync("cmd", ["/c", "start", "", verifyUrl], { stdio: "ignore" });
|
|
114
|
-
else spawnSync("xdg-open", [verifyUrl], { stdio: "ignore" });
|
|
115
129
|
|
|
116
130
|
const deadline = Date.now() + (expires_in ?? 900) * 1000;
|
|
117
131
|
const waitMs = Math.max(2, interval ?? 5) * 1000;
|
package/skill/scripts/signin.mjs
CHANGED
|
@@ -86,6 +86,31 @@ if (probe) {
|
|
|
86
86
|
process.exit(state === "failed" ? 1 : 0);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// NO WALL? (founder 2026-10-14: "open the page and look"; the first listening
|
|
90
|
+
// run waited four minutes on a public site). Give the page a few seconds; if
|
|
91
|
+
// nothing on it asks for a sign-in — no password field, no sign-in path, no
|
|
92
|
+
// bot check — remember the address as open and leave without a word.
|
|
93
|
+
const wallCheck = async () => {
|
|
94
|
+
try {
|
|
95
|
+
return await page.evaluate(() => {
|
|
96
|
+
const pw = document.querySelector('input[type="password"]');
|
|
97
|
+
const path = location.pathname.toLowerCase();
|
|
98
|
+
const loginPath = /sign-?in|log-?in|login|auth\b|sso|signup|register/.test(path);
|
|
99
|
+
const bot = !!document.querySelector('input[name="cf-turnstile-response"], iframe[src*="challenges.cloudflare"], iframe[src*="recaptcha"], iframe[src*="hcaptcha"]');
|
|
100
|
+
return { wall: !!pw || loginPath || bot };
|
|
101
|
+
});
|
|
102
|
+
} catch { return { wall: true }; }
|
|
103
|
+
};
|
|
104
|
+
await page.waitForTimeout(4000);
|
|
105
|
+
const first = await wallCheck();
|
|
106
|
+
if (!first.wall) {
|
|
107
|
+
const origin = new URL(page.url()).origin;
|
|
108
|
+
console.log(`✓ No sign-in needed for ${origin}.`);
|
|
109
|
+
try { const l = readLedger(); l.origins = { ...(l.origins ?? {}), [origin]: new Date().toISOString() }; fs.mkdirSync(path.dirname(ledgerPath), { recursive: true }); fs.writeFileSync(ledgerPath, JSON.stringify(l, null, 2) + "\n"); } catch { /* fine */ }
|
|
110
|
+
await postEvent("signed_in", { origin, none: true });
|
|
111
|
+
await ctx.close();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
89
114
|
console.log("⏳ Waiting for you — sign in in the Chrome window that just opened. Your hands only; nothing you type is seen.");
|
|
90
115
|
await postEvent("signin_wait", { url });
|
|
91
116
|
const started = Date.now();
|