@samva/cli 0.3.2 → 0.5.0

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.
Files changed (4) hide show
  1. package/AGENTS.md +30 -8
  2. package/README.md +125 -36
  3. package/dist/bin.mjs +40023 -15220
  4. package/package.json +8 -9
package/AGENTS.md CHANGED
@@ -4,26 +4,45 @@ Use `samva --help` and `samva <command> --help` as the command contract. For aut
4
4
  `SAMVA_API_KEY`, `--json` for one bounded result, and `--jsonl` with `--all` for streamed lists.
5
5
  Machine-readable results go to stdout; warnings and typed errors go to stderr.
6
6
 
7
- `samva templates export --dir <dir> --out <dir>` renders every local template's fixtures to HTML
8
- and text with the asset manifest, using no Samva credential; `--asset-base <origin>` is for an
7
+ `samva templates export --out <dir>` renders every local template's fixtures to HTML
8
+ and text with the asset manifest, needing a Samva credential only to resolve a live `samva:brand`; `--asset-base <origin>` is for an
9
9
  export that will be delivered, which cannot carry a relative reference.
10
10
  `samva templates check --watch` is the continuous authoring gate. Human mode prints observations;
11
11
  machine watch output uses `--jsonl`. `--json` remains one-shot.
12
12
 
13
+ The developer loop for a template project is `samva templates dev` (the local visual editor, no
14
+ `vite.config.ts`), `check` (type-check, compile, and render every fixture), `render <template>
15
+ --fixture <name>` (subject, preheader, and plain-text body; `--format html`, `--channel sms|whatsapp`),
16
+ and `snapshot <template> --fixture <name>` (desktop and mobile PNGs, light and dark, in
17
+ `.samva/snapshots`, needing `playwright` in the project or a system Chrome). With `--json`, every
18
+ finding carries `code`, `severity`, `message`, `file`, `line`/`column`, `range`, and, where the
19
+ compiler has them, `fix` and a machine-applicable `edit`. Templates live in `templates/` and
20
+ `emails/`; a template is a `.tsx` file that default-exports `defineTemplate`, and the compiler reads
21
+ it without running it.
22
+
13
23
  `--publication-id` and `--input-contract-id` on `email send`, `scheduled-messages create`, and
14
24
  `campaigns create`/`update` choose which publication a template send renders: one exact publication,
15
25
  or the newest publication that still validates input identically. They are mutually exclusive and
16
26
  each requires `--template-id`, because a reference is a statement about one template's history.
17
27
 
18
- `samva templates workspace <open|diff|patch|check|fixture|save|history|publish>` is the
28
+ `samva templates workspace <open|diff|patch|add-asset|check|fixture|save|history|publish>` is the
19
29
  hosted project workspace over the CLI, for a caller with no clone: an agent in a sandbox, a CI job,
20
30
  a script. It reaches the same operations the hosted MCP tools reach, so an agent can start a journey in one
21
- and finish it in the other. Every subcommand prints its complete
31
+ and finish it in the other; `add-asset` alone has no MCP twin, because a chat client holds no file
32
+ bytes to send. Every subcommand prints its complete
22
33
  result as JSON because these outputs — revisions, digests, diagnostics — are inputs to the next
23
34
  call. `open` returns the revision every other subcommand is fenced to; a workspace that moved refuses
24
35
  them, and reopening is the recovery. A source that does not build is a successful call carrying a
25
- null declaration and located findings, not a failed one. The Git commands remain the way to author a
26
- project you have cloned.
36
+ null declaration and located findings, not a failed one. `add-asset <template-id> <file>` writes one
37
+ image or font as unsaved bytes at `assets/<file name>` unless `--path` names another project path;
38
+ `--replace` is required to change a path that already holds a file. The Git commands remain the way
39
+ to author a project you have cloned.
40
+
41
+ `samva brands pull [slug]` writes the brand snapshot `.samva/brands/<slug>.json` that `@samva/vite`
42
+ resolves `samva:brand` from offline; bare `pull` snapshots the default brand, and `theme.css` is
43
+ never written. Keep `.samva` gitignored. `samva brands push <file>` validates brand CSS locally,
44
+ exits `1` with located findings when it is outside the brand grammar, and otherwise replaces the
45
+ CSS of the brand `--brand <slug>` names, or of the default brand. `--dry-run` validates offline.
27
46
 
28
47
  `samva templates types --out <file>` writes one TypeScript module from the authenticated template
29
48
  catalog: the input each published template accepts, and the reference a send carries. The default
