moshcode 0.24.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +580 -0
  3. package/bin/moshcode.mjs +674 -0
  4. package/bin/moshscript.mjs +29 -0
  5. package/examples/alive.mosh +6 -0
  6. package/examples/scripting-the-cli.mosh +21 -0
  7. package/examples/team-secrets.mosh +20 -0
  8. package/examples/templates/bun-caddy-sqlite/.env.example +14 -0
  9. package/examples/templates/bun-caddy-sqlite/Caddyfile +18 -0
  10. package/examples/templates/bun-caddy-sqlite/README.md +97 -0
  11. package/examples/templates/bun-caddy-sqlite/deploy/moshcode-dns.service +39 -0
  12. package/examples/templates/bun-caddy-sqlite/deploy/moshpit-service.service +38 -0
  13. package/examples/templates/bun-caddy-sqlite/package.json +15 -0
  14. package/examples/templates/bun-caddy-sqlite/src/db.ts +47 -0
  15. package/examples/templates/bun-caddy-sqlite/src/server.ts +44 -0
  16. package/examples/templates/bun-caddy-sqlite/template.json +10 -0
  17. package/examples/templates/caddy-proxy/Caddyfile +36 -0
  18. package/examples/templates/caddy-proxy/README.md +104 -0
  19. package/examples/templates/caddy-proxy/deploy/moshcode-dns.service +39 -0
  20. package/examples/templates/caddy-proxy/template.json +8 -0
  21. package/examples/templates/caddy-static/Caddyfile +16 -0
  22. package/examples/templates/caddy-static/README.md +90 -0
  23. package/examples/templates/caddy-static/deploy/moshcode-dns.service +39 -0
  24. package/examples/templates/caddy-static/site/index.html +11 -0
  25. package/examples/templates/caddy-static/template.json +8 -0
  26. package/install.sh +194 -0
  27. package/package.json +28 -0
  28. package/prd/0000-template.md +49 -0
  29. package/prd/0001-wrap-ugig-and-coinpay-clis.md +121 -0
  30. package/prd/0002-separate-agent-and-raw-engine-launches.md +113 -0
  31. package/prd/0003-cross-engine-mcp-and-skill-installation.md +165 -0
  32. package/prd/0004-moshscript-run-programmable-moshcode.md +344 -0
  33. package/prd/0005-hosted-moshpit-resolver.md +192 -0
  34. package/prd/0006-help.md +359 -0
  35. package/prd/0007-profullstack-site-init.md +1183 -0
  36. package/prd/README.md +26 -0
  37. package/src/ads.mjs +58 -0
  38. package/src/auth.mjs +193 -0
  39. package/src/cli-schema.mjs +533 -0
  40. package/src/cli.mjs +118 -0
  41. package/src/commands.mjs +259 -0
  42. package/src/completion.mjs +594 -0
  43. package/src/console.mjs +244 -0
  44. package/src/dns-system.mjs +404 -0
  45. package/src/dns.mjs +2872 -0
  46. package/src/doh-server.mjs +256 -0
  47. package/src/doh.mjs +218 -0
  48. package/src/engines.mjs +385 -0
  49. package/src/escalate.mjs +85 -0
  50. package/src/help.mjs +443 -0
  51. package/src/integrations.mjs +265 -0
  52. package/src/mcp-catalog.mjs +50 -0
  53. package/src/mcp.mjs +155 -0
  54. package/src/mirror.mjs +187 -0
  55. package/src/notify.mjs +86 -0
  56. package/src/open-url.mjs +34 -0
  57. package/src/parking-http.mjs +65 -0
  58. package/src/pins.mjs +190 -0
  59. package/src/pit-url.mjs +13 -0
  60. package/src/prd.mjs +341 -0
  61. package/src/pty.mjs +176 -0
  62. package/src/pwd.mjs +103 -0
  63. package/src/registry.mjs +37 -0
  64. package/src/release-install.mjs +191 -0
  65. package/src/runtime.mjs +161 -0
  66. package/src/selfupdate.mjs +215 -0
  67. package/src/serve.mjs +502 -0
  68. package/src/skills.mjs +93 -0
  69. package/src/tabs.mjs +144 -0
  70. package/src/templates.mjs +456 -0
  71. package/src/tools.mjs +231 -0
  72. package/src/trade.mjs +137 -0
  73. package/src/trust.mjs +712 -0
  74. package/src/tui.mjs +736 -0
  75. package/src/ui.mjs +49 -0
  76. package/src/uninstall.mjs +113 -0
  77. package/src/upgrade.mjs +217 -0
