inferative 0.2.0-next.20261023154401 → 0.2.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.
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  The Inferative command line. It is on public npm, ships built JavaScript and runs under plain `node` (version 24 or
4
4
  later). It has the command `init`, which creates a project, the group `site`: the commands a website runs against
5
- its channel, the group `backup`: one archive of a project's database and files, its restore and its check, and the group `plugin`: the MCP server and the skill of a coding agent. To start a project, run `npm create inferative@latest my-site`: that command needs no registry setting.
5
+ its channel (its import of entries and media included), the group `backup`: one archive of a project's database and files, its restore and its check, the group `plugin`: the MCP server and the skill of a coding agent, and the commands `setup-token` and
6
+ `chat-webhook-secret`, which print a secret that a host derives from its root secret. To start a project, run `npm create inferative@latest my-site`: that command needs no registry setting.
6
7
 
7
8
  ```sh
8
9
  npm create inferative@latest my-site
@@ -37,7 +38,7 @@ npm create inferative@latest my-site
37
38
  | Argument or flag | What it does |
38
39
  | :-- | :-- |
39
40
  | `directory` | The folder to create. When you leave it out, the current folder is the target. |
40
- | `--starter <name>` | The starter to use: `nuxt-cloudflare` (a client site, the default) or `dashboard-cloudflare` (the dashboard). |
41
+ | `--starter <name>` | The starter to use: `nuxt-cloudflare` (a client site, the default), `dashboard-cloudflare` (the dashboard) or `nuxt-sdk` (a website with its own server that reads a deployment with the SDK). |
41
42
  | `--source <giget source>` | Download this giget source alone, instead of the default sources. |
42
43
  | `--local <folder>` | Copy `<folder>/<starter>` instead of a download. For tests and for work on the starters. |
43
44
  | `--no-install` | Do not run `pnpm install`. |
@@ -81,6 +82,7 @@ file is the site's own TypeScript. `--site <path>` names another declarations mo
81
82
  | `inferative site sync` | Publishes the manifest to the channel, then waits until the channel serves that version. `--first-sync` publishes the first-sync catalog. `--manifest <file>` publishes a prebuilt envelope as it is. |
82
83
  | `inferative site doctor [origin]` | Reports registry access, then reads the diagnostic of a running site at `<origin>/_site/health`. `--local` diagnoses from the site's configuration instead. `--path <path>` names a diagnostic mounted elsewhere. |
83
84
  | `inferative site config` | Prints what the command line read from the declarations and the runtime configuration. |
85
+ | `inferative site import <folder>` | Loads entries and media files from a folder into the channel, through the editor API. `--dry-run` writes nothing. |
84
86
 
85
87
  `sync` presents the key in `INFERATIVE_MANIFEST_TOKEN`. The channel and the API come from the `site` runtime config
86
88
  section, or from the `INFERATIVE_SITE_*` variables. Every `site` command first reads the `.env` file of the site root,
@@ -88,6 +90,51 @@ the file the connect guide of the dashboard fills. A variable that is already se
88
90
  file, and a missing file is not an error. The agent skill (`docs/skills/inferative`, the page
89
91
  `site-deploy-and-topologies`) lists every variable and every exit code.
90
92
 
93
+ ### `inferative site import`
94
+
95
+ `inferative site import <folder>` loads a site's entries and media from files. It presents the `ak_` key in
96
+ `INFERATIVE_IMPORT_TOKEN` (`content:read`, `content:write`, `files:read` and `files:write`; `content:publish` for
97
+ `--publish` and `files:delete` for `--release`). The channel and the API come from `--channel` and `--api`, or from the
98
+ `site.sync` group as for `sync`. The channel must serve a schema: run `sync` first.
99
+
100
+ - `entries/<type>/<language>/<slug>.md`, `.html`, `.yaml` or `.json` is one entry in one language. The type is a
101
+ served key, with or without its namespace. A Markdown or HTML file has YAML or JSON front matter for the fields, and
102
+ its body goes to the type's one rich-text field (`_body` names another). The write converts the body with
103
+ `RichTextImport`, sent as `{ format, text }`. The same slug in two language folders is one entry with two languages.
104
+ - `media/<path>` is one file. A side file `<path>.yaml` (or `.yml`, `.json`) holds its `title`, `alt`, `caption`,
105
+ `credit`, `focalPoint`, `visibility` and `contentType`. A media field names the file by `<path>`; a reference field
106
+ names an entry by `<type>/<slug>`.
107
+
108
+ The run is idempotent. The entry id is a UUIDv7 that the channel, the type and the slug derive. An entry with no row
109
+ at that id is looked up by each unique field that the type lets a list filter on (`wire.filter`), with the editor
110
+ read `GET /content/editor/types/:typeKey/unique-holder`. A field inside an object is looked up by its dotted path, and
111
+ a localized field, or a field inside a localized object, is looked up in each language that the files give it. The read answers the entry that the uniqueness check stores as the holder: the whole value folded
112
+ (`CaseFold`), never cut to `indexedTextLength`, an empty value included, and for a field unique per channel only an
113
+ entry that the channel owns (a shared entry holds its value in another scope). Two holders of one field (in two
114
+ languages) stop the entry with an error. A create refused because another entry holds a unique value (`content/unique-conflict`,
115
+ `content/single-entry-taken`) updates the entry that the answer names in `holdingEntryId`. A second run patches only
116
+ the served fields and languages whose value differs; it converts a rich-text body locally with `RichTextImport` and
117
+ compares the whole document, so an unchanged entry gets no new draft and a removed mark is a change. Objects, lists,
118
+ sections and reference fields (`{ to, meta }`) are converted by their own fields, and a localized child field joins
119
+ its languages; a section is matched to its stored twin by `id`. A part that the files do not name keeps its stored
120
+ value, a reference's own fields and their languages included (also when the files write the reference as a bare
121
+ key), with only the members that the channel's schema
122
+ declares at every depth. A stored section of a type that the channel does not serve, or that its field does not take,
123
+ is left out of the write, and the server keeps it in its place. The write does not convert a rich-text field of a reference's own fields, so the command
124
+ sends its document. An entry that names another entry of the folder is written after it; in a cycle the reference is written
125
+ once both exist. A media file is found by its stored name (`FileNameRule.storedFormOf`), its size and its visibility,
126
+ and its bytes are compared by SHA-256 with the digest that the files read answers (`sha256`). A file with no stored
127
+ digest is read through the delivery route when it is ready. The key goes to the API origin only: a redirect to another
128
+ origin is followed with no credential. A file that waits for a release is reused when its stored digest matches, so a
129
+ repeat run uploads nothing; `--release` releases it when it waits only for a release (`scan-pending`). A file held for
130
+ another reason stays held, with a note. A held file with no stored digest has bytes that no key reads, so
131
+ the command uploads a new file beside it. A missing file is uploaded in one request (`PUT /files/uploads`), then its
132
+ credit is set. `--publish` sends the draft revision that the run wrote or read as `expectedDraftRevision`, so a later
133
+ draft refuses the publish. A refused file stops the run before any write. The report has one line for each entry and
134
+ each media file, and a `note` line for each part of a rich-text body that was kept as text, changed or removed.
135
+ `--dry-run` makes the reads and writes nothing. The exit code is `0` when every file was read and every write landed,
136
+ and `1` otherwise.
137
+
91
138
  ## The `backup` commands
92
139
 
93
140
  Run them in the project folder. A project made from a starter runs them as `pnpm backup`. The full procedure is the
@@ -95,8 +142,8 @@ runbook [Backup and restore](https://inferative.dev/docs/deploy/backup-and-resto
95
142
 
96
143
  | Command | What it does |
97
144
  | :-- | :-- |
98
- | `inferative backup create --out <file>` | Writes one archive (`.tar.gz.age`) of the Postgres database and the file store, with a manifest of the row count and digest of each table and the size and SHA-256 of each object, encrypted with age to the public keys of `--recipient` or `INFERATIVE_BACKUP_AGE_RECIPIENT`. `--unencrypted` writes a plain `.tar.gz`. The archive must not exist. |
99
- | `inferative backup restore --archive <file>` | Restores an archive into an empty database and an empty file store, then runs the check of `verify`. It applies the migrations of the backup from `server/persistence/pg/migrations` (`--migrations` names another folder). |
145
+ | `inferative backup create --out <file>` | Writes one archive (`.tar.gz.age`) of the Postgres or MySQL database and the file store, with a manifest of the row count and digest of each table and the size and SHA-256 of each object, encrypted with age to the public keys of `--recipient` or `INFERATIVE_BACKUP_AGE_RECIPIENT`. `--unencrypted` writes a plain `.tar.gz`. The archive must not exist. |
146
+ | `inferative backup restore --archive <file>` | Restores an archive into an empty database of its dialect and an empty file store, then runs the check of `verify`. It applies the migrations of the backup from `server/persistence/pg/migrations`, or from `server/persistence/mysql/migrations` for a `mysql://` address (`--migrations` names another folder). It holds the work of the agents of each organization with an agent (a restore flag in `agent_pause_flags`) until a person releases the hold. A backup and `verify` leave the restore flags out. |
100
147
  | `inferative backup verify --archive <file>` | Compares the database and the file store with the archive. `--archive-only` checks the archive alone. |