@@ -34,14 +53,17 @@ catalog, naming what moved. A schema the generator cannot express as a checking
34
53
  with the template and the JSON pointer rather than generated as `unknown`.
35
54
 
36
55
  `samva templates init --name <name>` creates a starter-backed Artifact project and clones its Git
37
- repository. `samva templates clone <template-id>` clones an existing project. Both configure a
56
+ repository. `samva templates clone <project-or-template-id>` clones an existing project. Both configure a
38
57
  repository-local credential helper that exchanges Samva authorization for a short-lived,
39
58
  repository-scoped credential; the token is never stored in the remote URL or Git config. Normal
40
59
  `git fetch`, `git pull`, `git add`, `git commit`, and `git push` remain the synchronization commands.
41
60
  Install from the project's lockfile (`bun install --frozen-lockfile` or `npm ci`) before `samva templates check` or `samva templates build`, then
42
61
  publish a clean, pushed commit with `samva templates publish`. Publish checks that the selected
43
62
  commit is an ancestor of the fetched `origin/<default-branch>` and creates an immutable receipt; it
44
- does not push or rewrite history. The CLI loads the project's installed authoring toolchain so its
63
+ does not push or rewrite history. A project holds several emails: `samva templates add
64
+ <templates/x.tsx>` registers another entry under its `defineTemplate` id, `samva templates publish --all`
65
+ publishes every active entry at one commit and exits non-zero if any failed, and `samva templates
66
+ move <old> <new>` follows a `git mv`. The CLI loads the project's installed authoring toolchain so its
45
67
  compiler and editor versions stay pinned by the project lockfile.
46
68
  Template authoring commands run that toolchain under Node.js and name the Node.js version they need
47
69
  when it is missing; other CLI commands are self-contained.
package/README.md CHANGED
@@ -108,9 +108,6 @@ samva email doctor --json
108
108
  samva email tracking defaults get
109
109
  samva email tracking defaults set --opens on --clicks off
110
110
 
111
- # Pull and push the organization email design kit as Tailwind `@theme` CSS.
112
- samva email design pull
113
- samva email design push --dry-run
114
111
  ```
115
112
 
116
113
  ## DKIM rotation
@@ -135,52 +132,81 @@ identity and Artifact repository; Git owns the local checkout and synchronizatio
135
132
 
136
133
  ```sh
137
134
  samva templates init --name "Welcome email" --dir welcome-email
138
- # samva templates clone tmpl_abc123 --dir welcome-email
135
+ # samva templates clone tproj_abc123 --dir welcome-email
139
136
  cd welcome-email
140
137
  bun install --frozen-lockfile
141
138
  bun run dev
142
139
  ```
143
140
 
144
141
  The starter repository contains `@samva/markup`, `@samva/vite`, Vite, the project theme, and its
145
- lockfile. The CLI deliberately loads `@samva/vite/build` from the current project, so local preview,
146
- check, and build use the compiler version recorded in that lockfile. The clone has a normal HTTPS
142
+ lockfile. The CLI deliberately loads `@samva/vite` from the current project, so local preview,
143
+ check, render, and build use the compiler version recorded in that lockfile. A project without
144
+ `@samva/markup`, `@samva/vite`, and `vite` fails with the install command
145
+ (`npm install --save-dev @samva/markup @samva/vite vite`). The clone has a normal HTTPS
147
146
  `origin`; its repository-local credential helper obtains a one-hour, repository-scoped Artifact
148
147
  credential from Samva when Git needs one. The credential is not written to the remote URL or Git
149
148
  configuration.
150
149
 
151
150
  ```sh
152
- samva templates check --dir emails
151
+ samva templates dev
152
+ samva templates check
153
153
  samva templates check --json
154
- samva templates build --dir emails --out dist/templates.json
154
+ samva templates render welcome --fixture default
155
+ samva templates snapshot welcome --fixture default
156
+ samva templates build --out dist/templates.json
155
157
  samva templates check --watch
156
158
  samva templates check --watch --jsonl
157
- git add emails theme.css
159
+ git add templates theme.css
158
160
  git commit -m "feat: update welcome email"
159
161
  git push origin main
160
162
  samva templates publish --commit HEAD --json
