@stringsync/splates 0.0.46 → 0.0.47
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/index.js +71 -18
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -12,11 +12,11 @@ import { assets } from "@stringsync/splates-server/server";
|
|
|
12
12
|
import { NodeFs } from "webappwiz/system";
|
|
13
13
|
|
|
14
14
|
// splates.ts
|
|
15
|
-
import { DEFAULT_PORT } from "@stringsync/splates-server";
|
|
15
|
+
import { DEFAULT_PORT as DEFAULT_PORT2 } from "@stringsync/splates-server";
|
|
16
16
|
import { agents } from "@stringsync/splates-server/agents";
|
|
17
17
|
import { attach } from "@stringsync/splates-server/artifacts";
|
|
18
18
|
import { commaList as commaList4 } from "@stringsync/splates-server/comma-list";
|
|
19
|
-
import { exits, fail as
|
|
19
|
+
import { exits, fail as fail6 } from "@stringsync/splates-server/exit";
|
|
20
20
|
import { DEFAULT_COUNT, log as showLog } from "@stringsync/splates-server/log";
|
|
21
21
|
import { merge } from "@stringsync/splates-server/merge";
|
|
22
22
|
import { portsLs } from "@stringsync/splates-server/ports";
|
|
@@ -270,13 +270,20 @@ splatesBudget.command("end").description("end a raise now, by the number `show`
|
|
|
270
270
|
|
|
271
271
|
// coordinator.ts
|
|
272
272
|
import {
|
|
273
|
-
CoordinatorBox
|
|
273
|
+
CoordinatorBox,
|
|
274
|
+
keepCarriedCreds,
|
|
275
|
+
machineMove
|
|
274
276
|
} from "@stringsync/splates-server/coordinator";
|
|
275
|
-
import {
|
|
277
|
+
import { SYSTEM_STORES as SYSTEM_STORES2 } from "@stringsync/splates-server/creds";
|
|
278
|
+
import { fail as fail3 } from "@stringsync/splates-server/exit";
|
|
279
|
+
import {
|
|
280
|
+
DEFAULT_PORT,
|
|
281
|
+
RUNNING
|
|
282
|
+
} from "@stringsync/splates-server/server";
|
|
276
283
|
import { cli as cli3 } from "webappwiz/cmd";
|
|
277
284
|
import { z as z4 } from "zod";
|
|
278
285
|
var splatesCoordinator = cli3("coordinator").description("the always-on box in your own Hetzner Cloud account that `splates server` moves onto, made through Pulumi with the token and state the Cloud workers setup readied: a CPX21 in Ashburn with Hetzner's daily backups, reached by SSH alone");
|
|
279
|
-
splatesCoordinator.command("up").description("make the coordinator's box, or bring it to its program, with splates of this release installed for the user splates and an SSH key splates makes under ~/.splates/coordinator; run again, it changes only what is not so already. The box serves nothing until
|
|
286
|
+
splatesCoordinator.command("up").description("make the coordinator's box, or bring it to its program, with splates of this release installed for the user splates and an SSH key splates makes under ~/.splates/coordinator; run again, it changes only what is not so already. The box serves nothing until `splates coordinator move`").option("json", z4.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
280
287
|
ctx.log.info("making the coordinator's box; its first boot takes minutes");
|
|
281
288
|
print(ctx, await box(ctx).up(), opts.json);
|
|
282
289
|
});
|
|
@@ -292,6 +299,51 @@ splatesCoordinator.command("remove").description("destroy the coordinator's box
|
|
|
292
299
|
await box(ctx).remove();
|
|
293
300
|
ctx.log.info("removed the coordinator's box");
|
|
294
301
|
});
|
|
302
|
+
splatesCoordinator.command("move").description("move the coordinator onto its box, over SSH. First it checks what the move needs, brings the box's splates to this release, and gives each project a clone there with a deploy key of its own; until GitHub lets each key push trunk, it prints each key with where to add it, with write access, and exits `blocked`: add them and run it again. Then it asks every agent of this machine's `splates server` to checkpoint and stops it, carries the coordinator's records and creds to the box, starts `splates server` there for good under systemd, with the saved tunnel, and joins this machine to it as a worker, kept up by a LaunchAgent, where each task carries on in its worktree here. This machine's records go aside, kept, and its `splates` reaches the box from then on").option("dry-run", z4.boolean(), {
|
|
303
|
+
default: false,
|
|
304
|
+
description: "stop once the box is ready, before this machine's server stops or anything here changes"
|
|
305
|
+
}).option("dev", z4.string(), {
|
|
306
|
+
default: `http://localhost:${DEFAULT_PORT}`,
|
|
307
|
+
description: "this machine's `splates server`, which the move stops"
|
|
308
|
+
}).action(async (opts, ctx) => {
|
|
309
|
+
const moved = await machineMove({
|
|
310
|
+
ps: ctx.ps,
|
|
311
|
+
fs: ctx.fs,
|
|
312
|
+
log: ctx.log,
|
|
313
|
+
version: RUNNING,
|
|
314
|
+
dev: opts.dev,
|
|
315
|
+
stores: ctx.secrets ?? SYSTEM_STORES2,
|
|
316
|
+
...ctx.fetch === undefined ? {} : { fetch: ctx.fetch }
|
|
317
|
+
}).run({ dryRun: opts["dry-run"] });
|
|
318
|
+
if (moved.status === "keys") {
|
|
319
|
+
fail3("blocked", [
|
|
320
|
+
'GitHub does not let these deploy keys push yet. Add each to its repository with "Allow write access" ticked, then move again:',
|
|
321
|
+
...moved.keys.flatMap(({ slug, key, add }) => [
|
|
322
|
+
"",
|
|
323
|
+
` ${slug}${add === null ? "" : `: ${add}`}`,
|
|
324
|
+
` ${key}`
|
|
325
|
+
])
|
|
326
|
+
].join(`
|
|
327
|
+
`), { keys: moved.keys });
|
|
328
|
+
}
|
|
329
|
+
if (moved.status === "ready") {
|
|
330
|
+
ctx.log.info(`the box is ready to coordinate ${moved.projects.join(", ")} through ${moved.host}: move again without --dry-run`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
ctx.log.info([
|
|
334
|
+
`the coordinator runs on the box: https://${moved.host}`,
|
|
335
|
+
`this machine is its worker ${ctx.ps.hostname}, kept up by ${moved.worker}`,
|
|
336
|
+
`its own records are kept aside in ${moved.aside} and each project's .git/splates.moved`
|
|
337
|
+
].join(`
|
|
338
|
+
`));
|
|
339
|
+
});
|
|
340
|
+
splatesCoordinator.command("keep-creds").description("keep the creds `splates coordinator move` sends on stdin in this machine's stores, as the box does when the coordinator moves onto it").action(async (_opts, ctx) => {
|
|
341
|
+
const kept = await keepCarriedCreds({
|
|
342
|
+
stores: ctx.secrets ?? SYSTEM_STORES2,
|
|
343
|
+
sent: await Bun.stdin.text()
|
|
344
|
+
});
|
|
345
|
+
ctx.log.info(`kept ${kept} creds`);
|
|
346
|
+
});
|
|
295
347
|
function box(ctx) {
|
|
296
348
|
return new CoordinatorBox({ version: RUNNING, ps: ctx.ps, fs: ctx.fs });
|
|
297
349
|
}
|
|
@@ -310,7 +362,7 @@ function print(ctx, standing, json) {
|
|
|
310
362
|
|
|
311
363
|
// creds.ts
|
|
312
364
|
import { DEFAULT_TIMEOUT } from "@stringsync/splates-server/creds";
|
|
313
|
-
import { fail as
|
|
365
|
+
import { fail as fail4 } from "@stringsync/splates-server/exit";
|
|
314
366
|
import { repository as repository2 } from "@stringsync/splates-server/records";
|
|
315
367
|
import { untilAfter } from "@stringsync/splates-server/tasks";
|
|
316
368
|
import { cli as cli4 } from "webappwiz/cmd";
|
|
@@ -356,7 +408,7 @@ splatesCreds.command("wait").use(repository2({ needs: "own" })).description("blo
|
|
|
356
408
|
await ctx.journal.waiting("creds wait", null, { name: opts.name, until: untilAfter(timeout) }, async () => {
|
|
357
409
|
const ended = await ctx.creds.wait(opts.name, { timeout });
|
|
358
410
|
if (ended === "dismissed") {
|
|
359
|
-
|
|
411
|
+
fail4("dismissed", `${opts.name} was dismissed: carry on without it, or ask the person why`, { name: opts.name });
|
|
360
412
|
}
|
|
361
413
|
});
|
|
362
414
|
ctx.log.info(`${color.green("saved")} ${opts.name}`);
|
|
@@ -468,7 +520,7 @@ splatesQueue.command("move").description("move an agent waiting for a slot to an
|
|
|
468
520
|
|
|
469
521
|
// setup.ts
|
|
470
522
|
import { dirname } from "node:path";
|
|
471
|
-
import { SYSTEM_STORES as
|
|
523
|
+
import { SYSTEM_STORES as SYSTEM_STORES3 } from "@stringsync/splates-server/creds";
|
|
472
524
|
import { splatesHome as splatesHome2 } from "@stringsync/splates-server/models";
|
|
473
525
|
import { Projects } from "@stringsync/splates-server/projects";
|
|
474
526
|
import {
|
|
@@ -510,7 +562,7 @@ async function setup(deps, opts) {
|
|
|
510
562
|
printStep(log, step);
|
|
511
563
|
}
|
|
512
564
|
async function setupOf(deps) {
|
|
513
|
-
const { fs, ps, secrets =
|
|
565
|
+
const { fs, ps, secrets = SYSTEM_STORES3, fetch } = deps;
|
|
514
566
|
const root = deps.project === undefined ? await repoRoot(ps) : (await Projects.ofMachine(deps).named(deps.project)).root;
|
|
515
567
|
return new Setup({
|
|
516
568
|
fs,
|
|
@@ -633,7 +685,7 @@ import {
|
|
|
633
685
|
todoUpdate
|
|
634
686
|
} from "@stringsync/splates-server/board";
|
|
635
687
|
import { commaList as commaList3 } from "@stringsync/splates-server/comma-list";
|
|
636
|
-
import { fail as
|
|
688
|
+
import { fail as fail5 } from "@stringsync/splates-server/exit";
|
|
637
689
|
import { repository as repository5 } from "@stringsync/splates-server/records";
|
|
638
690
|
import {
|
|
639
691
|
DEFAULT_TIMEOUT as DEFAULT_TIMEOUT2,
|
|
@@ -669,7 +721,7 @@ splatesTodo.command("add").use(repository5({ needs: "todos" })).description("not
|
|
|
669
721
|
description: "with --agent: the thinking level, one the agent offers (default: the agent's own)"
|
|
670
722
|
}).action(async (opts, ctx) => {
|
|
671
723
|
if (ctx.elsewhere !== null && opts.lane !== "") {
|
|
672
|
-
|
|
724
|
+
fail5("not_granted", "a todo from another project goes in its untriaged lane, for whoever orchestrates it there to place: leave --lane off", { subject: opts.subject });
|
|
673
725
|
}
|
|
674
726
|
const from = await here(ctx);
|
|
675
727
|
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
@@ -1042,10 +1094,10 @@ splates.command("add").use(repository7({ needs: "tasks" })).description("start a
|
|
|
1042
1094
|
description: "with --agent: hand it out though you have as many tasks out to agents as agentsPerAgent (splates.config.ts) allows; only when the person explicitly asked for more at once"
|
|
1043
1095
|
}).action(async (opts, ctx) => {
|
|
1044
1096
|
if (opts.agent === "" && (opts.model !== "" || opts.effort !== "" || opts.prompt !== "" || opts["over-capacity"])) {
|
|
1045
|
-
|
|
1097
|
+
fail6("usage", "--model, --effort, --prompt and --over-capacity go with --agent", { task: opts.task });
|
|
1046
1098
|
}
|
|
1047
1099
|
if (opts.goal.trim() === "" && opts.todo === "") {
|
|
1048
|
-
|
|
1100
|
+
fail6("usage", `say what done means: \`splates add ${opts.task} --goal "<one or two lines>"\`, or take todos with --todo`, { task: opts.task });
|
|
1049
1101
|
}
|
|
1050
1102
|
const dispatch = opts.agent === "" ? undefined : {
|
|
1051
1103
|
agent: opts.agent,
|
|
@@ -1124,7 +1176,7 @@ splates.command("log").use(repository7({ needs: "read" })).description("show wha
|
|
|
1124
1176
|
description: "how many entries to show"
|
|
1125
1177
|
}).option("json", z13.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => showLog(ctx, { count: opts.count, json: opts.json }));
|
|
1126
1178
|
splates.command("server").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 todos and tasks, where todos can be added, updated, reordered or removed; it brings each project's records forward as it opens it").option("port", z13.coerce.number(), {
|
|
1127
|
-
default:
|
|
1179
|
+
default: DEFAULT_PORT2,
|
|
1128
1180
|
description: "port to listen on, or the next open one above it"
|
|
1129
1181
|
}).option("allow-hosts", z13.string(), {
|
|
1130
1182
|
default: "",
|
|
@@ -1151,17 +1203,18 @@ splates.command("server").description("serve every splates project on this machi
|
|
|
1151
1203
|
default: false,
|
|
1152
1204
|
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"
|
|
1153
1205
|
}).action((opts, ctx) => serveDev(ctx, opts));
|
|
1154
|
-
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", z13.string(), {
|
|
1206
|
+
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, which on a worker whose coordinator runs elsewhere only that worker's own ports have; give the user both").arg("task", z13.string(), {
|
|
1155
1207
|
default: "",
|
|
1156
1208
|
description: "only this task's ports; every task's by default"
|
|
1157
1209
|
}).option("dev", z13.string(), {
|
|
1158
1210
|
default: "",
|
|
1159
|
-
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"
|
|
1211
|
+
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 the coordinator this machine's worker relays to, else this machine's on its default port"
|
|
1160
1212
|
}).option("json", z13.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => portsLs({
|
|
1161
1213
|
task: opts.task,
|
|
1162
1214
|
dev: opts.dev,
|
|
1163
1215
|
json: opts.json,
|
|
1164
1216
|
log: ctx.log,
|
|
1217
|
+
fs: ctx.fs,
|
|
1165
1218
|
ps: ctx.ps,
|
|
1166
1219
|
send: ctx.fetch
|
|
1167
1220
|
}));
|
|
@@ -1181,10 +1234,10 @@ splates.command("setup").description("set splates up on this machine, one step a
|
|
|
1181
1234
|
description: "with a step: ask for it again"
|
|
1182
1235
|
}).action(async (opts, ctx) => {
|
|
1183
1236
|
if (opts.step === "" && (opts.skip || opts.unskip)) {
|
|
1184
|
-
|
|
1237
|
+
fail6("usage", "--skip and --unskip name a step", {});
|
|
1185
1238
|
}
|
|
1186
1239
|
if (opts.skip && opts.unskip) {
|
|
1187
|
-
|
|
1240
|
+
fail6("usage", "--skip or --unskip, not both", { step: opts.step });
|
|
1188
1241
|
}
|
|
1189
1242
|
await setup(ctx, opts);
|
|
1190
1243
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stringsync/splates",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
4
4
|
"description": "Runs several AI coding agents on one repository at once, each in its own git worktree",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "stringsync LLC",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@stringsync/splates-server": "^0.0.
|
|
20
|
+
"@stringsync/splates-server": "^0.0.47",
|
|
21
21
|
"webappwiz": "0.0.46",
|
|
22
22
|
"zod": "^4.4.3"
|
|
23
23
|
},
|