aimeat 1.33.2 → 1.35.0

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 (328) hide show
  1. package/dist/locales/en.json +437 -15
  2. package/dist/locales/fi.json +437 -15
  3. package/dist/public/cortex-bundled/aimeat-charts.js +3 -3
  4. package/dist/public/cortex-bundled/aimeat-charts.yaml +3 -3
  5. package/dist/public/cortex-bundled/aimeat-i18n.js +123 -0
  6. package/dist/public/cortex-bundled/aimeat-i18n.yaml +64 -0
  7. package/dist/public/css/views/landing.css +2 -0
  8. package/dist/public/css/views/secretary.css +464 -2
  9. package/dist/public/js/i18n.js +9 -0
  10. package/dist/public/js/services/apps.js +4 -2
  11. package/dist/public/js/services/memory.js +9 -3
  12. package/dist/public/js/services/offers.js +1 -1
  13. package/dist/public/js/services/organisms.js +49 -17
  14. package/dist/public/js/services/secretary-helpers.js +266 -17
  15. package/dist/public/js/services/secretary-policy.js +28 -2
  16. package/dist/public/js/services/secretary-rules.js +70 -0
  17. package/dist/public/js/theme.js +20 -0
  18. package/dist/public/lib/VENDORED-STYLING.md +31 -0
  19. package/dist/public/lib/aimeat-daisyui-bridge.css +48 -0
  20. package/dist/public/lib/chartjs@4.js +14 -0
  21. package/dist/public/lib/daisyui@5.css +7 -0
  22. package/dist/public/lib/tailwindcss@4.js +8 -0
  23. package/dist/public/llms-template.txt +9 -6
  24. package/dist/public/showroom.html +104 -0
  25. package/dist/public/showroom.js +247 -0
  26. package/dist/public/spa.html +18 -16
  27. package/dist/public/views/aimeat-os.js +3 -2
  28. package/dist/public/views/landing.js +50 -6
  29. package/dist/public/views/profile/agents/agent-card.js +13 -4
  30. package/dist/public/views/profile/agents/state-detector.js +6 -0
  31. package/dist/public/views/profile/agents/tab-integration.js +11 -2
  32. package/dist/public/views/profile/apps-tab.js +46 -0
  33. package/dist/public/views/profile/extensions-tab.js +4 -0
  34. package/dist/public/views/profile/inbox-tab.js +1 -1
  35. package/dist/public/views/profile/landing-page.js +8 -3
  36. package/dist/public/views/profile/memory-tab.js +37 -6
  37. package/dist/public/views/profile/organisms/home.js +32 -0
  38. package/dist/public/views/profile/organisms/workspace-list.js +36 -5
  39. package/dist/public/views/profile/organisms/workspace.js +29 -4
  40. package/dist/public/views/profile/organisms-tab.js +61 -8
  41. package/dist/public/views/secretary/calendar.js +327 -0
  42. package/dist/public/views/secretary/cards-reach.js +16 -17
  43. package/dist/public/views/secretary/cards.js +217 -79
  44. package/dist/public/views/secretary/dashboard.js +617 -0
  45. package/dist/public/views/secretary/quality.js +156 -0
  46. package/dist/public/views/secretary/use-autonomy.js +17 -2
  47. package/dist/public/views/secretary/use-freshness.js +50 -0
  48. package/dist/public/views/secretary/use-intake.js +23 -4
  49. package/dist/public/views/secretary/use-layout.js +122 -0
  50. package/dist/public/views/secretary/use-quick-actions.js +98 -0
  51. package/dist/public/views/secretary/use-specialists.js +103 -0
  52. package/dist/public/views/secretary/use-triggers.js +159 -0
  53. package/dist/public/views/secretary/use-whats-next.js +340 -0
  54. package/dist/public/views/secretary/workflow-design.js +112 -0
  55. package/dist/public/views/secretary.js +836 -44
  56. package/dist/scripts/check-no-max-tokens.d.ts +2 -0
  57. package/dist/scripts/check-no-max-tokens.d.ts.map +1 -0
  58. package/dist/scripts/check-no-max-tokens.js +71 -0
  59. package/dist/scripts/check-no-max-tokens.js.map +1 -0
  60. package/dist/src/cli/connect/mcp/tools/organisms.d.ts +2 -0
  61. package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
  62. package/dist/src/cli/connect/mcp/tools/organisms.js +9 -1
  63. package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
  64. package/dist/src/cli/connect/onboarding-prompt.d.ts +3 -0
  65. package/dist/src/cli/connect/onboarding-prompt.d.ts.map +1 -1
  66. package/dist/src/cli/connect/onboarding-prompt.js +10 -3
  67. package/dist/src/cli/connect/onboarding-prompt.js.map +1 -1
  68. package/dist/src/data/aimeat-iam-package.d.ts +25 -0
  69. package/dist/src/data/aimeat-iam-package.d.ts.map +1 -0
  70. package/dist/src/data/aimeat-iam-package.js +312 -0
  71. package/dist/src/data/aimeat-iam-package.js.map +1 -0
  72. package/dist/src/data/aimeat-marketplace-package.d.ts +25 -0
  73. package/dist/src/data/aimeat-marketplace-package.d.ts.map +1 -0
  74. package/dist/src/data/aimeat-marketplace-package.js +231 -0
  75. package/dist/src/data/aimeat-marketplace-package.js.map +1 -0
  76. package/dist/src/data/app-templates.d.ts +48 -0
  77. package/dist/src/data/app-templates.d.ts.map +1 -0
  78. package/dist/src/data/app-templates.js +856 -0
  79. package/dist/src/data/app-templates.js.map +1 -0
  80. package/dist/src/data/example-packages.d.ts.map +1 -1
  81. package/dist/src/data/example-packages.js +3 -1
  82. package/dist/src/data/example-packages.js.map +1 -1
  83. package/dist/src/generated/api-types.d.ts +689 -7
  84. package/dist/src/generated/api-types.d.ts.map +1 -1
  85. package/dist/src/generated/prisma-postgres/edge.js +10 -3
  86. package/dist/src/generated/prisma-postgres/index-browser.js +7 -0
  87. package/dist/src/generated/prisma-postgres/index.d.ts +205 -2
  88. package/dist/src/generated/prisma-postgres/index.js +10 -3
  89. package/dist/src/generated/prisma-postgres/package.json +1 -1
  90. package/dist/src/generated/prisma-postgres/schema.prisma +23 -13
  91. package/dist/src/generated/prisma-postgres/wasm.js +10 -3
  92. package/dist/src/mcp/agent-onboarding.d.ts +4 -0
  93. package/dist/src/mcp/agent-onboarding.d.ts.map +1 -1
  94. package/dist/src/mcp/agent-onboarding.js +28 -2
  95. package/dist/src/mcp/agent-onboarding.js.map +1 -1
  96. package/dist/src/mcp/annotations.d.ts.map +1 -1
  97. package/dist/src/mcp/annotations.js +1 -0
  98. package/dist/src/mcp/annotations.js.map +1 -1
  99. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  100. package/dist/src/mcp/catalog/definitions.js +16 -1
  101. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  102. package/dist/src/mcp/catalog/surfaces.js +3 -3
  103. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  104. package/dist/src/mcp/index.d.ts +3 -0
  105. package/dist/src/mcp/index.d.ts.map +1 -1
  106. package/dist/src/mcp/index.js +11 -0
  107. package/dist/src/mcp/index.js.map +1 -1
  108. package/dist/src/mcp/organisms.d.ts.map +1 -1
  109. package/dist/src/mcp/organisms.js +48 -8
  110. package/dist/src/mcp/organisms.js.map +1 -1
  111. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  112. package/dist/src/mcp/workspaces.js +6 -4
  113. package/dist/src/mcp/workspaces.js.map +1 -1
  114. package/dist/src/models/agent-onboarding-schemas.d.ts +47 -0
  115. package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -1
  116. package/dist/src/models/agent-onboarding-schemas.js +117 -0
  117. package/dist/src/models/agent-onboarding-schemas.js.map +1 -1
  118. package/dist/src/models/offer-schemas.d.ts +8 -8
  119. package/dist/src/routes/agent-onboarding.d.ts +5 -0
  120. package/dist/src/routes/agent-onboarding.d.ts.map +1 -1
  121. package/dist/src/routes/agent-onboarding.js +21 -2
  122. package/dist/src/routes/agent-onboarding.js.map +1 -1
  123. package/dist/src/routes/ai.d.ts +8 -4
  124. package/dist/src/routes/ai.d.ts.map +1 -1
  125. package/dist/src/routes/ai.js +38 -14
  126. package/dist/src/routes/ai.js.map +1 -1
  127. package/dist/src/routes/app-grants.d.ts +2 -0
  128. package/dist/src/routes/app-grants.d.ts.map +1 -1
  129. package/dist/src/routes/app-grants.js +7 -1
  130. package/dist/src/routes/app-grants.js.map +1 -1
  131. package/dist/src/routes/app-templates.d.ts +16 -0
  132. package/dist/src/routes/app-templates.d.ts.map +1 -0
  133. package/dist/src/routes/app-templates.js +40 -0
  134. package/dist/src/routes/app-templates.js.map +1 -0
  135. package/dist/src/routes/apps.d.ts.map +1 -1
  136. package/dist/src/routes/apps.js +55 -5
  137. package/dist/src/routes/apps.js.map +1 -1
  138. package/dist/src/routes/auth.d.ts +3 -0
  139. package/dist/src/routes/auth.d.ts.map +1 -1
  140. package/dist/src/routes/auth.js +9 -4
  141. package/dist/src/routes/auth.js.map +1 -1
  142. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  143. package/dist/src/routes/bootstrap.js +1 -0
  144. package/dist/src/routes/bootstrap.js.map +1 -1
  145. package/dist/src/routes/generator.d.ts.map +1 -1
  146. package/dist/src/routes/generator.js +3 -2
  147. package/dist/src/routes/generator.js.map +1 -1
  148. package/dist/src/routes/lib-ai.d.ts +0 -1
  149. package/dist/src/routes/lib-ai.d.ts.map +1 -1
  150. package/dist/src/routes/lib-ai.js +13 -6
  151. package/dist/src/routes/lib-ai.js.map +1 -1
  152. package/dist/src/routes/lib-header.d.ts +2 -0
  153. package/dist/src/routes/lib-header.d.ts.map +1 -1
  154. package/dist/src/routes/lib-header.js +7 -16
  155. package/dist/src/routes/lib-header.js.map +1 -1
  156. package/dist/src/routes/lib-markdown.d.ts +18 -0
  157. package/dist/src/routes/lib-markdown.d.ts.map +1 -0
  158. package/dist/src/routes/lib-markdown.js +236 -0
  159. package/dist/src/routes/lib-markdown.js.map +1 -0
  160. package/dist/src/routes/libs.d.ts +22 -0
  161. package/dist/src/routes/libs.d.ts.map +1 -1
  162. package/dist/src/routes/libs.js +133 -10
  163. package/dist/src/routes/libs.js.map +1 -1
  164. package/dist/src/routes/memory.d.ts.map +1 -1
  165. package/dist/src/routes/memory.js +21 -2
  166. package/dist/src/routes/memory.js.map +1 -1
  167. package/dist/src/routes/openrouter.d.ts +4 -0
  168. package/dist/src/routes/openrouter.d.ts.map +1 -1
  169. package/dist/src/routes/openrouter.js +17 -0
  170. package/dist/src/routes/openrouter.js.map +1 -1
  171. package/dist/src/routes/organisms.d.ts.map +1 -1
  172. package/dist/src/routes/organisms.js +107 -7
  173. package/dist/src/routes/organisms.js.map +1 -1
  174. package/dist/src/routes/portal.d.ts +9 -0
  175. package/dist/src/routes/portal.d.ts.map +1 -1
  176. package/dist/src/routes/portal.js +1 -1
  177. package/dist/src/routes/portal.js.map +1 -1
  178. package/dist/src/routes/public-stats.d.ts +3 -0
  179. package/dist/src/routes/public-stats.d.ts.map +1 -1
  180. package/dist/src/routes/public-stats.js +9 -2
  181. package/dist/src/routes/public-stats.js.map +1 -1
  182. package/dist/src/routes/secretary.d.ts +34 -0
  183. package/dist/src/routes/secretary.d.ts.map +1 -0
  184. package/dist/src/routes/secretary.js +311 -0
  185. package/dist/src/routes/secretary.js.map +1 -0
  186. package/dist/src/routes/specialists.d.ts.map +1 -1
  187. package/dist/src/routes/specialists.js +21 -0
  188. package/dist/src/routes/specialists.js.map +1 -1
  189. package/dist/src/routes/subdomains.js +1 -1
  190. package/dist/src/routes/subdomains.js.map +1 -1
  191. package/dist/src/routes/workflows.d.ts +9 -3
  192. package/dist/src/routes/workflows.d.ts.map +1 -1
  193. package/dist/src/routes/workflows.js +9 -3
  194. package/dist/src/routes/workflows.js.map +1 -1
  195. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  196. package/dist/src/server-bootstrap/routes-loader.js +4 -0
  197. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  198. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  199. package/dist/src/server-bootstrap/service-init.js +7 -0
  200. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  201. package/dist/src/server-bootstrap/static-files.d.ts +4 -0
  202. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  203. package/dist/src/server-bootstrap/static-files.js +18 -0
  204. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  205. package/dist/src/services/archive.d.ts +74 -0
  206. package/dist/src/services/archive.d.ts.map +1 -0
  207. package/dist/src/services/archive.js +132 -0
  208. package/dist/src/services/archive.js.map +1 -0
  209. package/dist/src/services/cortex-seeder.d.ts +4 -1
  210. package/dist/src/services/cortex-seeder.d.ts.map +1 -1
  211. package/dist/src/services/cortex-seeder.js +25 -6
  212. package/dist/src/services/cortex-seeder.js.map +1 -1
  213. package/dist/src/services/directory.d.ts.map +1 -1
  214. package/dist/src/services/directory.js +3 -2
  215. package/dist/src/services/directory.js.map +1 -1
  216. package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
  217. package/dist/src/services/generator-prompt-seeds.js +5 -4
  218. package/dist/src/services/generator-prompt-seeds.js.map +1 -1
  219. package/dist/src/services/message-send.d.ts.map +1 -1
  220. package/dist/src/services/message-send.js +14 -0
  221. package/dist/src/services/message-send.js.map +1 -1
  222. package/dist/src/services/onboarding-guide.d.ts +53 -0
  223. package/dist/src/services/onboarding-guide.d.ts.map +1 -0
  224. package/dist/src/services/onboarding-guide.js +81 -0
  225. package/dist/src/services/onboarding-guide.js.map +1 -0
  226. package/dist/src/services/organism-search.d.ts +6 -2
  227. package/dist/src/services/organism-search.d.ts.map +1 -1
  228. package/dist/src/services/organism-search.js +2 -2
  229. package/dist/src/services/organism-search.js.map +1 -1
  230. package/dist/src/services/owner-memory.d.ts +1 -0
  231. package/dist/src/services/owner-memory.d.ts.map +1 -1
  232. package/dist/src/services/owner-memory.js.map +1 -1
  233. package/dist/src/services/owner-session.d.ts +1 -0
  234. package/dist/src/services/owner-session.d.ts.map +1 -1
  235. package/dist/src/services/owner-session.js +10 -2
  236. package/dist/src/services/owner-session.js.map +1 -1
  237. package/dist/src/services/package-seeder.d.ts +22 -0
  238. package/dist/src/services/package-seeder.d.ts.map +1 -0
  239. package/dist/src/services/package-seeder.js +30 -0
  240. package/dist/src/services/package-seeder.js.map +1 -0
  241. package/dist/src/services/presence.d.ts +3 -0
  242. package/dist/src/services/presence.d.ts.map +1 -1
  243. package/dist/src/services/presence.js +10 -0
  244. package/dist/src/services/presence.js.map +1 -1
  245. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  246. package/dist/src/services/prompt-defaults.js +8 -3
  247. package/dist/src/services/prompt-defaults.js.map +1 -1
  248. package/dist/src/services/scheduler.d.ts +50 -2
  249. package/dist/src/services/scheduler.d.ts.map +1 -1
  250. package/dist/src/services/scheduler.js +454 -26
  251. package/dist/src/services/scheduler.js.map +1 -1
  252. package/dist/src/services/secretary-authoring.d.ts +71 -0
  253. package/dist/src/services/secretary-authoring.d.ts.map +1 -0
  254. package/dist/src/services/secretary-authoring.js +336 -0
  255. package/dist/src/services/secretary-authoring.js.map +1 -0
  256. package/dist/src/services/secretary-clarify.d.ts +57 -0
  257. package/dist/src/services/secretary-clarify.d.ts.map +1 -0
  258. package/dist/src/services/secretary-clarify.js +104 -0
  259. package/dist/src/services/secretary-clarify.js.map +1 -0
  260. package/dist/src/services/secretary-feed.d.ts +11 -0
  261. package/dist/src/services/secretary-feed.d.ts.map +1 -0
  262. package/dist/src/services/secretary-feed.js +28 -0
  263. package/dist/src/services/secretary-feed.js.map +1 -0
  264. package/dist/src/services/secretary-tick.d.ts +243 -3
  265. package/dist/src/services/secretary-tick.d.ts.map +1 -1
  266. package/dist/src/services/secretary-tick.js +273 -3
  267. package/dist/src/services/secretary-tick.js.map +1 -1
  268. package/dist/src/services/secretary-workbrief.d.ts +144 -0
  269. package/dist/src/services/secretary-workbrief.d.ts.map +1 -0
  270. package/dist/src/services/secretary-workbrief.js +315 -0
  271. package/dist/src/services/secretary-workbrief.js.map +1 -0
  272. package/dist/src/services/secretary-worktier.d.ts +118 -0
  273. package/dist/src/services/secretary-worktier.d.ts.map +1 -0
  274. package/dist/src/services/secretary-worktier.js +157 -0
  275. package/dist/src/services/secretary-worktier.js.map +1 -0
  276. package/dist/src/services/skill-bundle/generator.d.ts +3 -0
  277. package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
  278. package/dist/src/services/skill-bundle/generator.js +19 -0
  279. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  280. package/dist/src/services/specialist-runner.d.ts +36 -0
  281. package/dist/src/services/specialist-runner.d.ts.map +1 -0
  282. package/dist/src/services/specialist-runner.js +91 -0
  283. package/dist/src/services/specialist-runner.js.map +1 -0
  284. package/dist/src/services/specialist.d.ts +9 -0
  285. package/dist/src/services/specialist.d.ts.map +1 -1
  286. package/dist/src/services/specialist.js +42 -0
  287. package/dist/src/services/specialist.js.map +1 -1
  288. package/dist/src/services/structure-overview.d.ts +13 -0
  289. package/dist/src/services/structure-overview.d.ts.map +1 -1
  290. package/dist/src/services/structure-overview.js +34 -11
  291. package/dist/src/services/structure-overview.js.map +1 -1
  292. package/dist/src/services/system-agent-responder.d.ts +36 -0
  293. package/dist/src/services/system-agent-responder.d.ts.map +1 -0
  294. package/dist/src/services/system-agent-responder.js +107 -0
  295. package/dist/src/services/system-agent-responder.js.map +1 -0
  296. package/dist/src/services/system-agent.d.ts +34 -1
  297. package/dist/src/services/system-agent.d.ts.map +1 -1
  298. package/dist/src/services/system-agent.js +73 -0
  299. package/dist/src/services/system-agent.js.map +1 -1
  300. package/dist/src/storage/interface.d.ts +33 -0
  301. package/dist/src/storage/interface.d.ts.map +1 -1
  302. package/dist/src/storage/interface.js.map +1 -1
  303. package/dist/src/storage/providers/mongodb/index.d.ts +37 -0
  304. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  305. package/dist/src/storage/providers/mongodb/index.js +132 -12
  306. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  307. package/dist/src/storage/providers/sqlite/index.d.ts +23 -1
  308. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  309. package/dist/src/storage/providers/sqlite/index.js +60 -8
  310. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  311. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +29 -1
  312. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  313. package/dist/src/storage/providers/sqlite/repos/memory.js +109 -10
  314. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  315. package/dist/src/storage/providers/sqlite/schema.d.ts +3 -0
  316. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  317. package/dist/src/storage/providers/sqlite/schema.js +64 -9
  318. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  319. package/dist/src/storage/repositories/app.repository.d.ts +15 -0
  320. package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
  321. package/dist/src/storage/repositories/memory.repository.d.ts +41 -1
  322. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  323. package/dist/src/storage/repositories/organism.repository.d.ts +2 -1
  324. package/dist/src/storage/repositories/organism.repository.d.ts.map +1 -1
  325. package/dist/static/app-catalog.html +469 -122
  326. package/package.json +2 -1
  327. package/prisma/schema.postgres.prisma +10 -0
  328. package/prisma/schema.prisma +12 -0
