@happyvertical/smrt-template-sveltekit 0.53.2 → 0.53.4

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 (47) hide show
  1. package/AGENTS.md +53 -26
  2. package/README.md +23 -16
  3. package/mcp-apps-template/package.dependencies.json +2 -2
  4. package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts +14 -6
  5. package/mcp-apps-template/src/routes/api/mcp/+server.ts +69 -12
  6. package/package.json +17 -15
  7. package/template/AGENTS.md +16 -5
  8. package/template/Dockerfile +0 -1
  9. package/template/INSTALL_PROMPT.md +2 -2
  10. package/template/README.md +52 -27
  11. package/template/compose.yaml +2 -2
  12. package/template/package.json +28 -27
  13. package/template/smrt.config.ts +17 -4
  14. package/template/src/app.d.ts +4 -11
  15. package/template/src/hooks.server.ts +10 -51
  16. package/template/src/lib/server/smrt.ts +70 -62
  17. package/template/src/routes/+layout.server.ts +4 -12
  18. package/template/src/routes/+layout.svelte +27 -99
  19. package/template/src/routes/api/_runtime/diagnostics/+server.ts +17 -3
  20. package/template/src/routes/api/_runtime/health/+server.ts +4 -28
  21. package/template/src/routes/settings/+page.svelte +3 -34
  22. package/template/src/routes/setup/+page.server.ts +15 -88
  23. package/template/src/routes/setup/+page.svelte +3 -23
  24. package/template/vite.config.ts +5 -43
  25. package/template.config.js +11 -11
  26. package/mcp-apps-template/src/lib/McpAppsBridge.svelte +0 -37
  27. package/mcp-apps-template/src/lib/server/mcp-hosted.ts +0 -28
  28. package/mcp-apps-template/src/lib/server/mcp.ts +0 -85
  29. package/template/scripts/smrt-app.mjs +0 -885
  30. package/template/scripts/smrt-mcp-apps.mjs +0 -18
  31. package/template/scripts/smrt-operation-lock.mjs +0 -102
  32. package/template/scripts/smrt-portability-assets.mjs +0 -707
  33. package/template/scripts/smrt-portability.mjs +0 -470
  34. package/template/scripts/smrt-prepare-migration.mjs +0 -93
  35. package/template/scripts/smrt-process.mjs +0 -82
  36. package/template/scripts/smrt-provider-readiness.mjs +0 -33
  37. package/template/scripts/smrt-runtime-identity.mjs +0 -245
  38. package/template/scripts/smrt-vite.mjs +0 -45
  39. package/template/scripts/smrt-web.mjs +0 -10
  40. package/template/scripts/smrt-worker.mjs +0 -100
  41. package/template/scripts/smrt-writer-lease.mjs +0 -199
  42. package/template/src/lib/RuntimeDiagnosticsWebMcp.svelte +0 -11
  43. package/template/src/lib/runtime-diagnostics-webmcp.ts +0 -108
  44. package/template/src/lib/server/application-runtime.ts +0 -262
  45. package/template/src/lib/server/runtime-diagnostics.ts +0 -114
  46. package/template/src/lib/server/tenancy.ts +0 -114
  47. package/template/src/smrt-webmcp.d.ts +0 -6
