@stringsync/splates 0.0.51 → 0.0.52
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 +68 -47
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
MIGRATING,
|
|
26
26
|
MIGRATION_LIMIT,
|
|
27
27
|
migrateRecords,
|
|
28
|
-
repository as
|
|
28
|
+
repository as repository10
|
|
29
29
|
} from "@stringsync/splates-server/records";
|
|
30
30
|
import {
|
|
31
31
|
newest,
|
|
@@ -358,8 +358,8 @@ import { fail as fail4 } from "@stringsync/splates-server/exit";
|
|
|
358
358
|
import {
|
|
359
359
|
HookStore,
|
|
360
360
|
hookAdd,
|
|
361
|
-
hookAudit,
|
|
362
361
|
hookEdit,
|
|
362
|
+
hookGardener,
|
|
363
363
|
hookGroomer,
|
|
364
364
|
hookList,
|
|
365
365
|
hookPause,
|
|
@@ -484,7 +484,7 @@ splatesHook.command("groomer").use(repository3({ needs: "tasks" })).description(
|
|
|
484
484
|
}
|
|
485
485
|
await hookGroomer({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null);
|
|
486
486
|
});
|
|
487
|
-
splatesHook.command("
|
|
487
|
+
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", z5.string(), {
|
|
488
488
|
default: "",
|
|
489
489
|
description: "the agent it runs as, as `splates agents` names it"
|
|
490
490
|
}).option("model", z5.string(), {
|
|
@@ -507,7 +507,7 @@ splatesHook.command("audit").use(repository3({ needs: "tasks" })).description("t
|
|
|
507
507
|
if (opts.off === (pick != null)) {
|
|
508
508
|
fail4("usage", "say how it runs with --agent, or turn it off with --off", {});
|
|
509
509
|
}
|
|
510
|
-
await
|
|
510
|
+
await hookGardener({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null, {
|
|
511
511
|
every: opts.every,
|
|
512
512
|
cron: opts.cron
|
|
513
513
|
});
|
|
@@ -674,6 +674,7 @@ splatesQueue.command("move").use(repository6({ needs: "tasks" })).description("m
|
|
|
674
674
|
import {
|
|
675
675
|
BoxShell,
|
|
676
676
|
CoordinatorBox as CoordinatorBox2,
|
|
677
|
+
coordinatorMoved,
|
|
677
678
|
deployKey,
|
|
678
679
|
machineMove,
|
|
679
680
|
neededCreds
|
|
@@ -685,7 +686,6 @@ import {
|
|
|
685
686
|
RUNNING,
|
|
686
687
|
serveDev
|
|
687
688
|
} from "@stringsync/splates-server/server";
|
|
688
|
-
import { coordinatorMoved } from "@stringsync/splates-server/setup";
|
|
689
689
|
import { LOCAL_SLOTS } from "@stringsync/splates-server/workers";
|
|
690
690
|
import { cli as cli9 } from "webappwiz/cmd";
|
|
691
691
|
import { z as z10 } from "zod";
|
|
@@ -694,26 +694,35 @@ import { z as z10 } from "zod";
|
|
|
694
694
|
import { resolve as resolve2 } from "node:path";
|
|
695
695
|
import { commaList as commaList4 } from "@stringsync/splates-server/comma-list";
|
|
696
696
|
import {
|
|
697
|
+
ACCESS_ENVIRONMENT,
|
|
698
|
+
accessEnvironmentExists,
|
|
697
699
|
CoordinatorBox,
|
|
700
|
+
pulumiOrg,
|
|
698
701
|
writeInfraConfig
|
|
699
702
|
} from "@stringsync/splates-server/coordinator";
|
|
700
703
|
import { fail as fail5 } from "@stringsync/splates-server/exit";
|
|
701
|
-
import {
|
|
704
|
+
import { infraConfigure } from "@stringsync/splates-server/infra/configure";
|
|
705
|
+
import { Projects } from "@stringsync/splates-server/projects";
|
|
706
|
+
import { repository as repository7 } from "@stringsync/splates-server/records";
|
|
707
|
+
import { savedTunnelOf } from "@stringsync/splates-server/server";
|
|
708
|
+
import { FsStorage as FsStorage2 } from "@stringsync/splates-server/storage";
|
|
702
709
|
import { cli as cli8 } from "webappwiz/cmd";
|
|
703
710
|
import { z as z9 } from "zod";
|
|
704
711
|
var splatesInfra = cli8("infra").description("the Pulumi project that makes everything the server's box needs in AWS, Cloudflare and GitHub, which `splates server deploy` then installs splates on");
|
|
705
|
-
splatesInfra.command("init").description("write a Pulumi project in <dir>, its program a dozen lines using the components of @stringsync/splates-server/infra, and the stack's config from the flags; run again, it writes only what is not so already and keeps values you changed. It
|
|
706
|
-
|
|
712
|
+
splatesInfra.command("init").use(repository7({ needs: "own" })).description("write a Pulumi project in <dir>, its program a dozen lines using the components of @stringsync/splates-server/infra, and the stack's config from the flags; run again, it writes only what is not so already and keeps values you changed. It fills the stack's config from what splates keeps (the projects, the saved tunnel, the Cloudflare ids, and each secret from the creds store), requesting on the Creds page any cred not kept and exiting `requested`; run again once saved. When the Pulumi ESC environment `coordinator-access` makes (splates/aws) is there, the stack imports it. It records the directory in ~/.splates/coordinator/infra.json, where `splates server deploy` finds the stack. Then `bun add @stringsync/splates-server @pulumi/pulumi @pulumi/aws @pulumi/awsx @pulumi/cloudflare @pulumi/github @pulumi/tls @pulumi/random` there and `pulumi up`").arg("dir", z9.string(), { description: "where the Pulumi project goes" }).option("stack", z9.string(), { default: "prod", description: "the stack" }).option("host", z9.string(), {
|
|
713
|
+
default: "",
|
|
714
|
+
description: "the public host name, as in splates.example.com; the saved tunnel's by default"
|
|
707
715
|
}).option("email", z9.string(), {
|
|
708
|
-
|
|
716
|
+
default: "",
|
|
717
|
+
description: "the email address Access lets in; the tunnel's Access application's by default"
|
|
709
718
|
}).option("region", z9.string(), {
|
|
710
719
|
default: "us-east-1",
|
|
711
720
|
description: "the AWS region"
|
|
712
721
|
}).option("projects", z9.string(), {
|
|
713
722
|
default: "",
|
|
714
|
-
description: "comma-separated slug=owner/repo, each a project the box pushes trunk of"
|
|
723
|
+
description: "comma-separated slug=owner/repo, each a project the box pushes trunk of; every project registered here with a GitHub origin by default"
|
|
715
724
|
}).action(async (opts, ctx) => {
|
|
716
|
-
const
|
|
725
|
+
const listed = commaList4(opts.projects).map((entry) => {
|
|
717
726
|
const [slug, repository] = entry.split("=");
|
|
718
727
|
if (!slug || !repository?.includes("/")) {
|
|
719
728
|
fail5("usage", `--projects wants slug=owner/repo, not ${entry}`, {
|
|
@@ -722,16 +731,28 @@ splatesInfra.command("init").description("write a Pulumi project in <dir>, its p
|
|
|
722
731
|
}
|
|
723
732
|
return { slug, repository };
|
|
724
733
|
});
|
|
725
|
-
const
|
|
734
|
+
const org = await pulumiOrg(ctx.ps);
|
|
735
|
+
const home = new CoordinatorBox({ ps: ctx.ps, fs: ctx.fs }).home;
|
|
736
|
+
const { written, todo } = await infraConfigure({
|
|
726
737
|
dir: opts.dir,
|
|
727
738
|
stack: opts.stack,
|
|
728
739
|
host: opts.host,
|
|
729
740
|
email: opts.email,
|
|
730
741
|
region: opts.region,
|
|
731
|
-
projects,
|
|
732
|
-
|
|
742
|
+
projects: listed.length === 0 ? null : listed,
|
|
743
|
+
registered: async () => (await Projects.in(new FsStorage2(home, ctx.fs), ctx.fs).list()).filter((project) => !project.gone),
|
|
744
|
+
creds: ctx.creds,
|
|
745
|
+
saved: savedTunnelOf(ctx),
|
|
746
|
+
...org !== null && await accessEnvironmentExists(ctx.ps, org) ? { environment: ACCESS_ENVIRONMENT } : {},
|
|
747
|
+
fs: ctx.fs,
|
|
748
|
+
ps: ctx.ps,
|
|
749
|
+
log: ctx.log,
|
|
750
|
+
fetch: ctx.fetch
|
|
751
|
+
});
|
|
752
|
+
await writeInfraConfig(ctx.fs, home, {
|
|
753
|
+
dir: resolve2(opts.dir),
|
|
754
|
+
stack: opts.stack
|
|
733
755
|
});
|
|
734
|
-
await writeInfraConfig(ctx.fs, new CoordinatorBox({ ps: ctx.ps, fs: ctx.fs }).home, { dir: resolve2(opts.dir), stack: opts.stack });
|
|
735
756
|
ctx.log.info(written.length === 0 ? "already so: nothing written" : `wrote ${written.join(", ")}`);
|
|
736
757
|
for (const command of todo) {
|
|
737
758
|
ctx.log.info(`still to run in ${opts.dir}: ${command}`);
|
|
@@ -886,7 +907,7 @@ import { dirname } from "node:path";
|
|
|
886
907
|
import { SYSTEM_STORES as SYSTEM_STORES3 } from "@stringsync/splates-server/creds";
|
|
887
908
|
import { fail as fail7 } from "@stringsync/splates-server/exit";
|
|
888
909
|
import { splatesHome as splatesHome2 } from "@stringsync/splates-server/models";
|
|
889
|
-
import { Projects } from "@stringsync/splates-server/projects";
|
|
910
|
+
import { Projects as Projects2 } from "@stringsync/splates-server/projects";
|
|
890
911
|
import {
|
|
891
912
|
SETUP_AREAS,
|
|
892
913
|
Setup,
|
|
@@ -947,7 +968,7 @@ async function setup(deps, opts) {
|
|
|
947
968
|
}
|
|
948
969
|
async function setupOf(deps) {
|
|
949
970
|
const { fs, ps, secrets = SYSTEM_STORES3, fetch } = deps;
|
|
950
|
-
const root = deps.project === undefined ? await repoRoot(ps) : (await
|
|
971
|
+
const root = deps.project === undefined ? await repoRoot(ps) : (await Projects2.ofMachine(deps).named(deps.project)).root;
|
|
951
972
|
return new Setup({
|
|
952
973
|
fs,
|
|
953
974
|
ps,
|
|
@@ -1093,7 +1114,7 @@ import {
|
|
|
1093
1114
|
import { commaList as commaList5 } from "@stringsync/splates-server/comma-list";
|
|
1094
1115
|
import { refuseGroomer as refuseGroomer3 } from "@stringsync/splates-server/conversations";
|
|
1095
1116
|
import { fail as fail8 } from "@stringsync/splates-server/exit";
|
|
1096
|
-
import { repository as
|
|
1117
|
+
import { repository as repository8 } from "@stringsync/splates-server/records";
|
|
1097
1118
|
import {
|
|
1098
1119
|
DEFAULT_TIMEOUT as DEFAULT_TIMEOUT2,
|
|
1099
1120
|
taskWait,
|
|
@@ -1110,7 +1131,7 @@ async function projectFrom(ctx) {
|
|
|
1110
1131
|
return (await ctx.projects.list()).find((each) => each.root === ctx.git.root)?.slug;
|
|
1111
1132
|
}
|
|
1112
1133
|
function adding(name, description, ready) {
|
|
1113
|
-
splatesTask.command(name).use(
|
|
1134
|
+
splatesTask.command(name).use(repository8({ needs: "queue" })).description(description).arg("subject", z12.string(), {
|
|
1114
1135
|
description: "what is left to do, in a line"
|
|
1115
1136
|
}).arg("text", z12.string(), {
|
|
1116
1137
|
default: "",
|
|
@@ -1164,7 +1185,7 @@ function adding(name, description, ready) {
|
|
|
1164
1185
|
}
|
|
1165
1186
|
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);
|
|
1166
1187
|
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);
|
|
1167
|
-
splatesTask.command("list").use(
|
|
1188
|
+
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(), {
|
|
1168
1189
|
default: false,
|
|
1169
1190
|
description: "only tasks no started one has taken up: the ones free to pick up"
|
|
1170
1191
|
}).option("ready", z12.boolean(), {
|
|
@@ -1176,12 +1197,12 @@ splatesTask.command("list").use(repository7({ needs: "read" })).description("eve
|
|
|
1176
1197
|
ready: opts.ready,
|
|
1177
1198
|
project: await projectFrom(ctx)
|
|
1178
1199
|
}));
|
|
1179
|
-
splatesTask.command("show").use(
|
|
1200
|
+
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) => taskShow(ctx, opts.id, {
|
|
1180
1201
|
json: opts.json,
|
|
1181
1202
|
project: await projectFrom(ctx),
|
|
1182
1203
|
others: (await ctx.projects.list()).filter((project) => project.root !== ctx.git.root).map((project) => project.slug)
|
|
1183
1204
|
}));
|
|
1184
|
-
splatesTask.command("update").use(
|
|
1205
|
+
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(), {
|
|
1185
1206
|
default: "",
|
|
1186
1207
|
description: "whatever more there is to say about it, in markdown; leave out to keep the detail"
|
|
1187
1208
|
}).option("subject", z12.string(), {
|
|
@@ -1246,11 +1267,11 @@ splatesTask.command("update").use(repository7({ needs: "tasks" })).description("
|
|
|
1246
1267
|
manual: opts.manual ? true : opts.auto ? false : undefined
|
|
1247
1268
|
}));
|
|
1248
1269
|
});
|
|
1249
|
-
splatesTask.command("remove").use(
|
|
1270
|
+
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) => {
|
|
1250
1271
|
await refuseGroomer3(ctx.service, "task remove", await ctx.tasks.find(opts.id));
|
|
1251
1272
|
await ctx.journal.record("task remove", null, () => taskRemove(ctx, opts.id), (removed) => ({ tasks: tasksNamed2([removed]) }));
|
|
1252
1273
|
});
|
|
1253
|
-
splatesTask.command("wait").use(
|
|
1274
|
+
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(), {
|
|
1254
1275
|
default: DEFAULT_TIMEOUT2.secs,
|
|
1255
1276
|
description: "how long to wait before giving up"
|
|
1256
1277
|
}).action(async (opts, ctx) => {
|
|
@@ -1264,8 +1285,8 @@ splatesTask.command("wait").use(repository7({ needs: "read" })).description("blo
|
|
|
1264
1285
|
});
|
|
1265
1286
|
|
|
1266
1287
|
// tunnel.ts
|
|
1267
|
-
import { repository as
|
|
1268
|
-
import { savedTunnelOf } from "@stringsync/splates-server/server";
|
|
1288
|
+
import { repository as repository9 } from "@stringsync/splates-server/records";
|
|
1289
|
+
import { savedTunnelOf as savedTunnelOf2 } from "@stringsync/splates-server/server";
|
|
1269
1290
|
import {
|
|
1270
1291
|
API_TOKEN,
|
|
1271
1292
|
tunnelRemove,
|
|
@@ -1274,7 +1295,7 @@ import {
|
|
|
1274
1295
|
import { cli as cli12 } from "webappwiz/cmd";
|
|
1275
1296
|
import { z as z13 } from "zod";
|
|
1276
1297
|
var splatesTunnel = cli12("tunnel").description("reach `splates server` from elsewhere: a Cloudflare tunnel with Cloudflare Access in front, set up once per machine");
|
|
1277
|
-
splatesTunnel.command("setup").use(
|
|
1298
|
+
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(), {
|
|
1278
1299
|
description: "the public host name, in a zone of the account, as in splates.example.com"
|
|
1279
1300
|
}).option("email", z13.string(), {
|
|
1280
1301
|
default: "",
|
|
@@ -1289,12 +1310,12 @@ splatesTunnel.command("setup").use(repository8({ needs: "own" })).description(`m
|
|
|
1289
1310
|
...opts,
|
|
1290
1311
|
creds: ctx.creds,
|
|
1291
1312
|
task: await here(ctx),
|
|
1292
|
-
saved:
|
|
1313
|
+
saved: savedTunnelOf2(ctx),
|
|
1293
1314
|
log: ctx.log,
|
|
1294
1315
|
fetch: ctx.fetch
|
|
1295
1316
|
})));
|
|
1296
1317
|
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) => {
|
|
1297
|
-
const saved =
|
|
1318
|
+
const saved = savedTunnelOf2(ctx);
|
|
1298
1319
|
const record = await saved.read();
|
|
1299
1320
|
const token = record === null ? false : await saved.token() !== undefined;
|
|
1300
1321
|
if (opts.json) {
|
|
@@ -1314,8 +1335,8 @@ splatesTunnel.command("status").description("print the tunnel `splates tunnel se
|
|
|
1314
1335
|
].join(`
|
|
1315
1336
|
`));
|
|
1316
1337
|
});
|
|
1317
|
-
splatesTunnel.command("remove").use(
|
|
1318
|
-
saved:
|
|
1338
|
+
splatesTunnel.command("remove").use(repository9({ needs: "own" })).description(`delete what \`splates tunnel setup\` and \`splates server --slot\` made in Cloudflare by what they saved: each tunnel, the DNS record routing to it, the Access applications and their policy, then forget the saved files and the tunnels' tokens; the API token kept as ${API_TOKEN} stays. Run again after a failure, it finishes what is left`).action(async (_opts, ctx) => ctx.journal.record("tunnel remove", null, async () => tunnelRemove({
|
|
1339
|
+
saved: savedTunnelOf2(ctx),
|
|
1319
1340
|
slots: slotTunnelsOf(ctx),
|
|
1320
1341
|
creds: ctx.creds,
|
|
1321
1342
|
task: await here(ctx),
|
|
@@ -1546,15 +1567,15 @@ function projectArgs(argv) {
|
|
|
1546
1567
|
return { argv };
|
|
1547
1568
|
}
|
|
1548
1569
|
var splates = cli15("splates").use(exits());
|
|
1549
|
-
splates.command("notes").use(
|
|
1550
|
-
splates.command("init").use(
|
|
1570
|
+
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));
|
|
1571
|
+
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));
|
|
1551
1572
|
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) => {
|
|
1552
1573
|
const format = await migrateRecords(ctx);
|
|
1553
1574
|
if (ctx.ps.env(MIGRATING) === undefined) {
|
|
1554
1575
|
ctx.log.info(`splates's records are in format ${format}`);
|
|
1555
1576
|
}
|
|
1556
1577
|
});
|
|
1557
|
-
splates.command("update").use(recordsFirst()).use(
|
|
1578
|
+
splates.command("update").use(recordsFirst()).use(repository10({ needs: "own" })).description("bring this repository up to the newest splates on npm: run `update` on that release when it is newer than this one, which moves the records under .git/splates to its format, as `splates migrate` does, unless an older `splates server` serves the repository, which it asks to restart onto that release once its agents have checkpointed, migrating them as it starts; then installs its splates skill with `bunx skills add` over the copy there, leaving one that links to your own; commit what changed").action(async (_opts, ctx) => {
|
|
1558
1579
|
const code = await update({
|
|
1559
1580
|
root: ctx.git.root,
|
|
1560
1581
|
fs: ctx.fs,
|
|
@@ -1574,8 +1595,8 @@ splates.command("heavy").description("run a heavy command (a full test run, an i
|
|
|
1574
1595
|
ctx.ps.exit(code);
|
|
1575
1596
|
}
|
|
1576
1597
|
});
|
|
1577
|
-
splates.command("forget").use(
|
|
1578
|
-
splates.command("add").use(
|
|
1598
|
+
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));
|
|
1599
|
+
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(), {
|
|
1579
1600
|
description: "the task's name, for its branch and worktree (lowercase-with-dashes)"
|
|
1580
1601
|
}).option("base", z16.string(), {
|
|
1581
1602
|
default: "",
|
|
@@ -1643,11 +1664,11 @@ splates.command("add").use(repository9({ needs: "tasks" })).description("start a
|
|
|
1643
1664
|
await serveProject(ctx);
|
|
1644
1665
|
}
|
|
1645
1666
|
});
|
|
1646
|
-
splates.command("claim").use(
|
|
1667
|
+
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) => {
|
|
1647
1668
|
await ctx.journal.record("claim", opts.task, () => claim(ctx, opts.task), (claimed) => claimed);
|
|
1648
1669
|
await follow(ctx, opts.task);
|
|
1649
1670
|
});
|
|
1650
|
-
splates.command("merge").use(
|
|
1671
|
+
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(), {
|
|
1651
1672
|
default: "",
|
|
1652
1673
|
description: "squash the task's commits into one with this message before landing, saying what the base gains in at least 5 characters"
|
|
1653
1674
|
}).action(async (opts, ctx) => {
|
|
@@ -1660,7 +1681,7 @@ splates.command("merge").use(repository9({ needs: "own" })).description("land th
|
|
|
1660
1681
|
await follow(ctx, null, { from: task });
|
|
1661
1682
|
}
|
|
1662
1683
|
});
|
|
1663
|
-
splates.command("remove").use(
|
|
1684
|
+
splates.command("remove").use(repository10({ needs: "tasks" })).description("discard a task: worktree, branch and state file; 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(), {
|
|
1664
1685
|
default: false,
|
|
1665
1686
|
description: "discard even when another agent holds the lease"
|
|
1666
1687
|
}).option("reason", z16.optional(z16.enum(REMOVE_REASONS)), {
|
|
@@ -1669,19 +1690,19 @@ splates.command("remove").use(repository9({ needs: "tasks" })).description("disc
|
|
|
1669
1690
|
await ctx.journal.record("remove", opts.task, () => remove(ctx, opts.task, { force: opts.force, reason: opts.reason }), ({ released }) => ({ why: opts.reason ?? null, released }));
|
|
1670
1691
|
await follow(ctx, null, { from: opts.task });
|
|
1671
1692
|
});
|
|
1672
|
-
splates.command("list").use(
|
|
1693
|
+
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(), {
|
|
1673
1694
|
default: false,
|
|
1674
1695
|
description: "add each task's Goal and changed files (committed or not), to check for overlap before starting"
|
|
1675
1696
|
}).action((opts, ctx) => list(ctx, { json: opts.json, files: opts.files }));
|
|
1676
|
-
splates.command("show").use(
|
|
1677
|
-
splates.command("wait").use(
|
|
1697
|
+
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 }));
|
|
1698
|
+
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(), {
|
|
1678
1699
|
default: DEFAULT_TIMEOUT3.secs,
|
|
1679
1700
|
description: "how long to wait before giving up"
|
|
1680
1701
|
}).action((opts, ctx) => {
|
|
1681
1702
|
const timeout = Duration3.secs(opts["timeout-secs"]);
|
|
1682
1703
|
return ctx.journal.waiting("wait", opts.task, { until: untilAfter3(timeout) }, () => wait(ctx, opts.task, { timeout }));
|
|
1683
1704
|
});
|
|
1684
|
-
splates.command("log").use(
|
|
1705
|
+
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(), {
|
|
1685
1706
|
default: DEFAULT_COUNT,
|
|
1686
1707
|
description: "how many entries to show"
|
|
1687
1708
|
}).option("json", z16.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => showLog(ctx, { count: opts.count, json: opts.json }));
|
|
@@ -1719,14 +1740,14 @@ splates.command("setup").description("set splates up on this machine, one step a
|
|
|
1719
1740
|
default: false,
|
|
1720
1741
|
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"
|
|
1721
1742
|
}).action((opts, ctx) => setup(ctx, opts));
|
|
1722
|
-
splates.command("path").use(
|
|
1743
|
+
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(), {
|
|
1723
1744
|
default: "",
|
|
1724
1745
|
description: "task name; omit for the main tree"
|
|
1725
1746
|
}).action((opts, ctx) => path(ctx, opts.task || undefined));
|
|
1726
|
-
splates.command("checkpoint").use(
|
|
1747
|
+
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) => {
|
|
1727
1748
|
await checkpoint(ctx, ctx.ps.cwd());
|
|
1728
1749
|
});
|
|
1729
|
-
splates.command("attach").use(
|
|
1750
|
+
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(), {
|
|
1730
1751
|
default: "",
|
|
1731
1752
|
description: "files to store, comma separated"
|
|
1732
1753
|
}).option("task", z16.string(), {
|
|
@@ -1743,7 +1764,7 @@ splates.command("attach").use(repository9({ needs: "own" })).description("store
|
|
|
1743
1764
|
remove: commaList6(opts.remove),
|
|
1744
1765
|
conversation: opts.conversation || undefined
|
|
1745
1766
|
}), (stored) => ({ stored, removed: commaList6(opts.remove) })));
|
|
1746
|
-
splates.command("retry").use(
|
|
1767
|
+
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));
|
|
1747
1768
|
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("budget", splatesBudget);
|
|
1748
1769
|
|
|
1749
1770
|
// index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stringsync/splates",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.52",
|
|
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.52",
|
|
21
21
|
"webappwiz": "0.0.47",
|
|
22
22
|
"zod": "^4.4.3"
|
|
23
23
|
},
|