@herdingchats/friday 0.1.561 → 0.1.567
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 +70 -25
- 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.567", gitHead: "816afa2c448b0a5330ddece39cdb0567ef65a3c5" };
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -3156,6 +3156,7 @@ import { spawn as spawn6 } from "node:child_process";
|
|
|
3156
3156
|
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
|
|
3157
3157
|
import { homedir as homedir4 } from "node:os";
|
|
3158
3158
|
import { dirname as dirname10, join as join14 } from "node:path";
|
|
3159
|
+
import { createInterface } from "node:readline/promises";
|
|
3159
3160
|
import { z as z22 } from "zod";
|
|
3160
3161
|
|
|
3161
3162
|
// src/auth.ts
|
|
@@ -3384,7 +3385,7 @@ var HERD_FLAGS = {
|
|
|
3384
3385
|
persona: []
|
|
3385
3386
|
};
|
|
3386
3387
|
var FRIDAY_FLAGS = {
|
|
3387
|
-
start: ["--agents"],
|
|
3388
|
+
start: ["--agents", "--config"],
|
|
3388
3389
|
stop: [],
|
|
3389
3390
|
status: [],
|
|
3390
3391
|
logs: ["--follow", "-f", "-n"]
|
|
@@ -5170,16 +5171,18 @@ async function chooseAgents(options, print) {
|
|
|
5170
5171
|
}
|
|
5171
5172
|
const saved = loaded.status === "ok" ? loaded.config : void 0;
|
|
5172
5173
|
const choices = agentChoices(installed, saved);
|
|
5173
|
-
const asked = options.given ? null : await (options.pick?.(choices) ?? Promise.resolve(null));
|
|
5174
|
+
const asked = options.given || !options.ask ? null : await (options.pick?.(choices) ?? Promise.resolve(null));
|
|
5174
5175
|
const shares = options.given ?? asked ?? // No terminal: what was chosen before, or what's installed.
|
|
5175
5176
|
Object.fromEntries(choices.map((choice) => [choice.cli, choice.share]));
|
|
5176
|
-
|
|
5177
|
+
const choosing = options.ask || options.given !== void 0;
|
|
5178
|
+
if (choosing && (!sameChoice(saved, shares) || !existsSync5(options.configPath))) {
|
|
5177
5179
|
mkdirSync6(dirname6(options.configPath), { recursive: true, mode: 448 });
|
|
5178
5180
|
writeFileSync5(options.configPath, configSource(withChoice(saved, shares)), { mode: 384 });
|
|
5179
5181
|
}
|
|
5180
5182
|
const chosen = ADAPTERS.map(({ cli }) => cli).filter((cli) => (shares[cli] ?? "off") !== "off");
|
|
5183
|
+
const change = options.ask ? "" : ` ${START_COMMAND} --config changes that.`;
|
|
5181
5184
|
print(
|
|
5182
|
-
chosen.length > 0 ? `This machine runs ${chosen.map((cli) => `${label2(cli)} (${SHARE_LABELS[shares[cli]]})`).join(", ")}
|
|
5185
|
+
chosen.length > 0 ? `This machine runs ${chosen.map((cli) => `${label2(cli)} (${SHARE_LABELS[shares[cli]]})`).join(", ")}.${change}` : `This machine runs no agents, so it takes no work. ${START_COMMAND} --config chooses some.`
|
|
5183
5186
|
);
|
|
5184
5187
|
return chosen;
|
|
5185
5188
|
}
|
|
@@ -5888,16 +5891,25 @@ async function runStart(options) {
|
|
|
5888
5891
|
const existing = readRoot2(paths);
|
|
5889
5892
|
const wanted = chooseRoot(options.cwd, options.home);
|
|
5890
5893
|
const running = runningSupervisor(paths);
|
|
5894
|
+
let moving = false;
|
|
5891
5895
|
if (existing && real(existing) !== real(wanted)) {
|
|
5892
5896
|
print(`This machine's root is ${existing}, which holds the repos Friday works on.`);
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5897
|
+
moving = await options.confirm?.(
|
|
5898
|
+
`Make ${wanted} the root instead? Conversations already going carry on where they are.`
|
|
5899
|
+
) ?? false;
|
|
5900
|
+
if (!moving) {
|
|
5901
|
+
print(
|
|
5902
|
+
options.confirm ? "The root stays where it is." : `Run this in a terminal to move the root to ${wanted}.`
|
|
5903
|
+
);
|
|
5904
|
+
if (running) {
|
|
5905
|
+
print(`Friday is running there, version ${running.version}, so nothing changed.`);
|
|
5906
|
+
await waitForAgents(options, await chooseAndAsk(options, print), print);
|
|
5907
|
+
await printSetup(options, print);
|
|
5908
|
+
return 0;
|
|
5909
|
+
}
|
|
5898
5910
|
}
|
|
5899
5911
|
}
|
|
5900
|
-
const root = existing ?? wanted;
|
|
5912
|
+
const root = moving ? wanted : existing ?? wanted;
|
|
5901
5913
|
if (running && root === existing) {
|
|
5902
5914
|
print(`Friday is running from ${root}, version ${running.version}. Nothing changed.`);
|
|
5903
5915
|
await waitForAgents(options, await chooseAndAsk(options, print), print);
|
|
@@ -5906,6 +5918,7 @@ async function runStart(options) {
|
|
|
5906
5918
|
}
|
|
5907
5919
|
const repo = insideWorkTree(root);
|
|
5908
5920
|
if (repo) {
|
|
5921
|
+
if (moving) print("The root stays where it is.");
|
|
5909
5922
|
throw new StartError(
|
|
5910
5923
|
`${root} is inside the git repo ${repo}, so it can't be the machine's root: every conversation would load that repo's settings. Run ${START_COMMAND} in a folder outside any repo, such as the one that holds your checkouts, like ~/code.`
|
|
5911
5924
|
);
|
|
@@ -5929,13 +5942,18 @@ async function runStart(options) {
|
|
|
5929
5942
|
`Stopped the old daemon: removed ${retired.join(" and ")}. Its checkout is left as it was.`
|
|
5930
5943
|
);
|
|
5931
5944
|
await ensureInstalled(options, print);
|
|
5932
|
-
mkdirSync10(root, { recursive: true });
|
|
5933
|
-
writeRoot(paths, root);
|
|
5934
5945
|
const command = [process.execPath, binOf(paths.current, "friday"), "supervise"];
|
|
5935
5946
|
const env = { HERD_HOME: paths.herdHome, ...options.env };
|
|
5936
5947
|
const service = (options.service ?? defaultService(options))(command, env);
|
|
5948
|
+
if (moving && running) {
|
|
5949
|
+
print(`Stopping Friday at ${existing} to start it again from ${root}.`);
|
|
5950
|
+
await service.stop();
|
|
5951
|
+
await stopped(options);
|
|
5952
|
+
}
|
|
5953
|
+
mkdirSync10(root, { recursive: true });
|
|
5954
|
+
writeRoot(paths, root);
|
|
5937
5955
|
const changed = await service.install();
|
|
5938
|
-
await service.start(changed);
|
|
5956
|
+
await service.start(changed || moving);
|
|
5939
5957
|
print(
|
|
5940
5958
|
`Friday is running from ${root}, version ${currentVersion(paths)}, ${service.kind === "launchd" ? "and starts whenever you log in" : service.kind === "systemd" ? "and starts whenever you log in, as a systemd user service" : "in the background"}.`
|
|
5941
5959
|
);
|
|
@@ -5946,10 +5964,22 @@ async function runStart(options) {
|
|
|
5946
5964
|
);
|
|
5947
5965
|
return 0;
|
|
5948
5966
|
}
|
|
5967
|
+
async function stopped(options) {
|
|
5968
|
+
const deadline = Date.now() + (options.stopWaitMs ?? 15e3);
|
|
5969
|
+
while (runningSupervisor(options.paths)) {
|
|
5970
|
+
if (Date.now() > deadline) {
|
|
5971
|
+
throw new StartError(
|
|
5972
|
+
`Friday didn't stop within ${Math.round((options.stopWaitMs ?? 15e3) / 1e3)} seconds, so the root wasn't moved. Run ${FRIDAY_COMMAND} stop, then this again.`
|
|
5973
|
+
);
|
|
5974
|
+
}
|
|
5975
|
+
await new Promise((done) => setTimeout(done, options.stopPollMs ?? 250));
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5949
5978
|
async function chooseAndAsk(options, print) {
|
|
5950
5979
|
if (!options.agents) return null;
|
|
5951
5980
|
const chosen = await chooseAgents(options.agents, print);
|
|
5952
5981
|
if (!chosen) return null;
|
|
5982
|
+
if (!options.agents.ask && !options.agents.given) return null;
|
|
5953
5983
|
return { chosen, since: askToActivate(options.paths.herdHome) };
|
|
5954
5984
|
}
|
|
5955
5985
|
async function waitForAgents(options, asked, print) {
|
|
@@ -6235,10 +6265,12 @@ Usage: ${FRIDAY_COMMAND} <command>
|
|
|
6235
6265
|
|
|
6236
6266
|
start Make this computer a machine, with the folder this runs in as its root, which
|
|
6237
6267
|
holds your repos, such as ~/code. Signs in through your browser when
|
|
6238
|
-
needed, asks how much of each agent you have, starts the daemon
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6268
|
+
needed, asks how much of each agent you have the first time, starts the daemon
|
|
6269
|
+
now and whenever you log in, then shows what's left to set up. Safe to run
|
|
6270
|
+
again. Run in another folder, it offers to move the root there. --config asks
|
|
6271
|
+
about the agents again, and --agents claude=lot,codex=little sets their shares
|
|
6272
|
+
without asking: off, little, some or lot, where a bare name is some and one
|
|
6273
|
+
left out off
|
|
6242
6274
|
stop Stop the daemon until you log in again, or run start again
|
|
6243
6275
|
status Whether the daemon is running, which version, and its root
|
|
6244
6276
|
logs [-f] Show the daemon's latest log lines. -f keeps following them
|
|
@@ -6312,7 +6344,11 @@ async function friday(args, deps = defaults()) {
|
|
|
6312
6344
|
print: deps.print,
|
|
6313
6345
|
setup: fetchSetup,
|
|
6314
6346
|
...process.env.HERD_PACKAGE_SPEC ? { install: { spec: process.env.HERD_PACKAGE_SPEC } } : {},
|
|
6347
|
+
...process.stdin.isTTY && process.stdout.isTTY ? { confirm: confirmInTerminal } : {},
|
|
6315
6348
|
agents: {
|
|
6349
|
+
// The choice lives in herd.config.ts, so it's asked for only before there is one, or
|
|
6350
|
+
// with --config.
|
|
6351
|
+
ask: rest.includes("--config") || !existsSync11(herdConfigIn(deps.paths.herdHome)),
|
|
6316
6352
|
installed: async () => installedAgents(),
|
|
6317
6353
|
load: () => loadHerdConfig(herdConfigIn(deps.paths.herdHome)),
|
|
6318
6354
|
configPath: herdConfigIn(deps.paths.herdHome),
|
|
@@ -6345,6 +6381,14 @@ ${HELP}`);
|
|
|
6345
6381
|
throw error;
|
|
6346
6382
|
}
|
|
6347
6383
|
}
|
|
6384
|
+
async function confirmInTerminal(question) {
|
|
6385
|
+
const lines = createInterface({ input: process.stdin, output: process.stdout });
|
|
6386
|
+
try {
|
|
6387
|
+
return /^y(es)?$/i.test((await lines.question(`${question} [y/N] `)).trim());
|
|
6388
|
+
} finally {
|
|
6389
|
+
lines.close();
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6348
6392
|
function agentsFlag(args) {
|
|
6349
6393
|
const at = args.findIndex((arg) => arg === "--agents" || arg.startsWith("--agents="));
|
|
6350
6394
|
if (at < 0) return void 0;
|
|
@@ -7603,6 +7647,7 @@ var QUEUED = /\(alt\+m to send immediately\)\s*$|Press up to edit queued message
|
|
|
7603
7647
|
var TRUST_QUESTION = /^\s*Do you trust the contents of this project\?/;
|
|
7604
7648
|
var TRUST_YES = /^\s*(>)?\s*Yes, I trust this folder/;
|
|
7605
7649
|
var NOT_SIGNED_IN = /You are currently not signed in\./;
|
|
7650
|
+
var LOGIN_METHODS = /^\s*Select login method:/;
|
|
7606
7651
|
var RESUME_HINT = /^Resume with -c/;
|
|
7607
7652
|
function promptBox2(lines) {
|
|
7608
7653
|
for (let bottom = lines.length - 1; bottom >= 2; bottom--) {
|
|
@@ -7641,7 +7686,7 @@ function readAgyScreen(raw) {
|
|
|
7641
7686
|
trustPrompt: { yesHighlighted: Boolean(yes && TRUST_YES.exec(yes)?.[1]) }
|
|
7642
7687
|
};
|
|
7643
7688
|
}
|
|
7644
|
-
if (lines.some((line2) => NOT_SIGNED_IN.test(line2))) {
|
|
7689
|
+
if (lines.some((line2) => NOT_SIGNED_IN.test(line2)) && lines.some((line2) => LOGIN_METHODS.test(line2))) {
|
|
7645
7690
|
return { ...reading, program: "agy", state: "signed_out" };
|
|
7646
7691
|
}
|
|
7647
7692
|
const last = [...lines].reverse().find((line2) => line2.trim().length > 0) ?? "";
|
|
@@ -9763,7 +9808,7 @@ var HostTerminals = class {
|
|
|
9763
9808
|
await this.askFor(name, { op: "kill", name });
|
|
9764
9809
|
}
|
|
9765
9810
|
async retire(canStop) {
|
|
9766
|
-
const
|
|
9811
|
+
const stopped2 = [];
|
|
9767
9812
|
for (const host of await this.discover()) {
|
|
9768
9813
|
if (host.current) continue;
|
|
9769
9814
|
const version = host.hello.version ?? "unversioned";
|
|
@@ -9775,12 +9820,12 @@ var HostTerminals = class {
|
|
|
9775
9820
|
continue;
|
|
9776
9821
|
}
|
|
9777
9822
|
if (await this.stop(host)) {
|
|
9778
|
-
|
|
9823
|
+
stopped2.push(version);
|
|
9779
9824
|
this.log(`Retired terminal host ${version}, which had nothing left running.`);
|
|
9780
9825
|
}
|
|
9781
9826
|
}
|
|
9782
|
-
if (
|
|
9783
|
-
return
|
|
9827
|
+
if (stopped2.length > 0) await this.discover();
|
|
9828
|
+
return stopped2;
|
|
9784
9829
|
}
|
|
9785
9830
|
/** Stops a host: politely where it knows how, and otherwise by its process. */
|
|
9786
9831
|
async stop(host) {
|
|
@@ -10855,12 +10900,12 @@ async function retireHosts(ws) {
|
|
|
10855
10900
|
if (!terminals.retire || Date.now() - ws.retiredAt < every) return;
|
|
10856
10901
|
ws.retiredAt = Date.now();
|
|
10857
10902
|
const inHost = (version) => Object.values(ws.load()).filter((workspace) => workspace.hostVersion === version);
|
|
10858
|
-
const
|
|
10903
|
+
const stopped2 = await terminals.retire(
|
|
10859
10904
|
(version) => inHost(version).every(
|
|
10860
10905
|
(workspace) => !workspace.claimId && (workspace.status === "idle" || workspace.status === "exited")
|
|
10861
10906
|
)
|
|
10862
10907
|
);
|
|
10863
|
-
for (const version of
|
|
10908
|
+
for (const version of stopped2) {
|
|
10864
10909
|
for (const workspace of inHost(version)) {
|
|
10865
10910
|
const id2 = workspace.conversationId;
|
|
10866
10911
|
await ws.serially(id2, async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herdingchats/friday",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.567",
|
|
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": "816afa2c448b0a5330ddece39cdb0567ef65a3c5"
|
|
26
26
|
}
|