@nuraly/lumenjs 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +73 -0
  11. package/dist/auth/native-auth.js +293 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +98 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/utils.d.ts +7 -0
  31. package/dist/auth/routes/utils.js +35 -0
  32. package/dist/auth/routes/verify.d.ts +3 -0
  33. package/dist/auth/routes/verify.js +26 -0
  34. package/dist/auth/routes.d.ts +8 -0
  35. package/dist/auth/routes.js +110 -0
  36. package/dist/auth/session.d.ts +8 -0
  37. package/dist/auth/session.js +54 -0
  38. package/dist/auth/token.d.ts +33 -0
  39. package/dist/auth/token.js +90 -0
  40. package/dist/auth/types.d.ts +156 -0
  41. package/dist/auth/types.js +2 -0
  42. package/dist/build/build-client.d.ts +15 -0
  43. package/dist/build/build-client.js +45 -0
  44. package/dist/build/build-prerender.d.ts +11 -0
  45. package/dist/build/build-prerender.js +159 -0
  46. package/dist/build/build-server.d.ts +17 -0
  47. package/dist/build/build-server.js +98 -0
  48. package/dist/build/build.js +48 -120
  49. package/dist/build/scan.d.ts +17 -0
  50. package/dist/build/scan.js +76 -6
  51. package/dist/build/serve-api.js +8 -2
  52. package/dist/build/serve-loaders.d.ts +4 -4
  53. package/dist/build/serve-loaders.js +26 -18
  54. package/dist/build/serve-ssr.js +38 -11
  55. package/dist/build/serve-static.js +3 -3
  56. package/dist/build/serve.js +218 -15
  57. package/dist/cli.js +37 -6
  58. package/dist/communication/encryption.d.ts +35 -0
  59. package/dist/communication/encryption.js +90 -0
  60. package/dist/communication/handlers/context.d.ts +27 -0
  61. package/dist/communication/handlers/context.js +1 -0
  62. package/dist/communication/handlers/conversation.d.ts +24 -0
  63. package/dist/communication/handlers/conversation.js +113 -0
  64. package/dist/communication/handlers/file-upload.d.ts +17 -0
  65. package/dist/communication/handlers/file-upload.js +62 -0
  66. package/dist/communication/handlers/messaging.d.ts +30 -0
  67. package/dist/communication/handlers/messaging.js +237 -0
  68. package/dist/communication/handlers/presence.d.ts +15 -0
  69. package/dist/communication/handlers/presence.js +76 -0
  70. package/dist/communication/handlers.d.ts +5 -0
  71. package/dist/communication/handlers.js +5 -0
  72. package/dist/communication/index.d.ts +9 -0
  73. package/dist/communication/index.js +7 -0
  74. package/dist/communication/link-preview.d.ts +18 -0
  75. package/dist/communication/link-preview.js +115 -0
  76. package/dist/communication/schema.d.ts +10 -0
  77. package/dist/communication/schema.js +101 -0
  78. package/dist/communication/server.d.ts +86 -0
  79. package/dist/communication/server.js +212 -0
  80. package/dist/communication/signaling.d.ts +43 -0
  81. package/dist/communication/signaling.js +271 -0
  82. package/dist/communication/store.d.ts +71 -0
  83. package/dist/communication/store.js +289 -0
  84. package/dist/communication/types.d.ts +454 -0
  85. package/dist/communication/types.js +1 -0
  86. package/dist/create.d.ts +1 -0
  87. package/dist/create.js +55 -0
  88. package/dist/db/auto-migrate.d.ts +3 -0
  89. package/dist/db/auto-migrate.js +100 -0
  90. package/dist/db/client.d.ts +3 -0
  91. package/dist/db/client.js +18 -0
  92. package/dist/db/index.d.ts +17 -13
  93. package/dist/db/index.js +205 -26
  94. package/dist/db/seed.d.ts +12 -0
  95. package/dist/db/seed.js +88 -0
  96. package/dist/db/table.d.ts +10 -0
  97. package/dist/db/table.js +12 -0
  98. package/dist/dev-server/config.d.ts +11 -0
  99. package/dist/dev-server/config.js +23 -20
  100. package/dist/dev-server/index-html.d.ts +3 -0
  101. package/dist/dev-server/index-html.js +18 -6
  102. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  103. package/dist/dev-server/nuralyui-aliases.js +115 -94
  104. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  105. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  106. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  107. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  108. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  109. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  110. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  111. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  113. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  114. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  115. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  116. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  117. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  118. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  119. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  120. package/dist/dev-server/plugins/vite-plugin-routes.js +15 -5
  121. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  122. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  123. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  125. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  126. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  127. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +111 -2
  128. package/dist/dev-server/server.js +127 -13
  129. package/dist/dev-server/ssr-render.d.ts +2 -1
  130. package/dist/dev-server/ssr-render.js +107 -48
  131. package/dist/editor/ai/backend.d.ts +20 -0
  132. package/dist/editor/ai/backend.js +104 -0
  133. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  134. package/dist/editor/ai/claude-code-client.js +145 -0
  135. package/dist/editor/ai/opencode-client.d.ts +14 -0
  136. package/dist/editor/ai/opencode-client.js +125 -0
  137. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  138. package/dist/editor/ai/snapshot-store.js +35 -0
  139. package/dist/editor/ai/types.d.ts +30 -0
  140. package/dist/editor/ai/types.js +136 -0
  141. package/dist/editor/ai-chat-panel.d.ts +13 -0
  142. package/dist/editor/ai-chat-panel.js +587 -0
  143. package/dist/editor/ai-markdown.d.ts +10 -0
  144. package/dist/editor/ai-markdown.js +70 -0
  145. package/dist/editor/ai-project-panel.d.ts +11 -0
  146. package/dist/editor/ai-project-panel.js +332 -0
  147. package/dist/editor/ast-modification.d.ts +11 -0
  148. package/dist/editor/ast-modification.js +1 -0
  149. package/dist/editor/ast-service.d.ts +30 -0
  150. package/dist/editor/ast-service.js +180 -0
  151. package/dist/editor/css-rules.d.ts +54 -0
  152. package/dist/editor/css-rules.js +423 -0
  153. package/dist/editor/editor-api-client.d.ts +51 -0
  154. package/dist/editor/editor-api-client.js +162 -0
  155. package/dist/editor/editor-bridge.d.ts +1 -0
  156. package/dist/editor/editor-bridge.js +17 -8
  157. package/dist/editor/editor-toolbar.d.ts +14 -0
  158. package/dist/editor/editor-toolbar.js +115 -0
  159. package/dist/editor/file-editor.d.ts +9 -0
  160. package/dist/editor/file-editor.js +236 -0
  161. package/dist/editor/file-service.d.ts +16 -0
  162. package/dist/editor/file-service.js +52 -0
  163. package/dist/editor/i18n-key-gen.d.ts +1 -0
  164. package/dist/editor/i18n-key-gen.js +7 -0
  165. package/dist/editor/inline-text-edit.d.ts +5 -0
  166. package/dist/editor/inline-text-edit.js +173 -92
  167. package/dist/editor/overlay-events.d.ts +5 -0
  168. package/dist/editor/overlay-events.js +364 -0
  169. package/dist/editor/overlay-hmr.d.ts +2 -0
  170. package/dist/editor/overlay-hmr.js +75 -0
  171. package/dist/editor/overlay-selection.d.ts +29 -0
  172. package/dist/editor/overlay-selection.js +148 -0
  173. package/dist/editor/overlay-utils.d.ts +12 -0
  174. package/dist/editor/overlay-utils.js +59 -0
  175. package/dist/editor/properties-panel-persist.d.ts +14 -0
  176. package/dist/editor/properties-panel-persist.js +70 -0
  177. package/dist/editor/properties-panel-rows.d.ts +10 -0
  178. package/dist/editor/properties-panel-rows.js +349 -0
  179. package/dist/editor/properties-panel-styles.d.ts +4 -0
  180. package/dist/editor/properties-panel-styles.js +174 -0
  181. package/dist/editor/properties-panel.d.ts +4 -0
  182. package/dist/editor/properties-panel.js +148 -0
  183. package/dist/editor/property-registry.d.ts +16 -0
  184. package/dist/editor/property-registry.js +303 -0
  185. package/dist/editor/standalone-file-panel.d.ts +0 -0
  186. package/dist/editor/standalone-file-panel.js +1 -0
  187. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  188. package/dist/editor/standalone-overlay-dom.js +1 -0
  189. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  190. package/dist/editor/standalone-overlay-styles.js +1 -0
  191. package/dist/editor/standalone-overlay.d.ts +1 -0
  192. package/dist/editor/standalone-overlay.js +76 -0
  193. package/dist/editor/syntax-highlighter.d.ts +4 -0
  194. package/dist/editor/syntax-highlighter.js +81 -0
  195. package/dist/editor/text-toolbar.d.ts +11 -0
  196. package/dist/editor/text-toolbar.js +327 -0
  197. package/dist/editor/toolbar-styles.d.ts +4 -0
  198. package/dist/editor/toolbar-styles.js +198 -0
  199. package/dist/email/index.d.ts +32 -0
  200. package/dist/email/index.js +154 -0
  201. package/dist/email/providers/resend.d.ts +2 -0
  202. package/dist/email/providers/resend.js +24 -0
  203. package/dist/email/providers/sendgrid.d.ts +2 -0
  204. package/dist/email/providers/sendgrid.js +31 -0
  205. package/dist/email/providers/smtp.d.ts +13 -0
  206. package/dist/email/providers/smtp.js +125 -0
  207. package/dist/email/template-engine.d.ts +18 -0
  208. package/dist/email/template-engine.js +116 -0
  209. package/dist/email/templates/base.d.ts +9 -0
  210. package/dist/email/templates/base.js +65 -0
  211. package/dist/email/templates/password-reset.d.ts +5 -0
  212. package/dist/email/templates/password-reset.js +15 -0
  213. package/dist/email/templates/verify-email.d.ts +5 -0
  214. package/dist/email/templates/verify-email.js +15 -0
  215. package/dist/email/templates/welcome.d.ts +5 -0
  216. package/dist/email/templates/welcome.js +13 -0
  217. package/dist/email/types.d.ts +49 -0
  218. package/dist/email/types.js +1 -0
  219. package/dist/llms/generate.d.ts +46 -0
  220. package/dist/llms/generate.js +185 -0
  221. package/dist/permissions/guard.d.ts +28 -0
  222. package/dist/permissions/guard.js +30 -0
  223. package/dist/permissions/index.d.ts +6 -0
  224. package/dist/permissions/index.js +3 -0
  225. package/dist/permissions/service.d.ts +80 -0
  226. package/dist/permissions/service.js +210 -0
  227. package/dist/permissions/tables.d.ts +5 -0
  228. package/dist/permissions/tables.js +68 -0
  229. package/dist/permissions/types.d.ts +33 -0
  230. package/dist/permissions/types.js +1 -0
  231. package/dist/runtime/app-shell.js +163 -0
  232. package/dist/runtime/auth.d.ts +10 -0
  233. package/dist/runtime/auth.js +30 -0
  234. package/dist/runtime/communication.d.ts +137 -0
  235. package/dist/runtime/communication.js +228 -0
  236. package/dist/runtime/error-boundary.d.ts +23 -0
  237. package/dist/runtime/error-boundary.js +120 -0
  238. package/dist/runtime/i18n.d.ts +6 -1
  239. package/dist/runtime/i18n.js +42 -21
  240. package/dist/runtime/router-data.d.ts +3 -0
  241. package/dist/runtime/router-data.js +102 -17
  242. package/dist/runtime/router-hydration.js +25 -0
  243. package/dist/runtime/router.d.ts +16 -1
  244. package/dist/runtime/router.js +188 -42
  245. package/dist/runtime/socket-client.d.ts +2 -0
  246. package/dist/runtime/socket-client.js +30 -0
  247. package/dist/runtime/webrtc.d.ts +47 -0
  248. package/dist/runtime/webrtc.js +178 -0
  249. package/dist/shared/graceful-shutdown.d.ts +8 -0
  250. package/dist/shared/graceful-shutdown.js +36 -0
  251. package/dist/shared/health.d.ts +8 -0
  252. package/dist/shared/health.js +25 -0
  253. package/dist/shared/llms-txt.d.ts +31 -0
  254. package/dist/shared/llms-txt.js +85 -0
  255. package/dist/shared/logger.d.ts +32 -0
  256. package/dist/shared/logger.js +93 -0
  257. package/dist/shared/meta.d.ts +27 -0
  258. package/dist/shared/meta.js +71 -0
  259. package/dist/shared/middleware-runner.d.ts +9 -0
  260. package/dist/shared/middleware-runner.js +29 -0
  261. package/dist/shared/rate-limit.d.ts +18 -0
  262. package/dist/shared/rate-limit.js +71 -0
  263. package/dist/shared/request-id.d.ts +5 -0
  264. package/dist/shared/request-id.js +18 -0
  265. package/dist/shared/route-matching.js +16 -1
  266. package/dist/shared/security-headers.d.ts +18 -0
  267. package/dist/shared/security-headers.js +38 -0
  268. package/dist/shared/socket-io-setup.d.ts +11 -0
  269. package/dist/shared/socket-io-setup.js +51 -0
  270. package/dist/shared/types.d.ts +14 -0
  271. package/dist/shared/utils.d.ts +33 -7
  272. package/dist/shared/utils.js +164 -27
  273. package/dist/storage/adapters/local.d.ts +44 -0
  274. package/dist/storage/adapters/local.js +85 -0
  275. package/dist/storage/adapters/s3.d.ts +32 -0
  276. package/dist/storage/adapters/s3.js +116 -0
  277. package/dist/storage/adapters/types.d.ts +53 -0
  278. package/dist/storage/adapters/types.js +1 -0
  279. package/dist/storage/index.d.ts +76 -0
  280. package/dist/storage/index.js +83 -0
  281. package/package.json +19 -7
  282. package/templates/blog/api/posts.ts +4 -18
  283. package/templates/blog/data/migrations/001_init.sql +6 -5
  284. package/templates/blog/lumenjs.config.ts +3 -0
  285. package/templates/blog/package.json +14 -0
  286. package/templates/blog/pages/_layout.ts +25 -0
  287. package/templates/blog/pages/index.ts +48 -22
  288. package/templates/blog/pages/posts/[slug].ts +45 -20
  289. package/templates/blog/pages/tag/[tag].ts +44 -0
  290. package/templates/dashboard/api/stats.ts +8 -5
  291. package/templates/dashboard/lumenjs.config.ts +3 -0
  292. package/templates/dashboard/package.json +14 -0
  293. package/templates/dashboard/pages/_layout.ts +25 -0
  294. package/templates/dashboard/pages/index.ts +54 -23
  295. package/templates/dashboard/pages/settings/index.ts +29 -0
  296. package/templates/default/lumenjs.config.ts +3 -0
  297. package/templates/default/package.json +14 -0
  298. package/templates/default/pages/index.ts +24 -0
  299. package/templates/social/api/posts/[id].ts +14 -0
  300. package/templates/social/api/posts.ts +11 -0
  301. package/templates/social/api/profile/[username].ts +10 -0
  302. package/templates/social/api/upload.ts +19 -0
  303. package/templates/social/data/migrations/001_init.sql +78 -0
  304. package/templates/social/data/migrations/002_add_image_url.sql +1 -0
  305. package/templates/social/data/migrations/003_auth.sql +7 -0
  306. package/templates/social/docs/architecture.md +76 -0
  307. package/templates/social/docs/components.md +100 -0
  308. package/templates/social/docs/data.md +89 -0
  309. package/templates/social/docs/pages.md +96 -0
  310. package/templates/social/docs/theming.md +52 -0
  311. package/templates/social/lib/media.ts +130 -0
  312. package/templates/social/lumenjs.auth.ts +21 -0
  313. package/templates/social/lumenjs.config.ts +3 -0
  314. package/templates/social/package.json +5 -0
  315. package/templates/social/pages/_layout.ts +239 -0
  316. package/templates/social/pages/apps/[id].ts +173 -0
  317. package/templates/social/pages/apps/index.ts +116 -0
  318. package/templates/social/pages/auth/login.ts +92 -0
  319. package/templates/social/pages/bookmarks.ts +57 -0
  320. package/templates/social/pages/explore.ts +73 -0
  321. package/templates/social/pages/index.ts +351 -0
  322. package/templates/social/pages/messages.ts +298 -0
  323. package/templates/social/pages/new.ts +77 -0
  324. package/templates/social/pages/notifications.ts +73 -0
  325. package/templates/social/pages/post/[id].ts +124 -0
  326. package/templates/social/pages/profile/[username].ts +100 -0
  327. package/templates/social/pages/settings/accessibility.ts +153 -0
  328. package/templates/social/pages/settings/account.ts +260 -0
  329. package/templates/social/pages/settings/help.ts +141 -0
  330. package/templates/social/pages/settings/language.ts +103 -0
  331. package/templates/social/pages/settings/privacy.ts +183 -0
  332. package/templates/social/pages/settings/security.ts +133 -0
  333. package/templates/social/pages/settings.ts +185 -0
