@stringsync/splates 0.0.53 → 0.0.54
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/artifact.d.ts +3 -0
- package/budget.d.ts +3 -0
- package/creds.d.ts +3 -0
- package/flags.d.ts +37 -0
- package/hook.d.ts +3 -0
- package/index.d.ts +2 -0
- package/index.js +1802 -0
- package/infra.d.ts +3 -0
- package/lane.d.ts +3 -0
- package/notes.d.ts +4 -0
- package/package.json +12 -14
- package/projects.d.ts +3 -0
- package/queue.d.ts +3 -0
- package/server.d.ts +7 -0
- package/setup.d.ts +22 -0
- package/slots.d.ts +3 -0
- package/splates.d.ts +10 -0
- package/task.d.ts +3 -0
- package/tunnel.d.ts +3 -0
- package/waiting.d.ts +3 -0
- package/worker.d.ts +3 -0
- package/artifact.ts +0 -120
- package/budget.ts +0 -117
- package/checkout.ts +0 -8
- package/creds.test.ts +0 -336
- package/creds.ts +0 -144
- package/dev/dev.ts +0 -86
- package/dev/eval.ts +0 -21
- package/dev/fix.ts +0 -63
- package/dev/index.ts +0 -1
- package/dev/prove.ts +0 -38
- package/dev/root.ts +0 -7
- package/dev/ship.ts +0 -46
- package/dev/test.ts +0 -154
- package/e2e.test.ts +0 -472
- package/flags.ts +0 -157
- package/hook.ts +0 -328
- package/index.ts +0 -18
- package/infra.ts +0 -107
- package/lane.test.ts +0 -29
- package/lane.ts +0 -172
- package/notes.ts +0 -19
- package/projects.test.ts +0 -191
- package/projects.ts +0 -37
- package/queue.ts +0 -82
- package/screenshots/annotate.png +0 -0
- package/screenshots/demo.png +0 -0
- package/screenshots/parallel.png +0 -0
- package/screenshots/questions.png +0 -0
- package/server.ts +0 -287
- package/setup.ts +0 -233
- package/slots.ts +0 -65
- package/splates.test.ts +0 -338
- package/splates.ts +0 -567
- package/task.ts +0 -354
- package/tunnel.ts +0 -106
- package/wait-journal.test.ts +0 -96
- package/waiting.ts +0 -59
- package/worker-relay.test.ts +0 -233
- package/worker.ts +0 -300
package/task.ts
DELETED
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addedFacts,
|
|
3
|
-
recordUpdate,
|
|
4
|
-
taskAdd,
|
|
5
|
-
taskList,
|
|
6
|
-
taskRemove,
|
|
7
|
-
taskShow,
|
|
8
|
-
tasksNamed,
|
|
9
|
-
taskUpdate,
|
|
10
|
-
} from "@stringsync/splates-server/board";
|
|
11
|
-
import { commaList } from "@stringsync/splates-server/comma-list";
|
|
12
|
-
import { refuseGroomer } from "@stringsync/splates-server/conversations";
|
|
13
|
-
import { fail } from "@stringsync/splates-server/exit";
|
|
14
|
-
import { repository } from "@stringsync/splates-server/records";
|
|
15
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
16
|
-
import {
|
|
17
|
-
DEFAULT_TIMEOUT,
|
|
18
|
-
taskWait,
|
|
19
|
-
untilAfter,
|
|
20
|
-
} from "@stringsync/splates-server/tasks";
|
|
21
|
-
import { cli } from "webappwiz/cmd";
|
|
22
|
-
import { Duration } from "webappwiz/time";
|
|
23
|
-
import { z } from "zod";
|
|
24
|
-
import { assigneeIn, here, laneIn, position, taskIds } from "./flags";
|
|
25
|
-
|
|
26
|
-
/** `splates task`, the queue, which the root cli mounts after the commands for the task you work on. */
|
|
27
|
-
export const splatesTask = cli<SplatesDeps>("task").description(
|
|
28
|
-
"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",
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* This project's slug when the command is run from another with `--project`,
|
|
33
|
-
* so what it prints names a task as `webappwiz #12`, which the page links to
|
|
34
|
-
* this project; undefined from this project itself, where `#12` is bare.
|
|
35
|
-
*/
|
|
36
|
-
async function projectFrom(ctx: {
|
|
37
|
-
elsewhere: string | null;
|
|
38
|
-
git: { root: string };
|
|
39
|
-
projects: { list(): Promise<{ root: string; slug: string }[]> };
|
|
40
|
-
}): Promise<string | undefined> {
|
|
41
|
-
if (ctx.elsewhere === null) {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
return (await ctx.projects.list()).find((each) => each.root === ctx.git.root)
|
|
45
|
-
?.slug;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* `splates task add` or `defer`: one writes a task its agent groomed, for
|
|
50
|
-
* ready, the other a rough one, for the backlog and whoever grooms it.
|
|
51
|
-
*/
|
|
52
|
-
function adding(name: string, description: string, ready: boolean) {
|
|
53
|
-
splatesTask
|
|
54
|
-
.command(name)
|
|
55
|
-
.use(repository({ needs: "queue" }))
|
|
56
|
-
.description(description)
|
|
57
|
-
.arg("subject", z.string(), {
|
|
58
|
-
description: "what is left to do, in a line",
|
|
59
|
-
})
|
|
60
|
-
.arg("text", z.string(), {
|
|
61
|
-
default: "",
|
|
62
|
-
description: "whatever more there is to say about it, in markdown",
|
|
63
|
-
})
|
|
64
|
-
.option("slug", z.string(), {
|
|
65
|
-
description:
|
|
66
|
-
"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",
|
|
67
|
-
})
|
|
68
|
-
.option("position", position, {
|
|
69
|
-
description:
|
|
70
|
-
"where it goes in the list, 1 at the top, pushing those from there down (default: the bottom)",
|
|
71
|
-
})
|
|
72
|
-
.option("file", z.string(), {
|
|
73
|
-
default: "",
|
|
74
|
-
description:
|
|
75
|
-
"files to attach, comma separated: copied under .git/splates/tasks/<n>/",
|
|
76
|
-
})
|
|
77
|
-
.option("blocked-by", z.string(), {
|
|
78
|
-
default: "",
|
|
79
|
-
description:
|
|
80
|
-
"numbers of the tasks blocking it, comma separated: each has to merge before this one starts",
|
|
81
|
-
})
|
|
82
|
-
.option("lane", z.string(), {
|
|
83
|
-
default: "",
|
|
84
|
-
description:
|
|
85
|
-
"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)",
|
|
86
|
-
})
|
|
87
|
-
.option("agent", z.string(), {
|
|
88
|
-
default: "",
|
|
89
|
-
description:
|
|
90
|
-
"the agent on the roster (`splates agents`) that should work it, which Start on the `splates server` board offers first",
|
|
91
|
-
})
|
|
92
|
-
.option("model", z.string(), {
|
|
93
|
-
default: "",
|
|
94
|
-
description: "with --agent: the model, by its id or name on the roster",
|
|
95
|
-
})
|
|
96
|
-
.option("effort", z.string(), {
|
|
97
|
-
default: "",
|
|
98
|
-
description:
|
|
99
|
-
"with --agent: the thinking level, one the agent offers (default: the agent's own)",
|
|
100
|
-
})
|
|
101
|
-
.action(async (opts, ctx) => {
|
|
102
|
-
if (ctx.elsewhere !== null && opts.lane !== "") {
|
|
103
|
-
fail(
|
|
104
|
-
"not_granted",
|
|
105
|
-
"a task from another project goes in its untriaged lane, for whoever orchestrates it there to place: leave --lane off",
|
|
106
|
-
{ subject: opts.subject },
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
const from = await here(ctx);
|
|
110
|
-
const assignee = await assigneeIn(
|
|
111
|
-
ctx,
|
|
112
|
-
opts.agent,
|
|
113
|
-
opts.model,
|
|
114
|
-
opts.effort,
|
|
115
|
-
);
|
|
116
|
-
// Run with `--project`, the working directory is in the other project.
|
|
117
|
-
const origin =
|
|
118
|
-
ctx.elsewhere === null
|
|
119
|
-
? undefined
|
|
120
|
-
: {
|
|
121
|
-
project: ctx.elsewhere,
|
|
122
|
-
task:
|
|
123
|
-
ctx.project === undefined
|
|
124
|
-
? null
|
|
125
|
-
: ctx.service.taskFor(
|
|
126
|
-
await ctx.git.currentBranch(ctx.ps.cwd()),
|
|
127
|
-
),
|
|
128
|
-
};
|
|
129
|
-
const project = await projectFrom(ctx);
|
|
130
|
-
await ctx.journal.record(
|
|
131
|
-
"task add",
|
|
132
|
-
from,
|
|
133
|
-
() =>
|
|
134
|
-
taskAdd(ctx, opts.subject, from, {
|
|
135
|
-
slug: opts.slug,
|
|
136
|
-
text: opts.text,
|
|
137
|
-
position: opts.position,
|
|
138
|
-
files: commaList(opts.file),
|
|
139
|
-
blockedBy: taskIds(opts["blocked-by"]),
|
|
140
|
-
lane: laneIn(opts.lane) ?? null,
|
|
141
|
-
assignee,
|
|
142
|
-
origin,
|
|
143
|
-
ready,
|
|
144
|
-
project,
|
|
145
|
-
}),
|
|
146
|
-
addedFacts,
|
|
147
|
-
);
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
adding(
|
|
152
|
-
"add",
|
|
153
|
-
"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",
|
|
154
|
-
true,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
adding(
|
|
158
|
-
"defer",
|
|
159
|
-
"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",
|
|
160
|
-
false,
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
splatesTask
|
|
164
|
-
.command("list")
|
|
165
|
-
.use(repository({ needs: "read" }))
|
|
166
|
-
.description(
|
|
167
|
-
"every queued task, top of the list first, with the task it came from and the started one that took it up",
|
|
168
|
-
)
|
|
169
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
170
|
-
.option("open", z.boolean(), {
|
|
171
|
-
default: false,
|
|
172
|
-
description:
|
|
173
|
-
"only tasks no started one has taken up: the ones free to pick up",
|
|
174
|
-
})
|
|
175
|
-
.option("ready", z.boolean(), {
|
|
176
|
-
default: false,
|
|
177
|
-
description:
|
|
178
|
-
"only tasks free to start now: none has taken them, and nothing blocking them is still open",
|
|
179
|
-
})
|
|
180
|
-
.action(async (opts, ctx) =>
|
|
181
|
-
taskList(ctx, {
|
|
182
|
-
json: opts.json,
|
|
183
|
-
open: opts.open,
|
|
184
|
-
ready: opts.ready,
|
|
185
|
-
project: await projectFrom(ctx),
|
|
186
|
-
}),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
splatesTask
|
|
190
|
-
.command("show")
|
|
191
|
-
.use(repository({ needs: "read" }))
|
|
192
|
-
.description("one task in full: where it stands, its files, and its detail")
|
|
193
|
-
.arg("id", z.coerce.number().int().positive(), { description: "task number" })
|
|
194
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
195
|
-
.action(async (opts, ctx) =>
|
|
196
|
-
taskShow(ctx, opts.id, {
|
|
197
|
-
json: opts.json,
|
|
198
|
-
project: await projectFrom(ctx),
|
|
199
|
-
others: (await ctx.projects.list())
|
|
200
|
-
.filter((project) => project.root !== ctx.git.root)
|
|
201
|
-
.map((project) => project.slug),
|
|
202
|
-
}),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
splatesTask
|
|
206
|
-
.command("update")
|
|
207
|
-
.use(repository({ needs: "tasks" }))
|
|
208
|
-
.description(
|
|
209
|
-
"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",
|
|
210
|
-
)
|
|
211
|
-
.arg("id", z.coerce.number().int().positive(), { description: "task number" })
|
|
212
|
-
.arg("text", z.string(), {
|
|
213
|
-
default: "",
|
|
214
|
-
description:
|
|
215
|
-
"whatever more there is to say about it, in markdown; leave out to keep the detail",
|
|
216
|
-
})
|
|
217
|
-
.option("subject", z.string(), {
|
|
218
|
-
default: "",
|
|
219
|
-
description: "what is left to do, in a line; leave out to keep it",
|
|
220
|
-
})
|
|
221
|
-
.option("slug", z.string(), {
|
|
222
|
-
default: "",
|
|
223
|
-
description:
|
|
224
|
-
"a new name for the task that takes it (lowercase-with-dashes); leave out to keep it",
|
|
225
|
-
})
|
|
226
|
-
.option("position", position, {
|
|
227
|
-
description:
|
|
228
|
-
"where to move it in the list, 1 at the top; the others close up around it",
|
|
229
|
-
})
|
|
230
|
-
.option("file", z.string(), {
|
|
231
|
-
default: "",
|
|
232
|
-
description: "files to attach, comma separated",
|
|
233
|
-
})
|
|
234
|
-
.option("remove-file", z.string(), {
|
|
235
|
-
default: "",
|
|
236
|
-
description:
|
|
237
|
-
"attached files to drop, comma separated, by path or stored name",
|
|
238
|
-
})
|
|
239
|
-
.option("blocked-by", z.string(), {
|
|
240
|
-
default: "",
|
|
241
|
-
description:
|
|
242
|
-
"numbers of more tasks blocking it, comma separated: each has to merge before this one starts; refused when that makes a loop",
|
|
243
|
-
})
|
|
244
|
-
.option("remove-blocked-by", z.string(), {
|
|
245
|
-
default: "",
|
|
246
|
-
description: "numbers of tasks no longer blocking it, comma separated",
|
|
247
|
-
})
|
|
248
|
-
.option("lane", z.string(), {
|
|
249
|
-
default: "",
|
|
250
|
-
description:
|
|
251
|
-
"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",
|
|
252
|
-
})
|
|
253
|
-
.option("agent", z.string(), {
|
|
254
|
-
default: "",
|
|
255
|
-
description:
|
|
256
|
-
"the agent on the roster (`splates agents`) that should work it, which Start on the `splates server` board offers first, or `none`",
|
|
257
|
-
})
|
|
258
|
-
.option("model", z.string(), {
|
|
259
|
-
default: "",
|
|
260
|
-
description: "with --agent: the model, by its id or name on the roster",
|
|
261
|
-
})
|
|
262
|
-
.option("effort", z.string(), {
|
|
263
|
-
default: "",
|
|
264
|
-
description:
|
|
265
|
-
"with --agent: the thinking level, one the agent offers (default: the agent's own)",
|
|
266
|
-
})
|
|
267
|
-
.option("ready", z.boolean(), {
|
|
268
|
-
default: false,
|
|
269
|
-
description:
|
|
270
|
-
"mark it groomed, moving it from the backlog to ready: no task already asks it, and what blocks it is linked",
|
|
271
|
-
})
|
|
272
|
-
.option("manual", z.boolean(), {
|
|
273
|
-
default: false,
|
|
274
|
-
description:
|
|
275
|
-
"queue it by hand only: a queued lane passes over it, and it starts once you `splates queue add` it",
|
|
276
|
-
})
|
|
277
|
-
.option("auto", z.boolean(), {
|
|
278
|
-
default: false,
|
|
279
|
-
description: "let a queued lane queue it again, undoing --manual",
|
|
280
|
-
})
|
|
281
|
-
.action(async (opts, ctx) => {
|
|
282
|
-
if (opts.manual && opts.auto) {
|
|
283
|
-
fail("usage", "pass --manual or --auto, not both", { number: opts.id });
|
|
284
|
-
}
|
|
285
|
-
await refuseGroomer(
|
|
286
|
-
ctx.service,
|
|
287
|
-
"task update",
|
|
288
|
-
await ctx.tasks.find(opts.id),
|
|
289
|
-
);
|
|
290
|
-
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
291
|
-
await recordUpdate(ctx.journal, ctx.tasks, opts.id, () =>
|
|
292
|
-
taskUpdate(ctx, opts.id, {
|
|
293
|
-
subject: opts.subject,
|
|
294
|
-
slug: opts.slug,
|
|
295
|
-
text: opts.text,
|
|
296
|
-
position: opts.position,
|
|
297
|
-
files: commaList(opts.file),
|
|
298
|
-
removeFiles: commaList(opts["remove-file"]),
|
|
299
|
-
blockedBy: taskIds(opts["blocked-by"]),
|
|
300
|
-
removeBlockedBy: taskIds(opts["remove-blocked-by"]),
|
|
301
|
-
lane: laneIn(opts.lane),
|
|
302
|
-
assignee,
|
|
303
|
-
ready: opts.ready,
|
|
304
|
-
manual: opts.manual ? true : opts.auto ? false : undefined,
|
|
305
|
-
}),
|
|
306
|
-
);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
splatesTask
|
|
310
|
-
.command("remove")
|
|
311
|
-
.use(repository({ needs: "tasks" }))
|
|
312
|
-
.description(
|
|
313
|
-
"drop a queued task that was done some other way or no longer applies",
|
|
314
|
-
)
|
|
315
|
-
.arg("id", z.coerce.number().int().positive(), { description: "task number" })
|
|
316
|
-
.action(async (opts, ctx) => {
|
|
317
|
-
await refuseGroomer(
|
|
318
|
-
ctx.service,
|
|
319
|
-
"task remove",
|
|
320
|
-
await ctx.tasks.find(opts.id),
|
|
321
|
-
);
|
|
322
|
-
await ctx.journal.record(
|
|
323
|
-
"task remove",
|
|
324
|
-
null,
|
|
325
|
-
() => taskRemove(ctx, opts.id),
|
|
326
|
-
(removed) => ({ tasks: tasksNamed([removed]) }),
|
|
327
|
-
);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
splatesTask
|
|
331
|
-
.command("wait")
|
|
332
|
-
.use(repository({ needs: "read" }))
|
|
333
|
-
.description(
|
|
334
|
-
"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`",
|
|
335
|
-
)
|
|
336
|
-
.arg("id", z.coerce.number().int().positive(), { description: "task number" })
|
|
337
|
-
.option("timeout-secs", z.coerce.number(), {
|
|
338
|
-
default: DEFAULT_TIMEOUT.secs,
|
|
339
|
-
description: "how long to wait before giving up",
|
|
340
|
-
})
|
|
341
|
-
.action(async (opts, ctx) => {
|
|
342
|
-
const timeout = Duration.secs(opts["timeout-secs"]);
|
|
343
|
-
const task = (await ctx.tasks.all()).find((each) => each.id === opts.id);
|
|
344
|
-
await ctx.journal.waiting(
|
|
345
|
-
"task wait",
|
|
346
|
-
null,
|
|
347
|
-
{
|
|
348
|
-
number: opts.id,
|
|
349
|
-
subject: task?.subject ?? null,
|
|
350
|
-
until: untilAfter(timeout),
|
|
351
|
-
},
|
|
352
|
-
() => taskWait(ctx, opts.id, { timeout }),
|
|
353
|
-
);
|
|
354
|
-
});
|
package/tunnel.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { repository } from "@stringsync/splates-server/records";
|
|
2
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
3
|
-
import { savedTunnelOf } from "@stringsync/splates-server/server";
|
|
4
|
-
import {
|
|
5
|
-
API_TOKEN,
|
|
6
|
-
tunnelRemove,
|
|
7
|
-
tunnelSetup,
|
|
8
|
-
} from "@stringsync/splates-server/tunnel";
|
|
9
|
-
import { cli } from "webappwiz/cmd";
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
import { here, slotTunnelsOf } from "./flags";
|
|
12
|
-
|
|
13
|
-
/** `splates tunnel`, which the root cli mounts after `splates lane`. */
|
|
14
|
-
export const splatesTunnel = cli<SplatesDeps>("tunnel").description(
|
|
15
|
-
"reach `splates server` from elsewhere: a Cloudflare tunnel with Cloudflare Access in front, set up once per machine",
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
splatesTunnel
|
|
19
|
-
.command("setup")
|
|
20
|
-
.use(repository({ needs: "own" }))
|
|
21
|
-
.description(
|
|
22
|
-
`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`,
|
|
23
|
-
)
|
|
24
|
-
.arg("host", z.string(), {
|
|
25
|
-
description:
|
|
26
|
-
"the public host name, in a zone of the account, as in splates.example.com",
|
|
27
|
-
})
|
|
28
|
-
.option("email", z.string(), {
|
|
29
|
-
default: "",
|
|
30
|
-
description: "comma-separated email addresses Access lets in",
|
|
31
|
-
})
|
|
32
|
-
.option("name", z.string(), {
|
|
33
|
-
default: "splates",
|
|
34
|
-
description:
|
|
35
|
-
"what the tunnel, the Access application and its policy are called",
|
|
36
|
-
})
|
|
37
|
-
.option("team", z.string(), {
|
|
38
|
-
default: "",
|
|
39
|
-
description:
|
|
40
|
-
"the Zero Trust team to make, as in <team>.cloudflareaccess.com, when the account has none yet",
|
|
41
|
-
})
|
|
42
|
-
.action(async (opts, ctx) =>
|
|
43
|
-
ctx.journal.record("tunnel setup", null, async () =>
|
|
44
|
-
tunnelSetup({
|
|
45
|
-
...opts,
|
|
46
|
-
creds: ctx.creds,
|
|
47
|
-
task: await here(ctx),
|
|
48
|
-
saved: savedTunnelOf(ctx),
|
|
49
|
-
log: ctx.log,
|
|
50
|
-
fetch: ctx.fetch,
|
|
51
|
-
}),
|
|
52
|
-
),
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
splatesTunnel
|
|
56
|
-
.command("status")
|
|
57
|
-
.description(
|
|
58
|
-
"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",
|
|
59
|
-
)
|
|
60
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
61
|
-
.action(async (opts, ctx) => {
|
|
62
|
-
const saved = savedTunnelOf(ctx);
|
|
63
|
-
const record = await saved.read();
|
|
64
|
-
const token = record === null ? false : (await saved.token()) !== undefined;
|
|
65
|
-
if (opts.json) {
|
|
66
|
-
ctx.log.info(
|
|
67
|
-
JSON.stringify(record === null ? null : { ...record, token }),
|
|
68
|
-
);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (record === null) {
|
|
72
|
-
ctx.log.info(
|
|
73
|
-
"no tunnel saved: `splates tunnel setup <host> --email <you>` makes one",
|
|
74
|
-
);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
ctx.log.info(
|
|
78
|
-
[
|
|
79
|
-
`host: https://${record.host}`,
|
|
80
|
-
`team: ${record.team}.cloudflareaccess.com`,
|
|
81
|
-
`aud: ${record.aud}`,
|
|
82
|
-
`tunnel: ${record.tunnel}`,
|
|
83
|
-
`token: ${token ? "in the device's store" : "missing: run `splates tunnel setup` again"}`,
|
|
84
|
-
].join("\n"),
|
|
85
|
-
);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
splatesTunnel
|
|
89
|
-
.command("remove")
|
|
90
|
-
.use(repository({ needs: "own" }))
|
|
91
|
-
.description(
|
|
92
|
-
`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`,
|
|
93
|
-
)
|
|
94
|
-
.action(async (_opts, ctx) =>
|
|
95
|
-
ctx.journal.record("tunnel remove", null, async () =>
|
|
96
|
-
tunnelRemove({
|
|
97
|
-
saved: savedTunnelOf(ctx),
|
|
98
|
-
slots: slotTunnelsOf(ctx),
|
|
99
|
-
creds: ctx.creds,
|
|
100
|
-
task: await here(ctx),
|
|
101
|
-
fs: ctx.fs,
|
|
102
|
-
log: ctx.log,
|
|
103
|
-
fetch: ctx.fetch,
|
|
104
|
-
}),
|
|
105
|
-
),
|
|
106
|
-
);
|
package/wait-journal.test.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import type { Event } from "@stringsync/splates-server/records";
|
|
3
|
-
import { Testing } from "@stringsync/splates-server/testing";
|
|
4
|
-
import { Duration, sleep } from "webappwiz/time";
|
|
5
|
-
import { splates } from "./splates";
|
|
6
|
-
|
|
7
|
-
describe("splates's waits in the journal", () => {
|
|
8
|
-
let env: Testing;
|
|
9
|
-
let deps: Parameters<typeof splates.run>[0];
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
env = await Testing.open();
|
|
13
|
-
env.ps.cd(env.root);
|
|
14
|
-
deps = {
|
|
15
|
-
log: env.log,
|
|
16
|
-
fs: env.fs,
|
|
17
|
-
ps: env.ps,
|
|
18
|
-
assets: env.assets,
|
|
19
|
-
secrets: env.secrets,
|
|
20
|
-
};
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(() => env.disposeAsync());
|
|
24
|
-
|
|
25
|
-
it("logs a cred wait as it begins, then as it ends, naming the first", async () => {
|
|
26
|
-
await splates.run(deps, [
|
|
27
|
-
"creds",
|
|
28
|
-
"request",
|
|
29
|
-
"API_TOKEN",
|
|
30
|
-
"--purpose",
|
|
31
|
-
"To test.",
|
|
32
|
-
"--instructions",
|
|
33
|
-
"Paste it.",
|
|
34
|
-
]);
|
|
35
|
-
|
|
36
|
-
const waiting = splates.run(deps, ["creds", "wait", "API_TOKEN"]);
|
|
37
|
-
await sleep(Duration.ms(20));
|
|
38
|
-
await env.creds.save("API_TOKEN", "secret");
|
|
39
|
-
await waiting;
|
|
40
|
-
|
|
41
|
-
const [began, ended] = (await env.journal.tail(2)) as Extract<
|
|
42
|
-
Event,
|
|
43
|
-
{ action: "creds wait" }
|
|
44
|
-
>[];
|
|
45
|
-
expect(began).toMatchObject({
|
|
46
|
-
action: "creds wait",
|
|
47
|
-
reason: null,
|
|
48
|
-
name: "API_TOKEN",
|
|
49
|
-
began: null,
|
|
50
|
-
});
|
|
51
|
-
expect(ended).toMatchObject({
|
|
52
|
-
action: "creds wait",
|
|
53
|
-
reason: null,
|
|
54
|
-
name: "API_TOKEN",
|
|
55
|
-
began: began?.id,
|
|
56
|
-
});
|
|
57
|
-
expect(Date.parse(ended?.until ?? "")).toBeGreaterThan(
|
|
58
|
-
Date.now() + Duration.mins(14).ms,
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("logs a queued task wait with its subject, and how it gave up", async () => {
|
|
63
|
-
await splates.run(deps, [
|
|
64
|
-
"task",
|
|
65
|
-
"add",
|
|
66
|
-
"Seven",
|
|
67
|
-
"detail",
|
|
68
|
-
"--slug",
|
|
69
|
-
"seven",
|
|
70
|
-
]);
|
|
71
|
-
const [queued] = await env.tasks.all();
|
|
72
|
-
|
|
73
|
-
await splates.run(deps, [
|
|
74
|
-
"task",
|
|
75
|
-
"wait",
|
|
76
|
-
String(queued?.id),
|
|
77
|
-
"--timeout-secs",
|
|
78
|
-
"0",
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
const [began, ended] = await env.journal.tail(2);
|
|
82
|
-
expect(began).toMatchObject({
|
|
83
|
-
action: "task wait",
|
|
84
|
-
reason: null,
|
|
85
|
-
number: queued?.id,
|
|
86
|
-
subject: "Seven",
|
|
87
|
-
began: null,
|
|
88
|
-
});
|
|
89
|
-
expect(ended).toMatchObject({
|
|
90
|
-
action: "task wait",
|
|
91
|
-
reason: "timeout",
|
|
92
|
-
number: queued?.id,
|
|
93
|
-
began: began?.id,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
package/waiting.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
2
|
-
import { listQueue, moveQueued } from "@stringsync/splates-server/workers";
|
|
3
|
-
import { cli } from "webappwiz/cmd";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
|
|
6
|
-
const DEV = {
|
|
7
|
-
default: "",
|
|
8
|
-
description:
|
|
9
|
-
"the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV",
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/** `splates worker queue`, which `splates worker` mounts. */
|
|
13
|
-
export const splatesWaiting = cli<SplatesDeps>("queue").description(
|
|
14
|
-
"the agents waiting for a slot on a worker of the running `splates server`: each one another agent started (with `splates add --agent`, or a lane's orchestrator handing out tasks), started or resumed while every worker it can run on is full, waits here, first come, first served, and starts once a slot frees, as when an agent left idle 30 minutes stops; none is ever failed for waiting. An agent you start waits only while no worker it can run on is up, as with this machine's own removed or none on the platform its project requires",
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
splatesWaiting
|
|
18
|
-
.command("list")
|
|
19
|
-
.description(
|
|
20
|
-
"list the agents waiting for a slot, first in line first: position, project, task, conversation, how long each has waited and why (no free slot, no worker up, or a platform no worker runs, as `needs macOS`)",
|
|
21
|
-
)
|
|
22
|
-
.option("dev", z.string(), DEV)
|
|
23
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
24
|
-
.action((opts, ctx) =>
|
|
25
|
-
listQueue({
|
|
26
|
-
dev: opts.dev,
|
|
27
|
-
json: opts.json,
|
|
28
|
-
log: ctx.log,
|
|
29
|
-
fs: ctx.fs,
|
|
30
|
-
ps: ctx.ps,
|
|
31
|
-
send: ctx.fetch,
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
splatesWaiting
|
|
36
|
-
.command("move")
|
|
37
|
-
.description(
|
|
38
|
-
"move an agent waiting for a slot to another place in line, from 1 for the next to start, then list the queue",
|
|
39
|
-
)
|
|
40
|
-
.arg("which", z.string(), {
|
|
41
|
-
description: "the task it works, or its conversation's id",
|
|
42
|
-
})
|
|
43
|
-
.arg("position", z.coerce.number().int(), {
|
|
44
|
-
description: "where in line to put it, from 1",
|
|
45
|
-
})
|
|
46
|
-
.option("dev", z.string(), DEV)
|
|
47
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
48
|
-
.action((opts, ctx) =>
|
|
49
|
-
moveQueued({
|
|
50
|
-
which: opts.which,
|
|
51
|
-
position: opts.position,
|
|
52
|
-
dev: opts.dev,
|
|
53
|
-
json: opts.json,
|
|
54
|
-
log: ctx.log,
|
|
55
|
-
fs: ctx.fs,
|
|
56
|
-
ps: ctx.ps,
|
|
57
|
-
send: ctx.fetch,
|
|
58
|
-
}),
|
|
59
|
-
);
|