@taprootio/site-authoring 0.1.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 (57) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +239 -0
  3. package/bin/taproot-site.js +5 -0
  4. package/package.json +40 -0
  5. package/src/api.js +1507 -0
  6. package/src/appearance-contract.js +311 -0
  7. package/src/atomic-file.js +136 -0
  8. package/src/bounded-file.js +59 -0
  9. package/src/capabilities.js +29 -0
  10. package/src/cli.js +872 -0
  11. package/src/config.js +412 -0
  12. package/src/constants.js +173 -0
  13. package/src/content/components.js +870 -0
  14. package/src/content/free-form-section-registry.json +524 -0
  15. package/src/content/free-form-sections.js +582 -0
  16. package/src/content/index.js +29 -0
  17. package/src/content/inline-facts.js +97 -0
  18. package/src/content/markdown.js +1625 -0
  19. package/src/content/tiptap-prosemirror.ts +1266 -0
  20. package/src/content/validate-document.js +1079 -0
  21. package/src/content/vocabulary.js +397 -0
  22. package/src/credentials.js +544 -0
  23. package/src/errors.js +133 -0
  24. package/src/footer-contract.js +687 -0
  25. package/src/footer-draft-hash.js +203 -0
  26. package/src/footer-workspace.js +136 -0
  27. package/src/image-metadata.js +169 -0
  28. package/src/index.js +16 -0
  29. package/src/json-guard.js +111 -0
  30. package/src/output.js +157 -0
  31. package/src/presentation-reference.js +600 -0
  32. package/src/reference-help.js +927 -0
  33. package/src/session.js +316 -0
  34. package/src/settings-catalog.js +187 -0
  35. package/src/settings.js +157 -0
  36. package/src/theme-validation.js +344 -0
  37. package/src/transport.js +777 -0
  38. package/src/verbs/approve.js +178 -0
  39. package/src/verbs/deploy.js +327 -0
  40. package/src/verbs/env.js +89 -0
  41. package/src/verbs/footer-push.js +115 -0
  42. package/src/verbs/index.js +125 -0
  43. package/src/verbs/login.js +529 -0
  44. package/src/verbs/logout.js +39 -0
  45. package/src/verbs/media-upload.js +319 -0
  46. package/src/verbs/nav-push.js +333 -0
  47. package/src/verbs/pages-push.js +990 -0
  48. package/src/verbs/preview-page.js +107 -0
  49. package/src/verbs/preview-revoke.js +56 -0
  50. package/src/verbs/pull.js +727 -0
  51. package/src/verbs/sites.js +57 -0
  52. package/src/verbs/status.js +94 -0
  53. package/src/verbs/theme-push.js +197 -0
  54. package/src/verbs/use.js +129 -0
  55. package/src/verbs/validate.js +477 -0
  56. package/src/verbs/whoami.js +108 -0
  57. package/src/workspace.js +1085 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Taproot
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # `@taprootio/site-authoring`
2
+
3
+ The command-line authoring surface for a Taproot site. `taproot-site` pulls a
4
+ site's pages, navigation, and settings into a local workspace, validates what
5
+ you author there against the same contracts the published site renders, and
6
+ pushes, previews, approves, and deploys the result — for a person at a
7
+ terminal or an agent acting on an owner's behalf.
8
+
9
+ The CLI is deliberately strict where the server is permissive. Taproot stores
10
+ a page body as it is sent and renders what it recognizes, so a malformed
11
+ document fails silently at render time. Every push therefore validates every
12
+ document it will send before sending any of them, and refuses anything the
13
+ renderer would drop.
14
+
15
+ ## Availability, as of September 2026
16
+
17
+ Taproot cannot yet deliver sign-in email to people outside the team, so no
18
+ one outside it can create an account or approve a sign-in today. The package
19
+ is published ahead of that so it is installed and ready when access opens.
20
+ Until then, `help` and `validate` work fully offline, and `login` will print
21
+ its approval code and URL and then wait for an approval that cannot yet be
22
+ given, giving up when the approval window closes (about fifteen minutes) with
23
+ `login.timeout`. This note is removed when access opens.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install --global @taprootio/site-authoring
29
+ taproot-site --help
30
+ ```
31
+
32
+ Node 22 or later. The package has one runtime dependency,
33
+ `@taprootio/espalier`, pinned to an exact version: the CLI validates themes
34
+ against one Espalier theme contract, and a floating range would let an install
35
+ resolve a different contract than the CLI was tested against.
36
+
37
+ ## First commands
38
+
39
+ `taproot-site --help` lists every verb and the configuration contract.
40
+ `taproot-site help` is the offline reference family agents read before
41
+ authoring: page types, components, navigation, media, previews, themes,
42
+ appearance, and the footer, each with `--json` for stable machine-readable
43
+ output.
44
+
45
+ ```bash
46
+ taproot-site help
47
+ taproot-site help page free-form
48
+ taproot-site help component hero-section --json
49
+ ```
50
+
51
+ `taproot-site validate <fixture-directory>` checks a complete offline fixture
52
+ with no credential, no network, and no write: a directory laid out like a
53
+ pulled workspace — `pages/`, `nav.json`, and the four `settings/` documents —
54
+ whose `manifest.fixture.json` binds deterministic page, resource, image, and
55
+ settings identities under a versioned `fixture` block. It proves structure,
56
+ page content and theme contexts, navigation references, the complete theme,
57
+ appearance, header, brand, and footer, and fixture-local image identities. It
58
+ does not prove authorization, site ownership, concurrency, server round trips,
59
+ or rendering; a real pull and an authorized preview do that.
60
+
61
+ ## Authorize
62
+
63
+ Sign in once. `login` starts a device-style authorization: it prints an
64
+ eight-character code and the approval URL on the Taproot origin it is
65
+ configured to talk to, and waits while an owner enters that code in a browser
66
+ already signed in to Taproot. Typing the code — rather than following a
67
+ prefilled link — is what proves the person approving can see the terminal
68
+ that asked, so the URL never carries the code and the page accepts none.
69
+
70
+ ```bash
71
+ taproot-site login
72
+ taproot-site sites
73
+ taproot-site use "My Site"
74
+ taproot-site whoami
75
+ ```
76
+
77
+ The issued sign-in is stored outside any repository at
78
+ `$XDG_CONFIG_HOME/taproot-site/credentials.json` (falling back to
79
+ `~/.config/taproot-site/`), directory `0700` and file `0600`, one per Taproot
80
+ origin. It authorizes nothing on any site. It can do exactly two things: list
81
+ the account's authorable sites, and exchange itself for a short-lived site
82
+ credential, which every authoring command mints for itself and holds only in
83
+ memory. Exchanged credentials expire within the hour and carry only the
84
+ capabilities the command needs — a content push never holds deploy. The
85
+ sign-in expires 24 hours after its last successful exchange, and never past
86
+ any expiry the approving owner chose.
87
+
88
+ `sites` lists what the sign-in may author; `use <name or id>` records the
89
+ choice as `siteId` in `taproot-site.json`, creating the file in the current
90
+ directory when there is none. `whoami` answers from local state alone: which
91
+ Taproot, which account, which site, what the last exchange granted, and when
92
+ the sign-in expires. `logout` discards the stored sign-in locally and revokes
93
+ nothing; the credential stays valid until an owner revokes it under
94
+ Account → Settings → API keys. The secret itself is never displayed, logged,
95
+ or placed in any result: credentials are named by id and display prefix only.
96
+
97
+ ### `TAPROOT_SITE_KEY` overrides the sign-in
98
+
99
+ CI and non-interactive agents authenticate with a site-scoped site authoring
100
+ key issued under Account → Settings → API keys and supplied only through the
101
+ environment:
102
+
103
+ ```bash
104
+ export TAPROOT_SITE_KEY='the one-time displayed site authoring key'
105
+ taproot-site status
106
+ ```
107
+
108
+ When the variable is set it wins outright and the exchange is skipped, so
109
+ logging in on a machine never changes what existing automation does. Presence
110
+ is authoritative: an empty or malformed value is refused as `auth.key_invalid`
111
+ rather than falling through to a stored sign-in and running the command as a
112
+ different identity. There is no flag and no configuration field for either
113
+ credential, and no second environment variable.
114
+
115
+ ## Configuration
116
+
117
+ Commands that act on a site read `taproot-site.json`, discovered by walking
118
+ upward from the current directory through a bounded number of parents.
119
+ Exactly one must be found: none is `config.not_found`, more than one is
120
+ `config.ambiguous`, and `--config <path>` placed before the verb selects one
121
+ explicitly and bypasses discovery. `login`, `logout`, `sites`, `use`,
122
+ `whoami`, and `env` need no configuration and no site.
123
+
124
+ ```json
125
+ {
126
+ "configVersion": 1,
127
+ "siteId": "11111111-1111-4111-8111-111111111111",
128
+ "workspaceDir": "site"
129
+ }
130
+ ```
131
+
132
+ | Field | Rule |
133
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
134
+ | `configVersion` | Must be `1`. |
135
+ | `siteId` | Optional. The canonical lowercase UUID of the site the next command writes to. `use` writes it; a present but malformed value is refused. |
136
+ | `workspaceDir` | Required. A relative POSIX path beneath the configuration directory that `pull` writes into. Every existing segment must be a real directory. |
137
+
138
+ The file is a small, closed JSON object. Unknown fields, duplicate keys, links
139
+ in the path, and unsupported versions are refused before any credential is
140
+ attached to a request. `apiBaseUrl` is not a field and is refused by name:
141
+ which Taproot the CLI talks to is a property of the machine, not the project.
142
+
143
+ ### Which Taproot the CLI talks to
144
+
145
+ ```bash
146
+ taproot-site env # report
147
+ taproot-site env local # https://app.taproot.test/api
148
+ taproot-site env production # https://app.taproot.io/api, the default
149
+ ```
150
+
151
+ The choice is remembered per machine in `settings.json` beside the
152
+ credential, because it has to be known before any `taproot-site.json` exists.
153
+ Only a reviewed origin set is accepted: `app.taproot.io`, `app.taproot.test`
154
+ for a local Tilt stack, or an explicit loopback host such as
155
+ `http://localhost:8080/api` for development. Taproot is not a self-hostable
156
+ distribution, and every other host is refused before a bearer can be built.
157
+ Sign-ins are stored per origin, so switching away and back finds the one that
158
+ was already there.
159
+
160
+ ## The authoring loop
161
+
162
+ ```bash
163
+ taproot-site pull
164
+ # edit site/pages/about.md
165
+ taproot-site pages push about
166
+ taproot-site preview page about
167
+ taproot-site approve about
168
+ taproot-site deploy --staging
169
+ taproot-site deploy --production
170
+ ```
171
+
172
+ `pull` snapshots pages, navigation, and settings into the workspace:
173
+ `pages/`, `nav.json`, `settings/`, `media/`, and two dot-prefixed manifests
174
+ that carry site binding and page identity between commands. Every page path
175
+ has exactly one authoritative source — `pages/<name>.md` in the documented
176
+ Markdown subset with `title`, `path`, and `description` front matter, or
177
+ `pages/<name>.pm.json` as a ProseMirror document — and pull never writes a
178
+ generated `.pm.json` beside a tracked `.md`. For a page tracked as Markdown
179
+ the site's own document is kept as internal state under
180
+ `.taproot-site-state/`, which is never a page source and never pushed. A page
181
+ edited on the site since the last pull is a `pages.pull_conflict` refusal
182
+ before anything under `pages/` changes, with both recovery paths named.
183
+
184
+ `pages push [page-path...]` validates and sends the selected pages, or every
185
+ workspace page when none is named; the homepage is addressed as `/`.
186
+ `media upload [path...]` uploads raster files and records component-ready
187
+ delivery fields. `nav push`, `theme push`, and `footer push` replace the whole
188
+ navigation tree, the complete light/dark theme pair with its appearance
189
+ settings, and the closed footer document.
190
+
191
+ `preview page <page-path-or-id>` renders one persisted draft behind the
192
+ staging gate and mints a browser handoff URL, reported only in the final JSON
193
+ result. **Each handoff is single-use.** Opening the URL consumes it; a reused,
194
+ shared, or bookmarked preview URL answers `Not found`, which is the handoff
195
+ being spent rather than the preview being broken. Run `preview page` again
196
+ for another. The handoff also expires two minutes after it is minted;
197
+ `preview revoke <page-id> <snapshot-id>` releases a preview early.
198
+
199
+ `approve [page-path...]` publishes drafts into the approved candidate pool.
200
+ It stages; nothing reaches an audience until `deploy --staging` publishes the
201
+ candidate and `deploy --production` promotes that completed staging
202
+ deployment. `status` reports deployments, readiness, and image processing.
203
+
204
+ ## Output contract
205
+
206
+ Human progress goes to stderr and `--quiet` silences it (except for `login`,
207
+ whose approval code and URL exist only as progress). Stdout carries exactly
208
+ one JSON object per run, schema version `1`: `ok`, the CLI name and version,
209
+ the verb, and the verb's own result on success; a stable `error.code` with an
210
+ optional `field`, `status`, and classified `refusal` on failure. Exit codes are
211
+ `0` success, `1` failure, and `2` usage fault. The four refusal classes an
212
+ automation should branch on are `platform_paused` (external authoring is
213
+ paused; retry later), `credential_rejected` (stop and re-issue),
214
+ `plan_limit` (the plan's published-page allowance, surfaced at deploy), and
215
+ `throttled` (back off).
216
+
217
+ When `GITHUB_OUTPUT` names the runner's existing output file, every
218
+ operational verb appends the same JSON under `taproot_site_result` through a
219
+ random delimiter block, plus `taproot_site_verb` on success. Credentials,
220
+ Authorization values, signed upload headers, preview handoffs outside the
221
+ final result, and page bodies never appear in progress, diagnostics, JSON, or
222
+ Actions output.
223
+
224
+ ## Public release
225
+
226
+ Private Taproot is the source authority. A private `site-authoring-v<version>`
227
+ tag verifies this package — the release commit must belong to `main`, the tag
228
+ must name the version `package.json` declares, and `@taprootio/espalier` must
229
+ be pinned to the exact version the release manifest records — then
230
+ allowlist-exports only `packages/site-authoring/` to public
231
+ [`taprootio/trunk`](https://github.com/taprootio/trunk). Trunk's separate
232
+ `publish-site-authoring.yml` workflow tests and publishes those exact public
233
+ tag bytes with npm trusted-publishing provenance, under its own concurrency
234
+ group, GitHub Environment, tag prefix, and npm identity.
235
+
236
+ The package's tests run in both places. A few compare this package against
237
+ sources that live only in the private monorepo — the canonical renderer, the
238
+ shared section registry, the seeded default theme, and the API's contracts —
239
+ and those skip by name in the public tree rather than pretending to pass.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCli } from "../src/cli.js";
4
+
5
+ process.exitCode = await runCli();
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@taprootio/site-authoring",
3
+ "version": "0.1.0",
4
+ "description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
5
+ "type": "module",
6
+ "license": "ISC",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/taprootio/trunk.git",
10
+ "directory": "packages/site-authoring"
11
+ },
12
+ "engines": {
13
+ "node": ">=22"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "import": "./src/index.js"
18
+ }
19
+ },
20
+ "bin": {
21
+ "taproot-site": "./bin/taproot-site.js"
22
+ },
23
+ "files": [
24
+ "bin/",
25
+ "src/",
26
+ "LICENSE",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@taprootio/espalier": "4.4.0"
31
+ },
32
+ "scripts": {
33
+ "test": "node --test",
34
+ "prepack": "npm test"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ }
40
+ }