@stringsync/splates 0.0.57 → 0.0.59
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/README.md +10 -10
- package/blockers.d.ts +32 -0
- package/evals.d.ts +3 -0
- package/index.js +554 -319
- package/package.json +3 -3
- package/notes.d.ts +0 -4
package/index.js
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
4
|
+
var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose");
|
|
5
|
+
var __using = (stack, value, async) => {
|
|
6
|
+
if (value != null) {
|
|
7
|
+
if (typeof value !== "object" && typeof value !== "function")
|
|
8
|
+
throw TypeError('Object expected to be assigned to "using" declaration');
|
|
9
|
+
var dispose;
|
|
10
|
+
if (async)
|
|
11
|
+
dispose = value[__asyncDispose];
|
|
12
|
+
if (dispose === undefined)
|
|
13
|
+
dispose = value[__dispose];
|
|
14
|
+
if (typeof dispose !== "function")
|
|
15
|
+
throw TypeError("Object not disposable");
|
|
16
|
+
stack.push([async, dispose, value]);
|
|
17
|
+
} else if (async) {
|
|
18
|
+
stack.push([async]);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
};
|
|
22
|
+
var __callDispose = (stack, error, hasError) => {
|
|
23
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
24
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
25
|
+
}, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => {
|
|
26
|
+
while (it = stack.pop()) {
|
|
27
|
+
try {
|
|
28
|
+
var result = it[1] && it[1].call(it[2]);
|
|
29
|
+
if (it[0])
|
|
30
|
+
return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
fail(e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (hasError)
|
|
36
|
+
throw error;
|
|
37
|
+
};
|
|
38
|
+
return next();
|
|
39
|
+
};
|
|
3
40
|
|
|
4
41
|
// index.ts
|
|
5
42
|
import { basename as basename2 } from "path";
|
|
@@ -48,9 +85,9 @@ async function handOffDev(argv, cwd) {
|
|
|
48
85
|
// splates.ts
|
|
49
86
|
import { agents } from "@stringsync/splates-server/agents";
|
|
50
87
|
import { attach } from "@stringsync/splates-server/artifacts";
|
|
51
|
-
import { commaList as
|
|
88
|
+
import { commaList as commaList6 } from "@stringsync/splates-server/comma-list";
|
|
52
89
|
import { refuseGroomer as refuseGroomer4 } from "@stringsync/splates-server/conversations";
|
|
53
|
-
import { exits, fail as
|
|
90
|
+
import { exits, fail as fail10 } from "@stringsync/splates-server/exit";
|
|
54
91
|
import { DEFAULT_COUNT, log as showLog } from "@stringsync/splates-server/log";
|
|
55
92
|
import { merge } from "@stringsync/splates-server/merge";
|
|
56
93
|
import { portsLs } from "@stringsync/splates-server/ports";
|
|
@@ -59,15 +96,18 @@ import {
|
|
|
59
96
|
MIGRATING,
|
|
60
97
|
MIGRATION_LIMIT,
|
|
61
98
|
migrateRecords,
|
|
99
|
+
OpenBoards as OpenBoards2,
|
|
62
100
|
repository as repository10
|
|
63
101
|
} from "@stringsync/splates-server/records";
|
|
64
102
|
import {
|
|
65
103
|
newest,
|
|
66
104
|
recordsFirst,
|
|
105
|
+
refreshHere,
|
|
67
106
|
rosterOf as rosterOf2,
|
|
68
107
|
seeded as seeded2,
|
|
69
108
|
update
|
|
70
109
|
} from "@stringsync/splates-server/server";
|
|
110
|
+
import { ship } from "@stringsync/splates-server/ship";
|
|
71
111
|
import { addFactTasks } from "@stringsync/splates-server/stats";
|
|
72
112
|
import {
|
|
73
113
|
add,
|
|
@@ -85,9 +125,9 @@ import {
|
|
|
85
125
|
untilAfter as untilAfter3,
|
|
86
126
|
wait
|
|
87
127
|
} from "@stringsync/splates-server/tasks";
|
|
88
|
-
import { cli as
|
|
128
|
+
import { cli as cli15 } from "webappwiz/cmd";
|
|
89
129
|
import { Duration as Duration3 } from "webappwiz/time";
|
|
90
|
-
import { z as
|
|
130
|
+
import { z as z16 } from "zod";
|
|
91
131
|
|
|
92
132
|
// artifact.ts
|
|
93
133
|
import { basename, resolve as resolve2 } from "node:path";
|
|
@@ -244,9 +284,87 @@ splatesArtifact.command("remove").use(repository({ needs: "own" })).description(
|
|
|
244
284
|
splatesArtifact.command("list").use(repository({ needs: "own" })).description("the artifacts this conversation lists, and their current version").option(...conversation).action(async (opts, ctx) => listArtifacts(ctx, await conversationOf(ctx.service, opts.conversation)));
|
|
245
285
|
splatesArtifact.command("show").use(repository({ needs: "own" })).description("every version of an artifact, and which is current").arg("name", z2.string(), { description: "the artifact's name" }).option(...conversation).action(async (opts, ctx) => listArtifacts(ctx, await conversationOf(ctx.service, opts.conversation), opts.name));
|
|
246
286
|
|
|
287
|
+
// blockers.ts
|
|
288
|
+
import { commaList as commaList3 } from "@stringsync/splates-server/comma-list";
|
|
289
|
+
import { fail as fail3 } from "@stringsync/splates-server/exit";
|
|
290
|
+
async function blockerFlags({
|
|
291
|
+
boards,
|
|
292
|
+
here,
|
|
293
|
+
blockedBy = [],
|
|
294
|
+
removeBlockedBy = []
|
|
295
|
+
}) {
|
|
296
|
+
const ids = [];
|
|
297
|
+
const blockedElsewhere = [];
|
|
298
|
+
for (const named of await namedIn(boards, here, blockedBy)) {
|
|
299
|
+
if (named.project === null) {
|
|
300
|
+
ids.push(named.id);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const found = await boards.find(named.project, named.id);
|
|
304
|
+
if (found === null) {
|
|
305
|
+
fail3("not_found", `the tasks of ${named.written} cannot be read: is ${named.slug} set up with \`splates init\`?`, { blockedBy: named.written });
|
|
306
|
+
}
|
|
307
|
+
if (found.status === "merged" || found.status === "removed") {
|
|
308
|
+
fail3("not_found", `${named.written} is ${found.status === "merged" ? `merged, as ${found.commit}` : "gone, removed without merging"}: nothing waits on it any more`, { blockedBy: named.written });
|
|
309
|
+
}
|
|
310
|
+
blockedElsewhere.push({
|
|
311
|
+
project: named.project,
|
|
312
|
+
id: named.id,
|
|
313
|
+
subject: found.subject
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const unblocked = { ids: [], elsewhere: [] };
|
|
317
|
+
for (const named of await namedIn(boards, here, removeBlockedBy)) {
|
|
318
|
+
if (named.project === null) {
|
|
319
|
+
unblocked.ids.push(named.id);
|
|
320
|
+
} else {
|
|
321
|
+
unblocked.elsewhere.push({ project: named.project, id: named.id });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return { blockedBy: ids, blockedElsewhere, unblocked };
|
|
325
|
+
}
|
|
326
|
+
function id(digits) {
|
|
327
|
+
const parsed = Number(digits);
|
|
328
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
329
|
+
fail3("usage", `'${digits}' is not a task number`, { number: digits });
|
|
330
|
+
}
|
|
331
|
+
return parsed;
|
|
332
|
+
}
|
|
333
|
+
async function namedIn(boards, here, raw) {
|
|
334
|
+
const named = [];
|
|
335
|
+
for (const value of commaList3(raw.join(","))) {
|
|
336
|
+
const local = /^#?(\d+)$/.exec(value);
|
|
337
|
+
if (local !== null) {
|
|
338
|
+
named.push({
|
|
339
|
+
project: null,
|
|
340
|
+
slug: "",
|
|
341
|
+
id: id(local[1]),
|
|
342
|
+
written: value
|
|
343
|
+
});
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const other = /^#?([\w.-]+)(?:\s+task)?\s*#(\d+)$/i.exec(value);
|
|
347
|
+
if (other === null) {
|
|
348
|
+
fail3("usage", `'${value}' is not a task: pass its number, or <project>#<number> for one of another project`, { blockedBy: value });
|
|
349
|
+
}
|
|
350
|
+
const slug = other[1];
|
|
351
|
+
const root = await boards.root(slug);
|
|
352
|
+
if (root === null) {
|
|
353
|
+
fail3("not_found", `no splates project '${slug}' on this machine: \`splates projects\` lists them`, { blockedBy: value });
|
|
354
|
+
}
|
|
355
|
+
named.push({
|
|
356
|
+
project: root === here ? null : root,
|
|
357
|
+
slug,
|
|
358
|
+
id: id(other[2]),
|
|
359
|
+
written: `${slug} #${other[2]}`
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return named;
|
|
363
|
+
}
|
|
364
|
+
|
|
247
365
|
// creds.ts
|
|
248
366
|
import { DEFAULT_TIMEOUT, PROVIDERS } from "@stringsync/splates-server/creds";
|
|
249
|
-
import { fail as
|
|
367
|
+
import { fail as fail4 } from "@stringsync/splates-server/exit";
|
|
250
368
|
import { repository as repository2 } from "@stringsync/splates-server/records";
|
|
251
369
|
import { untilAfter } from "@stringsync/splates-server/tasks";
|
|
252
370
|
import { cli as cli2 } from "webappwiz/cmd";
|
|
@@ -296,7 +414,7 @@ splatesCreds.command("wait").use(repository2({ needs: "own" })).description("blo
|
|
|
296
414
|
await ctx.journal.waiting("creds wait", null, { name: opts.name, until: untilAfter(timeout) }, async () => {
|
|
297
415
|
const ended = await ctx.creds.wait(opts.name, { timeout });
|
|
298
416
|
if (ended === "dismissed") {
|
|
299
|
-
|
|
417
|
+
fail4("dismissed", `${opts.name} was dismissed: carry on without it, or ask the person why`, { name: opts.name });
|
|
300
418
|
}
|
|
301
419
|
});
|
|
302
420
|
ctx.log.info(`${color.green("saved")} ${opts.name}`);
|
|
@@ -322,14 +440,61 @@ splatesCreds.command("list").use(repository2({ needs: "read" })).description("th
|
|
|
322
440
|
}
|
|
323
441
|
});
|
|
324
442
|
|
|
443
|
+
// evals.ts
|
|
444
|
+
import { resolve as resolve3 } from "node:path";
|
|
445
|
+
import {
|
|
446
|
+
inspectBatch,
|
|
447
|
+
writeImported
|
|
448
|
+
} from "@stringsync/splates-server/evals";
|
|
449
|
+
import { fail as fail5 } from "@stringsync/splates-server/exit";
|
|
450
|
+
import { cli as cli3 } from "webappwiz/cmd";
|
|
451
|
+
import { color as color2 } from "webappwiz/log";
|
|
452
|
+
import { z as z4 } from "zod";
|
|
453
|
+
var splatesEvals = cli3("evals").description("a project's evals from another framework, which the Evals page of `splates server` shows once they are a splates-evals/1 batch in the directory `evals.results` in splates.config.ts names (docs/evals.md)");
|
|
454
|
+
splatesEvals.command("import").description("turn a framework's results into a splates-evals/1 batch in <batch-dir>, replacing one there: for inspect, an Inspect AI eval log, `.json` read as it is and `.eval` through `inspect log dump`, a cell per sample and epoch that passes when every scorer's value, read as Inspect does (C 1, P 0.5, I 0), reaches --pass-at").arg("framework", z4.enum(["inspect"]), {
|
|
455
|
+
description: "what wrote the results: inspect"
|
|
456
|
+
}).arg("file", z4.string(), { description: "the framework's results file" }).arg("batch-dir", z4.string(), {
|
|
457
|
+
description: "the batch's directory, inside the one evals.results names; its name is the batch's"
|
|
458
|
+
}).option("pass-at", z4.coerce.number(), {
|
|
459
|
+
default: 1,
|
|
460
|
+
description: "the value, from 0 to 1, each scorer must reach for a pass"
|
|
461
|
+
}).action(async (opts, ctx) => {
|
|
462
|
+
const file = resolve3(ctx.ps.cwd(), opts.file);
|
|
463
|
+
const dir = resolve3(ctx.ps.cwd(), opts["batch-dir"]);
|
|
464
|
+
if (!await ctx.fs.exists(file)) {
|
|
465
|
+
fail5("not_found", `no file at ${file}`, { file });
|
|
466
|
+
}
|
|
467
|
+
const imported = inspectBatch(await inspectLog(ctx, file), opts["pass-at"]);
|
|
468
|
+
await writeImported(dir, imported);
|
|
469
|
+
ctx.log.info(`${color2.green("imported")} ${imported.batch.cells.length} cells to ${dir}`);
|
|
470
|
+
});
|
|
471
|
+
async function inspectLog(ctx, file) {
|
|
472
|
+
const text = file.endsWith(".eval") ? await dumped(ctx, file) : await ctx.fs.read(file);
|
|
473
|
+
try {
|
|
474
|
+
const log = JSON.parse(text);
|
|
475
|
+
if (typeof log.eval?.task === "string") {
|
|
476
|
+
return log;
|
|
477
|
+
}
|
|
478
|
+
} catch {}
|
|
479
|
+
return fail5("usage", `${file} is no Inspect eval log`, { file });
|
|
480
|
+
}
|
|
481
|
+
async function dumped(ctx, file) {
|
|
482
|
+
const { exitCode, stdout, stderr } = await ctx.ps.spawnCapture(["inspect", "log", "dump", file]).catch(() => fail5("usage", "`inspect` is not on PATH: activate the environment Inspect is installed in, or pass a log written with --log-format json"));
|
|
483
|
+
if (exitCode !== 0) {
|
|
484
|
+
fail5("usage", `inspect log dump ${file} failed: ${stderr.trim()}`, {
|
|
485
|
+
file
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return stdout;
|
|
489
|
+
}
|
|
490
|
+
|
|
325
491
|
// hook.ts
|
|
326
|
-
import { commaList as
|
|
327
|
-
import { fail as
|
|
492
|
+
import { commaList as commaList4 } from "@stringsync/splates-server/comma-list";
|
|
493
|
+
import { fail as fail6 } from "@stringsync/splates-server/exit";
|
|
328
494
|
import {
|
|
329
495
|
HookStore,
|
|
330
496
|
hookAdd,
|
|
331
497
|
hookEdit,
|
|
332
|
-
hookGardener,
|
|
333
498
|
hookGroomer,
|
|
334
499
|
hookList,
|
|
335
500
|
hookPause,
|
|
@@ -340,37 +505,37 @@ import {
|
|
|
340
505
|
TASK_EVENTS
|
|
341
506
|
} from "@stringsync/splates-server/hooks";
|
|
342
507
|
import { repository as repository3 } from "@stringsync/splates-server/records";
|
|
343
|
-
import { cli as
|
|
344
|
-
import { z as
|
|
345
|
-
var splatesHook =
|
|
346
|
-
splatesHook.command("list").use(repository3({ needs: "read" })).description("every automation set here, by number, with its trigger").option("json",
|
|
347
|
-
splatesHook.command("add").use(repository3({ needs: "tasks" })).description("run something on a task event, or on a schedule with `--every` or `--cron` in place of the event: `--agent <a> --prompt <words>` starts that agent in the main tree, told the event as `[SPLATES EVENT <event> #N]`, or a schedule as `[SPLATES SCHEDULE <number>]`, then your words; `--run <command>` runs it via `sh -c` in the main tree, with $SPLATES_EVENT (`schedule` for one), $SPLATES_TASKS and $SPLATES_TASK set. A schedule that came due while `splates server` was down runs once as it starts").arg("event",
|
|
508
|
+
import { cli as cli4 } from "webappwiz/cmd";
|
|
509
|
+
import { z as z5 } from "zod";
|
|
510
|
+
var splatesHook = cli4("hook").description(`automations: what runs on a task event (${TASK_EVENTS.join(", ")}), as \`splates server\` sees the journal take it, or on a schedule it keeps: an agent told of it; or a shell command. Kept for you in this project's .git/splates, never checked in, since an agent hook spends your tokens; splates.config.ts's \`on\` holds the shell commands a project checks in. None set, nothing runs. The page's Automations tab lists them with when each last ran and for what task`);
|
|
511
|
+
splatesHook.command("list").use(repository3({ needs: "read" })).description("every automation set here, by number, with its trigger").option("json", z5.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => hookList({ hooks: storeOf(ctx), log: ctx.log }, { json: opts.json }));
|
|
512
|
+
splatesHook.command("add").use(repository3({ needs: "tasks" })).description("run something on a task event, or on a schedule with `--every` or `--cron` in place of the event: `--agent <a> --prompt <words>` starts that agent in the main tree, told the event as `[SPLATES EVENT <event> #N]`, or a schedule as `[SPLATES SCHEDULE <number>]`, then your words; `--run <command>` runs it via `sh -c` in the main tree, with $SPLATES_EVENT (`schedule` for one), $SPLATES_TASKS and $SPLATES_TASK set. A schedule that came due while `splates server` was down runs once as it starts").arg("event", z5.string().optional(), {
|
|
348
513
|
description: "the task event, as task.created; none for a schedule"
|
|
349
|
-
}).option("stage",
|
|
514
|
+
}).option("stage", z5.string(), {
|
|
350
515
|
default: "",
|
|
351
516
|
description: "only for tasks in these stages, comma separated: backlog, ready, queued, running"
|
|
352
|
-
}).option("lane",
|
|
517
|
+
}).option("lane", z5.string(), {
|
|
353
518
|
default: "",
|
|
354
519
|
description: "only for tasks in these lanes, by number, comma separated"
|
|
355
|
-
}).option("every",
|
|
520
|
+
}).option("every", z5.string(), {
|
|
356
521
|
default: "",
|
|
357
522
|
description: "run every so often, as 30m, 2h or 1d"
|
|
358
|
-
}).option("cron",
|
|
523
|
+
}).option("cron", z5.string(), {
|
|
359
524
|
default: "",
|
|
360
525
|
description: `run on a five-field cron line, in this machine's time, as "0 9 * * 1-5"`
|
|
361
|
-
}).option("agent",
|
|
526
|
+
}).option("agent", z5.string(), {
|
|
362
527
|
default: "",
|
|
363
528
|
description: "the agent to run, as `splates agents` names it"
|
|
364
|
-
}).option("model",
|
|
529
|
+
}).option("model", z5.string(), {
|
|
365
530
|
default: "",
|
|
366
531
|
description: "its model (default: the agent's own)"
|
|
367
|
-
}).option("effort",
|
|
532
|
+
}).option("effort", z5.string(), {
|
|
368
533
|
default: "",
|
|
369
534
|
description: "its thinking level (default: the agent's own)"
|
|
370
|
-
}).option("prompt",
|
|
535
|
+
}).option("prompt", z5.string(), {
|
|
371
536
|
default: "",
|
|
372
537
|
description: "what the agent is told after the event"
|
|
373
|
-
}).option("run",
|
|
538
|
+
}).option("run", z5.string(), {
|
|
374
539
|
default: "",
|
|
375
540
|
description: "a shell command to run"
|
|
376
541
|
}).action(async (opts, ctx) => {
|
|
@@ -378,128 +543,100 @@ splatesHook.command("add").use(repository3({ needs: "tasks" })).description("run
|
|
|
378
543
|
let hook;
|
|
379
544
|
if (opts.run !== "") {
|
|
380
545
|
if (pick != null || opts.prompt !== "") {
|
|
381
|
-
|
|
546
|
+
fail6("usage", "--run goes alone: a shell command runs no agent", {});
|
|
382
547
|
}
|
|
383
548
|
hook = { kind: "run", command: opts.run };
|
|
384
549
|
} else if (pick == null) {
|
|
385
|
-
|
|
550
|
+
fail6("usage", "say what runs: --agent with --prompt, or --run", {});
|
|
386
551
|
} else if (opts.prompt === "") {
|
|
387
|
-
|
|
552
|
+
fail6("usage", "an agent hook needs --prompt: what to tell it", {});
|
|
388
553
|
} else {
|
|
389
554
|
hook = { kind: "agent", prompt: opts.prompt, ...pick };
|
|
390
555
|
}
|
|
391
556
|
await hookAdd({ hooks: storeOf(ctx), log: ctx.log }, triggerOf(opts), hook);
|
|
392
557
|
});
|
|
393
|
-
splatesHook.command("edit").use(repository3({ needs: "tasks" })).description("change one of your automations, by its number in `splates hook list`, keeping its number, its pause and its last runs. Each flag changes only what it names, as `splates hook add` takes it: `--event`, `--every` or `--cron` sets what sets it off anew, `--stage` or `--lane` alone narrows its event again (`any` for no narrowing), `--prompt` or `--agent` changes what an agent is told or which agent, and `--run` makes it a shell command. The groomer changes through `splates hook groomer`").arg("id",
|
|
558
|
+
splatesHook.command("edit").use(repository3({ needs: "tasks" })).description("change one of your automations, by its number in `splates hook list`, keeping its number, its pause and its last runs. Each flag changes only what it names, as `splates hook add` takes it: `--event`, `--every` or `--cron` sets what sets it off anew, `--stage` or `--lane` alone narrows its event again (`any` for no narrowing), `--prompt` or `--agent` changes what an agent is told or which agent, and `--run` makes it a shell command. The groomer changes through `splates hook groomer`").arg("id", z5.string(), { description: "its number in `splates hook list`" }).option("event", z5.string(), {
|
|
394
559
|
default: "",
|
|
395
560
|
description: "a task event to run on, as task.created, in place of what sets it off now"
|
|
396
|
-
}).option("stage",
|
|
561
|
+
}).option("stage", z5.string(), {
|
|
397
562
|
default: "",
|
|
398
563
|
description: "only for tasks in these stages, comma separated, or `any`: backlog, ready, queued, running"
|
|
399
|
-
}).option("lane",
|
|
564
|
+
}).option("lane", z5.string(), {
|
|
400
565
|
default: "",
|
|
401
566
|
description: "only for tasks in these lanes, by number, comma separated, or `any`"
|
|
402
|
-
}).option("every",
|
|
567
|
+
}).option("every", z5.string(), {
|
|
403
568
|
default: "",
|
|
404
569
|
description: "run every so often, as 30m, 2h or 1d"
|
|
405
|
-
}).option("cron",
|
|
570
|
+
}).option("cron", z5.string(), {
|
|
406
571
|
default: "",
|
|
407
572
|
description: `run on a five-field cron line, in this machine's time, as "0 9 * * 1-5"`
|
|
408
|
-
}).option("agent",
|
|
573
|
+
}).option("agent", z5.string(), {
|
|
409
574
|
default: "",
|
|
410
575
|
description: "the agent to run, as `splates agents` names it"
|
|
411
|
-
}).option("model",
|
|
576
|
+
}).option("model", z5.string(), {
|
|
412
577
|
default: "",
|
|
413
578
|
description: "its model, with --agent (default: the agent's own)"
|
|
414
|
-
}).option("effort",
|
|
579
|
+
}).option("effort", z5.string(), {
|
|
415
580
|
default: "",
|
|
416
581
|
description: "its thinking level, with --agent (default: the agent's own)"
|
|
417
|
-
}).option("prompt",
|
|
582
|
+
}).option("prompt", z5.string(), {
|
|
418
583
|
default: "",
|
|
419
584
|
description: "what the agent is told after the event"
|
|
420
|
-
}).option("run",
|
|
585
|
+
}).option("run", z5.string(), {
|
|
421
586
|
default: "",
|
|
422
587
|
description: "a shell command to run in place of an agent"
|
|
423
588
|
}).action(async (opts, ctx) => {
|
|
424
589
|
const { id, agent, model, effort, ...change } = opts;
|
|
425
590
|
const pick = await assigneeIn(ctx, agent, model, effort);
|
|
426
591
|
if (pick === null) {
|
|
427
|
-
|
|
592
|
+
fail6("usage", "an agent automation needs an agent: name one, or --run", {});
|
|
428
593
|
}
|
|
429
594
|
await hookEdit({ hooks: storeOf(ctx), log: ctx.log }, id, {
|
|
430
595
|
...change,
|
|
431
596
|
...pick === undefined ? {} : { pick }
|
|
432
597
|
});
|
|
433
598
|
});
|
|
434
|
-
splatesHook.command("remove").use(repository3({ needs: "tasks" })).description("take away an automation, by its number in `splates hook list`, or every one on an event").arg("id",
|
|
599
|
+
splatesHook.command("remove").use(repository3({ needs: "tasks" })).description("take away an automation, by its number in `splates hook list`, or every one on an event").arg("id", z5.string(), {
|
|
435
600
|
description: "its number, or a task event, as task.created"
|
|
436
601
|
}).action((opts, ctx) => hookRemove({ hooks: storeOf(ctx), log: ctx.log }, opts.id));
|
|
437
|
-
splatesHook.command("pause").use(repository3({ needs: "tasks" })).description("stop an automation running, keeping it to resume").arg("id",
|
|
438
|
-
splatesHook.command("resume").use(repository3({ needs: "tasks" })).description("let a paused automation run again").arg("id",
|
|
439
|
-
splatesHook.command("run").use(repository3({ needs: "tasks" })).description("run an automation now, as the Automations page's Run now does, its schedule left as it was: one on a schedule
|
|
440
|
-
description: "its number in `splates hook list`, or `groomer`
|
|
602
|
+
splatesHook.command("pause").use(repository3({ needs: "tasks" })).description("stop an automation running, keeping it to resume").arg("id", z5.string(), { description: "its number in `splates hook list`" }).action((opts, ctx) => hookPause({ hooks: storeOf(ctx), log: ctx.log }, opts.id, true));
|
|
603
|
+
splatesHook.command("resume").use(repository3({ needs: "tasks" })).description("let a paused automation run again").arg("id", z5.string(), { description: "its number in `splates hook list`" }).action((opts, ctx) => hookPause({ hooks: storeOf(ctx), log: ctx.log }, opts.id, false));
|
|
604
|
+
splatesHook.command("run").use(repository3({ needs: "tasks" })).description("run an automation now, as the Automations page's Run now does, its schedule left as it was: one on a schedule or the groomer, never one on another task event, which needs that event's tasks. `splates server` starts it at once, or as it starts when it is not running, and lists the run as `by hand`; it runs paused too").arg("id", z5.string(), {
|
|
605
|
+
description: "its number in `splates hook list`, or `groomer`"
|
|
441
606
|
}).action((opts, ctx) => hookRun({ hooks: storeOf(ctx), log: ctx.log }, opts.id));
|
|
442
|
-
splatesHook.command("groomer").use(repository3({ needs: "tasks" })).description("turn on the project's groomer, built-in automation `groomer`, or set the agent it runs as, as the Automations page does: on each task deferred to the backlog, and the backlog as `splates server` starts, a conversation of its own per run, archived as the run ends, organizes each task in order and marks it ready, never doing it. `--off` turns it off, retiring its conversations").option("agent",
|
|
607
|
+
splatesHook.command("groomer").use(repository3({ needs: "tasks" })).description("turn on the project's groomer, built-in automation `groomer`, or set the agent it runs as, as the Automations page does: on each task deferred to the backlog, and the backlog as `splates server` starts, a conversation of its own per run, archived as the run ends, organizes each task in order and marks it ready, never doing it. `--off` turns it off, retiring its conversations").option("agent", z5.string(), {
|
|
443
608
|
default: "",
|
|
444
609
|
description: "the agent it runs as, as `splates agents` names it"
|
|
445
|
-
}).option("model",
|
|
610
|
+
}).option("model", z5.string(), {
|
|
446
611
|
default: "",
|
|
447
612
|
description: "its model (default: the agent's own)"
|
|
448
|
-
}).option("effort",
|
|
613
|
+
}).option("effort", z5.string(), {
|
|
449
614
|
default: "",
|
|
450
615
|
description: "its thinking level (default: the agent's own)"
|
|
451
|
-
}).option("off",
|
|
616
|
+
}).option("off", z5.boolean(), {
|
|
452
617
|
default: false,
|
|
453
618
|
description: "turn it off"
|
|
454
619
|
}).action(async (opts, ctx) => {
|
|
455
620
|
const pick = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
456
621
|
if (opts.off === (pick != null)) {
|
|
457
|
-
|
|
622
|
+
fail6("usage", "say how it runs with --agent, or turn it off with --off", {});
|
|
458
623
|
}
|
|
459
624
|
await hookGroomer({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null);
|
|
460
625
|
});
|
|
461
|
-
splatesHook.command("gardener").use(repository3({ needs: "tasks" })).description("turn on the notes gardener, built-in automation `notes-gardener`, or set the agent it runs as and its schedule, as the Automations page does: on each run, an agent reads each project note no open task names by its path, checks it against the code and the other notes, and drafts the fixes in one task, `notes-garden`: rewriting what went stale, merging notes that say one thing, splitting one that says several, settling a contradiction the code settles and sharpening descriptions, then asks you to review the change rather than merge it, asking about any contradiction the code does not settle. No run starts while `notes-garden` is open. Off by default, since each run reads code for every note on your tokens; it runs every 7d until you set `--every` or `--cron`. `--off` turns it off").option("agent", z4.string(), {
|
|
462
|
-
default: "",
|
|
463
|
-
description: "the agent it runs as, as `splates agents` names it"
|
|
464
|
-
}).option("model", z4.string(), {
|
|
465
|
-
default: "",
|
|
466
|
-
description: "its model (default: the agent's own)"
|
|
467
|
-
}).option("effort", z4.string(), {
|
|
468
|
-
default: "",
|
|
469
|
-
description: "its thinking level (default: the agent's own)"
|
|
470
|
-
}).option("every", z4.string(), {
|
|
471
|
-
default: "",
|
|
472
|
-
description: "run every so often, as 1d (default: as it was, or 7d)"
|
|
473
|
-
}).option("cron", z4.string(), {
|
|
474
|
-
default: "",
|
|
475
|
-
description: `run on a five-field cron line, in this machine's time, as "0 9 * * 1"`
|
|
476
|
-
}).option("off", z4.boolean(), {
|
|
477
|
-
default: false,
|
|
478
|
-
description: "turn it off"
|
|
479
|
-
}).action(async (opts, ctx) => {
|
|
480
|
-
const pick = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
481
|
-
if (opts.off === (pick != null)) {
|
|
482
|
-
fail4("usage", "say how it runs with --agent, or turn it off with --off", {});
|
|
483
|
-
}
|
|
484
|
-
await hookGardener({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null, {
|
|
485
|
-
every: opts.every,
|
|
486
|
-
cron: opts.cron
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
626
|
function triggerOf(opts) {
|
|
490
627
|
const given = [opts.event, opts.every, opts.cron].filter((each) => each !== undefined && each !== "");
|
|
491
628
|
if (given.length !== 1) {
|
|
492
|
-
|
|
629
|
+
fail6("usage", "say what sets it off: a task event, --every or --cron, one of them", {});
|
|
493
630
|
}
|
|
494
631
|
if (opts.event === undefined || opts.event === "") {
|
|
495
632
|
if (opts.stage !== "" || opts.lane !== "") {
|
|
496
|
-
|
|
633
|
+
fail6("usage", "--stage and --lane narrow a task event, not a schedule", {});
|
|
497
634
|
}
|
|
498
635
|
return opts.every !== "" ? { every: opts.every } : { cron: opts.cron };
|
|
499
636
|
}
|
|
500
637
|
const filter = {};
|
|
501
638
|
if (opts.stage !== "") {
|
|
502
|
-
filter.stages = stagesNamed(
|
|
639
|
+
filter.stages = stagesNamed(commaList4(opts.stage));
|
|
503
640
|
}
|
|
504
641
|
if (opts.lane !== "") {
|
|
505
642
|
filter.lanes = lanesNamed(opts.lane);
|
|
@@ -527,25 +664,25 @@ import { refuseGroomer } from "@stringsync/splates-server/conversations";
|
|
|
527
664
|
import { UNTRIAGED } from "@stringsync/splates-server/plans";
|
|
528
665
|
import { refuseAgentQueue } from "@stringsync/splates-server/queue";
|
|
529
666
|
import { repository as repository4 } from "@stringsync/splates-server/records";
|
|
530
|
-
import { cli as
|
|
531
|
-
import { z as
|
|
532
|
-
var laneNumber =
|
|
533
|
-
|
|
534
|
-
|
|
667
|
+
import { cli as cli5 } from "webappwiz/cmd";
|
|
668
|
+
import { z as z6 } from "zod";
|
|
669
|
+
var laneNumber = z6.union([
|
|
670
|
+
z6.literal("backlog").transform(() => BACKLOG_LANE2),
|
|
671
|
+
z6.coerce.number().int().nonnegative()
|
|
535
672
|
]);
|
|
536
|
-
var splatesLane =
|
|
537
|
-
splatesLane.command("list").use(repository4({ needs: "read" })).description("every lane: its name, its queued tasks in order, the started tasks on it, its next task, and what blocks that in other lanes").option("json",
|
|
538
|
-
splatesLane.command("show").use(repository4({ needs: "read" })).description("one lane step by step, and what its agent does next: start the next task, or wait for the one blocking it in another lane to land first; `untriaged` lists the tasks in no lane, to place in lanes").arg("lane",
|
|
673
|
+
var splatesLane = cli5("lane").description("lanes: named groups of tasks, for you to see related work together on the board; queue one whole with `splates lane queue`. Backlog (lane 0) is permanent and holds unmatched deferred work. Untriaged is groomed into lanes and cannot be queued whole");
|
|
674
|
+
splatesLane.command("list").use(repository4({ needs: "read" })).description("every lane: its name, its queued tasks in order, the started tasks on it, its next task, and what blocks that in other lanes").option("json", z6.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => laneList(ctx, { json: opts.json }));
|
|
675
|
+
splatesLane.command("show").use(repository4({ needs: "read" })).description("one lane step by step, and what its agent does next: start the next task, or wait for the one blocking it in another lane to land first; `untriaged` lists the tasks in no lane, to place in lanes").arg("lane", z6.union([z6.literal(UNTRIAGED).transform(() => null), laneNumber], {
|
|
539
676
|
error: "pass a lane's number, backlog, or untriaged"
|
|
540
|
-
}), { description: "lane number, `backlog`, or `untriaged`" }).option("json",
|
|
541
|
-
splatesLane.command("add").use(repository4({ needs: "tasks" })).description("start an empty lane, after the others").arg("name",
|
|
677
|
+
}), { description: "lane number, `backlog`, or `untriaged`" }).option("json", z6.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => laneShow(ctx, opts.lane, { json: opts.json }));
|
|
678
|
+
splatesLane.command("add").use(repository4({ needs: "tasks" })).description("start an empty lane, after the others").arg("name", z6.string(), {
|
|
542
679
|
description: "what its tasks are for, like `Licensing`"
|
|
543
680
|
}).action(async (opts, ctx) => {
|
|
544
681
|
await ctx.journal.record("lane add", null, () => laneAdd(ctx, opts.name), (lane) => ({ lane: lane.id, name: lane.name }));
|
|
545
682
|
});
|
|
546
683
|
splatesLane.command("update").use(repository4({ needs: "tasks" })).description("rename a lane, or move it among the others").arg("lane", laneNumber, {
|
|
547
684
|
description: "lane number"
|
|
548
|
-
}).option("name",
|
|
685
|
+
}).option("name", z6.string().optional(), {
|
|
549
686
|
description: "what it is called from now on"
|
|
550
687
|
}).option("position", position, {
|
|
551
688
|
description: "where to move it among the lanes, 1 the first column on the board; the others close up around it"
|
|
@@ -582,18 +719,6 @@ splatesLane.command("remove").use(repository4({ needs: "tasks" })).description("
|
|
|
582
719
|
return recordLaneRemove(ctx.journal, ctx.tasks, opts.lane, () => laneRemove(ctx, opts.lane, { emptyOnly }));
|
|
583
720
|
});
|
|
584
721
|
|
|
585
|
-
// notes.ts
|
|
586
|
-
import { loadConfig } from "@stringsync/splates-server/config";
|
|
587
|
-
import { Notes } from "@stringsync/splates-server/notes";
|
|
588
|
-
async function notes(ctx) {
|
|
589
|
-
const root = ctx.project === undefined ? (await ctx.git.run(ctx.ps.cwd(), "rev-parse", "--show-toplevel")).stdout.trim() : ctx.git.root;
|
|
590
|
-
const config = await loadConfig(root, { fs: ctx.fs, git: ctx.git });
|
|
591
|
-
const found = await new Notes(ctx.fs).list(root, config);
|
|
592
|
-
for (const note of found) {
|
|
593
|
-
ctx.log.info(note.description);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
722
|
// projects.ts
|
|
598
723
|
import {
|
|
599
724
|
grantReach,
|
|
@@ -601,11 +726,11 @@ import {
|
|
|
601
726
|
REACHES
|
|
602
727
|
} from "@stringsync/splates-server/projects";
|
|
603
728
|
import { repository as repository5 } from "@stringsync/splates-server/records";
|
|
604
|
-
import { cli as
|
|
605
|
-
import { z as
|
|
606
|
-
var splatesProjects =
|
|
607
|
-
splatesProjects.command("list").use(repository5({ needs: "read" })).description("print each other project: its slug, its root, what it may do here, and what this one may do there").option("json",
|
|
608
|
-
splatesProjects.command("grant").use(repository5({ needs: "own" })).description("let agents of another project do as much as `read`, `queue` or `tasks` here, each including those before it; the user's call,
|
|
729
|
+
import { cli as cli6 } from "webappwiz/cmd";
|
|
730
|
+
import { z as z7 } from "zod";
|
|
731
|
+
var splatesProjects = cli6("projects").description("the other splates projects on this machine, and what agents of each may do in the others through splates: `read` their tasks and lanes; add tasks to their untriaged lane with `queue`, as every project may until granted otherwise; or run their queue, lanes and hooks with `tasks`. Any repo command runs on another project as `splates --project <slug> <command>`, refused where it needs more than that project granted").fallback("list");
|
|
732
|
+
splatesProjects.command("list").use(repository5({ needs: "read" })).description("print each other project: its slug, its root, what it may do here, and what this one may do there").option("json", z7.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => listReaches(ctx, { json: opts.json }));
|
|
733
|
+
splatesProjects.command("grant").use(repository5({ needs: "own" })).description("let agents of another project do as much as `read`, `queue` or `tasks` here, each including those before it; the user's call, refused an agent, which asks with a `## \uD83D\uDD10 Permission: grant <this> <reach> to <other>` card instead").arg("project", z7.string(), { description: "the other project's slug" }).arg("reach", z7.enum(REACHES), {
|
|
609
734
|
description: "read, queue or tasks"
|
|
610
735
|
}).action((opts, ctx) => grantReach(ctx, opts.project, opts.reach));
|
|
611
736
|
|
|
@@ -621,31 +746,31 @@ import {
|
|
|
621
746
|
} from "@stringsync/splates-server/queue";
|
|
622
747
|
import { repository as repository6 } from "@stringsync/splates-server/records";
|
|
623
748
|
import { setQueuePaused } from "@stringsync/splates-server/workers";
|
|
624
|
-
import { cli as
|
|
625
|
-
import { z as
|
|
626
|
-
var splatesQueue =
|
|
627
|
-
splatesQueue.command("list").use(repository6({ needs: "read" })).description("the queue in the order workers start it, with each task's model and what it waits on: the open tasks it is blocked by, or those removed without merging").option("json",
|
|
628
|
-
splatesQueue.command("add").use(repository6({ needs: "tasks" })).description("queue tasks for a worker to start once nothing they are blocked by is open; refused for one already under way").rest("ids",
|
|
749
|
+
import { cli as cli7 } from "webappwiz/cmd";
|
|
750
|
+
import { z as z8 } from "zod";
|
|
751
|
+
var splatesQueue = cli7("queue").description("the queue: the tasks `splates server` starts on its worker as soon as nothing they are blocked by is open, without capping how many run, otherwise in the order you set. Only you queue a task, from the backlog or groomed, and an agent asks you to with a queue card; one that names no model uses Auto pick to choose its agent and model");
|
|
752
|
+
splatesQueue.command("list").use(repository6({ needs: "read" })).description("the queue in the order workers start it, with each task's model and what it waits on: the open tasks it is blocked by, or those removed without merging").option("json", z8.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => queueList(ctx, { json: opts.json }));
|
|
753
|
+
splatesQueue.command("add").use(repository6({ needs: "tasks" })).description("queue tasks for a worker to start once nothing they are blocked by is open; refused for one already under way").rest("ids", z8.coerce.number().int().positive(), {
|
|
629
754
|
description: "task numbers"
|
|
630
755
|
}).action(async (opts, ctx) => {
|
|
631
756
|
refuseAgentQueue2(ctx.service, "queue add");
|
|
632
757
|
await refuseGroomer2(ctx.service, "queue add");
|
|
633
758
|
await ctx.journal.record("queue add", await here(ctx), () => queueAdd(ctx, opts.ids), (queued) => ({ tasks: tasksNamed(queued) }));
|
|
634
759
|
});
|
|
635
|
-
splatesQueue.command("remove").use(repository6({ needs: "tasks" })).description("take tasks out of the queue, back to ready when they name a model, else the backlog; the tasks stay").rest("ids",
|
|
760
|
+
splatesQueue.command("remove").use(repository6({ needs: "tasks" })).description("take tasks out of the queue, back to ready when they name a model, else the backlog; the tasks stay").rest("ids", z8.coerce.number().int().positive(), {
|
|
636
761
|
description: "task numbers"
|
|
637
762
|
}).action(async (opts, ctx) => {
|
|
638
763
|
refuseAgentQueue2(ctx.service, "queue remove");
|
|
639
764
|
await ctx.journal.record("queue remove", await here(ctx), () => queueRemove(ctx, opts.ids), (removed) => ({ tasks: tasksNamed(removed) }));
|
|
640
765
|
});
|
|
641
|
-
splatesQueue.command("move").use(repository6({ needs: "tasks" })).description("move a queued task to another place in the queue, from 1 for the next to start; what it is blocked by still goes first. Then lists the queue").arg("id",
|
|
766
|
+
splatesQueue.command("move").use(repository6({ needs: "tasks" })).description("move a queued task to another place in the queue, from 1 for the next to start; what it is blocked by still goes first. Then lists the queue").arg("id", z8.coerce.number().int().positive(), { description: "task number" }).arg("position", z8.coerce.number().int().positive(), {
|
|
642
767
|
description: "where in the queue to put it, from 1"
|
|
643
768
|
}).action(async (opts, ctx) => {
|
|
644
769
|
refuseAgentQueue2(ctx.service, "queue move");
|
|
645
770
|
return queueMove(ctx, opts.id, opts.position);
|
|
646
771
|
});
|
|
647
772
|
for (const command of ["pause", "resume"]) {
|
|
648
|
-
splatesQueue.command(command).use(repository6({ needs: "tasks" })).description(command === "pause" ? "pause automatic starts across the server, leaving running agents to finish and the person's own agents able to start; only the person may pause" : "resume automatic starts across the server; only the person may resume").option("dev",
|
|
773
|
+
splatesQueue.command(command).use(repository6({ needs: "tasks" })).description(command === "pause" ? "pause automatic starts across the server, leaving running agents to finish and the person's own agents able to start; only the person may pause" : "resume automatic starts across the server; only the person may resume").option("dev", z8.string(), {
|
|
649
774
|
default: "",
|
|
650
775
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one from SPLATES_DEV"
|
|
651
776
|
}).action(async (opts, ctx) => {
|
|
@@ -663,9 +788,9 @@ for (const command of ["pause", "resume"]) {
|
|
|
663
788
|
|
|
664
789
|
// replica.ts
|
|
665
790
|
import { dirname as dirname2 } from "node:path";
|
|
666
|
-
import { loadConfig
|
|
791
|
+
import { loadConfig } from "@stringsync/splates-server/config";
|
|
667
792
|
import { SYSTEM_STORES as SYSTEM_STORES2 } from "@stringsync/splates-server/creds";
|
|
668
|
-
import { fail as
|
|
793
|
+
import { fail as fail7 } from "@stringsync/splates-server/exit";
|
|
669
794
|
import { Projects } from "@stringsync/splates-server/projects";
|
|
670
795
|
import {
|
|
671
796
|
FORMAT,
|
|
@@ -680,11 +805,11 @@ import {
|
|
|
680
805
|
restoreFromReplica,
|
|
681
806
|
statusLines
|
|
682
807
|
} from "@stringsync/splates-server/replica";
|
|
683
|
-
import { cli as
|
|
808
|
+
import { cli as cli8 } from "webappwiz/cmd";
|
|
684
809
|
import { Credentials } from "webappwiz/creds";
|
|
685
|
-
import { z as
|
|
686
|
-
var splatesReplica =
|
|
687
|
-
splatesReplica.command("status").use(repository7({ needs: "read" })).description("print where the replica is, when it last held every change, how many changes wait to reach it and since when, and why it is failing if it is; as of the server's last batch, so it reads the same with the server stopped").option("json",
|
|
810
|
+
import { z as z9 } from "zod";
|
|
811
|
+
var splatesReplica = cli8("replica").description("the copy of this project's records `splates server` keeps a few seconds behind every change, in the bucket or folder `replica` names in splates.config.ts");
|
|
812
|
+
splatesReplica.command("status").use(repository7({ needs: "read" })).description("print where the replica is, when it last held every change, how many changes wait to reach it and since when, and why it is failing if it is; as of the server's last batch, so it reads the same with the server stopped").option("json", z9.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
688
813
|
const status = await Replicator.recorded(ctx.storage);
|
|
689
814
|
if (opts.json) {
|
|
690
815
|
ctx.log.info(JSON.stringify(status));
|
|
@@ -693,7 +818,7 @@ splatesReplica.command("status").use(repository7({ needs: "read" })).description
|
|
|
693
818
|
ctx.log.info(statusLines(status, ctx.config.replica !== null, Date.now()).join(`
|
|
694
819
|
`));
|
|
695
820
|
});
|
|
696
|
-
splatesReplica.command("restore").description("with `splates server` stopped, rebuild this project's records from the replica `replica` names in splates.config.ts, adopting its origin so the server started on them replicates on into it: how a server moves to another machine or comes back after its box is lost. Stops when the project already has records here, unless --force leaves them aside as state.db.moved. The roster and Auto pick rules are not in the replica; set them up again. A bucket's REPLICA_ACCESS_KEY and REPLICA_SECRET_KEY must be filled on this machine first").option("force",
|
|
821
|
+
splatesReplica.command("restore").description("with `splates server` stopped, rebuild this project's records from the replica `replica` names in splates.config.ts, adopting its origin so the server started on them replicates on into it: how a server moves to another machine or comes back after its box is lost. Stops when the project already has records here, unless --force leaves them aside as state.db.moved. The roster and Auto pick rules are not in the replica; set them up again. A bucket's REPLICA_ACCESS_KEY and REPLICA_SECRET_KEY must be filled on this machine first").option("force", z9.boolean(), {
|
|
697
822
|
default: false,
|
|
698
823
|
description: "restore over the records here, leaving them aside"
|
|
699
824
|
}).action(async (opts, ctx) => {
|
|
@@ -705,9 +830,9 @@ splatesReplica.command("restore").description("with `splates server` stopped, re
|
|
|
705
830
|
await stores.project(root),
|
|
706
831
|
stores.device
|
|
707
832
|
]);
|
|
708
|
-
const config = await
|
|
833
|
+
const config = await loadConfig(root, { fs: ctx.fs });
|
|
709
834
|
if (config.replica === null) {
|
|
710
|
-
|
|
835
|
+
fail7("usage", "no replica: name one as `replica` in splates.config.ts first");
|
|
711
836
|
}
|
|
712
837
|
const replica = openReplica(config.replica, {
|
|
713
838
|
root,
|
|
@@ -734,10 +859,10 @@ splatesReplica.command("restore").description("with `splates server` stopped, re
|
|
|
734
859
|
].join(`
|
|
735
860
|
`));
|
|
736
861
|
} catch (error) {
|
|
737
|
-
|
|
862
|
+
fail7("usage", error instanceof Error ? error.message : String(error));
|
|
738
863
|
}
|
|
739
864
|
});
|
|
740
|
-
splatesReplica.command("retire").use(repository7({ needs: "own" })).description("after the server moved elsewhere (stop it and check `splates replica status` says caught up first), leave this project's records aside as state.db.moved, so a splates run here finds no stale records and says where the server went").arg("to",
|
|
865
|
+
splatesReplica.command("retire").use(repository7({ needs: "own" })).description("after the server moved elsewhere (stop it and check `splates replica status` says caught up first), leave this project's records aside as state.db.moved, so a splates run here finds no stale records and says where the server went").arg("to", z9.string(), {
|
|
741
866
|
description: "where the server went, as `droplet.example.com`"
|
|
742
867
|
}).action(async (opts, ctx) => {
|
|
743
868
|
const home = new ProjectHome(ctx.git.root, ctx);
|
|
@@ -755,7 +880,7 @@ async function mainTree(ctx) {
|
|
|
755
880
|
"--git-common-dir"
|
|
756
881
|
]);
|
|
757
882
|
if (exitCode !== 0) {
|
|
758
|
-
|
|
883
|
+
fail7("usage", `not a git repository: ${ctx.ps.cwd()}`);
|
|
759
884
|
}
|
|
760
885
|
return dirname2(stdout.trim());
|
|
761
886
|
}
|
|
@@ -765,34 +890,34 @@ import {
|
|
|
765
890
|
DEFAULT_PORT,
|
|
766
891
|
serveDev
|
|
767
892
|
} from "@stringsync/splates-server/server";
|
|
768
|
-
import { cli as
|
|
769
|
-
import { z as
|
|
770
|
-
var splatesServer =
|
|
771
|
-
splatesServer.command("run").description("serve every splates project on this machine as a web page, from wherever it is run: the page picks the project it shows, adds one by its path, running `splates init` there, and shows its queued and started tasks, where queued tasks can be added, updated, reordered or removed; it brings each project's records forward as it opens it. A server that crashes starts again after 2s, then 4s, 8s and 16s, and is left down after five crashes in a row that each came within a minute of its start").option("port",
|
|
893
|
+
import { cli as cli9 } from "webappwiz/cmd";
|
|
894
|
+
import { z as z10 } from "zod";
|
|
895
|
+
var splatesServer = cli9("server").description("the splates server: run it on this machine").fallback("run");
|
|
896
|
+
splatesServer.command("run").description("serve every splates project on this machine as a web page, from wherever it is run: the page picks the project it shows, adds one by its path, running `splates init` there, and shows its queued and started tasks, where queued tasks can be added, updated, reordered or removed; it brings each project's records forward as it opens it. A server that crashes starts again after 2s, then 4s, 8s and 16s, and is left down after five crashes in a row that each came within a minute of its start").option("port", z10.coerce.number(), {
|
|
772
897
|
default: DEFAULT_PORT,
|
|
773
898
|
description: "port to listen on, or the next open one above it"
|
|
774
|
-
}).option("allow-hosts",
|
|
899
|
+
}).option("allow-hosts", z10.string(), {
|
|
775
900
|
default: "",
|
|
776
901
|
description: "comma-separated host names the page may also be reached by, like a tunnel's; put Cloudflare Access in front of them with --access-team and --access-aud"
|
|
777
|
-
}).option("access-team",
|
|
902
|
+
}).option("access-team", z10.string(), {
|
|
778
903
|
default: "",
|
|
779
904
|
description: "the Cloudflare Access team in front of --allow-hosts, as in <team>.cloudflareaccess.com; with --access-aud, those hosts answer only to a verified Access token, and may then use conversations and terminals"
|
|
780
|
-
}).option("access-aud",
|
|
905
|
+
}).option("access-aud", z10.string(), {
|
|
781
906
|
default: "",
|
|
782
907
|
description: "the AUD tag of that Access application"
|
|
783
|
-
}).option("tunnel",
|
|
908
|
+
}).option("tunnel", z10.string(), {
|
|
784
909
|
default: "",
|
|
785
910
|
description: "the public host name of a tunnel to run, with cloudflared or the tunnel provider named by splates.config.ts in the repository it is run in, if any, which is allowed as a host too; refused without --access-team and --access-aud, so a tunnel never exposes the page unauthenticated. With none of the tunnel and Access flags, the tunnel `splates tunnel setup` saved runs, if any"
|
|
786
|
-
}).option("tunnel-name",
|
|
911
|
+
}).option("tunnel-name", z10.string(), {
|
|
787
912
|
default: "",
|
|
788
913
|
description: "the named tunnel cloudflared runs for --tunnel, by default the one its own config names; not with a tunnel provider of splates.config.ts"
|
|
789
|
-
}).option("ports-host",
|
|
914
|
+
}).option("ports-host", z10.string(), {
|
|
790
915
|
default: "",
|
|
791
916
|
description: "the wildcard base host to serve task ports under, saved for this machine so a bare `splates server` serves it too: each port a task's processes listen on opens at https://<task>-<port>.<base>, to whoever Cloudflare Access lets in, and is refused without Access; routing *.<base> here, its DNS and TLS, are yours. `none` forgets it, leaving each task port forwarded to a port on this machine's loopback alone"
|
|
792
|
-
}).option("keepalive",
|
|
917
|
+
}).option("keepalive", z10.boolean(), {
|
|
793
918
|
default: false,
|
|
794
919
|
description: "keep a machine left to serve awake while the server runs: on macOS with `caffeinate`, on power or idle, though closing a laptop's lid still sleeps it; on Linux with a systemd-inhibit lock on sleep and idle, which the server serves on without, saying so, where systemd is missing or refuses it. Refused on other platforms"
|
|
795
|
-
}).option("slot",
|
|
920
|
+
}).option("slot", z10.boolean(), {
|
|
796
921
|
default: false,
|
|
797
922
|
description: "serve this checkout's splates at https://<word>.<zone>, a slot of its own on the zone `splates tunnel setup` uses, behind the same Access policy, so several run side by side and a phone opens each by name: a task's worktree keeps its word, as fox, until the task merges or is removed, which stops its server; run again, it stops the tree's last server first. Refused in a splates repository's main tree, which every agent shares. A slot keeps its own projects in ~/.splates/slots/<word>, apart from yours, starting with a throwaway project there; not with the tunnel and Access flags"
|
|
798
923
|
}).action((opts, ctx) => serveDev(ctx, opts));
|
|
@@ -800,7 +925,7 @@ splatesServer.command("run").description("serve every splates project on this ma
|
|
|
800
925
|
// setup.ts
|
|
801
926
|
import { dirname as dirname3 } from "node:path";
|
|
802
927
|
import { SYSTEM_STORES as SYSTEM_STORES3 } from "@stringsync/splates-server/creds";
|
|
803
|
-
import { fail as
|
|
928
|
+
import { fail as fail8 } from "@stringsync/splates-server/exit";
|
|
804
929
|
import { splatesHome as splatesHome2 } from "@stringsync/splates-server/models";
|
|
805
930
|
import { Projects as Projects2 } from "@stringsync/splates-server/projects";
|
|
806
931
|
import {
|
|
@@ -813,18 +938,18 @@ import {
|
|
|
813
938
|
setupProjectAt
|
|
814
939
|
} from "@stringsync/splates-server/setup";
|
|
815
940
|
import { fetchWorkers } from "@stringsync/splates-server/workers";
|
|
816
|
-
import { color as
|
|
941
|
+
import { color as color3 } from "webappwiz/log";
|
|
817
942
|
async function setup(deps, opts) {
|
|
818
943
|
if (opts.step === "" && (opts.skip || opts.unskip || opts.undo)) {
|
|
819
|
-
|
|
944
|
+
fail8("usage", "--skip and --unskip name a step, --undo an area", {});
|
|
820
945
|
}
|
|
821
946
|
if (opts.undo && (opts.skip || opts.unskip)) {
|
|
822
|
-
|
|
947
|
+
fail8("usage", "--undo names an area, not a step to skip", {
|
|
823
948
|
area: opts.step
|
|
824
949
|
});
|
|
825
950
|
}
|
|
826
951
|
if (opts.skip && opts.unskip) {
|
|
827
|
-
|
|
952
|
+
fail8("usage", "--skip or --unskip, not both", { step: opts.step });
|
|
828
953
|
}
|
|
829
954
|
const machine = await setupOf(deps);
|
|
830
955
|
const { log } = deps;
|
|
@@ -897,10 +1022,10 @@ async function repoRoot(ps) {
|
|
|
897
1022
|
return exitCode === 0 ? dirname3(stdout.trim()) : null;
|
|
898
1023
|
}
|
|
899
1024
|
var MARKS = {
|
|
900
|
-
done:
|
|
901
|
-
skipped:
|
|
902
|
-
ready:
|
|
903
|
-
waiting:
|
|
1025
|
+
done: color3.green("✓"),
|
|
1026
|
+
skipped: color3.dim("-"),
|
|
1027
|
+
ready: color3.yellow("○"),
|
|
1028
|
+
waiting: color3.dim("○")
|
|
904
1029
|
};
|
|
905
1030
|
function printProgress(log, progress) {
|
|
906
1031
|
for (const area of progress.areas) {
|
|
@@ -908,7 +1033,7 @@ function printProgress(log, progress) {
|
|
|
908
1033
|
`${area.done}/${area.steps.length} done`,
|
|
909
1034
|
area.skipped > 0 ? `${area.skipped} skipped` : ""
|
|
910
1035
|
].filter(Boolean);
|
|
911
|
-
log.info(`${
|
|
1036
|
+
log.info(`${color3.bold(area.title)} ${color3.dim(counts.join(", "))}`);
|
|
912
1037
|
for (const step of area.steps) {
|
|
913
1038
|
const notes = [
|
|
914
1039
|
step.optional ? "optional" : "",
|
|
@@ -916,24 +1041,24 @@ function printProgress(log, progress) {
|
|
|
916
1041
|
step.problem ?? "",
|
|
917
1042
|
step.warning === null ? "" : "to put right"
|
|
918
1043
|
].filter(Boolean);
|
|
919
|
-
log.info(` ${MARKS[step.status]} ${step.id} ${step.title}${notes.length > 0 ?
|
|
1044
|
+
log.info(` ${MARKS[step.status]} ${step.id} ${step.title}${notes.length > 0 ? color3.dim(` ${notes.join("; ")}`) : ""}`);
|
|
920
1045
|
}
|
|
921
1046
|
}
|
|
922
1047
|
const steps = progress.areas.flatMap((area) => area.steps);
|
|
923
1048
|
const next = steps.find((step) => step.id === progress.next);
|
|
924
1049
|
for (const step of steps.filter((each) => each.warning !== null)) {
|
|
925
|
-
log.info(`${
|
|
1050
|
+
log.info(`${color3.yellow("put right:")} ${step.title} ${color3.dim(`splates setup ${step.id}`)}`);
|
|
926
1051
|
}
|
|
927
|
-
log.info(next === undefined ?
|
|
1052
|
+
log.info(next === undefined ? color3.green("nothing left to set up") : `next: ${next.title} ${color3.dim(`splates setup ${next.id}`)}`);
|
|
928
1053
|
}
|
|
929
1054
|
function printStep(log, step) {
|
|
930
|
-
log.info(`${
|
|
1055
|
+
log.info(`${color3.bold(step.title)} ${color3.dim(`${step.status}${step.optional ? ", optional" : ""}`)}`);
|
|
931
1056
|
log.info(step.why);
|
|
932
1057
|
if (step.detail !== null) {
|
|
933
1058
|
log.info(step.detail);
|
|
934
1059
|
}
|
|
935
1060
|
if (step.warning !== null) {
|
|
936
|
-
log.info(`${
|
|
1061
|
+
log.info(`${color3.yellow("put right:")} ${step.warning}`);
|
|
937
1062
|
}
|
|
938
1063
|
log.info(`you: ${step.user}`);
|
|
939
1064
|
log.info(`agent: ${step.agent}`);
|
|
@@ -941,18 +1066,18 @@ function printStep(log, step) {
|
|
|
941
1066
|
log.info(`after: ${step.after.join(", ")}`);
|
|
942
1067
|
}
|
|
943
1068
|
if (step.problem !== null) {
|
|
944
|
-
log.info(`${
|
|
1069
|
+
log.info(`${color3.red("problem:")} ${step.problem}`);
|
|
945
1070
|
}
|
|
946
1071
|
}
|
|
947
1072
|
function printUndo(log, plan) {
|
|
948
|
-
log.info(`${
|
|
1073
|
+
log.info(`${color3.bold(`Undo ${plan.title}`)} ${color3.dim(plan.area)}`);
|
|
949
1074
|
if (plan.refused !== null) {
|
|
950
|
-
log.info(`${
|
|
1075
|
+
log.info(`${color3.red("refused:")} ${plan.refused}`);
|
|
951
1076
|
return;
|
|
952
1077
|
}
|
|
953
1078
|
for (const step of plan.steps) {
|
|
954
|
-
const started = step.done ? "" : ` ${
|
|
955
|
-
log.info(` ${step.title} ${
|
|
1079
|
+
const started = step.done ? "" : ` ${color3.yellow("started")}`;
|
|
1080
|
+
log.info(` ${step.title} ${color3.dim(step.id)}${started}`);
|
|
956
1081
|
for (const line of step.removes) {
|
|
957
1082
|
log.info(` removes ${line}`);
|
|
958
1083
|
}
|
|
@@ -961,20 +1086,20 @@ function printUndo(log, plan) {
|
|
|
961
1086
|
}
|
|
962
1087
|
}
|
|
963
1088
|
for (const cred of plan.creds) {
|
|
964
|
-
log.info(cred.keptFor === null ? ` forgets ${cred.name}` : ` keeps ${cred.name} ${
|
|
1089
|
+
log.info(cred.keptFor === null ? ` forgets ${cred.name}` : ` keeps ${cred.name} ${color3.dim(`${cred.keptFor} still uses it`)}`);
|
|
965
1090
|
}
|
|
966
1091
|
if (plan.confirm !== null) {
|
|
967
1092
|
log.info(` the page asks for ${plan.confirm} to be typed`);
|
|
968
1093
|
}
|
|
969
|
-
log.info(
|
|
1094
|
+
log.info(color3.dim("the Setup page's Undo does it, after the user confirms"));
|
|
970
1095
|
}
|
|
971
1096
|
|
|
972
1097
|
// slots.ts
|
|
973
1098
|
import { Slots } from "@stringsync/splates-server/ports";
|
|
974
|
-
import { cli as
|
|
975
|
-
import { z as
|
|
976
|
-
var splatesSlots =
|
|
977
|
-
splatesSlots.command("list").description("print each claimed word, the tree holding it, its server's pid while one runs, and whether it is orphaned: its tree gone, or a splates repository's main tree, which every agent shares").option("json",
|
|
1099
|
+
import { cli as cli10 } from "webappwiz/cmd";
|
|
1100
|
+
import { z as z11 } from "zod";
|
|
1101
|
+
var splatesSlots = cli10("slots").description("the words `splates server --slot` hands out on this machine, each held by the tree that claimed it: a task's worktree holds its word until the task merges or is removed, which stops its server and gives the word back");
|
|
1102
|
+
splatesSlots.command("list").description("print each claimed word, the tree holding it, its server's pid while one runs, and whether it is orphaned: its tree gone, or a splates repository's main tree, which every agent shares").option("json", z11.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
978
1103
|
const held = await Slots.ofMachine(ctx).status();
|
|
979
1104
|
if (opts.json) {
|
|
980
1105
|
ctx.log.info(JSON.stringify(held.map(({ slot, tree, pid, orphaned }) => ({
|
|
@@ -1017,19 +1142,19 @@ import {
|
|
|
1017
1142
|
tasksNamed as tasksNamed2,
|
|
1018
1143
|
taskUpdate
|
|
1019
1144
|
} from "@stringsync/splates-server/board";
|
|
1020
|
-
import { commaList as
|
|
1145
|
+
import { commaList as commaList5 } from "@stringsync/splates-server/comma-list";
|
|
1021
1146
|
import { refuseGroomer as refuseGroomer3 } from "@stringsync/splates-server/conversations";
|
|
1022
|
-
import { fail as
|
|
1023
|
-
import { repository as repository8 } from "@stringsync/splates-server/records";
|
|
1147
|
+
import { fail as fail9 } from "@stringsync/splates-server/exit";
|
|
1148
|
+
import { OpenBoards, repository as repository8 } from "@stringsync/splates-server/records";
|
|
1024
1149
|
import {
|
|
1025
1150
|
DEFAULT_TIMEOUT as DEFAULT_TIMEOUT2,
|
|
1026
1151
|
taskWait,
|
|
1027
1152
|
untilAfter as untilAfter2
|
|
1028
1153
|
} from "@stringsync/splates-server/tasks";
|
|
1029
|
-
import { cli as
|
|
1154
|
+
import { cli as cli11 } from "webappwiz/cmd";
|
|
1030
1155
|
import { Duration as Duration2 } from "webappwiz/time";
|
|
1031
|
-
import { z as
|
|
1032
|
-
var splatesTask =
|
|
1156
|
+
import { z as z12 } from "zod";
|
|
1157
|
+
var splatesTask = cli11("task").description("the numbered tasks: defer work for later instead of growing the task you are in, and `splates add <slug> --task <n>` starts one; `merge` recommends the next one when a task lands. The person puts tasks in `splates queue`, for workers to start");
|
|
1033
1158
|
async function projectFrom(ctx) {
|
|
1034
1159
|
if (ctx.elsewhere === null) {
|
|
1035
1160
|
return;
|
|
@@ -1037,62 +1162,77 @@ async function projectFrom(ctx) {
|
|
|
1037
1162
|
return (await ctx.projects.list()).find((each) => each.root === ctx.git.root)?.slug;
|
|
1038
1163
|
}
|
|
1039
1164
|
function adding(name, description, ready) {
|
|
1040
|
-
splatesTask.command(name).use(repository8({ needs: "queue" })).description(description).arg("subject",
|
|
1165
|
+
splatesTask.command(name).use(repository8({ needs: "queue" })).description(description).arg("subject", z12.string(), {
|
|
1041
1166
|
description: "what is left to do, in a line"
|
|
1042
|
-
}).arg("text",
|
|
1167
|
+
}).arg("text", z12.string(), {
|
|
1043
1168
|
default: "",
|
|
1044
1169
|
description: "whatever more there is to say about it, in markdown"
|
|
1045
|
-
}).option("slug",
|
|
1170
|
+
}).option("slug", z12.string(), {
|
|
1046
1171
|
description: "a short name for the task that takes it (lowercase-with-dashes), naming its branch, worktree and conversation: a few words for what it changes, not the subject cut short"
|
|
1047
1172
|
}).option("position", position, {
|
|
1048
1173
|
description: "where it goes in the list, 1 at the top, pushing those from there down (default: the bottom)"
|
|
1049
|
-
}).repeated("file",
|
|
1174
|
+
}).repeated("file", z12.string(), {
|
|
1050
1175
|
description: "files to attach, comma separated or the flag repeated: copied under .git/splates/tasks/<n>/"
|
|
1051
|
-
}).repeated("blocked-by",
|
|
1052
|
-
description: "
|
|
1053
|
-
}).option("lane",
|
|
1176
|
+
}).repeated("blocked-by", z12.string(), {
|
|
1177
|
+
description: "the tasks blocking it, comma separated or the flag repeated: a number for one here, <project>#<number> for one of another project (`webappwiz#404`); each has to merge before this one starts"
|
|
1178
|
+
}).option("lane", z12.string(), {
|
|
1054
1179
|
default: "",
|
|
1055
1180
|
description: "the lane it goes in, at the bottom: a lane's number, `backlog` for the permanent catch-all, or `new` for one of its own (default: none, untriaged)"
|
|
1056
|
-
}).option("agent",
|
|
1181
|
+
}).option("agent", z12.string(), {
|
|
1057
1182
|
default: "",
|
|
1058
1183
|
description: "the agent on the roster (`splates agents`) that should work it, which Start on the `splates server` board offers first"
|
|
1059
|
-
}).option("model",
|
|
1184
|
+
}).option("model", z12.string(), {
|
|
1060
1185
|
default: "",
|
|
1061
1186
|
description: "with --agent: the model, by its id or name on the roster"
|
|
1062
|
-
}).option("effort",
|
|
1187
|
+
}).option("effort", z12.string(), {
|
|
1063
1188
|
default: "",
|
|
1064
1189
|
description: "with --agent: the thinking level, one the agent offers (default: the agent's own)"
|
|
1065
1190
|
}).action(async (opts, ctx) => {
|
|
1066
|
-
|
|
1067
|
-
|
|
1191
|
+
let __stack = [];
|
|
1192
|
+
try {
|
|
1193
|
+
if (ctx.elsewhere !== null && opts.lane !== "") {
|
|
1194
|
+
fail9("not_granted", "a task from another project goes in its untriaged lane, for whoever orchestrates it there to place: leave --lane off", { subject: opts.subject });
|
|
1195
|
+
}
|
|
1196
|
+
const from = await here(ctx);
|
|
1197
|
+
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
1198
|
+
const origin = ctx.elsewhere === null ? undefined : {
|
|
1199
|
+
project: ctx.elsewhere,
|
|
1200
|
+
task: ctx.project === undefined ? null : ctx.service.taskFor(await ctx.git.currentBranch(ctx.ps.cwd()))
|
|
1201
|
+
};
|
|
1202
|
+
const project = await projectFrom(ctx);
|
|
1203
|
+
const boards = __using(__stack, new OpenBoards(ctx, ctx.projects), 0);
|
|
1204
|
+
const { blockedBy, blockedElsewhere } = await blockerFlags({
|
|
1205
|
+
boards,
|
|
1206
|
+
here: ctx.git.root,
|
|
1207
|
+
blockedBy: opts["blocked-by"]
|
|
1208
|
+
});
|
|
1209
|
+
await ctx.journal.record("task add", from, () => taskAdd(ctx, opts.subject, from, {
|
|
1210
|
+
slug: opts.slug,
|
|
1211
|
+
text: opts.text,
|
|
1212
|
+
position: opts.position,
|
|
1213
|
+
files: commaList5(opts.file.join(",")),
|
|
1214
|
+
blockedBy,
|
|
1215
|
+
blockedElsewhere,
|
|
1216
|
+
lane: laneIn(opts.lane) ?? null,
|
|
1217
|
+
assignee,
|
|
1218
|
+
origin,
|
|
1219
|
+
ready,
|
|
1220
|
+
project,
|
|
1221
|
+
boards
|
|
1222
|
+
}), addedFacts);
|
|
1223
|
+
} catch (_catch) {
|
|
1224
|
+
var _err = _catch, _hasErr = 1;
|
|
1225
|
+
} finally {
|
|
1226
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1068
1227
|
}
|
|
1069
|
-
const from = await here(ctx);
|
|
1070
|
-
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
1071
|
-
const origin = ctx.elsewhere === null ? undefined : {
|
|
1072
|
-
project: ctx.elsewhere,
|
|
1073
|
-
task: ctx.project === undefined ? null : ctx.service.taskFor(await ctx.git.currentBranch(ctx.ps.cwd()))
|
|
1074
|
-
};
|
|
1075
|
-
const project = await projectFrom(ctx);
|
|
1076
|
-
await ctx.journal.record("task add", from, () => taskAdd(ctx, opts.subject, from, {
|
|
1077
|
-
slug: opts.slug,
|
|
1078
|
-
text: opts.text,
|
|
1079
|
-
position: opts.position,
|
|
1080
|
-
files: commaList4(opts.file.join(",")),
|
|
1081
|
-
blockedBy: taskIds(opts["blocked-by"].join(",")),
|
|
1082
|
-
lane: laneIn(opts.lane) ?? null,
|
|
1083
|
-
assignee,
|
|
1084
|
-
origin,
|
|
1085
|
-
ready,
|
|
1086
|
-
project
|
|
1087
|
-
}), addedFacts);
|
|
1088
1228
|
});
|
|
1089
1229
|
}
|
|
1090
1230
|
adding("add", "queue a task to do later and move on, groomed, so it goes in ready: you checked no task already asks it, and linked what blocks it; run from a worktree, it records the task it came up in", true);
|
|
1091
1231
|
adding("defer", "queue a task as it is, rough, in the backlog for whoever grooms it: to check for one that already asks it, link what blocks it, and mark it ready; run from a worktree, it records the task it came up in", false);
|
|
1092
|
-
splatesTask.command("list").use(repository8({ needs: "read" })).description("every queued task, top of the list first, with the task it came from and the started one that took it up").option("json",
|
|
1232
|
+
splatesTask.command("list").use(repository8({ needs: "read" })).description("every queued task, top of the list first, with the task it came from and the started one that took it up").option("json", z12.boolean(), { default: false, description: "emit JSON" }).option("open", z12.boolean(), {
|
|
1093
1233
|
default: false,
|
|
1094
1234
|
description: "only tasks no started one has taken up: the ones free to pick up"
|
|
1095
|
-
}).option("ready",
|
|
1235
|
+
}).option("ready", z12.boolean(), {
|
|
1096
1236
|
default: false,
|
|
1097
1237
|
description: "only tasks free to start now: none has taken them, and nothing blocking them is still open"
|
|
1098
1238
|
}).action(async (opts, ctx) => taskList(ctx, {
|
|
@@ -1101,77 +1241,106 @@ splatesTask.command("list").use(repository8({ needs: "read" })).description("eve
|
|
|
1101
1241
|
ready: opts.ready,
|
|
1102
1242
|
project: await projectFrom(ctx)
|
|
1103
1243
|
}));
|
|
1104
|
-
splatesTask.command("show").use(repository8({ needs: "read" })).description("one task in full: where it stands, its files, and its detail").arg("id",
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1244
|
+
splatesTask.command("show").use(repository8({ needs: "read" })).description("one task in full: where it stands, its files, and its detail").arg("id", z12.coerce.number().int().positive(), { description: "task number" }).option("json", z12.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
1245
|
+
let __stack = [];
|
|
1246
|
+
try {
|
|
1247
|
+
const boards = __using(__stack, new OpenBoards(ctx, ctx.projects), 0);
|
|
1248
|
+
await taskShow(ctx, opts.id, {
|
|
1249
|
+
json: opts.json,
|
|
1250
|
+
project: await projectFrom(ctx),
|
|
1251
|
+
others: (await ctx.projects.list()).filter((project) => project.root !== ctx.git.root).map((project) => project.slug),
|
|
1252
|
+
boards,
|
|
1253
|
+
root: ctx.git.root
|
|
1254
|
+
});
|
|
1255
|
+
} catch (_catch) {
|
|
1256
|
+
var _err = _catch, _hasErr = 1;
|
|
1257
|
+
} finally {
|
|
1258
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
splatesTask.command("update").use(repository8({ needs: "tasks" })).description("say what a queued task is in other words, move it up or down the list, or attach and drop files; it keeps its number").arg("id", z12.coerce.number().int().positive(), { description: "task number" }).arg("text", z12.string(), {
|
|
1110
1262
|
default: "",
|
|
1111
1263
|
description: "whatever more there is to say about it, in markdown; leave out to keep the detail"
|
|
1112
|
-
}).option("subject",
|
|
1264
|
+
}).option("subject", z12.string(), {
|
|
1113
1265
|
default: "",
|
|
1114
1266
|
description: "what is left to do, in a line; leave out to keep it"
|
|
1115
|
-
}).option("slug",
|
|
1267
|
+
}).option("slug", z12.string(), {
|
|
1116
1268
|
default: "",
|
|
1117
1269
|
description: "a new name for the task that takes it (lowercase-with-dashes); leave out to keep it"
|
|
1118
1270
|
}).option("position", position, {
|
|
1119
1271
|
description: "where to move it in the list, 1 at the top; the others close up around it"
|
|
1120
|
-
}).repeated("file",
|
|
1272
|
+
}).repeated("file", z12.string(), {
|
|
1121
1273
|
description: "files to attach, comma separated or the flag repeated"
|
|
1122
|
-
}).repeated("remove-file",
|
|
1274
|
+
}).repeated("remove-file", z12.string(), {
|
|
1123
1275
|
description: "attached files to drop, comma separated or the flag repeated, by path or stored name"
|
|
1124
|
-
}).repeated("blocked-by",
|
|
1125
|
-
description: "
|
|
1126
|
-
}).repeated("remove-blocked-by",
|
|
1127
|
-
description: "
|
|
1128
|
-
}).option("lane",
|
|
1276
|
+
}).repeated("blocked-by", z12.string(), {
|
|
1277
|
+
description: "more tasks blocking it, comma separated or the flag repeated: a number for one here, <project>#<number> for one of another project (`webappwiz#404`); each has to merge before this one starts; refused when that makes a loop"
|
|
1278
|
+
}).repeated("remove-blocked-by", z12.string(), {
|
|
1279
|
+
description: "tasks no longer blocking it, as --blocked-by names them, comma separated or the flag repeated"
|
|
1280
|
+
}).option("lane", z12.string(), {
|
|
1129
1281
|
default: "",
|
|
1130
1282
|
description: "move it to a lane, at the bottom: a lane's number, `backlog` for the permanent catch-all, `new` for one of its own, or `none` for untriaged"
|
|
1131
|
-
}).option("agent",
|
|
1283
|
+
}).option("agent", z12.string(), {
|
|
1132
1284
|
default: "",
|
|
1133
1285
|
description: "the agent on the roster (`splates agents`) that should work it, which Start on the `splates server` board offers first, or `none`"
|
|
1134
|
-
}).option("model",
|
|
1286
|
+
}).option("model", z12.string(), {
|
|
1135
1287
|
default: "",
|
|
1136
1288
|
description: "with --agent: the model, by its id or name on the roster"
|
|
1137
|
-
}).option("effort",
|
|
1289
|
+
}).option("effort", z12.string(), {
|
|
1138
1290
|
default: "",
|
|
1139
1291
|
description: "with --agent: the thinking level, one the agent offers (default: the agent's own)"
|
|
1140
|
-
}).option("ready",
|
|
1292
|
+
}).option("ready", z12.boolean(), {
|
|
1141
1293
|
default: false,
|
|
1142
1294
|
description: "mark it groomed, moving it from the backlog to ready: no task already asks it, and what blocks it is linked"
|
|
1143
|
-
}).option("manual",
|
|
1295
|
+
}).option("manual", z12.boolean(), {
|
|
1144
1296
|
default: false,
|
|
1145
1297
|
description: "queue it by hand only: a queued lane passes over it, and it starts once you `splates queue add` it"
|
|
1146
|
-
}).option("auto",
|
|
1298
|
+
}).option("auto", z12.boolean(), {
|
|
1147
1299
|
default: false,
|
|
1148
1300
|
description: "let a queued lane queue it again, undoing --manual"
|
|
1149
1301
|
}).action(async (opts, ctx) => {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1302
|
+
let __stack = [];
|
|
1303
|
+
try {
|
|
1304
|
+
if (opts.manual && opts.auto) {
|
|
1305
|
+
fail9("usage", "pass --manual or --auto, not both", { number: opts.id });
|
|
1306
|
+
}
|
|
1307
|
+
await refuseGroomer3(ctx.service, "task update", await ctx.tasks.find(opts.id));
|
|
1308
|
+
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
1309
|
+
const boards = __using(__stack, new OpenBoards(ctx, ctx.projects), 0);
|
|
1310
|
+
const { blockedBy, blockedElsewhere, unblocked } = await blockerFlags({
|
|
1311
|
+
boards,
|
|
1312
|
+
here: ctx.git.root,
|
|
1313
|
+
blockedBy: opts["blocked-by"],
|
|
1314
|
+
removeBlockedBy: opts["remove-blocked-by"]
|
|
1315
|
+
});
|
|
1316
|
+
await recordUpdate(ctx.journal, ctx.tasks, opts.id, () => taskUpdate(ctx, opts.id, {
|
|
1317
|
+
subject: opts.subject,
|
|
1318
|
+
slug: opts.slug,
|
|
1319
|
+
text: opts.text,
|
|
1320
|
+
position: opts.position,
|
|
1321
|
+
files: commaList5(opts.file.join(",")),
|
|
1322
|
+
removeFiles: commaList5(opts["remove-file"].join(",")),
|
|
1323
|
+
blockedBy,
|
|
1324
|
+
removeBlockedBy: unblocked.ids,
|
|
1325
|
+
blockedElsewhere,
|
|
1326
|
+
removeElsewhere: unblocked.elsewhere,
|
|
1327
|
+
lane: laneIn(opts.lane),
|
|
1328
|
+
assignee,
|
|
1329
|
+
ready: opts.ready,
|
|
1330
|
+
manual: opts.manual ? true : opts.auto ? false : undefined,
|
|
1331
|
+
boards
|
|
1332
|
+
}));
|
|
1333
|
+
} catch (_catch) {
|
|
1334
|
+
var _err = _catch, _hasErr = 1;
|
|
1335
|
+
} finally {
|
|
1336
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1337
|
+
}
|
|
1169
1338
|
});
|
|
1170
|
-
splatesTask.command("remove").use(repository8({ needs: "tasks" })).description("drop a queued task that was done some other way or no longer applies").arg("id",
|
|
1339
|
+
splatesTask.command("remove").use(repository8({ needs: "tasks" })).description("drop a queued task that was done some other way or no longer applies").arg("id", z12.coerce.number().int().positive(), { description: "task number" }).action(async (opts, ctx) => {
|
|
1171
1340
|
await refuseGroomer3(ctx.service, "task remove", await ctx.tasks.find(opts.id));
|
|
1172
1341
|
await ctx.journal.record("task remove", null, () => taskRemove(ctx, opts.id), (removed) => ({ tasks: tasksNamed2([removed]) }));
|
|
1173
1342
|
});
|
|
1174
|
-
splatesTask.command("wait").use(repository8({ needs: "read" })).description("block until a task leaves the queue, which it does when the task that took it merges: for a task blocked by one another lane is on; ends early when that task waits on a person to answer its questions, and gives up with `timeout`").arg("id",
|
|
1343
|
+
splatesTask.command("wait").use(repository8({ needs: "read" })).description("block until a task leaves the queue, which it does when the task that took it merges: for a task blocked by one another lane is on; ends early when that task waits on a person to answer its questions, and gives up with `timeout`").arg("id", z12.coerce.number().int().positive(), { description: "task number" }).option("timeout-secs", z12.coerce.number(), {
|
|
1175
1344
|
default: DEFAULT_TIMEOUT2.secs,
|
|
1176
1345
|
description: "how long to wait before giving up"
|
|
1177
1346
|
}).action(async (opts, ctx) => {
|
|
@@ -1192,18 +1361,18 @@ import {
|
|
|
1192
1361
|
tunnelRemove,
|
|
1193
1362
|
tunnelSetup
|
|
1194
1363
|
} from "@stringsync/splates-server/tunnel";
|
|
1195
|
-
import { cli as
|
|
1196
|
-
import { z as
|
|
1197
|
-
var splatesTunnel =
|
|
1198
|
-
splatesTunnel.command("setup").use(repository9({ needs: "own" })).description(`make a Cloudflare tunnel to \`splates server\`, the DNS record routing <host> to it, and an Access application letting in only --email, through the API with the token kept as the cred ${API_TOKEN}, then save them for this machine, so a bare \`splates server\` comes up tunneled; run again, it changes only what is not so already. Without that token it requests it on the Creds page of \`splates server\`, with how to make it, and exits \`requested\`: \`splates creds wait ${API_TOKEN}\`, then run it again`).arg("host",
|
|
1364
|
+
import { cli as cli12 } from "webappwiz/cmd";
|
|
1365
|
+
import { z as z13 } from "zod";
|
|
1366
|
+
var splatesTunnel = cli12("tunnel").description("reach `splates server` from elsewhere: a Cloudflare tunnel with Cloudflare Access in front, set up once per machine");
|
|
1367
|
+
splatesTunnel.command("setup").use(repository9({ needs: "own" })).description(`make a Cloudflare tunnel to \`splates server\`, the DNS record routing <host> to it, and an Access application letting in only --email, through the API with the token kept as the cred ${API_TOKEN}, then save them for this machine, so a bare \`splates server\` comes up tunneled; run again, it changes only what is not so already. Without that token it requests it on the Creds page of \`splates server\`, with how to make it, and exits \`requested\`: \`splates creds wait ${API_TOKEN}\`, then run it again`).arg("host", z13.string(), {
|
|
1199
1368
|
description: "the public host name, in a zone of the account, as in splates.example.com"
|
|
1200
|
-
}).option("email",
|
|
1369
|
+
}).option("email", z13.string(), {
|
|
1201
1370
|
default: "",
|
|
1202
1371
|
description: "comma-separated email addresses Access lets in"
|
|
1203
|
-
}).option("name",
|
|
1372
|
+
}).option("name", z13.string(), {
|
|
1204
1373
|
default: "splates",
|
|
1205
1374
|
description: "what the tunnel, the Access application and its policy are called"
|
|
1206
|
-
}).option("team",
|
|
1375
|
+
}).option("team", z13.string(), {
|
|
1207
1376
|
default: "",
|
|
1208
1377
|
description: "the Zero Trust team to make, as in <team>.cloudflareaccess.com, when the account has none yet"
|
|
1209
1378
|
}).action(async (opts, ctx) => ctx.journal.record("tunnel setup", null, async () => tunnelSetup({
|
|
@@ -1214,7 +1383,7 @@ splatesTunnel.command("setup").use(repository9({ needs: "own" })).description(`m
|
|
|
1214
1383
|
log: ctx.log,
|
|
1215
1384
|
fetch: ctx.fetch
|
|
1216
1385
|
})));
|
|
1217
|
-
splatesTunnel.command("status").description("print the tunnel `splates tunnel setup` saved for this machine, which a bare `splates server` runs, and whether its token is in the system's secret store").option("json",
|
|
1386
|
+
splatesTunnel.command("status").description("print the tunnel `splates tunnel setup` saved for this machine, which a bare `splates server` runs, and whether its token is in the system's secret store").option("json", z13.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
1218
1387
|
const saved = savedTunnelOf(ctx);
|
|
1219
1388
|
const record = await saved.read();
|
|
1220
1389
|
const token = record === null ? false : await saved.token() !== undefined;
|
|
@@ -1254,20 +1423,20 @@ import {
|
|
|
1254
1423
|
removeWorker,
|
|
1255
1424
|
workerRun
|
|
1256
1425
|
} from "@stringsync/splates-server/workers";
|
|
1257
|
-
import { cli as
|
|
1426
|
+
import { cli as cli14 } from "webappwiz/cmd";
|
|
1258
1427
|
import { CompositeLogger } from "webappwiz/log";
|
|
1259
|
-
import { z as
|
|
1428
|
+
import { z as z15 } from "zod";
|
|
1260
1429
|
|
|
1261
1430
|
// waiting.ts
|
|
1262
1431
|
import { listQueue, moveQueued } from "@stringsync/splates-server/workers";
|
|
1263
|
-
import { cli as
|
|
1264
|
-
import { z as
|
|
1432
|
+
import { cli as cli13 } from "webappwiz/cmd";
|
|
1433
|
+
import { z as z14 } from "zod";
|
|
1265
1434
|
var DEV = {
|
|
1266
1435
|
default: "",
|
|
1267
1436
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV"
|
|
1268
1437
|
};
|
|
1269
|
-
var splatesWaiting =
|
|
1270
|
-
splatesWaiting.command("list").description("list the agents waiting to start, first in line first: position, project, task, conversation, how long each has waited and why (paused, no worker up, or worker draining)").option("dev",
|
|
1438
|
+
var splatesWaiting = cli13("queue").description("the agents waiting to start on a worker of the running `splates server`: nothing caps how many agents run, so one waits only while what splates starts on its own (the queue, automations, hooks, the groomer) is paused, or while no worker it can run on is up, first come, first served; none is ever failed for waiting. An agent you start waits only while no worker is up");
|
|
1439
|
+
splatesWaiting.command("list").description("list the agents waiting to start, first in line first: position, project, task, conversation, how long each has waited and why (paused, no worker up, or worker draining)").option("dev", z14.string(), DEV).option("json", z14.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => listQueue({
|
|
1271
1440
|
dev: opts.dev,
|
|
1272
1441
|
json: opts.json,
|
|
1273
1442
|
log: ctx.log,
|
|
@@ -1275,11 +1444,11 @@ splatesWaiting.command("list").description("list the agents waiting to start, fi
|
|
|
1275
1444
|
ps: ctx.ps,
|
|
1276
1445
|
send: ctx.fetch
|
|
1277
1446
|
}));
|
|
1278
|
-
splatesWaiting.command("move").description("move an agent waiting to start to another place in line, from 1 for the next to start, then list the queue").arg("which",
|
|
1447
|
+
splatesWaiting.command("move").description("move an agent waiting to start to another place in line, from 1 for the next to start, then list the queue").arg("which", z14.string(), {
|
|
1279
1448
|
description: "the task it works, or its conversation's id"
|
|
1280
|
-
}).arg("position",
|
|
1449
|
+
}).arg("position", z14.coerce.number().int(), {
|
|
1281
1450
|
description: "where in line to put it, from 1"
|
|
1282
|
-
}).option("dev",
|
|
1451
|
+
}).option("dev", z14.string(), DEV).option("json", z14.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => moveQueued({
|
|
1283
1452
|
which: opts.which,
|
|
1284
1453
|
position: opts.position,
|
|
1285
1454
|
dev: opts.dev,
|
|
@@ -1291,20 +1460,20 @@ splatesWaiting.command("move").description("move an agent waiting to start to an
|
|
|
1291
1460
|
}));
|
|
1292
1461
|
|
|
1293
1462
|
// worker.ts
|
|
1294
|
-
var splatesWorker =
|
|
1295
|
-
splatesWorker.command("run").description("run this machine’s worker until stopped, connecting over its private Unix socket and reconnecting when the server restarts").option("server",
|
|
1463
|
+
var splatesWorker = cli14("worker").description("this machine’s worker, which runs agents, terminals and process reads for `splates server` over its Unix socket");
|
|
1464
|
+
splatesWorker.command("run").description("run this machine’s worker until stopped, connecting over its private Unix socket and reconnecting when the server restarts").option("server", z15.string(), {
|
|
1296
1465
|
default: "",
|
|
1297
1466
|
description: "the `splates server` to dial: unix:<path>; by default this machine's"
|
|
1298
|
-
}).option("coordinator",
|
|
1467
|
+
}).option("coordinator", z15.string(), {
|
|
1299
1468
|
default: "",
|
|
1300
1469
|
description: "the old name for --server, still read for workers installed before it, and passed to a build older than it"
|
|
1301
|
-
}).option("slots",
|
|
1470
|
+
}).option("slots", z15.coerce.number().int().min(0), {
|
|
1302
1471
|
default: 0,
|
|
1303
1472
|
description: "ignored: nothing caps how many agents a worker runs; still read, since a `splates server` started before it was dropped passes it"
|
|
1304
|
-
}).option("id",
|
|
1473
|
+
}).option("id", z15.literal("local"), {
|
|
1305
1474
|
default: "local",
|
|
1306
1475
|
description: "what the server knows this worker by, kept across restarts so it picks up where it was; by default local"
|
|
1307
|
-
}).option("quiet",
|
|
1476
|
+
}).option("quiet", z15.boolean(), {
|
|
1308
1477
|
default: false,
|
|
1309
1478
|
description: "say only what went wrong, not each time it joins or loses its server, as the worker `splates server` starts beside itself does; all of it still goes to worker.log in ~/.splates, the one before it kept as worker.log.1"
|
|
1310
1479
|
}).action((opts, ctx) => workerRun({
|
|
@@ -1314,10 +1483,10 @@ splatesWorker.command("run").description("run this machine’s worker until stop
|
|
|
1314
1483
|
ps: ctx.ps,
|
|
1315
1484
|
fs: ctx.fs
|
|
1316
1485
|
}));
|
|
1317
|
-
splatesWorker.command("list").description("list the workers the running `splates server` has: platform, build, protocol, health with when it was last heard, and tasks, then any it refused for speaking another protocol").option("dev",
|
|
1486
|
+
splatesWorker.command("list").description("list the workers the running `splates server` has: platform, build, protocol, health with when it was last heard, and tasks, then any it refused for speaking another protocol").option("dev", z15.string(), {
|
|
1318
1487
|
default: "",
|
|
1319
1488
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV"
|
|
1320
|
-
}).option("json",
|
|
1489
|
+
}).option("json", z15.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => listWorkers({
|
|
1321
1490
|
dev: opts.dev,
|
|
1322
1491
|
json: opts.json,
|
|
1323
1492
|
log: ctx.log,
|
|
@@ -1329,12 +1498,12 @@ var dev = {
|
|
|
1329
1498
|
default: "",
|
|
1330
1499
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV"
|
|
1331
1500
|
};
|
|
1332
|
-
splatesWorker.command("drain").description("take no new agents on a worker, letting those on it run to their end, but for one asking you something in a task, which saves its task and lets its slot go, its question kept for you to answer and carry it on elsewhere; --undo takes new ones again").arg("id",
|
|
1501
|
+
splatesWorker.command("drain").description("take no new agents on a worker, letting those on it run to their end, but for one asking you something in a task, which saves its task and lets its slot go, its question kept for you to answer and carry it on elsewhere; --undo takes new ones again").arg("id", z15.string(), {
|
|
1333
1502
|
description: "the worker, as `splates worker list` names it"
|
|
1334
|
-
}).option("undo",
|
|
1503
|
+
}).option("undo", z15.boolean(), {
|
|
1335
1504
|
default: false,
|
|
1336
1505
|
description: "take new agents again"
|
|
1337
|
-
}).option("dev",
|
|
1506
|
+
}).option("dev", z15.string(), dev).action((opts, ctx) => drainWorker({
|
|
1338
1507
|
id: opts.id,
|
|
1339
1508
|
undo: opts.undo,
|
|
1340
1509
|
dev: opts.dev,
|
|
@@ -1343,12 +1512,12 @@ splatesWorker.command("drain").description("take no new agents on a worker, lett
|
|
|
1343
1512
|
ps: ctx.ps,
|
|
1344
1513
|
send: ctx.fetch
|
|
1345
1514
|
}));
|
|
1346
|
-
splatesWorker.command("remove").description("stop this machine’s worker taking agents, letting its agents run to their end; resume it with `splates worker drain local --undo`").arg("id",
|
|
1515
|
+
splatesWorker.command("remove").description("stop this machine’s worker taking agents, letting its agents run to their end; resume it with `splates worker drain local --undo`").arg("id", z15.string(), {
|
|
1347
1516
|
description: "the worker, as `splates worker list` names it"
|
|
1348
|
-
}).option("force",
|
|
1517
|
+
}).option("force", z15.boolean(), {
|
|
1349
1518
|
default: false,
|
|
1350
1519
|
description: "end the agents on it now rather than wait for them"
|
|
1351
|
-
}).option("dev",
|
|
1520
|
+
}).option("dev", z15.string(), dev).action((opts, ctx) => removeWorker({
|
|
1352
1521
|
id: opts.id,
|
|
1353
1522
|
force: opts.force,
|
|
1354
1523
|
dev: opts.dev,
|
|
@@ -1380,8 +1549,7 @@ function projectArgs(argv) {
|
|
|
1380
1549
|
}
|
|
1381
1550
|
return { argv };
|
|
1382
1551
|
}
|
|
1383
|
-
var splates =
|
|
1384
|
-
splates.command("notes").use(repository10({ needs: "read" })).description("print each project note's frontmatter description, one per line; notesDir in splates.config.ts defaults to notes; an absent directory prints nothing").action((_opts, ctx) => notes(ctx));
|
|
1552
|
+
var splates = cli15("splates").use(exits());
|
|
1385
1553
|
splates.command("init").use(repository10({ creates: true, needs: "own" })).description("make this repository a splates project: set up its records under .git/splates when it has none, install the splates skill of this release with `bunx skills add`, and register it in ~/.splates/projects.json, so `splates server` serves it beside every other; run again, it changes only what is not so already").action((_opts, ctx) => init(ctx));
|
|
1386
1554
|
splates.command("migrate").description(`move this repository's records under .git/splates to this splates's format, as \`splates server\` does as it starts: backed up first, the steps run with ${MIGRATION_LIMIT.human()} to finish, and the backup put back if one fails or runs over, with why kept in .git/splates-migration.json; it tries again past a failure kept there, which the server and every other command leave alone. Run it only while no older \`splates server\` serves the repository, which would refuse the records once moved; \`splates update\` restarts one first`).action(async (_opts, ctx) => {
|
|
1387
1555
|
const format = await migrateRecords(ctx);
|
|
@@ -1401,42 +1569,43 @@ splates.command("update").use(recordsFirst()).use(repository10({ needs: "own" })
|
|
|
1401
1569
|
ctx.ps.exit(code);
|
|
1402
1570
|
}
|
|
1403
1571
|
});
|
|
1572
|
+
splates.command("refresh").description("bring the tree you stand in, a task's worktree included, up to this splates: install its splates skill with `bunx skills add` over the copy there and move what an older config holds to where it reads it, leaving the records and any `splates server` alone; what a project runs once a ship's bump task has installed a newer splates, as splates's `afterBump` names; commit what changed").action((_opts, ctx) => refreshHere(ctx));
|
|
1404
1573
|
splates.command("forget").use(repository10({ needs: "own" })).description("take this repository off ~/.splates/projects.json, so `splates server` no longer offers it; its records stay, and `splates init` brings it back").action((_opts, ctx) => forget(ctx));
|
|
1405
|
-
splates.command("add").use(repository10({ needs: "tasks" })).description("start a task: a new one, or with --task one from the queue; create branch task/<slug>, a worktree at ../<repo>-splates.noindex/<slug> and a state record").arg("slug",
|
|
1574
|
+
splates.command("add").use(repository10({ needs: "tasks" })).description("start a task: a new one, or with --task one from the queue; create branch task/<slug>, a worktree at ../<repo>-splates.noindex/<slug> and a state record").arg("slug", z16.string(), {
|
|
1406
1575
|
description: "the task's name, for its branch and worktree (lowercase-with-dashes)"
|
|
1407
|
-
}).option("base",
|
|
1576
|
+
}).option("base", z16.string(), {
|
|
1408
1577
|
default: "",
|
|
1409
1578
|
description: "branch this task starts from and merges onto (default: trunk); `task/<other>` stacks this task on that one and lands the work in its worktree"
|
|
1410
|
-
}).option("task",
|
|
1579
|
+
}).option("task", z16.string(), {
|
|
1411
1580
|
default: "",
|
|
1412
1581
|
description: "start this queued task by number: its words become the plan's Goal, merging removes it, and removing this task puts it back where it was"
|
|
1413
|
-
}).option("goal",
|
|
1582
|
+
}).option("goal", z16.string(), {
|
|
1414
1583
|
default: "",
|
|
1415
1584
|
description: "one or two lines on what done means, written under `## Goal` in the task's SPLATES.md; required unless --task seeds the Goal"
|
|
1416
|
-
}).option("agent",
|
|
1585
|
+
}).option("agent", z16.string(), {
|
|
1417
1586
|
default: "",
|
|
1418
1587
|
description: "how `splates server`'s queue hands the task to an agent on the roster (`splates agents`), which it starts in the worktree; refused from an agent, which starts no agents and queues work with `splates task add` instead"
|
|
1419
|
-
}).option("model",
|
|
1588
|
+
}).option("model", z16.string(), {
|
|
1420
1589
|
default: "",
|
|
1421
1590
|
description: "with --agent: the model it works on, by its id or name on the roster (default: the agent's own)"
|
|
1422
|
-
}).option("effort",
|
|
1591
|
+
}).option("effort", z16.string(), {
|
|
1423
1592
|
default: "",
|
|
1424
1593
|
description: "with --agent: the thinking level it works at, one the agent offers (default: the agent's own)"
|
|
1425
|
-
}).option("prompt",
|
|
1594
|
+
}).option("prompt", z16.string(), {
|
|
1426
1595
|
default: "",
|
|
1427
1596
|
description: "with --agent: what to tell the agent beyond claiming the task"
|
|
1428
|
-
}).option("held",
|
|
1597
|
+
}).option("held", z16.string(), {
|
|
1429
1598
|
default: "",
|
|
1430
1599
|
description: "with --agent: the conversation `splates server`'s queue held for the queued task, which takes the task rather than a new one"
|
|
1431
1600
|
}).action(async (opts, ctx) => {
|
|
1432
1601
|
await refuseGroomer4(ctx.service, "add");
|
|
1433
1602
|
if (opts.agent === "" && (opts.model !== "" || opts.effort !== "" || opts.prompt !== "" || opts.held !== "")) {
|
|
1434
|
-
|
|
1603
|
+
fail10("usage", "--model, --effort, --prompt and --held go with --agent", {
|
|
1435
1604
|
task: opts.slug
|
|
1436
1605
|
});
|
|
1437
1606
|
}
|
|
1438
1607
|
if (opts.goal.trim() === "" && opts.task === "") {
|
|
1439
|
-
|
|
1608
|
+
fail10("usage", `say what done means: \`splates add ${opts.slug} --goal "<one or two lines>"\`, or start queued tasks with --task`, { task: opts.slug });
|
|
1440
1609
|
}
|
|
1441
1610
|
const dispatch = opts.agent === "" ? undefined : {
|
|
1442
1611
|
agent: opts.agent,
|
|
@@ -1470,11 +1639,11 @@ splates.command("add").use(repository10({ needs: "tasks" })).description("start
|
|
|
1470
1639
|
await serveProject(ctx);
|
|
1471
1640
|
}
|
|
1472
1641
|
});
|
|
1473
|
-
splates.command("claim").use(repository10({ needs: "own" })).description("resume an existing task: take ownership of its worktree and print its path, status and any half-finished rebase; refuses while another agent holds the lease, but takes a stale one silently, so `splates show` first if the tree may not be abandoned").arg("task",
|
|
1642
|
+
splates.command("claim").use(repository10({ needs: "own" })).description("resume an existing task: take ownership of its worktree and print its path, status and any half-finished rebase; refuses while another agent holds the lease, but takes a stale one silently, so `splates show` first if the tree may not be abandoned").arg("task", z16.string(), { description: "task name" }).action(async (opts, ctx) => {
|
|
1474
1643
|
await ctx.journal.record("claim", opts.task, () => claim(ctx, opts.task), (claimed) => claimed);
|
|
1475
1644
|
await follow(ctx, opts.task);
|
|
1476
1645
|
});
|
|
1477
|
-
splates.command("merge").use(repository10({ needs: "own" })).description("land this worktree's branch on its base (trunk unless created with --base): rebase onto it, run tests on the rebased code, fast-forward it in whichever worktree has it checked out, then discard the worktree, branch and record (linear history, never a merge commit, no flag to skip tests); requires committed work, refusing a dirty worktree").option("message",
|
|
1646
|
+
splates.command("merge").use(repository10({ needs: "own" })).description("land this worktree's branch on its base (trunk unless created with --base): rebase onto it, run tests on the rebased code, fast-forward it in whichever worktree has it checked out, then discard the worktree, branch and record (linear history, never a merge commit, no flag to skip tests); requires committed work, refusing a dirty worktree").option("message", z16.string(), {
|
|
1478
1647
|
default: "",
|
|
1479
1648
|
description: "squash the task's commits into one with this message before landing, saying what the base gains in at least 5 characters"
|
|
1480
1649
|
}).action(async (opts, ctx) => {
|
|
@@ -1489,39 +1658,75 @@ splates.command("merge").use(repository10({ needs: "own" })).description("land t
|
|
|
1489
1658
|
await follow(ctx, null, { from: task });
|
|
1490
1659
|
}
|
|
1491
1660
|
});
|
|
1492
|
-
splates.command("remove").use(repository10({ needs: "tasks" })).description("discard a task: worktree, branch and board tasks; cheap and encouraged, since redoing a task against current trunk often beats a hard rebase").arg("task",
|
|
1661
|
+
splates.command("remove").use(repository10({ needs: "tasks" })).description("discard a task: worktree, branch and board tasks; cheap and encouraged, since redoing a task against current trunk often beats a hard rebase").arg("task", z16.string(), { description: "task name" }).option("force", z16.boolean(), {
|
|
1493
1662
|
default: false,
|
|
1494
1663
|
description: "discard even when another agent holds the lease"
|
|
1495
|
-
}).option("reason",
|
|
1496
|
-
description: "why, for the Stats page: nothing (nothing to change), moot (no longer needed), superseded (done some other way), redo (better redone against current trunk), blocked (set aside until what it waits on lands, so back on the list)"
|
|
1664
|
+
}).option("reason", z16.optional(z16.enum(REMOVE_REASONS)), {
|
|
1665
|
+
description: "why, for the Stats page: nothing (nothing to change), moot (no longer needed), superseded (done some other way), redo (better redone against current trunk), blocked (set aside until what it waits on lands, so back on the list, which takes --why and at least one blocker)"
|
|
1666
|
+
}).option("why", z16.string(), {
|
|
1667
|
+
default: "",
|
|
1668
|
+
description: "with --reason blocked: what you found that stops the work, in a sentence or two, for the person and whoever takes it next"
|
|
1669
|
+
}).repeated("blocked-by", z16.string(), {
|
|
1670
|
+
description: "with --reason blocked: the tasks it waits on to merge, comma separated or the flag repeated: a number for one here, <project>#<number> for one of another project"
|
|
1671
|
+
}).repeated("ask", z16.string(), {
|
|
1672
|
+
description: "with --reason blocked: a question only the person can answer, in the skill's question format; repeat the flag for each"
|
|
1673
|
+
}).repeated("waiting-on", z16.string(), {
|
|
1674
|
+
description: "with --reason blocked: something outside splates it waits on, such as a key or an upstream release; repeat the flag for each"
|
|
1675
|
+
}).option("detail", z16.string(), {
|
|
1676
|
+
default: "",
|
|
1677
|
+
description: "with --reason blocked: the task's detail rewritten for whoever takes it next: why it stopped, what you found, the steps done and left, and where any pushed work is"
|
|
1497
1678
|
}).action(async (opts, ctx) => {
|
|
1498
|
-
|
|
1499
|
-
|
|
1679
|
+
let __stack = [];
|
|
1680
|
+
try {
|
|
1681
|
+
const boards = __using(__stack, new OpenBoards2(ctx, ctx.projects), 0);
|
|
1682
|
+
const { blockedBy, blockedElsewhere } = await blockerFlags({
|
|
1683
|
+
boards,
|
|
1684
|
+
here: ctx.git.root,
|
|
1685
|
+
blockedBy: opts["blocked-by"]
|
|
1686
|
+
});
|
|
1687
|
+
await ctx.journal.record("remove", opts.task, () => remove(ctx, opts.task, {
|
|
1688
|
+
force: opts.force,
|
|
1689
|
+
reason: opts.reason,
|
|
1690
|
+
deferral: {
|
|
1691
|
+
why: opts.why,
|
|
1692
|
+
blockedBy,
|
|
1693
|
+
blockedElsewhere,
|
|
1694
|
+
questions: opts.ask,
|
|
1695
|
+
outside: opts["waiting-on"],
|
|
1696
|
+
detail: opts.detail === "" ? undefined : opts.detail
|
|
1697
|
+
}
|
|
1698
|
+
}), ({ released }) => ({ why: opts.reason ?? null, released }));
|
|
1699
|
+
await follow(ctx, null, { from: opts.task });
|
|
1700
|
+
} catch (_catch) {
|
|
1701
|
+
var _err = _catch, _hasErr = 1;
|
|
1702
|
+
} finally {
|
|
1703
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1704
|
+
}
|
|
1500
1705
|
});
|
|
1501
|
-
splates.command("list").use(repository10({ needs: "read" })).description("list every started task: its number, name, status, lease (held: an agent is on it now; stale: gone quiet, normal for a task mid-edit; none), commits ahead of trunk, age; `splates task list` lists the queue").option("json",
|
|
1706
|
+
splates.command("list").use(repository10({ needs: "read" })).description("list every started task: its number, name, status, lease (held: an agent is on it now; stale: gone quiet, normal for a task mid-edit; none), commits ahead of trunk, age; `splates task list` lists the queue").option("json", z16.boolean(), { default: false, description: "emit JSON" }).option("files", z16.boolean(), {
|
|
1502
1707
|
default: false,
|
|
1503
1708
|
description: "add each task's Goal and changed files (committed or not), to check for overlap before starting"
|
|
1504
1709
|
}).action((opts, ctx) => list(ctx, { json: opts.json, files: opts.files }));
|
|
1505
|
-
splates.command("show").use(repository10({ needs: "read" })).description("read one task without touching it: everything `list` shows for it, plus the SPLATES.md its agent left at the worktree root; takes no lease, so it cannot knock that agent off its own tree; for a task no longer live, the newest finished one of that name: how it ended, its base, the commit it landed as, its conversation and the SPLATES.md it ended on").arg("task",
|
|
1506
|
-
splates.command("wait").use(repository10({ needs: "read" })).description("block until a task stops moving: waiting on a person to answer its questions, or gone (merged or removed), or broken; takes no lease, and gives up with `timeout` rather than waiting forever").arg("task",
|
|
1710
|
+
splates.command("show").use(repository10({ needs: "read" })).description("read one task without touching it: everything `list` shows for it, plus the SPLATES.md its agent left at the worktree root; takes no lease, so it cannot knock that agent off its own tree; for a task no longer live, the newest finished one of that name: how it ended, its base, the commit it landed as, its conversation and the SPLATES.md it ended on").arg("task", z16.string(), { description: "task name" }).option("json", z16.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => show(ctx, opts.task, { json: opts.json }));
|
|
1711
|
+
splates.command("wait").use(repository10({ needs: "read" })).description("block until a task stops moving: waiting on a person to answer its questions, or gone (merged or removed), or broken; takes no lease, and gives up with `timeout` rather than waiting forever").arg("task", z16.string(), { description: "task name" }).option("timeout-secs", z16.coerce.number(), {
|
|
1507
1712
|
default: DEFAULT_TIMEOUT3.secs,
|
|
1508
1713
|
description: "how long to wait before giving up"
|
|
1509
1714
|
}).action((opts, ctx) => {
|
|
1510
1715
|
const timeout = Duration3.secs(opts["timeout-secs"]);
|
|
1511
1716
|
return ctx.journal.waiting("wait", opts.task, { until: untilAfter3(timeout) }, () => wait(ctx, opts.task, { timeout }));
|
|
1512
1717
|
});
|
|
1513
|
-
splates.command("log").use(repository10({ needs: "read" })).description("show what has been done here recently: one line per add, claim, merge, remove, retry, queued task and more, with how it ended; outlives the tasks themselves").option("count",
|
|
1718
|
+
splates.command("log").use(repository10({ needs: "read" })).description("show what has been done here recently: one line per add, claim, merge, remove, retry, queued task and more, with how it ended; outlives the tasks themselves").option("count", z16.coerce.number(), {
|
|
1514
1719
|
default: DEFAULT_COUNT,
|
|
1515
1720
|
description: "how many entries to show"
|
|
1516
|
-
}).option("json",
|
|
1721
|
+
}).option("json", z16.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => showLog(ctx, { count: opts.count, json: opts.json }));
|
|
1517
1722
|
splates.mount("server", splatesServer);
|
|
1518
|
-
splates.command("ports").description("list the ports tasks serve, found by the running `splates server` as an editor finds ports to forward: each port a process in a task's worktree, or under its agent, listens on, with its link through the ports host `splates server --ports-host` saved, behind Cloudflare Access, and its link on this machine; give the user both").arg("task",
|
|
1723
|
+
splates.command("ports").description("list the ports tasks serve, found by the running `splates server` as an editor finds ports to forward: each port a process in a task's worktree, or under its agent, listens on, with its link through the ports host `splates server --ports-host` saved, behind Cloudflare Access, and its link on this machine; give the user both").arg("task", z16.string(), {
|
|
1519
1724
|
default: "",
|
|
1520
1725
|
description: "only this task's ports; every task's by default"
|
|
1521
|
-
}).option("dev",
|
|
1726
|
+
}).option("dev", z16.string(), {
|
|
1522
1727
|
default: "",
|
|
1523
1728
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV, else this machine's on its default port"
|
|
1524
|
-
}).option("json",
|
|
1729
|
+
}).option("json", z16.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => portsLs({
|
|
1525
1730
|
task: opts.task,
|
|
1526
1731
|
dev: opts.dev,
|
|
1527
1732
|
json: opts.json,
|
|
@@ -1529,50 +1734,80 @@ splates.command("ports").description("list the ports tasks serve, found by the r
|
|
|
1529
1734
|
ps: ctx.ps,
|
|
1530
1735
|
send: ctx.fetch
|
|
1531
1736
|
}));
|
|
1532
|
-
splates.command("agents").description("print the roster of agents splates can start, kept for every project in ~/.splates/agents.json: each agent's launch command and models, with what each model is good at and what it costs, and the thinking levels `--effort` takes, as the agent offered them when last started, for choosing whom to delegate to; writes the defaults (Claude, Codex) there first when there is none, and `splates server` starts conversations with those it lists").option("json",
|
|
1737
|
+
splates.command("agents").description("print the roster of agents splates can start, kept for every project in ~/.splates/agents.json: each agent's launch command and models, with what each model is good at and what it costs, and the thinking levels `--effort` takes, as the agent offered them when last started, for choosing whom to delegate to; writes the defaults (Claude, Codex) there first when there is none, and `splates server` starts conversations with those it lists").option("json", z16.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => agents({
|
|
1533
1738
|
roster: rosterOf2(ctx),
|
|
1534
1739
|
offered: await offeredOf(ctx).all(),
|
|
1535
1740
|
log: ctx.log
|
|
1536
1741
|
}, { json: opts.json }));
|
|
1537
|
-
splates.command("setup").description("set splates up on this machine, one step at a time: with no step, each area's steps, checked from real state, and the one to do next; with a step, why it matters, the one thing the user does and what the agent does. Skips are kept in ~/.splates/setup.json, so a skipped step stops asking").arg("step",
|
|
1742
|
+
splates.command("setup").description("set splates up on this machine, one step at a time: with no step, each area's steps, checked from real state, and the one to do next; with a step, why it matters, the one thing the user does and what the agent does. Skips are kept in ~/.splates/setup.json, so a skipped step stops asking").arg("step", z16.string(), {
|
|
1538
1743
|
default: "",
|
|
1539
1744
|
description: "a step's id; omit for every area's progress"
|
|
1540
|
-
}).option("json",
|
|
1745
|
+
}).option("json", z16.boolean(), { default: false, description: "emit JSON" }).option("skip", z16.boolean(), {
|
|
1541
1746
|
default: false,
|
|
1542
1747
|
description: "with a step: stop asking for it"
|
|
1543
|
-
}).option("unskip",
|
|
1748
|
+
}).option("unskip", z16.boolean(), {
|
|
1544
1749
|
default: false,
|
|
1545
1750
|
description: "with a step: ask for it again"
|
|
1546
|
-
}).option("undo",
|
|
1751
|
+
}).option("undo", z16.boolean(), {
|
|
1547
1752
|
default: false,
|
|
1548
1753
|
description: "with an area's id in place of a step: print what undoing it would remove, the creds it forgets and keeps, and what only the user can remove; the Setup page's Undo does it, after the user confirms"
|
|
1549
1754
|
}).action((opts, ctx) => setup(ctx, opts));
|
|
1550
|
-
splates.command("path").use(repository10({ needs: "read" })).description("print a task's worktree path, or the main tree with no task; names another agent's tree without taking its lease").arg("task",
|
|
1755
|
+
splates.command("path").use(repository10({ needs: "read" })).description("print a task's worktree path, or the main tree with no task; names another agent's tree without taking its lease").arg("task", z16.string(), {
|
|
1551
1756
|
default: "",
|
|
1552
1757
|
description: "task name; omit for the main tree"
|
|
1553
1758
|
}).action((opts, ctx) => path(ctx, opts.task || undefined));
|
|
1554
1759
|
splates.command("checkpoint").use(repository10({ needs: "own" })).description("save this task to the central remote (`remote` in splates.config.ts) before a stop: commit what is uncommitted as a `WIP: checkpoint` commit, to squash before merging, and push the branch as `splates/<task>`, so a worker that dies loses nothing; with no remote configured, leaves the tree as it is").action(async (_opts, ctx) => {
|
|
1555
1760
|
await checkpoint(ctx, ctx.ps.cwd());
|
|
1556
1761
|
});
|
|
1557
|
-
splates.command("attach").use(repository10({ needs: "own" })).description("store screenshots and recordings for this task's questions to show: prints the path each was stored at, under .git/splates, which the `splates server` page shows inline; from a conversation (its $SPLATES_CONVERSATION, or --conversation) they stay with it past the task, otherwise they go when the task merges or is removed; those the task's final SPLATES.md shows are copied to keep with its finished record. A task holds at most attachmentCapacity bytes; with no files, lists what it holds. A file the user will come back for on its own is an artifact instead (`splates artifact add`)").arg("files",
|
|
1762
|
+
splates.command("attach").use(repository10({ needs: "own" })).description("store screenshots and recordings for this task's questions to show: prints the path each was stored at, under .git/splates, which the `splates server` page shows inline; from a conversation (its $SPLATES_CONVERSATION, or --conversation) they stay with it past the task, otherwise they go when the task merges or is removed; those the task's final SPLATES.md shows are copied to keep with its finished record. A task holds at most attachmentCapacity bytes; with no files, lists what it holds. A file the user will come back for on its own is an artifact instead (`splates artifact add`)").arg("files", z16.string(), {
|
|
1558
1763
|
default: "",
|
|
1559
1764
|
description: "files to store, comma separated"
|
|
1560
|
-
}).option("task",
|
|
1765
|
+
}).option("task", z16.string(), {
|
|
1561
1766
|
default: "",
|
|
1562
1767
|
description: "task name, when run outside its worktree"
|
|
1563
|
-
}).option("remove",
|
|
1768
|
+
}).option("remove", z16.string(), {
|
|
1564
1769
|
default: "",
|
|
1565
1770
|
description: "files it holds to drop first, comma separated, by path or stored name"
|
|
1566
|
-
}).option("conversation",
|
|
1771
|
+
}).option("conversation", z16.string(), {
|
|
1567
1772
|
default: "",
|
|
1568
1773
|
description: "conversation to keep the files with, when $SPLATES_CONVERSATION is not set"
|
|
1569
|
-
}).action(async (opts, ctx) => ctx.journal.record("attach", opts.task || await here(ctx), () => attach(ctx,
|
|
1774
|
+
}).action(async (opts, ctx) => ctx.journal.record("attach", opts.task || await here(ctx), () => attach(ctx, commaList6(opts.files), ctx.ps.cwd(), {
|
|
1570
1775
|
task: opts.task || undefined,
|
|
1571
|
-
remove:
|
|
1776
|
+
remove: commaList6(opts.remove),
|
|
1572
1777
|
conversation: opts.conversation || undefined
|
|
1573
|
-
}), (stored) => ({ stored, removed:
|
|
1574
|
-
splates.command("retry").use(repository10({ needs: "own" })).description("give a task that spent its merge attempts another mergeRetryCount of them; the way out of `budget_exhausted` that is not remove and redo, for a person to run once they have seen the tree").arg("task",
|
|
1575
|
-
splates.
|
|
1778
|
+
}), (stored) => ({ stored, removed: commaList6(opts.remove) })));
|
|
1779
|
+
splates.command("retry").use(repository10({ needs: "own" })).description("give a task that spent its merge attempts another mergeRetryCount of them; the way out of `budget_exhausted` that is not remove and redo, for a person to run once they have seen the tree").arg("task", z16.string(), { description: "task name" }).action((opts, ctx) => recordRetry(ctx, opts.task));
|
|
1780
|
+
splates.command("ship").use(repository10({ needs: "read" })).description("ship a project and its cascade: `splates server` runs its `ship` command from splates.config.ts on its machine, under your own npm sign-in, signing in with `npm login --auth-type=web` first when npm knows no one, and shows npm's sign-in and 2FA links on the page for any device; this prints that ship's output until it ends. The server then carries the release through every registered project that depends on it, in dependency order: a bump task queued in each once all it takes from are shipped, and, once that merges, each public one with a `ship` command and dependents of its own ships in turn. A bump that asks you, or is removed, stops the cascade at that project until it merges or you retry or drop it, as does a failed ship. While a cascade is under way, this prints where it stands instead of starting another, or changes one of its pending publishes with `--bump`. `--plan` prints the cascade and runs nothing. The person's alone to start, refused an agent, which asks with a run-command card").arg("project", z16.string(), {
|
|
1781
|
+
default: "",
|
|
1782
|
+
description: "the registered project's slug; this one by default"
|
|
1783
|
+
}).option("plan", z16.boolean(), {
|
|
1784
|
+
default: false,
|
|
1785
|
+
description: "print the plan without running anything"
|
|
1786
|
+
}).option("json", z16.boolean(), { default: false, description: "emit JSON" }).option("dev", z16.string(), {
|
|
1787
|
+
default: "",
|
|
1788
|
+
description: "the `splates server` to run the ship on, a URL such as http://localhost:4310; by default the one from SPLATES_DEV, else this machine's on its default port"
|
|
1789
|
+
}).option("bump", z16.optional(z16.enum(["patch", "minor", "major"])), {
|
|
1790
|
+
description: "how far to move the starting project's version, patch by default; while its cascade runs, change this pending publish instead"
|
|
1791
|
+
}).option("drop", z16.string(), {
|
|
1792
|
+
default: "",
|
|
1793
|
+
description: "take this registered project out of the cascade under way, so those after it go on without its new version"
|
|
1794
|
+
}).option("retry", z16.string(), {
|
|
1795
|
+
default: "",
|
|
1796
|
+
description: "run this registered project's part of the cascade under way again: its ship again when that failed, or a new bump task when its last was removed"
|
|
1797
|
+
}).option("abandon", z16.boolean(), {
|
|
1798
|
+
default: false,
|
|
1799
|
+
description: "end the cascade under way: what shipped stays published, a running ship is stopped, its open bump tasks are removed, and history files it as abandoned"
|
|
1800
|
+
}).action((opts, ctx) => ship(ctx, {
|
|
1801
|
+
project: opts.project || undefined,
|
|
1802
|
+
plan: opts.plan,
|
|
1803
|
+
json: opts.json,
|
|
1804
|
+
dev: opts.dev,
|
|
1805
|
+
bump: opts.bump,
|
|
1806
|
+
drop: opts.drop || undefined,
|
|
1807
|
+
retry: opts.retry || undefined,
|
|
1808
|
+
abandon: opts.abandon
|
|
1809
|
+
}));
|
|
1810
|
+
splates.mount("artifact", splatesArtifact).mount("task", splatesTask).mount("lane", splatesLane).mount("projects", splatesProjects).mount("creds", splatesCreds).mount("tunnel", splatesTunnel).mount("slots", splatesSlots).mount("worker", splatesWorker).mount("queue", splatesQueue).mount("hook", splatesHook).mount("replica", splatesReplica).mount("evals", splatesEvals);
|
|
1576
1811
|
|
|
1577
1812
|
// index.ts
|
|
1578
1813
|
if (basename2(process.argv[1] ?? "") === LEGACY) {
|