@herdingchats/friday 0.1.683 → 0.1.688
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/herd.mjs +125 -42
- package/package.json +2 -2
package/herd.mjs
CHANGED
|
@@ -18,7 +18,7 @@ var __export = (target, all) => {
|
|
|
18
18
|
var define_HERD_PACKAGE_default;
|
|
19
19
|
var init_define_HERD_PACKAGE = __esm({
|
|
20
20
|
"<define:HERD_PACKAGE>"() {
|
|
21
|
-
define_HERD_PACKAGE_default = { version: "0.1.
|
|
21
|
+
define_HERD_PACKAGE_default = { version: "0.1.688", gitHead: "a284c931b34fdaa76115c8d2c91da8b2903d19ff" };
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -3309,7 +3309,7 @@ import { spawn as spawn8 } from "node:child_process";
|
|
|
3309
3309
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
3310
3310
|
import { homedir as homedir5 } from "node:os";
|
|
3311
3311
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
3312
|
-
import { createInterface } from "node:readline/promises";
|
|
3312
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3313
3313
|
import { z as z23 } from "zod";
|
|
3314
3314
|
|
|
3315
3315
|
// src/auth.ts
|
|
@@ -3368,16 +3368,93 @@ async function postForm(url, fields) {
|
|
|
3368
3368
|
body: new URLSearchParams(fields)
|
|
3369
3369
|
});
|
|
3370
3370
|
}
|
|
3371
|
-
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3371
|
+
var deviceRedirectFor = (api) => {
|
|
3372
|
+
const url = new URL(api);
|
|
3373
|
+
url.hostname = url.hostname.replace(/^api\./, "chat.");
|
|
3374
|
+
return `${url.origin}/sign-in/code`;
|
|
3375
|
+
};
|
|
3376
|
+
var wantsDevice = (args, env = process.env) => args.includes("--device") || Boolean(env.SSH_CONNECTION || env.SSH_TTY);
|
|
3377
|
+
function parsePasted(pasted) {
|
|
3378
|
+
const text4 = pasted.trim();
|
|
3379
|
+
if (/^https?:\/\//.test(text4)) {
|
|
3380
|
+
const url = new URL(text4);
|
|
3381
|
+
const code = url.searchParams.get("code");
|
|
3382
|
+
return code ? { state: url.searchParams.get("state") ?? "", code } : null;
|
|
3383
|
+
}
|
|
3384
|
+
const dot = text4.indexOf(".");
|
|
3385
|
+
return dot > 0 && dot < text4.length - 1 ? { state: text4.slice(0, dot), code: text4.slice(dot + 1) } : null;
|
|
3386
|
+
}
|
|
3387
|
+
async function loopback(openBrowser2, timeoutMs) {
|
|
3376
3388
|
const server = createServer();
|
|
3377
3389
|
await new Promise((resolve8) => server.listen(0, "127.0.0.1", resolve8));
|
|
3378
3390
|
const address = server.address();
|
|
3379
3391
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
3380
3392
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
3393
|
+
return {
|
|
3394
|
+
redirectUri,
|
|
3395
|
+
close: () => server.close(),
|
|
3396
|
+
code: async (authorize, state) => {
|
|
3397
|
+
const code = new Promise((resolve8, reject) => {
|
|
3398
|
+
const timer = setTimeout(
|
|
3399
|
+
() => reject(new HerdError("Nobody signed in within 10 minutes. Run herd login again.")),
|
|
3400
|
+
timeoutMs
|
|
3401
|
+
);
|
|
3402
|
+
server.on("request", (req, res) => {
|
|
3403
|
+
const url = new URL(req.url ?? "/", redirectUri);
|
|
3404
|
+
if (url.pathname !== "/callback") return void res.writeHead(404).end();
|
|
3405
|
+
clearTimeout(timer);
|
|
3406
|
+
const error = url.searchParams.get("error");
|
|
3407
|
+
const ok = !error && url.searchParams.get("state") === state;
|
|
3408
|
+
res.writeHead(ok ? 200 : 400, { "Content-Type": "text/plain" }).end(ok ? "Signed in to herd. You can close this tab." : "Sign-in didn't complete.");
|
|
3409
|
+
if (error) return reject(new HerdError(`Sign-in was refused: ${error}.`));
|
|
3410
|
+
if (!ok) return reject(new HerdError("Sign-in came back for a different request."));
|
|
3411
|
+
resolve8(url.searchParams.get("code") ?? "");
|
|
3412
|
+
});
|
|
3413
|
+
});
|
|
3414
|
+
code.catch(() => {
|
|
3415
|
+
});
|
|
3416
|
+
await openBrowser2(authorize);
|
|
3417
|
+
return code;
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
function device(redirectUri, print, readCode) {
|
|
3422
|
+
return {
|
|
3423
|
+
redirectUri,
|
|
3424
|
+
close: () => {
|
|
3425
|
+
},
|
|
3426
|
+
code: async (authorize, state) => {
|
|
3427
|
+
print(`Open this link on any device and sign in:
|
|
3428
|
+
|
|
3429
|
+
${authorize}
|
|
3430
|
+
`);
|
|
3431
|
+
for (let tries = 0; tries < 3; tries += 1) {
|
|
3432
|
+
const pasted = parsePasted(await readCode("Paste the code it shows: "));
|
|
3433
|
+
if (!pasted) {
|
|
3434
|
+
print("That doesn't look like the code. Copy it with the button on the page.");
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
if (pasted.state !== state) {
|
|
3438
|
+
throw new HerdError("That code is from a different sign-in. Run herd login again.");
|
|
3439
|
+
}
|
|
3440
|
+
return pasted.code;
|
|
3441
|
+
}
|
|
3442
|
+
throw new HerdError("No code was pasted. Run herd login again.");
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
async function login(how, {
|
|
3447
|
+
api = apiUrl(),
|
|
3448
|
+
timeoutMs = 10 * 60 * 1e3,
|
|
3449
|
+
deviceRedirect = deviceRedirectFor(api)
|
|
3450
|
+
} = {}) {
|
|
3451
|
+
const discovered = await fetch(`${api}/.well-known/oauth-authorization-server`).catch(() => null);
|
|
3452
|
+
const metadata = metadataSchema.safeParse(await discovered?.json().catch(() => null));
|
|
3453
|
+
if (!metadata.success) throw new HerdError(`Couldn't find where to sign in for ${api}.`);
|
|
3454
|
+
const meta = metadata.data;
|
|
3455
|
+
const way = typeof how === "function" ? { openBrowser: how } : how;
|
|
3456
|
+
const source = "openBrowser" in way ? await loopback(way.openBrowser, timeoutMs) : device(deviceRedirect, way.print, way.readCode);
|
|
3457
|
+
const { redirectUri } = source;
|
|
3381
3458
|
try {
|
|
3382
3459
|
const registration2 = await fetch(meta.registration_endpoint, {
|
|
3383
3460
|
method: "POST",
|
|
@@ -3406,30 +3483,11 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3406
3483
|
state,
|
|
3407
3484
|
resource: api
|
|
3408
3485
|
}).toString();
|
|
3409
|
-
const code =
|
|
3410
|
-
const timer = setTimeout(
|
|
3411
|
-
() => reject(new HerdError("Nobody signed in within 10 minutes. Run herd login again.")),
|
|
3412
|
-
timeoutMs
|
|
3413
|
-
);
|
|
3414
|
-
server.on("request", (req, res) => {
|
|
3415
|
-
const url = new URL(req.url ?? "/", redirectUri);
|
|
3416
|
-
if (url.pathname !== "/callback") return void res.writeHead(404).end();
|
|
3417
|
-
clearTimeout(timer);
|
|
3418
|
-
const error = url.searchParams.get("error");
|
|
3419
|
-
const ok = !error && url.searchParams.get("state") === state;
|
|
3420
|
-
res.writeHead(ok ? 200 : 400, { "Content-Type": "text/plain" }).end(ok ? "Signed in to herd. You can close this tab." : "Sign-in didn't complete.");
|
|
3421
|
-
if (error) return reject(new HerdError(`Sign-in was refused: ${error}.`));
|
|
3422
|
-
if (!ok) return reject(new HerdError("Sign-in came back for a different request."));
|
|
3423
|
-
resolve8(url.searchParams.get("code") ?? "");
|
|
3424
|
-
});
|
|
3425
|
-
});
|
|
3426
|
-
code.catch(() => {
|
|
3427
|
-
});
|
|
3428
|
-
await openBrowser2(authorize.toString());
|
|
3486
|
+
const code = await source.code(authorize.toString(), state);
|
|
3429
3487
|
const response = await postForm(meta.token_endpoint, {
|
|
3430
3488
|
grant_type: "authorization_code",
|
|
3431
3489
|
client_id: client.data.client_id,
|
|
3432
|
-
code
|
|
3490
|
+
code,
|
|
3433
3491
|
redirect_uri: redirectUri,
|
|
3434
3492
|
code_verifier: verifier,
|
|
3435
3493
|
resource: api
|
|
@@ -3446,7 +3504,7 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3446
3504
|
refreshToken: tokens.data.refresh_token
|
|
3447
3505
|
});
|
|
3448
3506
|
} finally {
|
|
3449
|
-
|
|
3507
|
+
source.close();
|
|
3450
3508
|
}
|
|
3451
3509
|
}
|
|
3452
3510
|
var EXPIRY_MARGIN_MS = 60 * 1e3;
|
|
@@ -3508,7 +3566,7 @@ async function apiFetch(path, init = {}) {
|
|
|
3508
3566
|
// src/cli-flags.ts
|
|
3509
3567
|
init_define_HERD_PACKAGE();
|
|
3510
3568
|
var HERD_FLAGS = {
|
|
3511
|
-
login: [],
|
|
3569
|
+
login: ["--device"],
|
|
3512
3570
|
logout: [],
|
|
3513
3571
|
daemon: ["--repo"],
|
|
3514
3572
|
attach: ["--watch"],
|
|
@@ -3539,7 +3597,7 @@ var HERD_FLAGS = {
|
|
|
3539
3597
|
persona: []
|
|
3540
3598
|
};
|
|
3541
3599
|
var FRIDAY_FLAGS = {
|
|
3542
|
-
start: ["--agents", "--config", "--verbose"],
|
|
3600
|
+
start: ["--agents", "--config", "--device", "--verbose"],
|
|
3543
3601
|
stop: [],
|
|
3544
3602
|
status: [],
|
|
3545
3603
|
logs: ["--follow", "-f", "-n"]
|
|
@@ -3591,6 +3649,7 @@ function commandHelp(overview, command) {
|
|
|
3591
3649
|
// src/commands/login.ts
|
|
3592
3650
|
init_define_HERD_PACKAGE();
|
|
3593
3651
|
import { spawn } from "node:child_process";
|
|
3652
|
+
import { createInterface } from "node:readline/promises";
|
|
3594
3653
|
function openBrowser(url) {
|
|
3595
3654
|
console.log(`Opening your browser to sign in. If it doesn't open, visit:
|
|
3596
3655
|
|
|
@@ -3603,12 +3662,34 @@ function openBrowser(url) {
|
|
|
3603
3662
|
} catch {
|
|
3604
3663
|
}
|
|
3605
3664
|
}
|
|
3606
|
-
async function
|
|
3665
|
+
async function readCodeFromTerminal(prompt) {
|
|
3666
|
+
if (!process.stdin.isTTY) {
|
|
3667
|
+
throw new HerdError("Signing in with a code needs a terminal to paste it into.");
|
|
3668
|
+
}
|
|
3669
|
+
const lines = createInterface({ input: process.stdin, output: process.stdout });
|
|
3670
|
+
try {
|
|
3671
|
+
return await lines.question(prompt);
|
|
3672
|
+
} finally {
|
|
3673
|
+
lines.close();
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
function signInWay(args, env = process.env) {
|
|
3677
|
+
if (wantsDevice(args, env)) {
|
|
3678
|
+
return { readCode: readCodeFromTerminal, print: (line2) => console.log(line2) };
|
|
3679
|
+
}
|
|
3680
|
+
return {
|
|
3681
|
+
openBrowser: (url) => {
|
|
3682
|
+
openBrowser(url);
|
|
3683
|
+
console.log("Signing in on another device, such as over SSH? Run this with --device.\n");
|
|
3684
|
+
}
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
async function runLogin(args = []) {
|
|
3607
3688
|
if (isLocal(apiUrl())) {
|
|
3608
3689
|
console.log("The local dev server needs no sign-in.");
|
|
3609
3690
|
return 0;
|
|
3610
3691
|
}
|
|
3611
|
-
await login(
|
|
3692
|
+
await login(signInWay(args));
|
|
3612
3693
|
console.log("Signed in.");
|
|
3613
3694
|
return 0;
|
|
3614
3695
|
}
|
|
@@ -6676,7 +6757,7 @@ async function runStart(options) {
|
|
|
6676
6757
|
}
|
|
6677
6758
|
}
|
|
6678
6759
|
if (!options.signedIn()) {
|
|
6679
|
-
print("Signing in
|
|
6760
|
+
print("Signing in.");
|
|
6680
6761
|
await options.signIn();
|
|
6681
6762
|
verbose2("signed in");
|
|
6682
6763
|
} else verbose2("already signed in");
|
|
@@ -7043,7 +7124,8 @@ Usage: ${FRIDAY_COMMAND} <command>
|
|
|
7043
7124
|
|
|
7044
7125
|
start Make this computer a machine, with the folder this runs in as its root, which
|
|
7045
7126
|
holds your repos, such as ~/code. Signs in through your browser when
|
|
7046
|
-
needed,
|
|
7127
|
+
needed, or with a code to paste from another device with --device or over
|
|
7128
|
+
SSH. Asks how much of each agent you have the first time, starts the daemon
|
|
7047
7129
|
now and whenever you log in, then shows what's left to set up. Safe to run
|
|
7048
7130
|
again. Run in another folder, it offers to move the root there. --config asks
|
|
7049
7131
|
about the agents again, and --agents claude=lot,codex=little sets their shares
|
|
@@ -7120,7 +7202,7 @@ async function friday(args, deps = defaults()) {
|
|
|
7120
7202
|
version: HERD_VERSION,
|
|
7121
7203
|
run: verboseExec(deps.run),
|
|
7122
7204
|
signedIn: () => isLocal(apiUrl()) || readCredentials() !== null,
|
|
7123
|
-
signIn: () => login(
|
|
7205
|
+
signIn: () => login(signInWay(rest)),
|
|
7124
7206
|
serviceKind: deps.serviceKind ?? await detectService(verboseExec(deps.run)),
|
|
7125
7207
|
env: passedEnv(),
|
|
7126
7208
|
print: deps.print,
|
|
@@ -7169,7 +7251,7 @@ ${HELP}`);
|
|
|
7169
7251
|
}
|
|
7170
7252
|
}
|
|
7171
7253
|
async function confirmInTerminal(question) {
|
|
7172
|
-
const lines =
|
|
7254
|
+
const lines = createInterface2({ input: process.stdin, output: process.stdout });
|
|
7173
7255
|
try {
|
|
7174
7256
|
return /^y(es)?$/i.test((await lines.question(`${question} [y/N] `)).trim());
|
|
7175
7257
|
} finally {
|
|
@@ -10647,15 +10729,15 @@ async function signInGh(options) {
|
|
|
10647
10729
|
const codeTimer = setTimeout(() => resolveCode(false), options.codeWaitMs ?? GH_CODE_WAIT_MS);
|
|
10648
10730
|
const found = await codeShown;
|
|
10649
10731
|
clearTimeout(codeTimer);
|
|
10650
|
-
const
|
|
10651
|
-
if (!found || !
|
|
10732
|
+
const device2 = readDeviceCode(output2);
|
|
10733
|
+
if (!found || !device2) {
|
|
10652
10734
|
child.kill();
|
|
10653
10735
|
const failed = { state: "failed", reason: "no_code" };
|
|
10654
10736
|
await options.update(failed);
|
|
10655
10737
|
return failed;
|
|
10656
10738
|
}
|
|
10657
10739
|
sent = true;
|
|
10658
|
-
await options.update({ state: "code", code:
|
|
10740
|
+
await options.update({ state: "code", code: device2.code, url: device2.url });
|
|
10659
10741
|
const approveTimer = setTimeout(() => child.kill(), options.approveWaitMs ?? GH_APPROVE_WAIT_MS);
|
|
10660
10742
|
await exited;
|
|
10661
10743
|
clearTimeout(approveTimer);
|
|
@@ -15592,7 +15674,8 @@ var HELP4 = `herd: work on HerdingChats conversations from this computer.
|
|
|
15592
15674
|
Usage: herd <command>
|
|
15593
15675
|
|
|
15594
15676
|
On this computer:
|
|
15595
|
-
login
|
|
15677
|
+
login [--device] Sign in through your browser. --device, or running over SSH, signs in from
|
|
15678
|
+
another device with a code to paste
|
|
15596
15679
|
logout Remove this computer's sign-in
|
|
15597
15680
|
daemon [--repo <path>]...
|
|
15598
15681
|
Run this machine's daemon, working from its root folder. Repos given with
|
|
@@ -15676,7 +15759,7 @@ async function main(args) {
|
|
|
15676
15759
|
console.log(HERD_VERSION);
|
|
15677
15760
|
return 0;
|
|
15678
15761
|
case "login":
|
|
15679
|
-
return await runLogin();
|
|
15762
|
+
return await runLogin(rest);
|
|
15680
15763
|
case "logout":
|
|
15681
15764
|
return await runLogout();
|
|
15682
15765
|
case "daemon":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herdingchats/friday",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.688",
|
|
4
4
|
"description": "Friday, HerdingChats' agent daemon, and the herd command it runs agents with.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"satori": "0.32.0",
|
|
23
23
|
"zod": "4.6.7"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "a284c931b34fdaa76115c8d2c91da8b2903d19ff"
|
|
26
26
|
}
|