spfn 0.3.0-beta.3 → 0.3.0-beta.5

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,7 @@
2
2
 
3
3
  `spfn` takes a Next.js idea from prototype to production with a consistent full-stack
4
4
  architecture. It can scaffold either a core-only backend or a production baseline with
5
- authentication, internationalization, and an agent-facing MCP endpoint, then runs the
5
+ authentication, internationalization, and a terminal operations surface, then runs the
6
6
  dev/build/start lifecycle, database tooling, RPC codegen, and environment validation.
7
7
 
8
8
  Consistent is the point rather than a nicety: what it scaffolds is one fixed shape per
@@ -31,7 +31,7 @@ is not supported — see [the root README](../../README.md#what-do-i-need-instal
31
31
  ## Usage
32
32
 
33
33
  ```bash
34
- # Prototype-to-Production baseline: core + auth + i18n + MCP
34
+ # Prototype-to-Production baseline: core + auth + i18n + ops
35
35
  npx spfn@beta create my-app --mode full
36
36
  cd my-app
37
37
  docker compose up -d # Postgres + Redis
@@ -54,7 +54,8 @@ with `--pm`. In a pnpm workspace, `create` installs from the workspace root.
54
54
  ## Commands
55
55
 
56
56
  Registered top-level commands: `create`, `init`, `add`, `dev`, `build`, `start`,
57
- `provision`, `codegen`, `contract`, `key`, `setup`, `db`, `env`, `ops`, `secret`.
57
+ `provision`, `codegen`, `contract`, `key`, `setup`, `db`, `env`, `ops`, `secret`,
58
+ `cloud`.
58
59
 
59
60
  ### `spfn create <name>`
60
61
 
@@ -71,7 +72,7 @@ pass `--mode full`.
71
72
  |--------|-------------|
72
73
  | `--pm <manager>` | Force package manager: `npm` \| `pnpm` \| `yarn` \| `bun` |
73
74
  | `--shadcn` | Also run `shadcn init` |
74
- | `--mode <mode>` | `bare` (core only) \| `full` (core, auth, i18n, MCP) |
75
+ | `--mode <mode>` | `bare` (core only) \| `full` (core, auth, i18n, ops) |
75
76
  | `--skip-install` | Skip dependency install |
76
77
  | `--skip-git` | Skip `git init` |
77
78
  | `-y, --yes` | Skip prompts, use defaults |
@@ -86,7 +87,7 @@ already exists). See [Scaffold structure](#scaffold-structure) for what lands on
86
87
 
87
88
  | Option | Description |
88
89
  |--------|-------------|
89
- | `--mode <mode>` | `bare` (core only) \| `full` (core, auth, i18n, MCP) |
90
+ | `--mode <mode>` | `bare` (core only) \| `full` (core, auth, i18n, ops) |
90
91
  | `-y, --yes` | Skip prompts, use defaults |
91
92
 
92
93
  Generated projects pin `drizzle-orm` and `drizzle-kit` to `1.0.0-rc.4`, matching
@@ -384,6 +385,31 @@ Schema-driven: a secret declared with `envSecret({ generate: 'base64url32' })` c
384
385
  minted/rotated automatically (`secret generate`/`rotate`); one without `generate` is an
385
386
  external value you paste in (`secret set`).
386
387
 
388
+ ### `spfn cloud`
389
+
390
+ Free-tier management for apps deployed to your own **Vercel Hobby + Supabase Free**
391
+ accounts: see the plan limits, watch live usage against them, keep the Supabase
392
+ project from pausing, and sync env vars/API keys. Account tokens and key values live
393
+ in the OS keychain and never appear in command output.
394
+
395
+ | Subcommand | Description |
396
+ |------------|-------------|
397
+ | `cloud link` | Connect accounts: Vercel access token + Supabase personal access token (masked prompt or `VERCEL_TOKEN`/`SUPABASE_ACCESS_TOKEN` env), pick the project on each side. Identifiers land in gitignored `.spfn/cloud.json`; tokens go to the keychain |
398
+ | `cloud limits` | The free-plan limits (constants verified against the official docs, date shown) — works before `link` |
399
+ | `cloud usage` | Current usage: Vercel billing feed (rolling 30 days), Supabase DB size + last-24h API requests |
400
+ | `cloud status` | Usage measured against the limits on one screen; items at ≥80% get a migration warning |
401
+ | `cloud keepalive` | Daily cron hitting `/api/backend/_core/health?detailed=true` (the detailed check runs a DB query, which is what prevents the ~7-idle-day pause). Vercel cron by default; `--github-actions --url <deployed-url>` for a workflow instead |
402
+ | `cloud env pull` | Supabase keys → local: project URL + anon key into `.env.local`, service-role key into the keychain (`.env.server` gets a reference). `--db-url` also composes `DATABASE_URL` (prompts for the DB password) |
403
+ | `cloud env push KEY…` | Push local env values to the Vercel project env by name. Values resolve from `.env`/`.env.local`/`.env.server`+keychain; everything is sent encrypted except `NEXT_PUBLIC_*` |
404
+
405
+ Free-tier behavior worth knowing (also printed by `cloud limits`): Vercel Hobby
406
+ allows one cron at most once per day and pauses a capability when its rolling
407
+ 30-day limit is hit (it never bills); Supabase Free quota is summed per
408
+ organization (except DB size), and org totals like egress/MAU have no public API —
409
+ `cloud status` shows per-item numbers and points at the dashboard for the rest.
410
+ Hobby is limited to personal, non-commercial use — a monetized app needs Vercel Pro
411
+ or a migration off the free tier.
412
+
387
413
  ### `spfn setup icons`
388
414
 
389
415
  Install and configure SVGR for SVG-as-component imports (Next.js only).
@@ -418,6 +444,29 @@ listSignups GET /_ops/signups
418
444
  Add `--json` for the raw JSON Schema. The server still validates every call — `--describe`
419
445
  reports what it will accept, and the app's answer decides.
420
446
 
447
+ #### Capability modules
448
+
449
+ An app can also mount ops commands a package described, with
450
+ [`defineOpsModule`](../core/README.md#can-a-package-ship-ops-commands). Those commands are
451
+ named `<module>.<command>` and carry a summary, an effect and their scopes, so the CLI can
452
+ group them and say what each one does. From **0.3.0-beta.5**:
453
+
454
+ ```bash
455
+ spfn ops modules # what is mounted, and from where
456
+ spfn ops modules --json # same, machine-readable
457
+ spfn ops list --module ledger # just that module's commands
458
+ spfn ops call ledger.compact --yes # effect=destructive needs this
459
+ ```
460
+
461
+ `spfn ops call` refuses a command the app declared `effect: destructive` unless `--yes` is
462
+ given. It refuses the same way when the app announced module metadata this CLI could not
463
+ validate: the effect is then unknown rather than absent, and an unknown effect is not
464
+ treated as a safe one. The command still lists — an operator reading a short list would
465
+ otherwise take it for the app's whole surface — and the warning names what was dropped.
466
+
467
+ Everything in the manifest is the app's own text written to your terminal, so control
468
+ characters in it are replaced before anything is printed.
469
+
421
470
  The app URL comes from `--app` or `SPFN_OPS_APP`, and it must be **https** — every one of
422
471
  these commands carries a secret, and `token issue` carries an administrator's password.
423
472
  `http` is accepted only against `localhost`, `127.0.0.1` and `::1`, where there is no
@@ -498,24 +547,30 @@ src/
498
547
  app/auth/callback/page.tsx # OAuth session handoff
499
548
  i18n/catalogs.ts # application-owned en/ko starter messages
500
549
  i18n/server.ts # configured server-side i18n registry
501
- server/mcp.ts # authenticated /mcp endpoint + starter app_status tool
502
- server/router.ts # authRouter + mcpRouter + global authenticate
550
+ server/routes/ops.ts # ops routes under /_ops + the manifest `spfn ops` reads
551
+ server/router.ts # authRouter + opsRouter + global authenticate
503
552
  server/server.config.ts # createAuthLifecycle + i18n startup
504
553
  next.config.ts # /_auth/* callback rewrite
505
554
  .env.local # generated auth session secret (gitignored)
506
- .env.server # auth keyring + MCP operator key (gitignored)
555
+ .env.server # auth keyring (gitignored)
507
556
  ```
508
557
 
509
558
  The full RPC proxy imports the auth interceptor and merges `authRouteMap`. Internal auth
510
- and MCP keys are generated with cryptographic randomness in ignored local env files;
559
+ keys are generated with cryptographic randomness in ignored local env files;
511
560
  `.env.example` contains placeholders only. Add only the provider keys you use, then run
512
- `pnpm spfn db migrate`. The starter MCP endpoint accepts `SPFN_MCP_API_KEY` as a Bearer
513
- token for first-party operation; replace that validator with OAuth before third-party access.
561
+ `pnpm spfn db migrate`.
562
+
563
+ Operating the app is [`spfn ops`](#spfn-ops), not a dashboard: the starter
564
+ `src/server/routes/ops.ts` exposes two read commands, and `spfn ops` discovers them from
565
+ the running server's manifest. Issuing the first token signs in as an administrator, so
566
+ uncomment `SPFN_AUTH_ADMIN_ACCOUNTS` in `.env.server` and restart before
567
+ `spfn ops token issue`. The ops surface adds no dependency — the router comes from
568
+ `@spfn/core/ops` and the tokens from `@spfn/auth`.
514
569
 
515
570
  `init` also patches `package.json` (scripts: `spfn:dev`, `spfn:server`, `spfn:next`,
516
571
  `spfn:build`, `spfn:start`, `codegen`; deps: `@spfn/core`, `spfn`, `drizzle-orm`,
517
572
  `@sinclair/typebox`, `concurrently`, etc.; full also adds `@spfn/auth`, `@spfn/i18n`,
518
- `@spfn/mcp`, auth's `@spfn/notification` peer, and a Node `>=20.0.0` engine when the
573
+ auth's `@spfn/notification` peer, and a Node `>=20.0.0` engine when the
519
574
  existing range still permits older Node versions), excludes `src/server` from the root
520
575
  `tsconfig.json` (Vercel compat), and adds `.spfn/`, `.env.local`, `.env.server` to
521
576
  `.gitignore`.
@@ -621,8 +676,8 @@ type ships from `spfn` (`@type {import('spfn').SpfnConfig}`).
621
676
  something with an AI coding agent and now want a real backend under it, `init` is the one.
622
677
 
623
678
  **`bare` or `full`?**
624
- `full` is the recommended baseline: core, auth, i18n and MCP wired together, so you get a
625
- working authenticated app on day one. `bare` is core only — the architecture with nothing
679
+ `full` is the recommended baseline: core, auth, i18n and the ops surface wired together, so
680
+ you get a working authenticated app on day one, operable from the terminal. `bare` is core only — the architecture with nothing
626
681
  else decided. Automation should always pass `--mode` explicitly, because a `--yes` run
627
682
  without one still produces `bare` for backward compatibility.
628
683
 
@@ -637,7 +692,7 @@ pointing at your own PostgreSQL works too. PostgreSQL itself is not optional.
637
692
 
638
693
  **Which Node version do I need?**
639
694
  20 or later, in both modes. `@spfn/core` runs on `@hono/node-server` 2, which declares
640
- that floor, and full mode's MCP server needs the same.
695
+ that floor, and full mode's `@spfn/auth` needs the same.
641
696
 
642
697
  **When do I have to run codegen by hand?**
643
698
  Whenever routes change outside `spfn dev`, which runs a codegen watcher for you. A stale or
@@ -685,5 +740,6 @@ committed.
685
740
 
686
741
  - [`@spfn/core`](../core/README.md) — server, route DSL, codegen, db, client runtime.
687
742
  - [`@spfn/auth`](../auth/README.md) — what `--mode full` wires in for accounts and roles.
688
- - [`@spfn/mcp`](../mcp/README.md) — what `--mode full` wires in for operating the app.
743
+ - [`@spfn/mcp`](../mcp/README.md) — an agent-facing MCP endpoint, added on demand with
744
+ `spfn add @spfn/mcp`.
689
745
  - Project root README — framework overview and getting started.