161
163
  ```
162
164
 
163
- Email entries default-export `defineEmail` with an input schema and declared fixtures. `check`
164
- typechecks the project and renders every fixture; `build` writes those rendered fixtures, the input
165
- schema, and the compiled executable module graph. Helper modules without a default export are not
166
- templates, and every email definition must have a unique id. SMS and WhatsApp retain their
167
- structured-channel compiler.
165
+ A project holds as many emails as you need, sharing its components, theme, and lockfile. Write
166
+ another entry, register it, then publish every entry at one commit:
168
167
 
169
- The JSON file written by `build` uses `schemaVersion: 2`: email previews have `kind: "email"` and
170
- contain actual fixture outputs; structured previews have `kind: "structured"`. Email source in the
171
- artifact is authored TSX. The CLI's stdout envelope remains version 1.
168
+ ```sh
169
+ samva templates add templates/reset.tsx
170
+ git add templates && git commit -m "feat: add password reset" && git push origin main
171
+ samva templates publish --all
172
+ ```
173
+
174
+ `add` uses the entry's `defineTemplate` id as the template's handle; pass `--slug` and `--name` to
175
+ choose others or to register an entry before the file exists. `publish --all` publishes every
176
+ active template in the project, reports each result, exits non-zero when any entry failed, and
177
+ lists `defineTemplate` files under `templates/` or `emails/` that are not registered. Rerunning it after a partial failure is safe.
178
+ After `git mv`, `samva templates move <old-path> <new-path>` points the template at the new file
179
+ and keeps its id, handle, and publications.
180
+
181
+ Templates live in `templates/`, and `emails/` is also recognized; every authoring command scans
182
+ whichever exist, and `--dir <dir>` scans only that one. A template is a `.tsx` file that
183
+ default-exports `defineTemplate` with an input schema, declared fixtures, and an `email`, `sms`, or
184
+ `whatsapp` body. The compiler reads the file without running it, so top-level code in a template
185
+ does not execute. `check` type-checks the project, compiles every template, and renders every
186
+ fixture through every channel the template declares; `build` writes those rendered fixtures, each
187
+ template's input schema, and its compiled IR. Modules without a template default export are
188
+ helpers, and every template needs a unique id.
189
+
190
+ The JSON file written by `build` uses `schemaVersion: 3`. Its `result` is the `@samva/vite/build`
191
+ result: `files[]`, one per template entry, each with `ok`, `diagnostics`, and a `template` carrying
192
+ `template` (the id), `channels`, `source`, `inputSchema`, `ir`, `fixtures[]` (each with its `email`
193
+ subject, preheader, HTML and text, its `sms` text, or its `whatsapp` message), and `compatibility`
194
+ findings. The former `preview.kind` `"email"` and `"structured"` shapes no longer exist. The CLI's
195
+ stdout envelope remains version 1.
172
196
 
173
197
  For imported email assets, pass their actual serving base to `check` or `build`, for example
174
198
  `samva templates build --out dist/templates.json --asset-base https://cdn.example.com/email`.
175
199
  These commands require an HTTPS base for delivery; they do not invent a URL or upload assets.
176
- `templates export --dir emails --out dist/email` remains the way to write a local HTML/text export
177
- with relative asset URLs.
200
+ `templates export --out dist/email` remains the way to write a local HTML/text export
201
+ with relative asset URLs, plus `<fixture>.sms.txt` and `<fixture>.whatsapp.json` for those channels.
202
+ Like `check` and `build`, it treats the current directory as the project: its `theme.css` themes
203
+ the export and `samva:brand` resolves live or from `.samva/brands`.
178
204
 
179
205
  `git fetch`, `git pull`, `git add`, `git commit`, and `git push` are ordinary Git operations. The
180
206
  CLI's `init` and `clone` commands configure the repository-local helper; `publish` does not push
181
207
  for you. It accepts a clean commit that is reachable from the fetched `origin/<default-branch>`
182
- and publishes that exact commit and entry path. Use `--template-id` and `--entry` when publishing
183
- from a checkout that was not created by `samva templates init` or `clone`.
208
+ and publishes that exact commit. A clone records its project in `samva.projectId`; pass
209
+ `--project-id` from a checkout that `samva templates init` or `clone` did not create.
184
210
 
185
211
  The helper is invoked by Git using its credential protocol. It accepts only an HTTPS host and path
186
212
  matching the project's repository, then exchanges the current Samva credential for a read or write
@@ -188,14 +214,47 @@ Artifact credential. `samva templates credential` is an internal helper command,
188
214
  credential store. Local OAuth credentials remain in the OS keyring; `SAMVA_API_KEY` remains the
189
215
  automation path.
190
216
 
217
+ `samva templates dev [--dir <dir>] [--port <n>] [--host <name>] [--open]` starts the local visual
218
+ editor from the project's `@samva/vite/dev` under Node.js, with no `vite.config.ts`. It prints the
219
+ editor URL and runs until interrupted; `SIGINT` and `SIGTERM` close the server.
220
+
191
221
  `samva templates check` runs the project's TypeScript configuration, then uses the project's
