everything-dev 1.42.1 → 1.42.2

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.
@@ -0,0 +1,355 @@
1
+ ---
2
+ name: api-and-auth
3
+ description: API architecture, oRPC contracts, auth middleware, plugin-client composition, session handling, and client-side auth. Use when adding API routes, creating middleware, calling other plugins in-process, or integrating auth in routes and UI.
4
+ metadata:
5
+ sources: "api/src/index.ts,api/src/contract.ts,api/src/lib/auth.ts,host/src/services/auth.ts,host/src/services/plugins.ts,host/src/program.ts,ui/src/lib/auth.ts,ui/src/lib/api.ts"
6
+ ---
7
+
8
+ # API Architecture & Auth
9
+
10
+ ## Plugin Anatomy
11
+
12
+ The API is an every-plugin registered via `createPlugin.withPlugins<PluginsClient>()`:
13
+
14
+ ```ts
15
+ export default createPlugin.withPlugins<PluginsClient>()({
16
+ variables: z.object({ /* typed config */ }),
17
+ secrets: z.object({ /* typed env vars, defaults for dev */ }),
18
+ context: z.object({ /* per-request context injected by host */ }),
19
+ contract,
20
+ initialize: (config, plugins) => Effect.promise(async () => {
21
+ return { db, upvoteService, publisher, auth, plugins };
22
+ }),
23
+ shutdown: (services) => Effect.promise(async () => { /* cleanup */ }),
24
+ createRouter: (services, builder) => ({
25
+ ping: builder.ping.handler(async () => ({ status: "ok", timestamp })),
26
+ }),
27
+ });
28
+ ```
29
+
30
+ Fields: `variables` (public config), `secrets` (private env), `context` (per-request host context), `contract` (oRPC router), `initialize` (startup, returns services), `createRouter` (maps procedures to handlers), `shutdown` (cleanup). `plugins` in `initialize` gives typed factories for all other plugins.
31
+
32
+ ## oRPC Contract Design
33
+
34
+ Defined in `api/src/contract.ts`:
35
+
36
+ ```ts
37
+ import { BAD_REQUEST, NOT_FOUND, UNAUTHORIZED } from "every-plugin/errors";
38
+ import { eventIterator, oc } from "every-plugin/orpc";
39
+ import { z } from "every-plugin/zod";
40
+
41
+ export const contract = oc.router({
42
+ ping: oc.route({ method: "GET", path: "/ping" }).output(
43
+ z.object({ status: z.literal("ok"), timestamp: z.iso.datetime() }),
44
+ ),
45
+ upvoteThing: oc
46
+ .route({ method: "POST", path: "/upvotes" })
47
+ .input(z.object({ thingId: z.string() }))
48
+ .output(z.object({ thingId: z.string(), userId: z.string(), totalCount: z.number().int().nonnegative() }))
49
+ .errors({ UNAUTHORIZED, BAD_REQUEST }),
50
+ getUserVote: oc
51
+ .route({ method: "GET", path: "/upvotes/{thingId}/me" })
52
+ .input(z.object({ thingId: z.string() }))
53
+ .output(z.object({ thingId: z.string(), hasUpvote: z.boolean() }))
54
+ .errors({ UNAUTHORIZED }),
55
+ getUpvoteFeed: oc
56
+ .route({ method: "GET", path: "/upvotes/feed" })
57
+ .input(z.object({ limit: z.number().int().min(1).max(100).optional(), cursor: z.string().optional() }))
58
+ .output(z.object({ data: z.array(/*...*/), meta: z.object({ total, hasMore, nextCursor }) })),
59
+ subscribeUpvotes: oc
60
+ .route({ method: "GET", path: "/upvotes/stream" })
61
+ .output(eventIterator(VoteEventSchema)),
62
+ });
63
+ ```
64
+
65
+ Conventions: `.errors()` declares typed errors, `{paramName}` path params match Zod input keys, `.output(eventIterator(Schema))` enables SSE streaming, export `type ContractType = typeof contract` for type generation.
66
+
67
+ ## Route Implementation
68
+
69
+ ```ts
70
+ createRouter: (services, builder) => {
71
+ const { requireAuth } = createAuthMiddleware(builder);
72
+
73
+ return {
74
+ ping: builder.ping.handler(async () => ({
75
+ status: "ok",
76
+ timestamp: new Date().toISOString(),
77
+ })),
78
+ upvoteThing: builder.upvoteThing.use(requireAuth).handler(async ({ input, context }) => {
79
+ return await services.upvoteService.upvoteThing(input.thingId, context.userId);
80
+ }),
81
+ subscribeUpvotes: builder.subscribeUpvotes.handler(async function* ({ signal, lastEventId }) {
82
+ const iterator = services.publisher.subscribe("vote", { signal, lastEventId });
83
+ for await (const event of iterator) yield event;
84
+ }),
85
+ };
86
+ };
87
+ ```
88
+
89
+ Handler receives `{ input, context, signal?, lastEventId? }`.
90
+
91
+ ## Middleware
92
+
93
+ Create auth middleware with `createAuthMiddleware(builder)` in `api/src/lib/auth.ts`. Each middleware narrows the context type through `.use()` — no non-null assertions needed.
94
+
95
+ ```ts
96
+ const { requireAuth } = createAuthMiddleware(builder);
97
+
98
+ builder.myRoute.use(requireAuth).handler(async ({ input, context }) => {
99
+ context.userId; // string — narrowed by middleware
100
+ });
101
+ ```
102
+
103
+ Available: `requireAuth`, `requireAuthOrApiKey`, `requireRole("admin")`, `requireOrganization`, `requireOrgRole("owner")`, `requireApiKey`. Apply via `.use()`:
104
+
105
+ ```ts
106
+ builder.authHealth.use(requireAuth).handler(...)
107
+ builder.adminAction.use(requireRole("admin")).handler(...)
108
+ ```
109
+
110
+ See `references/middleware.md` for the full middleware table, org metadata validation, and typed context helpers.
111
+
112
+ ## Error Handling
113
+
114
+ Use `ORPCError` from `every-plugin/errors`:
115
+
116
+ ```ts
117
+ import { ORPCError } from "every-plugin/orpc";
118
+ import { BAD_REQUEST, UNAUTHORIZED } from "every-plugin/errors";
119
+
120
+ throw new ORPCError("UNAUTHORIZED", {
121
+ message: "Authentication required",
122
+ data: { hint: "Sign in or provide an API key" },
123
+ });
124
+ ```
125
+
126
+ Declare throwable errors in the contract via `.errors({ UNAUTHORIZED, BAD_REQUEST })`. Client-side errors are intercepted by `onError` in `createRpcLink` (`ui/src/lib/api.ts`).
127
+
128
+ ## Auth Plugin Architecture
129
+
130
+ The auth plugin is an **external plugin** loaded in **Phase 0** of the host's initialization:
131
+
132
+ 1. **Phase 0** (`host/src/services/plugins.ts`): Load auth plugin, create `authClient` factory.
133
+ 2. **Phase 1**: Load all non-API plugins.
134
+ 3. **Phase 2**: Load API plugin with `pluginsClient` (includes auth + all other plugin factories).
135
+
136
+ The host mounts the auth handler at `/api/auth/*`:
137
+
138
+ ```ts
139
+ // host/src/services/auth.ts
140
+ export function registerAuthHandler(app, plugins) {
141
+ const services = getAuthServices(plugins);
142
+ if (!services) return;
143
+ app.on(["POST", "GET"], "/api/auth/*", (c) => services.handler(c.req.raw));
144
+ }
145
+ ```
146
+
147
+ ## Session Middleware
148
+
149
+ Runs on every non-auth request. Resolves the session from cookies and sets Hono request context:
150
+
151
+ ```ts
152
+ // host/src/services/auth.ts
153
+ export function createSessionMiddleware(plugins) {
154
+ return async (c, next) => {
155
+ if (c.req.path.startsWith("/api/auth/")) return next();
156
+ c.set("reqHeaders", c.req.raw.headers);
157
+
158
+ const authClient = authClientFactory({ reqHeaders });
159
+ const [session, context] = await Promise.all([
160
+ authClient.getSession(),
161
+ authClient.getContext(),
162
+ ]);
163
+
164
+ c.set("user", session?.user ?? context.user ?? null);
165
+ c.set("session", session?.session ?? null);
166
+ c.set("walletAddress", context.near.primaryAccountId ?? null);
167
+ c.set("apiKey", context.apiKey ?? null);
168
+ c.set("organizationId", context.organization?.activeOrganizationId ?? null);
169
+
170
+ await next();
171
+ };
172
+ }
173
+ ```
174
+
175
+ If resolution fails, all values are `null` — `requireAuth` routes reject with `UNAUTHORIZED`.
176
+
177
+ The context is transformed for the API plugin via `buildPluginContext()`, with the full `organization` envelope from Better Auth:
178
+
179
+ ```ts
180
+ export function buildPluginContext(c) {
181
+ return {
182
+ userId: user?.id, user: user ?? undefined,
183
+ organization: context.organization ?? undefined,
184
+ apiKey: apiKey ?? undefined,
185
+ reqHeaders: c.get("reqHeaders"),
186
+ getRawBody: c.get("getRawBody"),
187
+ };
188
+ }
189
+ ```
190
+
191
+ ## Auth in API Routes
192
+
193
+ The API plugin receives `auth` in `initialize`:
194
+
195
+ ```ts
196
+ initialize: (config, plugins) => Effect.promise(async () => {
197
+ const { auth, ...restPlugins } = plugins;
198
+ return { auth, plugins: restPlugins, ... };
199
+ })
200
+ ```
201
+
202
+ Use `getAuthClient()` for in-process calls:
203
+
204
+ ```ts
205
+ import { getAuthClient, createAuthMiddleware } from "./lib/auth";
206
+
207
+ const authClient = getAuthClient(services, { reqHeaders: context.reqHeaders });
208
+ const session = await authClient.getSession();
209
+ ```
210
+
211
+ `AuthCapableServices` requires an `auth` factory. If unavailable, `getAuthClient()` throws.
212
+
213
+ ## Auth on the Client
214
+
215
+ Create the client in `ui/src/lib/auth.ts`:
216
+
217
+ ```ts
218
+ export function createAuthClient(runtimeConfig) {
219
+ return betterAuth.createClient({
220
+ baseURL: runtimeConfig.authBaseUrl,
221
+ plugins: [siwn({ recipients, networkId }), passkey(), organization(), admin(), apiKey(), anonymous(), phone()],
222
+ });
223
+ }
224
+ ```
225
+
226
+ In route code:
227
+
228
+ ```ts
229
+ import { useAuthClient, sessionQueryOptions } from "@/app";
230
+
231
+ const authClient = useAuthClient();
232
+ ```
233
+
234
+ The `sessionQueryOptions()` helper provides standard TanStack Query config:
235
+
236
+ ```ts
237
+ const session = await queryClient.ensureQueryData(
238
+ sessionQueryOptions(authClient, context.session),
239
+ );
240
+ ```
241
+
242
+ ### Auth Route Guard
243
+
244
+ The `_authenticated.tsx` layout redirects unauthenticated users:
245
+
246
+ ```ts
247
+ export const Route = createFileRoute("/_layout/_authenticated")({
248
+ beforeLoad: async ({ context, location }) => {
249
+ const { queryClient, authClient } = context;
250
+ const session = await queryClient.ensureQueryData(
251
+ sessionQueryOptions(authClient, context.session),
252
+ );
253
+ if (!session?.user) {
254
+ throw redirect({ to: "/login", search: { redirect: location.href } });
255
+ }
256
+ return { auth: { isAuthenticated: true, user: session.user, session: session.session } };
257
+ },
258
+ component: AuthenticatedLayout,
259
+ });
260
+ ```
261
+
262
+ ## Plugin Client Composition
263
+
264
+ The host uses **two-phase loading** so API plugins can call other plugins in-process:
265
+
266
+ 1. **Phase 0**: Auth plugin → `authClient` factory
267
+ 2. **Phase 1**: All non-API plugins → `pluginsClient` map of `createClient` factories
268
+ 3. **Phase 2**: API plugin with all plugin factories merged
269
+
270
+ ```ts
271
+ // host/src/services/plugins.ts
272
+ const pluginsClient = { ...pluginClients };
273
+ if (authClient) pluginsClient.auth = authClient;
274
+ const baseApi = await loadPluginEntry(runtime, apiEntry, integrityRegistry, pluginsClient);
275
+ ```
276
+
277
+ ### Calling Plugins from API Routes
278
+
279
+ The API plugin receives `plugins` in `initialize`:
280
+
281
+ ```ts
282
+ initialize: (config, plugins) => Effect.promise(async () => {
283
+ const authClient = plugins.auth({ reqHeaders: someHeaders });
284
+ const session = await authClient.getSession();
285
+ return { auth: plugins.auth, plugins, ... };
286
+ })
287
+ ```
288
+
289
+ ### API-Owned Registry Pattern
290
+
291
+ When the API owns the durable registry and a plugin owns the semantic payload:
292
+
293
+ ```ts
294
+ const provider = thingProviders[input.pluginId];
295
+ if (!provider) {
296
+ throw new ORPCError("BAD_REQUEST", { message: `Unsupported pluginId: ${input.pluginId}` });
297
+ }
298
+ ```
299
+
300
+ Rules: API owns `thingId`, `pluginId`, timestamps. Plugin owns `type` and `payload`. Keep one SSE stream per concept and filter server-side.
301
+
302
+ ### Effect and DB Lifecycle
303
+
304
+ Prefer `Layer` for long-lived resources (DB, service singletons) and `Effect` for the work itself. Use `runEffect()` to bridge Effect and async handlers with clean ORPC error boundaries — unwraps `ORPCError` from Effect and converts unknown errors to `INTERNAL_SERVER_ERROR`:
305
+
306
+ ```ts
307
+ import { runEffect } from "@/lib/context";
308
+
309
+ const result = await runEffect(services.myService.doSomething(input));
310
+ ```
311
+
312
+ Best practices: Keep service interfaces Effect-native, bridge to async only at the handler boundary via `runEffect()`. Use `Context.Tag` for DI between services. Initialize long-lived resources in `initialize` and return them as services.
313
+
314
+ ### SSR Proxy Client
315
+
316
+ `createPluginsClient()` creates a Proxy that merges the API client with all plugin clients:
317
+
318
+ ```ts
319
+ export function createPluginsClient(result, context) {
320
+ const apiClient = result.api?.createClient(context);
321
+ const pluginClients = {};
322
+ for (const [key, plugin] of Object.entries(result.plugins)) {
323
+ if (key === "api") continue;
324
+ pluginClients[key] = plugin.createClient(context);
325
+ }
326
+ if (result.authClient) pluginClients.auth = result.authClient(context);
327
+
328
+ return new Proxy(apiClient, {
329
+ get(target, key) {
330
+ if (typeof key === "string" && key in pluginClients) return pluginClients[key];
331
+ return Reflect.get(target, key);
332
+ },
333
+ });
334
+ }
335
+ ```
336
+
337
+ ## Generated Types
338
+
339
+ See `references/generated-types.md` for the full table — files, contents, and regeneration triggers.
340
+
341
+ ## SSE Notes
342
+
343
+ Prefer a single publisher channel per concept and filter on the consumer side:
344
+
345
+ ```ts
346
+ const iterator = services.publisher.subscribe("thing", { signal, lastEventId });
347
+ for await (const event of iterator) {
348
+ if (input.pluginId && event.pluginId !== input.pluginId) continue;
349
+ yield event;
350
+ }
351
+ ```
352
+
353
+ ## How Routes Are Mounted
354
+
355
+ The host (`host/src/program.ts`) creates RPC and OpenAPI handlers from each plugin's router, mounted at `/api/rpc/<plugin-namespace>`. The session middleware runs on `/api/*` before the RPC handlers, ensuring context is set.
@@ -0,0 +1,14 @@
1
+ # Generated Types
2
+
3
+ | File | Contents | Regenerated by |
4
+ |------|----------|---------------|
5
+ | `api/src/lib/plugins-types.gen.ts` | `PluginsClient` — factory types for all plugins | `bos types gen` |
6
+ | `api/src/lib/auth-types.gen.ts` | Auth plugin request context types | `bos types gen` |
7
+ | `ui/src/lib/api-types.gen.ts` | `ApiContract` — all procedure types | `bos types gen` |
8
+ | `ui/src/lib/auth-types.gen.ts` | Auth client session/user types | `bos types gen` |
9
+
10
+ Type resolution:
11
+ - `local:plugins/<name>` → reads `src/contract.ts` directly from disk
12
+ - Remote URL → fetches contract types from the deployed plugin's manifest
13
+ - Missing local path with no URL → skipped with a warning
14
+ - Run `bos types gen` or restart `bos dev` after hand-editing `bos.config.json`
@@ -0,0 +1,38 @@
1
+ # Middleware Reference
2
+
3
+ ## Available Middlewares
4
+
5
+ | Middleware | Narrows |
6
+ |---|---|
7
+ | `requireAuth` | `userId: string`, `user: RequestAuthUser` |
8
+ | `requireAuthOrApiKey` | gate only — allows session or API key auth, no narrowing |
9
+ | `requireRole("admin")` | `userId`, `user` |
10
+ | `requireOrganization` | `userId`, `user`, `organization.activeOrganizationId: string` |
11
+ | `requireOrgRole("owner")` | all of above + `organization.member` non-null with `id`, `role` |
12
+ | `requireApiKey` | `apiKey: ApiKeyContext` |
13
+
14
+ ## Org Metadata Validation
15
+
16
+ Pass an optional Zod schema to `createAuthMiddleware` for runtime validation:
17
+
18
+ ```ts
19
+ import { z } from "every-plugin/zod";
20
+
21
+ const orgMetaSchema = z.object({ plan: z.enum(["free", "pro"]), seats: z.number() });
22
+ const { requireOrganization } = createAuthMiddleware(builder, { orgMetaSchema });
23
+ ```
24
+
25
+ When a schema is provided, `requireOrganization` and `requireOrgRole` call `schema.safeParse()` on the org metadata. On parse failure, throws `INTERNAL_SERVER_ERROR` (data integrity issue). When no schema is passed, metadata stays `Record<string, unknown>` with no validation.
26
+
27
+ ## Typed Org Context Helpers
28
+
29
+ ```ts
30
+ import type { OrgAuthenticatedContext } from "./lib/auth";
31
+
32
+ type MyOrgMeta = { plan: "free" | "pro"; seats: number };
33
+
34
+ const ctx = context as OrgAuthenticatedContext<MyOrgMeta>;
35
+ ctx.organization.organization?.metadata?.plan;
36
+ ```
37
+
38
+ Also available: `OrgMemberAuthenticatedContext<TMeta>` (guarantees `organization.member` is non-null).
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: cli-reference
3
+ description: Quick reference for all bos CLI commands — flags, options, environment settings, and links to detailed guidance in related skills. Use when any bos command comes up or the user needs a CLI overview.
4
+ metadata:
5
+ sources: "packages/everything-dev/src/contract.ts,packages/everything-dev/src/contract.meta.ts,packages/everything-dev/src/cli.ts,packages/everything-dev/src/cli/catalog.ts"
6
+ ---
7
+
8
+ # CLI Command Reference
9
+
10
+ 17 `bos` commands organised by workflow category.
11
+
12
+ ## Development
13
+
14
+ ### `bos dev`
15
+
16
+ Start a development session. Runs host, API, auth, and UI processes with hot reload.
17
+
18
+ **Flags:** `--host <local|remote>` `--ui <local|remote>` `--api <local|remote>` `--auth <local|remote>` `--remote-plugins <ids>` `--proxy` `--ssr` `--port <n>`
19
+
20
+ ```bash
21
+ bos dev # full local (all services)
22
+ bos dev --api remote # UI-only mode, use remote API
23
+ bos dev --ui remote # API-only mode, use remote UI
24
+ bos dev --proxy # local host proxies to remote API/UI
25
+ bos dev --remote-plugins auth,registry # force specific plugins remote
26
+ ```
27
+
28
+ → [`dev-workflow`](.) skill for port assignments, service-descriptor architecture, hot reload, and debugging.
29
+
30
+ ### `bos start`
31
+
32
+ Start the production host. Loads config from `bos.config.json`, wires MF remotes, serves SSR.
33
+
34
+ **Flags:** `--env <production|staging>` `--port <n>` `--account <id>` `--domain <domain>` `--no-interactive`
35
+
36
+ ```bash
37
+ bos start # production, auto-detects env
38
+ bos start --env staging # staging mode
39
+ bos start --account myapp.near # tenant override
40
+ ```
41
+
42
+ → [`dev-workflow`](.) skill for production topology and [`super-app`](.) for tenant overrides.
43
+
44
+ ### `bos build`
45
+
46
+ Build all workspace packages (or a subset) for production.
47
+
48
+ **Flags:** `--packages <list>` `--force` `--deploy`
49
+
50
+ ```bash
51
+ bos build # build all workspaces
52
+ bos build --packages ui,api # build specific packages
53
+ bos build --force # rebuild even if up-to-date
54
+ bos build --deploy # build + deploy to Zephyr
55
+ ```
56
+
57
+ → [`publish-sync`](.) skill for full deploy+publish workflow.
58
+
59
+ ### `bos config`
60
+
61
+ Print the loaded configuration (plain or fully resolved).
62
+
63
+ **Flags:** `--full`
64
+
65
+ ```bash
66
+ bos config # print bos.config.json with env overrides applied
67
+ bos config --full # print fully resolved config (extends + deep merge)
68
+ ```
69
+
70
+ → [`extends-config`](.) skill for deep merge semantics and resolved config lifecycle.
71
+
72
+ ## Lifecycle
73
+
74
+ ### `bos init`
75
+
76
+ Scaffold a new project from a deployed app or template. Interactive by default.
77
+
78
+ **Flags:** `--extends <ref>` `--directory <dir>` `--account <id>` `--domain <d>` `--source <dir>` `--plugins <list>` `--overrides <sections>` `--no-interactive` `--no-install`
79
+
80
+ ```bash
81
+ bos init # interactive wizard
82
+ bos init mysite.everything.dev # quick scaffold from current config
83
+ bos init --extends bos://account/gateway # from a specific parent
84
+ bos init --overrides ui,api,host,plugins # customise local sections
85
+ bos init --source ./my-app --no-interactive # from local source, skip prompts
86
+ ```
87
+
88
+ → [`init-upgrade`](.) skill for template download, file selection, snapshots, and conflict wiring.
89
+
90
+ ### `bos sync`
91
+
92
+ Sync template files from the parent project. Compares snapshot-versioned files to detect conflicts.
93
+
94
+ **Flags:** `--dry-run` `--no-install`
95
+
96
+ ```bash
97
+ bos sync
98
+ bos sync --dry-run # preview without writing
99
+ ```
100
+
101
+ → [`init-upgrade`](.) skill for snapshot-based conflict detection and framework-owned sections.
102
+
103
+ ### `bos upgrade`
104
+
105
+ Upgrade framework packages then sync template files. Interactive; prompts for package selection.
106
+
107
+ **Flags:** `--dry-run` `--no-install` `--no-sync`
108
+
109
+ ```bash
110
+ bos upgrade
111
+ bos upgrade --dry-run # preview version bumps
112
+ bos upgrade --no-sync # only upgrade packages, skip template sync
113
+ ```
114
+
115
+ → [`init-upgrade`](.) skill for catalog strategy, legacy import rewrites, and upgrade+publish flow.
116
+
117
+ ### `bos status`
118
+
119
+ Show project health: package versions, parent reachability, last sync, env file status, and update availability.
120
+
121
+ ```bash
122
+ bos status
123
+ ```
124
+
125
+ Output includes installed vs latest versions for each workspace package, whether the parent extends reference is reachable, whether `.env` exists, and when the last sync ran. No flags.
126
+
127
+ ## Publishing
128
+
129
+ ### `bos publish`
130
+
131
+ Publish `bos.config.json` to the FastKV on-chain registry. Optionally build and deploy workspaces first.
132
+
133
+ **Flags:** `--deploy` `--dry-run` `--network <mainnet|testnet>` `--private-key <key>` `--env <production|staging>` `--packages <list>`
134
+
135
+ ```bash
136
+ bos publish # publish current config snapshot
137
+ bos publish --deploy # build all + deploy + publish
138
+ bos publish --dry-run # preview what would be published
139
+ bos publish --network testnet # publish to testnet registry
140
+ ```
141
+
142
+ → [`publish-sync`](.) skill for config sections, SRI integrity, rollback, and FastKV troubleshooting.
143
+
144
+ ### `bos deploy`
145
+
146
+ Publish config and trigger a Railway redeploy. Builds and deploys by default.
147
+
148
+ **Flags:** `--env <production|staging>` `--no-build` `--dry-run` `--network <mainnet|testnet>` `--private-key <key>` `--service <name>` `--packages <list>`
149
+
150
+ ```bash
151
+ bos deploy # build + publish + trigger deploy
152
+ bos deploy --no-build # publish only, skip workspace build
153
+ bos deploy --service my-railway # override Railway service name
154
+ ```
155
+
156
+ → [`publish-sync`](.) skill and [`super-app`](.) for tenant-aware deploy considerations.
157
+
158
+ ### `bos key publish`
159
+
160
+ Generate a publish access key (NEAR function-call key) scoped to the FastKV registry contract.
161
+
162
+ **Flags:** `--allowance <amount>` (default: `0.25NEAR`)
163
+
164
+ ```bash
165
+ bos key publish # generate key with default allowance
166
+ bos key publish --allowance 1NEAR # custom allowance
167
+ ```
168
+
169
+ Outputs the public key, private key, and registry contract address. The generated key can only call the set and get methods on the BOS registry contract — it cannot transfer funds or call other contracts.
170
+
171
+ ## Plugins
172
+
173
+ ### `bos plugin add`
174
+
175
+ Add a plugin attachment to `bos.config.json` from a local path, BOS reference, or remote URL.
176
+
177
+ **Flags:** `<source>` (positional) `--as <alias>` `--production <url>`
178
+
179
+ ```bash
180
+ bos plugin add local:plugins/my-plugin # local plugin
181
+ bos plugin add bos://account/gateway # from BOS registry
182
+ bos plugin add https://cdn.example.com/remoteEntry.js # remote URL
183
+ bos plugin add local:plugins/my-plugin --as my-alias # with alias
184
+ bos plugin add local:plugins/my-plugin --production <prod-url> # separate prod URL
185
+ ```
186
+
187
+ → [`plugin-development`](.) skill for plugin anatomy, contract/service/index pattern, and registration.
188
+
189
+ ### `bos plugin remove`
190
+
191
+ Remove a plugin attachment from `bos.config.json`.
192
+
193
+ **Flags:** `<key>` (positional)
194
+
195
+ ```bash
196
+ bos plugin remove my-plugin
197
+ ```
198
+
199
+ ### `bos plugin list`
200
+
201
+ List all configured plugins with their sources (local vs remote), versions, and integrity hashes.
202
+
203
+ ```bash
204
+ bos plugin list
205
+ ```
206
+
207
+ Outputs each plugin's key, development source, production URL, local path, version, and SRI integrity hash.
208
+
209
+ ### `bos plugin publish`
210
+
211
+ Build and publish a single plugin to Zephyr CDN, then update `bos.config.json` with the production URL and SRI integrity hash.
212
+
213
+ **Flags:** `<key>` (positional)
214
+
215
+ ```bash
216
+ bos plugin publish my-plugin
217
+ ```
218
+
219
+ → [`plugin-development`](.) skill for build config (rspack.config.js), CLI lifecycle, and deploy workflow.
220
+
221
+ ## Types & DB
222
+
223
+ ### `bos types gen`
224
+
225
+ Generate and fetch type definitions from configured API and plugin contracts. Runs automatically on `bun install`, `bos dev`, `bos build`, `bos plugin add`, and `bos plugin remove`.
226
+
227
+ **Flags:** `--env <development|production>` `--dry-run`
228
+
229
+ ```bash
230
+ bos types gen # development mode (local + remote sources)
231
+ bos types gen --env production # production mode (remote sources only)
232
+ bos types gen --dry-run # preview without writing files
233
+ ```
234
+
235
+ Generated files: `api-types.gen.ts`, `auth-types.gen.ts` (ui, api, host), `plugins-types.gen.ts`, `plugin-sidebar.gen.ts`. Uses `local:` sources in dev and production URLs in production mode.
236
+
237
+ ### `bos db studio`
238
+
239
+ Open Drizzle Studio for inspecting a plugin's database.
240
+
241
+ **Flags:** `<plugin>` (positional, default: `api`)
242
+
243
+ ```bash
244
+ bos db studio # open Drizzle Studio for the API database
245
+ bos db studio auth # open for the auth plugin
246
+ bos db studio my-plugin # open for a custom plugin
247
+ ```
248
+
249
+ Requires the `DATABASE_URL` secret to be set on the target plugin. Uses the plugin's drizzle.config.ts for schema discovery.