@wervt/nuxt 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +4 -0
  2. package/dist/dev.d.ts +3 -1
  3. package/dist/env-types.js +14 -0
  4. package/dist/icons.js +31 -0
  5. package/dist/module.d.ts +8 -0
  6. package/dist/module.js +59 -0
  7. package/dist/node_modules/.pnpm/nitro@3.0.260903-beta/node_modules/nitro/dist/runtime/task.js +1 -2
  8. package/dist/og-image.js +39 -0
  9. package/dist/preview.d.ts +14 -0
  10. package/dist/preview.js +39 -0
  11. package/dist/provision.d.ts +19 -2
  12. package/dist/provision.js +49 -2
  13. package/dist/runtime/app/snapshot.d.ts +17 -0
  14. package/dist/runtime/app/snapshot.js +64 -0
  15. package/dist/runtime/app/useShape.d.ts +8 -1
  16. package/dist/runtime/app/useShape.js +8 -52
  17. package/dist/runtime/app/useWervtFeedback.d.ts +2 -1
  18. package/dist/runtime/email/index.d.ts +2 -1
  19. package/dist/runtime/email/index.js +2 -1
  20. package/dist/runtime/email/invite.d.ts +75 -0
  21. package/dist/runtime/email/invite.js +60 -0
  22. package/dist/runtime/server/access.d.ts +35 -0
  23. package/dist/runtime/server/access.js +88 -0
  24. package/dist/runtime/server/agent-token-plugin.d.ts +13 -0
  25. package/dist/runtime/server/agent-token-plugin.js +10 -0
  26. package/dist/runtime/server/anthropic.d.ts +21 -0
  27. package/dist/runtime/server/anthropic.js +53 -0
  28. package/dist/runtime/server/identity.d.ts +20 -0
  29. package/dist/runtime/server/identity.js +37 -0
  30. package/dist/runtime/server/index.d.ts +5 -3
  31. package/dist/runtime/server/index.js +4 -2
  32. package/dist/runtime/server/task-handlers.d.ts +2 -1
  33. package/dist/runtime/server/tools.d.ts +2 -1
  34. package/dist/runtime/server/user.d.ts +9 -3
  35. package/dist/runtime/server/user.js +28 -3
  36. package/dist/runtime/server/validate.d.ts +2 -1
  37. package/dist/runtime/shared/agent-token.d.ts +9 -0
  38. package/dist/runtime/shared/agent-token.js +17 -0
  39. package/dist/runtime/shared/shape.d.ts +2 -1
  40. package/dist/runtime/shared/user.d.ts +8 -0
  41. package/dist/runtime/shared/user.js +4 -1
  42. package/dist/storage.d.ts +48 -1
  43. package/dist/storage.js +217 -3
  44. package/package.json +15 -6
