@happyvertical/smrt-template-sveltekit 0.53.3 → 0.53.5
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/AGENTS.md +60 -27
- package/README.md +23 -16
- package/mcp-apps-template/package.dependencies.json +2 -2
- package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts +10 -6
- package/mcp-apps-template/src/routes/api/mcp/+server.ts +68 -12
- package/package.json +19 -15
- package/template/AGENTS.md +20 -5
- package/template/Dockerfile +0 -1
- package/template/INSTALL_PROMPT.md +2 -2
- package/template/README.md +86 -40
- package/template/compose.yaml +2 -2
- package/template/package.json +28 -27
- package/template/smrt.config.ts +16 -34
- package/template/src/app.d.ts +4 -11
- package/template/src/hooks.server.ts +10 -51
- package/template/src/lib/server/smrt.ts +22 -98
- package/template/src/routes/+layout.server.ts +4 -12
- package/template/src/routes/+layout.svelte +27 -99
- package/template/src/routes/+page.server.ts +4 -4
- package/template/src/routes/api/_runtime/diagnostics/+server.ts +17 -3
- package/template/src/routes/api/_runtime/health/+server.ts +4 -28
- package/template/src/routes/settings/+page.svelte +3 -34
- package/template/src/routes/setup/+page.server.ts +7 -94
- package/template/src/routes/setup/+page.svelte +3 -23
- package/template/vite.config.ts +5 -43
- package/template.config.js +11 -11
- package/mcp-apps-template/src/lib/McpAppsBridge.svelte +0 -37
- package/mcp-apps-template/src/lib/server/mcp-hosted-principal.ts +0 -12
- package/mcp-apps-template/src/lib/server/mcp-hosted.ts +0 -28
- package/mcp-apps-template/src/lib/server/mcp.ts +0 -85
- package/template/scripts/smrt-app.mjs +0 -885
- package/template/scripts/smrt-mcp-apps.mjs +0 -18
- package/template/scripts/smrt-operation-lock.mjs +0 -102
- package/template/scripts/smrt-portability-assets.mjs +0 -707
- package/template/scripts/smrt-portability.mjs +0 -470
- package/template/scripts/smrt-prepare-migration.mjs +0 -93
- package/template/scripts/smrt-process.mjs +0 -82
- package/template/scripts/smrt-provider-readiness.mjs +0 -33
- package/template/scripts/smrt-runtime-identity.mjs +0 -245
- package/template/scripts/smrt-vite.mjs +0 -45
- package/template/scripts/smrt-web.mjs +0 -10
- package/template/scripts/smrt-worker.mjs +0 -100
- package/template/scripts/smrt-writer-lease.mjs +0 -199
- package/template/src/lib/RuntimeDiagnosticsWebMcp.svelte +0 -11
- package/template/src/lib/runtime-diagnostics-webmcp.ts +0 -108
- package/template/src/lib/server/application-runtime.ts +0 -262
- package/template/src/lib/server/runtime-diagnostics.ts +0 -114
- package/template/src/lib/server/tenancy.ts +0 -114
- package/template/src/smrt-webmcp.d.ts +0 -6
|
@@ -1,113 +1,41 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { page } from '$app/state';
|
|
3
3
|
import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
AdminShell,
|
|
7
|
-
AppScopePanel,
|
|
8
|
-
type ShellNavItem,
|
|
9
|
-
TenantNav,
|
|
10
|
-
} from '@happyvertical/smrt-svelte/workspace';
|
|
11
|
-
import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
|
|
12
|
-
import '@happyvertical/smrt-ui/themes/styles/fonts.css';
|
|
13
|
-
import RuntimeDiagnosticsWebMcp from '$lib/RuntimeDiagnosticsWebMcp.svelte';
|
|
4
|
+
import { AppShell } from '@happyvertical/smrt-svelte/app';
|
|
5
|
+
import type { ShellNavItem } from '@happyvertical/smrt-svelte/workspace';
|
|
14
6
|
import type { LayoutProps } from './$types';
|
|
15
7
|
|
|
16
8
|
let { data, children }: LayoutProps = $props();
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
// Add application routes here. Generated REST routes live under /api and do
|
|
11
|
+
// not automatically imply a human-facing page.
|
|
12
|
+
const nav: ShellNavItem[] = [
|
|
13
|
+
{ href: '/', label: 'Items', description: 'The example s-m-r-t object' },
|
|
14
|
+
{ href: '/settings', label: 'Settings', description: 'Workspace layout and shortcuts' },
|
|
15
|
+
];
|
|
22
16
|
|
|
23
|
-
//
|
|
24
|
-
// WebMCP. SSR remains safe because the feature check is document-guarded.
|
|
17
|
+
// Generated read-only WebMCP tools, only in browsers that expose WebMCP.
|
|
25
18
|
const webmcp = $derived(
|
|
26
19
|
typeof document !== 'undefined' && 'modelContext' in document
|
|
27
|
-
? {
|
|
28
|
-
definitions: webMcpToolDefinitions,
|
|
29
|
-
basePath: '/api',
|
|
30
|
-
effects: ['read'] as const,
|
|
31
|
-
}
|
|
20
|
+
? { definitions: webMcpToolDefinitions, basePath: '/api', effects: ['read'] as const }
|
|
32
21
|
: false,
|
|
33
22
|
);
|
|
34
|
-
|
|
35
|
-
// Add application routes here. Generated REST routes live under /api and do
|
|
36
|
-
// not automatically imply a human-facing page.
|
|
37
|
-
const nav: ShellNavItem[] = [
|
|
38
|
-
{
|
|
39
|
-
href: '/',
|
|
40
|
-
label: 'Items',
|
|
41
|
-
description: 'The example s-m-r-t object',
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
href: '/settings',
|
|
45
|
-
label: 'Settings',
|
|
46
|
-
description: 'Workspace layout and shortcuts',
|
|
47
|
-
},
|
|
48
|
-
];
|
|
49
23
|
</script>
|
|
50
24
|
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<p class="tenant-selection">
|
|
69
|
-
Selected URL tenant: <strong>{data.session.selectedTenantSlug}</strong>
|
|
70
|
-
</p>
|
|
71
|
-
{/if}
|
|
72
|
-
<a href="/settings">Shell settings</a>
|
|
73
|
-
{/snippet}
|
|
74
|
-
</AppScopePanel>
|
|
75
|
-
{/snippet}
|
|
76
|
-
|
|
77
|
-
{#snippet tenantPanel()}
|
|
78
|
-
<TenantNav items={nav} {currentHref} />
|
|
79
|
-
{/snippet}
|
|
80
|
-
|
|
81
|
-
{@render children()}
|
|
82
|
-
</AdminShell>
|
|
83
|
-
</ThemeProvider>
|
|
84
|
-
</Provider>
|
|
85
|
-
|
|
86
|
-
<style>
|
|
87
|
-
:global(*),
|
|
88
|
-
:global(*::before),
|
|
89
|
-
:global(*::after) {
|
|
90
|
-
box-sizing: border-box;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
:global(html),
|
|
94
|
-
:global(body) {
|
|
95
|
-
min-height: 100%;
|
|
96
|
-
margin: 0;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
:global(body) {
|
|
100
|
-
background: var(--smrt-color-background);
|
|
101
|
-
color: var(--smrt-color-on-background);
|
|
102
|
-
font-family: var(--smrt-typography-body-font-family, Inter, system-ui, sans-serif);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.tenant-selection {
|
|
106
|
-
margin: 0 0 var(--smrt-spacing-2);
|
|
107
|
-
color: var(--smrt-color-on-surface-variant);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
a {
|
|
111
|
-
color: var(--smrt-color-primary);
|
|
112
|
-
}
|
|
113
|
-
</style>
|
|
25
|
+
<AppShell
|
|
26
|
+
title="s-m-r-t app"
|
|
27
|
+
subtitle="SvelteKit"
|
|
28
|
+
{webmcp}
|
|
29
|
+
{nav}
|
|
30
|
+
currentHref={page.url.pathname}
|
|
31
|
+
runtimeDiagnostics={data.session.authenticated}
|
|
32
|
+
tenantLabel={data.session.activeTenantId ? 'Authorized session tenant' : 'No active tenant'}
|
|
33
|
+
settingsHref="/settings"
|
|
34
|
+
>
|
|
35
|
+
{#snippet appPanelDocs()}
|
|
36
|
+
{#if data.session.selectedTenantSlug}
|
|
37
|
+
<p>Selected URL tenant: <strong>{data.session.selectedTenantSlug}</strong></p>
|
|
38
|
+
{/if}
|
|
39
|
+
{/snippet}
|
|
40
|
+
{@render children()}
|
|
41
|
+
</AppShell>
|
|
@@ -2,7 +2,7 @@ import { assertOperationPermission } from '@happyvertical/smrt-users';
|
|
|
2
2
|
import { fail } from '@sveltejs/kit';
|
|
3
3
|
|
|
4
4
|
import type { Item } from '$lib/objects/Item';
|
|
5
|
-
import {
|
|
5
|
+
import { runtime } from '$lib/server/smrt';
|
|
6
6
|
import type { Actions, PageServerLoad } from './$types';
|
|
7
7
|
|
|
8
8
|
const READ_PERMISSION = 'items.read';
|
|
@@ -38,7 +38,7 @@ export const load: PageServerLoad = async ({ depends, locals }) => {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
|
-
const items = await getCollection<Item>('Item');
|
|
41
|
+
const items = await runtime.getCollection<Item>('Item');
|
|
42
42
|
const rows = await items.list({
|
|
43
43
|
orderBy: 'created_at DESC',
|
|
44
44
|
limit: 50,
|
|
@@ -72,7 +72,7 @@ export const actions: Actions = {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const decision = await assertOperationPermission({
|
|
75
|
-
...
|
|
75
|
+
...runtime.classOptions('Item'),
|
|
76
76
|
action: 'create',
|
|
77
77
|
collection: 'items',
|
|
78
78
|
onDeny: 'return',
|
|
@@ -91,7 +91,7 @@ export const actions: Actions = {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
try {
|
|
94
|
-
const items = await getCollection<Item>('Item');
|
|
94
|
+
const items = await runtime.getCollection<Item>('Item');
|
|
95
95
|
await items.create({ title });
|
|
96
96
|
return { created: title };
|
|
97
97
|
} catch (error) {
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createRuntimeDiagnosticsHandler } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
2
|
+
import { RUNTIME_DIAGNOSTICS_WEBMCP_TOOL_NAME } from '@happyvertical/smrt-svelte/app/runtime-diagnostics';
|
|
3
|
+
import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
|
|
2
4
|
|
|
3
|
-
import {
|
|
5
|
+
import { runtime } from '$lib/server/smrt';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Authenticated, authorized (owner role or `runtime_diagnostics.read`) and
|
|
9
|
+
* redacted runtime diagnostics. Schema/migration status and the worker
|
|
10
|
+
* heartbeat stay `unknown` until the application installs real verifiers;
|
|
11
|
+
* process liveness is never substituted for them.
|
|
12
|
+
*/
|
|
13
|
+
export const GET = createRuntimeDiagnosticsHandler({
|
|
14
|
+
runtime,
|
|
15
|
+
toolNames: () => [
|
|
16
|
+
...webMcpToolDefinitions.map((definition) => definition.name),
|
|
17
|
+
RUNTIME_DIAGNOSTICS_WEBMCP_TOOL_NAME,
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -1,30 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { RequestHandler } from './$types';
|
|
3
|
-
import { resolveApplicationId } from '../../../../../scripts/smrt-runtime-identity.mjs';
|
|
4
|
-
import {
|
|
5
|
-
applicationRuntime,
|
|
6
|
-
applicationRuntimeConfiguration,
|
|
7
|
-
} from '$lib/server/application-runtime';
|
|
1
|
+
import { createRuntimeHealthHandler } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
sourceRoot: process.cwd(),
|
|
11
|
-
explicitId: process.env.SMRT_APP_ID,
|
|
12
|
-
});
|
|
3
|
+
import { runtime } from '$lib/server/smrt';
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
schemaVersion: 1,
|
|
17
|
-
status: 'ready',
|
|
18
|
-
profile: applicationRuntime.profile,
|
|
19
|
-
};
|
|
20
|
-
return json(
|
|
21
|
-
applicationRuntime.profile === 'local'
|
|
22
|
-
? {
|
|
23
|
-
...publicHealth,
|
|
24
|
-
application: applicationId,
|
|
25
|
-
instance: process.env.SMRT_PROCESS_INSTANCE || null,
|
|
26
|
-
configuration: applicationRuntimeConfiguration,
|
|
27
|
-
}
|
|
28
|
-
: publicHealth,
|
|
29
|
-
);
|
|
30
|
-
};
|
|
5
|
+
/** Liveness; local responses add the identity `smrt app start` verifies. */
|
|
6
|
+
export const GET = createRuntimeHealthHandler(runtime);
|
|
@@ -1,41 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
// This page renders inside the root layout's <AdminShell>, so the shell
|
|
5
|
-
// context set by AdminShell is available to descendants — ShellSettingsPanel
|
|
6
|
-
// reads it via useAdminShell() with no extra wiring. It lets users toggle
|
|
7
|
-
// panel visibility and remap the WASD hotkeys; changes persist through the
|
|
8
|
-
// shell's `storageKey` (localStorage), applied client-side after mount.
|
|
2
|
+
import { ShellSettingsPage } from '@happyvertical/smrt-svelte/app';
|
|
9
3
|
</script>
|
|
10
4
|
|
|
11
5
|
<svelte:head>
|
|
12
6
|
<title>Settings · s-m-r-t app</title>
|
|
13
7
|
</svelte:head>
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<h1>Settings</h1>
|
|
18
|
-
<p>Adjust the workspace shell — panel layout and keyboard shortcuts.</p>
|
|
19
|
-
</header>
|
|
20
|
-
|
|
21
|
-
<ShellSettingsPanel />
|
|
22
|
-
</section>
|
|
23
|
-
|
|
24
|
-
<style>
|
|
25
|
-
.settings {
|
|
26
|
-
display: grid;
|
|
27
|
-
gap: var(--smrt-spacing-6, 1.5rem);
|
|
28
|
-
max-width: 720px;
|
|
29
|
-
padding: var(--smrt-spacing-8, 2rem);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.settings header {
|
|
33
|
-
display: grid;
|
|
34
|
-
gap: var(--smrt-spacing-2, 0.5rem);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.settings h1,
|
|
38
|
-
.settings p {
|
|
39
|
-
margin: 0;
|
|
40
|
-
}
|
|
41
|
-
</style>
|
|
9
|
+
<!-- Panel layout and WASD hotkeys, persisted through the shell's storageKey. -->
|
|
10
|
+
<ShellSettingsPage />
|
|
@@ -1,96 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { fail, redirect } from '@sveltejs/kit';
|
|
4
|
-
import type { Actions, PageServerLoad } from './$types';
|
|
1
|
+
import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
5
2
|
|
|
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';
|
|
3
|
+
import { runtime } from '$lib/server/smrt';
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
};
|
|
27
|
-
|
|
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,
|
|
62
|
-
});
|
|
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.
|
|
93
|
-
}
|
|
94
|
-
throw redirect(303, '/');
|
|
95
|
-
},
|
|
96
|
-
};
|
|
5
|
+
/**
|
|
6
|
+
* Local-profile, loopback-only, single-use owner bootstrap. The runtime
|
|
7
|
+
* removes the `pnpm app:setup` onboarding hand-off files once the claim commits.
|
|
8
|
+
*/
|
|
9
|
+
export const { load, actions } = createOwnerSetupPage(runtime);
|
|
@@ -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
|
-
<
|
|
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} />
|
package/template/vite.config.ts
CHANGED
|
@@ -1,48 +1,10 @@
|
|
|
1
1
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
});
|
package/template.config.js
CHANGED
|
@@ -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.
|
|
23
|
-
'@happyvertical/smrt-config': '^0.53.
|
|
24
|
-
'@happyvertical/smrt-core': '^0.53.
|
|
25
|
-
'@happyvertical/smrt-jobs': '^0.53.
|
|
26
|
-
'@happyvertical/smrt-profiles': '^0.53.
|
|
27
|
-
'@happyvertical/smrt-svelte': '^0.53.
|
|
28
|
-
'@happyvertical/smrt-tenancy': '^0.53.
|
|
29
|
-
'@happyvertical/smrt-ui': '^0.53.
|
|
30
|
-
'@happyvertical/smrt-users': '^0.53.
|
|
31
|
-
'@happyvertical/smrt-web': '^0.53.
|
|
22
|
+
'@happyvertical/smrt-app-runtime': '^0.53.5',
|
|
23
|
+
'@happyvertical/smrt-config': '^0.53.5',
|
|
24
|
+
'@happyvertical/smrt-core': '^0.53.5',
|
|
25
|
+
'@happyvertical/smrt-jobs': '^0.53.5',
|
|
26
|
+
'@happyvertical/smrt-profiles': '^0.53.5',
|
|
27
|
+
'@happyvertical/smrt-svelte': '^0.53.5',
|
|
28
|
+
'@happyvertical/smrt-tenancy': '^0.53.5',
|
|
29
|
+
'@happyvertical/smrt-ui': '^0.53.5',
|
|
30
|
+
'@happyvertical/smrt-users': '^0.53.5',
|
|
31
|
+
'@happyvertical/smrt-web': '^0.53.5',
|
|
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.
|
|
37
|
+
'@happyvertical/smrt-cli': '^0.53.5',
|
|
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,12 +0,0 @@
|
|
|
1
|
-
import type { McpVerifiedIdentity, McpPrincipalMapping } from '@happyvertical/smrt-app-mcp/auth';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Deployments must replace this binding with a database-backed current-account
|
|
5
|
-
* and active-membership lookup. Token claims never select a tenant.
|
|
6
|
-
*/
|
|
7
|
-
export type HostedMcpPrincipalResolver = (
|
|
8
|
-
identity: McpVerifiedIdentity,
|
|
9
|
-
) => Promise<McpPrincipalMapping | null>;
|
|
10
|
-
|
|
11
|
-
export const resolveHostedMcpPrincipal: HostedMcpPrincipalResolver | undefined =
|
|
12
|
-
undefined;
|
|
@@ -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
|
-
}
|