192
- `@samva/vite/build` to evaluate default exports, emit SML, compile channel artifacts, and report
193
- phase/provenance diagnostics plus email-client compatibility findings. It writes no files and exits
194
- non-zero on TypeScript, discovery, module, theme, emit, compile, or breaking compatibility errors.
195
- `--watch` reuses one Vite evaluator across template, dependency, discovery, and theme changes.
196
- Human mode prints each observation; machine watch mode requires `--jsonl`. `--json` remains a
197
- bounded one-shot result. `SIGINT` and `SIGTERM` close the watcher and keep the CLI's documented
198
- interrupt exit codes.
222
+ `@samva/vite/build` to compile every template, render every fixture, and report located findings
223
+ plus email-client compatibility findings. It writes no files and exits non-zero on TypeScript,
224
+ compile, fixture, or breaking compatibility errors. `--watch` reuses one compiler session across
225
+ template, dependency, and theme changes. Human mode prints each observation; machine watch mode
226
+ requires `--jsonl`. `--json` remains a bounded one-shot result. `SIGINT` and `SIGTERM` close the
227
+ watcher and keep the CLI's documented interrupt exit codes.
228
+
229
+ Every finding in `check --json` (`problems[]`), `build --json`, `render --json`, and `export --json`
230
+ has the same fields: `code`, `severity`, `message`, `file`, `line` and `column` when located,
231
+ `range` (the located extent), `fix` (what to write instead) when the compiler has one, and `edit`
232
+ (a machine-applicable change) when it offers one; `check` and `build` add `phase`, and a finding
233
+ tied to one fixture names it in `fixture`. Human output prints
234
+ `file:line:column severity code: message` and the fix beneath it. `check --json` also carries
235
+ `warnings[]` for compile warnings and `compatibility` for client findings.
236
+
237
+ `samva templates render <template> --fixture <name>` renders one fixture of a local template
238
+ through the project's `@samva/vite/render`. `<template>` is a template id or its `.tsx` path.
239
+ `--channel email|sms|whatsapp` picks the body (default `email`), `--locale` and `--time-zone`
240
+ set formatter locale and IANA zone, `--asset-base` and `--offline` behave as for `check`, and
241
+ `--dir` narrows discovery. The default output is the subject, preheader, and plain-text body;
242
+ `--format html` prints the HTML and `--format json` (or `--json`) prints
243
+ `{ action: "templates.render", ok, template, fixture, subject, preheader?, html, text, diagnostics }`
244
+ (an SMS render carries `channel` and `text`; a WhatsApp render carries `channel` and `message`).
245
+ `--out <file>` writes the selected format to a file. A template or fixture that does not exist, or
246
+ a template that does not compile, exits `1` with located findings; an unknown name lists the valid
247
+ ones.
248
+
249
+ `samva templates snapshot <template> --fixture <name>` renders the email and screenshots it in a
250
+ headless browser at `desktop` (1280px) and `mobile` (390px) widths, in `light` and `dark` color
251
+ schemes, as full-page PNGs at `<out>/<template>-<fixture>-<viewport>-<scheme>.png` (default
252
+ `--out .samva/snapshots`). `--viewport desktop,mobile` and `--scheme light,dark` narrow the set.
253
+ `--json` prints `{ action: "templates.snapshot", ok, template, fixture, browser, out, files: [{ viewport,
254
+ scheme, width, path }] }`. The browser is the `playwright` (or `playwright-core`) your project
255
+ installs; without it, a system Google Chrome or Chromium, found in the usual locations or at
256
+ `SAMVA_CHROME`. With neither the command fails and names both remedies. Screenshots emulate the
257
+ color scheme; they do not reproduce a particular mail client's rendering.
199
258
 
200
259
  `samva templates build` runs the same compiler gate and writes the complete compiled result as a
201
260
  versioned JSON artifact for package and deployment pipelines. Its terminal/JSON result stays
@@ -203,11 +262,12 @@ bounded and reports only the destination, compiler version, and file count.
203
262
 
204
263
  `samva templates publish` resolves the requested revision to an exact commit, verifies the working
205
264
  tree is clean and the commit is an ancestor of the fetched `origin` default branch, then creates an
206
- immutable publication receipt for the project's configured TSX entry. It does not rewrite Git
207
- history or alter the working tree.
265
+ immutable publication receipt for each selected entry: the named entry, every active entry with
266
+ `--all`, or the project's only entry. It does not rewrite Git history or alter the working tree.
208
267
 
