@nuxtagentic/kit 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ Proprietary software. All rights reserved.
2
+
3
+ Use of Nuxt Agentic packages and starter materials is governed by the applicable
4
+ commercial agreement supplied with a purchase. This repository does not grant
5
+ an open-source license or permission to redistribute the packages as a competing
6
+ template or module library.
7
+
8
+ Third-party dependencies retain their respective licenses. This notice does not
9
+ replace or modify those licenses.
package/README.md CHANGED
@@ -1,3 +1,52 @@
1
- # Temporary Holding Version
1
+ # Module registration
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@nuxtagentic/kit` is the build-time helper shared by the local Nuxt modules.
4
+ It exports `registerWorkspaceModule` and `getWorkspaceModules`; it is not itself a
5
+ Nuxt module to add to the host's `modules` array.
6
+
7
+ `externalizeSsrDependencies(names, nuxt)` lets Nitro bundle shared server
8
+ libraries once when both SSR and server routes import the same dependency. It resolves from the importing module,
9
+ including unhoisted pnpm dependencies. Browser builds and development retain
10
+ their existing bundling behavior.
11
+
12
+ A module passes its ID, runtime directory, optional catalog entry, feature, metadata, and
13
+ server contribution paths to `registerWorkspaceModule`. Kit registers its
14
+ composable and server directories and creates four generated manifests:
15
+
16
+ | Alias | Contains |
17
+ | --------------------- | -------------------------------------------------------------- |
18
+ | `#workspace/modules` | Installable catalog entries, defaults, and dependencies |
19
+ | `#workspace/features` | UI and workspace contributions tagged with their owning module |
20
+ | `#workspace/client` | Browser-safe model, tool, and integration metadata |
21
+ | `#workspace/server` | Server resource factories and integration hooks |
22
+
23
+ Use `catalog` for business features that users can install. It provides a label,
24
+ description, icon, icon classes, optional opening URL, dependencies, and an optional
25
+ `defaultInstalled` flag. Shared services and core features omit the catalog entry
26
+ and remain active. A package can
27
+ own multiple contributions.
28
+ Client and server manifests expose `getWorkspaceMetadata(installedIds)` to select
29
+ active contributions. The no-argument metadata exports contain all available entries.
30
+
31
+ Each consuming module still owns its page, layout, component, configuration, and
32
+ worker registration in `module.ts`. Registration is scoped to the Nuxt instance
33
+ and rejects duplicate IDs.
34
+
35
+ Registration opts the package's TypeScript sources into both Vite and Nitro
36
+ transpilation. Nitro normally excludes `node_modules`; Kit narrows that default
37
+ to allow `@nuxtagentic` sources while preserving other exclusion rules. Registered
38
+ package roots are bundled into the server output, including Cloudflare Workers.
39
+
40
+ Kit does not mutate `cloud.config.ts` or run migrations. The host statically
41
+ composes schema exports so CloudConfig's CLI can read them outside Nuxt.
42
+ Feature packages declare schema exports in their `cloudconfig.models` metadata.
43
+ See [the package map and setup guide](../README.md).
44
+
45
+ The portable fixture installs one module at a time without the host app:
46
+
47
+ ```sh
48
+ NUXT_TEST_MODULE=agents pnpm exec nuxt typecheck packages/kit/tests/fixture
49
+ ```
50
+
51
+ Replace `agents` with another module name. `pnpm test:modules` checks import
52
+ boundaries, dependency cycles, exported sources, and fixture isolation.
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "@nuxtagentic/kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "files": [
6
+ "registration.ts",
7
+ "README.md",
8
+ "LICENSE.md"
9
+ ],
10
+ "exports": {
11
+ ".": "./registration.ts",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "dependencies": {
15
+ "@nuxt/kit": "4.6.1",
16
+ "@nuxt/schema": "4.6.1"
17
+ },
18
+ "peerDependencies": {
19
+ "nuxt": "^4.6.1"
20
+ },
21
+ "devDependencies": {
22
+ "tsx": "^4.23.15",
23
+ "typescript": "^5.9.3"
24
+ },
25
+ "license": "SEE LICENSE IN LICENSE.md",
26
+ "description": "Nuxt module registration and runtime discovery for Nuxt Agentic.",
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ },
31
+ "engines": {
32
+ "node": "^22.22.3 || ^24.15.0 || >=26.0.0"
33
+ }
6
34
  }