@@ -0,0 +1,1183 @@
1
+ ---
2
+ openprd: "0.2"
3
+ id: "0007"
4
+ title: "Generate batteries-included Profullstack sites for Moshpit names"
5
+ status: Draft
6
+ authors:
7
+ - anthony@profullstack.com
8
+ created: 2026-08-03
9
+ updated: 2026-08-03
10
+ repo: https://github.com/moshcoder/moshcode
11
+ discussion:
12
+ implementation:
13
+ tags:
14
+ - moshpit
15
+ - profullstack
16
+ - template
17
+ - site-init
18
+ - bun
19
+ - solidstart
20
+ - turso
21
+ - sqlite
22
+ - coinpay
23
+ - oauth
24
+ - blog
25
+ - rss
26
+ supersedes:
27
+ superseded-by:
28
+ ---
29
+
30
+ ## Problem
31
+
32
+ Moshcode can currently copy basic hosting templates and install web-server configuration for a Moshpit name, but a copied template is not yet a complete application. A developer still has to choose and wire a frontend framework, SSR, client navigation, an API, a database, authentication, migrations, blog routes, RSS, scheduling, deployment units, and secrets.
33
+
34
+ That work is repetitive and prevents a newly claimed Moshpit name from becoming a useful site immediately.
35
+
36
+ The Moshpit network also has unusual hosting constraints:
37
+
38
+ - Moshpit names are outside the public DNS root.
39
+ - Resolver-based visitors normally reach a name over plain HTTP.
40
+ - The public gateway strips cookies and `Authorization`.
41
+ - Pin-verified TLS is available only to Moshpit-aware clients and currently requires nginx.
42
+ - A normal OAuth callback and durable browser session therefore cannot safely depend on the plain Moshpit origin or the public gateway.
43
+
44
+ Moshcode needs a trusted, batteries-included application initializer that preserves the existing safety rule that arbitrary templates are copied but never executed.
45
+
46
+ ## Goals
47
+
48
+ 1. Let a user turn a claimed Moshpit name into a working full-stack application with one guided command.
49
+ 2. Generate one isomorphic TypeScript application whose route and component code is reused for initial SSR and subsequent SPA navigation.
50
+ 3. Include a complete versioned JSON API, CoinPay OAuth/OIDC login, durable sessions, role-based blog administration, API tokens, a blog, and an RSS feed.
51
+ 4. Use Bun for the generated application and SolidStart/SolidJS for SSR, hydration, routing, and shared UI code.
52
+ 5. Use the same application data layer against either a local SQLite-compatible file or a hosted Turso libSQL database.
53
+ 6. Prefer hosted Turso automatically when the Turso CLI is installed and authenticated; otherwise use local SQLite without blocking setup.
54
+ 7. Make the blog optional but enabled by default.
55
+ 8. Make the auto-blog pipeline optional but enabled by default, while avoiding surprise paid AI usage.
56
+ 9. Preserve the existing `moshcode template install` no-execution security boundary.
57
+ 10. Produce a deterministic, testable, resumable installer suitable for humans, CI, and coding agents.
58
+
59
+ ## Non-Goals
60
+
61
+ - Replacing `moshcode template install` with executable third-party templates.
62
+ - Building a general plugin marketplace or arbitrary template hook system.
63
+ - Making ordinary browsers trust Moshpit pin-verified TLS without a Moshpit-aware client.
64
+ - Using the public Moshpit gateway for authenticated sessions or write APIs.
65
+ - Providing local username/password authentication in v1.
66
+ - Shipping a general-purpose CMS comparable to WordPress.
67
+ - Adding a heavyweight ORM, separate backend project, separate frontend project, monorepo, React, Next.js, HTMX, or Hono JSX.
68
+ - Automatically purchasing or configuring a public DNS domain.
69
+ - Automatically spending money on an AI model.
70
+ - Supporting multi-region active-active writes to one local SQLite file.
71
+ - Solving every deployment target in v1; the supported host is a Linux server using systemd plus nginx or Caddy.
72
+
73
+ ## Users
74
+
75
+ ### Primary user: Moshpit name owner
76
+
77
+ A developer who has claimed a name such as `foo.whatever` and wants a real application rather than a static placeholder.
78
+
79
+ ### Secondary user: Coding agent
80
+
81
+ An automated coding engine that needs a predictable project layout, explicit configuration, stable API contracts, migrations, tests, and clear acceptance criteria.
82
+
83
+ ### Secondary user: Site visitor
84
+
85
+ A person using a Moshpit resolver, the public gateway, or the site's separate secure public origin to read pages and blog posts.
86
+
87
+ ### Secondary user: Site administrator
88
+
89
+ The verified CoinPay user authorized to create, edit, schedule, publish, and delete blog posts and run the auto-blog pipeline.
90
+
91
+ ## Product Decision
92
+
93
+ The bundled starter is named **`profullstack`**.
94
+
95
+ Two existing concepts remain distinct:
96
+
97
+ 1. `moshcode template install profullstack` copies files only. It never executes template code, installs packages, provisions a database, contacts CoinPay, writes secrets, changes system configuration, or starts services.
98
+ 2. `moshcode site init <name>` is a trusted workflow implemented inside Moshcode. It copies the bundled `profullstack` template and performs explicitly approved provisioning steps.
99
+
100
+ The existing `moshcode site <name>` behavior remains backward compatible and continues to plan or install web-server configuration.
101
+
102
+ ## Requirements
103
+
104
+ ### Command and compatibility
105
+
106
+ - **R1 [P0]** Add `moshcode site init <name>` as the guided application initializer.
107
+ - **R2 [P0]** Preserve the existing behavior of `moshcode site <name>`, `moshcode site <name> --install`, and all existing `site` flags.
108
+ - **R3 [P0]** Add `profullstack` to `examples/templates/` and to `moshcode template list`.
109
+ - **R4 [P0]** Keep `moshcode template install profullstack` copy-only. No file copied from any template may be executed by the installer.
110
+ - **R5 [P0]** Implement provisioning in trusted Moshcode source code, not in a `postinstall`, shell hook, package script, or executable template manifest.
111
+ - **R6 [P0]** Support interactive TTY use and deterministic non-interactive use.
112
+ - **R7 [P0]** Support `--dry-run`; it must display file operations, external commands, configuration choices, and privileged changes without writing files, creating cloud resources, registering OAuth clients, installing dependencies, or changing services.
113
+ - **R8 [P0]** Support `--json` for machine-readable results. JSON mode must write no human commentary to stdout.
114
+ - **R9 [P0]** Detect conflicts before writing any project files. Without `--force`, any conflict aborts the file-copy phase before changes are made.
115
+ - **R10 [P0]** Write a non-secret `.moshcode/site.json` state file so interrupted setup can be inspected and safely resumed.
116
+ - **R11 [P1]** Add `moshcode site init <name> --resume` and make plain re-execution detect and offer to resume an incomplete initialization.
117
+ - **R12 [P1]** Add shell completion and CLI schema entries for every new command and option.
118
+
119
+ ### CLI contract
120
+
121
+ The initializer must accept:
122
+
123
+ ```text
124
+ moshcode site init <name>
125
+
126
+ --into <directory>
127
+ --template <name> default: profullstack
128
+ --db auto|turso|sqlite default: auto
129
+ --turso-db <database-name>
130
+ --turso-group <group-name>
131
+ --auth coinpay|none default: coinpay
132
+ --auth-origin <https-url>
133
+ --owner-email <email>
134
+ --blog | --no-blog default: --blog
135
+ --autoblog | --no-autoblog default: --autoblog
136
+ --autoblog-source <source> repeatable: queue, release, ai
137
+ --port <1-65535> default: 3000
138
+ --install permit package install and system changes
139
+ --reload reload/enable services after installation
140
+ --tls request existing Moshpit pin-TLS support
141
+ --yes, -y accept safe prompts
142
+ --force overwrite conflicting project files
143
+ --dry-run
144
+ --json
145
+ ```
146
+
147
+ Rules:
148
+
149
+ - **R13 [P0]** `--yes` may accept safe application defaults but must not imply `--install`, `--reload`, OAuth client registration, cloud resource creation after an explicit failure, or destructive cleanup.
150
+ - **R14 [P0]** `--install` authorizes dependency installation and system-file writes but does not imply `--reload`.
151
+ - **R15 [P0]** `--reload` requires `--install`.
152
+ - **R16 [P0]** In a non-TTY, every value that cannot be derived safely must come from a flag or environment variable. The command must fail with a precise missing-input error rather than hang.
153
+ - **R17 [P0]** Secrets must never appear in command output, dry-run output, JSON output, process titles, or `.moshcode/site.json`.
154
+
155
+ ### Generated application stack
156
+
157
+ - **R18 [P0]** The generated runtime and package manager are Bun.
158
+ - **R19 [P0]** The generated web framework is SolidStart with SolidJS and Solid Router.
159
+ - **R20 [P0]** Initial page requests are server rendered.
160
+ - **R21 [P0]** The browser hydrates the same TSX routes and components and performs subsequent internal navigation as an SPA.
161
+ - **R22 [P0]** Shared UI code must not be duplicated into separate SSR and SPA implementations.
162
+ - **R23 [P0]** Database, OAuth, session, secret, scheduler, and privileged mutation code must remain server-only.
163
+ - **R24 [P0]** SolidStart server functions and shared domain modules are used for application UI data access; SSR routes must not make HTTP calls back into their own public API.
164
+ - **R25 [P0]** Public API routes and UI server functions must call the same server-side service layer.
165
+ - **R26 [P0]** The generated project uses strict TypeScript and includes a no-emit typecheck command.
166
+ - **R27 [P0]** Exact tested dependency versions are committed in `bun.lock`. No prerelease framework version is selected automatically.
167
+ - **R28 [P0]** The app binds to `127.0.0.1` by default. nginx or Caddy is the public client.
168
+ - **R29 [P0]** The app must boot with local SQLite and no external services after `bun install`, migration, and seed.
169
+
170
+ ### Required project layout
171
+
172
+ The generated project must follow this minimum layout:
173
+
174
+ ```text
175
+ profullstack-site/
176
+ ├── src/
177
+ │ ├── app.tsx
178
+ │ ├── app.css
179
+ │ ├── components/
180
+ │ │ ├── AppShell.tsx
181
+ │ │ ├── BlogCard.tsx
182
+ │ │ ├── BlogEditor.tsx
183
+ │ │ ├── LoginButton.tsx
184
+ │ │ └── UserMenu.tsx
185
+ │ ├── domain/
186
+ │ │ ├── api.ts
187
+ │ │ ├── auth.ts
188
+ │ │ ├── posts.ts
189
+ │ │ └── validation.ts
190
+ │ ├── routes/
191
+ │ │ ├── index.tsx
192
+ │ │ ├── account.tsx
193
+ │ │ ├── login.tsx
194
+ │ │ ├── setup.tsx
195
+ │ │ ├── blog/
196
+ │ │ │ ├── index.tsx
197
+ │ │ │ ├── [slug].tsx
198
+ │ │ │ └── feed.xml.ts
199
+ │ │ ├── admin/
200
+ │ │ │ └── blog/
201
+ │ │ │ ├── index.tsx
202
+ │ │ │ └── [id].tsx
203
+ │ │ ├── auth/
204
+ │ │ │ └── coinpay/
205
+ │ │ │ ├── index.ts
206
+ │ │ │ ├── callback.ts
207
+ │ │ │ └── logout.ts
208
+ │ │ └── api/
209
+ │ │ └── v1/
210
+ │ │ ├── index.ts
211
+ │ │ ├── health.ts
212
+ │ │ ├── session.ts
213
+ │ │ ├── openapi.json.ts
214
+ │ │ ├── posts/
215
+ │ │ │ ├── index.ts
216
+ │ │ │ └── [slug].ts
217
+ │ │ ├── account/
218
+ │ │ │ └── tokens.ts
219
+ │ │ └── admin/
220
+ │ │ ├── posts.ts
221
+ │ │ └── autoblog.ts
222
+ │ └── server/
223
+ │ ├── config.ts
224
+ │ ├── db.ts
225
+ │ ├── migrate.ts
226
+ │ ├── posts.ts
227
+ │ ├── rss.ts
228
+ │ ├── security.ts
229
+ │ ├── auth/
230
+ │ │ ├── coinpay.ts
231
+ │ │ ├── oauth-transactions.ts
232
+ │ │ ├── sessions.ts
233
+ │ │ └── api-tokens.ts
234
+ │ └── autoblog/
235
+ │ ├── index.ts
236
+ │ ├── queue.ts
237
+ │ ├── release.ts
238
+ │ └── ai.ts
239
+ ├── content/
240
+ │ └── queue/
241
+ ├── migrations/
242
+ │ └── 0001_initial.sql
243
+ ├── scripts/
244
+ │ ├── migrate.ts
245
+ │ ├── seed.ts
246
+ │ └── autoblog.ts
247
+ ├── test/
248
+ ├── public/
249
+ │ └── assets/
250
+ ├── deploy/
251
+ │ ├── profullstack.service
252
+ │ ├── profullstack-autoblog.service
253
+ │ └── profullstack-autoblog.timer
254
+ ├── .env.example
255
+ ├── .gitignore
256
+ ├── app.config.ts
257
+ ├── bun.lock
258
+ ├── Caddyfile
259
+ ├── package.json
260
+ ├── README.md
261
+ ├── template.json
262
+ └── tsconfig.json
263
+ ```
264
+
265
+ A small implementation may combine files, but it must preserve the conceptual boundaries between shared domain code, shared UI code, server-only services, public API routes, and migrations.
266
+
267
+ ### Database selection and provisioning
268
+
269
+ - **R30 [P0]** Use `@libsql/client` so one data-access API works with a local SQLite-compatible file and a hosted Turso libSQL database.
270
+ - **R31 [P0]** Do not add an ORM in v1. Use parameterized raw SQL and small repository/service functions.
271
+ - **R32 [P0]** Default database mode is `auto`.
272
+ - **R33 [P0]** In `auto` mode, look for `turso` on `PATH`.
273
+ - **R34 [P0]** When the Turso CLI is present, run `turso auth whoami`.
274
+ - **R35 [P0]** When the Turso CLI is present and authenticated, offer to create or use a hosted database. The interactive default is hosted Turso.
275
+ - **R36 [P0]** When the Turso CLI is absent, unauthenticated, declined, or unavailable, `auto` mode must fall back to local SQLite and continue.
276
+ - **R37 [P0]** Explicit `--db turso` must fail on provisioning failure and must never silently switch to local SQLite.
277
+ - **R38 [P0]** Explicit `--db sqlite` must never contact Turso.
278
+ - **R39 [P0]** In a non-TTY, `--db auto` uses Turso only when the CLI is already authenticated and all required choices are deterministic; otherwise it uses local SQLite.
279
+ - **R40 [P0]** Create hosted libSQL with:
280
+
281
+ ```sh
282
+ turso db create <database-name> --wait
283
+ turso db show <database-name> --url
284
+ turso db tokens create <database-name> --expiration never
285
+ ```
286
+
287
+ - **R41 [P0]** If an account has multiple Turso groups and no group is supplied, prompt interactively or fail non-interactively with a list of valid choices.
288
+ - **R42 [P0]** Sanitize the Moshpit name into a valid database name and append a short stable hash when needed to avoid collisions.
289
+ - **R43 [P0]** Store hosted credentials only in `.env`, with file mode `0600`.
290
+ - **R44 [P0]** Never commit `.env`, `data/*.db`, SQLite journal/WAL files, or generated tokens.
291
+ - **R45 [P0]** Local configuration is:
292
+
293
+ ```env
294
+ DATABASE_URL=file:./data/app.db
295
+ TURSO_AUTH_TOKEN=
296
+ ```
297
+
298
+ - **R46 [P0]** Hosted configuration is:
299
+
300
+ ```env
301
+ DATABASE_URL=libsql://<database-name>-<organization>.turso.io
302
+ TURSO_AUTH_TOKEN=<redacted>
303
+ ```
304
+
305
+ - **R47 [P0]** The application creates the parent directory for a local database before opening the file.
306
+ - **R48 [P0]** The migration runner is idempotent, transactional where SQLite allows, records applied migration filename and checksum, and refuses a changed checksum for an already-applied migration.
307
+ - **R49 [P0]** If a cloud database is created and a later step fails, do not destroy it automatically. Report its name and the exact resume or cleanup command.
308
+ - **R50 [P1]** Add a documented database-token rotation procedure.
309
+
310
+ ### Initial database schema
311
+
312
+ The first migration must create at least:
313
+
314
+ ```text
315
+ schema_migrations
316
+ users
317
+ sessions
318
+ oauth_transactions
319
+ api_tokens
320
+ posts
321
+ autoblog_runs
322
+ settings
323
+ ```
324
+
325
+ Minimum fields and constraints:
326
+
327
+ ```text
328
+ schema_migrations
329
+ filename TEXT PRIMARY KEY
330
+ checksum TEXT NOT NULL
331
+ applied_at INTEGER NOT NULL
332
+
333
+ users
334
+ id TEXT PRIMARY KEY
335
+ coinpay_sub TEXT NOT NULL UNIQUE
336
+ email TEXT
337
+ email_verified INTEGER NOT NULL DEFAULT 0
338
+ display_name TEXT
339
+ avatar_url TEXT
340
+ role TEXT NOT NULL DEFAULT 'user'
341
+ created_at INTEGER NOT NULL
342
+ updated_at INTEGER NOT NULL
343
+ last_login_at INTEGER
344
+
345
+ sessions
346
+ id TEXT PRIMARY KEY
347
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE
348
+ token_hash TEXT NOT NULL UNIQUE
349
+ created_at INTEGER NOT NULL
350
+ expires_at INTEGER NOT NULL
351
+ last_seen_at INTEGER NOT NULL
352
+ user_agent_hash TEXT
353
+ ip_prefix_hash TEXT
354
+
355
+ oauth_transactions
356
+ id TEXT PRIMARY KEY
357
+ state_hash TEXT NOT NULL UNIQUE
358
+ pkce_verifier_ciphertext TEXT NOT NULL
359
+ return_to TEXT NOT NULL
360
+ created_at INTEGER NOT NULL
361
+ expires_at INTEGER NOT NULL
362
+ consumed_at INTEGER
363
+
364
+ api_tokens
365
+ id TEXT PRIMARY KEY
366
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE
367
+ name TEXT NOT NULL
368
+ token_prefix TEXT NOT NULL
369
+ token_hash TEXT NOT NULL UNIQUE
370
+ scopes TEXT NOT NULL
371
+ created_at INTEGER NOT NULL
372
+ expires_at INTEGER
373
+ last_used_at INTEGER
374
+ revoked_at INTEGER
375
+
376
+ posts
377
+ id TEXT PRIMARY KEY
378
+ slug TEXT NOT NULL UNIQUE
379
+ title TEXT NOT NULL
380
+ excerpt TEXT
381
+ body_markdown TEXT NOT NULL
382
+ body_html TEXT NOT NULL
383
+ status TEXT NOT NULL
384
+ source TEXT NOT NULL
385
+ author_user_id TEXT REFERENCES users(id)
386
+ scheduled_at INTEGER
387
+ published_at INTEGER
388
+ created_at INTEGER NOT NULL
389
+ updated_at INTEGER NOT NULL
390
+
391
+ autoblog_runs
392
+ id TEXT PRIMARY KEY
393
+ source TEXT NOT NULL
394
+ status TEXT NOT NULL
395
+ input_ref TEXT
396
+ post_id TEXT REFERENCES posts(id)
397
+ error_code TEXT
398
+ error_message TEXT
399
+ started_at INTEGER NOT NULL
400
+ completed_at INTEGER
401
+
402
+ settings
403
+ key TEXT PRIMARY KEY
404
+ value_json TEXT NOT NULL
405
+ updated_at INTEGER NOT NULL
406
+ ```
407
+
408
+ Required indexes:
409
+
410
+ ```text
411
+ posts(status, published_at)
412
+ posts(status, scheduled_at)
413
+ sessions(expires_at)
414
+ oauth_transactions(expires_at)
415
+ api_tokens(user_id, revoked_at)
416
+ autoblog_runs(started_at)
417
+ ```
418
+
419
+ Allowed role values in v1 are `owner`, `editor`, and `user`.
420
+
421
+ Allowed post states in v1 are `draft`, `scheduled`, `published`, and `archived`.
422
+
423
+ ### CoinPay OAuth/OIDC
424
+
425
+ - **R51 [P0]** Authentication provider is CoinPay OAuth 2.0/OIDC authorization code flow with S256 PKCE.
426
+ - **R52 [P0]** Default scopes are `openid profile email`.
427
+ - **R53 [P1]** Optional scopes are `did` and `wallet:read`; they are never requested by default.
428
+ - **R54 [P0]** The installer supports:
429
+ 1. automatic client registration using a user-supplied CoinPay API token;
430
+ 2. entry of an existing client ID and client secret; or
431
+ 3. configure-later mode.
432
+ - **R55 [P0]** CoinPay secrets are entered without terminal echo and are written only to `.env`.
433
+ - **R56 [P0]** Automatic registration calls the authenticated CoinPay client-registration API with the final HTTPS callback URI and requested scopes.
434
+ - **R57 [P0]** If automatic registration succeeds, the client secret is persisted immediately because it may be shown only once.
435
+ - **R58 [P0]** If auth is selected but credentials are absent, the application still boots and `/api/v1/health` reports `auth.configured: false`; login UI must explain the exact missing configuration.
436
+ - **R59 [P0]** OAuth transactions contain a cryptographically random state value, a PKCE verifier, an allowed post-login return path, creation time, expiry, and single-use marker.
437
+ - **R60 [P0]** Store only a hash of the state token. Protect the PKCE verifier at rest with authenticated encryption derived from `SESSION_SECRET`.
438
+ - **R61 [P0]** OAuth transactions expire after ten minutes and are consumed atomically.
439
+ - **R62 [P0]** The callback rejects missing, expired, reused, or mismatched state before exchanging the code.
440
+ - **R63 [P0]** The authorization code exchange and UserInfo request occur only on the server.
441
+ - **R64 [P0]** The `sub` returned by the verified CoinPay identity is the stable user key. Email is not the primary key.
442
+ - **R65 [P0]** The implementation must not hand-roll JWT or JOSE verification. It must use a maintained library and the canonical CoinPay verification metadata confirmed before release.
443
+ - **R66 [P0]** Access, refresh, and ID tokens are not persisted for login-only use after identity establishment. Persist provider tokens only if a future feature explicitly needs delegated CoinPay API access.
444
+ - **R67 [P0]** Logout destroys the local session. Provider-token revocation is performed only if CoinPay exposes and documents a revocation endpoint.
445
+ - **R68 [P0]** Authentication errors return safe user messages and structured internal error codes without exposing provider responses, client secrets, codes, state, or tokens.
446
+ - **R69 [P0]** The installer asks for `OWNER_EMAIL`. The first CoinPay user whose verified email matches it is assigned `owner`.
447
+ - **R70 [P0]** If no owner email is configured, no user becomes owner automatically. The generated README must document the safe owner-bootstrap command.
448
+ - **R71 [P1]** Add a local administrative CLI script that can assign or revoke roles by CoinPay `sub` or verified email while running on the host.
449
+
450
+ #### Configurable CoinPay endpoints
451
+
452
+ Until CoinPay's public OAuth prompt and API documentation use one canonical endpoint set, the template must keep endpoint URLs configurable:
453
+
454
+ ```env
455
+ COINPAY_BASE_URL=https://coinpayportal.com
456
+ COINPAY_AUTHORIZE_PATH=/api/oauth/authorize
457
+ COINPAY_TOKEN_PATH=/api/oauth/token
458
+ COINPAY_USERINFO_PATH=/api/oauth/userinfo
459
+ COINPAY_CLIENTS_PATH=/api/oauth/clients
460
+ COINPAY_JWKS_PATH=/api/oauth/jwks
461
+ ```
462
+
463
+ Release is blocked until an integration test passes against the canonical production endpoint set and the chosen ID-token verification method is confirmed.
464
+
465
+ ### Session and API authentication
466
+
467
+ - **R72 [P0]** Generate `SESSION_SECRET` from at least 32 cryptographically random bytes.
468
+ - **R73 [P0]** Browser sessions use an opaque random token; store only its cryptographic hash in the database.
469
+ - **R74 [P0]** On a normal CA-valid HTTPS auth origin, the session cookie is `HttpOnly`, `Secure`, `SameSite=Lax`, host-only, and uses a `__Host-` prefix.
470
+ - **R75 [P0]** Rotate the session token after login and privilege changes.
471
+ - **R76 [P0]** Default session lifetime is 30 days with idle activity tracking and server-side revocation.
472
+ - **R77 [P0]** State-changing browser requests require same-origin validation and CSRF protection appropriate to SolidStart actions.
473
+ - **R78 [P0]** User-created API tokens are prefixed, shown once, stored as hashes, scoped, revocable, and accepted only over a CA-valid HTTPS origin.
474
+ - **R79 [P0]** Default API-token scopes are read-only. Write scopes require an explicit selection.
475
+ - **R80 [P0]** Authentication middleware must distinguish browser sessions from bearer API tokens and produce a normalized internal principal.
476
+ - **R81 [P0]** Admin blog mutations require `owner` or `editor`.
477
+ - **R82 [P0]** Owner-only operations include role management, OAuth configuration status, and destructive site settings.
478
+ - **R83 [P0]** Return `401` for unauthenticated requests and `403` for authenticated principals lacking permission.
479
+
480
+ ### Moshpit origin and secure auth origin
481
+
482
+ - **R84 [P0]** The generated app supports two logical origins served by the same application:
483
+
484
+ ```env
485
+ MOSHPIT_ORIGIN=http://foo.whatever
486
+ AUTH_ORIGIN=https://foo.apps.example.com
487
+ PUBLIC_CANONICAL_ORIGIN=https://foo.apps.example.com
488
+ ```
489
+
490
+ - **R85 [P0]** `AUTH_ORIGIN` must be a normal, CA-valid HTTPS URL for stock-browser CoinPay OAuth and authenticated sessions.
491
+ - **R86 [P0]** The plain Moshpit origin may serve public pages, public blog posts, RSS, health, and read-only API routes.
492
+ - **R87 [P0]** The plain Moshpit origin must not issue login sessions, accept bearer tokens, render account data, expose admin UI, or accept state-changing requests.
493
+ - **R88 [P0]** A login link opened on the Moshpit origin redirects to the equivalent route on `AUTH_ORIGIN`.
494
+ - **R89 [P0]** The public gateway is treated as anonymous and read-only because it strips cookies and `Authorization`.
495
+ - **R90 [P0]** Host/origin policy must be enforced on the server, not only hidden in the frontend.
496
+ - **R91 [P0]** `PUBLIC_CANONICAL_ORIGIN` supplies absolute public links in RSS, Open Graph metadata, canonical tags, and API documentation.
497
+ - **R92 [P1]** When `moshcode site <name> --tls` is used with nginx, the generated app may advertise the pin-verified Moshpit HTTPS origin to compatible clients, but it is not the default OAuth callback in v1.
498
+ - **R93 [P1]** Add platform support for automatically allocating a CA-valid companion hostname under a Moshcode-owned public domain. Until that service exists, prompt for `AUTH_ORIGIN`.
499
+
500
+ ### Public JSON API
501
+
502
+ All API endpoints are versioned beneath `/api/v1`.
503
+
504
+ Public endpoints:
505
+
506
+ ```text
507
+ GET /api/v1
508
+ GET /api/v1/health
509
+ GET /api/v1/session
510
+ GET /api/v1/posts
511
+ GET /api/v1/posts/:slug
512
+ GET /api/v1/openapi.json
513
+ ```
514
+
515
+ Authenticated account endpoints:
516
+
517
+ ```text
518
+ GET /api/v1/account/tokens
519
+ POST /api/v1/account/tokens
520
+ DELETE /api/v1/account/tokens/:id
521
+ ```
522
+
523
+ Administrative endpoints:
524
+
525
+ ```text
526
+ POST /api/v1/admin/posts
527
+ PATCH /api/v1/admin/posts/:id
528
+ DELETE /api/v1/admin/posts/:id
529
+ POST /api/v1/admin/autoblog/run
530
+ GET /api/v1/admin/autoblog/runs
531
+ ```
532
+
533
+ - **R94 [P0]** Use JSON request and response bodies except endpoints that explicitly return HTML, redirects, or XML.
534
+ - **R95 [P0]** Successful list responses use:
535
+
536
+ ```json
537
+ {
538
+ "data": [],
539
+ "meta": {
540
+ "limit": 20,
541
+ "cursor": null,
542
+ "next_cursor": null
543
+ }
544
+ }
545
+ ```
546
+
547
+ - **R96 [P0]** Errors use:
548
+
549
+ ```json
550
+ {
551
+ "error": {
552
+ "code": "machine_readable_code",
553
+ "message": "Safe human-readable message",
554
+ "request_id": "opaque-id"
555
+ }
556
+ }
557
+ ```
558
+
559
+ - **R97 [P0]** Validate every path, query, header, and body input using shared schemas.
560
+ - **R98 [P0]** Pagination is cursor-based, stable, and bounded. Default limit is 20; maximum is 100.
561
+ - **R99 [P0]** Public post responses include only published posts whose `published_at` is not in the future.
562
+ - **R100 [P0]** OpenAPI output documents auth methods, schemas, errors, and every v1 endpoint.
563
+ - **R101 [P0]** CORS is disabled by default except same-origin requests. Additional allowed origins require explicit configuration.
564
+ - **R102 [P0]** API responses include a request ID, `X-Content-Type-Options: nosniff`, and appropriate cache headers.
565
+ - **R103 [P0]** Read-only public endpoints may be cached; session, token, and admin endpoints must be `no-store`.
566
+ - **R104 [P1]** Add conditional GET support (`ETag` and/or `Last-Modified`) to post list and detail responses.
567
+
568
+ ### Blog
569
+
570
+ - **R105 [P0]** Blog is installed and enabled by default.
571
+ - **R106 [P0]** `--no-blog` keeps the code in the template but sets `BLOG_ENABLED=false`, removes blog navigation, disables auto-blog, and returns `404` from blog and feed routes.
572
+ - **R107 [P0]** Public routes are:
573
+
574
+ ```text
575
+ /blog
576
+ /blog/:slug
577
+ /blog/feed.xml
578
+ ```
579
+
580
+ - **R108 [P0]** Admin route is `/admin/blog` with create, edit, preview, schedule, publish, archive, and delete actions.
581
+ - **R109 [P0]** Blog pages use the same Solid components during SSR and hydrated SPA navigation.
582
+ - **R110 [P0]** Posts are authored in Markdown.
583
+ - **R111 [P0]** Markdown is rendered and sanitized on the server with maintained open-source libraries. Unsanitized HTML must never be rendered.
584
+ - **R112 [P0]** Slugs are normalized, unique, stable after publication unless explicitly changed, and validated against reserved routes.
585
+ - **R113 [P0]** A changed published slug records or generates a permanent redirect from the old slug.
586
+ - **R114 [P0]** The installer seeds one published welcome post so `/blog`, a post detail page, the posts API, and RSS are immediately non-empty.
587
+ - **R115 [P0]** Draft and scheduled posts are never exposed to anonymous visitors.
588
+ - **R116 [P0]** The admin editor uses progressive forms/actions that work after hydration and report validation errors without losing content.
589
+ - **R117 [P1]** Support tags and tag-filtered blog pages.
590
+ - **R118 [P1]** Support image attachments through a configurable object-storage adapter; v1 does not require this.
591
+
592
+ ### RSS
593
+
594
+ - **R119 [P0]** `/blog/feed.xml` returns RSS 2.0 as `application/rss+xml; charset=utf-8`.
595
+ - **R120 [P0]** The feed includes an Atom self-link, channel title, description, canonical site link, language, last build date, stable GUIDs, publication dates, excerpts, and sanitized full HTML in `content:encoded`.
596
+ - **R121 [P0]** Every XML field and URL is escaped correctly.
597
+ - **R122 [P0]** Only currently published posts appear.
598
+ - **R123 [P0]** Feed links use `PUBLIC_CANONICAL_ORIGIN`, not an untrusted request `Host` header.
599
+ - **R124 [P0]** The feed supports `ETag`, `Last-Modified`, and `304 Not Modified`.
600
+ - **R125 [P0]** RSS generation is covered by an XML-parser test, not only string snapshots.
601
+ - **R126 [P0]** When blog is disabled, the feed returns `404`.
602
+
603
+ ### Auto-blog
604
+
605
+ - **R127 [P0]** Auto-blog is enabled by default when the blog is enabled.
606
+ - **R128 [P0]** `--no-autoblog` sets `AUTOBLOG_ENABLED=false` and does not install or enable the timer.
607
+ - **R129 [P0]** Auto-blog has a provider-neutral pipeline and records every attempted run.
608
+ - **R130 [P0]** Default sources are `queue` and `release`.
609
+ - **R131 [P0]** Queue source imports Markdown files from `content/queue/` exactly once.
610
+ - **R132 [P0]** Release source can turn a local changelog or supplied release JSON into a post without requiring an AI provider.
611
+ - **R133 [P0]** AI source is included but inactive until an endpoint, model, and credential are explicitly configured.
612
+ - **R134 [P0]** Enabling auto-blog must not itself make paid model calls.
613
+ - **R135 [P0]** The baseline AI adapter uses an OpenAI-compatible HTTP API through `fetch` and does not require a vendor SDK.
614
+ - **R136 [P0]** AI output is validated, rendered, sanitized, and subject to the same post rules as manually authored content.
615
+ - **R137 [P0]** Default auto-publish behavior is:
616
+ - queue and release sources: publish;
617
+ - AI source: create a draft until `AUTOBLOG_AI_AUTOPUBLISH=true`.
618
+ - **R138 [P0]** A systemd oneshot service and timer run the pipeline on a configurable schedule. Default schedule is daily at 09:00 server local time.
619
+ - **R139 [P0]** The job uses a database lock/lease so concurrent timer, API, or manual runs cannot process the same input twice.
620
+ - **R140 [P0]** Each run records source, status, input reference, resulting post, timestamps, and a safe error.
621
+ - **R141 [P0]** Manual execution is available through `bun run autoblog` and the authorized admin API.
622
+ - **R142 [P0]** A failed item does not abort unrelated queued items.
623
+ - **R143 [P0]** Re-running a source is idempotent based on a stable input hash.
624
+ - **R144 [P1]** Add GitHub release ingestion through a webhook or scheduled fetch.
625
+ - **R145 [P1]** Add OpenRouter, OpenAI, Anthropic, and local Ollama presets while retaining the generic OpenAI-compatible adapter.
626
+
627
+ ### Configuration
628
+
629
+ The generated `.env.example` must include:
630
+
631
+ ```env
632
+ APP_NAME=foo.whatever
633
+ HOST=127.0.0.1
634
+ PORT=3000
635
+ NODE_ENV=development
636
+
637
+ MOSHPIT_ORIGIN=http://foo.whatever
638
+ AUTH_ORIGIN=https://foo.apps.example.com
639
+ PUBLIC_CANONICAL_ORIGIN=https://foo.apps.example.com
640
+ ALLOWED_ORIGINS=https://foo.apps.example.com
641
+
642
+ DATABASE_URL=file:./data/app.db
643
+ TURSO_AUTH_TOKEN=
644
+
645
+ COINPAY_BASE_URL=https://coinpayportal.com
646
+ COINPAY_AUTHORIZE_PATH=/api/oauth/authorize
647
+ COINPAY_TOKEN_PATH=/api/oauth/token
648
+ COINPAY_USERINFO_PATH=/api/oauth/userinfo
649
+ COINPAY_CLIENTS_PATH=/api/oauth/clients
650
+ COINPAY_JWKS_PATH=/api/oauth/jwks
651
+ COINPAY_CLIENT_ID=
652
+ COINPAY_CLIENT_SECRET=
653
+ COINPAY_SCOPES="openid profile email"
654
+
655
+ SESSION_SECRET=
656
+ SESSION_TTL_DAYS=30
657
+ OWNER_EMAIL=
658
+
659
+ BLOG_ENABLED=true
660
+ BLOG_TITLE=foo.whatever
661
+ BLOG_DESCRIPTION="Updates from foo.whatever"
662
+ BLOG_LANGUAGE=en-us
663
+ BLOG_POSTS_PER_PAGE=20
664
+
665
+ AUTOBLOG_ENABLED=true
666
+ AUTOBLOG_SOURCES=queue,release
667
+ AUTOBLOG_AI_AUTOPUBLISH=false
668
+ AUTOBLOG_OPENAI_BASE_URL=
669
+ AUTOBLOG_OPENAI_API_KEY=
670
+ AUTOBLOG_OPENAI_MODEL=
671
+ ```
672
+
673
+ - **R146 [P0]** Configuration is parsed once at startup and validated before the server accepts requests.
674
+ - **R147 [P0]** Missing required production values produce one actionable startup report listing all invalid fields.
675
+ - **R148 [P0]** Public environment values and server secrets are defined in separate typed configuration objects.
676
+ - **R149 [P0]** No secret is serialized into SSR payloads or browser bundles.
677
+ - **R150 [P0]** The health endpoint reports feature configuration as booleans and modes, never secret values.
678
+
679
+ ### Installer flow
680
+
681
+ Interactive default flow:
682
+
683
+ ```text
684
+ $ moshcode site init foo.whatever
685
+
686
+ Creating a Profullstack application for foo.whatever
687
+
688
+ Project directory: ./foo.whatever
689
+ Runtime: Bun
690
+ UI: SolidStart
691
+ API: /api/v1
692
+ Blog: /blog
693
+ RSS: /blog/feed.xml
694
+
695
+ ✓ Bun found
696
+
697
+ ✓ Turso CLI found
698
+ ✓ Turso login found
699
+
700
+ Use a hosted Turso database? [Y/n]
701
+ Database name [foo-whatever]:
702
+ Create database now? [Y/n]
703
+
704
+ ✓ Database created
705
+ ✓ Database URL retrieved
706
+ ✓ Database token created
707
+ ✓ Credentials written to .env (0600)
708
+
709
+ Enable blog? [Y/n]
710
+ Enable auto-blog? [Y/n]
711
+ Auto-blog sources [queue,release]:
712
+
713
+ Configure CoinPay login now? [Y/n]
714
+ Secure HTTPS origin:
715
+ Owner CoinPay email:
716
+ CoinPay setup:
717
+ 1. Register a new OAuth client with an API token
718
+ 2. Enter an existing client ID and secret
719
+ 3. Configure later
720
+
721
+ Install Bun dependencies? [Y/n]
722
+ Run migrations and seed data? [Y/n]
723
+ Build production bundle? [Y/n]
724
+ Install systemd and web-server configuration? [y/N]
725
+ Reload and enable services now? [y/N]
726
+
727
+ ✓ Application initialized
728
+ ```
729
+
730
+ - **R151 [P0]** Prompt order must avoid requesting secrets before the final callback URI and database choice are known.
731
+ - **R152 [P0]** Every external command's exit code and stderr are checked.
732
+ - **R153 [P0]** The installer stops at the first failed dependent step and prints completed steps plus the exact resume command.
733
+ - **R154 [P0]** Sensitive prompts use no-echo input.
734
+ - **R155 [P0]** The final summary contains:
735
+ - project directory;
736
+ - database mode and resource name;
737
+ - auth configured/not configured;
738
+ - public and secure origins;
739
+ - enabled features;
740
+ - service names;
741
+ - local development command;
742
+ - production start command;
743
+ - remaining manual actions.
744
+ - **R156 [P0]** The final summary never prints a full database token, OAuth client secret, session secret, API token, authorization code, or state.
745
+ - **R157 [P0]** If Bun is missing, offer the official Bun installation path interactively. Without approval, generate the project and print the install command; do not silently run a remote shell script.
746
+ - **R158 [P0]** If Turso is missing, local SQLite fallback is immediate; do not require Turso installation.
747
+ - **R159 [P0]** If CoinPay automatic registration is unavailable, provide the exact callback URI and scopes needed for manual dashboard registration.
748
+
749
+ ### Safe template rendering
750
+
751
+ - **R160 [P0]** The initializer may replace documented inert tokens such as `__APP_NAME__`, `__APP_SLUG__`, `__MOSHPIT_ORIGIN__`, `__AUTH_ORIGIN__`, and `__PORT__`.
752
+ - **R161 [P0]** Only trusted initializer code performs token replacement.
753
+ - **R162 [P0]** Template values are escaped for the target format; a Moshpit name must not be able to inject TypeScript, JSON, shell, systemd, nginx, or Caddy syntax.
754
+ - **R163 [P0]** No arbitrary expression evaluation, JavaScript evaluation, shell interpolation, or user-provided template engine is allowed.
755
+ - **R164 [P0]** Generated systemd unit names and paths use sanitized identifiers and absolute paths.
756
+
757
+ ### Deployment
758
+
759
+ - **R165 [P0]** The generated application includes a systemd service that runs the built SolidStart Bun server from an unprivileged user and restarts on failure.
760
+ - **R166 [P0]** The service binds to `127.0.0.1:<port>`.
761
+ - **R167 [P0]** The service loads secrets from an environment file with restrictive permissions.
762
+ - **R168 [P0]** The service uses a dedicated writable data directory and reasonable systemd hardening that does not break Bun or SQLite.
763
+ - **R169 [P0]** The initializer can call the existing trusted `moshcode site <name> --proxy <port>` planning/install path rather than reimplementing web-server detection.
764
+ - **R170 [P0]** Caddy serves the HTTP Moshpit origin and reverse-proxies to Bun.
765
+ - **R171 [P0]** nginx may serve both plain HTTP and registry pin-verified TLS using the existing `--tls` behavior.
766
+ - **R172 [P0]** Port 80 never redirects unconditionally to pin-verified port 443.
767
+ - **R173 [P0]** A separate CA-valid `AUTH_ORIGIN` reverse proxy is documented and generated when its hostname points to the server.
768
+ - **R174 [P0]** `--install` may write project service units and web-server configuration only after showing a plan.
769
+ - **R175 [P0]** `--reload` validates configuration before reloading and must not take unrelated sites down on invalid generated configuration.
770
+ - **R176 [P0]** Auto-blog timer installation is skipped when blog or auto-blog is disabled.
771
+ - **R177 [P1]** Add Railway deployment documentation and an external scheduler alternative to systemd.
772
+
773
+ ### Security and privacy
774
+
775
+ - **R178 [P0]** Use parameterized SQL everywhere.
776
+ - **R179 [P0]** Normalize and validate all redirect targets; post-login `return_to` values must be local paths from an allowlist and must not permit open redirects.
777
+ - **R180 [P0]** Enforce maximum request-body sizes for JSON and forms.
778
+ - **R181 [P0]** Add conservative rate limits for OAuth initiation/callback, session creation, token creation, admin mutations, and auto-blog execution.
779
+ - **R182 [P0]** Do not trust proxy headers unless the request came from the configured local reverse proxy.
780
+ - **R183 [P0]** Escape HTML by default and sanitize rendered Markdown.
781
+ - **R184 [P0]** Add a Content Security Policy compatible with SolidStart and the site's own assets.
782
+ - **R185 [P0]** Do not store raw IP addresses by default. If abuse controls use client metadata, store a keyed or salted coarse-prefix hash with a retention policy.
783
+ - **R186 [P0]** Redact secrets and tokens from logs.
784
+ - **R187 [P0]** Log structured request IDs, route, status, duration, and safe error codes.
785
+ - **R188 [P0]** Refuse authenticated writes when the request arrives through the plain Moshpit origin, gateway, or an unapproved host.
786
+ - **R189 [P0]** Dependency and lockfile scanning must run in CI.
787
+ - **R190 [P0]** The generated README must clearly label plain Moshpit HTTP as public/non-sensitive and `AUTH_ORIGIN` as the only stock-browser authenticated origin.
788
+
789
+ ### Observability and health
790
+
791
+ - **R191 [P0]** `GET /api/v1/health` returns at least:
792
+
793
+ ```json
794
+ {
795
+ "ok": true,
796
+ "version": "0.1.0",
797
+ "runtime": "bun",
798
+ "database": {
799
+ "mode": "sqlite",
800
+ "reachable": true,
801
+ "migrations_current": true
802
+ },
803
+ "auth": {
804
+ "provider": "coinpay",
805
+ "configured": false
806
+ },
807
+ "blog": {
808
+ "enabled": true
809
+ },
810
+ "autoblog": {
811
+ "enabled": true,
812
+ "sources": ["queue", "release"]
813
+ }
814
+ }
815
+ ```
816
+
817
+ - **R192 [P0]** Health must return a non-2xx status when the database is unreachable or required migrations are missing.
818
+ - **R193 [P0]** A liveness route must avoid expensive external calls.
819
+ - **R194 [P1]** Add a separate readiness route when deployment targets need it.
820
+ - **R195 [P0]** Auto-blog run failures are visible in admin UI and API without exposing model prompts, credentials, or provider-sensitive payloads.
821
+
822
+ ### Documentation
823
+
824
+ - **R196 [P0]** Add `docs/profullstack-template.md`.
825
+ - **R197 [P0]** Update `docs/hosting-a-moshpit-name.md` to explain:
826
+ - copy-only templates;
827
+ - trusted `site init`;
828
+ - plain HTTP;
829
+ - pin-verified TLS;
830
+ - gateway stripping;
831
+ - the need for a CA-valid auth origin.
832
+ - **R198 [P0]** Update the root README's template and site sections.
833
+ - **R199 [P0]** Generated README includes local development, Turso setup, CoinPay client setup, owner bootstrap, migrations, blog management, RSS, auto-blog, API examples, deployment, backup, restore, and token rotation.
834
+ - **R200 [P0]** API examples use `curl` and never include real secrets.
835
+ - **R201 [P0]** Add a troubleshooting matrix for resolver, web server, Bun service, database, OAuth callback, session cookie, RSS, and timer failures.
836
+
837
+ ## UX Notes
838
+
839
+ ### First-run principles
840
+
841
+ - Prefer useful defaults.
842
+ - Show the user the architecture before asking questions.
843
+ - Ask one decision at a time.
844
+ - Do not ask for a value that can be safely derived.
845
+ - Do not hide fallback behavior.
846
+ - Never print secrets back to the terminal.
847
+ - Every cloud or privileged action must be visible before execution.
848
+ - A failed optional integration must not prevent a local application from running.
849
+
850
+ ### Database states
851
+
852
+ | State | Interactive behavior | Non-interactive `--db auto` |
853
+ |---|---|---|
854
+ | Turso absent | Explain local fallback; continue | Use SQLite |
855
+ | Turso present, logged out | Offer login; fallback if declined | Use SQLite |
856
+ | Turso present, logged in | Hosted Turso is default choice | Use Turso only when group/name are deterministic |
857
+ | Explicit `--db turso` fails | Stop with actionable error | Stop with actionable error |
858
+ | Explicit `--db sqlite` | Never contact Turso | Never contact Turso |
859
+
860
+ ### Auth states
861
+
862
+ | State | Application behavior |
863
+ |---|---|
864
+ | CoinPay configured + HTTPS auth origin | Login works |
865
+ | CoinPay selected but credentials missing | App boots; login page shows setup instructions |
866
+ | Plain Moshpit origin | Public/read-only; login redirects to secure origin |
867
+ | Public gateway | Anonymous/read-only |
868
+ | Secure origin with bad/missing host | Reject request |
869
+ | Callback state expired/reused | Reject and offer restart login |
870
+
871
+ ### Blog states
872
+
873
+ | Setting | `/blog` | `/blog/feed.xml` | Admin | Timer |
874
+ |---|---|---|---|---|
875
+ | Blog on, auto-blog on | Enabled | Enabled | Enabled | Enabled |
876
+ | Blog on, auto-blog off | Enabled | Enabled | Enabled | Disabled |
877
+ | Blog off | 404 | 404 | 404 | Disabled |
878
+
879
+ ### Code-reuse rule
880
+
881
+ The required rendering lifecycle is:
882
+
883
+ ```text
884
+ same Solid route/component code
885
+
886
+ ├── initial request: rendered by Bun/SolidStart
887
+
888
+ └── after hydration: reused by Solid Router as SPA UI
889
+ ```
890
+
891
+ No feature may ship separate React-like client and server component trees for the same page.
892
+
893
+ ## Acceptance Criteria
894
+
895
+ ### A. Local zero-cloud setup
896
+
897
+ Given a machine with Bun but no Turso CLI, when the user runs:
898
+
899
+ ```sh
900
+ moshcode site init foo.whatever --db auto --auth none --yes
901
+ ```
902
+
903
+ then:
904
+
905
+ 1. a project is generated without contacting Turso;
906
+ 2. `DATABASE_URL=file:./data/app.db` is configured;
907
+ 3. migrations apply;
908
+ 4. a welcome post is seeded;
909
+ 5. `bun run dev` starts successfully;
910
+ 6. `/`, `/blog`, `/blog/<welcome-slug>`, `/blog/feed.xml`, `/api/v1/health`, and `/api/v1/posts` return valid responses;
911
+ 7. internal navigation hydrates and continues without full-page reloads;
912
+ 8. no secret or database file is tracked by Git.
913
+
914
+ ### B. Hosted Turso setup
915
+
916
+ Given an authenticated Turso CLI, when the user accepts the hosted default, then:
917
+
918
+ 1. exactly one database is created or selected;
919
+ 2. the database URL and token are retrieved;
920
+ 3. `.env` is mode `0600`;
921
+ 4. secrets are redacted from output;
922
+ 5. migrations and seed run against the hosted database;
923
+ 6. re-running the initializer does not create a duplicate database.
924
+
925
+ ### C. Explicit Turso failure
926
+
927
+ Given `--db turso`, when Turso provisioning fails, then:
928
+
929
+ 1. setup exits non-zero;
930
+ 2. it does not switch to SQLite;
931
+ 3. it reports completed resources and the resume command;
932
+ 4. it does not destroy a created database automatically.
933
+
934
+ ### D. CoinPay login
935
+
936
+ Given a CA-valid `AUTH_ORIGIN` and valid CoinPay client credentials, then:
937
+
938
+ 1. login initiates authorization code flow with S256 PKCE and state;
939
+ 2. callback validates state before token exchange;
940
+ 3. a verified CoinPay identity creates or updates one local user keyed by `sub`;
941
+ 4. the matching verified owner email receives the `owner` role;
942
+ 5. a hashed opaque session is stored;
943
+ 6. a secure host-only cookie is set;
944
+ 7. logout revokes the local session;
945
+ 8. the Moshpit HTTP origin never receives or accepts the authenticated cookie.
946
+
947
+ ### E. Blog and RSS
948
+
949
+ Given the seeded welcome post, then:
950
+
951
+ 1. it appears on `/blog`;
952
+ 2. it has a server-rendered detail page;
953
+ 3. it appears in `/api/v1/posts`;
954
+ 4. it appears in valid RSS;
955
+ 5. draft and future scheduled posts do not appear publicly;
956
+ 6. RSS supports conditional requests.
957
+
958
+ ### F. Auto-blog
959
+
960
+ Given the default queue source, when a unique Markdown file is placed in `content/queue/` and the job runs twice, then:
961
+
962
+ 1. one post is created and published;
963
+ 2. one successful run is recorded;
964
+ 3. the second execution does not create a duplicate;
965
+ 4. no AI provider is called.
966
+
967
+ ### G. Template safety
968
+
969
+ Given a remote template containing a `postinstall`, executable script, symlink, or malicious manifest, then `moshcode template install` continues to copy only permitted regular files and does not execute anything.
970
+
971
+ Given the bundled `profullstack` template, `moshcode site init` performs only operations explicitly implemented in trusted Moshcode source.
972
+
973
+ ### H. Dry run
974
+
975
+ Given any valid initializer command with `--dry-run`, then:
976
+
977
+ 1. no files are written;
978
+ 2. no cloud resources are created;
979
+ 3. no package installer is run;
980
+ 4. no OAuth client is registered;
981
+ 5. no system service or web server is changed;
982
+ 6. all planned actions are displayed with secrets redacted.
983
+
984
+ ## Testing Requirements
985
+
986
+ ### Moshcode CLI tests
987
+
988
+ - Argument parsing for all flags, aliases, conflicts, and missing values.
989
+ - Backward compatibility for existing `moshcode site <name>` behavior.
990
+ - TTY and non-TTY decision matrix.
991
+ - Turso absent, logged-out, logged-in, multi-group, create success, partial failure, and explicit failure.
992
+ - Secret redaction in text, JSON, error, and dry-run output.
993
+ - File-conflict atomicity.
994
+ - Resume and idempotency.
995
+ - Safe template-token rendering against injection payloads.
996
+ - No execution through template installation.
997
+ - Privileged plan generation without applying it.
998
+ - Mock CoinPay registration success and failure.
999
+
1000
+ ### Generated application tests
1001
+
1002
+ - Strict TypeScript typecheck.
1003
+ - Migration idempotency and checksum mismatch.
1004
+ - Local SQLite integration tests.
1005
+ - Mocked remote libSQL client tests.
1006
+ - SSR route output.
1007
+ - Hydration and client-side navigation smoke test.
1008
+ - Shared service behavior used by UI and API.
1009
+ - API validation, pagination, error envelopes, auth, roles, and caching.
1010
+ - OAuth state, expiry, reuse, PKCE, callback, and session rotation.
1011
+ - Host/origin gating.
1012
+ - Session and API-token hashing/revocation.
1013
+ - Markdown sanitization with malicious payloads.
1014
+ - Blog state visibility.
1015
+ - RSS parse and conditional requests.
1016
+ - Auto-blog idempotency, locking, and provider-disabled behavior.
1017
+ - Health behavior with missing migrations or database outage.
1018
+
1019
+ ### CI matrix
1020
+
1021
+ At minimum:
1022
+
1023
+ ```text
1024
+ Moshcode CLI: Node.js 18 and current LTS
1025
+ Generated app: current supported Bun release
1026
+ Database: local file mode
1027
+ OS: Ubuntu latest LTS
1028
+ ```
1029
+
1030
+ A nightly or release-gate integration job may test real Turso and CoinPay using isolated credentials. Pull-request CI must use mocks and must not create external resources.
1031
+
1032
+ ## Success Metrics
1033
+
1034
+ 1. A user with Bun can generate and run the local site in one command sequence without editing source code.
1035
+ 2. At least 90% of successful interactive initializations reach a passing health endpoint without manual debugging.
1036
+ 3. Local SQLite fallback succeeds whenever Turso is unavailable in `auto` mode.
1037
+ 4. No installer output or committed file contains generated secrets.
1038
+ 5. SSR and hydrated SPA navigation are both covered by automated tests.
1039
+ 6. A newly generated default site has a working blog and RSS feed immediately.
1040
+ 7. Default auto-blog processing creates no paid AI requests.
1041
+ 8. Existing template and site command tests remain green.
1042
+ 9. The generated application has no separate frontend/backend repositories and no duplicate page component implementations.
1043
+ 10. A coding agent can implement or extend a site using the generated README, OpenAPI document, tests, and this PRD without reverse-engineering installer behavior.
1044
+
1045
+ ## Rollout Plan
1046
+
1047
+ ### Phase 1: Template and local application
1048
+
1049
+ - Add the `profullstack` bundled files.
1050
+ - Implement SolidStart SSR/hydration, local SQLite, migrations, API, blog, RSS, seed, and tests.
1051
+ - Keep auth configurable but disabled in local smoke tests.
1052
+ - Add copy-only template listing and install coverage.
1053
+
1054
+ ### Phase 2: Trusted initializer and Turso
1055
+
1056
+ - Add `site init` parsing, state file, dry run, rendering, conflict handling, Bun detection, Turso detection, provisioning, fallback, migration, seed, and build.
1057
+ - Add text and JSON output.
1058
+ - Add resume/idempotency tests.
1059
+
1060
+ ### Phase 3: CoinPay authentication
1061
+
1062
+ - Confirm canonical CoinPay endpoints and ID-token verification method.
1063
+ - Implement client registration options, PKCE/state, callback, user/session creation, owner bootstrap, host/origin enforcement, and account/API-token UI.
1064
+ - Add mocked and production-gated integration tests.
1065
+
1066
+ ### Phase 4: Deployment and auto-blog
1067
+
1068
+ - Integrate existing `moshcode site` proxy planning.
1069
+ - Add systemd service/timer units and installation workflow.
1070
+ - Add queue/release auto-blog, locking, run history, admin controls, and optional OpenAI-compatible adapter.
1071
+ - Update hosting documentation.
1072
+
1073
+ ### Phase 5: Companion secure origin
1074
+
1075
+ - Document manual CA-valid companion origin setup.
1076
+ - Add automated `apps.moshcode.sh` allocation only when a supporting Moshcode platform API exists.
1077
+ - Do not block phases 1–4 on automatic hostname allocation.
1078
+
1079
+ ## Repository Changes
1080
+
1081
+ Expected Moshcode changes:
1082
+
1083
+ ```text
1084
+ bin/moshcode.mjs
1085
+ src/serve.mjs
1086
+ src/site-init.mjs
1087
+ src/site-init/
1088
+ args.mjs
1089
+ plan.mjs
1090
+ render.mjs
1091
+ state.mjs
1092
+ turso.mjs
1093
+ coinpay.mjs
1094
+ install.mjs
1095
+ src/cli-schema.mjs
1096
+ src/completion.mjs
1097
+ src/help.mjs
1098
+ examples/templates/profullstack/**
1099
+ test/site-init.test.mjs
1100
+ test/site-init-turso.test.mjs
1101
+ test/site-init-coinpay.test.mjs
1102
+ test/site-init-render.test.mjs
1103
+ docs/profullstack-template.md
1104
+ docs/hosting-a-moshpit-name.md
1105
+ README.md
1106
+ prd/README.md
1107
+ ```
1108
+
1109
+ The implementation may split modules differently, but database provisioning, CoinPay provisioning, rendering, state/resume, and privileged install planning must remain independently testable.
1110
+
1111
+ ## Risks & Open Questions
1112
+
1113
+ 1. **CoinPay documentation consistency:** Current CoinPay documentation surfaces different OAuth/JWKS paths in different integration pages. The canonical authorization, token, UserInfo, JWKS, issuer, audience, signing algorithm, and revocation behavior must be confirmed before release.
1114
+ 2. **Secure companion origin:** CoinPay OAuth for stock browsers requires a CA-valid HTTPS callback. Automatic `apps.moshcode.sh` allocation needs a platform API that may not yet exist.
1115
+ 3. **Pinned Moshpit TLS:** It is useful for Moshpit-aware clients but cannot be treated as universally trusted by ordinary browsers or external OAuth providers.
1116
+ 4. **Turso product modes:** Turso supports both its newer database engine and libSQL. This PRD intentionally selects hosted libSQL because `@libsql/client` also supports the local file mode. Revisit when one SDK provides equally simple local-file and hosted-new-engine behavior.
1117
+ 5. **SolidStart release selection:** Pin the latest tested stable release at implementation time; do not automatically adopt a prerelease in a production starter.
1118
+ 6. **Auto-blog definition:** Queue and release sources are fully defined for v1. More autonomous topic selection, crawling, editorial policy, citations, and duplicate-content controls need a later PRD.
1119
+ 7. **HTML sanitization:** The exact Markdown and sanitization libraries must be selected based on maintained status, Bun compatibility, and security review.
1120
+ 8. **Systemd hardening:** Restrictive unit settings must be tested with Bun, local SQLite writes, build assets, DNS, and outbound CoinPay/Turso requests.
1121
+ 9. **Multiple sites on one host:** Port selection, system user strategy, service naming, data directories, and Turso database names must remain collision-safe.
1122
+ 10. **Backups:** Local SQLite backup and hosted Turso recovery procedures must be documented before declaring the template production-ready.
1123
+ 11. **Owner recovery:** A lost owner account needs a host-local recovery path that does not expose a network setup token.
1124
+ 12. **API stability:** `/api/v1` is a compatibility promise. Breaking response changes require `/api/v2` or an explicit deprecation process.
1125
+
1126
+ ## Decision Summary
1127
+
1128
+ The default generated site is:
1129
+
1130
+ ```text
1131
+ Bun
1132
+ SolidStart + SolidJS
1133
+ SSR first request
1134
+ hydrated SPA navigation
1135
+ shared TSX routes/components
1136
+ SolidStart server functions
1137
+ versioned JSON API
1138
+ @libsql/client
1139
+ local SQLite fallback
1140
+ hosted Turso libSQL when available
1141
+ CoinPay OAuth/OIDC
1142
+ hashed database sessions
1143
+ scoped API tokens
1144
+ /blog
1145
+ /blog/feed.xml
1146
+ default-on queue/release auto-blog
1147
+ systemd
1148
+ nginx or Caddy
1149
+ plain public Moshpit origin
1150
+ separate CA-valid authenticated origin
1151
+ ```
1152
+
1153
+ The primary experience is:
1154
+
1155
+ ```sh
1156
+ moshcode site init foo.whatever
1157
+ cd foo.whatever
1158
+ bun run dev
1159
+ ```
1160
+
1161
+ The existing template security boundary remains:
1162
+
1163
+ ```text
1164
+ template install = copy files only
1165
+ site init = trusted, explicit provisioning
1166
+ ```
1167
+
1168
+ ## References
1169
+
1170
+ - Moshcode bundled templates: https://github.com/moshcoder/moshcode/tree/main/examples/templates
1171
+ - Moshcode template safety implementation: https://github.com/moshcoder/moshcode/blob/main/src/templates.mjs
1172
+ - Moshcode site/hosting implementation: https://github.com/moshcoder/moshcode/blob/main/src/serve.mjs
1173
+ - Moshpit hosting constraints: https://github.com/moshcoder/moshcode/blob/main/docs/hosting-a-moshpit-name.md
1174
+ - Moshcode OpenPRD template: https://github.com/moshcoder/moshcode/blob/main/prd/0000-template.md
1175
+ - SolidStart configuration: https://docs.solidjs.com/solid-start/reference/config/define-config
1176
+ - SolidStart data fetching and server functions: https://docs.solidjs.com/solid-start/guides/data-fetching
1177
+ - SolidStart authentication guidance: https://docs.solidjs.com/solid-start/advanced/auth
1178
+ - Turso TypeScript quickstart: https://docs.turso.tech/sdk/ts/quickstart
1179
+ - Turso CLI database creation: https://docs.turso.tech/cli/db/create
1180
+ - Turso CLI authentication status: https://docs.turso.tech/cli/auth/whoami
1181
+ - Turso database token creation: https://docs.turso.tech/cli/db/tokens/create
1182
+ - CoinPay OAuth/OIDC documentation: https://coinpayportal.com/docs
1183
+ - CoinPay OAuth integration prompt: https://coinpayportal.com/docs/prompts/OAUTH