@@ -11,13 +11,21 @@ import { ssrRenderPage } from './ssr-render.js';
11
11
  import { readProjectConfig, getLumenJSNodeModules, getLumenJSDirs } from './config.js';
12
12
  import { getNuralyUIAliases, resolveNuralyUIPaths } from './nuralyui-aliases.js';
13
13
  import { litDedupPlugin } from './plugins/vite-plugin-lit-dedup.js';
14
+ import { autoDefinePlugin } from './plugins/vite-plugin-auto-define.js';
14
15
  import { autoImportPlugin } from './plugins/vite-plugin-auto-import.js';
15
16
  import { litHmrPlugin } from './plugins/vite-plugin-lit-hmr.js';
16
17
  import { sourceAnnotatorPlugin } from './plugins/vite-plugin-source-annotator.js';
18
+ import { editorApiPlugin } from './plugins/vite-plugin-editor-api.js';
17
19
  import { virtualModulesPlugin } from './plugins/vite-plugin-virtual-modules.js';
18
20
  import { i18nPlugin, loadTranslationsFromDisk } from './plugins/vite-plugin-i18n.js';
21
+ import { authPlugin } from './plugins/vite-plugin-auth.js';
22
+ import { communicationPlugin } from './plugins/vite-plugin-communication.js';
23
+ import { lumenStoragePlugin } from './plugins/vite-plugin-storage.js';
24
+ import { lumenSocketIOPlugin } from './plugins/vite-plugin-socketio.js';
19
25
  import { resolveLocale } from './middleware/locale.js';
