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.
- package/dist/cli/prompts.cjs +2 -1
- package/dist/cli/prompts.cjs.map +1 -1
- package/dist/cli/prompts.mjs +2 -1
- package/dist/cli/prompts.mjs.map +1 -1
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/api-and-auth/SKILL.md +355 -0
- package/skills/api-and-auth/references/generated-types.md +14 -0
- package/skills/api-and-auth/references/middleware.md +38 -0
- package/skills/cli-reference/SKILL.md +249 -0
- package/skills/plugin-development/SKILL.md +478 -0
- package/skills/ui-integration/SKILL.md +383 -0
|
@@ -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.
|