@stringsync/splates 0.0.0-stage → 0.0.46

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stringsync LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,421 @@
1
- # Temporary Holding Version
1
+ # Spinning Plates
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [spinningplates.tech](https://spinningplates.tech) · command: `splates`
4
+
5
+ Run many coding agents on one repository at once. Each works in its own git
6
+ worktree and lands its change on `main` itself, without pull requests.
7
+
8
+ ```bash
9
+ bunx @stringsync/splates init
10
+ ```
11
+
12
+ Run it in your repository and commit what it writes: the splates skill, which it
13
+ installs with [`skills`](https://skills.sh) from this release's tag into
14
+ `.agents/skills` for Codex and most agents, linked from `.claude/skills` for
15
+ Claude Code, and `skills-lock.json`. `splates update` moves it to a newer release.
16
+ Without splates, `bunx skills add stringsync/splates#v<release> --skill splates`
17
+ installs the same skill. From then on, your agents start each change with
18
+ `splates add` and land it with `splates merge`.
19
+
20
+ Spinning Plates was arbor (`@webappwiz/arbor`). In a project that used it, run
21
+ `bunx @stringsync/splates update`: it installs the splates skill in place of arbor's and
22
+ renames `arbor.config.ts`. The next `splates server` moves
23
+ `~/.arbor` and each repository's `.git/arbor` to their new names, leaving links
24
+ behind. For one release, `arbor` still runs splates, and the `ARBOR_*`
25
+ variables, `ARBOR.md` and the `[ARBOR …]` directives are still read.
26
+
27
+ Contributions are welcome: see [Contributing](CONTRIBUTING.md).
28
+
29
+ ## How it works
30
+
31
+ At its core, splates is a consistent way to coordinate worktrees. Each change is
32
+ a task with its own branch and worktree, and every agent moves tasks through
33
+ the same few commands, so agents never edit the same checkout and always know
34
+ what the others are doing. There is no daemon and no orchestrator: splates is a
35
+ CLI over git, and any agent in any terminal can use it. `splates --help` lists
36
+ every command. A bare `splates` prints help too; start the web page with
37
+ `splates server`. The `splates dev` namespace belongs only to this repository
38
+ and is absent from the published CLI.
39
+
40
+ **Merging.** `splates merge` rebases the task onto `main`, runs your checks
41
+ (`preMerge` in `splates.config.ts`) on the result, fast-forwards `main`, and
42
+ removes the worktree. A conflict or a failing check goes back to the agent that
43
+ made the change, which fixes it and merges again. A check that writes a JUnit
44
+ XML report to `$SPLATES_TEST_REPORT` (`bun test --reporter=junit
45
+ --reporter-outfile="$SPLATES_TEST_REPORT"`) gets a todo for each test slower
46
+ than 5 seconds.
47
+
48
+ **Heavy jobs.** Hooks (`postCheckout`, `postRewrite`, `preMerge`) run in one
49
+ of the machine's heavy-job slots, two by default (`SPLATES_HEAVY_JOBS` sets
50
+ how many), so several agents testing at once queue rather than run the
51
+ machine out of memory. `splates heavy -- <command>` runs anything else as
52
+ heavy, such as a test suite an agent runs mid-task, in a slot too.
53
+
54
+ **Pushing.** With `remote: "origin"` in `splates.config.ts`, each task's
55
+ branch goes to that remote as `splates/<task>`: after every commit an agent
56
+ makes under `splates server`, at `splates checkpoint`, escalation and handoff,
57
+ and it is deleted once the task merges or is removed. `splates checkpoint`
58
+ commits uncommitted work first, as a `WIP: checkpoint` commit squashed before
59
+ merging. A branch that tracks `SPLATES.md` is never pushed or merged. With the
60
+ GitHub App's id and private key kept as the creds `SPLATES_GITHUB_APP_ID` and
61
+ `SPLATES_GITHUB_APP_KEY`, pushes to github.com use an hour-long token the App
62
+ mints for that repository's contents alone; otherwise, the machine's own git
63
+ credentials. Without `remote`, nothing is pushed.
64
+
65
+ With `remote`, `main` is the remote's: `splates merge` fetches it, rebases
66
+ onto it, runs the checks, pushes the tested `splates/<task>`, and asks
67
+ `splates server`, the coordinator, to move `main` there. The coordinator alone
68
+ pushes `main`, with its machine's own git credentials, and only when the
69
+ remote's `main` is still the commit the checks ran on; then it fast-forwards
70
+ its own `main` checkout. A merge refuses while the local `main` holds commits
71
+ the remote lacks, until they are pushed. Protect `main` with a ruleset that
72
+ lets only the coordinator's identity update it, and every tag with one, so the
73
+ App's tokens, which workers push with, reach `splates/*` alone.
74
+
75
+ Setup's GitHub pushing area walks through it for the project it is opened
76
+ from (`splates setup` in its repository, or the page on that project), each
77
+ step checked against GitHub:
78
+
79
+ 1. `github-app`: [make a GitHub App](https://github.com/settings/apps/new)
80
+ with Webhook off and Repository permissions **Contents: Read and write**
81
+ alone, and keep its App ID and a private key it generates as the device
82
+ creds above.
83
+ 2. `github-install`: install it on the project's repository alone.
84
+ 3. `github-ruleset`: in the repository's **Settings → Rules → Rulesets**,
85
+ two active rulesets that restrict creations, updates and deletions, with
86
+ only **Repository admin** allowed to bypass them, the role of the account
87
+ the coordinator pushes `main` with: a branch ruleset on **All branches**
88
+ but `splates/*`, and a tag ruleset on **All tags**, since the App's
89
+ Contents permission would let a token push or move a release tag. GitHub
90
+ has no token that pushes branches but not tags. Or, with your own `gh`
91
+ sign-in, once for each body:
92
+
93
+ ```sh
94
+ gh api --method POST repos/<owner>/<repo>/rulesets --input - <<'JSON'
95
+ {"name":"splates","target":"branch","enforcement":"active",
96
+ "conditions":{"ref_name":{"include":["~ALL"],"exclude":["refs/heads/splates/*"]}},
97
+ "rules":[{"type":"creation"},{"type":"update"},{"type":"deletion"}],
98
+ "bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}]}
99
+ JSON
100
+ gh api --method POST repos/<owner>/<repo>/rulesets --input - <<'JSON'
101
+ {"name":"splates-tags","target":"tag","enforcement":"active",
102
+ "conditions":{"ref_name":{"include":["~ALL"],"exclude":[]}},
103
+ "rules":[{"type":"creation"},{"type":"update"},{"type":"deletion"}],
104
+ "bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}]}
105
+ JSON
106
+ ```
107
+
108
+ 4. `github-push`: add `remote: "origin"` to `splates.config.ts` and merge it.
109
+
110
+ **Escalating.** When an agent needs you (a decision, access, a review, or
111
+ merges that keep failing), it runs `splates escalate`: it stops, leaves its
112
+ worktree as it is, and writes down its questions. You answer, and it picks up
113
+ where it left off.
114
+
115
+ ## The harness
116
+
117
+ `splates server` is an optional page for running agents and answering them, on
118
+ http://localhost:4269.
119
+
120
+ ```bash
121
+ bunx @stringsync/splates server
122
+ ```
123
+
124
+ Run it from anywhere: it serves every splates project on this machine. Its
125
+ project picker switches between them, and adds a repository by its path,
126
+ running `splates init` there and asking before it commits what that wrote.
127
+
128
+ ### Parallel agents
129
+
130
+ Start as many conversations as you like, each in a task of its own.
131
+
132
+ ![Several agents working at once](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/parallel.png)
133
+
134
+ ### Questions and demos
135
+
136
+ Agents ask as they go instead of guessing. Each question comes with its
137
+ choices, the one the agent would pick, and a screenshot of each when there is
138
+ something to see. Answer in a click.
139
+
140
+ ![Questions with a screenshot per choice](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/questions.png)
141
+
142
+ When work is ready, the agent hands you a demo: steps to try, what you should
143
+ see, and a checklist to tick off before you merge or ask for changes.
144
+
145
+ ![A demo to try before merging](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/demo.png)
146
+
147
+ ### Annotate any image
148
+
149
+ Mark up any image, one you upload or one an agent shows you. Draw a box or an
150
+ arrow, pin a note to each, and send it back.
151
+
152
+ ![An agent's screenshot, marked up](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/annotate.png)
153
+
154
+ ### Remote access
155
+
156
+ To open the page from another machine, put it behind a Cloudflare tunnel and
157
+ Cloudflare Access, which decides who may enter. Set it up once per machine:
158
+
159
+ ```bash
160
+ bunx @stringsync/splates tunnel setup splates.example.com --email me@example.com
161
+ bunx @stringsync/splates creds wait SPLATES_CLOUDFLARE_API_TOKEN
162
+ bunx @stringsync/splates tunnel setup splates.example.com --email me@example.com
163
+ bunx @stringsync/splates server
164
+ ```
165
+
166
+ `tunnel setup` makes, through Cloudflare's API, a tunnel, the DNS record
167
+ routing the host to it, and an Access application letting in only those
168
+ emails, and saves them: the host, team and AUD tag in
169
+ `~/.splates/tunnel.json`, the tunnel's token in the system's secret store (the
170
+ Keychain on macOS). From then on a bare `splates server` runs the tunnel with
171
+ `cloudflared`, no `cloudflared login` needed. Run again, it changes only what
172
+ differs, such as the emails. The first run has no API token yet, so it
173
+ requests one on the Creds page of `splates server`, with how to make it; paste it
174
+ there, and run it again. An account with no Zero Trust organization gets one
175
+ with `--team <name>`, for `<name>.cloudflareaccess.com`. `splates tunnel status`
176
+ shows what is saved, and `splates tunnel remove` deletes it all again: the tunnels
177
+ of the machine and of every slot, their DNS records, the Access applications
178
+ and their policy, and the saved files and tunnel tokens. The API token stays.
179
+
180
+ To run several at once, as when trying changes to splates from a few worktrees,
181
+ give each its own slot:
182
+
183
+ ```bash
184
+ bunx @stringsync/splates server --slot
185
+ ```
186
+
187
+ `--slot` claims a short animal word for the checkout it runs in, as `fox`, and
188
+ serves it at `https://fox.<zone>`, on the zone `tunnel setup` used, behind the
189
+ same Access policy, so a phone opens each by name. A task's worktree keeps its
190
+ word until the task merges or is removed, which stops its server and gives the
191
+ word back; run again, `--slot` stops the tree's last server first. It refuses
192
+ a splates repository's main tree, which every agent shares. `splates slots list`
193
+ shows who holds each word, and `splates slots prune` frees those no tree owns any
194
+ more, stopping their servers. A word's tunnel and DNS record are made on its first
195
+ claim and kept, and one wildcard Access application covers every slot. A slot
196
+ keeps its own projects, roster and tunnel in `~/.splates/slots/<word>`, apart
197
+ from yours, and starts in a throwaway project there, unless that home
198
+ registers the project it is run in (`SPLATES_HOME=~/.splates/slots/fox splates
199
+ init`). The page shows the slot's emoji beside the project's name.
200
+
201
+ Or give the tunnel and the Access application by hand, over what is saved:
202
+
203
+ ```bash
204
+ bunx @stringsync/splates server --tunnel splates.example.com \
205
+ --access-team my-team --access-aud <the application's AUD tag>
206
+ ```
207
+
208
+ `--tunnel` runs `cloudflared tunnel run` against the page for as long as the
209
+ server lives (`--tunnel-name` picks the named tunnel, else cloudflared's own
210
+ config does), and is refused without both Access flags. Requests to that host
211
+ must carry a verified Access token; with one, conversations and terminals
212
+ work too.
213
+
214
+ To use another tunnel, give `splates.config.ts` a `tunnel`, any object that
215
+ implements `TunnelProvider`:
216
+
217
+ ```ts
218
+ import type { ConfigFile } from "@stringsync/splates-server/config";
219
+ import type { TunnelProvider } from "@stringsync/splates-server/tunnel";
220
+
221
+ const myTunnel: TunnelProvider = {
222
+ // throw, saying what to do, unless the tunnel can run
223
+ async ready() {},
224
+ // carry route.host to route.url until the returned resource is disposed
225
+ run: ({ host, url, log }) => {
226
+ const child = Bun.spawn(["my-tunnel", "--host", host, "--to", url]);
227
+ return { disposeAsync: async () => void child.kill() };
228
+ },
229
+ };
230
+
231
+ export default { tunnel: myTunnel } satisfies ConfigFile;
232
+ ```
233
+
234
+ `--tunnel <host>` then runs yours instead of cloudflared. Access is still
235
+ required, since splates verifies Cloudflare Access tokens whatever carries the
236
+ traffic, and `--tunnel-name` is refused beside a provider of your own. One
237
+ that is not ready, or fails to start, is logged and skipped: the page keeps
238
+ serving on this machine.
239
+
240
+ ### Worker providers
241
+
242
+ To have splates make workers when agents queue for want of a slot, give
243
+ `splates.config.ts`, in the repo `splates server` starts in, a
244
+ `workerProvider`, any object that implements `WorkerProvider`:
245
+
246
+ ```ts
247
+ import type { ConfigFile } from "@stringsync/splates-server/config";
248
+ import type { WorkerProvider } from "@stringsync/splates-server/workers";
249
+
250
+ const myCloud: WorkerProvider = {
251
+ // the machines it makes, with what each costs you per hour
252
+ types: async () => [
253
+ { name: "small", platform: "linux", slots: 4, dollarsPerHour: 0.05 },
254
+ ],
255
+ // build an image by running recipe.script as root on a fresh machine
256
+ snapshot: async (recipe) => ({ id: "img-1", version: recipe.version, builtAt: Date.now() }),
257
+ // boot spec.snapshot and run spec.boot on it, as the `splates` user
258
+ create: async (spec) => ({ id: spec.id, type: spec.type }),
259
+ destroy: async (id) => {},
260
+ // the machines it made that still exist
261
+ list: async () => [],
262
+ };
263
+
264
+ export default { workerProvider: myCloud } satisfies ConfigFile;
265
+ ```
266
+
267
+ A made worker boots from a snapshot splates asks for when there is none, and
268
+ again when splates's version changes or the snapshot is a week old; it holds
269
+ bun, git, the browser and splates, and no credentials. Its boot command spends
270
+ a single-use join token, dialing the page at its tunnel host, so the server
271
+ needs a tunnel. Placement makes one only once agents other agents started are
272
+ queued, and only as far as the worker budget allows; none is kept warm.
273
+ `splates worker add --provider [--type <name>]` makes one by hand, past the
274
+ rate cap, and `splates worker remove` destroys its machine.
275
+
276
+ The worker budget is in dollars, at each type's list price, so every figure is
277
+ an estimate: a rate cap on what the made workers running at once cost an hour
278
+ ($0.25/h by default), and limits on what they cost in a day ($5) and a month
279
+ ($75), from the start of each in local time. Past the rate cap no worker is
280
+ made, and the queue says which limit holds it, as `rate cap $0.25/h reached`.
281
+ Once a day's or month's limit is spent, made workers take nothing new, their
282
+ agents are asked to stop at a checkpoint, and each is destroyed once its last
283
+ agent ends. Each limit warns at 80%, 90% and 100%, once in its window, by Web
284
+ Push and on the page's header. Agents' own API spend is not counted here.
285
+
286
+ ```bash
287
+ bunx @stringsync/splates budget show
288
+ bunx @stringsync/splates budget set day 10 # or `default`
289
+ bunx @stringsync/splates budget raise hour 0.1 --for 1d
290
+ bunx @stringsync/splates budget end 1 # a raise, by its number
291
+ ```
292
+
293
+ ### Task ports
294
+
295
+ Any port a task's processes listen on, in its worktree or under its agent, is
296
+ found and forwarded as VS Code forwards ports, through the worker that runs
297
+ it. Save a wildcard base host for this machine to open each one anywhere,
298
+ behind the same Access application:
299
+
300
+ ```bash
301
+ bunx @stringsync/splates server --ports-host ports.example.com
302
+ ```
303
+
304
+ `https://<task>-<port>.ports.example.com` then reaches that task's server,
305
+ seen there as `localhost:<port>`, and only with a verified Access token;
306
+ routing `*.ports.example.com` to `splates server`, its DNS and TLS, are yours.
307
+ `--ports-host none` forgets the base. `splates ports [task]` lists each port
308
+ with its remote and local links, as the task's dialog on the page does.
309
+
310
+ ### Stats
311
+
312
+ As each task merges or is removed, splates writes down how it went in
313
+ `~/.splates/outcomes.jsonl`: how many times it came to you with questions or for
314
+ a review, how many merges failed and why, how many of the agent's picks you
315
+ overturned or took with words added, how long it waited on you, why it was
316
+ removed, and the agent and model behind it. The Stats page sums it over the
317
+ last 6 hours, day, 3 days or 7 days, cut into bars of half an hour to six
318
+ hours, or over all time a bar a week, by model, project and system, so you can tell whether tasks are landing
319
+ with fewer questions as your prompts and setup change. Its Act on cards
320
+ compare each figure with the as long a span before, saying whether it got
321
+ better or worse, and say what to change when one stands out. Fixed rules over
322
+ the counts make those calls, not a model, so the same outcomes always read
323
+ the same.
324
+
325
+ ### Optional OpenCode ACP agent
326
+
327
+ Open **Settings → Setup** and visit the optional OpenCode steps. Check this
328
+ machine, install if needed, and connect an existing Go/Go Plus account through
329
+ the native masked key prompt. The supported CLI exposes the V2 standalone login,
330
+ auth listing and metadata APIs; unsupported installations show update guidance.
331
+ No key belongs in chat, and splates never reads or exports native credential files.
332
+
333
+ Refresh the Go catalog, choose exact main and auxiliary models, review the
334
+ configuration, and confirm **Use balance** is off in Console before applying.
335
+ The save updates the running server's roster immediately. Claude/Codex entries,
336
+ custom fields, model descriptions and defaults are preserved.
337
+
338
+ The final **Check configuration** step rereads the saved recipe, including after
339
+ a server restart, and checks it for the current project's main directory. It
340
+ checks installation, stored authentication, ACP initialize, both model IDs and
341
+ effective agent configuration. It sends no prompt. Project OpenCode configuration
342
+ is disabled only for this launch recipe; global files remain untouched. New
343
+ custom or managed configuration that conflicts with the recipe requires repair.
344
+
345
+ **New OpenCode conversation** repeats that check, explicitly selects the saved
346
+ main model and opens an empty conversation in the displayed directory. Its first
347
+ message is your decision. Configuration readiness does not establish successful
348
+ inference, entitlement, quota or billing. Console confirmation is your assertion;
349
+ splates cannot verify or change that setting. Revisit/repair returns to the existing
350
+ steps. Disable removes only the wizard-owned roster entry for new conversations;
351
+ existing conversations and native authentication remain.
352
+
353
+ #### Advanced manual reference
354
+
355
+ Manual roster editing remains available for custom launches; use the supported
356
+ V2 recipe and inspection commands in [the setup contracts](../../designs/opencode-acp.md#374-readiness-and-completion).
357
+ A plain `opencode acp` entry or custom command does not establish wizard readiness.
358
+ Custom commands are never replaced or removed automatically. Avoid the older V1
359
+ `small_model`/provider recipe: it does not pin every V2 auxiliary/custom agent.
360
+
361
+ Splates requires the full, case-sensitive model ID returned in ACP settings for
362
+ OpenCode. Missing choices or a different confirmed provider/model stop the turn;
363
+ it never substitutes another provider. Refresh your editable catalog from
364
+ OpenCode's offered models when it changes. In Choosing agents and models,
365
+ choose OpenCode from New, then select the model in the composer (Settings on a
366
+ phone). Go choices show “OpenCode Go” and their full `opencode-go/...` ID; models
367
+ with the same display name remain distinct across providers. ACP config updates
368
+ replace the offered options. A removed selection is shown as unavailable until
369
+ you explicitly choose a replacement; it is never silently changed to the current
370
+ agent default. OpenCode choices do not change Claude/Codex saved settings.
371
+ Usage provenance and comparisons are tracked in #354.
372
+
373
+ OpenCode owns its native login and tools. Splates neither imports its credentials
374
+ nor exports OS-store secrets to it. Recovery uses advertised resume support, or
375
+ advertised load with history replay suppressed. An imported CLI session still
376
+ replays once. OpenCode recovery uses its original absolute directory; if that
377
+ directory is gone, restore it or explicitly start a new conversation. Unsupported
378
+ recovery reports an error. Without steering support, messages queue for the next
379
+ turn. Client file and terminal capabilities are not advertised.
380
+
381
+ Compatibility as of 2026-10-17: this integration is verified with synthetic ACP
382
+ fixtures, not a recorded OpenCode executable. `opencode` was absent from the
383
+ implementation environment's PATH, so no executable version or live login was
384
+ verified and no minimum supported version is claimed. Production acceptance
385
+ requires recording a version and sanitized handshake separately; no paid smoke
386
+ test was run. See [the experiment plan](../../designs/opencode-acp.md) for opt-in setup,
387
+ provider restrictions, and the separate paid-overflow control. Fixture checks
388
+ never establish account entitlement or remaining quota.
389
+
390
+ ### OpenCode experiment usage
391
+
392
+ OpenCode's Usage popover shows the last dispatched requested/confirmed full model
393
+ IDs, measured first-text and elapsed latency, and parent-only per-turn ACP token
394
+ counts. Missing fields read **Unknown**, including optional cache counts. Reported
395
+ USD cost is a cumulative session observation, separate from unknown additional
396
+ charges; it is not a subscription invoice. Quota and mixed-model remaining
397
+ allowance stay unknown: consult OpenCode Console and keep **Use balance** disabled.
398
+ Legacy task cost/token aggregates exclude these differently scoped observations.
399
+
400
+ See [the manual comparison recipe](../../designs/opencode-acp.md#354-implementation-and-manual-comparison-record)
401
+ for identical baseline files, a results worksheet, provenance and the authorization
402
+ gate. Fixtures need no production login. Live executable compatibility remains
403
+ unverified and no real comparison was run. `splates dev fix --code-only` disables
404
+ both Scry model roles and avoids opening credential stores; model-dependent rules
405
+ are reported as unchecked. `splates dev test` runs the local fixture suites.
406
+
407
+ ### OpenCode setup wizard (optional)
408
+
409
+ Open Settings → Set up splates → OpenCode to check installation, explicitly install
410
+ the official CLI with npm, and connect an existing Go/Go Plus account through
411
+ OpenCode's masked native key prompt. Return and choose Check again to inspect
412
+ native credential metadata and negotiate ACP without sending a model prompt.
413
+ Older native login interfaces show recovery instructions. No keys belong in an
414
+ agent conversation. The native controls currently require macOS or Linux.
415
+
416
+ Installation/connection is the first part of this wizard. Model selection and
417
+ configuration (#373), then final conversation readiness (#374), remain separate
418
+ lane steps. Until those land, this step does not add an agent roster entry. A
419
+ stored credential and ACP handshake do not verify subscription access, billing,
420
+ model availability or inference. See the [setup contract](../../designs/opencode-acp.md#372-setup-wizard-contract-lane-53-step-1)
421
+ for exact files and supported interfaces.
package/artifact.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { SplatesDeps } from "@stringsync/splates-server/server";
2
+ /** `splates artifact`, which the root cli mounts after `splates attach`. */
3
+ export declare const splatesArtifact: import("webappwiz/cmd").Cli<SplatesDeps, SplatesDeps>;
package/budget.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { SplatesDeps } from "@stringsync/splates-server/server";
2
+ /** `splates budget`, which the root cli mounts beside `splates worker`. */
3
+ export declare const splatesBudget: import("webappwiz/cmd").Cli<SplatesDeps, SplatesDeps>;
@@ -0,0 +1,3 @@
1
+ import { type SplatesDeps } from "@stringsync/splates-server/server";
2
+ /** `splates coordinator`, which the root cli mounts after `splates budget`. */
3
+ export declare const splatesCoordinator: import("webappwiz/cmd").Cli<SplatesDeps, SplatesDeps>;
package/creds.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { SplatesDeps } from "@stringsync/splates-server/server";
2
+ /** `splates creds`, which the root cli mounts after `splates lane`. */
3
+ export declare const splatesCreds: import("webappwiz/cmd").Cli<SplatesDeps, SplatesDeps>;
package/flags.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { type AgentPick, Offered, type RosterAgent } from "@stringsync/splates-server/models";
2
+ import type { Repository } from "@stringsync/splates-server/records";
3
+ import type { SplatesDeps } from "@stringsync/splates-server/server";
4
+ import { SavedTunnel } from "@stringsync/splates-server/tunnel";
5
+ import { z } from "zod";
6
+ /** A place in the todo list, 1 at the top; absent leaves it to the command. */
7
+ export declare const position: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
8
+ /**
9
+ * The agent `--agent`, `--model` and `--effort` name off the roster:
10
+ * undefined when none is given, null for `--agent none`.
11
+ */
12
+ export declare function assigneeIn(deps: SplatesDeps, agent: string, model: string, effort: string): Promise<AgentPick | null | undefined>;
13
+ /**
14
+ * `agent` on `model` off `agents`, at the thinking level `effort` names
15
+ * among those it offers, each left out when empty.
16
+ */
17
+ export declare function pickIn(deps: SplatesDeps, agents: RosterAgent[], agent: string, model: string, effort: string, data?: Record<string, unknown>): Promise<AgentPick>;
18
+ /**
19
+ * `{ effort }` for the thinking level `effort` names among those `agent`
20
+ * offered when last started, or nothing when it is empty.
21
+ */
22
+ export declare function effortIn(deps: SplatesDeps, agent: string, effort: string, data?: Record<string, unknown>): Promise<{
23
+ effort?: string;
24
+ }>;
25
+ /** The settings each agent offered when last started, from any project. */
26
+ export declare function offeredOf({ ps, fs }: SplatesDeps): Offered;
27
+ /** The tunnel each slot's home may have saved, for every word of the pool. */
28
+ export declare function slotTunnelsOf({ ps, fs, secrets, }: SplatesDeps): SavedTunnel[];
29
+ /** `--lane 3`, `new` or `none` as a lane; absent leaves it to the command. */
30
+ export declare function laneIn(raw: string): number | "new" | null | undefined;
31
+ /** `--todo 3,5` as ids, refusing anything that is not one. */
32
+ export declare function todoIds(raw: string): number[];
33
+ /**
34
+ * Which task a command that takes no task name is about, so the journal can
35
+ * name it. `merge` and `escalate` read it off the current branch.
36
+ */
37
+ export declare function here({ service, git, ps, project, }: SplatesDeps & Repository): Promise<string | null>;
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};