@spooky-sync/cli 0.0.1-canary.33 → 0.0.1-canary.331

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/AGENTS.md ADDED
@@ -0,0 +1,213 @@
1
+ # `@spooky-sync/cli` (`spky`) — agent guide
2
+
3
+ ## What this package is
4
+
5
+ The sp00ky toolchain. A Rust binary (`spky`) plus a thin npm wrapper. It parses `.surql` schemas, emits typed `schema.gen.ts` (and `.dart`), runs migrations, manages buckets and API backends, drives the local dev environment, and orchestrates Sp00ky Cloud deployments.
6
+
7
+ ## Binary
8
+
9
+ ```
10
+ spky <subcommand> [flags]
11
+ ```
12
+
13
+ Installed via `npx @spooky-sync/cli` or globally as `spky`. The `bin` field in `package.json` is `spky` — *not* `sp00ky`.
14
+
15
+ ## Project layout it expects
16
+
17
+ ```
18
+ your-app/
19
+ ├── sp00ky.yml # config: schema path, generated outputs, backends, buckets
20
+ ├── schema/
21
+ │ └── schema.surql # source of truth — your domain model
22
+ ├── src/
23
+ │ └── schema.gen.ts # GENERATED — never hand-edit
24
+ └── migrations/ # GENERATED migrations; modified files are tracked by checksum
25
+ ```
26
+
27
+ `spky` finds `sp00ky.yml` in the current directory by default; pass `--config <path>` to override.
28
+
29
+ ## Running it as an agent: always pass `-y`
30
+
31
+ You are almost certainly running on a **pseudo-terminal**. The CLI sees a terminal, renders its
32
+ prompt, and waits forever for a keypress you cannot send. Pass `-y` / `--yes` (any subcommand, any
33
+ position; `SPKY_YES=1` also works) and nothing blocks:
34
+
35
+ - **Consent prompts** ("Apply pending migrations to this PRODUCTION database?", "Restart SurrealDB?",
36
+ "Overwrite?") are answered **yes**, and the answer is printed so your log shows what you agreed to.
37
+ - **Preference prompts** ("Initialize git?", "Create a backup before resetting?") take their
38
+ **default**, never a blanket yes.
39
+ - **Menus** in `spky dev` (schema drift, pending migrations) take their unattended path: continue
40
+ with drift, auto-apply pending migrations.
41
+ - **Guided flows** (login, project picker, billing) do not open. They stop and print the exact
42
+ command or env var to use instead, e.g. `SP00KY_CLOUD_PROJECT` or `slug:` in `sp00ky.yml`.
43
+
44
+ Two operations are **not** covered by `-y`, on purpose, because they cannot be undone:
45
+ `spky project destroy` and `spky backup reset`. They need `--confirm <project-slug>`, spelled
46
+ correctly. Do not reach for these unless the user asked for exactly that, by name.
47
+
48
+ Without `-y` and without a terminal, a consent prompt **refuses** rather than proceeding, and the
49
+ error names the flag. If you see "No terminal to ask on", that is this.
50
+
51
+ ## Subcommands an app developer/agent uses most
52
+
53
+ - **`spky generate` / `spky gen`** — read `sp00ky.yml`, parse all `.surql`, emit `schema.gen.ts` (and Dart equivalents per config). **Run this after every schema edit.**
54
+ - **`spky migrate create <name>`** — diff current schema against the last applied migration and emit a new `.surql` migration file.
55
+ - **`spky migrate apply`** — apply pending migrations against the configured database. `--fix-checksums` updates stored checksums for legitimately-modified migration files. `--mode` defaults to `mode` in `sp00ky.yml`. Against a self-hosted database pass `--endpoint <ssp or scheduler URL as SurrealDB reaches it> --secret <SPKY_AUTH_SECRET>`: that also installs the `$sp00ky_endpoint` / `$sp00ky_secret` params the sync events post to, without which every write to a synced table fails.
56
+ - **`spky migrate status`** — show pending vs applied vs modified-but-applied migrations.
57
+ - **`spky migrate fix [--fix-checksums]`** — repair schema drift / checksum mismatches.
58
+ - **`spky verify [--fix]`** — confirm SSP/scheduler snapshot matches upstream SurrealDB, and print the scheduler's own drift verdict (`/health/snapshot` `drift`). `--fix` re-clones the scheduler replica when its counts are off, otherwise forces every SSP to re-bootstrap. The scheduler runs the same count check itself at startup and after each snapshot drain, repairs a drifted table in place by default, and re-clones only when a repair cannot fix it (`SPKY_DRIFT_AUTO_RECLONE`). Tables added, changed or removed upstream are picked up by a running scheduler and SSP without a restart or re-clone.
59
+ - **`spky lint`** — validate `sp00ky.yml` and referenced files exist.
60
+ - **`spky dev [--apply-migrations] [--clean] [--verbose]`** — boots a local SurrealDB + SSP + scheduler stack via Docker. `--clean` wipes SSP/scheduler state but preserves user data in SurrealDB. Startup renders as step lines via `src/ui.rs` (indicatif/console; plain lines when not a TTY); after "ready" only ERROR/crash-looking lines (infra and app streams) show unless `--verbose` / `SPKY_VERBOSE=1`. A bare `logLevel` is scoped to sp00ky crates by `backend::scoped_rust_log`.
61
+ - **`spky init`** — scaffold a new sp00ky project in the current directory. (`spky create` is a removed alias that only prints a migration hint.)
62
+ - **`spky bucket add`** / **`spky api add`** — append a bucket or backend definition to `sp00ky.yml`.
63
+ - **`spky mcp`** — start the bundled `@spooky-sync/devtools-mcp` server (so AI assistants can introspect the running app). Its `token` / `tokens` / `revoke` / `install` subcommands manage credentials for the separate Sp00ky Cloud MCP server. A deployed scheduler also serves its own MCP server for operator actions; see `docs/reference/mcp.mdx`.
64
+ - **`spky admin list|add <user>|remove <user>`** — manage the `_00_admin` roster. Roster members can sign in to the scheduler's operator dashboard and administer feature flags. `add` takes an optional `--note`.
65
+
66
+ ## Deployment subcommands
67
+
68
+ These are top level, not under a `cloud` prefix. `spky cloud ...` was removed and now only
69
+ prints a migration hint.
70
+
71
+ `spky login | logout | deploy | release | status | stats | logs | restart | push | scale | backup | env | domain | link | team | billing | token | project | notice`.
72
+ See `spky <command> --help`. Most app code agents touch never need these.
73
+
74
+ Two that come up when a deployment misbehaves:
75
+
76
+ - **`spky restart [TARGET...] [--upgrade] [--clean] [--surreal] [--all-backends] [-y]`** —
77
+ recreate containers. Targets are roles (`db`, `scheduler`, `ssp`, `frontend`) or an app
78
+ name; with none it restarts the scheduler and SSPs. `--upgrade` pulls the latest scheduler
79
+ and SSP images, `--clean` wipes the scheduler's volume (replica and WAL, not SurrealDB
80
+ data). `-y` (the global flag, see above) confirms the SurrealDB prompt; without it and without
81
+ a terminal the restart is refused.
82
+ - **`spky migrate prod -y`** — apply pending migrations plus the internal Sp00ky schema to the
83
+ deployment, without a full deploy. `-y` confirms the production gate, which otherwise refuses
84
+ when there is no terminal. Set `SPKY_DB_HTTP_TIMEOUT_SECS` if the internal schema step times out.
85
+
86
+ ## Schema annotations the parser recognizes
87
+
88
+ In your `.surql` source, comment annotations attached to `DEFINE FIELD` / `DEFINE TABLE` change codegen output:
89
+
90
+ - `-- @crdt text` / `-- @crdt prosemirror` (above a `DEFINE FIELD ... TYPE string`), optionally with `-- @cursor`: a collaborative Loro field. `crdt_schema::expand` (run by every schema pipeline: server schema, events, internal schema, client schema) rewrites the field into a server-written plain-text mirror (`DEFAULT ''` plus a VALUE guard that keeps `$before` for any record-user write) and generates, per parent `P`: `P__crdt_log` (append-only edit log; create follows P's update rule and select P's select rule, both rebased through the `doc` link on the AST by `rebase_permission`; update/delete NONE; impersonation sessions may not create), `P__crdt_presence` (`@cursor` only), the `<field>__crdt` fold stamp on `P`, a cleanup event, and the shared `_00_crdt_doc` (canonical snapshots, SSP-only) and `_00_crdt_peer` (peer id ownership). It refuses a non-string type, a VALUE/ASSERT on the field, nested fields, relation tables, reserved names (`*__crdt_log`, `*__crdt_presence`, `_00_crdt_doc`, `*__crdt` fields) and the `surrealism` deploy mode. Clients edit through `useCrdtField`; the column itself is readable and filterable like any string. Design: `docs/crdt-v2.md`.
91
+ - `-- @parent` (suffix on `DEFINE FIELD ... TYPE record<...>`) — marks the column as the parent side of a relationship; written automatically from the auth context, never by client code.
92
+ - `-- @nosync` (above a `DEFINE TABLE`) — marks a table as server-only: it is omitted from generated types and relations (and any `record<...>` link pointing at it is dropped), no sync events are emitted for it, and the scheduler/SSP exclude it from snapshots and bootstrap. The table still lives in the main DB and is still backed up. The CLI bakes a `COMMENT 'sp00ky:nosync'` marker onto the server-side `DEFINE TABLE` so the runtime services detect it via `INFO FOR DB`. Distinct from `PERMISSIONS FOR select WHERE false`, which only locks reads — a permission-locked table is still synced.
93
+ - `-- @nosync` (above a `DEFINE FIELD`) — marks a single field server-only: omitted from generated types and from the client's local cache schema, omitted from sync event payloads, and omitted from the scheduler replica and SSP bootstrap row scans. **Not a read barrier**: a client's down-sync `SELECT` still returns the column over the wire, and it is only discarded on arrival (`cleanRecord`). For real secrecy use `PERMISSIONS FOR select WHERE false` on the field, or move it to a `@nosync` table.
94
+ - `-- @opaque` (above a `DEFINE FIELD`) — the field IS synced to the client (it stays in generated types and the local cache, flagged `opaque: true` on the column) but no server-side component stores the value. Intended for large blobs you render but never query on. Because nothing holds the value it cannot be evaluated: using it in `where`/`orderBy`/a join throws in the query builder and is rejected with a 400 at SSP registration, and a schema whose `PERMISSIONS` or `DEFINE INDEX` references one fails to build. Delivery works because sync payloads carry ids + versions, not field values — the client reads the row body straight from SurrealDB.
95
+
96
+ Both field-level exclusions (`@nosync`, `@opaque`) get `COMMENT 'sp00ky:opaque'` baked onto the server `DEFINE FIELD` (`schema_builder::add_opaque_field_markers`). The scheduler replica and the SSP bootstrap read that marker from `INFO FOR TABLE` and turn it into a `SELECT * OMIT ...` projection. Both halves are required: skipping a field from the ingest payload while the bootstrap still loads it makes the SSP circuit and the scheduler replica disagree about the row's key set permanently (the replica applies updates with `MERGE`, the circuit replaces the whole row), which shows up as an unfixable `spky verify` mismatch. A `@crdt` field is not excluded (its mirror is an ordinary synced column) unless it also carries `@opaque`; the generated log's `data` and presence `state` columns are `@opaque` with the marker baked in, because the internal-schema step re-applies those definitions without the marker pass.
97
+
98
+ Example:
99
+ ```sql
100
+ DEFINE TABLE thread SCHEMAFULL ...;
101
+
102
+ -- @crdt prosemirror
103
+ -- @cursor
104
+ DEFINE FIELD content ON TABLE thread TYPE string;
105
+
106
+ DEFINE FIELD author ON TABLE thread TYPE record<user>; -- @parent
107
+
108
+ -- @opaque
109
+ DEFINE FIELD preview_png ON TABLE thread TYPE option<bytes>;
110
+
111
+ -- @nosync
112
+ DEFINE TABLE audit_log SCHEMALESS;
113
+ ```
114
+
115
+ A descriptor must sit directly above its statement (no blank line between). One that attaches to nothing is warned about, not silently dropped (`annotations::warn_unattached_annotations`). A descriptor is the marker alone plus at most one value token (`-- @crdt text`); a longer comment that merely starts with a marker name is prose, not an annotation, and `-- @nosync true` is refused with a warning.
116
+
117
+ ## Docker dev apps (`type: docker`)
118
+
119
+ Besides `backend`/`frontend` apps, `sp00ky.yml` can declare `type: docker` apps —
120
+ containers `spky dev` runs alongside SurrealDB/SSP/scheduler on the
121
+ `sp00ky-dev-net` network (each reachable from the others by its app **name**, via
122
+ a `--network-alias`). Use `scope: devOnly` for local-only sidecars: never
123
+ deployed, and they skip the backend spec/method/deploy validation.
124
+
125
+ Fields:
126
+
127
+ - `image` (required) — image to run, e.g. `bluenviron/mediamtx:latest` or `golang:1.22`.
128
+ - `ports` — published to the host: `[1935, "8189/udp", "3000:8080"]` (a bare value maps the same port host:container; `/udp` suffix preserved).
129
+ - `args` — appended after the image (the container command), e.g. `["go", "run", "."]`.
130
+ - `env` — same forms as other apps (inline map / dotenv path / vault). User values **override** the auto-injected `SPKY_*` vars. `${PROJECT_DIR}` (the absolute dir of `sp00ky.yml`) is expanded in values.
131
+ - `volumes` — bind/volume mounts (`-v`), e.g. `["/var/run/docker.sock:/var/run/docker.sock", "${PROJECT_DIR}/../..:/src", "gomod:/go"]`. `${PROJECT_DIR}` is expanded in the host portion (Docker normalizes `..`).
132
+ - `workdir` — working directory inside the container (`-w`).
133
+ - `dependsOn` — names of other docker apps that must be **ready** before this one starts; `spky dev` starts apps in dependency order. Validated at config load — an unknown name, a self-dependency, or a **cycle** is a hard error (`spky lint` reports it).
134
+ - `healthcheck` — an HTTP path (e.g. `/health`) polled on the app's first published host port until it returns 200. Lets a dependency signal real readiness so `dependsOn` waits for "up", not just "container started". Without it, a dependency counts as ready once its container is running.
135
+
136
+ Containers run as `sp00ky-dev-<name>` with `--rm` and are killed on Ctrl-C.
137
+ `dependsOn`/`healthcheck` are `spky dev` concerns; the cloud deploy path ignores
138
+ them (and `cloudOnly` docker apps are skipped by `spky dev`).
139
+
140
+ Example — a relay built from source via `go run`, and a publisher that waits for it:
141
+ ```yaml
142
+ apps:
143
+ relay:
144
+ type: docker
145
+ scope: devOnly
146
+ image: golang:1.22
147
+ workdir: /src/apps/relay
148
+ args: ["go", "run", "."]
149
+ ports: [3670]
150
+ healthcheck: /health
151
+ volumes:
152
+ - "${PROJECT_DIR}/../..:/src"
153
+ - "gomod:/go"
154
+ publisher:
155
+ type: docker
156
+ scope: devOnly
157
+ image: golang:1.22
158
+ workdir: /src/apps/publisher
159
+ args: ["go", "run", "."]
160
+ dependsOn: [relay] # started only after relay's /health returns 200
161
+ volumes:
162
+ - "${PROJECT_DIR}/../..:/src"
163
+ - "gomod:/go"
164
+ ```
165
+
166
+ ## Sync transport (`sync.transport`)
167
+
168
+ `sync.transport: changefeed` changes what the schema pipeline emits, in three
169
+ places that must agree: `sp00ky::generate_sp00ky_events` keeps the
170
+ `_00_version` bookkeeping but drops the `http::post` (`post_ingest`),
171
+ `schema_builder::add_changefeed_clauses` puts `CHANGEFEED <retention> INCLUDE
172
+ ORIGINAL` on every synced `DEFINE TABLE` in the desired schema (after
173
+ `COMMENT`, before `PERMISSIONS`, the order SurrealDB renders, or `schema
174
+ diff` sees a phantom change forever), and `migrate::apply_internal_schema`
175
+ emits `ALTER TABLE IF EXISTS ... CHANGEFEED` for every synced user table after
176
+ the events (a user migration's `DEFINE TABLE OVERWRITE` without the clause
177
+ drops the feed; the ALTER puts it back on every deploy) plus `REMOVE EVENT
178
+ _00_dbsp_cleanup`. `backend::sync_settings_for(config_path)` is the single
179
+ reader of the setting. The scheduler/SSP get `SPKY_INGEST_TRANSPORT` through
180
+ `deployment.env` (cloud) or the dev launch specs. Retention must be in
181
+ SurrealDB's rendered form (`1d`, not `24h`): `valid_changefeed_retention`.
182
+
183
+ "Synced" means every user table that is not `@nosync`, **including `TYPE RELATION`
184
+ tables**: `schema_builder::table_takes_changefeed` is the one predicate, and it has
185
+ to agree with `Replica::discover_sync_tables`, which clones and drift-checks every
186
+ table that does not carry the `sp00ky:nosync` marker. Relations used to be left out
187
+ of the events and the feed while staying in the replica and the SSP bootstrap, so a
188
+ live query over an edge table never updated. An edge's `in`/`out` are implicit (not
189
+ in `table.fields`), so `sp00ky::relation_endpoints` adds them to the ingest payload.
190
+ The same marker is re-asserted next to the changefeed ALTER
191
+ (`schema_builder::nosync_alter_statements`), for the same reason.
192
+
193
+ Both halves are also applied at the moment a migration defines a table, not only by the
194
+ pass after it: `migrate::SyncIdentity::complete` rewrites each migration's `DEFINE TABLE`
195
+ with the clause and the marker as `LegacyEngine` applies it (`migrate::apply_completing`).
196
+ Without that, rows written between a migration and the internal-schema pass landed in a
197
+ table with no feed. The ephemeral schema-diff replay still uses the verbatim
198
+ `migrate::apply`, so a completed clause never reads as drift, and the recorded checksum
199
+ is always the committed file's.
200
+
201
+ ## Common gotchas
202
+
203
+ - **`schema.gen.ts` must be regenerated after every `.surql` change.** `spky generate`. CI typically asserts no drift.
204
+ - **Migrations are checksum-tracked.** Editing a previously-applied migration file won't silently re-run; `spky migrate status` flags it. Use `--fix-checksums` only when you're sure the change is semantically a no-op.
205
+ - **`sp00ky.yml` is the entry point.** The CLI never crawls for `.surql`; everything is wired explicitly through the config.
206
+ - **Generation modes matter.** The `--mode` flag (`singlenode`, `cluster`, `surrealism`) changes what the generated client connects to. Default is `singlenode` (HTTP to a single SSP). `surrealism` embeds the WASM stream processor in-browser.
207
+ - **Don't commit the bin output.** The Rust binary is built per-platform and shipped via the npm tarball under `dist/`.
208
+
209
+ ## Pointers
210
+
211
+ - Sync engine the generated client targets: `node_modules/@spooky-sync/core/AGENTS.md`
212
+ - Reactive UI bindings: `node_modules/@spooky-sync/client-solid/AGENTS.md`
213
+ - Live MCP introspection during dev: `node_modules/@spooky-sync/devtools-mcp/AGENTS.md`
package/README.md CHANGED
@@ -18,19 +18,19 @@ This package wraps a Rust-based code generator that parses SurrealDB `.surql` sc
18
18
 