@@ -0,0 +1,75 @@
1
+ //#region src/runtime/email/invite.d.ts
2
+ /**
3
+ * An invitation to an app: who invited, where to go, and that signing in with this address is
4
+ * all it takes. Send it with `sendMail({ to, template: InviteEmail, props: { by, app, url } })`.
5
+ */
6
+ export declare const InviteEmail: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ /** Who invited them, by name or email. */
8
+ by: {
9
+ type: StringConstructor;
10
+ required: true;
11
+ };
12
+ /** The app they're invited to, as people know it. */
13
+ app: {
14
+ type: StringConstructor;
15
+ required: true;
16
+ };
17
+ /** Where the button goes: the app, or a link that lets them in. */
18
+ url: {
19
+ type: StringConstructor;
20
+ required: true;
21
+ };
22
+ /** What the app is, in a sentence after "<by> invited you to <app>." */
23
+ message: {
24
+ type: StringConstructor;
25
+ default: string;
26
+ };
27
+ /** The button's label; "Open <app>" unless set. */
28
+ action: {
29
+ type: StringConstructor;
30
+ default: string;
31
+ };
32
+ /** A hint under the button; by default, to sign in with this address. */
33
+ note: {
34
+ type: StringConstructor;
35
+ default: string;
36
+ };
37
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
38
+ [key: string]: any;
39
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
40
+ /** Who invited them, by name or email. */
41
+ by: {
42
+ type: StringConstructor;
43
+ required: true;
44
+ };
45
+ /** The app they're invited to, as people know it. */
46
+ app: {
47
+ type: StringConstructor;
48
+ required: true;
49
+ };
50
+ /** Where the button goes: the app, or a link that lets them in. */
51
+ url: {
52
+ type: StringConstructor;
53
+ required: true;
54
+ };
55
+ /** What the app is, in a sentence after "<by> invited you to <app>." */
56
+ message: {
57
+ type: StringConstructor;
58
+ default: string;
59
+ };
60
+ /** The button's label; "Open <app>" unless set. */
61
+ action: {
62
+ type: StringConstructor;
63
+ default: string;
64
+ };
65
+ /** A hint under the button; by default, to sign in with this address. */
66
+ note: {
67
+ type: StringConstructor;
68
+ default: string;
69
+ };
70
+ }>> & Readonly<{}>, {
71
+ message: string;
72
+ action: string;
73
+ note: string;
74
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
75
+ //#endregion
@@ -0,0 +1,60 @@
1
+ import { EmailButton, EmailHeading, EmailMuted, EmailText, WervtEmail } from "./components.js";
2
+ import { defineComponent, h } from "vue";
3
+ //#region src/runtime/email/invite.ts
4
+ /**
5
+ * An invitation to an app: who invited, where to go, and that signing in with this address is
6
+ * all it takes. Send it with `sendMail({ to, template: InviteEmail, props: { by, app, url } })`.
7
+ */
8
+ const InviteEmail = defineComponent({
9
+ name: "InviteEmail",
10
+ props: {
11
+ /** Who invited them, by name or email. */
12
+ by: {
13
+ type: String,
14
+ required: true
15
+ },
16
+ /** The app they're invited to, as people know it. */
17
+ app: {
18
+ type: String,
19
+ required: true
20
+ },
21
+ /** Where the button goes: the app, or a link that lets them in. */
22
+ url: {
23
+ type: String,
24
+ required: true
25
+ },
26
+ /** What the app is, in a sentence after "<by> invited you to <app>." */
27
+ message: {
28
+ type: String,
29
+ default: ""
30
+ },
31
+ /** The button's label; "Open <app>" unless set. */
32
+ action: {
33
+ type: String,
34
+ default: ""
35
+ },
36
+ /** A hint under the button; by default, to sign in with this address. */
37
+ note: {
38
+ type: String,
39
+ default: ""
40
+ }
41
+ },
42
+ setup(props) {
43
+ return () => h(WervtEmail, {
44
+ subject: `${props.by} invited you to ${props.app}`,
45
+ app: props.app,
46
+ preview: props.message || `Join ${props.app}`,
47
+ reason: `You received this email because ${props.by} invited this address.`
48
+ }, () => [
49
+ h(EmailHeading, { eyebrow: "Invitation" }, () => `You're invited to ${props.app}`),
50
+ h(EmailText, () => [`${props.by} invited you to ${props.app}.`, props.message].filter(Boolean).join(" ")),
51
+ h(EmailButton, {
52
+ href: props.url,
53
+ color: "neutral"
54
+ }, () => props.action || `Open ${props.app}`),
55
+ h(EmailMuted, () => props.note || "Sign in with this email address to get in.")
56
+ ]);
57
+ }
58
+ });
59
+ //#endregion
60
+ export { InviteEmail };
@@ -0,0 +1,35 @@
1
+ import { RequestEvent } from "nuxt/server";
2
+ //#region src/runtime/server/access.d.ts
3
+ export interface WervtInvite {
4
+ email: string;
5
+ /** Who invited them. */
6
+ by: string;
7
+ /** ISO time. */
8
+ at: string;
9
+ }
10
+ export interface WervtAccess {
11
+ /** Lets `email` into the app. `invited` is false when they could already get in. */
12
+ invite(email: string): Promise<{
13
+ email: string;
14
+ invited: boolean;
15
+ }>;
16
+ /** Takes back an invite (not the owner's allow list). */
17
+ revoke(email: string): Promise<{
18
+ email: string;
19
+ revoked: boolean;
20
+ }>;
21
+ /** Everyone the app invited, with who invited them and when. */
22
+ list(): Promise<WervtInvite[]>;
23
+ }
24
+ /**
25
+ * Invites on an invite-only app (`wervt access <app> --invite`), as the user of this request: the
26
+ * runtime vouches for them, and only someone with access can invite. Everyone with access may
27
+ * invite, or only the owner, members and admins with `wervt access <app> --invite-by owners`;
28
+ * restrict it further in the route if the app has its own roles.
29
+ *
30
+ * Throws with the platform's status: 401 without a signed-in user, 403 when they may not invite,
31
+ * 409 when the app isn't invite-only, 429 past the limits (25 invites per person and day, 500 per
32
+ * app).
33
+ */
34
+ export declare function useWervtAccess(event: RequestEvent): WervtAccess;
35
+ //#endregion
@@ -0,0 +1,88 @@
1
+ import { DEV_LOGIN_COOKIE, devUser } from "../shared/user.js";
2
+ import { createError, getCookie } from "nuxt/server";
3
+ //#region src/runtime/server/access.ts
4
+ const devInvites = [];
5
+ /**
6
+ * Invites on an invite-only app (`wervt access <app> --invite`), as the user of this request: the
7
+ * runtime vouches for them, and only someone with access can invite. Everyone with access may
8
+ * invite, or only the owner, members and admins with `wervt access <app> --invite-by owners`;
9
+ * restrict it further in the route if the app has its own roles.
10
+ *
11
+ * Throws with the platform's status: 401 without a signed-in user, 403 when they may not invite,
12
+ * 409 when the app isn't invite-only, 429 past the limits (25 invites per person and day, 500 per
13
+ * app).
14
+ */
15
+ function useWervtAccess(event) {
16
+ const runtime = process.env.WERVT_RUNTIME_URL;
17
+ if (!runtime) return devAccess(event);
18
+ async function call(method, body) {
19
+ const res = await fetch(new URL("/_wervt/access", runtime), {
20
+ method,
21
+ headers: {
22
+ "content-type": "application/json",
23
+ "x-wervt-app": process.env.WERVT_APP ?? "",
24
+ "x-wervt-token": process.env.WERVT_TOKEN ?? "",
25
+ "x-wervt-user": event.req.headers.get("x-wervt-user") ?? "",
26
+ "x-wervt-user-proof": event.req.headers.get("x-wervt-user-proof") ?? ""
27
+ },
28
+ body: body && JSON.stringify(body)
29
+ });
30
+ const data = await res.json().catch(() => ({}));
31
+ if (!res.ok) throw createError({
32
+ status: res.status,
33
+ message: data.error ?? `access ${res.status}`
34
+ });
35
+ return data;
36
+ }
37
+ return {
38
+ invite: (email) => call("POST", { email }),
39
+ revoke: (email) => call("DELETE", { email }),
40
+ list: async () => (await call("GET")).invited
41
+ };
42
+ }
43
+ function devAccess(event) {
44
+ const user = devUser(getCookie(event, DEV_LOGIN_COOKIE) === "1");
45
+ const by = () => {
46
+ if (!user) throw createError({
47
+ status: 401,
48
+ message: "sign in at /_wervt/login first"
49
+ });
50
+ return user.email;
51
+ };
52
+ return {
53
+ async invite(email) {
54
+ const inviter = by();
55
+ email = email.trim().toLowerCase();
56
+ if (devInvites.some((invite) => invite.email === email)) return {
57
+ email,
58
+ invited: false
59
+ };
60
+ devInvites.push({
61
+ email,
62
+ by: inviter,
63
+ at: (/* @__PURE__ */ new Date()).toISOString()
64
+ });
65
+ console.info(`[access] ${inviter} invited ${email} (dev: not stored)`);
66
+ return {
67
+ email,
68
+ invited: true
69
+ };
70
+ },
71
+ async revoke(email) {
72
+ by();
73
+ email = email.trim().toLowerCase();
74
+ const index = devInvites.findIndex((invite) => invite.email === email);
75
+ if (index >= 0) devInvites.splice(index, 1);
76
+ return {
77
+ email,
78
+ revoked: index >= 0
79
+ };
80
+ },
81
+ async list() {
82
+ by();
83
+ return [...devInvites];
84
+ }
85
+ };
86
+ }
87
+ //#endregion
88
+ export { useWervtAccess };
@@ -0,0 +1,13 @@
1
+ //#region src/runtime/server/agent-token-plugin.d.ts
2
+ interface EnrichContext {
3
+ event: Record<string, unknown>;
4
+ headers?: Record<string, string>;
5
+ }
6
+ interface NitroAppHooks {
7
+ hooks: {
8
+ hook(name: 'evlog:enrich', handler: (ctx: EnrichContext) => void): unknown;
9
+ };
10
+ }
11
+ export default function wervtAgentToken(nitroApp: NitroAppHooks): void;
12
+ //#endregion
13
+ export {};
@@ -0,0 +1,10 @@
1
+ import { parseAgentToken } from "../shared/agent-token.js";
2
+ //#region src/runtime/server/agent-token-plugin.ts
3
+ function wervtAgentToken(nitroApp) {
4
+ nitroApp.hooks.hook("evlog:enrich", (ctx) => {
5
+ const agentToken = parseAgentToken(ctx.headers?.["x-wervt-agent-token"]);
6
+ if (agentToken) ctx.event.agentToken = agentToken;
7
+ });
8
+ }
9
+ //#endregion
10
+ export { wervtAgentToken as default };
@@ -0,0 +1,21 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ //#region src/runtime/server/anthropic.d.ts
3
+ export interface UseAnthropicOptions {
4
+ /**
5
+ * The key for `nuxt dev`, where there is no workload identity. Default: `ANTHROPIC_API_KEY`.
6
+ * Never used on wervt.
7
+ */
8
+ devApiKey?: string;
9
+ }
10
+ /**
11
+ * An Anthropic client that authenticates as this app through its wervt identity: no API key, only
12
+ * IDs from the Anthropic Console in the app's env (`ANTHROPIC_FEDERATION_RULE_ID`,
13
+ * `ANTHROPIC_ORGANIZATION_ID`, optionally `ANTHROPIC_SERVICE_ACCOUNT_ID` and
14
+ * `ANTHROPIC_WORKSPACE_ID`). The SDK trades an identity token for an Anthropic token and renews it
15
+ * before it runs out.
16
+ *
17
+ * Needs `wervt identity enable <app>` and a federation rule for the app's subject
18
+ * (`wervt identity status <app>`). In `nuxt dev` it uses `options.devApiKey` instead.
19
+ */
20
+ export declare function useAnthropic(options?: UseAnthropicOptions): Anthropic;
21
+ //#endregion
@@ -0,0 +1,53 @@
1
+ import { useWorkloadIdentity } from "./identity.js";
2
+ import { createError } from "nuxt/server";
3
+ import Anthropic from "@anthropic-ai/sdk";
4
+ import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
5
+ //#region src/runtime/server/anthropic.ts
6
+ const BASE_URL = "https://api.anthropic.com";
7
+ let client;
8
+ /**
9
+ * An Anthropic client that authenticates as this app through its wervt identity: no API key, only
10
+ * IDs from the Anthropic Console in the app's env (`ANTHROPIC_FEDERATION_RULE_ID`,
11
+ * `ANTHROPIC_ORGANIZATION_ID`, optionally `ANTHROPIC_SERVICE_ACCOUNT_ID` and
12
+ * `ANTHROPIC_WORKSPACE_ID`). The SDK trades an identity token for an Anthropic token and renews it
13
+ * before it runs out.
14
+ *
15
+ * Needs `wervt identity enable <app>` and a federation rule for the app's subject
16
+ * (`wervt identity status <app>`). In `nuxt dev` it uses `options.devApiKey` instead.
17
+ */
18
+ function useAnthropic(options = {}) {
19
+ return client ??= create(options);
20
+ }
21
+ function create(options) {
22
+ if (!process.env.WERVT_RUNTIME_URL) {
23
+ const apiKey = options.devApiKey ?? process.env.ANTHROPIC_API_KEY;
24
+ if (!apiKey) throw createError({
25
+ status: 503,
26
+ message: "useAnthropic() in `nuxt dev` needs an API key (ANTHROPIC_API_KEY or devApiKey)."
27
+ });
28
+ return new Anthropic({ apiKey });
29
+ }
30
+ const federationRuleId = process.env.ANTHROPIC_FEDERATION_RULE_ID;
31
+ const organizationId = process.env.ANTHROPIC_ORGANIZATION_ID;
32
+ if (!federationRuleId || !organizationId) throw createError({
33
+ status: 503,
34
+ message: "useAnthropic() needs ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID (wervt env <app> --set …)."
35
+ });
36
+ const identity = useWorkloadIdentity();
37
+ return new Anthropic({
38
+ apiKey: null,
39
+ authToken: null,
40
+ baseURL: BASE_URL,
41
+ credentials: oidcFederationProvider({
42
+ identityTokenProvider: () => identity.token(BASE_URL),
43
+ federationRuleId,
44
+ organizationId,
45
+ serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID || void 0,
46
+ workspaceId: process.env.ANTHROPIC_WORKSPACE_ID || void 0,
47
+ baseURL: BASE_URL,
48
+ fetch: (...args) => fetch(...args)
49
+ })
50
+ });
51
+ }
52
+ //#endregion
53
+ export { useAnthropic };
@@ -0,0 +1,20 @@
1
+ //#region src/runtime/server/identity.d.ts
2
+ export interface WorkloadIdentity {
3
+ /**
4
+ * A fresh JWT that says "I am this wervt app", for `audience` (a provider on wervt's list, e.g.
5
+ * `https://api.anthropic.com`). Valid for 5 minutes and single-use: trade it for a provider
6
+ * token right away. Each call signs a new one; providers' SDKs cache what they get for it.
7
+ */
8
+ token(audience: string): Promise<string>;
9
+ }
10
+ /**
11
+ * The app's workload identity: tokens signed by wervt's issuer, so the app can authenticate to an
12
+ * AI provider (Anthropic's Workload Identity Federation) without an API key. Its owner turns it
13
+ * on with `wervt identity enable <app>`; `useAnthropic()` (`@wervt/nuxt/anthropic`) uses it.
14
+ *
15
+ * Throws with the platform's status: 403 when the app has no identity, 400 for an audience wervt
16
+ * doesn't sign for, 429 past 10 tokens a minute, 503 when the issuer is off. In `nuxt dev` there
17
+ * is no issuer, so it always throws.
18
+ */
19
+ export declare function useWorkloadIdentity(): WorkloadIdentity;
20
+ //#endregion
@@ -0,0 +1,37 @@
1
+ import { createError } from "nuxt/server";
2
+ //#region src/runtime/server/identity.ts
3
+ /**
4
+ * The app's workload identity: tokens signed by wervt's issuer, so the app can authenticate to an
5
+ * AI provider (Anthropic's Workload Identity Federation) without an API key. Its owner turns it
6
+ * on with `wervt identity enable <app>`; `useAnthropic()` (`@wervt/nuxt/anthropic`) uses it.
7
+ *
8
+ * Throws with the platform's status: 403 when the app has no identity, 400 for an audience wervt
9
+ * doesn't sign for, 429 past 10 tokens a minute, 503 when the issuer is off. In `nuxt dev` there
10
+ * is no issuer, so it always throws.
11
+ */
12
+ function useWorkloadIdentity() {
13
+ return { async token(audience) {
14
+ const runtime = process.env.WERVT_RUNTIME_URL;
15
+ if (!runtime) throw createError({
16
+ status: 503,
17
+ message: "Workload identity only exists on wervt, not in `nuxt dev`."
18
+ });
19
+ const res = await fetch(new URL("/_wervt/identity-token", runtime), {
20
+ method: "POST",
21
+ headers: {
22
+ "content-type": "application/json",
23
+ "x-wervt-app": process.env.WERVT_APP ?? "",
24
+ "x-wervt-token": process.env.WERVT_TOKEN ?? ""
25
+ },
26
+ body: JSON.stringify({ audience })
27
+ });
28
+ const body = await res.json().catch(() => ({}));
29
+ if (!res.ok || !body.token) throw createError({
30
+ status: res.status,
31
+ message: body.error ?? `identity token failed (${res.status})`
32
+ });
33
+ return body.token;
34
+ } };
35
+ }
36
+ //#endregion
37
+ export { useWorkloadIdentity };
@@ -1,6 +1,8 @@
1
- import { WervtUser } from "../shared/user.js";
1
+ import { WervtRole, WervtUser } from "../shared/user.js";
2
2
  import { EmailProps, RenderedEmail, renderEmail } from "../email/render.js";
3
+ import { WervtAccess, WervtInvite, useWervtAccess } from "./access.js";
3
4
  import { useDb } from "./db.js";
5
+ import { WorkloadIdentity, useWorkloadIdentity } from "./identity.js";
4
6
  import { createEvlogError, log, useLogger } from "./logger.js";
5
7
  import { SendMailOptions, sendMail } from "./mail.js";
6
8
  import { McpTokenContext } from "./mcp-auth.js";
@@ -10,5 +12,5 @@ import { ShapeOptions, ShapeRow, proxyShape } from "./shape.js";
10
12
  import { Task, TaskEvent, TaskResult, defineTask } from "./tasks.js";
11
13
  import { SafeResult, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
12
14
  import { Tool, ToolRoute, defineTool, defineToolRoute } from "./tools.js";
13
- import { useWervtUser } from "./user.js";
14
- export { type EmailProps, type McpTokenContext, type RenderedEmail, type SafeResult, type SendMailOptions, type ShapeOptions, type ShapeResponse, type ShapeRow, type Task, type TaskEvent, type TaskResult, type Tool, type ToolRoute, type WervtUser, createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtUser, vh };
15
+ import { requireWervtRole, useWervtUser } from "./user.js";
16
+ export { type EmailProps, type McpTokenContext, type RenderedEmail, type SafeResult, type SendMailOptions, type ShapeOptions, type ShapeResponse, type ShapeRow, type Task, type TaskEvent, type TaskResult, type Tool, type ToolRoute, type WervtAccess, type WervtInvite, type WervtRole, type WervtUser, type WorkloadIdentity, createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, requireWervtRole, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, useWorkloadIdentity, vh };
@@ -1,4 +1,6 @@
1
1
  import { renderEmail } from "../email/render.js";
2
+ import { useWervtAccess } from "./access.js";
3
+ import { useWorkloadIdentity } from "./identity.js";
2
4
  import { useDb } from "./db.js";
3
5
  import { createEvlogError, log, useLogger } from "./logger.js";
4
6
  import { sendMail } from "./mail.js";
@@ -7,5 +9,5 @@ import { proxyShape } from "./shape.js";
7
9
  import { defineTask } from "./tasks.js";
8
10
  import { useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
9
11
  import { defineTool, defineToolRoute } from "./tools.js";
10
- import { useWervtUser } from "./user.js";
11
- export { createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtUser, vh };
12
+ import { requireWervtRole, useWervtUser } from "./user.js";
13
+ export { createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, requireWervtRole, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, useWorkloadIdentity, vh };
@@ -25,4 +25,5 @@ export declare function taskListHandler(tasks: Registry): import("nuxt/server").
25
25
  }[] | {
26
26
  error: string;
27
27
  }>>;
28
- //#endregion
28
+ //#endregion
29
+ export {};
@@ -48,4 +48,5 @@ export declare const TOOL_ROUTE: unique symbol;
48
48
  export declare function defineToolRoute<R, P extends Input | undefined = undefined, I extends Input | undefined = undefined>(route: ToolRoute<P, I, R>): import("nuxt/server").EventHandler<Promise<R>> & {
49
49
  [TOOL_ROUTE]: Tool<Input | undefined, unknown>;
50
50
  };
51
- //#endregion
51
+ //#endregion
52
+ export {};
@@ -1,4 +1,4 @@
1
- import { DEV_LOGIN_COOKIE, WervtUser, parseWervtUser } from "../shared/user.js";
1
+ import { DEV_LOGIN_COOKIE, WervtRole, WervtUser, parseWervtUser } from "../shared/user.js";
2
2
  import { RequestEvent } from "nuxt/server";
3
3
  //#region src/runtime/server/user.d.ts
4
4
  /**
@@ -7,6 +7,12 @@ import { RequestEvent } from "nuxt/server";
7
7
  * apps get one when the visitor is signed in. Send people to `/_wervt/login` to sign in.
8
8
  * In a tool called over MCP, it's the user the client signed in as.
9
9
  */
10
- export declare const useWervtUser: (event: RequestEvent) => WervtUser | undefined;
10
+ export declare function useWervtUser(event: RequestEvent): WervtUser | undefined;
11
+ /**
12
+ * The signed-in user if their role is one of `roles`, e.g.
13
+ * `requireWervtRole(event, 'owner', 'member', 'admin')` for the app's own admin features.
14
+ * Throws 401 without a signed-in user, 403 with another role.
15
+ */
16
+ export declare function requireWervtRole(event: RequestEvent, ...roles: WervtRole[]): WervtUser;
11
17
  //#endregion
12
- export { DEV_LOGIN_COOKIE, type WervtUser, parseWervtUser };
18
+ export { DEV_LOGIN_COOKIE, type WervtRole, type WervtUser, parseWervtUser };
@@ -1,5 +1,5 @@
1
1
  import { DEV_LOGIN_COOKIE, devUser, parseWervtUser } from "../shared/user.js";
2
- import { getCookie } from "nuxt/server";
2
+ import { createError, getCookie } from "nuxt/server";
3
3
  //#region src/runtime/server/user.ts
4
4
  const MCP_USER = "wervtMcpUser";
5
5
  /**
@@ -8,6 +8,31 @@ const MCP_USER = "wervtMcpUser";
8
8
  * apps get one when the visitor is signed in. Send people to `/_wervt/login` to sign in.
9
9
  * In a tool called over MCP, it's the user the client signed in as.
10
10
  */
11
- const useWervtUser = (event) => event.context[MCP_USER] ?? parseWervtUser(event.req.headers.get("x-wervt-user")) ?? devUser(getCookie(event, "wervt_dev_login") === "1");
11
+ function useWervtUser(event) {
12
+ const header = parseWervtUser(event.req.headers.get("x-wervt-user"));
13
+ const mcp = event.context[MCP_USER];
14
+ if (mcp) return mcp.role || header?.id !== mcp.id ? mcp : {
15
+ ...mcp,
16
+ role: header.role
17
+ };
18
+ return header ?? devUser(getCookie(event, "wervt_dev_login") === "1");
19
+ }
20
+ /**
21
+ * The signed-in user if their role is one of `roles`, e.g.
22
+ * `requireWervtRole(event, 'owner', 'member', 'admin')` for the app's own admin features.
23
+ * Throws 401 without a signed-in user, 403 with another role.
24
+ */
25
+ function requireWervtRole(event, ...roles) {
26
+ const user = useWervtUser(event);
27
+ if (!user) throw createError({
28
+ status: 401,
29
+ message: "sign in at /_wervt/login first"
30
+ });
31
+ if (!user.role || !roles.includes(user.role)) throw createError({
32
+ status: 403,
33
+ message: `needs role ${roles.join(", ")}`
34
+ });
35
+ return user;
36
+ }
12
37
  //#endregion
13
- export { DEV_LOGIN_COOKIE, parseWervtUser, useWervtUser };
38
+ export { DEV_LOGIN_COOKIE, parseWervtUser, requireWervtRole, useWervtUser };
@@ -40,4 +40,5 @@ export declare const vh: {
40
40
  /** Positive integer id (e.g. a serial primary key) → number. */
41
41
  id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.DigitsAction<string, undefined>, v.TransformAction<any, number>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>;
42
42
  };
43
- //#endregion
43
+ //#endregion
44
+ export {};
@@ -0,0 +1,9 @@
1
+ //#region src/runtime/shared/agent-token.d.ts
2
+ /** Who minted the agent token a request came with, and its id (the control plane's log). */
3
+ export interface AgentToken {
4
+ id: string;
5
+ by: string;
6
+ }
7
+ /** Parses the `x-wervt-agent-token` header the runtime sets (and strips from client requests). */
8
+ export declare function parseAgentToken(header: string | null | undefined): AgentToken | undefined;
9
+ //#endregion
@@ -0,0 +1,17 @@
1
+ //#region src/runtime/shared/agent-token.ts
2
+ /** Parses the `x-wervt-agent-token` header the runtime sets (and strips from client requests). */
3
+ function parseAgentToken(header) {
4
+ if (!header) return void 0;
5
+ try {
6
+ const value = JSON.parse(decodeURIComponent(header));
7
+ if (typeof value?.id !== "string" || typeof value.by !== "string") return void 0;
8
+ return {
9
+ id: value.id,
10
+ by: value.by
11
+ };
12
+ } catch {
13
+ return;
14
+ }
15
+ }
16
+ //#endregion
17
+ export { parseAgentToken };
@@ -15,4 +15,5 @@ export declare const shapeParser: {
15
15
  timestamp: typeof parseTimestamp;
16
16
  timestamptz: typeof parseTimestamp;
17
17
  };
18
- //#endregion
18
+ //#endregion
19
+ export {};
@@ -1,4 +1,10 @@
1
1
  //#region src/runtime/shared/user.d.ts
2
+ /**
3
+ * What the user is to this app: its `owner` or one of its `member`s (`wervt members`), a wervt
4
+ * `admin`, someone `invited` through the app (invite-only apps) or `allowed` by the app's list
5
+ * (`wervt access --add`). `public` is anyone else signed in to a public app.
6
+ */
7
+ export type WervtRole = 'owner' | 'member' | 'admin' | 'invited' | 'allowed' | 'public';
2
8
  export interface WervtUser {
3
9
  id: string;
4
10
  email: string;
@@ -6,6 +12,8 @@ export interface WervtUser {
6
12
  image?: string;
7
13
  /** Linked login accounts, provider id to the user's id there, e.g. `{ twitch: '1234' }`. */
8
14
  accounts?: Record<string, string>;
15
+ /** Missing only for users an app's own `/mcp` tokens stand for (`wervt:mcp:token`). */
16
+ role?: WervtRole;
9
17
  }
10
18
  /** Cookie set by the dev stand-in for `/_wervt/login` (see `server/dev-auth.ts`). */
11
19
  export declare const DEV_LOGIN_COOKIE = "wervt_dev_login";
@@ -14,7 +14,10 @@ function parseWervtUser(header) {
14
14
  function devUser(loggedIn) {
15
15
  const user = process.env.WERVT_DEV_USER;
16
16
  if (!import.meta.dev || !loggedIn || !user) return void 0;
17
- return JSON.parse(user);
17
+ return {
18
+ role: "owner",
19
+ ...JSON.parse(user)
20
+ };
18
21
  }
19
22
  //#endregion
20
23
  export { DEV_LOGIN_COOKIE, devUser, parseWervtUser };