@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/flags.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { BACKLOG_LANE } from "@stringsync/splates-server/board";
|
|
2
|
-
import { commaList } from "@stringsync/splates-server/comma-list";
|
|
3
|
-
import { SYSTEM_STORES } from "@stringsync/splates-server/creds";
|
|
4
|
-
import { fail } from "@stringsync/splates-server/exit";
|
|
5
|
-
import {
|
|
6
|
-
type AgentPick,
|
|
7
|
-
Offered,
|
|
8
|
-
pickAgent,
|
|
9
|
-
pickEffort,
|
|
10
|
-
type RosterAgent,
|
|
11
|
-
splatesHome,
|
|
12
|
-
} from "@stringsync/splates-server/models";
|
|
13
|
-
import { SLOT_POOL } from "@stringsync/splates-server/ports";
|
|
14
|
-
import type { Repository } from "@stringsync/splates-server/records";
|
|
15
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
16
|
-
import { rosterOf, seeded } from "@stringsync/splates-server/server";
|
|
17
|
-
import { FsStorage } from "@stringsync/splates-server/storage";
|
|
18
|
-
import { SavedTunnel } from "@stringsync/splates-server/tunnel";
|
|
19
|
-
import { z } from "zod";
|
|
20
|
-
|
|
21
|
-
/** A place in the queue, 1 at the top; absent leaves it to the command. */
|
|
22
|
-
export const position = z.coerce.number().int().positive().optional();
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The agent `--agent`, `--model` and `--effort` name off the roster:
|
|
26
|
-
* undefined when none is given, null for `--agent none`.
|
|
27
|
-
*/
|
|
28
|
-
export async function assigneeIn(
|
|
29
|
-
deps: SplatesDeps,
|
|
30
|
-
agent: string,
|
|
31
|
-
model: string,
|
|
32
|
-
effort: string,
|
|
33
|
-
): Promise<AgentPick | null | undefined> {
|
|
34
|
-
if (agent === "") {
|
|
35
|
-
if (model !== "" || effort !== "") {
|
|
36
|
-
fail("usage", "--model and --effort go with --agent", {
|
|
37
|
-
model,
|
|
38
|
-
effort,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
if (agent === "none") {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return pickIn(deps, await seeded(rosterOf(deps)), agent, model, effort);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* `agent` on `model` off `agents`, at the thinking level `effort` names
|
|
51
|
-
* among those it offers, each left out when empty.
|
|
52
|
-
*/
|
|
53
|
-
export async function pickIn(
|
|
54
|
-
deps: SplatesDeps,
|
|
55
|
-
agents: RosterAgent[],
|
|
56
|
-
agent: string,
|
|
57
|
-
model: string,
|
|
58
|
-
effort: string,
|
|
59
|
-
data: Record<string, unknown> = {},
|
|
60
|
-
): Promise<AgentPick> {
|
|
61
|
-
const picked = pickAgent(
|
|
62
|
-
agents,
|
|
63
|
-
agent,
|
|
64
|
-
model === "" ? undefined : model,
|
|
65
|
-
data,
|
|
66
|
-
);
|
|
67
|
-
return { ...picked, ...(await effortIn(deps, picked.agent, effort, data)) };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* `{ effort }` for the thinking level `effort` names among those `agent`
|
|
72
|
-
* offered when last started, or nothing when it is empty.
|
|
73
|
-
*/
|
|
74
|
-
export async function effortIn(
|
|
75
|
-
deps: SplatesDeps,
|
|
76
|
-
agent: string,
|
|
77
|
-
effort: string,
|
|
78
|
-
data: Record<string, unknown> = {},
|
|
79
|
-
): Promise<{ effort?: string }> {
|
|
80
|
-
if (effort === "") {
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
|
-
const offered = (await offeredOf(deps).all())[agent];
|
|
84
|
-
return { effort: pickEffort(offered, agent, effort, data) };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** The settings each agent offered when last started, from any project. */
|
|
88
|
-
export function offeredOf({ ps, fs }: SplatesDeps): Offered {
|
|
89
|
-
return new Offered(
|
|
90
|
-
new FsStorage(splatesHome(ps), fs).document("offered.json"),
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** The tunnel each slot's home may have saved, for every word of the pool. */
|
|
95
|
-
export function slotTunnelsOf({
|
|
96
|
-
ps,
|
|
97
|
-
fs,
|
|
98
|
-
secrets = SYSTEM_STORES,
|
|
99
|
-
}: SplatesDeps): SavedTunnel[] {
|
|
100
|
-
return SLOT_POOL.map(({ word }) =>
|
|
101
|
-
SavedTunnel.in(
|
|
102
|
-
new FsStorage(`${splatesHome(ps)}/slots/${word}`, fs),
|
|
103
|
-
secrets.device,
|
|
104
|
-
),
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** `--lane 3`, `new` or `none` as a lane; absent leaves it to the command. */
|
|
109
|
-
export function laneIn(raw: string): number | "new" | null | undefined {
|
|
110
|
-
const value = raw.trim();
|
|
111
|
-
if (value === "") {
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
if (value === "new") {
|
|
115
|
-
return "new";
|
|
116
|
-
}
|
|
117
|
-
if (value === "none") {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
if (value === "backlog") {
|
|
121
|
-
return BACKLOG_LANE;
|
|
122
|
-
}
|
|
123
|
-
const id = Number(value);
|
|
124
|
-
if (!Number.isInteger(id) || id < 0) {
|
|
125
|
-
fail("usage", `'${value}' is not a lane: pass its number, new or none`, {
|
|
126
|
-
lane: value,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return id;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** `--task 3,5` as task numbers, refusing anything that is not one. */
|
|
133
|
-
export function taskIds(raw: string): number[] {
|
|
134
|
-
return commaList(raw).map((value) => {
|
|
135
|
-
const id = Number(value);
|
|
136
|
-
if (!Number.isInteger(id) || id <= 0) {
|
|
137
|
-
fail("usage", `'${value}' is not a task number`, { number: value });
|
|
138
|
-
}
|
|
139
|
-
return id;
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Which task a command that takes no task name is about, so the journal can
|
|
145
|
-
* name it. `merge` reads it off the current branch.
|
|
146
|
-
*/
|
|
147
|
-
export async function here({
|
|
148
|
-
service,
|
|
149
|
-
git,
|
|
150
|
-
ps,
|
|
151
|
-
project,
|
|
152
|
-
}: SplatesDeps & Repository): Promise<string | null> {
|
|
153
|
-
// Run with `--project`, the working directory is another repository's.
|
|
154
|
-
return project === undefined
|
|
155
|
-
? service.taskFor(await git.currentBranch(ps.cwd()))
|
|
156
|
-
: null;
|
|
157
|
-
}
|
package/hook.ts
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { commaList } from "@stringsync/splates-server/comma-list";
|
|
2
|
-
import { fail } from "@stringsync/splates-server/exit";
|
|
3
|
-
import {
|
|
4
|
-
type Hook,
|
|
5
|
-
HookStore,
|
|
6
|
-
hookAdd,
|
|
7
|
-
hookEdit,
|
|
8
|
-
hookGardener,
|
|
9
|
-
hookGroomer,
|
|
10
|
-
hookList,
|
|
11
|
-
hookPause,
|
|
12
|
-
hookRemove,
|
|
13
|
-
lanesNamed,
|
|
14
|
-
stagesNamed,
|
|
15
|
-
TASK_EVENTS,
|
|
16
|
-
type TaskEventName,
|
|
17
|
-
type TaskFilter,
|
|
18
|
-
type Trigger,
|
|
19
|
-
} from "@stringsync/splates-server/hooks";
|
|
20
|
-
import { repository } from "@stringsync/splates-server/records";
|
|
21
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
22
|
-
import type { Storage } from "@stringsync/splates-server/storage";
|
|
23
|
-
import { cli } from "webappwiz/cmd";
|
|
24
|
-
import { z } from "zod";
|
|
25
|
-
import { assigneeIn } from "./flags";
|
|
26
|
-
|
|
27
|
-
/** `splates hook`, which the root cli mounts beside `splates queue`. */
|
|
28
|
-
export const splatesHook = cli<SplatesDeps>("hook").description(
|
|
29
|
-
`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`,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
splatesHook
|
|
33
|
-
.command("list")
|
|
34
|
-
.use(repository({ needs: "read" }))
|
|
35
|
-
.description("every automation set here, by number, with its trigger")
|
|
36
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
37
|
-
.action((opts, ctx) =>
|
|
38
|
-
hookList({ hooks: storeOf(ctx), log: ctx.log }, { json: opts.json }),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
splatesHook
|
|
42
|
-
.command("add")
|
|
43
|
-
.use(repository({ needs: "tasks" }))
|
|
44
|
-
.description(
|
|
45
|
-
"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",
|
|
46
|
-
)
|
|
47
|
-
.arg("event", z.string().optional(), {
|
|
48
|
-
description: "the task event, as task.created; none for a schedule",
|
|
49
|
-
})
|
|
50
|
-
.option("stage", z.string(), {
|
|
51
|
-
default: "",
|
|
52
|
-
description:
|
|
53
|
-
"only for tasks in these stages, comma separated: backlog, ready, queued, running",
|
|
54
|
-
})
|
|
55
|
-
.option("lane", z.string(), {
|
|
56
|
-
default: "",
|
|
57
|
-
description: "only for tasks in these lanes, by number, comma separated",
|
|
58
|
-
})
|
|
59
|
-
.option("every", z.string(), {
|
|
60
|
-
default: "",
|
|
61
|
-
description: "run every so often, as 30m, 2h or 1d",
|
|
62
|
-
})
|
|
63
|
-
.option("cron", z.string(), {
|
|
64
|
-
default: "",
|
|
65
|
-
description:
|
|
66
|
-
'run on a five-field cron line, in this machine\'s time, as "0 9 * * 1-5"',
|
|
67
|
-
})
|
|
68
|
-
.option("agent", z.string(), {
|
|
69
|
-
default: "",
|
|
70
|
-
description: "the agent to run, as `splates agents` names it",
|
|
71
|
-
})
|
|
72
|
-
.option("model", z.string(), {
|
|
73
|
-
default: "",
|
|
74
|
-
description: "its model (default: the agent's own)",
|
|
75
|
-
})
|
|
76
|
-
.option("effort", z.string(), {
|
|
77
|
-
default: "",
|
|
78
|
-
description: "its thinking level (default: the agent's own)",
|
|
79
|
-
})
|
|
80
|
-
.option("prompt", z.string(), {
|
|
81
|
-
default: "",
|
|
82
|
-
description: "what the agent is told after the event",
|
|
83
|
-
})
|
|
84
|
-
.option("run", z.string(), {
|
|
85
|
-
default: "",
|
|
86
|
-
description: "a shell command to run",
|
|
87
|
-
})
|
|
88
|
-
.action(async (opts, ctx) => {
|
|
89
|
-
const pick = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
90
|
-
let hook: Hook;
|
|
91
|
-
if (opts.run !== "") {
|
|
92
|
-
if (pick != null || opts.prompt !== "") {
|
|
93
|
-
fail("usage", "--run goes alone: a shell command runs no agent", {});
|
|
94
|
-
}
|
|
95
|
-
hook = { kind: "run", command: opts.run };
|
|
96
|
-
} else if (pick == null) {
|
|
97
|
-
fail("usage", "say what runs: --agent with --prompt, or --run", {});
|
|
98
|
-
} else if (opts.prompt === "") {
|
|
99
|
-
fail("usage", "an agent hook needs --prompt: what to tell it", {});
|
|
100
|
-
} else {
|
|
101
|
-
hook = { kind: "agent", prompt: opts.prompt, ...pick };
|
|
102
|
-
}
|
|
103
|
-
await hookAdd({ hooks: storeOf(ctx), log: ctx.log }, triggerOf(opts), hook);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
splatesHook
|
|
107
|
-
.command("edit")
|
|
108
|
-
.use(repository({ needs: "tasks" }))
|
|
109
|
-
.description(
|
|
110
|
-
"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`",
|
|
111
|
-
)
|
|
112
|
-
.arg("id", z.string(), { description: "its number in `splates hook list`" })
|
|
113
|
-
.option("event", z.string(), {
|
|
114
|
-
default: "",
|
|
115
|
-
description:
|
|
116
|
-
"a task event to run on, as task.created, in place of what sets it off now",
|
|
117
|
-
})
|
|
118
|
-
.option("stage", z.string(), {
|
|
119
|
-
default: "",
|
|
120
|
-
description:
|
|
121
|
-
"only for tasks in these stages, comma separated, or `any`: backlog, ready, queued, running",
|
|
122
|
-
})
|
|
123
|
-
.option("lane", z.string(), {
|
|
124
|
-
default: "",
|
|
125
|
-
description:
|
|
126
|
-
"only for tasks in these lanes, by number, comma separated, or `any`",
|
|
127
|
-
})
|
|
128
|
-
.option("every", z.string(), {
|
|
129
|
-
default: "",
|
|
130
|
-
description: "run every so often, as 30m, 2h or 1d",
|
|
131
|
-
})
|
|
132
|
-
.option("cron", z.string(), {
|
|
133
|
-
default: "",
|
|
134
|
-
description:
|
|
135
|
-
'run on a five-field cron line, in this machine\'s time, as "0 9 * * 1-5"',
|
|
136
|
-
})
|
|
137
|
-
.option("agent", z.string(), {
|
|
138
|
-
default: "",
|
|
139
|
-
description: "the agent to run, as `splates agents` names it",
|
|
140
|
-
})
|
|
141
|
-
.option("model", z.string(), {
|
|
142
|
-
default: "",
|
|
143
|
-
description: "its model, with --agent (default: the agent's own)",
|
|
144
|
-
})
|
|
145
|
-
.option("effort", z.string(), {
|
|
146
|
-
default: "",
|
|
147
|
-
description: "its thinking level, with --agent (default: the agent's own)",
|
|
148
|
-
})
|
|
149
|
-
.option("prompt", z.string(), {
|
|
150
|
-
default: "",
|
|
151
|
-
description: "what the agent is told after the event",
|
|
152
|
-
})
|
|
153
|
-
.option("run", z.string(), {
|
|
154
|
-
default: "",
|
|
155
|
-
description: "a shell command to run in place of an agent",
|
|
156
|
-
})
|
|
157
|
-
.action(async (opts, ctx) => {
|
|
158
|
-
const { id, agent, model, effort, ...change } = opts;
|
|
159
|
-
const pick = await assigneeIn(ctx, agent, model, effort);
|
|
160
|
-
if (pick === null) {
|
|
161
|
-
fail(
|
|
162
|
-
"usage",
|
|
163
|
-
"an agent automation needs an agent: name one, or --run",
|
|
164
|
-
{},
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
await hookEdit({ hooks: storeOf(ctx), log: ctx.log }, id, {
|
|
168
|
-
...change,
|
|
169
|
-
...(pick === undefined ? {} : { pick }),
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
splatesHook
|
|
174
|
-
.command("remove")
|
|
175
|
-
.use(repository({ needs: "tasks" }))
|
|
176
|
-
.description(
|
|
177
|
-
"take away an automation, by its number in `splates hook list`, or every one on an event",
|
|
178
|
-
)
|
|
179
|
-
.arg("id", z.string(), {
|
|
180
|
-
description: "its number, or a task event, as task.created",
|
|
181
|
-
})
|
|
182
|
-
.action((opts, ctx) =>
|
|
183
|
-
hookRemove({ hooks: storeOf(ctx), log: ctx.log }, opts.id),
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
splatesHook
|
|
187
|
-
.command("pause")
|
|
188
|
-
.use(repository({ needs: "tasks" }))
|
|
189
|
-
.description("stop an automation running, keeping it to resume")
|
|
190
|
-
.arg("id", z.string(), { description: "its number in `splates hook list`" })
|
|
191
|
-
.action((opts, ctx) =>
|
|
192
|
-
hookPause({ hooks: storeOf(ctx), log: ctx.log }, opts.id, true),
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
splatesHook
|
|
196
|
-
.command("resume")
|
|
197
|
-
.use(repository({ needs: "tasks" }))
|
|
198
|
-
.description("let a paused automation run again")
|
|
199
|
-
.arg("id", z.string(), { description: "its number in `splates hook list`" })
|
|
200
|
-
.action((opts, ctx) =>
|
|
201
|
-
hookPause({ hooks: storeOf(ctx), log: ctx.log }, opts.id, false),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
splatesHook
|
|
205
|
-
.command("groomer")
|
|
206
|
-
.use(repository({ needs: "tasks" }))
|
|
207
|
-
.description(
|
|
208
|
-
"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",
|
|
209
|
-
)
|
|
210
|
-
.option("agent", z.string(), {
|
|
211
|
-
default: "",
|
|
212
|
-
description: "the agent it runs as, as `splates agents` names it",
|
|
213
|
-
})
|
|
214
|
-
.option("model", z.string(), {
|
|
215
|
-
default: "",
|
|
216
|
-
description: "its model (default: the agent's own)",
|
|
217
|
-
})
|
|
218
|
-
.option("effort", z.string(), {
|
|
219
|
-
default: "",
|
|
220
|
-
description: "its thinking level (default: the agent's own)",
|
|
221
|
-
})
|
|
222
|
-
.option("off", z.boolean(), {
|
|
223
|
-
default: false,
|
|
224
|
-
description: "turn it off",
|
|
225
|
-
})
|
|
226
|
-
.action(async (opts, ctx) => {
|
|
227
|
-
const pick = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
228
|
-
if (opts.off === (pick != null)) {
|
|
229
|
-
fail(
|
|
230
|
-
"usage",
|
|
231
|
-
"say how it runs with --agent, or turn it off with --off",
|
|
232
|
-
{},
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
await hookGroomer({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
splatesHook
|
|
239
|
-
.command("gardener")
|
|
240
|
-
.use(repository({ needs: "tasks" }))
|
|
241
|
-
.description(
|
|
242
|
-
"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",
|
|
243
|
-
)
|
|
244
|
-
.option("agent", z.string(), {
|
|
245
|
-
default: "",
|
|
246
|
-
description: "the agent it runs as, as `splates agents` names it",
|
|
247
|
-
})
|
|
248
|
-
.option("model", z.string(), {
|
|
249
|
-
default: "",
|
|
250
|
-
description: "its model (default: the agent's own)",
|
|
251
|
-
})
|
|
252
|
-
.option("effort", z.string(), {
|
|
253
|
-
default: "",
|
|
254
|
-
description: "its thinking level (default: the agent's own)",
|
|
255
|
-
})
|
|
256
|
-
.option("every", z.string(), {
|
|
257
|
-
default: "",
|
|
258
|
-
description: "run every so often, as 1d (default: as it was, or 7d)",
|
|
259
|
-
})
|
|
260
|
-
.option("cron", z.string(), {
|
|
261
|
-
default: "",
|
|
262
|
-
description:
|
|
263
|
-
'run on a five-field cron line, in this machine\'s time, as "0 9 * * 1"',
|
|
264
|
-
})
|
|
265
|
-
.option("off", z.boolean(), {
|
|
266
|
-
default: false,
|
|
267
|
-
description: "turn it off",
|
|
268
|
-
})
|
|
269
|
-
.action(async (opts, ctx) => {
|
|
270
|
-
const pick = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
271
|
-
if (opts.off === (pick != null)) {
|
|
272
|
-
fail(
|
|
273
|
-
"usage",
|
|
274
|
-
"say how it runs with --agent, or turn it off with --off",
|
|
275
|
-
{},
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
await hookGardener({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null, {
|
|
279
|
-
every: opts.every,
|
|
280
|
-
cron: opts.cron,
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
/** What sets the automation `splates hook add` adds off, refusing two triggers or none. */
|
|
285
|
-
function triggerOf(opts: TriggerOptions): Trigger {
|
|
286
|
-
const given = [opts.event, opts.every, opts.cron].filter(
|
|
287
|
-
(each) => each !== undefined && each !== "",
|
|
288
|
-
);
|
|
289
|
-
if (given.length !== 1) {
|
|
290
|
-
fail(
|
|
291
|
-
"usage",
|
|
292
|
-
"say what sets it off: a task event, --every or --cron, one of them",
|
|
293
|
-
{},
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
if (opts.event === undefined || opts.event === "") {
|
|
297
|
-
if (opts.stage !== "" || opts.lane !== "") {
|
|
298
|
-
fail(
|
|
299
|
-
"usage",
|
|
300
|
-
"--stage and --lane narrow a task event, not a schedule",
|
|
301
|
-
{},
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
return opts.every !== "" ? { every: opts.every } : { cron: opts.cron };
|
|
305
|
-
}
|
|
306
|
-
const filter: TaskFilter = {};
|
|
307
|
-
if (opts.stage !== "") {
|
|
308
|
-
filter.stages = stagesNamed(commaList(opts.stage));
|
|
309
|
-
}
|
|
310
|
-
if (opts.lane !== "") {
|
|
311
|
-
filter.lanes = lanesNamed(opts.lane);
|
|
312
|
-
}
|
|
313
|
-
const event = opts.event as TaskEventName;
|
|
314
|
-
return Object.keys(filter).length === 0 ? { event } : { event, filter };
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/** The options of `splates hook add` that say what sets it off. */
|
|
318
|
-
interface TriggerOptions {
|
|
319
|
-
event?: string;
|
|
320
|
-
every: string;
|
|
321
|
-
cron: string;
|
|
322
|
-
stage: string;
|
|
323
|
-
lane: string;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function storeOf(ctx: { storage: Storage }) {
|
|
327
|
-
return new HookStore(ctx.storage);
|
|
328
|
-
}
|
package/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { basename } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
LEGACY,
|
|
5
|
-
legacyWarnings,
|
|
6
|
-
systemPs,
|
|
7
|
-
} from "@stringsync/splates-server/config";
|
|
8
|
-
import { assets } from "@stringsync/splates-server/server";
|
|
9
|
-
import { NodeFs } from "webappwiz/system";
|
|
10
|
-
import { projectArgs, splates } from "./splates";
|
|
11
|
-
|
|
12
|
-
// The bin's old name, kept for one release.
|
|
13
|
-
if (basename(process.argv[1] ?? "") === LEGACY) {
|
|
14
|
-
legacyWarnings.once(LEGACY, "splates");
|
|
15
|
-
}
|
|
16
|
-
const ps = systemPs();
|
|
17
|
-
const { project, argv } = projectArgs(ps.args);
|
|
18
|
-
await splates.run({ fs: new NodeFs(), ps, assets, project }, argv);
|
package/infra.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import { commaList } from "@stringsync/splates-server/comma-list";
|
|
3
|
-
import {
|
|
4
|
-
ACCESS_ENVIRONMENT,
|
|
5
|
-
accessEnvironmentExists,
|
|
6
|
-
CoordinatorBox,
|
|
7
|
-
pulumiOrg,
|
|
8
|
-
writeInfraConfig,
|
|
9
|
-
} from "@stringsync/splates-server/coordinator";
|
|
10
|
-
import { fail } from "@stringsync/splates-server/exit";
|
|
11
|
-
import { infraConfigure } from "@stringsync/splates-server/infra/configure";
|
|
12
|
-
import { secretsEnvironmentUrl } from "@stringsync/splates-server/infra/secrets";
|
|
13
|
-
import { Projects } from "@stringsync/splates-server/projects";
|
|
14
|
-
import { repository } from "@stringsync/splates-server/records";
|
|
15
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
16
|
-
import { savedTunnelOf } from "@stringsync/splates-server/server";
|
|
17
|
-
import { FsStorage } from "@stringsync/splates-server/storage";
|
|
18
|
-
import { cli } from "webappwiz/cmd";
|
|
19
|
-
import { z } from "zod";
|
|
20
|
-
|
|
21
|
-
/** `splates server infra`, which `splates server` mounts. */
|
|
22
|
-
export const splatesInfra = cli<SplatesDeps>("infra").description(
|
|
23
|
-
"the Pulumi project that makes everything the server's box needs in AWS, Cloudflare and GitHub, which `splates server deploy` then installs splates on",
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
splatesInfra
|
|
27
|
-
.command("init")
|
|
28
|
-
.use(repository({ needs: "own" }))
|
|
29
|
-
.description(
|
|
30
|
-
"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). The secrets never pass through splates: it makes the Pulumi ESC environment splates/secrets with a placeholder for each (the Cloudflare token, a GitHub token per owner, the Anthropic key), which the stack imports and the user replaces on the Pulumi site; `splates setup` has a step for each saying how to get it. 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. Its runtime is bun, which runs the program's TypeScript itself. Commit the project but not Pulumi.<stack>.yaml, which holds your own accounts and ids and which the .gitignore it writes keeps out of git; init writes it again from what splates keeps. Then `bun add @stringsync/splates-server @pulumi/pulumi @pulumi/aws @pulumi/awsx @pulumi/cloudflare @pulumi/github @pulumi/tls @pulumi/random` there and `pulumi up`",
|
|
31
|
-
)
|
|
32
|
-
.arg("dir", z.string(), { description: "where the Pulumi project goes" })
|
|
33
|
-
.option("stack", z.string(), { default: "prod", description: "the stack" })
|
|
34
|
-
.option("host", z.string(), {
|
|
35
|
-
default: "",
|
|
36
|
-
description:
|
|
37
|
-
"the public host name, as in splates.example.com; the saved tunnel's by default",
|
|
38
|
-
})
|
|
39
|
-
.option("email", z.string(), {
|
|
40
|
-
default: "",
|
|
41
|
-
description:
|
|
42
|
-
"the email address Access lets in; the tunnel's Access application's by default",
|
|
43
|
-
})
|
|
44
|
-
.option("region", z.string(), {
|
|
45
|
-
default: "us-east-1",
|
|
46
|
-
description: "the AWS region",
|
|
47
|
-
})
|
|
48
|
-
.option("projects", z.string(), {
|
|
49
|
-
default: "",
|
|
50
|
-
description:
|
|
51
|
-
"comma-separated slug=owner/repo, each a project the box pushes trunk of; every project registered here with a GitHub origin by default",
|
|
52
|
-
})
|
|
53
|
-
.action(async (opts, ctx) => {
|
|
54
|
-
const listed = commaList(opts.projects).map((entry) => {
|
|
55
|
-
const [slug, repository] = entry.split("=");
|
|
56
|
-
if (!slug || !repository?.includes("/")) {
|
|
57
|
-
fail("usage", `--projects wants slug=owner/repo, not ${entry}`, {
|
|
58
|
-
entry,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return { slug, repository };
|
|
62
|
-
});
|
|
63
|
-
const org = await pulumiOrg(ctx.ps);
|
|
64
|
-
const home = new CoordinatorBox({ ps: ctx.ps, fs: ctx.fs }).home;
|
|
65
|
-
const { written, todo } = await infraConfigure({
|
|
66
|
-
dir: opts.dir,
|
|
67
|
-
stack: opts.stack,
|
|
68
|
-
host: opts.host,
|
|
69
|
-
email: opts.email,
|
|
70
|
-
region: opts.region,
|
|
71
|
-
projects: listed.length === 0 ? null : listed,
|
|
72
|
-
registered: async () =>
|
|
73
|
-
(await Projects.in(new FsStorage(home, ctx.fs), ctx.fs).list()).filter(
|
|
74
|
-
(project) => !project.gone,
|
|
75
|
-
),
|
|
76
|
-
creds: ctx.creds,
|
|
77
|
-
saved: savedTunnelOf(ctx),
|
|
78
|
-
// The sign-in `coordinator-access` made, once it is there.
|
|
79
|
-
...(org !== null && (await accessEnvironmentExists(ctx.ps, org))
|
|
80
|
-
? { environment: ACCESS_ENVIRONMENT }
|
|
81
|
-
: {}),
|
|
82
|
-
fs: ctx.fs,
|
|
83
|
-
ps: ctx.ps,
|
|
84
|
-
log: ctx.log,
|
|
85
|
-
fetch: ctx.fetch,
|
|
86
|
-
});
|
|
87
|
-
await writeInfraConfig(ctx.fs, home, {
|
|
88
|
-
dir: resolve(opts.dir),
|
|
89
|
-
stack: opts.stack,
|
|
90
|
-
});
|
|
91
|
-
ctx.log.info(
|
|
92
|
-
written.length === 0
|
|
93
|
-
? "already so: nothing written"
|
|
94
|
-
: `wrote ${written.join(", ")}`,
|
|
95
|
-
);
|
|
96
|
-
ctx.log.info(
|
|
97
|
-
`Pulumi.${opts.stack}.yaml stays out of git: it holds your own accounts and ids, and init writes it again from what splates keeps`,
|
|
98
|
-
);
|
|
99
|
-
for (const command of todo) {
|
|
100
|
-
ctx.log.info(`still to run in ${opts.dir}: ${command}`);
|
|
101
|
-
}
|
|
102
|
-
if (org !== null) {
|
|
103
|
-
ctx.log.info(
|
|
104
|
-
`paste each secret over its placeholder at ${secretsEnvironmentUrl(org)}: \`splates setup\` has a step for each saying how to get it`,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
});
|
package/lane.test.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { expect, it } from "bun:test";
|
|
2
|
-
import { Testing } from "@stringsync/splates-server/testing";
|
|
3
|
-
import { splates } from "./splates";
|
|
4
|
-
|
|
5
|
-
it("addresses permanent Backlog by name in both task and lane commands", async () => {
|
|
6
|
-
await using testing = await Testing.open();
|
|
7
|
-
testing.ps.cd(testing.root);
|
|
8
|
-
const deps = {
|
|
9
|
-
log: testing.log,
|
|
10
|
-
fs: testing.fs,
|
|
11
|
-
ps: testing.ps,
|
|
12
|
-
assets: testing.assets,
|
|
13
|
-
secrets: testing.secrets,
|
|
14
|
-
};
|
|
15
|
-
await splates.run(deps, [
|
|
16
|
-
"task",
|
|
17
|
-
"defer",
|
|
18
|
-
"later",
|
|
19
|
-
"--slug",
|
|
20
|
-
"later",
|
|
21
|
-
"--lane",
|
|
22
|
-
"backlog",
|
|
23
|
-
]);
|
|
24
|
-
const [task] = await testing.tasks.all();
|
|
25
|
-
expect(task?.lane, testing.out()).toBe(0);
|
|
26
|
-
testing.log.clear();
|
|
27
|
-
await splates.run(deps, ["lane", "show", "backlog", "--json"]);
|
|
28
|
-
expect(JSON.parse(testing.out())).toMatchObject({ lane: 0, name: "Backlog" });
|
|
29
|
-
});
|