@herdingchats/friday 0.1.635 → 0.1.641
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 +273 -236
- 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.641", gitHead: "e1411e26f2203efeb542823bfefded0e5dd8b296" };
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -2385,7 +2385,7 @@ var init_widget = __esm({
|
|
|
2385
2385
|
});
|
|
2386
2386
|
|
|
2387
2387
|
// ../common-board/src/widgets/conversations.tsx
|
|
2388
|
-
import { z as
|
|
2388
|
+
import { z as z29 } from "zod";
|
|
2389
2389
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2390
2390
|
function badge(item) {
|
|
2391
2391
|
if (pullRequestBadge(item)) {
|
|
@@ -2429,10 +2429,10 @@ var init_conversations = __esm({
|
|
|
2429
2429
|
conversations2 = defineWidget({
|
|
2430
2430
|
kind: "conversations",
|
|
2431
2431
|
describe: "Blocked, working and queued conversations, then the latest done, with what each is doing, where it came from and who holds it",
|
|
2432
|
-
options:
|
|
2433
|
-
states:
|
|
2434
|
-
limit:
|
|
2435
|
-
show:
|
|
2432
|
+
options: z29.object({
|
|
2433
|
+
states: z29.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
|
|
2434
|
+
limit: z29.number().int().min(1).max(BOARD_ITEMS).default(4),
|
|
2435
|
+
show: z29.array(z29.enum(DETAILS)).default([...DETAILS])
|
|
2436
2436
|
}),
|
|
2437
2437
|
reads: ["items", "machines"],
|
|
2438
2438
|
Component: ({ board, options, theme, now }) => {
|
|
@@ -2476,7 +2476,7 @@ var init_conversations = __esm({
|
|
|
2476
2476
|
});
|
|
2477
2477
|
|
|
2478
2478
|
// ../common-board/src/widgets/counts.tsx
|
|
2479
|
-
import { z as
|
|
2479
|
+
import { z as z30 } from "zod";
|
|
2480
2480
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2481
2481
|
var COUNTS, LABELS2, counts;
|
|
2482
2482
|
var init_counts = __esm({
|
|
@@ -2496,8 +2496,8 @@ var init_counts = __esm({
|
|
|
2496
2496
|
counts = defineWidget({
|
|
2497
2497
|
kind: "counts",
|
|
2498
2498
|
describe: "How many conversations are stuck, unanswered, waiting and working, and done today",
|
|
2499
|
-
options:
|
|
2500
|
-
show:
|
|
2499
|
+
options: z30.object({
|
|
2500
|
+
show: z30.array(z30.enum(COUNTS)).min(1).max(5).default([...COUNTS])
|
|
2501
2501
|
}),
|
|
2502
2502
|
reads: ["counts", "doneToday"],
|
|
2503
2503
|
Component: ({ board, options, theme }) => {
|
|
@@ -2536,7 +2536,7 @@ var init_counts = __esm({
|
|
|
2536
2536
|
});
|
|
2537
2537
|
|
|
2538
2538
|
// ../common-board/src/widgets/header.tsx
|
|
2539
|
-
import { z as
|
|
2539
|
+
import { z as z31 } from "zod";
|
|
2540
2540
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2541
2541
|
function summary(board) {
|
|
2542
2542
|
const parts = [
|
|
@@ -2561,9 +2561,9 @@ var init_header = __esm({
|
|
|
2561
2561
|
header = defineWidget({
|
|
2562
2562
|
kind: "header",
|
|
2563
2563
|
describe: "The board's title, a line of how much is open, and when it was drawn",
|
|
2564
|
-
options:
|
|
2565
|
-
title:
|
|
2566
|
-
summary:
|
|
2564
|
+
options: z31.object({
|
|
2565
|
+
title: z31.string().min(1).max(40).default(`@${BOT_NAME}`),
|
|
2566
|
+
summary: z31.boolean().default(true)
|
|
2567
2567
|
}),
|
|
2568
2568
|
reads: ["counts"],
|
|
2569
2569
|
Component: ({ board, options, theme, now }) => {
|
|
@@ -2581,7 +2581,7 @@ var init_header = __esm({
|
|
|
2581
2581
|
});
|
|
2582
2582
|
|
|
2583
2583
|
// ../common-board/src/widgets/machines.tsx
|
|
2584
|
-
import { z as
|
|
2584
|
+
import { z as z32 } from "zod";
|
|
2585
2585
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2586
2586
|
function usageText(worker) {
|
|
2587
2587
|
const usage = worker.usage;
|
|
@@ -2612,7 +2612,7 @@ var init_machines2 = __esm({
|
|
|
2612
2612
|
machines = defineWidget({
|
|
2613
2613
|
kind: "machines",
|
|
2614
2614
|
describe: "One bubble per agent on an online machine, with its usage, most recently active first",
|
|
2615
|
-
options:
|
|
2615
|
+
options: z32.object({ limit: z32.number().int().min(1).max(8).default(3) }),
|
|
2616
2616
|
reads: ["machines"],
|
|
2617
2617
|
Component: ({ board, options, theme }) => {
|
|
2618
2618
|
const workers2 = board.machines.filter((machine) => machine.online).flatMap((machine) => machine.workers.map((worker) => ({ ...worker, machine: machine.name }))).sort((a, b) => (b.reportedAt ?? 0) - (a.reportedAt ?? 0));
|
|
@@ -2729,9 +2729,9 @@ var init_board2 = __esm({
|
|
|
2729
2729
|
});
|
|
2730
2730
|
|
|
2731
2731
|
// ../common-board/src/composition.ts
|
|
2732
|
-
import { z as
|
|
2732
|
+
import { z as z33 } from "zod";
|
|
2733
2733
|
function readComposition(stored) {
|
|
2734
|
-
const shape =
|
|
2734
|
+
const shape = z33.object({ widgets: z33.array(placedWidgetSchema) }).safeParse(stored);
|
|
2735
2735
|
if (!shape.success) return DEFAULT_COMPOSITION;
|
|
2736
2736
|
const known = shape.data.widgets.filter(
|
|
2737
2737
|
(widget) => WIDGETS[widget.kind]?.options.safeParse(widget.options).success
|
|
@@ -2746,11 +2746,11 @@ var init_composition = __esm({
|
|
|
2746
2746
|
init_registry();
|
|
2747
2747
|
MAX_WIDGETS = 12;
|
|
2748
2748
|
MAX_COMPOSITION_BYTES = 4096;
|
|
2749
|
-
placedWidgetSchema =
|
|
2750
|
-
kind:
|
|
2751
|
-
options:
|
|
2749
|
+
placedWidgetSchema = z33.object({
|
|
2750
|
+
kind: z33.string().min(1),
|
|
2751
|
+
options: z33.record(z33.string(), z33.unknown()).default({})
|
|
2752
2752
|
});
|
|
2753
|
-
compositionSchema =
|
|
2753
|
+
compositionSchema = z33.object({ widgets: z33.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
|
|
2754
2754
|
if (JSON.stringify(composition).length > MAX_COMPOSITION_BYTES) {
|
|
2755
2755
|
context2.addIssue({
|
|
2756
2756
|
code: "custom",
|
|
@@ -4567,6 +4567,7 @@ var localRequestSchema = z19.object({
|
|
|
4567
4567
|
"watch",
|
|
4568
4568
|
"repos",
|
|
4569
4569
|
"repo",
|
|
4570
|
+
"repo-add",
|
|
4570
4571
|
"activate"
|
|
4571
4572
|
]),
|
|
4572
4573
|
// The key of the agent's start, from `HERD_START`, which `herd inbox` proves the start with.
|
|
@@ -4578,6 +4579,11 @@ var localRequestSchema = z19.object({
|
|
|
4578
4579
|
base: z19.string().min(1).max(300).optional(),
|
|
4579
4580
|
fetchFailed: z19.string().max(1e3).optional()
|
|
4580
4581
|
}).optional(),
|
|
4582
|
+
// `herd repo`: what the agent named and the folder it asked for, for the daemon to add.
|
|
4583
|
+
repoAdd: z19.object({
|
|
4584
|
+
asked: z19.string().min(1).max(500),
|
|
4585
|
+
folder: z19.string().min(1).max(100).optional()
|
|
4586
|
+
}).optional(),
|
|
4581
4587
|
// `herd delegate --repo`, given once for each repo the delegate works in.
|
|
4582
4588
|
repos: z19.array(z19.string().min(1).max(500)).max(10).optional(),
|
|
4583
4589
|
// `herd watch`, `herd unwatch` and `herd watching`: what to do, and the pull request.
|
|
@@ -11071,6 +11077,142 @@ import { join as join32 } from "node:path";
|
|
|
11071
11077
|
// src/workspaces/repos.ts
|
|
11072
11078
|
init_define_HERD_PACKAGE();
|
|
11073
11079
|
import { join as join29 } from "node:path";
|
|
11080
|
+
|
|
11081
|
+
// src/commands/repo.ts
|
|
11082
|
+
init_define_HERD_PACKAGE();
|
|
11083
|
+
import { z as z27 } from "zod";
|
|
11084
|
+
var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
|
|
11085
|
+
|
|
11086
|
+
Checks a repo out in this conversation's folder, on a branch named for the conversation, started
|
|
11087
|
+
from the freshly fetched default branch, and records it in the conversation. It comes from your
|
|
11088
|
+
owner's own checkout of the repo in the machine's root, and gets that checkout's local files git
|
|
11089
|
+
ignores, such as .env, copied in. A repo the root doesn't have is cloned into it first, with your
|
|
11090
|
+
owner's own git credentials. owner/name means one of the repos herd repos lists, or else
|
|
11091
|
+
github.com/owner/name. The folder is the repo's name, or <owner>-<name> when that's taken, unless
|
|
11092
|
+
you give one. Running it again for a repo that's already here changes nothing.`;
|
|
11093
|
+
var REPOS_HELP = `Usage: herd repos [--json]
|
|
11094
|
+
|
|
11095
|
+
Lists the repos in this conversation's folder, the repos checked out in this machine's root, and
|
|
11096
|
+
the repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
|
|
11097
|
+
var repoContextSchema = z27.object({
|
|
11098
|
+
folder: z27.string(),
|
|
11099
|
+
root: z27.string(),
|
|
11100
|
+
branch: z27.string(),
|
|
11101
|
+
legacy: z27.boolean(),
|
|
11102
|
+
legacyReason: z27.string().optional(),
|
|
11103
|
+
recorded: z27.array(z27.object({ folder: z27.string(), remote: z27.string(), branch: z27.string() })),
|
|
11104
|
+
cloned: z27.array(z27.object({ key: z27.string(), remote: z27.string() })),
|
|
11105
|
+
described: z27.array(
|
|
11106
|
+
z27.object({ remote: z27.string(), key: z27.string().nullable(), about: z27.string().optional() })
|
|
11107
|
+
)
|
|
11108
|
+
});
|
|
11109
|
+
var isUrl = (text4) => /^[a-z][a-z0-9+.-]*:\/\//i.test(text4) || text4.startsWith("/") || /^[^@/\s]+@[^:/\s]+:/.test(text4);
|
|
11110
|
+
function resolveRepo(asked, context2) {
|
|
11111
|
+
const text4 = asked.trim().replace(/\/+$/, "");
|
|
11112
|
+
if (!text4) throw new HerdError(REPO_HELP);
|
|
11113
|
+
if (isUrl(text4)) return text4;
|
|
11114
|
+
const known = [
|
|
11115
|
+
...context2.recorded.map((repo) => repo.remote),
|
|
11116
|
+
...context2.cloned.map((repo) => repo.remote),
|
|
11117
|
+
...context2.described.map((repo) => repo.remote)
|
|
11118
|
+
];
|
|
11119
|
+
const wanted = text4.toLowerCase().replace(/\.git$/, "");
|
|
11120
|
+
const parts = wanted.split("/");
|
|
11121
|
+
const matches = known.filter((remote) => {
|
|
11122
|
+
const key = repoKey(remote);
|
|
11123
|
+
if (!key) return false;
|
|
11124
|
+
return parts.length === 1 ? repoName(key).toLowerCase() === wanted : key.toLowerCase() === wanted || key.toLowerCase().endsWith(`/${wanted}`);
|
|
11125
|
+
});
|
|
11126
|
+
const keys = [...new Set(matches.map((remote) => repoKey(remote)))];
|
|
11127
|
+
if (keys.length === 1 && matches[0]) return matches[0];
|
|
11128
|
+
if (keys.length > 1) {
|
|
11129
|
+
throw new HerdError(
|
|
11130
|
+
`More than one repo is called ${text4}: ${keys.join(", ")}. Name one of them.`
|
|
11131
|
+
);
|
|
11132
|
+
}
|
|
11133
|
+
if (parts.length === 1) {
|
|
11134
|
+
throw new HerdError(
|
|
11135
|
+
`herd doesn't know a repo called ${text4}. Name it as owner/name or a URL. herd repos lists the ones it knows.`
|
|
11136
|
+
);
|
|
11137
|
+
}
|
|
11138
|
+
return parts.length >= 3 && parts[0]?.includes(".") ? `https://${text4}` : `https://github.com/${text4}`;
|
|
11139
|
+
}
|
|
11140
|
+
async function context() {
|
|
11141
|
+
const reply = await fromTerminal({ op: "repos" });
|
|
11142
|
+
const parsed = repoContextSchema.safeParse(reply);
|
|
11143
|
+
if (!parsed.success)
|
|
11144
|
+
throw new HerdError("The daemon's answer about this folder wasn't readable.");
|
|
11145
|
+
return parsed.data;
|
|
11146
|
+
}
|
|
11147
|
+
async function runRepo(rest) {
|
|
11148
|
+
const args = rest.filter((arg) => arg !== "--");
|
|
11149
|
+
if (args.length === 0 || args.length > 2 || args[0] === "--help" || args[0] === "-h") {
|
|
11150
|
+
console.log(REPO_HELP);
|
|
11151
|
+
return args[0] === "--help" || args[0] === "-h" ? 0 : 1;
|
|
11152
|
+
}
|
|
11153
|
+
const [asked = "", folderName] = args;
|
|
11154
|
+
if (folderName !== void 0 && !validFolderName(folderName)) {
|
|
11155
|
+
throw new HerdError(
|
|
11156
|
+
`${folderName} can't be a folder name here. Use letters, digits, . _ or -.`
|
|
11157
|
+
);
|
|
11158
|
+
}
|
|
11159
|
+
const reply = await fromTerminal({
|
|
11160
|
+
op: "repo-add",
|
|
11161
|
+
repoAdd: { asked, ...folderName !== void 0 ? { folder: folderName } : {} }
|
|
11162
|
+
});
|
|
11163
|
+
const lines = z27.array(z27.string()).safeParse(reply.lines);
|
|
11164
|
+
if (!lines.success) throw new HerdError("The daemon's answer about this repo wasn't readable.");
|
|
11165
|
+
console.log(lines.data.join("\n"));
|
|
11166
|
+
return 0;
|
|
11167
|
+
}
|
|
11168
|
+
function formatRepos(here) {
|
|
11169
|
+
const inFolder = new Set(here.recorded.map((repo) => repoKey(repo.remote)));
|
|
11170
|
+
const lines = ["In this conversation's folder:"];
|
|
11171
|
+
if (here.recorded.length === 0) lines.push(" none yet");
|
|
11172
|
+
for (const repo of here.recorded)
|
|
11173
|
+
lines.push(` ${repo.folder} ${repo.remote} on ${repo.branch}`);
|
|
11174
|
+
const cloned = here.cloned.filter((repo) => !inFolder.has(repo.key));
|
|
11175
|
+
lines.push("", "Checked out in the root:");
|
|
11176
|
+
if (cloned.length === 0) lines.push(" none others");
|
|
11177
|
+
const about = new Map(here.described.map((repo) => [repo.key, repo.about]));
|
|
11178
|
+
for (const repo of cloned) {
|
|
11179
|
+
const said = about.get(repo.key);
|
|
11180
|
+
lines.push(` ${repo.key}${said ? ` ${said}` : ""}`);
|
|
11181
|
+
}
|
|
11182
|
+
const clonedKeys = new Set(here.cloned.map((repo) => repo.key));
|
|
11183
|
+
const described = here.described.filter((repo) => !repo.key || !clonedKeys.has(repo.key));
|
|
11184
|
+
if (described.length > 0) {
|
|
11185
|
+
lines.push("", "Described in herd.config.ts, not cloned yet:");
|
|
11186
|
+
for (const repo of described) {
|
|
11187
|
+
lines.push(` ${repo.key ?? repo.remote}${repo.about ? ` ${repo.about}` : ""}`);
|
|
11188
|
+
}
|
|
11189
|
+
}
|
|
11190
|
+
lines.push("", "Add one with herd repo <url or owner/name> [folder].");
|
|
11191
|
+
return lines.join("\n");
|
|
11192
|
+
}
|
|
11193
|
+
async function runRepos(rest) {
|
|
11194
|
+
const unknown = rest.find((arg) => arg !== "--json");
|
|
11195
|
+
if (unknown === "--help" || unknown === "-h") {
|
|
11196
|
+
console.log(REPOS_HELP);
|
|
11197
|
+
return 0;
|
|
11198
|
+
}
|
|
11199
|
+
if (unknown) throw new HerdError(`herd repos doesn't understand "${unknown}".`);
|
|
11200
|
+
const here = await context();
|
|
11201
|
+
if (rest.includes("--json")) {
|
|
11202
|
+
console.log(
|
|
11203
|
+
JSON.stringify(
|
|
11204
|
+
{ conversation: here.recorded, cloned: here.cloned, described: here.described },
|
|
11205
|
+
null,
|
|
11206
|
+
2
|
|
11207
|
+
)
|
|
11208
|
+
);
|
|
11209
|
+
} else {
|
|
11210
|
+
console.log(formatRepos(here));
|
|
11211
|
+
}
|
|
11212
|
+
return 0;
|
|
11213
|
+
}
|
|
11214
|
+
|
|
11215
|
+
// src/workspaces/repos.ts
|
|
11074
11216
|
var rootOf = (ws) => ws.options.root?.() ?? ws.options.config().root ?? defaultRoot();
|
|
11075
11217
|
var gitEnvOf = (ws) => ws.options.gitEnv?.() ?? loginShellEnv();
|
|
11076
11218
|
function knownRemotes(ws) {
|
|
@@ -11237,6 +11379,91 @@ function handoffRepos(workspace) {
|
|
|
11237
11379
|
unpushed: hasUnpushed(join29(workspace.folder, repo.folder))
|
|
11238
11380
|
}));
|
|
11239
11381
|
}
|
|
11382
|
+
async function addRepo(ws, workspace, request) {
|
|
11383
|
+
if (workspace.legacy) return { ok: false, error: LEGACY };
|
|
11384
|
+
const wanted = request.repoAdd;
|
|
11385
|
+
if (!wanted) return { ok: false, error: REPO_HELP };
|
|
11386
|
+
if (wanted.folder !== void 0 && !validFolderName(wanted.folder)) {
|
|
11387
|
+
return {
|
|
11388
|
+
ok: false,
|
|
11389
|
+
error: `${wanted.folder} can't be a folder name here. Use letters, digits, . _ or -.`
|
|
11390
|
+
};
|
|
11391
|
+
}
|
|
11392
|
+
const here = repoContextSchema.parse(repoContext(ws, workspace));
|
|
11393
|
+
let source;
|
|
11394
|
+
try {
|
|
11395
|
+
source = resolveRepo(wanted.asked, here);
|
|
11396
|
+
} catch (error) {
|
|
11397
|
+
if (error instanceof HerdError) return { ok: false, error: error.message };
|
|
11398
|
+
throw error;
|
|
11399
|
+
}
|
|
11400
|
+
const key = repoKey(source);
|
|
11401
|
+
if (!key) return { ok: false, error: `${wanted.asked} isn't a repo's URL.` };
|
|
11402
|
+
const place = folderFor(workspace.folder, key, wanted.folder);
|
|
11403
|
+
if (!place) {
|
|
11404
|
+
return {
|
|
11405
|
+
ok: false,
|
|
11406
|
+
error: wanted.folder ? `${wanted.folder} is already taken in this conversation's folder. Give another name.` : `Both ${repoName(key)} and ${repoOwner(key)}-${repoName(key)} are taken in this conversation's folder. Give a folder name: herd repo ${wanted.asked} <folder>.`
|
|
11407
|
+
};
|
|
11408
|
+
}
|
|
11409
|
+
const path = join29(workspace.folder, place.name);
|
|
11410
|
+
const already = here.recorded.find((repo) => repo.folder === place.name);
|
|
11411
|
+
if (place.existing && already) {
|
|
11412
|
+
return {
|
|
11413
|
+
ok: true,
|
|
11414
|
+
lines: [`${key} is already here, in ${path}, on ${already.branch}. Nothing changed.`]
|
|
11415
|
+
};
|
|
11416
|
+
}
|
|
11417
|
+
let added;
|
|
11418
|
+
try {
|
|
11419
|
+
added = await addWorktree({
|
|
11420
|
+
root: here.root,
|
|
11421
|
+
remote: cloneSource(source, [
|
|
11422
|
+
...here.cloned.map((repo) => repo.remote),
|
|
11423
|
+
...here.described.map((repo) => repo.remote)
|
|
11424
|
+
]),
|
|
11425
|
+
path,
|
|
11426
|
+
branch: here.branch,
|
|
11427
|
+
env: gitEnvOf(ws),
|
|
11428
|
+
...ws.options.storeLock ? { lock: ws.options.storeLock } : {}
|
|
11429
|
+
});
|
|
11430
|
+
} catch (error) {
|
|
11431
|
+
if (error instanceof StoreError) {
|
|
11432
|
+
return {
|
|
11433
|
+
ok: false,
|
|
11434
|
+
error: `Couldn't add ${key}, so nothing was added. git said:
|
|
11435
|
+
${error.message}`
|
|
11436
|
+
};
|
|
11437
|
+
}
|
|
11438
|
+
throw error;
|
|
11439
|
+
}
|
|
11440
|
+
const found = reposInFolder(workspace.folder).find((repo) => repo.folder === place.name);
|
|
11441
|
+
const recorded = await recordRepo(ws, workspace.conversationId, {
|
|
11442
|
+
folder: place.name,
|
|
11443
|
+
remote: key,
|
|
11444
|
+
branch: found?.branch ?? here.branch,
|
|
11445
|
+
...added.created ? { base: added.base } : {},
|
|
11446
|
+
fetchFailed: added.fetchFailed
|
|
11447
|
+
});
|
|
11448
|
+
const lines = [
|
|
11449
|
+
added.created ? `Added ${key} in ${path}, on ${added.branch} from ${added.base}.${added.cloned ? " herd cloned it into the root first." : ""}` : `${key} is already here, in ${path}.`
|
|
11450
|
+
];
|
|
11451
|
+
if (added.fetchFailed) {
|
|
11452
|
+
lines.push(
|
|
11453
|
+
`Couldn't fetch it, so it started from the last fetched ${added.base}: ${added.fetchFailed}`
|
|
11454
|
+
);
|
|
11455
|
+
}
|
|
11456
|
+
if (added.copied.length > 0) lines.push(describeCopied(place.name, added.copied));
|
|
11457
|
+
if (added.created) lines.push("Its own AGENTS.md or README says how to set it up.");
|
|
11458
|
+
if (added.created && confinementOn()) {
|
|
11459
|
+
ws.update(workspace.conversationId, { exitRequested: true });
|
|
11460
|
+
lines.push(
|
|
11461
|
+
"You can read and edit its files now. git in it works from your next turn, once herd restarts this session with it in reach, which it does when this turn ends."
|
|
11462
|
+
);
|
|
11463
|
+
}
|
|
11464
|
+
if (!recorded) lines.push("The conversation couldn't record it yet.");
|
|
11465
|
+
return { ok: true, lines };
|
|
11466
|
+
}
|
|
11240
11467
|
|
|
11241
11468
|
// src/workspaces/setup.ts
|
|
11242
11469
|
init_define_HERD_PACKAGE();
|
|
@@ -12107,19 +12334,19 @@ function resolveAttachments(worktree, files) {
|
|
|
12107
12334
|
// src/workers.ts
|
|
12108
12335
|
init_define_HERD_PACKAGE();
|
|
12109
12336
|
init_src();
|
|
12110
|
-
import { z as
|
|
12111
|
-
var workerListingSchema =
|
|
12112
|
-
workers:
|
|
12113
|
-
machines:
|
|
12114
|
-
|
|
12115
|
-
id:
|
|
12116
|
-
name:
|
|
12117
|
-
online:
|
|
12118
|
-
missing:
|
|
12119
|
-
configError:
|
|
12337
|
+
import { z as z28 } from "zod";
|
|
12338
|
+
var workerListingSchema = z28.object({
|
|
12339
|
+
workers: z28.array(workerSchema),
|
|
12340
|
+
machines: z28.array(
|
|
12341
|
+
z28.object({
|
|
12342
|
+
id: z28.string(),
|
|
12343
|
+
name: z28.string(),
|
|
12344
|
+
online: z28.boolean(),
|
|
12345
|
+
missing: z28.array(z28.string()),
|
|
12346
|
+
configError: z28.string().optional()
|
|
12120
12347
|
})
|
|
12121
12348
|
),
|
|
12122
|
-
configType:
|
|
12349
|
+
configType: z28.string()
|
|
12123
12350
|
});
|
|
12124
12351
|
async function readWorkers(fetch2) {
|
|
12125
12352
|
const response = await fetch2("/api/workers");
|
|
@@ -12183,20 +12410,20 @@ function formatWorkers(listing, current = null) {
|
|
|
12183
12410
|
);
|
|
12184
12411
|
return lines.join("\n");
|
|
12185
12412
|
}
|
|
12186
|
-
var routingCheckSchema =
|
|
12187
|
-
suits:
|
|
12188
|
-
eligible:
|
|
12189
|
-
removed:
|
|
12190
|
-
questions:
|
|
12191
|
-
|
|
12413
|
+
var routingCheckSchema = z28.object({
|
|
12414
|
+
suits: z28.array(z28.object({ key: z28.string(), name: z28.string() })),
|
|
12415
|
+
eligible: z28.array(z28.object({ key: z28.string(), name: z28.string() })),
|
|
12416
|
+
removed: z28.array(z28.object({ key: z28.string(), name: z28.string(), rule: z28.string() })),
|
|
12417
|
+
questions: z28.array(
|
|
12418
|
+
z28.object({ id: z28.string(), question: z28.string(), probability: z28.number().nullable() })
|
|
12192
12419
|
),
|
|
12193
|
-
fallback:
|
|
12194
|
-
fits:
|
|
12195
|
-
|
|
12196
|
-
key:
|
|
12197
|
-
fit:
|
|
12198
|
-
statement:
|
|
12199
|
-
level:
|
|
12420
|
+
fallback: z28.string().optional(),
|
|
12421
|
+
fits: z28.array(
|
|
12422
|
+
z28.object({
|
|
12423
|
+
key: z28.string(),
|
|
12424
|
+
fit: z28.number(),
|
|
12425
|
+
statement: z28.string().optional(),
|
|
12426
|
+
level: z28.string().optional()
|
|
12200
12427
|
})
|
|
12201
12428
|
).optional()
|
|
12202
12429
|
});
|
|
@@ -12319,6 +12546,7 @@ async function answer(ws, grant, request, gone = new AbortController().signal) {
|
|
|
12319
12546
|
if (request.op === "work") return workObjects(ws, workspace, request);
|
|
12320
12547
|
if (request.op === "repos") return repoContext(ws, workspace);
|
|
12321
12548
|
if (request.op === "repo") return repoAdded(ws, workspace, request);
|
|
12549
|
+
if (request.op === "repo-add") return addRepo(ws, workspace, request);
|
|
12322
12550
|
if (request.op === "workers") return workers(ws, workspace);
|
|
12323
12551
|
if (request.op === "check") return check(ws, workspace, request);
|
|
12324
12552
|
if (request.op === "delegate") return delegate(ws, workspace, request);
|
|
@@ -14410,197 +14638,6 @@ async function runTerminalHost(rest) {
|
|
|
14410
14638
|
return 0;
|
|
14411
14639
|
}
|
|
14412
14640
|
|
|
14413
|
-
// src/commands/repo.ts
|
|
14414
|
-
init_define_HERD_PACKAGE();
|
|
14415
|
-
import { join as join37 } from "node:path";
|
|
14416
|
-
import { z as z33 } from "zod";
|
|
14417
|
-
var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
|
|
14418
|
-
|
|
14419
|
-
Checks a repo out in this conversation's folder, on a branch named for the conversation, started
|
|
14420
|
-
from the freshly fetched default branch, and records it in the conversation. It comes from your
|
|
14421
|
-
owner's own checkout of the repo in the machine's root, and gets that checkout's local files git
|
|
14422
|
-
ignores, such as .env, copied in. A repo the root doesn't have is cloned into it first, with your
|
|
14423
|
-
owner's own git credentials. owner/name means one of the repos herd repos lists, or else
|
|
14424
|
-
github.com/owner/name. The folder is the repo's name, or <owner>-<name> when that's taken, unless
|
|
14425
|
-
you give one. Running it again for a repo that's already here changes nothing.`;
|
|
14426
|
-
var REPOS_HELP = `Usage: herd repos [--json]
|
|
14427
|
-
|
|
14428
|
-
Lists the repos in this conversation's folder, the repos checked out in this machine's root, and
|
|
14429
|
-
the repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
|
|
14430
|
-
var repoContextSchema = z33.object({
|
|
14431
|
-
folder: z33.string(),
|
|
14432
|
-
root: z33.string(),
|
|
14433
|
-
branch: z33.string(),
|
|
14434
|
-
legacy: z33.boolean(),
|
|
14435
|
-
legacyReason: z33.string().optional(),
|
|
14436
|
-
recorded: z33.array(z33.object({ folder: z33.string(), remote: z33.string(), branch: z33.string() })),
|
|
14437
|
-
cloned: z33.array(z33.object({ key: z33.string(), remote: z33.string() })),
|
|
14438
|
-
described: z33.array(
|
|
14439
|
-
z33.object({ remote: z33.string(), key: z33.string().nullable(), about: z33.string().optional() })
|
|
14440
|
-
)
|
|
14441
|
-
});
|
|
14442
|
-
var isUrl = (text4) => /^[a-z][a-z0-9+.-]*:\/\//i.test(text4) || text4.startsWith("/") || /^[^@/\s]+@[^:/\s]+:/.test(text4);
|
|
14443
|
-
function resolveRepo(asked, context2) {
|
|
14444
|
-
const text4 = asked.trim().replace(/\/+$/, "");
|
|
14445
|
-
if (!text4) throw new HerdError(REPO_HELP);
|
|
14446
|
-
if (isUrl(text4)) return text4;
|
|
14447
|
-
const known = [
|
|
14448
|
-
...context2.recorded.map((repo) => repo.remote),
|
|
14449
|
-
...context2.cloned.map((repo) => repo.remote),
|
|
14450
|
-
...context2.described.map((repo) => repo.remote)
|
|
14451
|
-
];
|
|
14452
|
-
const wanted = text4.toLowerCase().replace(/\.git$/, "");
|
|
14453
|
-
const parts = wanted.split("/");
|
|
14454
|
-
const matches = known.filter((remote) => {
|
|
14455
|
-
const key = repoKey(remote);
|
|
14456
|
-
if (!key) return false;
|
|
14457
|
-
return parts.length === 1 ? repoName(key).toLowerCase() === wanted : key.toLowerCase() === wanted || key.toLowerCase().endsWith(`/${wanted}`);
|
|
14458
|
-
});
|
|
14459
|
-
const keys = [...new Set(matches.map((remote) => repoKey(remote)))];
|
|
14460
|
-
if (keys.length === 1 && matches[0]) return matches[0];
|
|
14461
|
-
if (keys.length > 1) {
|
|
14462
|
-
throw new HerdError(
|
|
14463
|
-
`More than one repo is called ${text4}: ${keys.join(", ")}. Name one of them.`
|
|
14464
|
-
);
|
|
14465
|
-
}
|
|
14466
|
-
if (parts.length === 1) {
|
|
14467
|
-
throw new HerdError(
|
|
14468
|
-
`herd doesn't know a repo called ${text4}. Name it as owner/name or a URL. herd repos lists the ones it knows.`
|
|
14469
|
-
);
|
|
14470
|
-
}
|
|
14471
|
-
return parts.length >= 3 && parts[0]?.includes(".") ? `https://${text4}` : `https://github.com/${text4}`;
|
|
14472
|
-
}
|
|
14473
|
-
async function context() {
|
|
14474
|
-
const reply = await fromTerminal({ op: "repos" });
|
|
14475
|
-
const parsed = repoContextSchema.safeParse(reply);
|
|
14476
|
-
if (!parsed.success)
|
|
14477
|
-
throw new HerdError("The daemon's answer about this folder wasn't readable.");
|
|
14478
|
-
return parsed.data;
|
|
14479
|
-
}
|
|
14480
|
-
async function runRepo(rest) {
|
|
14481
|
-
const args = rest.filter((arg) => arg !== "--");
|
|
14482
|
-
if (args.length === 0 || args.length > 2 || args[0] === "--help" || args[0] === "-h") {
|
|
14483
|
-
console.log(REPO_HELP);
|
|
14484
|
-
return args[0] === "--help" || args[0] === "-h" ? 0 : 1;
|
|
14485
|
-
}
|
|
14486
|
-
const [asked = "", folderName] = args;
|
|
14487
|
-
if (folderName !== void 0 && !validFolderName(folderName)) {
|
|
14488
|
-
throw new HerdError(
|
|
14489
|
-
`${folderName} can't be a folder name here. Use letters, digits, . _ or -.`
|
|
14490
|
-
);
|
|
14491
|
-
}
|
|
14492
|
-
const here = await context();
|
|
14493
|
-
if (here.legacy) throw new HerdError(here.legacyReason ?? "This conversation can't add repos.");
|
|
14494
|
-
const source = resolveRepo(asked, here);
|
|
14495
|
-
const key = repoKey(source);
|
|
14496
|
-
if (!key) throw new HerdError(`${asked} isn't a repo's URL.`);
|
|
14497
|
-
const place = folderFor(here.folder, key, folderName);
|
|
14498
|
-
if (!place) {
|
|
14499
|
-
throw new HerdError(
|
|
14500
|
-
folderName ? `${folderName} is already taken in this conversation's folder. Give another name.` : `Both ${repoName(key)} and ${repoOwner(key)}-${repoName(key)} are taken in this conversation's folder. Give a folder name: herd repo ${asked} <folder>.`
|
|
14501
|
-
);
|
|
14502
|
-
}
|
|
14503
|
-
const path = join37(here.folder, place.name);
|
|
14504
|
-
const recorded = here.recorded.some((repo) => repo.folder === place.name);
|
|
14505
|
-
if (place.existing && recorded) {
|
|
14506
|
-
const branch = here.recorded.find((repo) => repo.folder === place.name)?.branch ?? here.branch;
|
|
14507
|
-
console.log(`${key} is already here, in ${path}, on ${branch}. Nothing changed.`);
|
|
14508
|
-
return 0;
|
|
14509
|
-
}
|
|
14510
|
-
let added;
|
|
14511
|
-
try {
|
|
14512
|
-
added = await addWorktree({
|
|
14513
|
-
root: here.root,
|
|
14514
|
-
remote: cloneSource(source, [
|
|
14515
|
-
...here.cloned.map((repo) => repo.remote),
|
|
14516
|
-
...here.described.map((repo) => repo.remote)
|
|
14517
|
-
]),
|
|
14518
|
-
path,
|
|
14519
|
-
branch: here.branch,
|
|
14520
|
-
env: process.env
|
|
14521
|
-
});
|
|
14522
|
-
} catch (error) {
|
|
14523
|
-
if (error instanceof StoreError) {
|
|
14524
|
-
throw new HerdError(`Couldn't add ${key}, so nothing was added. git said:
|
|
14525
|
-
${error.message}`);
|
|
14526
|
-
}
|
|
14527
|
-
throw error;
|
|
14528
|
-
}
|
|
14529
|
-
let note = "";
|
|
14530
|
-
try {
|
|
14531
|
-
const reply = await fromTerminal({
|
|
14532
|
-
op: "repo",
|
|
14533
|
-
repo: {
|
|
14534
|
-
remote: key,
|
|
14535
|
-
folder: place.name,
|
|
14536
|
-
...added.created ? { base: added.base } : {},
|
|
14537
|
-
...added.fetchFailed ? { fetchFailed: added.fetchFailed } : {}
|
|
14538
|
-
}
|
|
14539
|
-
});
|
|
14540
|
-
if (reply.recorded === false) note = " The conversation couldn't record it yet.";
|
|
14541
|
-
} catch (error) {
|
|
14542
|
-
note = ` The conversation couldn't record it: ${error instanceof Error ? error.message : error}`;
|
|
14543
|
-
}
|
|
14544
|
-
const lines = [
|
|
14545
|
-
added.created ? `Added ${key} in ${path}, on ${added.branch} from ${added.base}.${added.cloned ? " herd cloned it into the root first." : ""}` : `${key} is already here, in ${path}.`
|
|
14546
|
-
];
|
|
14547
|
-
if (added.fetchFailed) {
|
|
14548
|
-
lines.push(
|
|
14549
|
-
`Couldn't fetch it, so it started from the last fetched ${added.base}: ${added.fetchFailed}`
|
|
14550
|
-
);
|
|
14551
|
-
}
|
|
14552
|
-
if (added.copied.length > 0) lines.push(describeCopied(place.name, added.copied));
|
|
14553
|
-
if (added.created) lines.push("Its own AGENTS.md or README says how to set it up.");
|
|
14554
|
-
console.log(lines.join("\n") + note);
|
|
14555
|
-
return 0;
|
|
14556
|
-
}
|
|
14557
|
-
function formatRepos(here) {
|
|
14558
|
-
const inFolder = new Set(here.recorded.map((repo) => repoKey(repo.remote)));
|
|
14559
|
-
const lines = ["In this conversation's folder:"];
|
|
14560
|
-
if (here.recorded.length === 0) lines.push(" none yet");
|
|
14561
|
-
for (const repo of here.recorded)
|
|
14562
|
-
lines.push(` ${repo.folder} ${repo.remote} on ${repo.branch}`);
|
|
14563
|
-
const cloned = here.cloned.filter((repo) => !inFolder.has(repo.key));
|
|
14564
|
-
lines.push("", "Checked out in the root:");
|
|
14565
|
-
if (cloned.length === 0) lines.push(" none others");
|
|
14566
|
-
const about = new Map(here.described.map((repo) => [repo.key, repo.about]));
|
|
14567
|
-
for (const repo of cloned) {
|
|
14568
|
-
const said = about.get(repo.key);
|
|
14569
|
-
lines.push(` ${repo.key}${said ? ` ${said}` : ""}`);
|
|
14570
|
-
}
|
|
14571
|
-
const clonedKeys = new Set(here.cloned.map((repo) => repo.key));
|
|
14572
|
-
const described = here.described.filter((repo) => !repo.key || !clonedKeys.has(repo.key));
|
|
14573
|
-
if (described.length > 0) {
|
|
14574
|
-
lines.push("", "Described in herd.config.ts, not cloned yet:");
|
|
14575
|
-
for (const repo of described) {
|
|
14576
|
-
lines.push(` ${repo.key ?? repo.remote}${repo.about ? ` ${repo.about}` : ""}`);
|
|
14577
|
-
}
|
|
14578
|
-
}
|
|
14579
|
-
lines.push("", "Add one with herd repo <url or owner/name> [folder].");
|
|
14580
|
-
return lines.join("\n");
|
|
14581
|
-
}
|
|
14582
|
-
async function runRepos(rest) {
|
|
14583
|
-
const unknown = rest.find((arg) => arg !== "--json");
|
|
14584
|
-
if (unknown === "--help" || unknown === "-h") {
|
|
14585
|
-
console.log(REPOS_HELP);
|
|
14586
|
-
return 0;
|
|
14587
|
-
}
|
|
14588
|
-
if (unknown) throw new HerdError(`herd repos doesn't understand "${unknown}".`);
|
|
14589
|
-
const here = await context();
|
|
14590
|
-
if (rest.includes("--json")) {
|
|
14591
|
-
console.log(
|
|
14592
|
-
JSON.stringify(
|
|
14593
|
-
{ conversation: here.recorded, cloned: here.cloned, described: here.described },
|
|
14594
|
-
null,
|
|
14595
|
-
2
|
|
14596
|
-
)
|
|
14597
|
-
);
|
|
14598
|
-
} else {
|
|
14599
|
-
console.log(formatRepos(here));
|
|
14600
|
-
}
|
|
14601
|
-
return 0;
|
|
14602
|
-
}
|
|
14603
|
-
|
|
14604
14641
|
// src/commands/send.ts
|
|
14605
14642
|
init_define_HERD_PACKAGE();
|
|
14606
14643
|
init_src();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herdingchats/friday",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.641",
|
|
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": "e1411e26f2203efeb542823bfefded0e5dd8b296"
|
|
26
26
|
}
|