@@ -0,0 +1,856 @@
1
+ /**
2
+ * @file app-templates.ts
3
+ * @description Authoring-template registry — the "booster kit" data. Curated starting points
4
+ * the app-prompt builders (app-catalog + landing) inject so the AI copies from a model instead
5
+ * of building from scratch. Templates are DATA: adding one is a new entry here (+ its content),
6
+ * no code change. Served as JSON at GET /v1/app-templates and consumed by both prompt surfaces.
7
+ *
8
+ * kinds (layered — see docs/internal/authoring-templates/):
9
+ * - app-shell : a full app skeleton (boot + auth + layout + theme). Tiers T1/T2/T3.
10
+ * - component : a reusable block + its lib deps (future).
11
+ * - use-case : composes an app-shell + components (+ optional package) (future).
12
+ * @structure AppTemplate · getAppTemplates() · getAppTemplateIndex()
13
+ * @version-history
14
+ * v1.0.0 — 2026-06-26 — initial registry + first app-shell (T1 pure-client).
15
+ * v1.1.0 — 2026-06-26 — app-shells T2 (cortex) + T3 (extension).
16
+ * v1.2.0 — 2026-06-26 — component library (auth-gated, private-store, shared-feed, ai-action, data-table, settings, dated-archive).
17
+ * v1.3.0 — 2026-06-26 — components: image-upload, realtime-room, search, list+detail (for marketplace / realtime-social / homepage use-cases).
18
+ * v1.4.0 — 2026-06-26 — use-case templates (full working scaffolds) + composes field; first: realtime-social.
19
+ * v1.5.0 — 2026-06-26 — use-case: marketplace (anon browse + search + detail + post with image); fix image-upload URL.
20
+ * v1.6.0 — 2026-06-26 — component: markdown (AIMEAT.md.render); use-case: homepage (personal site — profile + markdown blog + images + AI stories).
21
+ * v1.7.0 — 2026-06-26 — plain-language app-shell titles (drop T1/T2/T3 jargon): "Standard app",
22
+ * "Data app", "Connected app" — what a non-technical user gets, not the architecture.
23
+ */
24
+ // ── T1 pure-client app shell ─────────────────────────────────────────
25
+ // Boot (auth + data), self-hosted Tailwind + daisyUI + theme bridge, a navbar with the login
26
+ // pill, a single content area, light/dark via data-theme, and private/shared data helpers with
27
+ // loading + error handling. Slots are marked {{LIKE_THIS}} for the AI to fill.
28
+ const SHELL_PURE_CLIENT = `<!DOCTYPE html>
29
+ <!-- AIMEAT App Manifest
30
+ name: {{app-name}}
31
+ version: 1.0.0
32
+ description: {{one-line description — REQUIRED for publishing}}
33
+ entry: index.html
34
+ -->
35
+ <html lang="en" data-theme="dark">
36
+ <head>
37
+ <meta charset="UTF-8" />
38
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
39
+ <title>{{App Title}}</title>
40
+ <!-- Self-hosted Tailwind v4 + daisyUI 5 + theme bridge (served by the node, not a CDN) -->
41
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
42
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
43
+ <script src="/lib/tailwindcss@4.js"></script>
44
+ </head>
45
+ <body class="bg-base-100 text-base-content min-h-screen flex flex-col">
46
+ <nav class="navbar bg-base-200 px-4 shadow-sm sticky top-0 z-50">
47
+ <div class="flex-1"><span class="text-lg font-bold">{{App Title}}</span></div>
48
+ <div class="flex-none"><span id="login"></span></div>
49
+ </nav>
50
+
51
+ <main id="app" class="flex-1 w-full max-w-3xl mx-auto p-4 flex flex-col gap-4">
52
+ <div id="status" class="alert">Loading…</div>
53
+ <!-- {{BUILD YOUR VIEWS HERE — cards/sections using daisyUI classes (card, btn-primary, input…)}} -->
54
+ </main>
55
+
56
+ <footer class="footer footer-center p-3 text-xs opacity-50">{{footer text}}</footer>
57
+
58
+ <script src="/v1/libs/aimeat-auth.js"></script>
59
+ <script src="/v1/libs/aimeat-data.js"></script>
60
+ <script>
61
+ var session = null;
62
+ function setStatus(msg, cls) { var e = document.getElementById('status'); e.className = 'alert ' + (cls || ''); e.textContent = msg; }
63
+
64
+ // PRIVATE data (only the logged-in owner can read it):
65
+ // await AIMEAT.data.set('{{app-name}}.key', value, { visibility: 'private' });
66
+ // var mine = await AIMEAT.data.get('{{app-name}}.key');
67
+ // SHARED/community data (everyone reads; each user writes their own key):
68
+ // await AIMEAT.data.set('{{app-name}}.shared.' + id, entry, { visibility: 'public' });
69
+ // var theirs = await AIMEAT.data.getPublic(ownerGaii, '{{app-name}}.shared.' + id);
70
+ // Always read back after a write to confirm it persisted; show loading + error states.
71
+
72
+ function boot(s) {
73
+ session = s;
74
+ setStatus('Ready.', 'alert-success');
75
+ // {{LOAD DATA + RENDER YOUR VIEWS — handle empty/loading/error states}}
76
+ }
77
+
78
+ var booted = false;
79
+ function tryBoot() { if (booted) return; var s = AIMEAT.auth.getSession && AIMEAT.auth.getSession(); if (s && s.jwt) { booted = true; boot(s); } }
80
+ AIMEAT.auth.mountLoginButton('#login', {
81
+ onLogin: function () { tryBoot(); },
82
+ onLogout: function () { booted = false; setStatus('Log in to continue.', 'alert-warning'); }
83
+ });
84
+ // App origin: the silent/grant login resolves async and may not call onLogin — poll getSession.
85
+ var _iv = setInterval(function () { tryBoot(); if (booted) clearInterval(_iv); }, 300);
86
+ tryBoot();
87
+ </script>
88
+ </body>
89
+ </html>`;
90
+ // ── T2 client + cortex UI libs ───────────────────────────────────────
91
+ // T1 base, plus the node's bundled cortex UI libraries (data tables, forms, layouts) for
92
+ // richer, structured UIs without hand-rolling components.
93
+ const SHELL_CORTEX = `<!DOCTYPE html>
94
+ <!-- AIMEAT App Manifest
95
+ name: {{app-name}}
96
+ version: 1.0.0
97
+ description: {{one-line description — REQUIRED for publishing}}
98
+ entry: index.html
99
+ -->
100
+ <html lang="en" data-theme="dark">
101
+ <head>
102
+ <meta charset="UTF-8" />
103
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
104
+ <title>{{App Title}}</title>
105
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
106
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
107
+ <script src="/lib/tailwindcss@4.js"></script>
108
+ </head>
109
+ <body class="bg-base-100 text-base-content min-h-screen flex flex-col">
110
+ <nav class="navbar bg-base-200 px-4 shadow-sm sticky top-0 z-50">
111
+ <div class="flex-1"><span class="text-lg font-bold">{{App Title}}</span></div>
112
+ <div class="flex-none"><span id="login"></span></div>
113
+ </nav>
114
+ <main id="app" class="flex-1 w-full max-w-4xl mx-auto p-4 flex flex-col gap-4">
115
+ <div id="status" class="alert">Loading…</div>
116
+ <div id="view"></div>
117
+ </main>
118
+
119
+ <script src="/v1/libs/aimeat-auth.js"></script>
120
+ <script src="/v1/libs/aimeat-data.js"></script>
121
+ <!-- Bundled cortex UI libraries (node-level — available on every AIMEAT node). Load only what you use. -->
122
+ <script src="/v1/cortex/aimeat-ui-viewers/libs/aimeat-ui-viewers.js"></script>
123
+ <script src="/v1/cortex/aimeat-ui-forms/libs/aimeat-ui-forms.js"></script>
124
+ <!-- Also available: aimeat-ui-layout, aimeat-ui-nav, aimeat-ui-dialogs, aimeat-charts, aimeat-canvas -->
125
+ <script>
126
+ var session = null;
127
+ function setStatus(m, c) { var e = document.getElementById('status'); e.className = 'alert ' + (c || ''); e.textContent = m; }
128
+ function boot(s) {
129
+ session = s;
130
+ setStatus('Ready.', 'alert-success');
131
+ // Example — render structured data with the viewers cortex (replace with your data):
132
+ // AIMEAT.ui.viewers.DataTable({ target: document.getElementById('view'),
133
+ // columns: [{key:'name',label:'Name'}], rows: [{name:'…'}], sortable:true, filterable:true });
134
+ // Forms via AIMEAT.ui.forms.FormGroup({ target, fields:[…], onSubmit }).
135
+ // {{BUILD YOUR VIEWS — load data from AIMEAT.data, render with the cortex libs}}
136
+ }
137
+ var booted = false;
138
+ function tryBoot() { if (booted) return; var s = AIMEAT.auth.getSession && AIMEAT.auth.getSession(); if (s && s.jwt) { booted = true; boot(s); } }
139
+ AIMEAT.auth.mountLoginButton('#login', { onLogin: function () { tryBoot(); }, onLogout: function () { booted = false; setStatus('Log in to continue.', 'alert-warning'); } });
140
+ var _iv = setInterval(function () { tryBoot(); if (booted) clearInterval(_iv); }, 300);
141
+ tryBoot();
142
+ </script>
143
+ </body>
144
+ </html>`;
145
+ // ── T3 client + server extension ─────────────────────────────────────
146
+ // For apps that need SERVER-SIDE work (fetch an external API, scheduled jobs). The app is the
147
+ // client; a sandboxed extension does the server work and the app calls its actions. Build the
148
+ // extension separately (Profile → Cortex/Extensions → Create with AI) or ship it in a package.
149
+ const SHELL_EXTENSION = `<!DOCTYPE html>
150
+ <!-- AIMEAT App Manifest
151
+ name: {{app-name}}
152
+ version: 1.0.0
153
+ description: {{one-line description — REQUIRED for publishing}}
154
+ entry: index.html
155
+ -->
156
+ <html lang="en" data-theme="dark">
157
+ <head>
158
+ <meta charset="UTF-8" />
159
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
160
+ <title>{{App Title}}</title>
161
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
162
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
163
+ <script src="/lib/tailwindcss@4.js"></script>
164
+ </head>
165
+ <body class="bg-base-100 text-base-content min-h-screen flex flex-col">
166
+ <nav class="navbar bg-base-200 px-4 shadow-sm sticky top-0 z-50">
167
+ <div class="flex-1"><span class="text-lg font-bold">{{App Title}}</span></div>
168
+ <div class="flex-none"><span id="login"></span></div>
169
+ </nav>
170
+ <main id="app" class="flex-1 w-full max-w-3xl mx-auto p-4 flex flex-col gap-4">
171
+ <div id="status" class="alert">Loading…</div>
172
+ <div id="view"></div>
173
+ </main>
174
+
175
+ <script src="/v1/libs/aimeat-auth.js"></script>
176
+ <script src="/v1/libs/aimeat-data.js"></script>
177
+ <script>
178
+ // SERVER-SIDE work lives in an extension. Build "{{extension-name}}" with actions like
179
+ // "{{action}}" (it does the external fetch / cron and writes to memory), then call it here.
180
+ // The extension enforces auth + does CORS-free server-to-server fetches the browser can't.
181
+ var EXT = '/v1/ext/{{extension-name}}';
182
+ var session = null;
183
+ function setStatus(m, c) { var e = document.getElementById('status'); e.className = 'alert ' + (c || ''); e.textContent = m; }
184
+ function callExt(action, body) {
185
+ return session.fetch(EXT + '/' + action, { method: 'POST', body: JSON.stringify(body || {}) });
186
+ }
187
+ function boot(s) {
188
+ session = s;
189
+ setStatus('Ready.', 'alert-success');
190
+ // Example: var r = await callExt('{{action}}', { /* input */ }); render(r.data);
191
+ // {{CALL YOUR EXTENSION ACTIONS + RENDER — handle loading/empty/error}}
192
+ }
193
+ var booted = false;
194
+ function tryBoot() { if (booted) return; var s = AIMEAT.auth.getSession && AIMEAT.auth.getSession(); if (s && s.jwt) { booted = true; boot(s); } }
195
+ AIMEAT.auth.mountLoginButton('#login', { onLogin: function () { tryBoot(); }, onLogout: function () { booted = false; setStatus('Log in to continue.', 'alert-warning'); } });
196
+ var _iv = setInterval(function () { tryBoot(); if (booted) clearInterval(_iv); }, 300);
197
+ tryBoot();
198
+ </script>
199
+ </body>
200
+ </html>`;
201
+ // ── Component templates ──────────────────────────────────────────────
202
+ // Reusable blocks (snippets + their lib deps) the AI drops into an app-shell. Not full pages —
203
+ // patterns to copy. {{app}} = the app's memory namespace.
204
+ const COMP_AUTH_GATED = `// auth-gated section — show content only to logged-in users (aimeat-auth).
205
+ var gate = document.getElementById('members-only');
206
+ function applyAuth(s) { if (gate) gate.style.display = (s && s.jwt) ? '' : 'none'; }
207
+ AIMEAT.auth.mountLoginButton('#login', {
208
+ onLogin: function () { applyAuth(AIMEAT.auth.getSession()); },
209
+ onLogout: function () { applyAuth(null); }
210
+ });
211
+ applyAuth(AIMEAT.auth.getSession());`;
212
+ const COMP_PRIVATE_STORE = `// private-store — a per-owner private collection (aimeat-data). Only the owner can read it.
213
+ async function listItems() { return (await AIMEAT.data.get('{{app}}.items')) || []; }
214
+ async function addItem(item) {
215
+ var items = await listItems();
216
+ items.push(Object.assign({ id: Date.now() + '', createdAt: new Date().toISOString() }, item));
217
+ await AIMEAT.data.set('{{app}}.items', items, { visibility: 'private' });
218
+ return items;
219
+ }
220
+ async function removeItem(id) {
221
+ var items = (await listItems()).filter(function (x) { return x.id !== id; });
222
+ await AIMEAT.data.set('{{app}}.items', items, { visibility: 'private' });
223
+ return items;
224
+ }`;
225
+ const COMP_SHARED_FEED = `// shared-feed — a public community feed (aimeat-data). Each user writes their OWN key; everyone reads.
226
+ async function post(text) {
227
+ var id = Date.now() + '-' + Math.random().toString(36).slice(2, 7);
228
+ await AIMEAT.data.set('{{app}}.feed.' + id,
229
+ { id: id, text: text, by: AIMEAT.auth.getSession().owner, at: new Date().toISOString() },
230
+ { visibility: 'public' });
231
+ }
232
+ async function loadFeed() {
233
+ var results = await AIMEAT.data.search('{{app}}.feed.'); // public entries across all users
234
+ return results.sort(function (a, b) { return (b.at || '').localeCompare(a.at || ''); });
235
+ }`;
236
+ const COMP_AI_ACTION = `// ai-action — run the user's own LLM on demand (aimeat-ai; load it + aimeat-auth).
237
+ async function aiSuggest(promptText, outEl) {
238
+ if (!(await AIMEAT.ai.isAvailable())) { outEl.value = 'Log in and add an OpenRouter key to enable AI.'; return; }
239
+ try { var r = await AIMEAT.ai.complete({ app_id: '{{app}}', prompt: promptText }); outEl.value = r.content; }
240
+ catch (e) { outEl.value = 'AI error: ' + (e.message || e); }
241
+ }
242
+ // Render into an EDITABLE field so the user reviews before saving. Gate the button on isAvailable().`;
243
+ const COMP_DATA_TABLE = `// data-table — sortable / filterable / paginated table (aimeat-ui-viewers cortex).
244
+ // Load: <script src="/v1/cortex/aimeat-ui-viewers/libs/aimeat-ui-viewers.js"></script>
245
+ AIMEAT.ui.viewers.DataTable({
246
+ target: document.getElementById('table'),
247
+ sortable: true, filterable: true, pageSize: 10,
248
+ columns: [{ key: 'name', label: 'Name' }, { key: 'value', label: 'Value' }],
249
+ rows: yourRows // [{ name: '…', value: … }]
250
+ });`;
251
+ const COMP_SETTINGS = `// settings-panel — read/write the app's settings (aimeat-data).
252
+ async function getSettings() { return (await AIMEAT.data.get('{{app}}.settings')) || { /* defaults */ }; }
253
+ async function saveSettings(patch) {
254
+ var s = Object.assign(await getSettings(), patch);
255
+ await AIMEAT.data.set('{{app}}.settings', s, { visibility: 'private' });
256
+ return s;
257
+ }
258
+ // Bind toggles/selects to saveSettings({ key: value }); re-read on load to populate the form.`;
259
+ const COMP_DATED_ARCHIVE = `// dated-archive — show entries by date, newest first (aimeat-data). Keys like {{app}}.YYYY-MM-DD.*
260
+ async function loadArchive() {
261
+ var entries = await AIMEAT.data.search('{{app}}.'); // matching entries
262
+ var byDay = {};
263
+ entries.forEach(function (e) { var d = (e.date || (e.key || '').split('.')[1] || ''); (byDay[d] = byDay[d] || []).push(e); });
264
+ return Object.keys(byDay).sort().reverse().map(function (d) { return { date: d, items: byDay[d] }; });
265
+ }`;
266
+ const COMP_IMAGE_UPLOAD = `// image-upload — upload an image and get an anon-visible URL (aimeat-storage + aimeat-auth).
267
+ // Load: <script src="/v1/libs/aimeat-storage.js"></script>
268
+ async function uploadImage(file) {
269
+ var up = await AIMEAT.storage.upload(file, { visibility: 'public' }); // -> { key, ... }
270
+ var ghii = AIMEAT.auth.getSession().ghii; // owner@node-id
271
+ return '/v1/pub/' + encodeURIComponent(ghii) + '/' + encodeURIComponent(up.key); // public files load for anon
272
+ }
273
+ // <input type="file" accept="image/*" onchange="uploadImage(this.files[0]).then(setImageUrl)">`;
274
+ const COMP_REALTIME_ROOM = `// realtime-room — live presence + messages over a shared room (no backend to run).
275
+ async function joinRoom(name, onMessage) {
276
+ var room = (await session.fetch('/v1/realtime/rooms', { method: 'POST', body: JSON.stringify({ name: name }) })).data; // { id, ws_url }
277
+ var ws = new WebSocket(location.origin.replace(/^http/, 'ws') + room.ws_url);
278
+ ws.onmessage = function (e) { onMessage(JSON.parse(e.data)); };
279
+ return { send: function (msg) { ws.send(JSON.stringify(msg)); }, ws: ws };
280
+ }
281
+ // var room = await joinRoom('lobby', function (m) { /* render presence / messages */ });
282
+ // room.send({ type: 'chat', text: '…' }); // for low-latency P2P use AimeatRealtime (/lib/realtime.js)`;
283
+ const COMP_SEARCH = `// search — instant client-side filter, or server-side memory search.
284
+ function filterItems(items, q) {
285
+ q = (q || '').toLowerCase().trim();
286
+ if (!q) return items;
287
+ return items.filter(function (it) { return JSON.stringify(it).toLowerCase().indexOf(q) !== -1; });
288
+ }
289
+ // Bind: input.addEventListener('input', function () { render(filterItems(all, input.value)); });
290
+ // Server-side across stored entries (aimeat-data): var hits = await AIMEAT.data.search('{{app}}.' + query);`;
291
+ const COMP_LIST_DETAIL = `// list+detail — master/detail: a list on the left, the selected item's detail on the right.
292
+ function renderListDetail(target, items, rowLabel, renderDetail) {
293
+ target.innerHTML = '<div class="flex gap-4"><div id="ld-list" class="w-1/3 flex flex-col gap-1"></div><div id="ld-detail" class="flex-1"></div></div>';
294
+ var listEl = target.querySelector('#ld-list'), detailEl = target.querySelector('#ld-detail');
295
+ items.forEach(function (it) {
296
+ var row = document.createElement('button');
297
+ row.className = 'btn-ghost text-left px-3 py-2 rounded';
298
+ row.textContent = rowLabel(it);
299
+ row.onclick = function () { detailEl.innerHTML = ''; detailEl.appendChild(renderDetail(it)); };
300
+ listEl.appendChild(row);
301
+ });
302
+ }
303
+ // Or use the cortex: AIMEAT.ui.layout.MainDetail({ target, list, detail }).`;
304
+ // ── Use-case: realtime-social (full working scaffold) ────────────────
305
+ // A live room: logged-in users join the realtime room for live presence + chat, with durable
306
+ // history persisted to public memory (loaded after login; anon sees the login prompt). Composes
307
+ // realtime-room + shared-feed + auth-gated. Models the proven Presence Board pattern.
308
+ // {{app}} = memory namespace; customise the {{SLOTS}}.
309
+ const USECASE_REALTIME_SOCIAL = `<!DOCTYPE html>
310
+ <!-- AIMEAT App Manifest
311
+ name: {{app-name}}
312
+ version: 1.0.0
313
+ description: {{one-line description — REQUIRED for publishing}}
314
+ entry: index.html
315
+ -->
316
+ <html lang="en" data-theme="dark">
317
+ <head>
318
+ <meta charset="UTF-8" />
319
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
320
+ <title>{{App Title}}</title>
321
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
322
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
323
+ <script src="/lib/tailwindcss@4.js"></script>
324
+ </head>
325
+ <body class="bg-base-100 text-base-content min-h-screen flex flex-col">
326
+ <nav class="navbar bg-base-200 px-4 shadow-sm sticky top-0 z-50">
327
+ <div class="flex-1"><span class="text-lg font-bold">{{App Title}}</span></div>
328
+ <div class="flex-none"><span id="login"></span></div>
329
+ </nav>
330
+
331
+ <main id="app" class="flex-1 w-full max-w-2xl mx-auto p-4 flex flex-col gap-4">
332
+ <div class="flex items-center gap-2 flex-wrap">
333
+ <span class="text-sm opacity-70">Online:</span>
334
+ <span id="presence" class="flex gap-1 flex-wrap"></span>
335
+ </div>
336
+ <div id="feed" class="flex-1 flex flex-col gap-2 overflow-y-auto" style="min-height:50vh"></div>
337
+ <form id="composer" class="join w-full" hidden>
338
+ <input id="msg" class="input input-bordered join-item flex-1" placeholder="Say something…" autocomplete="off" />
339
+ <button class="btn-primary join-item px-6" type="submit">Send</button>
340
+ </form>
341
+ <div id="login-hint" class="alert">Log in to join the live room and post.</div>
342
+ </main>
343
+
344
+ <script src="/v1/libs/aimeat-auth.js"></script>
345
+ <script src="/v1/libs/aimeat-data.js"></script>
346
+ <script>
347
+ // Realtime protocol (server-defined): connect ws with ?room=&token=&nick=, send { type:'chat',
348
+ // payload }, receive { type:'chat', sender, payload } broadcast to all (incl. sender). Presence
349
+ // via { type:'joined', peers:[{nick}] } + { type:'participant', action:'join'|'leave', name }.
350
+ var FEED_KEY = '{{app}}.feed.'; // durable history (loaded after login — memory search needs auth)
351
+ var ROOM = '{{app}}-lobby';
352
+ var session = null, ws = null, me = null;
353
+ var online = {}; // name -> true
354
+ var feedEl = document.getElementById('feed');
355
+ var presEl = document.getElementById('presence');
356
+
357
+ function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; }
358
+ function addMessage(by, text) {
359
+ var row = document.createElement('div');
360
+ row.className = 'chat ' + (by === me ? 'chat-end' : 'chat-start');
361
+ row.innerHTML = '<div class="chat-header text-xs opacity-60">' + esc(by) + '</div>' +
362
+ '<div class="chat-bubble">' + esc(text) + '</div>';
363
+ feedEl.appendChild(row);
364
+ feedEl.scrollTop = feedEl.scrollHeight;
365
+ }
366
+ function renderPresence() {
367
+ var names = Object.keys(online);
368
+ presEl.innerHTML = names.length ? names.map(function (n) { return '<span class="badge badge-success badge-sm">' + esc(n) + '</span>'; }).join('') : '<span class="opacity-50 text-sm">—</span>';
369
+ }
370
+
371
+ async function loadHistory() {
372
+ try {
373
+ var hits = await AIMEAT.data.search(FEED_KEY); // search needs auth -> history loads after login
374
+ hits.sort(function (a, b) { return (a.at || '').localeCompare(b.at || ''); });
375
+ feedEl.innerHTML = '';
376
+ hits.forEach(function (m) { addMessage(m.by, m.text); });
377
+ } catch (e) { /* empty feed is fine */ }
378
+ }
379
+
380
+ async function joinLive(s) {
381
+ session = s; me = s.owner;
382
+ document.getElementById('login-hint').style.display = 'none';
383
+ document.getElementById('composer').hidden = false;
384
+ loadHistory(); // durable history (needs auth)
385
+ var room = (await session.fetch('/v1/realtime/rooms', { method: 'POST', body: JSON.stringify({ name: ROOM }) })).data;
386
+ // ws_url already has ?room=ID; the WebSocket can't set headers, so pass the JWT + nick as query.
387
+ var wsUrl = location.origin.replace(/^http/, 'ws') + room.ws_url + '&token=' + encodeURIComponent(s.jwt) + '&nick=' + encodeURIComponent(me);
388
+ ws = new WebSocket(wsUrl);
389
+ ws.onmessage = function (e) {
390
+ var d = JSON.parse(e.data);
391
+ if (d.type === 'joined') { (d.peers || []).forEach(function (p) { online[p.nick] = true; }); online[me] = true; renderPresence(); }
392
+ else if (d.type === 'participant') { if (d.action === 'join') online[d.name] = true; else if (d.action === 'leave') delete online[d.name]; renderPresence(); }
393
+ else if (d.type === 'chat') { addMessage(d.sender, d.payload); } // server echoes to all incl. sender
394
+ };
395
+ ws.onclose = function () { online = {}; renderPresence(); };
396
+ }
397
+
398
+ document.getElementById('composer').addEventListener('submit', function (ev) {
399
+ ev.preventDefault();
400
+ var input = document.getElementById('msg'), text = input.value.trim();
401
+ if (!text || !ws || ws.readyState !== 1) return;
402
+ input.value = '';
403
+ ws.send(JSON.stringify({ type: 'chat', payload: text })); // live — server echoes back, then we render
404
+ var id = Date.now() + '-' + Math.random().toString(36).slice(2, 7);
405
+ AIMEAT.data.set(FEED_KEY + id, { by: me, text: text, at: new Date().toISOString() }, { visibility: 'public' }); // durable
406
+ });
407
+
408
+ AIMEAT.auth.mountLoginButton('#login', {
409
+ onLogin: function () { joinLive(AIMEAT.auth.getSession()); },
410
+ onLogout: function () { location.reload(); }
411
+ });
412
+ var s0 = AIMEAT.auth.getSession && AIMEAT.auth.getSession();
413
+ if (s0 && s0.jwt) joinLive(s0); // history + live load after login (anon sees the login prompt)
414
+ </script>
415
+ </body>
416
+ </html>`;
417
+ // ── Use-case: marketplace — single-seller storefront (full working scaffold) ─────────────────
418
+ // Anyone browses + searches the seller's public listings and opens a detail view; the SELLER
419
+ // (OWNER) posts listings with an uploaded image. All listings live in ONE public key
420
+ // ({{app}}.listings) under the owner's GHII, so anon can read them via getPublic (the only
421
+ // anon-accessible memory read — there is no anon search). For a MULTI-seller marketplace use a
422
+ // server extension for the shared store. Composes list+detail + image-upload + search +
423
+ // auth-gated. Models the Sales-Board / owner-curated storefront. {{app}} = memory namespace.
424
+ const USECASE_MARKETPLACE = `<!DOCTYPE html>
425
+ <!-- AIMEAT App Manifest
426
+ name: {{app-name}}
427
+ version: 1.0.0
428
+ description: {{one-line description — REQUIRED for publishing}}
429
+ entry: index.html
430
+ -->
431
+ <html lang="en" data-theme="dark">
432
+ <head>
433
+ <meta charset="UTF-8" />
434
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
435
+ <title>{{App Title}}</title>
436
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
437
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
438
+ <script src="/lib/tailwindcss@4.js"></script>
439
+ </head>
440
+ <body class="bg-base-100 text-base-content min-h-screen flex flex-col">
441
+ <nav class="navbar bg-base-200 px-4 shadow-sm sticky top-0 z-50 gap-2">
442
+ <div class="flex-1 min-w-0"><span class="text-lg font-bold">{{App Title}}</span></div>
443
+ <input id="search" class="input input-bordered input-sm w-40 sm:w-64" placeholder="Search…" />
444
+ <button id="post-btn" class="btn-primary px-4" hidden>+ Post</button>
445
+ <span id="login"></span>
446
+ </nav>
447
+
448
+ <main class="flex-1 w-full max-w-5xl mx-auto p-4">
449
+ <div id="status" class="alert mb-4" hidden></div>
450
+ <div id="grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"></div>
451
+ <div id="empty" class="text-center opacity-60 py-16" hidden>No listings yet.</div>
452
+ </main>
453
+
454
+ <!-- Detail overlay -->
455
+ <div id="detail" class="fixed inset-0 bg-black/60 z-50 hidden items-center justify-center p-4" style="display:none">
456
+ <div class="card bg-base-200 max-w-lg w-full max-h-[90vh] overflow-y-auto">
457
+ <div id="detail-body" class="card-body"></div>
458
+ </div>
459
+ </div>
460
+
461
+ <!-- Post overlay (logged-in) -->
462
+ <div id="post" class="fixed inset-0 bg-black/60 z-50 hidden items-center justify-center p-4" style="display:none">
463
+ <form id="post-form" class="card bg-base-200 max-w-md w-full">
464
+ <div class="card-body gap-3">
465
+ <h3 class="text-lg font-bold">Post a listing</h3>
466
+ <input id="f-title" class="input input-bordered" placeholder="Title" required />
467
+ <input id="f-price" class="input input-bordered" placeholder="Price (e.g. 20 €)" />
468
+ <textarea id="f-desc" class="textarea textarea-bordered" placeholder="Description" rows="3"></textarea>
469
+ <input id="f-image" type="file" accept="image/*" class="file-input file-input-bordered" />
470
+ <div class="flex gap-2 justify-end">
471
+ <button type="button" id="post-cancel" class="btn-ghost px-4">Cancel</button>
472
+ <button type="submit" id="post-submit" class="btn-primary px-6">Publish</button>
473
+ </div>
474
+ </div>
475
+ </form>
476
+ </div>
477
+
478
+ <script src="/v1/libs/aimeat-auth.js"></script>
479
+ <script src="/v1/libs/aimeat-data.js"></script>
480
+ <script src="/v1/libs/aimeat-storage.js"></script>
481
+ <script>
482
+ var OWNER = '{{owner-ghii}}'; // the seller's GHII (owner@node-id) — anyone reads listings from here
483
+ var LISTINGS_KEY = '{{app}}.listings';
484
+ var session = null, all = [];
485
+ var gridEl = document.getElementById('grid'), emptyEl = document.getElementById('empty');
486
+
487
+ function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; }
488
+ function filtered() {
489
+ var q = document.getElementById('search').value.toLowerCase().trim();
490
+ if (!q) return all;
491
+ return all.filter(function (it) { return ((it.title || '') + ' ' + (it.desc || '')).toLowerCase().indexOf(q) !== -1; });
492
+ }
493
+ function render() {
494
+ var items = filtered();
495
+ emptyEl.hidden = items.length > 0;
496
+ gridEl.innerHTML = '';
497
+ items.forEach(function (it) {
498
+ var card = document.createElement('div');
499
+ card.className = 'card bg-base-200 shadow cursor-pointer hover:ring-2 hover:ring-primary transition';
500
+ card.innerHTML =
501
+ (it.imageUrl ? '<figure class="aspect-square overflow-hidden"><img src="' + esc(it.imageUrl) + '" class="w-full h-full object-cover" /></figure>'
502
+ : '<figure class="aspect-square bg-base-300 flex items-center justify-center text-4xl opacity-40">🖼️</figure>') +
503
+ '<div class="card-body p-3 gap-1"><h3 class="font-semibold text-sm truncate">' + esc(it.title) + '</h3>' +
504
+ '<div class="text-primary font-bold text-sm">' + esc(it.price || '') + '</div></div>';
505
+ card.onclick = function () { openDetail(it); };
506
+ gridEl.appendChild(card);
507
+ });
508
+ }
509
+ function openDetail(it) {
510
+ document.getElementById('detail-body').innerHTML =
511
+ (it.imageUrl ? '<img src="' + esc(it.imageUrl) + '" class="rounded-lg w-full object-contain max-h-80 mb-3" />' : '') +
512
+ '<h2 class="text-xl font-bold">' + esc(it.title) + '</h2>' +
513
+ '<div class="text-primary font-bold text-lg my-1">' + esc(it.price || '') + '</div>' +
514
+ '<p class="whitespace-pre-wrap opacity-90">' + esc(it.desc || '') + '</p>' +
515
+ '<div class="text-xs opacity-60 mt-3">Seller: ' + esc(it.by || '') + '</div>' +
516
+ '<div class="text-right mt-4"><button class="btn-ghost px-4" onclick="closeDetail()">Close</button></div>';
517
+ show('detail');
518
+ }
519
+ window.closeDetail = function () { hide('detail'); };
520
+ function show(id) { var e = document.getElementById(id); e.style.display = 'flex'; }
521
+ function hide(id) { var e = document.getElementById(id); e.style.display = 'none'; }
522
+
523
+ async function load() {
524
+ try {
525
+ var arr = await AIMEAT.data.getPublic(OWNER, LISTINGS_KEY); // anon-readable single public key
526
+ all = Array.isArray(arr) ? arr.slice() : [];
527
+ all.sort(function (a, b) { return (b.at || '').localeCompare(a.at || ''); });
528
+ } catch (e) { all = []; }
529
+ render();
530
+ }
531
+ document.getElementById('search').addEventListener('input', render);
532
+
533
+ // ── posting (only the seller = OWNER can post; writes go to OWNER's own public key) ──
534
+ function maybeEnablePosting(s) {
535
+ session = s;
536
+ if (OWNER.indexOf('{{') === 0 && s.ghii) OWNER = s.ghii; // owner-preview convenience before the GHII is baked in
537
+ if (s.ghii === OWNER) document.getElementById('post-btn').hidden = false;
538
+ }
539
+ document.getElementById('post-btn').onclick = function () { show('post'); };
540
+ document.getElementById('post-cancel').onclick = function () { hide('post'); };
541
+ document.getElementById('post-form').addEventListener('submit', async function (ev) {
542
+ ev.preventDefault();
543
+ var btn = document.getElementById('post-submit'); btn.disabled = true; btn.textContent = 'Publishing…';
544
+ try {
545
+ var imageUrl = '';
546
+ var fileInput = document.getElementById('f-image');
547
+ if (fileInput.files[0]) {
548
+ var up = await AIMEAT.storage.upload(fileInput.files[0], { visibility: 'public' });
549
+ imageUrl = '/v1/pub/' + encodeURIComponent(session.ghii) + '/' + encodeURIComponent(up.key);
550
+ }
551
+ var listing = {
552
+ id: Date.now() + '-' + Math.random().toString(36).slice(2, 7),
553
+ title: document.getElementById('f-title').value.trim(),
554
+ price: document.getElementById('f-price').value.trim(),
555
+ desc: document.getElementById('f-desc').value.trim(),
556
+ imageUrl: imageUrl, by: session.ghii, at: new Date().toISOString()
557
+ };
558
+ all.unshift(listing);
559
+ await AIMEAT.data.set(LISTINGS_KEY, all, { visibility: 'public' }); // one public key holds all listings
560
+ document.getElementById('post-form').reset();
561
+ hide('post'); render();
562
+ } catch (e) { alert('Could not publish: ' + (e.message || e)); }
563
+ btn.disabled = false; btn.textContent = 'Publish';
564
+ });
565
+
566
+ AIMEAT.auth.mountLoginButton('#login', {
567
+ onLogin: function () { maybeEnablePosting(AIMEAT.auth.getSession()); load(); },
568
+ onLogout: function () { location.reload(); }
569
+ });
570
+ var s0 = AIMEAT.auth.getSession && AIMEAT.auth.getSession();
571
+ if (s0 && s0.jwt) maybeEnablePosting(s0); // sets OWNER for the owner's own preview before load
572
+ load(); // everyone browses
573
+ </script>
574
+ </body>
575
+ </html>`;
576
+ const COMP_MARKDOWN = `// markdown — render safe GFM markdown (AI stories, blog posts) to styled HTML (aimeat-markdown).
577
+ // Load: <script src="/v1/libs/aimeat-markdown.js"></script>
578
+ AIMEAT.md.render(markdownString, '#target'); // replaces #target content with a rendered .md-body
579
+ // Or get a node: var node = AIMEAT.md.render(text); someEl.appendChild(node);
580
+ // XSS-safe for LLM-authored text (no innerHTML; hrefs/imgs sanitized). Pairs well with aimeat-ai output.`;
581
+ // ── Use-case: homepage / personal site (full working scaffold) ───────
582
+ // A single-writer public site: anyone views the owner's profile + blog/feed; the OWNER edits the
583
+ // profile and publishes posts (markdown body, optional image, optional AI-written draft). Profile
584
+ // + posts live in public keys read via getPublic (anon-readable). Composes markdown + image-upload
585
+ // + ai-action + auth-gated. {{app}} = memory namespace; {{owner-ghii}} = the owner's GHII.
586
+ const USECASE_HOMEPAGE = `<!DOCTYPE html>
587
+ <!-- AIMEAT App Manifest
588
+ name: {{app-name}}
589
+ version: 1.0.0
590
+ description: {{one-line description — REQUIRED for publishing}}
591
+ entry: index.html
592
+ -->
593
+ <html lang="en" data-theme="dark">
594
+ <head>
595
+ <meta charset="UTF-8" />
596
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
597
+ <meta name="aimeat-scopes" content="ai:use" /> <!-- lets the owner's "Write with AI" button spend their AI budget -->
598
+ <title>{{App Title}}</title>
599
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
600
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
601
+ <script src="/lib/tailwindcss@4.js"></script>
602
+ </head>
603
+ <body class="bg-base-100 text-base-content min-h-screen flex flex-col">
604
+ <nav class="navbar bg-base-200 px-4 shadow-sm sticky top-0 z-50 gap-2">
605
+ <div class="flex-1 min-w-0"><span class="text-lg font-bold">{{App Title}}</span></div>
606
+ <span id="owner-actions" hidden class="flex gap-2">
607
+ <button id="edit-btn" class="btn-outline px-3">Edit profile</button>
608
+ <button id="post-btn" class="btn-primary px-3">+ Post</button>
609
+ </span>
610
+ <span id="login"></span>
611
+ </nav>
612
+
613
+ <main class="flex-1 w-full max-w-3xl mx-auto p-4 flex flex-col gap-8">
614
+ <section id="hero" class="text-center pt-4"></section>
615
+ <section id="posts" class="flex flex-col gap-6"></section>
616
+ </main>
617
+
618
+ <!-- Profile edit overlay (owner) -->
619
+ <div id="profile-modal" class="fixed inset-0 bg-black/60 z-50 hidden items-center justify-center p-4" style="display:none">
620
+ <form id="profile-form" class="card bg-base-200 max-w-md w-full">
621
+ <div class="card-body gap-3">
622
+ <h3 class="text-lg font-bold">Edit profile</h3>
623
+ <input id="p-name" class="input input-bordered" placeholder="Your name" />
624
+ <textarea id="p-bio" class="textarea textarea-bordered" placeholder="Bio (markdown supported)" rows="4"></textarea>
625
+ <input id="p-avatar" type="file" accept="image/*" class="file-input file-input-bordered" />
626
+ <div class="flex gap-2 justify-end">
627
+ <button type="button" class="btn-ghost px-4" onclick="document.getElementById('profile-modal').style.display='none'">Cancel</button>
628
+ <button type="submit" class="btn-primary px-6">Save</button>
629
+ </div>
630
+ </div>
631
+ </form>
632
+ </div>
633
+
634
+ <!-- New post overlay (owner) -->
635
+ <div id="post-modal" class="fixed inset-0 bg-black/60 z-50 hidden items-center justify-center p-4" style="display:none">
636
+ <form id="post-form" class="card bg-base-200 max-w-lg w-full max-h-[92vh] overflow-y-auto">
637
+ <div class="card-body gap-3">
638
+ <h3 class="text-lg font-bold">New post</h3>
639
+ <input id="f-title" class="input input-bordered" placeholder="Title" required />
640
+ <div class="flex gap-2 items-center">
641
+ <input id="f-idea" class="input input-bordered input-sm flex-1" placeholder="Idea for AI (optional)" />
642
+ <button type="button" id="ai-btn" class="btn-info px-3" hidden>✨ Write with AI</button>
643
+ </div>
644
+ <textarea id="f-body" class="textarea textarea-bordered font-mono text-sm" placeholder="Write in markdown… (## heading, **bold**, - lists, links, images)" rows="8"></textarea>
645
+ <input id="f-image" type="file" accept="image/*" class="file-input file-input-bordered" />
646
+ <div class="flex gap-2 justify-end">
647
+ <button type="button" class="btn-ghost px-4" onclick="document.getElementById('post-modal').style.display='none'">Cancel</button>
648
+ <button type="submit" id="post-submit" class="btn-primary px-6">Publish</button>
649
+ </div>
650
+ </div>
651
+ </form>
652
+ </div>
653
+
654
+ <script src="/v1/libs/aimeat-auth.js"></script>
655
+ <script src="/v1/libs/aimeat-data.js"></script>
656
+ <script src="/v1/libs/aimeat-storage.js"></script>
657
+ <script src="/v1/libs/aimeat-ai.js"></script>
658
+ <script src="/v1/libs/aimeat-markdown.js"></script>
659
+ <script>
660
+ var OWNER = '{{owner-ghii}}'; // the site owner's GHII (owner@node-id) — content is read from here
661
+ var PROFILE_KEY = '{{app}}.profile';
662
+ var POSTS_KEY = '{{app}}.posts';
663
+ var session = null, profile = {}, posts = [];
664
+
665
+ function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; }
666
+ function show(id) { document.getElementById(id).style.display = 'flex'; }
667
+ function hide(id) { document.getElementById(id).style.display = 'none'; }
668
+ function isOwner() { return !!session && session.ghii === OWNER; }
669
+
670
+ function renderHero() {
671
+ var h = document.getElementById('hero'); h.innerHTML = '';
672
+ if (profile.avatarUrl) { var img = document.createElement('img'); img.src = profile.avatarUrl; img.className = 'w-24 h-24 rounded-full object-cover mx-auto'; h.appendChild(img); }
673
+ var name = document.createElement('h1'); name.className = 'text-3xl font-bold mt-3'; name.textContent = profile.name || '{{Your name}}'; h.appendChild(name);
674
+ if (profile.bio) { var bio = document.createElement('div'); bio.className = 'max-w-2xl mx-auto mt-2 text-left'; AIMEAT.md.render(profile.bio, bio); h.appendChild(bio); }
675
+ }
676
+ function renderPosts() {
677
+ var c = document.getElementById('posts'); c.innerHTML = '';
678
+ if (!posts.length) { c.innerHTML = '<div class="text-center opacity-50 py-8">No posts yet.</div>'; return; }
679
+ posts.forEach(function (p) {
680
+ var card = document.createElement('article'); card.className = 'card bg-base-200 shadow';
681
+ card.innerHTML = '<div class="card-body"><h2 class="text-xl font-bold">' + esc(p.title) + '</h2>' +
682
+ '<div class="text-xs opacity-60">' + esc((p.at || '').slice(0, 10)) + '</div></div>';
683
+ var body = card.querySelector('.card-body');
684
+ if (p.imageUrl) { var im = document.createElement('img'); im.src = p.imageUrl; im.className = 'rounded-lg w-full object-cover max-h-96 my-2'; body.appendChild(im); }
685
+ var md = document.createElement('div'); AIMEAT.md.render(p.body || '', md); body.appendChild(md);
686
+ c.appendChild(card);
687
+ });
688
+ }
689
+
690
+ async function load() {
691
+ try { profile = (await AIMEAT.data.getPublic(OWNER, PROFILE_KEY)) || {}; } catch (e) { profile = {}; }
692
+ try { var pp = await AIMEAT.data.getPublic(OWNER, POSTS_KEY); posts = Array.isArray(pp) ? pp.slice() : []; } catch (e) { posts = []; }
693
+ posts.sort(function (a, b) { return (b.at || '').localeCompare(a.at || ''); });
694
+ renderHero(); renderPosts();
695
+ }
696
+
697
+ // ── owner editing ──
698
+ function applyOwnerUi() {
699
+ if (OWNER.indexOf('{{') === 0 && session && session.ghii) OWNER = session.ghii; // owner-preview before the GHII is baked in
700
+ document.getElementById('owner-actions').hidden = !isOwner();
701
+ document.getElementById('ai-btn').hidden = true;
702
+ if (isOwner()) AIMEAT.ai.isAvailable().then(function (ok) { if (ok) document.getElementById('ai-btn').hidden = false; });
703
+ }
704
+
705
+ document.getElementById('edit-btn').onclick = function () {
706
+ document.getElementById('p-name').value = profile.name || '';
707
+ document.getElementById('p-bio').value = profile.bio || '';
708
+ show('profile-modal');
709
+ };
710
+ document.getElementById('post-btn').onclick = function () { show('post-modal'); };
711
+
712
+ document.getElementById('ai-btn').onclick = async function () {
713
+ var idea = document.getElementById('f-idea').value.trim() || document.getElementById('f-title').value.trim();
714
+ if (!idea) { alert('Type a title or an idea first.'); return; }
715
+ this.disabled = true; this.textContent = '✨ Writing…';
716
+ try {
717
+ var r = await AIMEAT.ai.complete({ prompt: 'Write a short, engaging blog post in markdown about: ' + idea, app_id: '{{app}}' });
718
+ document.getElementById('f-body').value = r.content;
719
+ } catch (e) { alert('AI error: ' + (e.message || e)); }
720
+ this.disabled = false; this.textContent = '✨ Write with AI';
721
+ };
722
+
723
+ document.getElementById('profile-form').addEventListener('submit', async function (ev) {
724
+ ev.preventDefault();
725
+ try {
726
+ var avatarUrl = profile.avatarUrl || '';
727
+ var af = document.getElementById('p-avatar');
728
+ if (af.files[0]) { var up = await AIMEAT.storage.upload(af.files[0], { visibility: 'public' }); avatarUrl = '/v1/pub/' + encodeURIComponent(session.ghii) + '/' + encodeURIComponent(up.key); }
729
+ profile = { name: document.getElementById('p-name').value.trim(), bio: document.getElementById('p-bio').value, avatarUrl: avatarUrl };
730
+ await AIMEAT.data.set(PROFILE_KEY, profile, { visibility: 'public' });
731
+ hide('profile-modal'); renderHero();
732
+ } catch (e) { alert('Could not save: ' + (e.message || e)); }
733
+ });
734
+
735
+ document.getElementById('post-form').addEventListener('submit', async function (ev) {
736
+ ev.preventDefault();
737
+ var btn = document.getElementById('post-submit'); btn.disabled = true; btn.textContent = 'Publishing…';
738
+ try {
739
+ var imageUrl = '';
740
+ var fi = document.getElementById('f-image');
741
+ if (fi.files[0]) { var up = await AIMEAT.storage.upload(fi.files[0], { visibility: 'public' }); imageUrl = '/v1/pub/' + encodeURIComponent(session.ghii) + '/' + encodeURIComponent(up.key); }
742
+ posts.unshift({ id: Date.now() + '-' + Math.random().toString(36).slice(2, 7), title: document.getElementById('f-title').value.trim(), body: document.getElementById('f-body').value, imageUrl: imageUrl, at: new Date().toISOString() });
743
+ await AIMEAT.data.set(POSTS_KEY, posts, { visibility: 'public' });
744
+ document.getElementById('post-form').reset(); hide('post-modal'); renderPosts();
745
+ } catch (e) { alert('Could not publish: ' + (e.message || e)); }
746
+ btn.disabled = false; btn.textContent = 'Publish';
747
+ });
748
+
749
+ AIMEAT.auth.mountLoginButton('#login', {
750
+ onLogin: function () { session = AIMEAT.auth.getSession(); applyOwnerUi(); load(); },
751
+ onLogout: function () { location.reload(); }
752
+ });
753
+ var s0 = AIMEAT.auth.getSession && AIMEAT.auth.getSession();
754
+ if (s0 && s0.jwt) { session = s0; applyOwnerUi(); }
755
+ load(); // everyone views
756
+ </script>
757
+ </body>
758
+ </html>`;
759
+ const TEMPLATES = [
760
+ {
761
+ id: 'shell-pure-client',
762
+ kind: 'app-shell',
763
+ tier: 'T1',
764
+ title: 'Standard app — login + saves your data',
765
+ description: 'Single-file HTML app: login + private/shared memory, self-hosted Tailwind + daisyUI, light/dark theme. The 80% case — notes, trackers, boards, dashboards.',
766
+ libs: ['aimeat-auth', 'aimeat-data'],
767
+ content: SHELL_PURE_CLIENT,
768
+ },
769
+ {
770
+ id: 'shell-cortex',
771
+ kind: 'app-shell',
772
+ tier: 'T2',
773
+ title: 'Data app — built-in tables, forms & charts',
774
+ description: 'Standard app plus the bundled cortex UI libraries (DataTable, forms, layouts, charts) for richer structured UIs without hand-rolling components.',
775
+ libs: ['aimeat-auth', 'aimeat-data', 'aimeat-ui-viewers', 'aimeat-ui-forms'],
776
+ content: SHELL_CORTEX,
777
+ },
778
+ {
779
+ id: 'shell-extension',
780
+ kind: 'app-shell',
781
+ tier: 'T3',
782
+ title: 'Connected app — fetches outside data / runs on a schedule (advanced)',
783
+ description: 'For apps needing server-side work (external API fetch, scheduled jobs): the client calls a sandboxed extension. Build the extension separately or ship it in a package.',
784
+ libs: ['aimeat-auth', 'aimeat-data'],
785
+ content: SHELL_EXTENSION,
786
+ },
787
+ { id: 'comp-auth-gated', kind: 'component', title: 'Auth-gated section', description: 'Show/hide a section based on login state.', libs: ['aimeat-auth'], content: COMP_AUTH_GATED },
788
+ { id: 'comp-private-store', kind: 'component', title: 'Private store', description: 'Save / list / remove a per-owner private collection.', libs: ['aimeat-data'], content: COMP_PRIVATE_STORE },
789
+ { id: 'comp-shared-feed', kind: 'component', title: 'Shared feed', description: 'A public community feed — each user writes their own key, everyone reads.', libs: ['aimeat-data'], content: COMP_SHARED_FEED },
790
+ { id: 'comp-ai-action', kind: 'component', title: 'AI action button', description: "Run the user's own LLM on demand, render into an editable field.", libs: ['aimeat-auth', 'aimeat-ai'], content: COMP_AI_ACTION },
791
+ { id: 'comp-data-table', kind: 'component', title: 'Data table', description: 'Sortable / filterable / paginated table via the viewers cortex.', libs: ['aimeat-ui-viewers'], content: COMP_DATA_TABLE },
792
+ { id: 'comp-settings', kind: 'component', title: 'Settings panel', description: "Read / write the app's settings from memory.", libs: ['aimeat-data'], content: COMP_SETTINGS },
793
+ { id: 'comp-dated-archive', kind: 'component', title: 'Dated archive', description: 'Group entries by date and render newest-first (news/journal).', libs: ['aimeat-data'], content: COMP_DATED_ARCHIVE },
794
+ { id: 'comp-image-upload', kind: 'component', title: 'Image upload', description: 'Upload an image to storage and get a shareable public URL (marketplace listings, avatars).', libs: ['aimeat-auth', 'aimeat-storage'], content: COMP_IMAGE_UPLOAD },
795
+ { id: 'comp-realtime-room', kind: 'component', title: 'Realtime room', description: 'Live presence + messages over a shared room — multiplayer games, chat, presence boards.', libs: ['aimeat-auth'], content: COMP_REALTIME_ROOM },
796
+ { id: 'comp-search', kind: 'component', title: 'Search / filter', description: 'Instant client-side filter over a list, or server-side memory search.', libs: ['aimeat-data'], content: COMP_SEARCH },
797
+ { id: 'comp-list-detail', kind: 'component', title: 'List + detail', description: 'Master/detail layout: a list, click an item to show its detail (directories, catalogs).', libs: [], content: COMP_LIST_DETAIL },
798
+ { id: 'comp-markdown', kind: 'component', title: 'Markdown render', description: 'Render safe GFM markdown (AI stories, blog posts, docs) to styled HTML.', libs: ['aimeat-markdown'], content: COMP_MARKDOWN },
799
+ {
800
+ id: 'usecase-realtime-social',
801
+ kind: 'use-case',
802
+ title: 'Realtime social room',
803
+ description: 'A live room: logged-in users get live presence + chat with durable history. Chat, presence boards, multiplayer lobbies.',
804
+ libs: ['aimeat-auth', 'aimeat-data'],
805
+ composes: ['comp-realtime-room', 'comp-shared-feed', 'comp-auth-gated'],
806
+ content: USECASE_REALTIME_SOCIAL,
807
+ },
808
+ {
809
+ id: 'usecase-marketplace',
810
+ kind: 'use-case',
811
+ title: 'Marketplace (single-seller storefront)',
812
+ description: 'Anyone browses + searches the public listings and opens a detail view; the seller posts listings with images. One public index key, anon-readable. For multi-seller, use an extension.',
813
+ libs: ['aimeat-auth', 'aimeat-data', 'aimeat-storage'],
814
+ composes: ['comp-list-detail', 'comp-image-upload', 'comp-search', 'comp-auth-gated'],
815
+ content: USECASE_MARKETPLACE,
816
+ },
817
+ {
818
+ id: 'usecase-homepage',
819
+ kind: 'use-case',
820
+ title: 'Homepage / personal site',
821
+ description: 'A single-writer public site: anyone views the owner profile + blog/feed; the owner edits the profile and publishes posts (markdown body, images, optional AI-written draft).',
822
+ libs: ['aimeat-auth', 'aimeat-data', 'aimeat-storage', 'aimeat-ai', 'aimeat-markdown'],
823
+ composes: ['comp-markdown', 'comp-image-upload', 'comp-ai-action', 'comp-auth-gated'],
824
+ content: USECASE_HOMEPAGE,
825
+ },
826
+ ];
827
+ // Localized title/description for the picker (the templates a non-technical user
828
+ // actually sees). English lives on the template itself; other languages overlay
829
+ // here, keyed by id. Only the picker-visible templates (shells + use-cases) need it.
830
+ const TRANSLATIONS = {
831
+ fi: {
832
+ 'shell-pure-client': { title: 'Vakiosovellus — kirjautuminen + tallentaa tietosi' },
833
+ 'shell-cortex': { title: 'Datasovellus — valmiit taulukot, lomakkeet & kaaviot' },
834
+ 'shell-extension': { title: 'Yhdistetty sovellus — hakee ulkoista dataa / ajastetut tehtävät (edistynyt)' },
835
+ 'usecase-realtime-social': { title: 'Reaaliaikainen yhteisöhuone', description: 'Live-huone: kirjautuneet käyttäjät saavat live-läsnäolon + chatin pysyvällä historialla. Chatit, läsnäolotaulut, moninpeliaulat.' },
836
+ 'usecase-marketplace': { title: 'Kauppapaikka (yhden myyjän myymälä)', description: 'Kuka tahansa selaa + hakee julkisia ilmoituksia ja avaa yksityiskohdat; myyjä lisää ilmoituksia kuvilla.' },
837
+ 'usecase-homepage': { title: 'Kotisivu / henkilökohtainen sivusto', description: 'Yhden kirjoittajan julkinen sivusto: kuka tahansa katselee profiilia + blogia; omistaja muokkaa ja julkaisee.' },
838
+ },
839
+ };
840
+ /** All authoring templates. */
841
+ export function getAppTemplates() {
842
+ return TEMPLATES;
843
+ }
844
+ /**
845
+ * Lightweight index (no content) — for injecting a menu into a prompt or rendering
846
+ * a picker. Pass a `lang` (e.g. 'fi') to get localized title/description where a
847
+ * translation exists; everything else falls back to the canonical English.
848
+ */
849
+ export function getAppTemplateIndex(lang) {
850
+ const tr = (lang && TRANSLATIONS[lang]) || null;
851
+ return TEMPLATES.map(({ id, kind, tier, title, description, libs }) => {
852
+ const o = tr && tr[id];
853
+ return { id, kind, tier, title: (o && o.title) || title, description: (o && o.description) || description, libs };
854
+ });
855
+ }
856
+ //# sourceMappingURL=app-templates.js.map