@clawnify/app 0.4.0 → 0.4.1
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/index.d.ts +44 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,13 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
145
145
|
key: z.ZodString;
|
|
146
146
|
title: z.ZodString;
|
|
147
147
|
at: z.ZodOptional<z.ZodString>;
|
|
148
|
+
/**
|
|
149
|
+
* The section this widget summarises, drawn as its tile next to the title:
|
|
150
|
+
* use the same `icon` and `color` as that section's <AppNav> item (an icon
|
|
151
|
+
* name from TILE_ICONS). Without one the dashboard shows the app's icon.
|
|
152
|
+
*/
|
|
153
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
154
|
+
color: z.ZodOptional<z.ZodEnum<["red", "orange", "amber", "green", "sky", "blue", "violet", "pink"]>>;
|
|
148
155
|
}, "strip", z.ZodTypeAny, {
|
|
149
156
|
kind: "metric";
|
|
150
157
|
value: number;
|
|
@@ -153,6 +160,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
153
160
|
at?: string | undefined;
|
|
154
161
|
currency?: string | undefined;
|
|
155
162
|
format?: "number" | "currency" | "percent" | undefined;
|
|
163
|
+
icon?: string | undefined;
|
|
164
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
156
165
|
}, {
|
|
157
166
|
kind: "metric";
|
|
158
167
|
value: number;
|
|
@@ -161,6 +170,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
161
170
|
at?: string | undefined;
|
|
162
171
|
currency?: string | undefined;
|
|
163
172
|
format?: "number" | "currency" | "percent" | undefined;
|
|
173
|
+
icon?: string | undefined;
|
|
174
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
164
175
|
}>, z.ZodObject<{
|
|
165
176
|
kind: z.ZodLiteral<"series">;
|
|
166
177
|
/** Oldest first. `x` is an ISO date or a short label. */
|
|
@@ -180,6 +191,13 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
180
191
|
key: z.ZodString;
|
|
181
192
|
title: z.ZodString;
|
|
182
193
|
at: z.ZodOptional<z.ZodString>;
|
|
194
|
+
/**
|
|
195
|
+
* The section this widget summarises, drawn as its tile next to the title:
|
|
196
|
+
* use the same `icon` and `color` as that section's <AppNav> item (an icon
|
|
197
|
+
* name from TILE_ICONS). Without one the dashboard shows the app's icon.
|
|
198
|
+
*/
|
|
199
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
200
|
+
color: z.ZodOptional<z.ZodEnum<["red", "orange", "amber", "green", "sky", "blue", "violet", "pink"]>>;
|
|
183
201
|
}, "strip", z.ZodTypeAny, {
|
|
184
202
|
kind: "series";
|
|
185
203
|
title: string;
|
|
@@ -191,6 +209,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
191
209
|
at?: string | undefined;
|
|
192
210
|
currency?: string | undefined;
|
|
193
211
|
format?: "number" | "currency" | "percent" | undefined;
|
|
212
|
+
icon?: string | undefined;
|
|
213
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
194
214
|
}, {
|
|
195
215
|
kind: "series";
|
|
196
216
|
title: string;
|
|
@@ -202,6 +222,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
202
222
|
at?: string | undefined;
|
|
203
223
|
currency?: string | undefined;
|
|
204
224
|
format?: "number" | "currency" | "percent" | undefined;
|
|
225
|
+
icon?: string | undefined;
|
|
226
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
205
227
|
}>, z.ZodObject<{
|
|
206
228
|
kind: z.ZodLiteral<"breakdown">;
|
|
207
229
|
/** Drawn as ranked horizontal bars in one hue, largest first. */
|
|
@@ -221,6 +243,13 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
221
243
|
key: z.ZodString;
|
|
222
244
|
title: z.ZodString;
|
|
223
245
|
at: z.ZodOptional<z.ZodString>;
|
|
246
|
+
/**
|
|
247
|
+
* The section this widget summarises, drawn as its tile next to the title:
|
|
248
|
+
* use the same `icon` and `color` as that section's <AppNav> item (an icon
|
|
249
|
+
* name from TILE_ICONS). Without one the dashboard shows the app's icon.
|
|
250
|
+
*/
|
|
251
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
252
|
+
color: z.ZodOptional<z.ZodEnum<["red", "orange", "amber", "green", "sky", "blue", "violet", "pink"]>>;
|
|
224
253
|
}, "strip", z.ZodTypeAny, {
|
|
225
254
|
kind: "breakdown";
|
|
226
255
|
items: {
|
|
@@ -232,6 +261,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
232
261
|
at?: string | undefined;
|
|
233
262
|
currency?: string | undefined;
|
|
234
263
|
format?: "number" | "currency" | "percent" | undefined;
|
|
264
|
+
icon?: string | undefined;
|
|
265
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
235
266
|
}, {
|
|
236
267
|
kind: "breakdown";
|
|
237
268
|
items: {
|
|
@@ -243,6 +274,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
243
274
|
at?: string | undefined;
|
|
244
275
|
currency?: string | undefined;
|
|
245
276
|
format?: "number" | "currency" | "percent" | undefined;
|
|
277
|
+
icon?: string | undefined;
|
|
278
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
246
279
|
}>, z.ZodObject<{
|
|
247
280
|
kind: z.ZodLiteral<"list">;
|
|
248
281
|
items: z.ZodArray<z.ZodObject<{
|
|
@@ -262,6 +295,13 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
262
295
|
key: z.ZodString;
|
|
263
296
|
title: z.ZodString;
|
|
264
297
|
at: z.ZodOptional<z.ZodString>;
|
|
298
|
+
/**
|
|
299
|
+
* The section this widget summarises, drawn as its tile next to the title:
|
|
300
|
+
* use the same `icon` and `color` as that section's <AppNav> item (an icon
|
|
301
|
+
* name from TILE_ICONS). Without one the dashboard shows the app's icon.
|
|
302
|
+
*/
|
|
303
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
304
|
+
color: z.ZodOptional<z.ZodEnum<["red", "orange", "amber", "green", "sky", "blue", "violet", "pink"]>>;
|
|
265
305
|
}, "strip", z.ZodTypeAny, {
|
|
266
306
|
kind: "list";
|
|
267
307
|
items: {
|
|
@@ -272,6 +312,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
272
312
|
title: string;
|
|
273
313
|
key: string;
|
|
274
314
|
at?: string | undefined;
|
|
315
|
+
icon?: string | undefined;
|
|
316
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
275
317
|
}, {
|
|
276
318
|
kind: "list";
|
|
277
319
|
items: {
|
|
@@ -282,6 +324,8 @@ declare const WidgetSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
|
282
324
|
title: string;
|
|
283
325
|
key: string;
|
|
284
326
|
at?: string | undefined;
|
|
327
|
+
icon?: string | undefined;
|
|
328
|
+
color?: "red" | "orange" | "amber" | "green" | "sky" | "blue" | "violet" | "pink" | undefined;
|
|
285
329
|
}>]>;
|
|
286
330
|
type Widget = z.infer<typeof WidgetSchema>;
|
|
287
331
|
/**
|
package/dist/index.js
CHANGED
|
@@ -71,11 +71,19 @@ var text = z.string().min(1).max(WIDGET_LIMITS.text);
|
|
|
71
71
|
var at = z.string().max(512).regex(/^\/(?!\/)/, "must be an in-app path starting with a single /");
|
|
72
72
|
var format = z.enum(["number", "currency", "percent"]).optional();
|
|
73
73
|
var currency = z.string().regex(/^[A-Z]{3}$/).optional();
|
|
74
|
+
var NAV_COLOR_NAMES = ["red", "orange", "amber", "green", "sky", "blue", "violet", "pink"];
|
|
74
75
|
var base = {
|
|
75
76
|
/** Stable id, unique within the app: lowercase letters, digits, dashes. */
|
|
76
77
|
key: z.string().regex(/^[a-z0-9][a-z0-9-]{0,39}$/),
|
|
77
78
|
title: text,
|
|
78
|
-
at: at.optional()
|
|
79
|
+
at: at.optional(),
|
|
80
|
+
/**
|
|
81
|
+
* The section this widget summarises, drawn as its tile next to the title:
|
|
82
|
+
* use the same `icon` and `color` as that section's <AppNav> item (an icon
|
|
83
|
+
* name from TILE_ICONS). Without one the dashboard shows the app's icon.
|
|
84
|
+
*/
|
|
85
|
+
icon: z.string().regex(/^[a-z0-9-]{1,40}$/).optional(),
|
|
86
|
+
color: z.enum(NAV_COLOR_NAMES).optional()
|
|
79
87
|
};
|
|
80
88
|
var WidgetSchema = z.discriminatedUnion("kind", [
|
|
81
89
|
z.object({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/identity.ts","../src/widgets.ts"],"sourcesContent":["/**\n * @clawnify/app — the standard Clawnify app skeleton in one call.\n *\n * `createApp(opts)` returns a fully-wired OpenAPIHono with every always-required,\n * cross-cutting concern pre-baked, so an app's server file is just imports +\n * routes + `export default app`:\n *\n * import { createApp, createRoute, z } from \"@clawnify/app\";\n * const app = createApp<Env>({ title: \"CRM App\", version: \"1.0.0\" });\n * app.openapi(listContacts, handler);\n * export default app;\n *\n * What it bakes in — the boilerplate every app used to re-write in its server\n * file, and could forget:\n * • OpenAPIHono construction (typed to your Env).\n * • Database init — `initDB(c.env)` middleware on every request. @clawnify/db\n * auto-detects the D1 binding (production WfP) or the Storage capability\n * binding (Dynamic Workers preview), so handlers can query immediately.\n * • API discovery — `mountDiscovery` serves GET /api/openapi.json (the machine\n * contract) and GET /llms.txt (the lean agent index), generated from the\n * live routes so they can't drift.\n * • Identity — `user(c)` / `orgId(c)` / `caller(c)` read the platform-injected\n * `X-Clawnify-*` headers, so an app never hand-parses them (see identity.ts).\n *\n * The point: a new always-required concern is added HERE once and every app\n * inherits it on the next rebuild — instead of a line each agent must remember\n * to write (and a build gate to enforce) in every app.\n */\nimport { OpenAPIHono, mountDiscovery, type DiscoveryOptions } from \"@clawnify/routes\";\nimport { initDB } from \"@clawnify/db\";\nimport type { Env } from \"hono\";\n\n// One routing import for app authors: everything needed to declare routes and\n// build the app comes from @clawnify/app. These are re-exports of the same\n// @hono/zod-openapi primitives @clawnify/routes exposes — never reimplemented —\n// so route creation, discovery, and construction all live behind one import.\nexport {\n OpenAPIHono,\n createRoute,\n z,\n mountDiscovery,\n describeRoutes,\n PageQuery,\n pageParams,\n pagedResponse,\n DEFAULT_PAGE_LIMIT,\n MAX_PAGE_LIMIT,\n} from \"@clawnify/routes\";\nexport type { DiscoveryOptions, DiscoverableApp, PageParams } from \"@clawnify/routes\";\n\n// Identity of the caller, read from the platform-injected headers. Apps never\n// authenticate anyone themselves — see identity.ts.\nexport { user, orgId, caller } from \"./identity\";\nexport type { AppUser, Caller, RequestLike } from \"./identity\";\n\n// Dashboard widgets: standard tiles the dashboard draws from data the app\n// serves at GET /api/widgets — see widgets.ts.\nexport { widgets, WidgetSchema, WIDGET_LIMITS } from \"./widgets\";\nexport type { Widget } from \"./widgets\";\n\nexport interface CreateAppOptions extends DiscoveryOptions {\n /**\n * Wire the per-request database middleware (`initDB(c.env)` on `*`). Default\n * true — every Clawnify app has a D1 / Storage binding. Set false only for the\n * rare app with no database.\n */\n db?: boolean;\n /**\n * Mount GET /api/openapi.json + GET /llms.txt discovery. Default true. Set\n * false only if the app deliberately opts out of API discovery.\n */\n discovery?: boolean;\n}\n\n/**\n * Create a fully-wired Clawnify app.\n *\n * `E` is your Hono env — `{ Bindings: { DB: D1Database, ... } }` — pass it so\n * route handlers get a typed `c.env`. Everything after this call is your app's\n * own routes and business logic:\n *\n * const app = createApp<Env>({ title: \"CRM App\", version: \"1.0.0\" });\n * app.openapi(route, handler);\n * export default app;\n */\nexport function createApp<E extends Env = Env>(\n opts: CreateAppOptions = {},\n): OpenAPIHono<E> {\n const app = new OpenAPIHono<E>();\n\n if (opts.db !== false) {\n app.use(\"*\", async (c, next) => {\n // @clawnify/db reads the DB (D1) or Storage binding off the env and caches\n // the Drizzle client for this request. Safe to call on every request.\n initDB(c.env as unknown as Parameters<typeof initDB>[0]);\n await next();\n });\n }\n\n if (opts.discovery !== false) {\n mountDiscovery(app, opts);\n }\n\n return app;\n}\n","/**\n * Identity — who is calling this app.\n *\n * The platform authenticates the caller at the perimeter (app-router, the\n * app-supervisor preview tier, or the /v1/apps/:id/proxy API) and injects a\n * *fresh* set of `X-Clawnify-*` headers. Any client-supplied `X-Clawnify-*` is\n * stripped first, so these headers cannot be spoofed and an app can trust them\n * directly.\n *\n * Apps therefore never authenticate anyone themselves: never read\n * `Authorization`, never parse a cookie, never verify a JWT. Read these\n * helpers instead.\n *\n * import { user, orgId, caller } from \"@clawnify/app\";\n *\n * app.openapi(route, (c) => {\n * const u = user(c); // null when the caller isn't a person\n * return c.json({ hello: u?.name ?? \"there\", org: orgId(c) });\n * });\n */\n\n/**\n * Minimal shape this module needs — Hono's `Context` satisfies it structurally,\n * so `user(c)` works in any handler regardless of the app's Env generics.\n */\nexport interface RequestLike {\n req: { header(name: string): string | undefined };\n}\n\n/**\n * What kind of principal is calling.\n *\n * Only `user`, `api` and `agent` carry a human identity; the rest are machine\n * or anonymous callers, for which `user()` returns null.\n *\n * | Value | Who |\n * |-----------------|------------------------------------------------------------|\n * | `user` | a signed-in person in a browser |\n * | `api` | a person via the REST API / CLI / MCP (their own token) |\n * | `agent` | the org's OpenClaw agent calling the app's API |\n * | `agent-browser` | the agent driving a real browser on its VPS |\n * | `public` | an unauthenticated visitor on a declared public route |\n * | `bypass` | platform-internal fetch (build/screenshot) |\n * | `system` | the build pipeline |\n * | `app` | another app in the same org (or a headless org-token caller)|\n */\nexport type Caller =\n | \"user\"\n | \"api\"\n | \"agent\"\n | \"agent-browser\"\n | \"public\"\n | \"bypass\"\n | \"system\"\n | \"app\";\n\nconst CALLERS = new Set<Caller>([\n \"user\",\n \"api\",\n \"agent\",\n \"agent-browser\",\n \"public\",\n \"bypass\",\n \"system\",\n \"app\",\n]);\n\n/** The signed-in person behind a request. */\nexport interface AppUser {\n /** Supabase user id — the stable key to store on rows (`created_by`). */\n id: string;\n email: string | null;\n /** Display name, e.g. \"Ada Lovelace\". Null if the account has no name set. */\n name: string | null;\n /** Profile picture URL (https). Null if the account has no avatar. */\n avatarUrl: string | null;\n /**\n * Best-effort split of `name` — a HEURISTIC, not platform data.\n *\n * The platform stores one `full_name`; there is no real given/family split\n * behind it. This takes the first whitespace-separated token, which is wrong\n * for many names (mononyms, multi-word given names, family-name-first\n * cultures). Use it for a friendly greeting (\"Hi, Ada\"), never as a stored\n * field or a record of someone's legal name — persist `name` instead.\n */\n firstName: string | null;\n /** Remainder of `name` after the first token. Same heuristic caveat. */\n lastName: string | null;\n}\n\nfunction read(c: RequestLike, name: string): string | null {\n const v = c.req.header(name);\n if (v === undefined) return null;\n const trimmed = v.trim();\n return trimmed === \"\" ? null : trimmed;\n}\n\nfunction splitName(name: string | null): Pick<AppUser, \"firstName\" | \"lastName\"> {\n if (!name) return { firstName: null, lastName: null };\n const parts = name.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return { firstName: null, lastName: null };\n if (parts.length === 1) return { firstName: parts[0], lastName: null };\n return { firstName: parts[0], lastName: parts.slice(1).join(\" \") };\n}\n\n/**\n * What kind of principal is calling.\n *\n * Defaults to `\"public\"` — the least-privileged value — when the header is\n * missing or unrecognised, which happens when the app is reached outside the\n * platform perimeter (local `pnpm dev`). Gate privileged behaviour on an\n * explicit check (`caller(c) === \"user\"`), never on \"not public\".\n */\nexport function caller(c: RequestLike): Caller {\n const v = read(c, \"X-Clawnify-Caller\");\n return v && CALLERS.has(v as Caller) ? (v as Caller) : \"public\";\n}\n\n/**\n * The organization this request belongs to — the tenant key. Scope every query\n * by it.\n *\n * Present for `user`, `api`, `agent` and `app` callers. **Null** for\n * `agent-browser`, `public` and `bypass` — those reach the app through the\n * perimeter without an org-scoped identity — and null outside the platform.\n *\n * Treat null as \"no access\", never as a wildcard: a query filtered on a null\n * org must return nothing, not everything.\n */\nexport function orgId(c: RequestLike): string | null {\n return read(c, \"X-Clawnify-Org-Id\");\n}\n\n/**\n * The person behind this request, or `null` when the caller isn't one.\n *\n * Null for `agent-browser`, `public`, `bypass`, `system` and `app` — five of\n * the eight caller types — so always handle the null case rather than assuming\n * a user is present:\n *\n * const u = user(c);\n * const label = u ? u.name ?? u.email : \"Automated\";\n */\nexport function user(c: RequestLike): AppUser | null {\n const id = read(c, \"X-Clawnify-User-Id\");\n if (!id) return null;\n\n const name = read(c, \"X-Clawnify-User-Name\");\n return {\n id,\n email: read(c, \"X-Clawnify-User-Email\"),\n name,\n avatarUrl: read(c, \"X-Clawnify-User-Avatar\"),\n ...splitName(name),\n };\n}\n","/**\n * Dashboard widgets: glanceable tiles an app offers, drawn by the Clawnify\n * dashboard while the app itself is closed.\n *\n * The widget kinds are standard and the dashboard renders them with its own\n * components. An app supplies DATA only — no markup, no CSS, no chart code —\n * so every widget looks native and no app can break the home page.\n *\n * import { createApp, widgets, orgId } from \"@clawnify/app\";\n * const app = createApp<Env>({ title: \"CRM\" });\n * widgets(app, async (c) => [\n * { key: \"deals-this-month\", kind: \"metric\", title: \"Deals created this month\",\n * value: await countDealsThisMonth(c), at: \"/deals?created=this-month\" },\n * ]);\n *\n * Served at GET /api/widgets and listed in /api/openapi.json, so an agent can\n * read the same summary a person sees. The handler runs with the caller's\n * identity (`user(c)` / `orgId(c)`), so return only what that caller may see.\n *\n * The dashboard re-validates everything it receives (apps/api\n * services/app-widgets.ts is the trust boundary); the schema here exists so an\n * author gets a clear error at their own desk instead of a silently dropped\n * tile. Keep the two in step: kinds, fields and caps.\n */\nimport { createRoute, z, type OpenAPIHono } from \"@clawnify/routes\";\nimport type { Context, Env } from \"hono\";\n\nexport const WIDGET_LIMITS = {\n widgets: 6,\n seriesPoints: 90,\n breakdownItems: 12,\n listItems: 5,\n text: 80,\n} as const;\n\nconst text = z.string().min(1).max(WIDGET_LIMITS.text);\n// An in-app path. The dashboard opens the app there (its `?at=` deep link).\nconst at = z\n .string()\n .max(512)\n .regex(/^\\/(?!\\/)/, \"must be an in-app path starting with a single /\");\n// \"percent\" takes a fraction: 0.42 renders as 42%.\nconst format = z.enum([\"number\", \"currency\", \"percent\"]).optional();\n// ISO 4217, only meaningful with format \"currency\".\nconst currency = z.string().regex(/^[A-Z]{3}$/).optional();\n\nconst base = {\n /** Stable id, unique within the app: lowercase letters, digits, dashes. */\n key: z.string().regex(/^[a-z0-9][a-z0-9-]{0,39}$/),\n title: text,\n at: at.optional(),\n};\n\nexport const WidgetSchema = z.discriminatedUnion(\"kind\", [\n z.object({\n ...base,\n kind: z.literal(\"metric\"),\n value: z.number().finite(),\n format,\n currency,\n }),\n z.object({\n ...base,\n kind: z.literal(\"series\"),\n /** Oldest first. `x` is an ISO date or a short label. */\n points: z\n .array(z.object({ x: text, y: z.number().finite() }))\n .max(WIDGET_LIMITS.seriesPoints),\n format,\n currency,\n }),\n z.object({\n ...base,\n kind: z.literal(\"breakdown\"),\n /** Drawn as ranked horizontal bars in one hue, largest first. */\n items: z\n .array(z.object({ label: text, value: z.number().finite() }))\n .max(WIDGET_LIMITS.breakdownItems),\n format,\n currency,\n }),\n z.object({\n ...base,\n kind: z.literal(\"list\"),\n items: z\n .array(z.object({ label: text, meta: text.optional(), at: at.optional() }))\n .max(WIDGET_LIMITS.listItems),\n }),\n]);\n\nexport type Widget = z.infer<typeof WidgetSchema>;\n\nconst WidgetsResponse = z.object({\n widgets: z.array(WidgetSchema).max(WIDGET_LIMITS.widgets),\n});\n\nconst route = createRoute({\n method: \"get\",\n path: \"/api/widgets\",\n summary: \"Dashboard widgets: a bounded summary of this app for the current caller\",\n responses: {\n 200: {\n description: \"The app's widgets with their current data\",\n content: { \"application/json\": { schema: WidgetsResponse } },\n },\n },\n});\n\n/**\n * Serve the app's dashboard widgets at GET /api/widgets.\n *\n * `list` is called per request with the Hono context. Throwing (or returning\n * an invalid widget) yields a 500 naming the problem, so a broken widget is\n * loud in development; the dashboard shows that app's tiles as unavailable\n * and keeps rendering everything else.\n */\nexport function widgets<E extends Env>(\n app: OpenAPIHono<E>,\n list: (c: Context<E>) => Widget[] | Promise<Widget[]>,\n): void {\n app.openapi(route, async (c) => {\n const parsed = WidgetsResponse.safeParse({ widgets: await list(c as unknown as Context<E>) });\n if (!parsed.success) {\n return c.json(\n { error: \"invalid widgets\", issues: parsed.error.issues.slice(0, 5) },\n 500,\n ) as never;\n }\n return c.json(parsed.data, 200);\n });\n}\n"],"mappings":";AA4BA,SAAS,aAAa,sBAA6C;AACnE,SAAS,cAAc;AAOvB;AAAA,EACE,eAAAA;AAAA,EACA,eAAAC;AAAA,EACA,KAAAC;AAAA,EACA,kBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACSP,IAAM,UAAU,oBAAI,IAAY;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAyBD,SAAS,KAAK,GAAgB,MAA6B;AACzD,QAAM,IAAI,EAAE,IAAI,OAAO,IAAI;AAC3B,MAAI,MAAM,OAAW,QAAO;AAC5B,QAAM,UAAU,EAAE,KAAK;AACvB,SAAO,YAAY,KAAK,OAAO;AACjC;AAEA,SAAS,UAAU,MAA8D;AAC/E,MAAI,CAAC,KAAM,QAAO,EAAE,WAAW,MAAM,UAAU,KAAK;AACpD,QAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,OAAO,OAAO;AAC9C,MAAI,MAAM,WAAW,EAAG,QAAO,EAAE,WAAW,MAAM,UAAU,KAAK;AACjE,MAAI,MAAM,WAAW,EAAG,QAAO,EAAE,WAAW,MAAM,CAAC,GAAG,UAAU,KAAK;AACrE,SAAO,EAAE,WAAW,MAAM,CAAC,GAAG,UAAU,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE;AACnE;AAUO,SAAS,OAAO,GAAwB;AAC7C,QAAM,IAAI,KAAK,GAAG,mBAAmB;AACrC,SAAO,KAAK,QAAQ,IAAI,CAAW,IAAK,IAAe;AACzD;AAaO,SAAS,MAAM,GAA+B;AACnD,SAAO,KAAK,GAAG,mBAAmB;AACpC;AAYO,SAAS,KAAK,GAAgC;AACnD,QAAM,KAAK,KAAK,GAAG,oBAAoB;AACvC,MAAI,CAAC,GAAI,QAAO;AAEhB,QAAM,OAAO,KAAK,GAAG,sBAAsB;AAC3C,SAAO;AAAA,IACL;AAAA,IACA,OAAO,KAAK,GAAG,uBAAuB;AAAA,IACtC;AAAA,IACA,WAAW,KAAK,GAAG,wBAAwB;AAAA,IAC3C,GAAG,UAAU,IAAI;AAAA,EACnB;AACF;;;ACnIA,SAAS,aAAa,SAA2B;AAG1C,IAAM,gBAAgB;AAAA,EAC3B,SAAS;AAAA,EACT,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,MAAM;AACR;AAEA,IAAM,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,cAAc,IAAI;AAErD,IAAM,KAAK,EACR,OAAO,EACP,IAAI,GAAG,EACP,MAAM,aAAa,iDAAiD;AAEvE,IAAM,SAAS,EAAE,KAAK,CAAC,UAAU,YAAY,SAAS,CAAC,EAAE,SAAS;AAElE,IAAM,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,EAAE,SAAS;AAEzD,IAAM,OAAO;AAAA;AAAA,EAEX,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B;AAAA,EACjD,OAAO;AAAA,EACP,IAAI,GAAG,SAAS;AAClB;AAEO,IAAM,eAAe,EAAE,mBAAmB,QAAQ;AAAA,EACvD,EAAE,OAAO;AAAA,IACP,GAAG;AAAA,IACH,MAAM,EAAE,QAAQ,QAAQ;AAAA,IACxB,OAAO,EAAE,OAAO,EAAE,OAAO;AAAA,IACzB;AAAA,IACA;AAAA,EACF,CAAC;AAAA,EACD,EAAE,OAAO;AAAA,IACP,GAAG;AAAA,IACH,MAAM,EAAE,QAAQ,QAAQ;AAAA;AAAA,IAExB,QAAQ,EACL,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,EACnD,IAAI,cAAc,YAAY;AAAA,IACjC;AAAA,IACA;AAAA,EACF,CAAC;AAAA,EACD,EAAE,OAAO;AAAA,IACP,GAAG;AAAA,IACH,MAAM,EAAE,QAAQ,WAAW;AAAA;AAAA,IAE3B,OAAO,EACJ,MAAM,EAAE,OAAO,EAAE,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,EAC3D,IAAI,cAAc,cAAc;AAAA,IACnC;AAAA,IACA;AAAA,EACF,CAAC;AAAA,EACD,EAAE,OAAO;AAAA,IACP,GAAG;AAAA,IACH,MAAM,EAAE,QAAQ,MAAM;AAAA,IACtB,OAAO,EACJ,MAAM,EAAE,OAAO,EAAE,OAAO,MAAM,MAAM,KAAK,SAAS,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,CAAC,EACzE,IAAI,cAAc,SAAS;AAAA,EAChC,CAAC;AACH,CAAC;AAID,IAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,SAAS,EAAE,MAAM,YAAY,EAAE,IAAI,cAAc,OAAO;AAC1D,CAAC;AAED,IAAM,QAAQ,YAAY;AAAA,EACxB,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,WAAW;AAAA,IACT,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS,EAAE,oBAAoB,EAAE,QAAQ,gBAAgB,EAAE;AAAA,IAC7D;AAAA,EACF;AACF,CAAC;AAUM,SAAS,QACd,KACA,MACM;AACN,MAAI,QAAQ,OAAO,OAAO,MAAM;AAC9B,UAAM,SAAS,gBAAgB,UAAU,EAAE,SAAS,MAAM,KAAK,CAA0B,EAAE,CAAC;AAC5F,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,EAAE;AAAA,QACP,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,MAAM,GAAG,CAAC,EAAE;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,KAAK,OAAO,MAAM,GAAG;AAAA,EAChC,CAAC;AACH;;;AF7CO,SAAS,UACd,OAAyB,CAAC,GACV;AAChB,QAAM,MAAM,IAAI,YAAe;AAE/B,MAAI,KAAK,OAAO,OAAO;AACrB,QAAI,IAAI,KAAK,OAAO,GAAG,SAAS;AAG9B,aAAO,EAAE,GAA8C;AACvD,YAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAEA,MAAI,KAAK,cAAc,OAAO;AAC5B,mBAAe,KAAK,IAAI;AAAA,EAC1B;AAEA,SAAO;AACT;","names":["OpenAPIHono","createRoute","z","mountDiscovery"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/identity.ts","../src/widgets.ts"],"sourcesContent":["/**\n * @clawnify/app — the standard Clawnify app skeleton in one call.\n *\n * `createApp(opts)` returns a fully-wired OpenAPIHono with every always-required,\n * cross-cutting concern pre-baked, so an app's server file is just imports +\n * routes + `export default app`:\n *\n * import { createApp, createRoute, z } from \"@clawnify/app\";\n * const app = createApp<Env>({ title: \"CRM App\", version: \"1.0.0\" });\n * app.openapi(listContacts, handler);\n * export default app;\n *\n * What it bakes in — the boilerplate every app used to re-write in its server\n * file, and could forget:\n * • OpenAPIHono construction (typed to your Env).\n * • Database init — `initDB(c.env)` middleware on every request. @clawnify/db\n * auto-detects the D1 binding (production WfP) or the Storage capability\n * binding (Dynamic Workers preview), so handlers can query immediately.\n * • API discovery — `mountDiscovery` serves GET /api/openapi.json (the machine\n * contract) and GET /llms.txt (the lean agent index), generated from the\n * live routes so they can't drift.\n * • Identity — `user(c)` / `orgId(c)` / `caller(c)` read the platform-injected\n * `X-Clawnify-*` headers, so an app never hand-parses them (see identity.ts).\n *\n * The point: a new always-required concern is added HERE once and every app\n * inherits it on the next rebuild — instead of a line each agent must remember\n * to write (and a build gate to enforce) in every app.\n */\nimport { OpenAPIHono, mountDiscovery, type DiscoveryOptions } from \"@clawnify/routes\";\nimport { initDB } from \"@clawnify/db\";\nimport type { Env } from \"hono\";\n\n// One routing import for app authors: everything needed to declare routes and\n// build the app comes from @clawnify/app. These are re-exports of the same\n// @hono/zod-openapi primitives @clawnify/routes exposes — never reimplemented —\n// so route creation, discovery, and construction all live behind one import.\nexport {\n OpenAPIHono,\n createRoute,\n z,\n mountDiscovery,\n describeRoutes,\n PageQuery,\n pageParams,\n pagedResponse,\n DEFAULT_PAGE_LIMIT,\n MAX_PAGE_LIMIT,\n} from \"@clawnify/routes\";\nexport type { DiscoveryOptions, DiscoverableApp, PageParams } from \"@clawnify/routes\";\n\n// Identity of the caller, read from the platform-injected headers. Apps never\n// authenticate anyone themselves — see identity.ts.\nexport { user, orgId, caller } from \"./identity\";\nexport type { AppUser, Caller, RequestLike } from \"./identity\";\n\n// Dashboard widgets: standard tiles the dashboard draws from data the app\n// serves at GET /api/widgets — see widgets.ts.\nexport { widgets, WidgetSchema, WIDGET_LIMITS } from \"./widgets\";\nexport type { Widget } from \"./widgets\";\n\nexport interface CreateAppOptions extends DiscoveryOptions {\n /**\n * Wire the per-request database middleware (`initDB(c.env)` on `*`). Default\n * true — every Clawnify app has a D1 / Storage binding. Set false only for the\n * rare app with no database.\n */\n db?: boolean;\n /**\n * Mount GET /api/openapi.json + GET /llms.txt discovery. Default true. Set\n * false only if the app deliberately opts out of API discovery.\n */\n discovery?: boolean;\n}\n\n/**\n * Create a fully-wired Clawnify app.\n *\n * `E` is your Hono env — `{ Bindings: { DB: D1Database, ... } }` — pass it so\n * route handlers get a typed `c.env`. Everything after this call is your app's\n * own routes and business logic:\n *\n * const app = createApp<Env>({ title: \"CRM App\", version: \"1.0.0\" });\n * app.openapi(route, handler);\n * export default app;\n */\nexport function createApp<E extends Env = Env>(\n opts: CreateAppOptions = {},\n): OpenAPIHono<E> {\n const app = new OpenAPIHono<E>();\n\n if (opts.db !== false) {\n app.use(\"*\", async (c, next) => {\n // @clawnify/db reads the DB (D1) or Storage binding off the env and caches\n // the Drizzle client for this request. Safe to call on every request.\n initDB(c.env as unknown as Parameters<typeof initDB>[0]);\n await next();\n });\n }\n\n if (opts.discovery !== false) {\n mountDiscovery(app, opts);\n }\n\n return app;\n}\n","/**\n * Identity — who is calling this app.\n *\n * The platform authenticates the caller at the perimeter (app-router, the\n * app-supervisor preview tier, or the /v1/apps/:id/proxy API) and injects a\n * *fresh* set of `X-Clawnify-*` headers. Any client-supplied `X-Clawnify-*` is\n * stripped first, so these headers cannot be spoofed and an app can trust them\n * directly.\n *\n * Apps therefore never authenticate anyone themselves: never read\n * `Authorization`, never parse a cookie, never verify a JWT. Read these\n * helpers instead.\n *\n * import { user, orgId, caller } from \"@clawnify/app\";\n *\n * app.openapi(route, (c) => {\n * const u = user(c); // null when the caller isn't a person\n * return c.json({ hello: u?.name ?? \"there\", org: orgId(c) });\n * });\n */\n\n/**\n * Minimal shape this module needs — Hono's `Context` satisfies it structurally,\n * so `user(c)` works in any handler regardless of the app's Env generics.\n */\nexport interface RequestLike {\n req: { header(name: string): string | undefined };\n}\n\n/**\n * What kind of principal is calling.\n *\n * Only `user`, `api` and `agent` carry a human identity; the rest are machine\n * or anonymous callers, for which `user()` returns null.\n *\n * | Value | Who |\n * |-----------------|------------------------------------------------------------|\n * | `user` | a signed-in person in a browser |\n * | `api` | a person via the REST API / CLI / MCP (their own token) |\n * | `agent` | the org's OpenClaw agent calling the app's API |\n * | `agent-browser` | the agent driving a real browser on its VPS |\n * | `public` | an unauthenticated visitor on a declared public route |\n * | `bypass` | platform-internal fetch (build/screenshot) |\n * | `system` | the build pipeline |\n * | `app` | another app in the same org (or a headless org-token caller)|\n */\nexport type Caller =\n | \"user\"\n | \"api\"\n | \"agent\"\n | \"agent-browser\"\n | \"public\"\n | \"bypass\"\n | \"system\"\n | \"app\";\n\nconst CALLERS = new Set<Caller>([\n \"user\",\n \"api\",\n \"agent\",\n \"agent-browser\",\n \"public\",\n \"bypass\",\n \"system\",\n \"app\",\n]);\n\n/** The signed-in person behind a request. */\nexport interface AppUser {\n /** Supabase user id — the stable key to store on rows (`created_by`). */\n id: string;\n email: string | null;\n /** Display name, e.g. \"Ada Lovelace\". Null if the account has no name set. */\n name: string | null;\n /** Profile picture URL (https). Null if the account has no avatar. */\n avatarUrl: string | null;\n /**\n * Best-effort split of `name` — a HEURISTIC, not platform data.\n *\n * The platform stores one `full_name`; there is no real given/family split\n * behind it. This takes the first whitespace-separated token, which is wrong\n * for many names (mononyms, multi-word given names, family-name-first\n * cultures). Use it for a friendly greeting (\"Hi, Ada\"), never as a stored\n * field or a record of someone's legal name — persist `name` instead.\n */\n firstName: string | null;\n /** Remainder of `name` after the first token. Same heuristic caveat. */\n lastName: string | null;\n}\n\nfunction read(c: RequestLike, name: string): string | null {\n const v = c.req.header(name);\n if (v === undefined) return null;\n const trimmed = v.trim();\n return trimmed === \"\" ? null : trimmed;\n}\n\nfunction splitName(name: string | null): Pick<AppUser, \"firstName\" | \"lastName\"> {\n if (!name) return { firstName: null, lastName: null };\n const parts = name.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return { firstName: null, lastName: null };\n if (parts.length === 1) return { firstName: parts[0], lastName: null };\n return { firstName: parts[0], lastName: parts.slice(1).join(\" \") };\n}\n\n/**\n * What kind of principal is calling.\n *\n * Defaults to `\"public\"` — the least-privileged value — when the header is\n * missing or unrecognised, which happens when the app is reached outside the\n * platform perimeter (local `pnpm dev`). Gate privileged behaviour on an\n * explicit check (`caller(c) === \"user\"`), never on \"not public\".\n */\nexport function caller(c: RequestLike): Caller {\n const v = read(c, \"X-Clawnify-Caller\");\n return v && CALLERS.has(v as Caller) ? (v as Caller) : \"public\";\n}\n\n/**\n * The organization this request belongs to — the tenant key. Scope every query\n * by it.\n *\n * Present for `user`, `api`, `agent` and `app` callers. **Null** for\n * `agent-browser`, `public` and `bypass` — those reach the app through the\n * perimeter without an org-scoped identity — and null outside the platform.\n *\n * Treat null as \"no access\", never as a wildcard: a query filtered on a null\n * org must return nothing, not everything.\n */\nexport function orgId(c: RequestLike): string | null {\n return read(c, \"X-Clawnify-Org-Id\");\n}\n\n/**\n * The person behind this request, or `null` when the caller isn't one.\n *\n * Null for `agent-browser`, `public`, `bypass`, `system` and `app` — five of\n * the eight caller types — so always handle the null case rather than assuming\n * a user is present:\n *\n * const u = user(c);\n * const label = u ? u.name ?? u.email : \"Automated\";\n */\nexport function user(c: RequestLike): AppUser | null {\n const id = read(c, \"X-Clawnify-User-Id\");\n if (!id) return null;\n\n const name = read(c, \"X-Clawnify-User-Name\");\n return {\n id,\n email: read(c, \"X-Clawnify-User-Email\"),\n name,\n avatarUrl: read(c, \"X-Clawnify-User-Avatar\"),\n ...splitName(name),\n };\n}\n","/**\n * Dashboard widgets: glanceable tiles an app offers, drawn by the Clawnify\n * dashboard while the app itself is closed.\n *\n * The widget kinds are standard and the dashboard renders them with its own\n * components. An app supplies DATA only — no markup, no CSS, no chart code —\n * so every widget looks native and no app can break the home page.\n *\n * import { createApp, widgets, orgId } from \"@clawnify/app\";\n * const app = createApp<Env>({ title: \"CRM\" });\n * widgets(app, async (c) => [\n * { key: \"deals-this-month\", kind: \"metric\", title: \"Deals created this month\",\n * value: await countDealsThisMonth(c), at: \"/deals?created=this-month\" },\n * ]);\n *\n * Served at GET /api/widgets and listed in /api/openapi.json, so an agent can\n * read the same summary a person sees. The handler runs with the caller's\n * identity (`user(c)` / `orgId(c)`), so return only what that caller may see.\n *\n * The dashboard re-validates everything it receives (apps/api\n * services/app-widgets.ts is the trust boundary); the schema here exists so an\n * author gets a clear error at their own desk instead of a silently dropped\n * tile. Keep the two in step: kinds, fields and caps.\n */\nimport { createRoute, z, type OpenAPIHono } from \"@clawnify/routes\";\nimport type { Context, Env } from \"hono\";\n\nexport const WIDGET_LIMITS = {\n widgets: 6,\n seriesPoints: 90,\n breakdownItems: 12,\n listItems: 5,\n text: 80,\n} as const;\n\nconst text = z.string().min(1).max(WIDGET_LIMITS.text);\n// An in-app path. The dashboard opens the app there (its `?at=` deep link).\nconst at = z\n .string()\n .max(512)\n .regex(/^\\/(?!\\/)/, \"must be an in-app path starting with a single /\");\n// \"percent\" takes a fraction: 0.42 renders as 42%.\nconst format = z.enum([\"number\", \"currency\", \"percent\"]).optional();\n// ISO 4217, only meaningful with format \"currency\".\nconst currency = z.string().regex(/^[A-Z]{3}$/).optional();\n\n// Same palette as <AppNav> sections (NAV_COLORS in ./client). Kept as names\n// here so the server entry never imports the React client module.\nconst NAV_COLOR_NAMES = [\"red\", \"orange\", \"amber\", \"green\", \"sky\", \"blue\", \"violet\", \"pink\"] as const;\n\nconst base = {\n /** Stable id, unique within the app: lowercase letters, digits, dashes. */\n key: z.string().regex(/^[a-z0-9][a-z0-9-]{0,39}$/),\n title: text,\n at: at.optional(),\n /**\n * The section this widget summarises, drawn as its tile next to the title:\n * use the same `icon` and `color` as that section's <AppNav> item (an icon\n * name from TILE_ICONS). Without one the dashboard shows the app's icon.\n */\n icon: z.string().regex(/^[a-z0-9-]{1,40}$/).optional(),\n color: z.enum(NAV_COLOR_NAMES).optional(),\n};\n\nexport const WidgetSchema = z.discriminatedUnion(\"kind\", [\n z.object({\n ...base,\n kind: z.literal(\"metric\"),\n value: z.number().finite(),\n format,\n currency,\n }),\n z.object({\n ...base,\n kind: z.literal(\"series\"),\n /** Oldest first. `x` is an ISO date or a short label. */\n points: z\n .array(z.object({ x: text, y: z.number().finite() }))\n .max(WIDGET_LIMITS.seriesPoints),\n format,\n currency,\n }),\n z.object({\n ...base,\n kind: z.literal(\"breakdown\"),\n /** Drawn as ranked horizontal bars in one hue, largest first. */\n items: z\n .array(z.object({ label: text, value: z.number().finite() }))\n .max(WIDGET_LIMITS.breakdownItems),\n format,\n currency,\n }),\n z.object({\n ...base,\n kind: z.literal(\"list\"),\n items: z\n .array(z.object({ label: text, meta: text.optional(), at: at.optional() }))\n .max(WIDGET_LIMITS.listItems),\n }),\n]);\n\nexport type Widget = z.infer<typeof WidgetSchema>;\n\nconst WidgetsResponse = z.object({\n widgets: z.array(WidgetSchema).max(WIDGET_LIMITS.widgets),\n});\n\nconst route = createRoute({\n method: \"get\",\n path: \"/api/widgets\",\n summary: \"Dashboard widgets: a bounded summary of this app for the current caller\",\n responses: {\n 200: {\n description: \"The app's widgets with their current data\",\n content: { \"application/json\": { schema: WidgetsResponse } },\n },\n },\n});\n\n/**\n * Serve the app's dashboard widgets at GET /api/widgets.\n *\n * `list` is called per request with the Hono context. Throwing (or returning\n * an invalid widget) yields a 500 naming the problem, so a broken widget is\n * loud in development; the dashboard shows that app's tiles as unavailable\n * and keeps rendering everything else.\n */\nexport function widgets<E extends Env>(\n app: OpenAPIHono<E>,\n list: (c: Context<E>) => Widget[] | Promise<Widget[]>,\n): void {\n app.openapi(route, async (c) => {\n const parsed = WidgetsResponse.safeParse({ widgets: await list(c as unknown as Context<E>) });\n if (!parsed.success) {\n return c.json(\n { error: \"invalid widgets\", issues: parsed.error.issues.slice(0, 5) },\n 500,\n ) as never;\n }\n return c.json(parsed.data, 200);\n });\n}\n"],"mappings":"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","names":["OpenAPIHono","createRoute","z","mountDiscovery"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawnify/app",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "The standard Clawnify app skeleton in one call. createApp() returns a fully-wired OpenAPIHono with D1/Storage init + /api/openapi.json + /llms.txt discovery pre-baked, so an app is just imports + routes + export.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|