20
26
  import { setProjectDir } from '../db/context.js';
27
+ import { scanMiddleware, getMiddlewareDirsForPathname } from '../build/scan.js';
28
+ import { runMiddlewareChain, extractMiddleware } from '../shared/middleware-runner.js';
21
29
  // Re-export for backwards compatibility
22
30
  export { readProjectConfig, readProjectTitle, getLumenJSNodeModules, getLumenJSDirs } from './config.js';
23
31
  export { getNuralyUIAliases, resolveNuralyUIPaths } from './nuralyui-aliases.js';
@@ -30,24 +38,37 @@ export function getSharedViteConfig(projectDir, options) {
30
38
  const isDev = mode === 'development';
31
39
  const pagesDir = path.join(projectDir, 'pages');
32
40
  const lumenNodeModules = getLumenJSNodeModules();
33
- const { runtimeDir, editorDir } = getLumenJSDirs();
41
+ const { distDir, runtimeDir, editorDir } = getLumenJSDirs();
34
42
  // Resolve NuralyUI paths for aliases (only when nuralyui integration is enabled)
35
43
  const aliases = {};
36
44
  if (options?.integrations?.includes('nuralyui')) {
37
45
  const nuralyUIPaths = resolveNuralyUIPaths(projectDir);
38
46
  if (nuralyUIPaths) {
39
47
  Object.assign(aliases, getNuralyUIAliases(nuralyUIPaths.componentsPath, nuralyUIPaths.commonPath));
48
+ // Add root aliases for theme CSS imports
49
+ const nuralyUIRoot = path.resolve(nuralyUIPaths.componentsPath, '..');
50
+ aliases['@nuralyui-theme'] = path.join(nuralyUIRoot, 'shared/themes');
51
+ aliases['@nuralyui-common'] = nuralyUIPaths.commonPath;
40
52
  }
41
53
  }
42
54
  const resolve = {
43
55
  alias: {
44
56
  ...aliases,
45
- // Map @lumenjs/i18n to the physical dist file so Vite resolves it
46
- // without going through node_modules (it's not an npm package).
47
57
  '@lumenjs/i18n': path.join(runtimeDir, 'i18n.js'),
58
+ '@lumenjs/auth': path.join(runtimeDir, 'auth.js'),
59
+ '@nuraly/lumenjs-auth': path.join(runtimeDir, 'auth.js'),
60
+ '@lumenjs/communication': path.join(runtimeDir, 'communication.js'),
61
+ '@lumenjs/webrtc': path.join(runtimeDir, 'webrtc.js'),
62
+ '@lumenjs/db': path.join(distDir, 'db', 'client.js'),
63
+ '@lumenjs/permissions': path.join(distDir, 'permissions', 'index.js'),
64
+ '@lumenjs/storage': path.join(distDir, 'storage', 'index.js'),
65
+ '@nuraly/lumenjs': path.resolve(distDir, '..'),
48
66
  },
49
67
  conditions: isDev ? ['development', 'browser'] : ['browser'],
50
- dedupe: ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'],
68
+ // Note: resolve.dedupe is NOT used — it resolves via Node's algorithm
69
+ // which ignores Vite's resolve.conditions, picking the `default` export
70
+ // (prod proxy) instead of `development`. The litDedupPlugin handles
71
+ // single-copy resolution with correct conditions instead.
51
72
  };
52
73
  const esbuild = {
53
74
  tsconfigRaw: {
@@ -60,6 +81,7 @@ export function getSharedViteConfig(projectDir, options) {
60
81
  const plugins = [
61
82
  lumenRoutesPlugin(pagesDir),
62
83
  lumenLoadersPlugin(pagesDir),
84
+ autoDefinePlugin(pagesDir),
63
85
  litDedupPlugin(lumenNodeModules, isDev),
64
86
  virtualModulesPlugin(runtimeDir, editorDir),
65
87
  ];
@@ -83,12 +105,17 @@ export function getSharedViteConfig(projectDir, options) {
83
105
  }
84
106
  export async function createDevServer(options) {
85
107
  const { projectDir, port, editorMode = false, base = '/' } = options;
86
- setProjectDir(projectDir);
87
108
  const pagesDir = path.join(projectDir, 'pages');
88
109
  const apiDir = path.join(projectDir, 'api');
89
110
  const publicDir = path.join(projectDir, 'public');
90
111
  const config = readProjectConfig(projectDir);
91
- const { title, integrations, i18n: i18nConfig } = config;
112
+ const { title, integrations, i18n: i18nConfig, prefetch: prefetchStrategy } = config;
113
+ // Read optional head.html for blocking scripts (e.g. theme initialization)
114
+ const headHtmlPath = path.join(projectDir, 'head.html');
115
+ const headContent = fs.existsSync(headHtmlPath) ? fs.readFileSync(headHtmlPath, 'utf-8') : undefined;
116
+ // Set project dir for DB context (used by loaders, API routes, plugins)
117
+ setProjectDir(projectDir);
118
+ process.env.LUMENJS_PROJECT_DIR = projectDir;
92
119
  const shared = getSharedViteConfig(projectDir, { integrations });
93
120
  const server = await createViteServer({
94
121
  root: projectDir,
@@ -99,15 +126,90 @@ export async function createDevServer(options) {
99
126
  strictPort: false,
100
127
  allowedHosts: true,
101
128
  cors: true,
102
- hmr: true,
129
+ hmr: process.env.HMR_CLIENT_PORT ? { clientPort: parseInt(process.env.HMR_CLIENT_PORT), protocol: process.env.HMR_PROTOCOL || 'wss', host: process.env.HMR_HOST || undefined } : true,
130
+ fs: {
131
+ allow: [projectDir, getLumenJSNodeModules(), path.resolve(getLumenJSNodeModules(), '..')],
132
+ },
103
133
  },
104
134
  resolve: shared.resolve,
105
135
  plugins: [
136
+ ...(integrations.includes('auth') ? [authPlugin(projectDir)] : []),
106
137
  ...shared.plugins,
138
+ ...(integrations.includes('communication') ? [communicationPlugin(projectDir)] : []),
139
+ lumenStoragePlugin(projectDir),
107
140
  lumenApiRoutesPlugin(apiDir, projectDir),
108
141
  litHmrPlugin(projectDir),
109
142
  ...(i18nConfig ? [i18nPlugin(projectDir, i18nConfig)] : []),
110
- ...(editorMode ? [sourceAnnotatorPlugin(projectDir)] : []),
143
+ ...(editorMode ? [sourceAnnotatorPlugin(projectDir), editorApiPlugin(projectDir)] : []),
144
+ lumenSocketIOPlugin(pagesDir),
145
+ {
146
+ // Clear SSR module cache on file changes so the next SSR request uses fresh code.
147
+ // Without this, HMR updates the client but SSR keeps serving stale modules.
148
+ name: 'lumenjs-ssr-invalidate-on-change',
149
+ handleHotUpdate({ file, server }) {
150
+ const mods = server.moduleGraph.getModulesByFile(file);
151
+ if (mods) {
152
+ for (const m of mods) {
153
+ m.ssrModule = null;
154
+ m.ssrTransformResult = null;
155
+ }
156
+ }
157
+ },
158
+ },
159
+ {
160
+ name: 'lumenjs-user-middleware',
161
+ config(config) {
162
+ const entries = scanMiddleware(pagesDir);
163
+ if (entries.length === 0)
164
+ return;
165
+ const npmDeps = new Set();
166
+ for (const entry of entries) {
167
+ try {
168
+ const content = fs.readFileSync(entry.filePath, 'utf-8');
169
+ const importMatches = content.matchAll(/(?:import|require)\s*(?:\(?\s*['"]([^./][^'"]*)['"]\s*\)?|.*from\s*['"]([^./][^'"]*)['"]\s*)/g);
170
+ for (const m of importMatches) {
171
+ const pkg = m[1] || m[2];
172
+ if (pkg) {
173
+ const pkgName = pkg.startsWith('@') ? pkg.split('/').slice(0, 2).join('/') : pkg.split('/')[0];
174
+ npmDeps.add(pkgName);
175
+ }
176
+ }
177
+ }
178
+ catch { }
179
+ }
180
+ if (npmDeps.size > 0) {
181
+ const existing = config.ssr?.external || [];
182
+ return { ssr: { external: [...existing, ...npmDeps] } };
183
+ }
184
+ },
185
+ configureServer(server) {
186
+ server.middlewares.use(async (req, res, next) => {
187
+ const pathname = (req.url || '/').split('?')[0];
188
+ if (pathname.startsWith('/@') || pathname.startsWith('/node_modules') || pathname.includes('.')) {
189
+ return next();
190
+ }
191
+ const middlewareEntries = scanMiddleware(pagesDir);
192
+ if (middlewareEntries.length === 0)
193
+ return next();
194
+ const matchingDirs = getMiddlewareDirsForPathname(pathname, middlewareEntries);
195
+ if (matchingDirs.length === 0)
196
+ return next();
197
+ const allMw = [];
198
+ for (const entry of matchingDirs) {
199
+ try {
200
+ const mod = await server.ssrLoadModule(entry.filePath);
201
+ allMw.push(...extractMiddleware(mod));
202
+ }
203
+ catch (err) {
204
+ console.error(`[LumenJS] Failed to load _middleware.ts (${entry.dir || 'root'}):`, err);
205
+ }
206
+ }
207
+ if (allMw.length === 0)
208
+ return next();
209
+ runMiddlewareChain(allMw, req, res, next);
210
+ });
211
+ }
212
+ },
111
213
  {
112
214
  name: 'lumenjs-index-html',
113
215
  configureServer(server) {
@@ -138,7 +240,7 @@ export async function createDevServer(options) {
138
240
  translations = loadTranslationsFromDisk(projectDir, locale);
139
241
  }
140
242
  const SSR_PLACEHOLDER = '<!--__NK_SSR_CONTENT__-->';
141
- ssrRenderPage(server, pagesDir, pathname, req.headers, locale).then(async (ssrResult) => {
243
+ ssrRenderPage(server, pagesDir, pathname, req.headers, locale, req.nkAuth?.user ?? undefined).then(async (ssrResult) => {
142
244
  if (ssrResult?.redirect) {
143
245
  res.writeHead(ssrResult.redirect.status, { Location: ssrResult.redirect.location });
144
246
  res.end();
@@ -154,6 +256,9 @@ export async function createDevServer(options) {
154
256
  locale,
155
257
  i18nConfig: i18nConfig || undefined,
156
258
  translations,
259
+ prefetch: prefetchStrategy,
260
+ authUser: ssrResult?.authUser ?? req.nkAuth?.user ?? undefined,
261
+ headContent,
157
262
  });
158
263
  const transformed = await server.transformIndexHtml(req.url, shellHtml);
159
264
  const finalHtml = ssrResult
@@ -164,7 +269,7 @@ export async function createDevServer(options) {
164
269
  res.end(finalHtml);
165
270
  }).catch(err => {
166
271
  console.error('[LumenJS] SSR/HTML generation error:', err);
167
- const html = generateIndexHtml({ title, editorMode, integrations, locale, i18nConfig: i18nConfig || undefined, translations });
272
+ const html = generateIndexHtml({ title, editorMode, integrations, locale, i18nConfig: i18nConfig || undefined, translations, prefetch: prefetchStrategy, headContent });
168
273
  server.transformIndexHtml(req.url, html).then(transformed => {
169
274
  res.setHeader('Content-Type', 'text/html');
170
275
  res.setHeader('Cache-Control', 'no-store');
@@ -180,12 +285,21 @@ export async function createDevServer(options) {
180
285
  ],
181
286
  esbuild: shared.esbuild,
182
287
  optimizeDeps: {
183
- include: ['lit', 'lit/decorators.js', 'lit/directive.js', 'lit/directive-helpers.js', 'lit/async-directive.js', 'lit-html', 'lit-element', '@lit/reactive-element'],
184
- exclude: ['@lumenjs/i18n'],
288
+ exclude: [
289
+ '@lumenjs/i18n',
290
+ '@nuraly/lumenjs-auth',
291
+ // Lit packages must NOT be pre-bundled — pre-bundling creates separate
292
+ // module entries (/.vite/deps/) alongside raw /@fs/ files, causing
293
+ // multiple lit-html instances. Instead, resolve.dedupe forces all lit
294
+ // imports to a single copy. The project's lit version MUST match
295
+ // lumenjs's lit version for dedupe to work.
296
+ 'lit', 'lit-html', 'lit-element', '@lit/reactive-element',
297
+ '@lit-labs/ssr-client',
298
+ ],
185
299
  },
186
300
  ssr: {
187
301
  noExternal: true,
188
- external: ['node-domexception'],
302
+ external: ['node-domexception', 'socket.io-client', 'xmlhttprequest-ssl', 'engine.io-client', 'better-sqlite3', '@lumenjs/db', '@lumenjs/permissions'],
189
303
  resolve: {
190
304
  conditions: ['node', 'import'],
191
305
  },
@@ -9,7 +9,7 @@ export interface LayoutSSRData {
9
9
  *
10
10
  * Returns pre-rendered HTML and loader data, or null on failure (falls back to CSR).
11
11
  */
12
- export declare function ssrRenderPage(server: ViteDevServer, pagesDir: string, pathname: string, headers?: Record<string, string | string[] | undefined>, locale?: string): Promise<{
12
+ export declare function ssrRenderPage(server: ViteDevServer, pagesDir: string, pathname: string, headers?: Record<string, string | string[] | undefined>, locale?: string, user?: any): Promise<{
13
13
  html: string;
14
14
  loaderData: any;
15
15
  layoutsData?: LayoutSSRData[];
@@ -17,4 +17,5 @@ export declare function ssrRenderPage(server: ViteDevServer, pagesDir: string, p
17
17
  location: string;
18
18
  status: number;
19
19
  };
20
+ authUser?: any;
20
21
  } | null>;
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
3
  import { resolvePageFile, extractRouteParams } from './plugins/vite-plugin-loaders.js';
4
- import { stripOuterLitMarkers, dirToLayoutTagName, filePathToTagName } from '../shared/utils.js';
4
+ import { stripOuterLitMarkers, dirToLayoutTagName, filePathToTagName, patchLoaderDataSpread } from '../shared/utils.js';
5
5
  import { installDomShims } from '../shared/dom-shims.js';
6
6
  import { loadTranslationsFromDisk } from './plugins/vite-plugin-i18n.js';
7
7
  /**
@@ -10,7 +10,7 @@ import { loadTranslationsFromDisk } from './plugins/vite-plugin-i18n.js';
10
10
  *
11
11
  * Returns pre-rendered HTML and loader data, or null on failure (falls back to CSR).
12
12
  */
13
- export async function ssrRenderPage(server, pagesDir, pathname, headers, locale) {
13
+ export async function ssrRenderPage(server, pagesDir, pathname, headers, locale, user) {
14
14
  try {
15
15
  const filePath = resolvePageFile(pagesDir, pathname);
16
16
  if (!filePath)
@@ -20,32 +20,27 @@ export async function ssrRenderPage(server, pagesDir, pathname, headers, locale)
20
20
  await server.ssrLoadModule('@lit-labs/ssr/lib/install-global-dom-shim.js');
21
21
  // Patch missing DOM APIs that NuralyUI components may use during SSR
22
22
  installDomShims();
23
- // Initialize i18n in the SSR context so t() works during render
24
- if (locale) {
25
- const projectDir = path.resolve(pagesDir, '..');
26
- const translations = loadTranslationsFromDisk(projectDir, locale);
27
- try {
28
- // Load the same i18n module the page will import (via resolve.alias)
29
- const i18nMod = await server.ssrLoadModule('@lumenjs/i18n');
30
- if (i18nMod?.initI18n) {
31
- i18nMod.initI18n({ locales: [], defaultLocale: locale, prefixDefault: false }, locale, translations);
32
- }
33
- }
34
- catch {
35
- // i18n module not available — translations will show keys
36
- }
37
- }
38
23
  // Invalidate SSR module cache so we always get fresh content after file edits.
39
24
  // Also clear the custom element from the SSR registry so the new class is used.
25
+ // IMPORTANT: This must happen BEFORE i18n/auth init, because invalidation
26
+ // recursively clears all SSR-imported modules (including @lumenjs/i18n).
27
+ // If we init i18n first, then invalidate, the page reload gets a fresh i18n
28
+ // module instance with empty translations.
40
29
  const g = globalThis;
41
30
  invalidateSsrModule(server, filePath);
42
31
  clearSsrCustomElement(g);
43
32
  // Load the page module via Vite (registers the custom element, applies transforms)
33
+ // Bypass get() so auto-define re-registers fresh classes
34
+ const registry = g.customElements;
35
+ if (registry)
36
+ registry.__nk_bypass_get = true;
44
37
  const mod = await server.ssrLoadModule(filePath);
38
+ if (registry)
39
+ registry.__nk_bypass_get = false;
45
40
  // Run loader if present
46
41
  let loaderData = undefined;
47
42
  if (mod.loader && typeof mod.loader === 'function') {
48
- loaderData = await mod.loader({ params, query: {}, url: pathname, headers: headers || {}, locale });
43
+ loaderData = await mod.loader({ params, query: {}, url: pathname, headers: headers || {}, locale, user: user ?? null });
49
44
  if (loaderData && typeof loaderData === 'object' && loaderData.__nk_redirect) {
50
45
  return { html: '', loaderData: null, redirect: { location: loaderData.location, status: loaderData.status || 302 } };
51
46
  }
@@ -53,8 +48,8 @@ export async function ssrRenderPage(server, pagesDir, pathname, headers, locale)
53
48
  // Determine the custom element tag name from file path (matches client router)
54
49
  const relPath = path.relative(pagesDir, filePath).replace(/\\/g, '/');
55
50
  const tagName = filePathToTagName(relPath);
56
- // Discover layout chain for this page
57
- const layoutChain = discoverLayoutChain(pagesDir, filePath);
51
+ // Standalone pages skip all layouts
52
+ const layoutChain = mod.standalone ? [] : discoverLayoutChain(pagesDir, filePath);
58
53
  const layoutsData = [];
59
54
  // Load layout modules and run their loaders
60
55
  const layoutModules = [];
@@ -62,10 +57,14 @@ export async function ssrRenderPage(server, pagesDir, pathname, headers, locale)
62
57
  // Invalidate layout module cache and clear SSR element registry
63
58
  invalidateSsrModule(server, layout.filePath);
64
59
  clearSsrCustomElement(g);
60
+ if (registry)
61
+ registry.__nk_bypass_get = true;
65
62
  const layoutMod = await server.ssrLoadModule(layout.filePath);
63
+ if (registry)
64
+ registry.__nk_bypass_get = false;
66
65
  let layoutLoaderData = undefined;
67
66
  if (layoutMod.loader && typeof layoutMod.loader === 'function') {
68
- layoutLoaderData = await layoutMod.loader({ params: {}, query: {}, url: pathname, headers: headers || {}, locale });
67
+ layoutLoaderData = await layoutMod.loader({ params: {}, query: {}, url: pathname, headers: headers || {}, locale, user: user ?? null });
69
68
  if (layoutLoaderData && typeof layoutLoaderData === 'object' && layoutLoaderData.__nk_redirect) {
70
69
  return { html: '', loaderData: null, redirect: { location: layoutLoaderData.location, status: layoutLoaderData.status || 302 } };
71
70
  }
@@ -74,6 +73,38 @@ export async function ssrRenderPage(server, pagesDir, pathname, headers, locale)
74
73
  layoutModules.push({ tagName: layoutTagName, loaderData: layoutLoaderData });
75
74
  layoutsData.push({ loaderPath: layout.dir, data: layoutLoaderData });
76
75
  }
76
+ // Patch element classes to spread loaderData into individual properties
77
+ for (const lm of layoutModules) {
78
+ patchLoaderDataSpread(lm.tagName);
79
+ }
80
+ patchLoaderDataSpread(tagName);
81
+ // Initialize i18n in the SSR context AFTER all page/layout modules are loaded.
82
+ // This ensures t() uses the same module instance the components imported.
83
+ // Must happen after invalidation + module loading, because invalidateSsrModule
84
+ // recursively clears cached modules — if i18n was initialized before, the
85
+ // reloaded page/layout would get a fresh i18n instance with empty translations.
86
+ if (locale) {
87
+ const projectDir = path.resolve(pagesDir, '..');
88
+ const translations = loadTranslationsFromDisk(projectDir, locale);
89
+ try {
90
+ const i18nMod = await server.ssrLoadModule('@lumenjs/i18n');
91
+ if (i18nMod?.initI18n) {
92
+ i18nMod.initI18n({ locales: [], defaultLocale: locale, prefixDefault: false }, locale, translations);
93
+ }
94
+ }
95
+ catch {
96
+ // i18n module not available — translations will show keys
97
+ }
98
+ }
99
+ // Initialize auth in the SSR context (same reason — after module loading)
100
+ if (user) {
101
+ try {
102
+ const authMod = await server.ssrLoadModule('@nuraly/lumenjs-auth');
103
+ if (authMod?.initAuth)
104
+ authMod.initAuth(user);
105
+ }
106
+ catch { }
107
+ }
77
108
  // Load SSR render + lit/static-html.js through Vite (same module registry as page)
78
109
  const { render } = await server.ssrLoadModule('@lit-labs/ssr');
79
110
  const { html, unsafeStatic } = await server.ssrLoadModule('lit/static-html.js');
@@ -106,7 +137,7 @@ export async function ssrRenderPage(server, pagesDir, pathname, headers, locale)
106
137
  htmlStr = layoutHtml + htmlStr;
107
138
  }
108
139
  }
109
- return { html: htmlStr, loaderData, layoutsData: layoutsData.length > 0 ? layoutsData : undefined };
140
+ return { html: htmlStr, loaderData, layoutsData: layoutsData.length > 0 ? layoutsData : undefined, authUser: user ?? undefined };
110
141
  }
111
142
  catch (err) {
112
143
  console.error('[LumenJS] SSR render failed, falling back to CSR:', err);
@@ -145,41 +176,69 @@ function findLayoutFile(dir) {
145
176
  return null;
146
177
  }
147
178
  /**
148
- * Aggressively invalidate a module for SSR re-execution.
179
+ * Aggressively invalidate a module and all its SSR-imported dependencies.
180
+ * Without this, editing a component imported by a page/layout serves stale SSR.
149
181
  */
150
182
  function invalidateSsrModule(server, filePath) {
151
- const byFile = server.moduleGraph.getModulesByFile(filePath);
152
- if (byFile) {
153
- for (const m of byFile) {
154
- server.moduleGraph.invalidateModule(m);
155
- m.ssrModule = null;
156
- m.ssrTransformResult = null;
183
+ const visited = new Set();
184
+ function invalidateRecursive(id) {
185
+ if (visited.has(id))
186
+ return;
187
+ visited.add(id);
188
+ const mods = server.moduleGraph.getModulesByFile(id);
189
+ if (mods) {
190
+ for (const m of mods) {
191
+ server.moduleGraph.invalidateModule(m);
192
+ m.ssrModule = null;
193
+ m.ssrTransformResult = null;
194
+ // Recurse into SSR-imported modules
195
+ if (m.ssrImportedModules) {
196
+ for (const dep of m.ssrImportedModules) {
197
+ if (dep.file)
198
+ invalidateRecursive(dep.file);
199
+ }
200
+ }
201
+ }
202
+ }
203
+ const urlMod = server.moduleGraph.getModuleById(id);
204
+ if (urlMod) {
205
+ server.moduleGraph.invalidateModule(urlMod);
206
+ urlMod.ssrModule = null;
207
+ urlMod.ssrTransformResult = null;
157
208
  }
158
209
  }
159
- const urlMod = server.moduleGraph.getModuleById(filePath);
160
- if (urlMod) {
161
- server.moduleGraph.invalidateModule(urlMod);
162
- urlMod.ssrModule = null;
163
- urlMod.ssrTransformResult = null;
164
- }
210
+ invalidateRecursive(filePath);
165
211
  }
166
212
  /**
167
- * Patch the SSR customElements registry to allow re-registration.
213
+ * Patch the SSR customElements registry to allow re-registration,
214
+ * and proactively clear all definitions so auto-define guards pass.
168
215
  */
169
216
  function clearSsrCustomElement(g) {
170
217
  const registry = g.customElements;
171
- if (!registry || registry.__nk_patched)
218
+ if (!registry)
172
219
  return;
173
- registry.__nk_patched = true;
174
- const origDefine = registry.define.bind(registry);
175
- registry.define = (name, ctor) => {
176
- if (registry.__definitions && registry.__definitions.has(name)) {
177
- const oldCtor = registry.__definitions.get(name)?.ctor;
178
- registry.__definitions.delete(name);
179
- if (oldCtor && registry.__reverseDefinitions) {
180
- registry.__reverseDefinitions.delete(oldCtor);
220
+ // Patch define() to allow re-registration and get() to bypass
221
+ // auto-define guards (one-time)
222
+ if (!registry.__nk_patched) {
223
+ registry.__nk_patched = true;
224
+ const origDefine = registry.define.bind(registry);
225
+ registry.define = (name, ctor) => {
226
+ if (registry.__definitions && registry.__definitions.has(name)) {
227
+ const oldCtor = registry.__definitions.get(name)?.ctor;
228
+ registry.__definitions.delete(name);
229
+ if (oldCtor && registry.__reverseDefinitions) {
230
+ registry.__reverseDefinitions.delete(oldCtor);
231
+ }
181
232
  }
182
- }
183
- return origDefine(name, ctor);
184
- };
233
+ return origDefine(name, ctor);
234
+ };
235
+ // Patch get() so auto-define's `if (!customElements.get('tag'))` guard
236
+ // always passes, allowing define() to re-register the fresh class.
237
+ const origGet = registry.get.bind(registry);
238
+ registry.get = (name) => {
239
+ if (registry.__nk_bypass_get)
240
+ return undefined;
241
+ return origGet(name);
242
+ };
243
+ }
185
244
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * AI backend router — selects between Claude Code and OpenCode based on:
3
+ * 1. AI_BACKEND env var ('claude-code' | 'opencode')
4
+ * 2. Auto-detection: Claude Code CLI first, then OpenCode server
5
+ */
6
+ import type { AiChatOptions, AiChatResult, AiStatusResult } from './types.js';
7
+ export type { AiChatOptions, AiChatResult, AiStatusResult } from './types.js';
8
+ /**
9
+ * Stream an AI chat message using the configured backend.
10
+ */
11
+ export declare function streamAiChat(projectDir: string, options: AiChatOptions): AiChatResult;
12
+ /**
13
+ * Warm up the AI backend session in the background.
14
+ * Call on editor startup so the first user request is fast.
15
+ */
16
+ export declare function warmUpAiSession(projectDir: string): Promise<void>;
17
+ /**
18
+ * Check if any AI backend is available.
19
+ */
20
+ export declare function checkAiStatus(): Promise<AiStatusResult>;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * AI backend router — selects between Claude Code and OpenCode based on:
3
+ * 1. AI_BACKEND env var ('claude-code' | 'opencode')
4
+ * 2. Auto-detection: Claude Code CLI first, then OpenCode server
5
+ */
6
+ let resolvedBackend = null;
7
+ async function detectBackend() {
8
+ if (resolvedBackend)
9
+ return resolvedBackend;
10
+ const explicit = process.env.AI_BACKEND;
11
+ if (explicit === 'claude-code' || explicit === 'opencode') {
12
+ resolvedBackend = explicit;
13
+ console.log(`[LumenJS] AI backend: ${explicit} (from AI_BACKEND env)`);
14
+ return resolvedBackend;
15
+ }
16
+ // Auto-detect: try Claude Code first (subscription-based, no server needed)
17
+ try {
18
+ const cc = await import('./claude-code-client.js');
19
+ const status = await cc.checkAiStatus();
20
+ if (status.configured) {
21
+ resolvedBackend = 'claude-code';
22
+ console.log('[LumenJS] AI backend: claude-code (auto-detected)');
23
+ return resolvedBackend;
24
+ }
25
+ }
26
+ catch {
27
+ // SDK not installed or CLI not found
28
+ }
29
+ // Fall back to OpenCode
30
+ resolvedBackend = 'opencode';
31
+ console.log('[LumenJS] AI backend: opencode (fallback)');
32
+ return resolvedBackend;
33
+ }
34
+ async function getClient() {
35
+ const backend = await detectBackend();
36
+ if (backend === 'claude-code') {
37
+ return import('./claude-code-client.js');
38
+ }
39
+ return import('./opencode-client.js');
40
+ }
41
+ /**
42
+ * Stream an AI chat message using the configured backend.
43
+ */
44
+ export function streamAiChat(projectDir, options) {
45
+ const tokenCallbacks = [];
46
+ const doneCallbacks = [];
47
+ const errorCallbacks = [];
48
+ let innerResult = null;
49
+ const run = async () => {
50
+ try {
51
+ const client = await getClient();
52
+ innerResult = client.streamAiChat(projectDir, options);
53
+ // Forward callbacks registered before the client loaded
54
+ for (const cb of tokenCallbacks)
55
+ innerResult.onToken(cb);
56
+ for (const cb of doneCallbacks)
57
+ innerResult.onDone(cb);
58
+ for (const cb of errorCallbacks)
59
+ innerResult.onError(cb);
60
+ }
61
+ catch (err) {
62
+ const error = err instanceof Error ? err : new Error(String(err));
63
+ for (const cb of errorCallbacks)
64
+ cb(error);
65
+ }
66
+ };
67
+ run();
68
+ return {
69
+ get sessionId() { return innerResult?.sessionId || options.sessionId || ''; },
70
+ onToken: (cb) => { tokenCallbacks.push(cb); innerResult?.onToken(cb); },
71
+ onDone: (cb) => { doneCallbacks.push(cb); innerResult?.onDone(cb); },
72
+ onError: (cb) => { errorCallbacks.push(cb); innerResult?.onError(cb); },
73
+ abort: () => innerResult?.abort(),
74
+ };
75
+ }
76
+ /**
77
+ * Warm up the AI backend session in the background.
78
+ * Call on editor startup so the first user request is fast.
79
+ */
80
+ export async function warmUpAiSession(projectDir) {
81
+ const backend = await detectBackend();
82
+ if (backend === 'claude-code') {
83
+ try {
84
+ const cc = await import('./claude-code-client.js');
85
+ await cc.warmUpSession(projectDir);
86
+ console.log('[LumenJS] AI session warmed up');
87
+ }
88
+ catch {
89
+ // Non-fatal
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Check if any AI backend is available.
95
+ */
96
+ export async function checkAiStatus() {
97
+ try {
98
+ const client = await getClient();
99
+ return client.checkAiStatus();
100
+ }
101
+ catch {
102
+ return { configured: false, backend: 'opencode' };
103
+ }
104
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Claude Code AI client — uses the Claude Agent SDK (@anthropic-ai/claude-agent-sdk).
3
+ * Spawns the `claude` CLI as a subprocess. Requires Claude Code CLI installed and logged in.
4
+ * Works with Pro/Max subscription — no API key needed.
5
+ */
6
+ import type { AiChatOptions, AiChatResult, AiStatusResult } from './types.js';
7
+ /**
8
+ * Warm up by pre-spawning a Claude Code session. The first real request
9
+ * will resume this session, skipping CLI cold-start entirely.
10
+ */
11
+ export declare function warmUpSession(projectDir: string): Promise<void>;
12
+ /**
13
+ * Stream an AI chat message via Claude Code Agent SDK.
14
+ */
15
+ export declare function streamAiChat(projectDir: string, options: AiChatOptions): AiChatResult;
16
+ /**
17
+ * Check if Claude Code CLI is installed and the Agent SDK is importable.
18
+ * Both are required — the CLI alone isn't enough.
19
+ */
20
+ export declare function checkAiStatus(): Promise<AiStatusResult>;