209
268
  `templates list`, `get`, `create`, `update`, and `remove` operate on template metadata. They do not
210
- replace the project repository, workspace, or native Git history.
269
+ replace the project repository, workspace, or native Git history. `remove` deactivates a template;
270
+ its file and publications remain.
211
271
 
212
272
  Inline email requires `--subject` and exactly one body source: `--html` or `--text`. Pass `-` as
213
273
  the value to read that body from stdin: `--text -` reads a plain-text body and `--html -` reads an
@@ -220,6 +280,33 @@ to a two-minute timeout. `--timeout` is valid only with `--wait`, and `--wait` c
220
280
  `--dry-run`, since a dry run makes no API call and has nothing to wait on. A timeout or terminal
221
281
  delivery failure returns non-zero after printing the last message state.
222
282
 
283
+ ## Brands
284
+
285
+ A template reaches its organization's brand with `@import "samva:brand";` (the default brand) or
286
+ `"samva:brand/<slug>"` in `theme.css`. The brand lives in Samva, not in the project, and these
287
+ commands work with it:
288
+
289
+ ```sh
290
+ samva brands list
291
+ samva brands get acme --json
292
+ samva brands pull # the default brand
293
+ samva brands pull acme
294
+ samva brands push brand.css --brand acme --dry-run
295
+ samva brands push brand.css # updates the default brand
296
+ ```
297
+
298
+ `samva brands pull [slug]` writes `.samva/brands/<slug>.json` in the project (`--project <dir>`,
299
+ default the current directory). `@samva/vite` resolves `samva:brand` from that snapshot when it
300
+ cannot reach the live brand, so builds and previews work offline. Pull never touches `theme.css`;
301
+ keep `.samva` in `.gitignore`, because the API owns the brand.
302
+
303
+ `samva brands push <file>` validates the file against the brand CSS grammar before uploading:
304
+ `@theme` variables, one dark-mode `@theme` of colors, and `@font-face` rules with https WOFF2
305
+ sources. CSS outside it exits `1` with every finding located at `file:line:column`, and nothing is
306
+ uploaded. `--brand <slug>` names the brand to replace; without it, push updates the organization's
307
+ default brand. `--dry-run` validates without credentials or an API call. Published templates keep
308
+ the brand they were built against until they are republished.
309
+
223
310
  ## Scheduling and campaigns
224
311
 
225
312
  ```sh
@@ -302,7 +389,7 @@ samva email send --help
302
389
  ```
303
390
 
304
391
  Released top-level commands are `login`, `logout`, `org`, `email`, `messages`, `campaigns`,
305
- `scheduled-messages`, `templates`, `webhooks`, `profile`, and `help`. Use `samva <command> --help`
392
+ `scheduled-messages`, `templates`, `brands`, `webhooks`, `profile`, and `help`. Use `samva <command> --help`
306
393
  for flags and runnable examples.
307
394
 
308
395
  Test a configured webhook against its real endpoint with an optional JSON payload. `--data` accepts
@@ -315,14 +402,16 @@ samva webhooks test wep_abc123 --data '{"event":"webhook.test"}' --json
315
402
  samva webhooks test wep_abc123 --data @payload.json --json
316
403
  ```
317
404
 
318
- `samva email doctor` reports resolved profile, API URL, auth source, organization access,
319
- sending domains, senders, and domain-verification readback. It is read-only and does not inspect
320
- receiving configuration, verify DNS, send an email, replay fixtures, or check provider readiness.
405
+ `samva email doctor` reports resolved profile, API URL, auth source, organization access, the send
406
+ review decision, sendable domain and verified sender counts, and domain-verification readback.
407
+ `readyToSend` is true when a production send would be admitted and at least one domain or sender can
408
+ carry it. It is read-only and does not inspect receiving configuration, check DNS itself, send an
409
+ email, or replay fixtures.
321
410
  Use `samva email receiving status <domain-id>` to read a domain's receiving prerequisite status.
322
411
  `samva email receiving enable <domain-id> ...` acts by default; its `--dry-run` is offline and does
323
412
  not require credentials or call the API. Each check is `passed`, `failed`, or
324
413
  `unavailable`. A `failed` check carries the underlying error detail; `unavailable`, such as no
325
- sending domain configured yet, is informational. The command always prints the full report, then
414
+ sending domain yet or a review in progress, is informational; a rejected review fails. The command always prints the full report, then
326
415
  exits `1` if any check failed and `0` otherwise. The `--json` result's `data` adds an aggregate
327
416
  `ok` boolean, and human output marks checks with `✓`, `!`, and `✗` respectively.
328
417