19
19
  ```bash
20
20
  # Generate TypeScript types
21
- spooky --input schema.surql --output types.ts
21
+ spky --input schema.surql --output types.ts
22
22
 
23
23
  # Generate Dart types
24
- spooky --input schema.surql --output types.dart
24
+ spky --input schema.surql --output types.dart
25
25
 
26
26
  # Generate JSON Schema
27
- spooky --input schema.surql --output schema.json
27
+ spky --input schema.surql --output schema.json
28
28
 
29
29
  # Generate all formats at once
30
- spooky --input schema.surql --output output --all
30
+ spky --input schema.surql --output output --all
31
31
 
32
32
  # Specify format explicitly
33
- spooky --input schema.surql --output output.ts --format typescript
33
+ spky --input schema.surql --output output.ts --format typescript
34
34
  ```
35
35
 
36
36
  ### Programmatic API
@@ -95,7 +95,7 @@ cli/
95
95
 
96
96
  ## How It Works
97
97
 
98
- 1. The Rust binary (`spooky`) parses SurrealDB schema files and generates JSON Schema
98
+ 1. The Rust binary (`spky`) parses SurrealDB schema files and generates JSON Schema
99
99
  2. For TypeScript/Dart output, it uses `quicktype` to convert JSON Schema to the target language
100
100
  3. The TypeScript wrapper (`src/index.ts`) spawns the Rust binary as a child process
101
101
  4. Vite bundles the TypeScript wrapper for distribution