@@ -0,0 +1,207 @@
1
+ import { addImportsDir, addServerScanDir, addTemplate, useNuxt } from '@nuxt/kit'
2
+ import { existsSync } from 'node:fs'
3
+ import { dirname } from 'node:path'
4
+ import type { Nuxt } from '@nuxt/schema'
5
+
6
+ const ssrDependenciesKey = Symbol.for('nuxt-agentic.ssrDependencies')
7
+
8
+ /** Let Nitro bundle shared server dependencies once, instead of rebundling Vite's copies. */
9
+ export function externalizeSsrDependencies(dependencies: readonly string[], nuxt = useNuxt()) {
10
+ if (nuxt.options.dev) {
11
+ return
12
+ }
13
+ const app = nuxt as Nuxt & { [ssrDependenciesKey]?: Set<string> }
14
+ if (app[ssrDependenciesKey]) {
15
+ for (const dependency of dependencies) {
16
+ app[ssrDependenciesKey].add(dependency)
17
+ }
18
+ return
19
+ }
20
+ const packages = new Set(dependencies)
21
+ app[ssrDependenciesKey] = packages
22
+ nuxt.hook('vite:extendConfig', (config, { isServer }) => {
23
+ if (!isServer) {
24
+ return
25
+ }
26
+ const viteConfig = config as { plugins?: NonNullable<typeof config.plugins> }
27
+ viteConfig.plugins ??= []
28
+ viteConfig.plugins.unshift({
29
+ name: 'nuxt-agentic:ssr-dependencies',
30
+ enforce: 'pre',
31
+ async resolveId(source, importer) {
32
+ if (!packages.has(source)) {
33
+ return null
34
+ }
35
+ // Resolve from the importing module so pnpm's unhoisted dependencies work.
36
+ const resolved = await this.resolve(source, importer, { skipSelf: true })
37
+ if (!resolved) {
38
+ throw new Error(`Cannot resolve SSR dependency ${source}`)
39
+ }
40
+ return { ...resolved, external: true }
41
+ },
42
+ })
43
+ })
44
+ }
45
+
46
+ export interface FeatureRegistration {
47
+ id: string
48
+ runtime: string
49
+ feature?: { from: string; name: string }
50
+ metadata?: string
51
+ server?: string
52
+ catalog?: Omit<WorkspaceModuleDefinition, 'id'>
53
+ }
54
+
55
+ export interface WorkspaceModuleDefinition {
56
+ id: string
57
+ label: string
58
+ description: string
59
+ icon: string
60
+ iconClass: string
61
+ to?: string
62
+ dependencies?: string[]
63
+ defaultInstalled?: boolean
64
+ }
65
+
66
+ // Nuxt can load this helper in separate module contexts; keep registration on its instance.
67
+ const registryKey = Symbol.for('nuxt-agentic.modules')
68
+ type RegisteredNuxt = Nuxt & { [registryKey]?: FeatureRegistration[] }
69
+
70
+ export function getWorkspaceModules(nuxt = useNuxt()) {
71
+ return (nuxt as RegisteredNuxt)[registryKey] ?? []
72
+ }
73
+
74
+ /** Include registered package UI in Tailwind's scan, including packages in node_modules. */
75
+ export function registerWorkspaceStyles(stylesheet: string, nuxt = useNuxt()) {
76
+ const sources = addTemplate({
77
+ filename: 'workspace/styles.css',
78
+ getContents: () =>
79
+ [
80
+ `@import ${JSON.stringify(stylesheet)};`,
81
+ ...getWorkspaceModules(nuxt).map(
82
+ ({ runtime }) => `@source ${JSON.stringify(dirname(runtime))};`,
83
+ ),
84
+ ].join('\n'),
85
+ write: true,
86
+ })
87
+ nuxt.options.css.push(sources.dst)
88
+ }
89
+
90
+ export function registerWorkspaceModule(entry: FeatureRegistration, nuxt = useNuxt()) {
91
+ const app = nuxt as RegisteredNuxt
92
+ let entries = app[registryKey]
93
+ if (!entries) {
94
+ entries = []
95
+ app[registryKey] = entries
96
+ const modules = entries
97
+ nuxt.hook('nitro:config', (config) => {
98
+ // Published packages ship TypeScript; Nitro otherwise skips everything in node_modules.
99
+ config.esbuild ??= {}
100
+ config.esbuild.options ??= {}
101
+ // Nuxt merges exclusion arrays, so adding an exception leaves its broad default active.
102
+ const exclusions = config.esbuild.options.exclude ?? [/node_modules/]
103
+ config.esbuild.options.exclude = (Array.isArray(exclusions) ? exclusions : [exclusions]).map(
104
+ (pattern) =>
105
+ pattern instanceof RegExp && pattern.source === 'node_modules'
106
+ ? /[/\\]node_modules[/\\](?!(?:.*[/\\])?@nuxtagentic[/\\])/
107
+ : pattern,
108
+ )
109
+ })
110
+ nuxt.hook('modules:done', () => {
111
+ if (modules.some((entry) => entry.id === 'workspace')) {
112
+ registerManifest(nuxt, '#workspace/features', 'features', () => featureManifest(modules))
113
+ registerManifest(nuxt, '#workspace/modules', 'modules', () =>
114
+ [
115
+ "import type { WorkspaceModuleDefinition } from '@nuxtagentic/kit'",
116
+ `export const MODULE_CATALOG: readonly WorkspaceModuleDefinition[] = ${JSON.stringify(
117
+ modules.flatMap((entry) =>
118
+ entry.catalog ? [{ id: entry.id, ...entry.catalog }] : [],
119
+ ),
120
+ )}`,
121
+ ].join('\n'),
122
+ )
123
+ }
124
+ if (modules.some((entry) => entry.id === 'prompt')) {
125
+ registerManifest(nuxt, '#workspace/client', 'client', () =>
126
+ metadataManifest(modules, false),
127
+ )
128
+ registerManifest(nuxt, '#workspace/server', 'server', () => metadataManifest(modules, true))
129
+ }
130
+ })
131
+ }
132
+ if (entries.some((module) => module.id === entry.id)) {
133
+ throw new Error(`Workspace module "${entry.id}" was registered twice.`)
134
+ }
135
+ entries.push(entry)
136
+ // Nuxt excludes node_modules from auto-import injection unless the runtime is opted in.
137
+ const runtimePath = entry.runtime.replaceAll('\\', '/').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
138
+ nuxt.options.imports.transform ??= {}
139
+ nuxt.options.imports.transform.include ??= []
140
+ nuxt.options.imports.transform.include.push(new RegExp(`^${runtimePath}/`))
141
+ nuxt.options.build.transpile.push(entry.runtime)
142
+ if (existsSync(`${entry.runtime}/composables`)) {
143
+ addImportsDir(`${entry.runtime}/composables`)
144
+ }
145
+ if (existsSync(`${entry.runtime}/server`)) {
146
+ addServerScanDir(`${entry.runtime}/server`)
147
+ }
148
+ }
149
+
150
+ function registerManifest(nuxt: Nuxt, alias: string, name: string, getContents: () => string) {
151
+ const template = addTemplate({ filename: `workspace/${name}.ts`, getContents, write: true })
152
+ nuxt.options.alias[alias] = template.dst
153
+ nuxt.hook('nitro:config', (config) => {
154
+ config.alias ??= {}
155
+ config.alias[alias] = template.dst
156
+ })
157
+ }
158
+
159
+ function featureManifest(entries: FeatureRegistration[]) {
160
+ const features = entries.flatMap((entry) =>
161
+ entry.feature ? [{ ...entry.feature, id: entry.id }] : [],
162
+ )
163
+ return [
164
+ "import type { WorkspaceContribution, WorkspaceFeature } from '@nuxtagentic/workspace/runtime/types'",
165
+ ...features.map(
166
+ (feature, index) =>
167
+ `import { ${feature.name} as feature${index} } from ${JSON.stringify(feature.from.replace(/\.ts$/, ''))}`,
168
+ ),
169
+ `export const WORKSPACE_CONTRIBUTIONS: readonly WorkspaceContribution[] = [${features.map((feature, index) => `[feature${index}].flat().map(entry => ({ ...entry, moduleId: ${JSON.stringify(feature.id)} }))`).join(', ')}].flat()`,
170
+ 'export const WORKSPACE_FEATURES = WORKSPACE_CONTRIBUTIONS.filter((entry): entry is WorkspaceFeature => "component" in entry && !!entry.component)',
171
+ 'export type WorkspaceFeatureId = string',
172
+ ].join('\n')
173
+ }
174
+
175
+ function metadataManifest(entries: FeatureRegistration[], server: boolean) {
176
+ const sources = entries.flatMap((entry) => {
177
+ const path = server ? (entry.server ?? entry.metadata) : entry.metadata
178
+ return path ? [{ path, id: entry.id, optional: !!entry.catalog }] : []
179
+ })
180
+ const type = server ? 'ServerContribution' : 'WorkspaceMetadata'
181
+ return [
182
+ `import type { ${type} } from '@nuxtagentic/prompt/server-contributions'`,
183
+ ...sources.map(
184
+ (source, index) =>
185
+ `import entry${index} from ${JSON.stringify(source.path.replace(/\.ts$/, ''))}`,
186
+ ),
187
+ `const contributions: (${type} & { moduleId: string; optional: boolean })[] = [${sources.map((source, index) => `({ ...entry${index}, moduleId: ${JSON.stringify(source.id)}, optional: ${source.optional} })`).join(', ')}]`,
188
+ 'export function getWorkspaceMetadata(installed?: readonly string[]) {',
189
+ ' const entries = contributions.filter(entry => !entry.optional || !installed || installed.includes(entry.moduleId))',
190
+ ' return {',
191
+ ' MODEL_VIEWS: entries.flatMap(entry => entry.views ?? []),',
192
+ ` AGENT_TOOLS: Object.assign({}, ...entries.map(entry => entry.tools ?? {})) as NonNullable<${type}['tools']>,`,
193
+ ` AGENT_RESOURCE_ALIASES: Object.assign({}, ...entries.map(entry => entry.resourceAliases ?? {})) as NonNullable<${type}['resourceAliases']>,`,
194
+ ` GOOGLE_SERVICES: Object.assign({}, ...entries.map(entry => entry.googleServices ?? {})) as NonNullable<${type}['googleServices']>,`,
195
+ ' AGENT_EXCLUDED_MODELS: entries.flatMap(entry => entry.excludedModels ?? []),',
196
+ ...(server
197
+ ? [
198
+ ' resourceFactories: entries.flatMap(entry => entry.resourceFactories ?? []),',
199
+ ' googleDisconnectHooks: entries.flatMap(entry => entry.googleDisconnectHooks ?? []),',
200
+ ' googleAvailabilityHooks: entries.flatMap(entry => entry.googleAvailabilityHooks ?? []),',
201
+ ]
202
+ : []),
203
+ ' }',
204
+ '}',
205
+ `export const { MODEL_VIEWS, AGENT_TOOLS, AGENT_RESOURCE_ALIASES, GOOGLE_SERVICES, AGENT_EXCLUDED_MODELS${server ? ', resourceFactories, googleDisconnectHooks, googleAvailabilityHooks' : ''} } = getWorkspaceMetadata()`,
206
+ ].join('\n')
207
+ }