@herdingchats/friday 0.1.683 → 0.1.701
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 +171 -49
- package/package.json +2 -2
- package/terminal-host.mjs +12 -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.701", gitHead: "dee83b97beed4102f6d56d692f1c1e306b62dbb6" };
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -508,7 +508,13 @@ var init_terminals = __esm({
|
|
|
508
508
|
rows: z6.number().int().min(5).max(1e3)
|
|
509
509
|
};
|
|
510
510
|
terminalFrameSchema = z6.discriminatedUnion("type", [
|
|
511
|
-
|
|
511
|
+
// The screen, and its size, which a watcher draws it at. Hosts from before sizes don't say.
|
|
512
|
+
z6.object({
|
|
513
|
+
type: z6.literal("snapshot"),
|
|
514
|
+
data: z6.string(),
|
|
515
|
+
cols: size.cols.optional(),
|
|
516
|
+
rows: size.rows.optional()
|
|
517
|
+
}),
|
|
512
518
|
z6.object({ type: z6.literal("data"), data: z6.string() }),
|
|
513
519
|
z6.object({ type: z6.literal("exit"), exitCode: z6.number().int().nullable() }),
|
|
514
520
|
z6.object({ type: z6.literal("mode"), mode: terminalModeSchema }),
|
|
@@ -1507,7 +1513,10 @@ var init_events = __esm({
|
|
|
1507
1513
|
kind: z13.literal("bound"),
|
|
1508
1514
|
machine: z13.object({ name: nonEmpty6, online: z13.boolean() }),
|
|
1509
1515
|
// The worker's name, such as `mac-mini/claude`, and never its configuration or capabilities.
|
|
1510
|
-
worker: nonEmpty6.optional()
|
|
1516
|
+
worker: nonEmpty6.optional(),
|
|
1517
|
+
// Only for the machine's owner, while it's online: what they may do with its terminal from
|
|
1518
|
+
// the web. Absent for anyone else, and where the machine allows nothing or can't.
|
|
1519
|
+
terminal: z13.enum(["drive", "watch"]).optional()
|
|
1511
1520
|
}),
|
|
1512
1521
|
z13.object({ kind: z13.literal("waiting_for_machine") }),
|
|
1513
1522
|
z13.object({ kind: z13.literal("author_not_linked") })
|
|
@@ -3309,7 +3318,7 @@ import { spawn as spawn8 } from "node:child_process";
|
|
|
3309
3318
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
3310
3319
|
import { homedir as homedir5 } from "node:os";
|
|
3311
3320
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
3312
|
-
import { createInterface } from "node:readline/promises";
|
|
3321
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3313
3322
|
import { z as z23 } from "zod";
|
|
3314
3323
|
|
|
3315
3324
|
// src/auth.ts
|
|
@@ -3368,16 +3377,93 @@ async function postForm(url, fields) {
|
|
|
3368
3377
|
body: new URLSearchParams(fields)
|
|
3369
3378
|
});
|
|
3370
3379
|
}
|
|
3371
|
-
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3380
|
+
var deviceRedirectFor = (api) => {
|
|
3381
|
+
const url = new URL(api);
|
|
3382
|
+
url.hostname = url.hostname.replace(/^api\./, "chat.");
|
|
3383
|
+
return `${url.origin}/sign-in/code`;
|
|
3384
|
+
};
|
|
3385
|
+
var wantsDevice = (args, env = process.env) => args.includes("--device") || Boolean(env.SSH_CONNECTION || env.SSH_TTY);
|
|
3386
|
+
function parsePasted(pasted) {
|
|
3387
|
+
const text4 = pasted.trim();
|
|
3388
|
+
if (/^https?:\/\//.test(text4)) {
|
|
3389
|
+
const url = new URL(text4);
|
|
3390
|
+
const code = url.searchParams.get("code");
|
|
3391
|
+
return code ? { state: url.searchParams.get("state") ?? "", code } : null;
|
|
3392
|
+
}
|
|
3393
|
+
const dot = text4.indexOf(".");
|
|
3394
|
+
return dot > 0 && dot < text4.length - 1 ? { state: text4.slice(0, dot), code: text4.slice(dot + 1) } : null;
|
|
3395
|
+
}
|
|
3396
|
+
async function loopback(openBrowser2, timeoutMs) {
|
|
3376
3397
|
const server = createServer();
|
|
3377
3398
|
await new Promise((resolve8) => server.listen(0, "127.0.0.1", resolve8));
|
|
3378
3399
|
const address = server.address();
|
|
3379
3400
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
3380
3401
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
3402
|
+
return {
|
|
3403
|
+
redirectUri,
|
|
3404
|
+
close: () => server.close(),
|
|
3405
|
+
code: async (authorize, state) => {
|
|
3406
|
+
const code = new Promise((resolve8, reject) => {
|
|
3407
|
+
const timer = setTimeout(
|
|
3408
|
+
() => reject(new HerdError("Nobody signed in within 10 minutes. Run herd login again.")),
|
|
3409
|
+
timeoutMs
|
|
3410
|
+
);
|
|
3411
|
+
server.on("request", (req, res) => {
|
|
3412
|
+
const url = new URL(req.url ?? "/", redirectUri);
|
|
3413
|
+
if (url.pathname !== "/callback") return void res.writeHead(404).end();
|
|
3414
|
+
clearTimeout(timer);
|
|
3415
|
+
const error = url.searchParams.get("error");
|
|
3416
|
+
const ok = !error && url.searchParams.get("state") === state;
|
|
3417
|
+
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.");
|
|
3418
|
+
if (error) return reject(new HerdError(`Sign-in was refused: ${error}.`));
|
|
3419
|
+
if (!ok) return reject(new HerdError("Sign-in came back for a different request."));
|
|
3420
|
+
resolve8(url.searchParams.get("code") ?? "");
|
|
3421
|
+
});
|
|
3422
|
+
});
|
|
3423
|
+
code.catch(() => {
|
|
3424
|
+
});
|
|
3425
|
+
await openBrowser2(authorize);
|
|
3426
|
+
return code;
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
function device(redirectUri, print, readCode) {
|
|
3431
|
+
return {
|
|
3432
|
+
redirectUri,
|
|
3433
|
+
close: () => {
|
|
3434
|
+
},
|
|
3435
|
+
code: async (authorize, state) => {
|
|
3436
|
+
print(`Open this link on any device and sign in:
|
|
3437
|
+
|
|
3438
|
+
${authorize}
|
|
3439
|
+
`);
|
|
3440
|
+
for (let tries = 0; tries < 3; tries += 1) {
|
|
3441
|
+
const pasted = parsePasted(await readCode("Paste the code it shows: "));
|
|
3442
|
+
if (!pasted) {
|
|
3443
|
+
print("That doesn't look like the code. Copy it with the button on the page.");
|
|
3444
|
+
continue;
|
|
3445
|
+
}
|
|
3446
|
+
if (pasted.state !== state) {
|
|
3447
|
+
throw new HerdError("That code is from a different sign-in. Run herd login again.");
|
|
3448
|
+
}
|
|
3449
|
+
return pasted.code;
|
|
3450
|
+
}
|
|
3451
|
+
throw new HerdError("No code was pasted. Run herd login again.");
|
|
3452
|
+
}
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
async function login(how, {
|
|
3456
|
+
api = apiUrl(),
|
|
3457
|
+
timeoutMs = 10 * 60 * 1e3,
|
|
3458
|
+
deviceRedirect = deviceRedirectFor(api)
|
|
3459
|
+
} = {}) {
|
|
3460
|
+
const discovered = await fetch(`${api}/.well-known/oauth-authorization-server`).catch(() => null);
|
|
3461
|
+
const metadata = metadataSchema.safeParse(await discovered?.json().catch(() => null));
|
|
3462
|
+
if (!metadata.success) throw new HerdError(`Couldn't find where to sign in for ${api}.`);
|
|
3463
|
+
const meta = metadata.data;
|
|
3464
|
+
const way = typeof how === "function" ? { openBrowser: how } : how;
|
|
3465
|
+
const source = "openBrowser" in way ? await loopback(way.openBrowser, timeoutMs) : device(deviceRedirect, way.print, way.readCode);
|
|
3466
|
+
const { redirectUri } = source;
|
|
3381
3467
|
try {
|
|
3382
3468
|
const registration2 = await fetch(meta.registration_endpoint, {
|
|
3383
3469
|
method: "POST",
|
|
@@ -3406,30 +3492,11 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3406
3492
|
state,
|
|
3407
3493
|
resource: api
|
|
3408
3494
|
}).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());
|
|
3495
|
+
const code = await source.code(authorize.toString(), state);
|
|
3429
3496
|
const response = await postForm(meta.token_endpoint, {
|
|
3430
3497
|
grant_type: "authorization_code",
|
|
3431
3498
|
client_id: client.data.client_id,
|
|
3432
|
-
code
|
|
3499
|
+
code,
|
|
3433
3500
|
redirect_uri: redirectUri,
|
|
3434
3501
|
code_verifier: verifier,
|
|
3435
3502
|
resource: api
|
|
@@ -3446,7 +3513,7 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3446
3513
|
refreshToken: tokens.data.refresh_token
|
|
3447
3514
|
});
|
|
3448
3515
|
} finally {
|
|
3449
|
-
|
|
3516
|
+
source.close();
|
|
3450
3517
|
}
|
|
3451
3518
|
}
|
|
3452
3519
|
var EXPIRY_MARGIN_MS = 60 * 1e3;
|
|
@@ -3508,7 +3575,7 @@ async function apiFetch(path, init = {}) {
|
|
|
3508
3575
|
// src/cli-flags.ts
|
|
3509
3576
|
init_define_HERD_PACKAGE();
|
|
3510
3577
|
var HERD_FLAGS = {
|
|
3511
|
-
login: [],
|
|
3578
|
+
login: ["--device"],
|
|
3512
3579
|
logout: [],
|
|
3513
3580
|
daemon: ["--repo"],
|
|
3514
3581
|
attach: ["--watch"],
|
|
@@ -3539,7 +3606,7 @@ var HERD_FLAGS = {
|
|
|
3539
3606
|
persona: []
|
|
3540
3607
|
};
|
|
3541
3608
|
var FRIDAY_FLAGS = {
|
|
3542
|
-
start: ["--agents", "--config", "--verbose"],
|
|
3609
|
+
start: ["--agents", "--config", "--device", "--verbose"],
|
|
3543
3610
|
stop: [],
|
|
3544
3611
|
status: [],
|
|
3545
3612
|
logs: ["--follow", "-f", "-n"]
|
|
@@ -3591,6 +3658,7 @@ function commandHelp(overview, command) {
|
|
|
3591
3658
|
// src/commands/login.ts
|
|
3592
3659
|
init_define_HERD_PACKAGE();
|
|
3593
3660
|
import { spawn } from "node:child_process";
|
|
3661
|
+
import { createInterface } from "node:readline/promises";
|
|
3594
3662
|
function openBrowser(url) {
|
|
3595
3663
|
console.log(`Opening your browser to sign in. If it doesn't open, visit:
|
|
3596
3664
|
|
|
@@ -3603,12 +3671,34 @@ function openBrowser(url) {
|
|
|
3603
3671
|
} catch {
|
|
3604
3672
|
}
|
|
3605
3673
|
}
|
|
3606
|
-
async function
|
|
3674
|
+
async function readCodeFromTerminal(prompt) {
|
|
3675
|
+
if (!process.stdin.isTTY) {
|
|
3676
|
+
throw new HerdError("Signing in with a code needs a terminal to paste it into.");
|
|
3677
|
+
}
|
|
3678
|
+
const lines = createInterface({ input: process.stdin, output: process.stdout });
|
|
3679
|
+
try {
|
|
3680
|
+
return await lines.question(prompt);
|
|
3681
|
+
} finally {
|
|
3682
|
+
lines.close();
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
function signInWay(args, env = process.env) {
|
|
3686
|
+
if (wantsDevice(args, env)) {
|
|
3687
|
+
return { readCode: readCodeFromTerminal, print: (line2) => console.log(line2) };
|
|
3688
|
+
}
|
|
3689
|
+
return {
|
|
3690
|
+
openBrowser: (url) => {
|
|
3691
|
+
openBrowser(url);
|
|
3692
|
+
console.log("Signing in on another device, such as over SSH? Run this with --device.\n");
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
async function runLogin(args = []) {
|
|
3607
3697
|
if (isLocal(apiUrl())) {
|
|
3608
3698
|
console.log("The local dev server needs no sign-in.");
|
|
3609
3699
|
return 0;
|
|
3610
3700
|
}
|
|
3611
|
-
await login(
|
|
3701
|
+
await login(signInWay(args));
|
|
3612
3702
|
console.log("Signed in.");
|
|
3613
3703
|
return 0;
|
|
3614
3704
|
}
|
|
@@ -5415,6 +5505,25 @@ var Activator = class {
|
|
|
5415
5505
|
}
|
|
5416
5506
|
this.request([{ name: agent, version }]);
|
|
5417
5507
|
}
|
|
5508
|
+
/**
|
|
5509
|
+
* The terminal an agent's activation runs in, for its owner to sign in from the web. One that
|
|
5510
|
+
* isn't running is started, and its terminal named once it's open. Null if it didn't open.
|
|
5511
|
+
*/
|
|
5512
|
+
async terminalFor(agent, version, { waitMs = 6e4, pollMs = 200 } = {}) {
|
|
5513
|
+
if (this.current?.agent !== agent) this.request([{ name: agent, version }], { force: true });
|
|
5514
|
+
const until = Date.now() + waitMs;
|
|
5515
|
+
while (Date.now() < until && !this.stopped) {
|
|
5516
|
+
const current = this.current;
|
|
5517
|
+
if (current?.agent === agent) {
|
|
5518
|
+
const open = (await this.options.terminals.list().catch(() => [])).some(
|
|
5519
|
+
(t) => t.name === current.terminal && !t.exited
|
|
5520
|
+
);
|
|
5521
|
+
if (open) return current.terminal;
|
|
5522
|
+
}
|
|
5523
|
+
await new Promise((resolve8) => setTimeout(resolve8, pollMs));
|
|
5524
|
+
}
|
|
5525
|
+
return null;
|
|
5526
|
+
}
|
|
5418
5527
|
/** Resolves once nothing is queued or running, as tests wait for. */
|
|
5419
5528
|
async settled() {
|
|
5420
5529
|
while (this.running) await this.running;
|
|
@@ -5515,7 +5624,8 @@ var Activator = class {
|
|
|
5515
5624
|
async waitForCheckIn(version, driver, current, folder, wasSignedOut) {
|
|
5516
5625
|
const { terminals } = this.options;
|
|
5517
5626
|
const { agent } = current;
|
|
5518
|
-
const
|
|
5627
|
+
const deadlineMs = this.options.deadlineMs ?? ACTIVATION_DEADLINE_MS;
|
|
5628
|
+
let deadline = Date.now() + deadlineMs;
|
|
5519
5629
|
const restartMs = this.options.signInRestartMs ?? SIGN_IN_RESTART_MS;
|
|
5520
5630
|
const checkInMs = this.options.checkInMs ?? CHECK_IN_MS;
|
|
5521
5631
|
const pollMs = this.options.pollMs ?? ACTIVATION_POLL_MS;
|
|
@@ -5551,6 +5661,8 @@ var Activator = class {
|
|
|
5551
5661
|
};
|
|
5552
5662
|
await start();
|
|
5553
5663
|
while (!current.checkedIn && Date.now() < deadline && !this.stopped) {
|
|
5664
|
+
const lookers = lookingAt((await terminals.list()).find((t) => t.name === current.terminal));
|
|
5665
|
+
if (lookers > 0) deadline = Math.max(deadline, Date.now() + deadlineMs);
|
|
5554
5666
|
const reading = await driver.read(current.terminal).catch(() => null);
|
|
5555
5667
|
if (reading?.state === "signed_out" && Date.now() - startedAt >= checkInMs) {
|
|
5556
5668
|
seen = "signed_out";
|
|
@@ -6676,7 +6788,7 @@ async function runStart(options) {
|
|
|
6676
6788
|
}
|
|
6677
6789
|
}
|
|
6678
6790
|
if (!options.signedIn()) {
|
|
6679
|
-
print("Signing in
|
|
6791
|
+
print("Signing in.");
|
|
6680
6792
|
await options.signIn();
|
|
6681
6793
|
verbose2("signed in");
|
|
6682
6794
|
} else verbose2("already signed in");
|
|
@@ -7043,7 +7155,8 @@ Usage: ${FRIDAY_COMMAND} <command>
|
|
|
7043
7155
|
|
|
7044
7156
|
start Make this computer a machine, with the folder this runs in as its root, which
|
|
7045
7157
|
holds your repos, such as ~/code. Signs in through your browser when
|
|
7046
|
-
needed,
|
|
7158
|
+
needed, or with a code to paste from another device with --device or over
|
|
7159
|
+
SSH. Asks how much of each agent you have the first time, starts the daemon
|
|
7047
7160
|
now and whenever you log in, then shows what's left to set up. Safe to run
|
|
7048
7161
|
again. Run in another folder, it offers to move the root there. --config asks
|
|
7049
7162
|
about the agents again, and --agents claude=lot,codex=little sets their shares
|
|
@@ -7120,7 +7233,7 @@ async function friday(args, deps = defaults()) {
|
|
|
7120
7233
|
version: HERD_VERSION,
|
|
7121
7234
|
run: verboseExec(deps.run),
|
|
7122
7235
|
signedIn: () => isLocal(apiUrl()) || readCredentials() !== null,
|
|
7123
|
-
signIn: () => login(
|
|
7236
|
+
signIn: () => login(signInWay(rest)),
|
|
7124
7237
|
serviceKind: deps.serviceKind ?? await detectService(verboseExec(deps.run)),
|
|
7125
7238
|
env: passedEnv(),
|
|
7126
7239
|
print: deps.print,
|
|
@@ -7169,7 +7282,7 @@ ${HELP}`);
|
|
|
7169
7282
|
}
|
|
7170
7283
|
}
|
|
7171
7284
|
async function confirmInTerminal(question) {
|
|
7172
|
-
const lines =
|
|
7285
|
+
const lines = createInterface2({ input: process.stdin, output: process.stdout });
|
|
7173
7286
|
try {
|
|
7174
7287
|
return /^y(es)?$/i.test((await lines.question(`${question} [y/N] `)).trim());
|
|
7175
7288
|
} finally {
|
|
@@ -10647,15 +10760,15 @@ async function signInGh(options) {
|
|
|
10647
10760
|
const codeTimer = setTimeout(() => resolveCode(false), options.codeWaitMs ?? GH_CODE_WAIT_MS);
|
|
10648
10761
|
const found = await codeShown;
|
|
10649
10762
|
clearTimeout(codeTimer);
|
|
10650
|
-
const
|
|
10651
|
-
if (!found || !
|
|
10763
|
+
const device2 = readDeviceCode(output2);
|
|
10764
|
+
if (!found || !device2) {
|
|
10652
10765
|
child.kill();
|
|
10653
10766
|
const failed = { state: "failed", reason: "no_code" };
|
|
10654
10767
|
await options.update(failed);
|
|
10655
10768
|
return failed;
|
|
10656
10769
|
}
|
|
10657
10770
|
sent = true;
|
|
10658
|
-
await options.update({ state: "code", code:
|
|
10771
|
+
await options.update({ state: "code", code: device2.code, url: device2.url });
|
|
10659
10772
|
const approveTimer = setTimeout(() => child.kill(), options.approveWaitMs ?? GH_APPROVE_WAIT_MS);
|
|
10660
10773
|
await exited;
|
|
10661
10774
|
clearTimeout(approveTimer);
|
|
@@ -10986,9 +11099,13 @@ var Session = class {
|
|
|
10986
11099
|
});
|
|
10987
11100
|
}
|
|
10988
11101
|
fromHost(message) {
|
|
10989
|
-
|
|
11102
|
+
const size2 = typeof message.cols === "number" && typeof message.rows === "number" ? { cols: message.cols, rows: message.rows } : null;
|
|
11103
|
+
if (message.type === "resize" && size2) {
|
|
11104
|
+
this.flush();
|
|
11105
|
+
this.send({ type: "resize", ...size2 });
|
|
11106
|
+
} else if (message.type === "snapshot") {
|
|
10990
11107
|
this.pending = "";
|
|
10991
|
-
this.send({ type: "snapshot", data: message.data ?? "" });
|
|
11108
|
+
this.send({ type: "snapshot", data: message.data ?? "", ...size2 ?? {} });
|
|
10992
11109
|
} else if (message.type === "data") {
|
|
10993
11110
|
if (this.behind) return;
|
|
10994
11111
|
this.pending += message.data ?? "";
|
|
@@ -14172,8 +14289,12 @@ async function runDaemon(args) {
|
|
|
14172
14289
|
}),
|
|
14173
14290
|
access: () => daemon?.configuration().config?.terminals?.remote ?? "drive",
|
|
14174
14291
|
resolve: async (target) => {
|
|
14175
|
-
if (target.kind
|
|
14176
|
-
|
|
14292
|
+
if (target.kind === "activation") {
|
|
14293
|
+
const cli = clis.find((candidate) => candidate.name === target.agent);
|
|
14294
|
+
if (!cli || !activator) return { refused: "This machine doesn't run that agent." };
|
|
14295
|
+
const terminal = await activator.terminalFor(cli.name, cli.version);
|
|
14296
|
+
if (!terminal) return { refused: "The agent's sign-in didn't start on this machine." };
|
|
14297
|
+
return { terminal, socketPath: await terminals.pathOf(terminal) };
|
|
14177
14298
|
}
|
|
14178
14299
|
const workspace = workspaces.get(target.conversationId);
|
|
14179
14300
|
if (!workspace) return { refused: "This machine doesn't hold that conversation." };
|
|
@@ -15592,7 +15713,8 @@ var HELP4 = `herd: work on HerdingChats conversations from this computer.
|
|
|
15592
15713
|
Usage: herd <command>
|
|
15593
15714
|
|
|
15594
15715
|
On this computer:
|
|
15595
|
-
login
|
|
15716
|
+
login [--device] Sign in through your browser. --device, or running over SSH, signs in from
|
|
15717
|
+
another device with a code to paste
|
|
15596
15718
|
logout Remove this computer's sign-in
|
|
15597
15719
|
daemon [--repo <path>]...
|
|
15598
15720
|
Run this machine's daemon, working from its root folder. Repos given with
|
|
@@ -15676,7 +15798,7 @@ async function main(args) {
|
|
|
15676
15798
|
console.log(HERD_VERSION);
|
|
15677
15799
|
return 0;
|
|
15678
15800
|
case "login":
|
|
15679
|
-
return await runLogin();
|
|
15801
|
+
return await runLogin(rest);
|
|
15680
15802
|
case "logout":
|
|
15681
15803
|
return await runLogout();
|
|
15682
15804
|
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.701",
|
|
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": "dee83b97beed4102f6d56d692f1c1e306b62dbb6"
|
|
26
26
|
}
|
package/terminal-host.mjs
CHANGED
|
@@ -230,6 +230,16 @@ var TerminalHost = class {
|
|
|
230
230
|
if (held.info.exited) return;
|
|
231
231
|
held.pty.resize(cols, rows);
|
|
232
232
|
held.term.resize(cols, rows);
|
|
233
|
+
for (const socket of held.watchers) {
|
|
234
|
+
socket.write(`${JSON.stringify({ type: "resize", cols, rows })}
|
|
235
|
+
`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/** The screen as `attach` and `watch` send it first: its contents, and its size. */
|
|
239
|
+
async sized(name) {
|
|
240
|
+
const held = this.get(name);
|
|
241
|
+
const data = await this.snapshot(name);
|
|
242
|
+
return { type: "snapshot", data, cols: held.term.cols, rows: held.term.rows };
|
|
233
243
|
}
|
|
234
244
|
subscribe(name, socket) {
|
|
235
245
|
const held = this.get(name);
|
|
@@ -253,7 +263,7 @@ var TerminalHost = class {
|
|
|
253
263
|
return;
|
|
254
264
|
}
|
|
255
265
|
if (size.cols && size.rows) this.resize(name, size.cols, size.rows);
|
|
256
|
-
socket.write(`${JSON.stringify(
|
|
266
|
+
socket.write(`${JSON.stringify(await this.sized(name))}
|
|
257
267
|
`);
|
|
258
268
|
held.viewers.add(socket);
|
|
259
269
|
held.drivenAt = Date.now();
|
|
@@ -294,7 +304,7 @@ var TerminalHost = class {
|
|
|
294
304
|
`);
|
|
295
305
|
return;
|
|
296
306
|
}
|
|
297
|
-
socket.write(`${JSON.stringify(
|
|
307
|
+
socket.write(`${JSON.stringify(await this.sized(name))}
|
|
298
308
|
`);
|
|
299
309
|
held.watchers.add(socket);
|
|
300
310
|
held.subscribers.add(socket);
|