101
148
  | `inferative backup upload --archive <file>` | Copies an encrypted archive into the R2 bucket of archives of the four `INFERATIVE_BACKUP_R2_*` values (`--bucket`, `--r2-endpoint`, `--key`), in one write with `If-None-Match: *`, so it never replaces an object. It takes at most 5 GiB less 5 MiB, the largest single write of R2. |
102
149
  | `inferative backup keygen --out <file>` | Writes a new age private key to a file of mode 600 and prints its public key. |
@@ -127,7 +174,16 @@ or a cleanup debt names is not required. The lease is 120
127
174
  minutes; `--fence-lease <minutes>` names another, up to 1440. A crashed run leaves its row until the lease ends. A run
128
175
  that outlives its lease fails and writes no archive. The archive holds no rows of the fence table.
129
176
  Exit status 0 means that the run ended and the result is the same as the backup, 1 is a failure or a difference, and
130
- 2 is a wrong option. Postgres only.
177
+ 2 is a wrong option.
178
+
179
+ The address names the dialect: `postgres://` or `postgresql://` for Postgres, `mysql://` for MySQL (PlanetScale
180
+ included). On MySQL the snapshot is a read-only transaction at `REPEATABLE READ` whose first statement reads one row
181
+ of each table, so a migration that changes a table waits for the backup; PlanetScale refuses `LOCK TABLES`. Every table must use InnoDB: the command refuses a MyISAM or MEMORY table. Each value
182
+ reads as the text that MySQL prints for it; a byte string goes in hexadecimal and a `BIT` value as a number. A MySQL
183
+ restore keeps every foreign key on: it writes the tables in the order of their keys, writes the columns of a
184
+ self-reference or a cycle after the rows, then sets each `AUTO_INCREMENT` value. Each of its statements commits on its
185
+ own, and it refuses a table with a trigger. PlanetScale ends a transaction after 20 seconds, so a MySQL database there
186
+ must read in 20 seconds; a larger one keeps the backups of PlanetScale.
131
187
 
