diff --git a/apps/host-cloudflare/package.json b/apps/host-cloudflare/package.json
index d4bee71ba..8d7640d16 100644
--- a/apps/host-cloudflare/package.json
+++ b/apps/host-cloudflare/package.json
@@ -45,6 +45,7 @@
"@executor-js/vite-plugin": "workspace:*",
"@tailwindcss/vite": "catalog:",
"@tanstack/router-plugin": "^1.167.12",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
diff --git a/apps/host-cloudflare/vite.config.ts b/apps/host-cloudflare/vite.config.ts
index dc371ef79..0a10b8695 100644
--- a/apps/host-cloudflare/vite.config.ts
+++ b/apps/host-cloudflare/vite.config.ts
@@ -4,6 +4,8 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
+import { rootRoute } from "@tanstack/virtual-file-routes";
+import { consoleRoutes } from "@executor-js/react/console-routes";
import executorVitePlugin from "@executor-js/vite-plugin";
// ---------------------------------------------------------------------------
@@ -51,6 +53,14 @@ export default defineConfig({
autoCodeSplitting: true,
routesDirectory: fileURLToPath(new URL("./web/routes", import.meta.url)),
generatedRouteTree: fileURLToPath(new URL("./web/routeTree.gen.ts", import.meta.url)),
+ // The route tree is composed, not hand-mirrored: the shared console
+ // routes come from @executor-js/react (the package whose shell/pages
+ // link to them); this app only owns its root (the Cloudflare-Access
+ // shell). To add app-specific routes, create web/routes/app and mount
+ // it here with `physical("", "app")` — the directory must exist.
+ virtualRouteConfig: rootRoute("__root.tsx", [
+ ...consoleRoutes({ dir: "../../../../packages/react/src/routes" }),
+ ]),
}),
...react(),
],
diff --git a/apps/host-cloudflare/web/routeTree.gen.ts b/apps/host-cloudflare/web/routeTree.gen.ts
index 4e9e39fa4..fc0a24546 100644
--- a/apps/host-cloudflare/web/routeTree.gen.ts
+++ b/apps/host-cloudflare/web/routeTree.gen.ts
@@ -9,86 +9,102 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as ToolsRouteImport } from './routes/tools'
-import { Route as SecretsRouteImport } from './routes/secrets'
-import { Route as PoliciesRouteImport } from './routes/policies'
-import { Route as IndexRouteImport } from './routes/index'
-import { Route as SourcesNamespaceRouteImport } from './routes/sources.$namespace'
-import { Route as ResumeExecutionIdRouteImport } from './routes/resume.$executionId'
-import { Route as SourcesAddPluginKeyRouteImport } from './routes/sources.add.$pluginKey'
-import { Route as PluginsPluginIdSplatRouteImport } from './routes/plugins.$pluginId.$'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey'
-const ToolsRoute = ToolsRouteImport.update({
- id: '/tools',
- path: '/tools',
- getParentRoute: () => rootRouteImport,
-} as any)
-const SecretsRoute = SecretsRouteImport.update({
- id: '/secrets',
- path: '/secrets',
- getParentRoute: () => rootRouteImport,
-} as any)
-const PoliciesRoute = PoliciesRouteImport.update({
- id: '/policies',
- path: '/policies',
- getParentRoute: () => rootRouteImport,
-} as any)
-const IndexRoute = IndexRouteImport.update({
- id: '/',
- path: '/',
- getParentRoute: () => rootRouteImport,
-} as any)
-const SourcesNamespaceRoute = SourcesNamespaceRouteImport.update({
- id: '/sources/$namespace',
- path: '/sources/$namespace',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({
- id: '/resume/$executionId',
- path: '/resume/$executionId',
- getParentRoute: () => rootRouteImport,
-} as any)
-const SourcesAddPluginKeyRoute = SourcesAddPluginKeyRouteImport.update({
- id: '/sources/add/$pluginKey',
- path: '/sources/add/$pluginKey',
- getParentRoute: () => rootRouteImport,
-} as any)
-const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({
- id: '/plugins/$pluginId/$',
- path: '/plugins/$pluginId/$',
- getParentRoute: () => rootRouteImport,
-} as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport.update({
+ id: '/tools',
+ path: '/tools',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport.update({
+ id: '/secrets',
+ path: '/secrets',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport.update({
+ id: '/policies',
+ path: '/policies',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport.update(
+ {
+ id: '/resume/$executionId',
+ path: '/resume/$executionId',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport.update(
+ {
+ id: '/integrations/$namespace',
+ path: '/integrations/$namespace',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update(
+ {
+ id: '/plugins/$pluginId/$',
+ path: '/plugins/$pluginId/$',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update(
+ {
+ id: '/integrations/add/$pluginKey',
+ path: '/integrations/add/$pluginKey',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
export interface FileRoutesByFullPath {
- '/': typeof IndexRoute
- '/policies': typeof PoliciesRoute
- '/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/sources/$namespace': typeof SourcesNamespaceRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
- '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRoutesByTo {
- '/': typeof IndexRoute
- '/policies': typeof PoliciesRoute
- '/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/sources/$namespace': typeof SourcesNamespaceRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
- '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
- '/': typeof IndexRoute
- '/policies': typeof PoliciesRoute
- '/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/sources/$namespace': typeof SourcesNamespaceRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
- '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -97,41 +113,41 @@ export interface FileRouteTypes {
| '/policies'
| '/secrets'
| '/tools'
+ | '/integrations/$namespace'
| '/resume/$executionId'
- | '/sources/$namespace'
+ | '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
- | '/sources/add/$pluginKey'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/policies'
| '/secrets'
| '/tools'
+ | '/integrations/$namespace'
| '/resume/$executionId'
- | '/sources/$namespace'
+ | '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
- | '/sources/add/$pluginKey'
id:
| '__root__'
| '/'
| '/policies'
| '/secrets'
| '/tools'
+ | '/integrations/$namespace'
| '/resume/$executionId'
- | '/sources/$namespace'
+ | '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
- | '/sources/add/$pluginKey'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
- IndexRoute: typeof IndexRoute
- PoliciesRoute: typeof PoliciesRoute
- SecretsRoute: typeof SecretsRoute
- ToolsRoute: typeof ToolsRoute
- ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute
- SourcesNamespaceRoute: typeof SourcesNamespaceRoute
- PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute
- SourcesAddPluginKeyRoute: typeof SourcesAddPluginKeyRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
declare module '@tanstack/react-router' {
@@ -140,70 +156,78 @@ declare module '@tanstack/react-router' {
id: '/tools'
path: '/tools'
fullPath: '/tools'
- preLoaderRoute: typeof ToolsRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport
parentRoute: typeof rootRouteImport
}
'/secrets': {
id: '/secrets'
path: '/secrets'
fullPath: '/secrets'
- preLoaderRoute: typeof SecretsRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport
parentRoute: typeof rootRouteImport
}
'/policies': {
id: '/policies'
path: '/policies'
fullPath: '/policies'
- preLoaderRoute: typeof PoliciesRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
- preLoaderRoute: typeof IndexRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/sources/$namespace': {
- id: '/sources/$namespace'
- path: '/sources/$namespace'
- fullPath: '/sources/$namespace'
- preLoaderRoute: typeof SourcesNamespaceRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/resume/$executionId': {
id: '/resume/$executionId'
path: '/resume/$executionId'
fullPath: '/resume/$executionId'
- preLoaderRoute: typeof ResumeExecutionIdRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport
parentRoute: typeof rootRouteImport
}
- '/sources/add/$pluginKey': {
- id: '/sources/add/$pluginKey'
- path: '/sources/add/$pluginKey'
- fullPath: '/sources/add/$pluginKey'
- preLoaderRoute: typeof SourcesAddPluginKeyRouteImport
+ '/integrations/$namespace': {
+ id: '/integrations/$namespace'
+ path: '/integrations/$namespace'
+ fullPath: '/integrations/$namespace'
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport
parentRoute: typeof rootRouteImport
}
'/plugins/$pluginId/$': {
id: '/plugins/$pluginId/$'
path: '/plugins/$pluginId/$'
fullPath: '/plugins/$pluginId/$'
- preLoaderRoute: typeof PluginsPluginIdSplatRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/integrations/add/$pluginKey': {
+ id: '/integrations/add/$pluginKey'
+ path: '/integrations/add/$pluginKey'
+ fullPath: '/integrations/add/$pluginKey'
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
- IndexRoute: IndexRoute,
- PoliciesRoute: PoliciesRoute,
- SecretsRoute: SecretsRoute,
- ToolsRoute: ToolsRoute,
- ResumeExecutionIdRoute: ResumeExecutionIdRoute,
- SourcesNamespaceRoute: SourcesNamespaceRoute,
- PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute,
- SourcesAddPluginKeyRoute: SourcesAddPluginKeyRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/apps/host-cloudflare/web/routes/secrets.tsx b/apps/host-cloudflare/web/routes/secrets.tsx
deleted file mode 100644
index 10bd4a10b..000000000
--- a/apps/host-cloudflare/web/routes/secrets.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { SecretsPage } from "@executor-js/react/pages/secrets";
-
-// The Providers/Secrets page lets self-host users inspect their credential
-// backends. Credential entry happens through the per-integration Add Account
-// flow (`connections.createHandoff` → `/integrations/{slug}?addAccount=1`),
-// not here, so this route takes no search params.
-export const Route = createFileRoute("/secrets")({
- component: () => ,
-});
diff --git a/bun.lock b/bun.lock
index d0521840a..7f0e200f6 100644
--- a/bun.lock
+++ b/bun.lock
@@ -185,6 +185,7 @@
"@executor-js/vite-plugin": "workspace:*",
"@tailwindcss/vite": "catalog:",
"@tanstack/router-plugin": "^1.167.12",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
@@ -583,7 +584,7 @@
"@modelcontextprotocol/sdk": "^1.29.0",
"effect": "catalog:",
"graphql-yoga": "^5.17.0",
- "zod": "^4.3.6",
+ "zod": "4.3.6",
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",
@@ -642,7 +643,7 @@
"@executor-js/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "^1.12.1",
"effect": "catalog:",
- "zod": "^4.3.0",
+ "zod": "4.3.6",
},
"devDependencies": {
"@effect/vitest": "catalog:",
@@ -879,7 +880,7 @@
"@executor-js/config": "workspace:*",
"@executor-js/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "^1.29.0",
- "zod": "^4.3.6",
+ "zod": "4.3.6",
},
"devDependencies": {
"@effect/atom-react": "catalog:",
@@ -1023,6 +1024,7 @@
"@shikijs/langs": "^4.0.2",
"@shikijs/themes": "^4.0.2",
"@tanstack/react-router": "catalog:",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -1046,10 +1048,12 @@
},
"devDependencies": {
"@effect/vitest": "catalog:",
+ "@tanstack/router-cli": "^1.167.17",
"@types/node": "catalog:",
"@types/react": "catalog:",
"hast-util-to-jsx-runtime": "^2.3.6",
"tailwindcss": "catalog:",
+ "tsup": "^8.5.1",
"typescript": "catalog:",
"vite": "catalog:",
},
@@ -2508,6 +2512,8 @@
"@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
+ "@tanstack/router-cli": ["@tanstack/router-cli@1.167.17", "", { "dependencies": { "@tanstack/router-generator": "1.167.17", "chokidar": "^5.0.0", "yargs": "^17.7.2" }, "bin": { "tsr": "bin/tsr.cjs" } }, "sha512-kws9PNdspkHbeZG6aLDjjpgH/hg7Q565BdSW9hHrjKmXPq/at7OKxQnA76aw9dJiJqy9OmST2RdOKlMEog43+g=="],
+
"@tanstack/router-core": ["@tanstack/router-core@1.168.15", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.166.32", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-utils": "1.161.6", "@tanstack/virtual-file-routes": "1.161.7", "magic-string": "^0.30.21", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-VuusKwEXcgKq+myq1JQfZogY8scTXIIeFls50dJ/UXgCXWp5n14iFreYNlg41wURcak2oA3M+t2TVfD0xUUD6g=="],
@@ -2528,7 +2534,7 @@
"@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
- "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
+ "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],
"@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="],
@@ -3732,7 +3738,7 @@
"jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
- "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
@@ -5344,6 +5350,8 @@
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+ "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
@@ -5356,8 +5364,16 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "@tanstack/router-cli/@tanstack/router-generator": ["@tanstack/router-generator@1.167.17", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.13", "@tanstack/router-utils": "1.162.2", "@tanstack/virtual-file-routes": "1.162.0", "jiti": "^2.7.0", "magic-string": "^0.30.21", "prettier": "^3.5.0", "zod": "^4.4.3" } }, "sha512-xtB9tB2Ws0tWR6Pi7nc3Qk9IYgoh1mQCKWjHqIl9tf6BNUpKoqniJoPAQ4+LGrK8FeZYU0o0p/qlZEyj9FAulA=="],
+
+ "@tanstack/router-cli/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
+
+ "@tanstack/router-generator/@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
+
"@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+ "@tanstack/router-plugin/@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
+
"@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@tanstack/start-plugin-core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
@@ -5400,6 +5416,8 @@
"app-builder-lib/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
+ "app-builder-lib/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
"app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
"astro/@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
@@ -5412,6 +5430,8 @@
"atmn/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
+ "atmn/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
"better-auth/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
"better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
@@ -5526,6 +5546,8 @@
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
+ "knip/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
"libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
"matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
@@ -5900,6 +5922,14 @@
"@react-grab/cli/ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
+ "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core": ["@tanstack/router-core@1.171.13", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-+NOwEj1kO/6IGmpHRIZHasYxYWpyBQGNIZAST9aNrk9Q3YlU9SgqVnl1pbLa9qAKfeNdXQIRve0RQb/0kyDeDA=="],
+
+ "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-utils": ["@tanstack/router-utils@1.162.2", "", { "dependencies": { "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ=="],
+
+ "@tanstack/router-cli/@tanstack/router-generator/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
+ "@tanstack/router-cli/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
+
"@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/cacheable-request/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -6352,6 +6382,12 @@
"@react-grab/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
+ "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core/@tanstack/history": ["@tanstack/history@1.162.0", "", {}, "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA=="],
+
+ "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core/seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="],
+
+ "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core/seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="],
+
"agents/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"agents/yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
diff --git a/packages/core/test-servers/package.json b/packages/core/test-servers/package.json
index 0015254cc..176f0a4fe 100644
--- a/packages/core/test-servers/package.json
+++ b/packages/core/test-servers/package.json
@@ -16,7 +16,7 @@
"@modelcontextprotocol/sdk": "^1.29.0",
"effect": "catalog:",
"graphql-yoga": "^5.17.0",
- "zod": "^4.3.6"
+ "zod": "4.3.6"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",
diff --git a/packages/hosts/mcp/package.json b/packages/hosts/mcp/package.json
index 3b0364318..4ea803fe9 100644
--- a/packages/hosts/mcp/package.json
+++ b/packages/hosts/mcp/package.json
@@ -29,7 +29,7 @@
"@executor-js/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "^1.12.1",
"effect": "catalog:",
- "zod": "^4.3.0"
+ "zod": "4.3.6"
},
"devDependencies": {
"@effect/vitest": "catalog:",
diff --git a/packages/plugins/mcp/package.json b/packages/plugins/mcp/package.json
index 89ee101ec..6161ddf5c 100644
--- a/packages/plugins/mcp/package.json
+++ b/packages/plugins/mcp/package.json
@@ -66,7 +66,7 @@
"@executor-js/config": "workspace:*",
"@executor-js/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "^1.29.0",
- "zod": "^4.3.6"
+ "zod": "4.3.6"
},
"devDependencies": {
"@effect/atom-react": "catalog:",
diff --git a/packages/react/package.json b/packages/react/package.json
index ec5471524..6deace349 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -4,6 +4,11 @@
"private": true,
"type": "module",
"exports": {
+ "./console-routes": {
+ "types": "./src/console-routes.ts",
+ "bun": "./src/console-routes.ts",
+ "default": "./dist/console-routes.js"
+ },
"./api/oauth-popup": "./src/api/oauth-popup.ts",
"./api/*": "./src/api/*.tsx",
"./plugins/*": "./src/plugins/*.tsx",
@@ -20,7 +25,8 @@
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsgo --noEmit",
- "typecheck:slow": "tsc --noEmit"
+ "typecheck:slow": "tsc --noEmit",
+ "build": "tsup"
},
"dependencies": {
"@base-ui/react": "^1.3.0",
@@ -32,6 +38,7 @@
"@shikijs/langs": "^4.0.2",
"@shikijs/themes": "^4.0.2",
"@tanstack/react-router": "catalog:",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -55,10 +62,12 @@
},
"devDependencies": {
"@effect/vitest": "catalog:",
+ "@tanstack/router-cli": "^1.167.17",
"@types/node": "catalog:",
"@types/react": "catalog:",
"hast-util-to-jsx-runtime": "^2.3.6",
"tailwindcss": "catalog:",
+ "tsup": "^8.5.1",
"typescript": "catalog:",
"vite": "catalog:"
}
diff --git a/packages/react/src/console-router.ts b/packages/react/src/console-router.ts
new file mode 100644
index 000000000..da82d3e68
--- /dev/null
+++ b/packages/react/src/console-router.ts
@@ -0,0 +1,24 @@
+import { createRouter } from "@tanstack/react-router";
+
+import { routeTree } from "./routes/routeTree.gen";
+
+// ---------------------------------------------------------------------------
+// The canonical console router TYPE. Nothing imports this module — it exists
+// so this package's own typecheck registers the console route tree (generated
+// from src/routes by `bun run routes:gen`), which makes every `Link to=` /
+// `navigate({ to })` in the shared pages and shell typecheck against the
+// console route contract instead of being loose strings. A shared component
+// linking to a route that isn't part of the shared console set fails HERE, in
+// the package that owns the contract, not at runtime in some app.
+//
+// Apps are unaffected: each app's own router.tsx registers its real router
+// (shared console routes + app-specific extras) for its own typecheck graph.
+// ---------------------------------------------------------------------------
+
+export const consoleRouter = createRouter({ routeTree });
+
+declare module "@tanstack/react-router" {
+ interface Register {
+ readonly router: typeof consoleRouter;
+ }
+}
diff --git a/packages/react/src/console-routes.ts b/packages/react/src/console-routes.ts
new file mode 100644
index 000000000..0a7c181dd
--- /dev/null
+++ b/packages/react/src/console-routes.ts
@@ -0,0 +1,59 @@
+import { index, route } from "@tanstack/virtual-file-routes";
+import type { VirtualRouteNode } from "@tanstack/virtual-file-routes";
+
+// ---------------------------------------------------------------------------
+// The shared console route contract. This package's pages and shell link to
+// these paths (`Link to="/integrations/$namespace"` etc.), so every app that
+// renders the shared console MUST register them — historically each app
+// re-declared the same route files by hand and they drifted (host-cloudflare
+// shipped `/sources/*` while the shell linked `/integrations/*`).
+//
+// `consoleRoutes()` makes the contract executable: app vite configs compose it
+// into their TanStack `virtualRouteConfig`, mounting the canonical route files
+// that live in `src/routes/` next to the pages they bind. Apps keep their own
+// `__root.tsx` (the auth shell is what genuinely differs) and add app-specific
+// routes alongside. An app with an intentionally different surface for one of
+// these paths excludes it here and registers its own file instead.
+// ---------------------------------------------------------------------------
+
+export const CONSOLE_ROUTE_PATHS = [
+ "/",
+ "/integrations/$namespace",
+ "/integrations/add/$pluginKey",
+ "/policies",
+ "/secrets",
+ "/tools",
+ "/resume/$executionId",
+ "/plugins/$pluginId/$",
+] as const;
+
+export type ConsoleRoutePath = (typeof CONSOLE_ROUTE_PATHS)[number];
+
+export interface ConsoleRoutesOptions {
+ /** Path from the app's `routesDirectory` to this package's `src/routes`. */
+ readonly dir: string;
+ /** Shared paths this app replaces with its own route file (or omits). */
+ readonly exclude?: ReadonlyArray;
+}
+
+export const consoleRoutes = (options: ConsoleRoutesOptions): Array => {
+ const excluded = new Set(options.exclude ?? []);
+ const file = (name: string): string => `${options.dir}/${name}`;
+ const entries: ReadonlyArray = [
+ ["/", index(file("index.tsx"))],
+ [
+ "/integrations/$namespace",
+ route("/integrations/$namespace", file("integrations.$namespace.tsx")),
+ ],
+ [
+ "/integrations/add/$pluginKey",
+ route("/integrations/add/$pluginKey", file("integrations.add.$pluginKey.tsx")),
+ ],
+ ["/policies", route("/policies", file("policies.tsx"))],
+ ["/secrets", route("/secrets", file("secrets.tsx"))],
+ ["/tools", route("/tools", file("tools.tsx"))],
+ ["/resume/$executionId", route("/resume/$executionId", file("resume.$executionId.tsx"))],
+ ["/plugins/$pluginId/$", route("/plugins/$pluginId/$", file("plugins.$pluginId.$.tsx"))],
+ ];
+ return entries.filter(([path]) => !excluded.has(path)).map(([, node]) => node);
+};
diff --git a/packages/react/src/routes/__root.tsx b/packages/react/src/routes/__root.tsx
new file mode 100644
index 000000000..63ec60603
--- /dev/null
+++ b/packages/react/src/routes/__root.tsx
@@ -0,0 +1,7 @@
+import { Outlet, createRootRoute } from "@tanstack/react-router";
+
+// Typing-only root for the package-local console route tree (see
+// console-router.ts). Apps never mount this file — each app supplies its own
+// __root.tsx (the auth shell is what genuinely differs per app) in its
+// virtualRouteConfig and composes the shared routes via consoleRoutes().
+export const Route = createRootRoute({ component: Outlet });
diff --git a/apps/host-cloudflare/web/routes/index.tsx b/packages/react/src/routes/index.tsx
similarity index 65%
rename from apps/host-cloudflare/web/routes/index.tsx
rename to packages/react/src/routes/index.tsx
index 2d57f82f0..9b62dcb15 100644
--- a/apps/host-cloudflare/web/routes/index.tsx
+++ b/packages/react/src/routes/index.tsx
@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationsPage } from "@executor-js/react/pages/integrations";
+
+import { IntegrationsPage } from "../pages/integrations";
export const Route = createFileRoute("/")({
component: IntegrationsPage,
diff --git a/apps/host-cloudflare/web/routes/sources.$namespace.tsx b/packages/react/src/routes/integrations.$namespace.tsx
similarity index 56%
rename from apps/host-cloudflare/web/routes/sources.$namespace.tsx
rename to packages/react/src/routes/integrations.$namespace.tsx
index b8fcd190b..61345d269 100644
--- a/apps/host-cloudflare/web/routes/sources.$namespace.tsx
+++ b/packages/react/src/routes/integrations.$namespace.tsx
@@ -1,7 +1,8 @@
import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail";
-export const Route = createFileRoute("/sources/$namespace")({
+import { IntegrationDetailPage } from "../pages/integration-detail";
+
+export const Route = createFileRoute("/integrations/$namespace")({
component: () => {
const { namespace } = Route.useParams();
return ;
diff --git a/apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx b/packages/react/src/routes/integrations.add.$pluginKey.tsx
similarity index 50%
rename from apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx
rename to packages/react/src/routes/integrations.add.$pluginKey.tsx
index 9924b2c9e..187ef4846 100644
--- a/apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx
+++ b/packages/react/src/routes/integrations.add.$pluginKey.tsx
@@ -1,19 +1,23 @@
import { Schema } from "effect";
import { createFileRoute } from "@tanstack/react-router";
-import { AddIntegrationPage } from "@executor-js/react/pages/integration-add";
+
+import { AddIntegrationPage } from "../pages/integration-add";
const SearchParams = Schema.toStandardSchemaV1(
Schema.Struct({
url: Schema.optional(Schema.String),
preset: Schema.optional(Schema.String),
+ namespace: Schema.optional(Schema.String),
}),
);
-export const Route = createFileRoute("/sources/add/$pluginKey")({
+export const Route = createFileRoute("/integrations/add/$pluginKey")({
validateSearch: SearchParams,
component: () => {
const { pluginKey } = Route.useParams();
- const { url, preset } = Route.useSearch();
- return ;
+ const { url, preset, namespace } = Route.useSearch();
+ return (
+
+ );
},
});
diff --git a/apps/host-cloudflare/web/routes/plugins.$pluginId.$.tsx b/packages/react/src/routes/plugins.$pluginId.$.tsx
similarity index 100%
rename from apps/host-cloudflare/web/routes/plugins.$pluginId.$.tsx
rename to packages/react/src/routes/plugins.$pluginId.$.tsx
diff --git a/apps/host-cloudflare/web/routes/policies.tsx b/packages/react/src/routes/policies.tsx
similarity index 69%
rename from apps/host-cloudflare/web/routes/policies.tsx
rename to packages/react/src/routes/policies.tsx
index a9de9ff6f..0bd91a688 100644
--- a/apps/host-cloudflare/web/routes/policies.tsx
+++ b/packages/react/src/routes/policies.tsx
@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
-import { PoliciesPage } from "@executor-js/react/pages/policies";
+
+import { PoliciesPage } from "../pages/policies";
export const Route = createFileRoute("/policies")({
component: () => ,
diff --git a/apps/host-cloudflare/web/routes/resume.$executionId.tsx b/packages/react/src/routes/resume.$executionId.tsx
similarity index 93%
rename from apps/host-cloudflare/web/routes/resume.$executionId.tsx
rename to packages/react/src/routes/resume.$executionId.tsx
index 32a84347b..90d0962ea 100644
--- a/apps/host-cloudflare/web/routes/resume.$executionId.tsx
+++ b/packages/react/src/routes/resume.$executionId.tsx
@@ -3,12 +3,10 @@ import { useAtomSet, useAtomValue } from "@effect/atom-react";
import { Data, Effect, Option, Schema } from "effect";
import * as Atom from "effect/unstable/reactivity/Atom";
import { createFileRoute } from "@tanstack/react-router";
-import {
- ResumeApprovalPage,
- ResumeApprovalPageView,
-} from "@executor-js/react/pages/resume-approval";
-import { pausedExecutionAtom } from "@executor-js/react/api/atoms";
-import type { ElicitationAction } from "@executor-js/react/components/elicitation-approval";
+
+import { ResumeApprovalPage, ResumeApprovalPageView } from "../pages/resume-approval";
+import { pausedExecutionAtom } from "../api/atoms";
+import type { ElicitationAction } from "../components/elicitation-approval";
const SearchParams = Schema.toStandardSchemaV1(
Schema.Struct({
diff --git a/packages/react/src/routes/routeTree.gen.ts b/packages/react/src/routes/routeTree.gen.ts
new file mode 100644
index 000000000..11684c806
--- /dev/null
+++ b/packages/react/src/routes/routeTree.gen.ts
@@ -0,0 +1,211 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './__root'
+import { Route as ToolsRouteImport } from './tools'
+import { Route as SecretsRouteImport } from './secrets'
+import { Route as PoliciesRouteImport } from './policies'
+import { Route as IndexRouteImport } from './index'
+import { Route as ResumeExecutionIdRouteImport } from './resume.$executionId'
+import { Route as IntegrationsNamespaceRouteImport } from './integrations.$namespace'
+import { Route as PluginsPluginIdSplatRouteImport } from './plugins.$pluginId.$'
+import { Route as IntegrationsAddPluginKeyRouteImport } from './integrations.add.$pluginKey'
+
+const ToolsRoute = ToolsRouteImport.update({
+ id: '/tools',
+ path: '/tools',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SecretsRoute = SecretsRouteImport.update({
+ id: '/secrets',
+ path: '/secrets',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PoliciesRoute = PoliciesRouteImport.update({
+ id: '/policies',
+ path: '/policies',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({
+ id: '/resume/$executionId',
+ path: '/resume/$executionId',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IntegrationsNamespaceRoute = IntegrationsNamespaceRouteImport.update({
+ id: '/integrations/$namespace',
+ path: '/integrations/$namespace',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({
+ id: '/plugins/$pluginId/$',
+ path: '/plugins/$pluginId/$',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IntegrationsAddPluginKeyRoute =
+ IntegrationsAddPluginKeyRouteImport.update({
+ id: '/integrations/add/$pluginKey',
+ path: '/integrations/add/$pluginKey',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/policies': typeof PoliciesRoute
+ '/secrets': typeof SecretsRoute
+ '/tools': typeof ToolsRoute
+ '/integrations/$namespace': typeof IntegrationsNamespaceRoute
+ '/resume/$executionId': typeof ResumeExecutionIdRoute
+ '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
+ '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/policies': typeof PoliciesRoute
+ '/secrets': typeof SecretsRoute
+ '/tools': typeof ToolsRoute
+ '/integrations/$namespace': typeof IntegrationsNamespaceRoute
+ '/resume/$executionId': typeof ResumeExecutionIdRoute
+ '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
+ '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/policies': typeof PoliciesRoute
+ '/secrets': typeof SecretsRoute
+ '/tools': typeof ToolsRoute
+ '/integrations/$namespace': typeof IntegrationsNamespaceRoute
+ '/resume/$executionId': typeof ResumeExecutionIdRoute
+ '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
+ '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/policies'
+ | '/secrets'
+ | '/tools'
+ | '/integrations/$namespace'
+ | '/resume/$executionId'
+ | '/integrations/add/$pluginKey'
+ | '/plugins/$pluginId/$'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | '/policies'
+ | '/secrets'
+ | '/tools'
+ | '/integrations/$namespace'
+ | '/resume/$executionId'
+ | '/integrations/add/$pluginKey'
+ | '/plugins/$pluginId/$'
+ id:
+ | '__root__'
+ | '/'
+ | '/policies'
+ | '/secrets'
+ | '/tools'
+ | '/integrations/$namespace'
+ | '/resume/$executionId'
+ | '/integrations/add/$pluginKey'
+ | '/plugins/$pluginId/$'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ PoliciesRoute: typeof PoliciesRoute
+ SecretsRoute: typeof SecretsRoute
+ ToolsRoute: typeof ToolsRoute
+ IntegrationsNamespaceRoute: typeof IntegrationsNamespaceRoute
+ ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute
+ IntegrationsAddPluginKeyRoute: typeof IntegrationsAddPluginKeyRoute
+ PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/tools': {
+ id: '/tools'
+ path: '/tools'
+ fullPath: '/tools'
+ preLoaderRoute: typeof ToolsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/secrets': {
+ id: '/secrets'
+ path: '/secrets'
+ fullPath: '/secrets'
+ preLoaderRoute: typeof SecretsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/policies': {
+ id: '/policies'
+ path: '/policies'
+ fullPath: '/policies'
+ preLoaderRoute: typeof PoliciesRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/resume/$executionId': {
+ id: '/resume/$executionId'
+ path: '/resume/$executionId'
+ fullPath: '/resume/$executionId'
+ preLoaderRoute: typeof ResumeExecutionIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/integrations/$namespace': {
+ id: '/integrations/$namespace'
+ path: '/integrations/$namespace'
+ fullPath: '/integrations/$namespace'
+ preLoaderRoute: typeof IntegrationsNamespaceRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/plugins/$pluginId/$': {
+ id: '/plugins/$pluginId/$'
+ path: '/plugins/$pluginId/$'
+ fullPath: '/plugins/$pluginId/$'
+ preLoaderRoute: typeof PluginsPluginIdSplatRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/integrations/add/$pluginKey': {
+ id: '/integrations/add/$pluginKey'
+ path: '/integrations/add/$pluginKey'
+ fullPath: '/integrations/add/$pluginKey'
+ preLoaderRoute: typeof IntegrationsAddPluginKeyRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ PoliciesRoute: PoliciesRoute,
+ SecretsRoute: SecretsRoute,
+ ToolsRoute: ToolsRoute,
+ IntegrationsNamespaceRoute: IntegrationsNamespaceRoute,
+ ResumeExecutionIdRoute: ResumeExecutionIdRoute,
+ IntegrationsAddPluginKeyRoute: IntegrationsAddPluginKeyRoute,
+ PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/packages/react/src/routes/secrets.tsx b/packages/react/src/routes/secrets.tsx
new file mode 100644
index 000000000..952c8809d
--- /dev/null
+++ b/packages/react/src/routes/secrets.tsx
@@ -0,0 +1,12 @@
+import { createFileRoute } from "@tanstack/react-router";
+
+import { SecretsPage } from "../pages/secrets";
+
+// The Providers/Secrets page lets users inspect their credential backends.
+// Credential entry happens through the per-integration Add Account flow
+// (`connections.createHandoff` → `/integrations/{slug}?addAccount=1`), not
+// here, so this route takes no search params. Apps with a different secrets
+// surface (e.g. cloud hides it) exclude this route and bring their own file.
+export const Route = createFileRoute("/secrets")({
+ component: () => ,
+});
diff --git a/apps/host-cloudflare/web/routes/tools.tsx b/packages/react/src/routes/tools.tsx
similarity index 69%
rename from apps/host-cloudflare/web/routes/tools.tsx
rename to packages/react/src/routes/tools.tsx
index 25929fd2b..ef8fbe62e 100644
--- a/apps/host-cloudflare/web/routes/tools.tsx
+++ b/packages/react/src/routes/tools.tsx
@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
-import { ToolsPage } from "@executor-js/react/pages/tools";
+
+import { ToolsPage } from "../pages/tools";
export const Route = createFileRoute("/tools")({
component: ToolsPage,
diff --git a/packages/react/tsr.config.json b/packages/react/tsr.config.json
new file mode 100644
index 000000000..b32421ec7
--- /dev/null
+++ b/packages/react/tsr.config.json
@@ -0,0 +1,4 @@
+{
+ "routesDirectory": "./src/routes",
+ "generatedRouteTree": "./src/routes/routeTree.gen.ts"
+}
diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts
new file mode 100644
index 000000000..1c11b59ec
--- /dev/null
+++ b/packages/react/tsup.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "tsup";
+
+// Only console-routes gets built: it is imported by app vite configs, which
+// vite's config loader runs under Node (it externalizes bare package imports,
+// and Node cannot load raw .ts). Everything else this package exports is
+// browser code compiled from src by each app's vite build. Same pattern as
+// @executor-js/vite-plugin.
+export default defineConfig({
+ entry: {
+ "console-routes": "src/console-routes.ts",
+ },
+ format: ["esm"],
+ dts: false,
+ sourcemap: true,
+ clean: true,
+ external: [/^@tanstack\//],
+});