@@ -1,96 +1,23 @@
1
1
  import { rmSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { fail, redirect } from '@sveltejs/kit';
4
- import type { Actions, PageServerLoad } from './$types';
5
3
 
6
- import {
7
- applicationRuntime,
8
- getLocalApplicationRuntime,
9
- } from '$lib/server/application-runtime';
10
- import {
11
- resolveApplicationId,
12
- resolveApplicationStateRoot,
13
- } from '../../../scripts/smrt-runtime-identity.mjs';
4
+ import { resolveApplicationStateRoot } from '@happyvertical/smrt-app-runtime';
5
+ import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';
14
6
 
15
- export const load: PageServerLoad = async ({ url, locals }) => {
16
- if (locals.user) throw redirect(303, '/');
17
- if (applicationRuntime.profile !== 'local') {
18
- return { available: false, token: '' };
19
- }
20
- const runtime = await getLocalApplicationRuntime();
21
- const diagnostics = await runtime.diagnostics();
22
- return {
23
- available: diagnostics.bootstrap.status !== 'claimed',
24
- token: url.searchParams.get('token') || '',
25
- };
26
- };
7
+ import { runtime } from '$lib/server/smrt';
27
8
 
28
- export const actions: Actions = {
29
- default: async (event) => {
30
- if (applicationRuntime.profile !== 'local') {
31
- return fail(404, { message: 'Local owner setup is disabled.' });
32
- }
33
- const form = await event.request.formData();
34
- const token = String(form.get('token') || '');
35
- const name = String(form.get('name') || '');
36
- const email = String(form.get('email') || '');
37
- if (!token || !name.trim() || !email.includes('@')) {
38
- return fail(400, { message: 'Name, email, and a valid setup token are required.' });
39
- }
40
- const runtime = await getLocalApplicationRuntime();
41
- let result;
42
- try {
43
- result = await runtime.claimOwner({
44
- token,
45
- name,
46
- email,
47
- userAgent: event.request.headers.get('user-agent') || undefined,
48
- ipAddress: event.getClientAddress(),
49
- });
50
- } catch {
51
- return fail(400, {
52
- message:
53
- 'The setup invitation is invalid, expired, or already used. Run pnpm app:stop, pnpm app:recover, pnpm app:start, then pnpm app:open.',
54
- });
55
- }
56
- event.cookies.set('sid', result.sessionId, {
57
- path: '/',
58
- httpOnly: true,
59
- secure: event.url.protocol === 'https:',
60
- sameSite: 'lax',
61
- maxAge: 7 * 24 * 60 * 60,
9
+ /** Local-profile, loopback-only, single-use owner bootstrap. */
10
+ export const { load, actions } = createOwnerSetupPage(runtime, {
11
+ // `pnpm app:setup` leaves the invitation in mode-0600 handoff files; once
12
+ // the owner exists they only hold a token that now fails closed.
13
+ onOwnerClaimed() {
14
+ const stateRoot = resolveApplicationStateRoot({
15
+ appId: runtime.applicationId(),
16
+ dataDirectory: process.env.SMRT_DATA_DIR,
17
+ sourceRoot: process.cwd(),
62
18
  });
63
- try {
64
- const appId = resolveApplicationId({
65
- sourceRoot: process.cwd(),
66
- explicitId: process.env.SMRT_APP_ID,
67
- });
68
- rmSync(
69
- join(
70
- resolveApplicationStateRoot({
71
- appId,
72
- dataDirectory: process.env.SMRT_DATA_DIR,
73
- sourceRoot: process.cwd(),
74
- }),
75
- 'onboarding.json',
76
- ),
77
- { force: true },
78
- );
79
- rmSync(
80
- join(
81
- resolveApplicationStateRoot({
82
- appId,
83
- dataDirectory: process.env.SMRT_DATA_DIR,
84
- sourceRoot: process.cwd(),
85
- }),
86
- 'onboarding-launch.html',
87
- ),
88
- { force: true },
89
- );
90
- } catch {
91
- // The owner and session are authoritative; stale mode-0600 handoff files
92
- // only contain a token that now fails closed as already claimed.
19
+ for (const file of ['onboarding.json', 'onboarding-launch.html']) {
20
+ rmSync(join(stateRoot, file), { force: true });
93
21
  }
94
- throw redirect(303, '/');
95
22
  },
96
- };
23
+ });
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { enhance } from '$app/forms';
3
+ import { OwnerSetupForm } from '@happyvertical/smrt-svelte/app';
2
4
  import type { PageProps } from './$types';
3
5
 
4
6
  let { data, form }: PageProps = $props();
@@ -6,26 +8,4 @@
6
8
 
7
9
  <svelte:head><title>Set up your local application</title></svelte:head>
8
10
 
9
- <main>
10
- <h1>Set up your local application</h1>
11
- {#if !data.available}
12
- <p>Local owner setup is unavailable or has already been completed.</p>
13
- {:else}
14
- <p>Create the real local owner account. This invitation works once and stays on this device.</p>
15
- {#if form?.message}<p role="alert">{form.message}</p>{/if}
16
- <form method="POST">
17
- <input type="hidden" name="token" value={data.token} />
18
- <label>Name <input name="name" autocomplete="name" required /></label>
19
- <label>Email <input name="email" type="email" autocomplete="email" required /></label>
20
- <button type="submit">Create owner</button>
21
- </form>
22
- {/if}
23
- </main>
24
-
25
- <style>
26
- main { max-width: 36rem; margin: 4rem auto; padding: 1.5rem; }
27
- form { display: grid; gap: 1rem; }
28
- label { display: grid; gap: 0.35rem; }
29
- input { padding: 0.65rem; }
30
- button { width: fit-content; padding: 0.65rem 1rem; }
31
- </style>
11
+ <OwnerSetupForm {data} {form} {enhance} />
@@ -1,48 +1,10 @@
1
1
  import { sveltekit } from '@sveltejs/kit/vite';
2
- import { smrtConsumer } from '@happyvertical/smrt-core/consumer-plugin';
3
- import { smrtPlugin } from '@happyvertical/smrt-core/vite-plugin';
2
+ import { smrt } from '@happyvertical/smrt-core/vite';
4
3
  import { defineConfig } from 'vite';
5
4
 
5
+ // `smrt()` applies the legacy-decorator transform, consumes the manifests of
6
+ // the packages listed in `smrt.config.ts` (`consumer.packages`), and generates
7
+ // this app's object registration, types, and API routes.
6
8
  export default defineConfig({
7
- // Lower `@smrt()` legacy (experimentalDecorators) decorators explicitly.
8
- // tsconfig.json sets experimentalDecorators, but Vite's oxc transform does
9
- // not reliably honor it through the SvelteKit `extends
10
- // "./.svelte-kit/tsconfig.json"` chain — raw `@decorator class` syntax then
11
- // reaches the SSR runtime and throws `SyntaxError: Invalid or unexpected
12
- // token` on the first request.
13
- oxc: {
14
- decorator: {
15
- legacy: true,
16
- emitDecoratorMetadata: true,
17
- },
18
- },
19
- plugins: [
20
- sveltekit(),
21
- // Only packages whose object manifests this app consumes belong here.
22
- // Keeping the list explicit avoids treating tooling/UI packages as domain
23
- // model providers and makes generated runtime registration deterministic.
24
- smrtConsumer({
25
- packages: [
26
- '@happyvertical/smrt-profiles',
27
- '@happyvertical/smrt-tenancy',
28
- '@happyvertical/smrt-users',
29
- ],
30
- generateTypes: true,
31
- typesDir: 'src/lib/types/smrt-generated',
32
- svelteKit: true,
33
- }),
34
- smrtPlugin({
35
- include: ['src/lib/objects/**/*.ts'],
36
- exclude: ['**/*.test.ts', '**/*.spec.ts'],
37
- generateTypes: true,
38
- typeDeclarationsPath: 'src/lib/types/smrt-generated',
39
- svelteKit: {
40
- enabled: true,
41
- routesDir: 'src/routes/api',
42
- objectsDir: 'src/lib/objects',
43
- configPath: 'src/lib/server',
44
- configFileName: 'smrt.ts',
45
- },
46
- }),
47
- ],
9
+ plugins: [sveltekit(), smrt()],
48
10
  });
@@ -19,22 +19,22 @@ export default {
19
19
 
20
20
  // Dependencies to add to generated project
21
21
  dependencies: {
22
- '@happyvertical/smrt-app-runtime': '^0.53.2',
23
- '@happyvertical/smrt-config': '^0.53.2',
24
- '@happyvertical/smrt-core': '^0.53.2',
25
- '@happyvertical/smrt-jobs': '^0.53.2',
26
- '@happyvertical/smrt-profiles': '^0.53.2',
27
- '@happyvertical/smrt-svelte': '^0.53.2',
28
- '@happyvertical/smrt-tenancy': '^0.53.2',
29
- '@happyvertical/smrt-ui': '^0.53.2',
30
- '@happyvertical/smrt-users': '^0.53.2',
31
- '@happyvertical/smrt-web': '^0.53.2',
22
+ '@happyvertical/smrt-app-runtime': '^0.53.4',
23
+ '@happyvertical/smrt-config': '^0.53.4',
24
+ '@happyvertical/smrt-core': '^0.53.4',
25
+ '@happyvertical/smrt-jobs': '^0.53.4',
26
+ '@happyvertical/smrt-profiles': '^0.53.4',
27
+ '@happyvertical/smrt-svelte': '^0.53.4',
28
+ '@happyvertical/smrt-tenancy': '^0.53.4',
29
+ '@happyvertical/smrt-ui': '^0.53.4',
30
+ '@happyvertical/smrt-users': '^0.53.4',
31
+ '@happyvertical/smrt-web': '^0.53.4',
32
32
  '@happyvertical/sql': '^0.89.12',
33
33
  '@modelcontextprotocol/server': '^2.0.0',
34
34
  },
35
35
 
36
36
  devDependencies: {
37
- '@happyvertical/smrt-cli': '^0.53.2',
37
+ '@happyvertical/smrt-cli': '^0.53.4',
38
38
  '@sveltejs/adapter-node': '^5.5.7',
39
39
  '@sveltejs/kit': '2.70.3',
40
40
  '@sveltejs/vite-plugin-svelte': '^7.3.0',
@@ -1,37 +0,0 @@
1
- <script lang="ts">
2
- import { requestOpenAiDisplayMode } from '@happyvertical/smrt-mcp-openai/client';
3
- import { useMcpApp } from '@happyvertical/smrt-svelte/mcp-apps';
4
-
5
- interface Props {
6
- /** Exact, application-configured host origin. Never derive this from URL input. */
7
- hostOrigin: string;
8
- }
9
-
10
- let { hostOrigin }: Props = $props();
11
- let expandUnavailable = $state(false);
12
- const app = useMcpApp(() => ({
13
- appInfo: { name: 'smrt-app', version: '0.1.0' },
14
- availableDisplayModes: ['inline', 'fullscreen'],
15
- hostOrigin,
16
- hostWindow: parent,
17
- }));
18
-
19
- async function expand() {
20
- try {
21
- if (!app.bridge) throw new Error('Host bridge unavailable');
22
- await requestOpenAiDisplayMode(app.bridge, 'fullscreen');
23
- } catch {
24
- expandUnavailable = true;
25
- }
26
- }
27
- </script>
28
-
29
- <section aria-labelledby="mcp-apps-title" class="mcp-apps-bridge">
30
- <h1 id="mcp-apps-title">Items</h1>
31
- <p>Use this view to review the items your signed-in tenant authorizes.</p>
32
- {#if app.error || expandUnavailable}
33
- <p role="status">Host controls are unavailable; this view remains inline.</p>
34
- {:else}
35
- <button type="button" onclick={expand}>Expand view</button>
36
- {/if}
37
- </section>
@@ -1,28 +0,0 @@
1
- import { createMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
2
- import { applicationRuntime } from './application-runtime.js';
3
- import { resolveHostedMcpPrincipal } from './mcp-hosted-principal.js';
4
-
5
- function required(name: string): string {
6
- const value = process.env[name]?.trim();
7
- if (!value) throw new Error(`Hosted MCP requires ${name}.`);
8
- return value;
9
- }
10
-
11
- let cachedAuth: ReturnType<typeof createMcpResourceAuth> | undefined;
12
-
13
- export function hostedMcpAuth() {
14
- if (applicationRuntime.profile === 'local') return null;
15
- if (cachedAuth) return cachedAuth;
16
- if (!resolveHostedMcpPrincipal)
17
- throw new Error('Hosted MCP requires an application-owned resolveHostedMcpPrincipal binding.');
18
- cachedAuth = createMcpResourceAuth({
19
- profile: applicationRuntime.profile,
20
- resource: required('SMRT_MCP_RESOURCE'),
21
- issuer: required('SMRT_MCP_ISSUER'),
22
- jwksUri: required('SMRT_MCP_JWKS_URI'),
23
- scopes: required('SMRT_MCP_SCOPES').split(/\s+/u),
24
- algorithms: ['RS256'],
25
- resolvePrincipal: resolveHostedMcpPrincipal,
26
- });
27
- return cachedAuth;
28
- }
@@ -1,85 +0,0 @@
1
- import {
2
- createMcpAppServer,
3
- type McpAppPrincipal,
4
- type McpWorkflowToolDefinition,
5
- } from '@happyvertical/smrt-app-mcp';
6
- import { openAiDisplayMetadata, withOpenAiEntrypoints } from '@happyvertical/smrt-mcp-openai';
7
- import { getApplicationDatabaseConfig } from './application-runtime.js';
8
-
9
- const ITEM_RESOURCE_URI = 'ui://smrt-app/v1/items.html';
10
- const itemResourceHtml = `<!doctype html><html lang="en"><head><title>Items</title></head><body><main tabindex="-1"><h1>Items</h1><p>Use the host controls to review your authorized items.</p></main></body></html>`;
11
-
12
- const itemsOverview: McpWorkflowToolDefinition = withOpenAiEntrypoints(
13
- {
14
- name: 'items_overview',
15
- description: 'Open the authorized item overview.',
16
- title: 'Items',
17
- inputSchema: { type: 'object', properties: {}, additionalProperties: false },
18
- outputSchema: {
19
- type: 'object',
20
- properties: { state: { type: 'string' } },
21
- required: ['state'],
22
- additionalProperties: false,
23
- },
24
- effect: 'read',
25
- idempotent: true,
26
- openWorld: false,
27
- ui: { resourceUri: ITEM_RESOURCE_URI },
28
- async execute() {
29
- return {
30
- content: [{ type: 'text', text: 'Item overview is ready.' }],
31
- structuredContent: { state: 'ready' },
32
- };
33
- },
34
- },
35
- ['global'],
36
- );
37
-
38
- /**
39
- * The session hook resolves membership and permissions for every request. This
40
- * adapter only projects that trusted, request-local result; headers and route
41
- * arguments are never identity inputs.
42
- */
43
- export function resolveMcpPrincipal(event: {
44
- locals?: Record<string, unknown>;
45
- }): McpAppPrincipal | null {
46
- const user = event.locals?.user as { id?: unknown } | undefined;
47
- const tenantId = event.locals?.tenantId;
48
- const permissions = event.locals?.permissions;
49
- if (typeof user?.id !== 'string' || typeof tenantId !== 'string') return null;
50
- if (!Array.isArray(permissions) || !permissions.every((value) => typeof value === 'string'))
51
- return null;
52
- return {
53
- id: user.id,
54
- tenantId,
55
- kind: 'human',
56
- scopes: [...permissions].sort(),
57
- };
58
- }
59
-
60
- export const mcpServer = createMcpAppServer({
61
- smrtOptions: () => ({ db: getApplicationDatabaseConfig() }),
62
- serverInfo: { name: 'smrt-app', version: '0.1.0' },
63
- allowedClassNames: ['Item'],
64
- workflowTools: [itemsOverview],
65
- resources: [
66
- {
67
- uri: ITEM_RESOURCE_URI,
68
- version: 'v1',
69
- name: 'Items',
70
- html: itemResourceHtml,
71
- csp: {},
72
- metadata: openAiDisplayMetadata({ availableDisplayModes: ['inline'] }),
73
- },
74
- ],
75
- toolPolicy: ({ principal }) =>
76
- principal?.kind === 'human' &&
77
- typeof principal.id === 'string' &&
78
- typeof principal.tenantId === 'string' &&
79
- principal.scopes?.includes('items.read') === true,
80
- resourcePolicy: ({ principal }) =>
81
- principal?.kind === 'human' &&
82
- typeof principal.id === 'string' &&
83
- typeof principal.tenantId === 'string' &&
84
- principal.scopes?.includes('items.read') === true,
85
- });