132
188
  ## The `plugin` commands
133
189
 
@@ -166,6 +222,27 @@ own sign-in: you sign in to the dev host as the owner in the browser and allow t
166
222
  The access token lives one hour by default (`identity.oauthServer.accessTokenTtlMs`), and the agent keeps it in its own
167
223
  credential store, outside the project. The host must import `McpModule` and set `identity.oauthServer.enabled`.
168
224
 
225
+ ## The derived secrets
226
+
227
+ A host derives some secrets from its root secret (`INFERATIVE_ROOT_SECRET`) when their own variable is not set. Two of
228
+ them are for a person: the token of the first-run step, and the secret that Telegram sends with each update of the
229
+ member chat. These commands print the value that the host derives, on one line, and nothing else:
230
+
231
+ ```sh
232
+ pnpm exec inferative setup-token
233
+ pnpm exec inferative chat-webhook-secret --env-file .env.worker
234
+ ```
235
+
236
+ - The command reads the variables of the environment and of `.env` in the folder; a variable that is set wins. With
237
+ `--env-file <path>`, the values of that file win over the environment.
238
+ - When the secret's own variable is set (`INFERATIVE_IDENTITY_SETUP_TOKEN`,
239
+ `INFERATIVE_NOTIFICATIONS_CHAT_TELEGRAM_WEBHOOK_SECRET`), the host uses it, so the command prints it.
240
+ - With neither value, the command prints which variable to set and exits with 1.
241
+
242
+ The setup token of a host with a root never changes, and the first-run step closes after its first use. Register the
243
+ webhook secret with `setWebhook` again after each rotation of the root: until then, the host still accepts the
244
+ secret of the previous root (`INFERATIVE_ROOT_SECRET_PREVIOUS`).
245
+
169
246
  ## How it is built
170
247
 
171
248
  `vp pack` bundles `src/bin.ts` into `dist/bin.mjs`, and `src/index.ts` into `dist/index.mjs`. The index is the library