aimeat 2.0.0 → 2.1.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 (412) hide show
  1. package/dist/locales/en.json +35 -1
  2. package/dist/locales/fi.json +35 -1
  3. package/dist/public/components/LinkPreview.js +101 -0
  4. package/dist/public/components/NotificationBell.js +18 -1
  5. package/dist/public/css/theme.css +57 -7
  6. package/dist/public/css/views/admin.css +8 -1
  7. package/dist/public/css/views/app-grant.css +18 -0
  8. package/dist/public/css/views/inbox.css +46 -0
  9. package/dist/public/css/views/portfolio.css +46 -0
  10. package/dist/public/css/views/profile.css +29 -4
  11. package/dist/public/js/services/apps.js +19 -3
  12. package/dist/public/js/services/messages-ai-prompts.js +38 -0
  13. package/dist/public/js/services/messages.js +26 -4
  14. package/dist/public/js/services/notebook.js +76 -0
  15. package/dist/public/js/services/unfurl.js +43 -0
  16. package/dist/public/lib/samples/LICENSE.md +11 -0
  17. package/dist/public/lib/samples/bass/A1.mp3 +0 -0
  18. package/dist/public/lib/samples/bass/B2.mp3 +0 -0
  19. package/dist/public/lib/samples/bass/D2.mp3 +0 -0
  20. package/dist/public/lib/samples/bass/E1.mp3 +0 -0
  21. package/dist/public/lib/samples/bass/E3.mp3 +0 -0
  22. package/dist/public/lib/samples/bass/G2.mp3 +0 -0
  23. package/dist/public/lib/samples/epiano/C2.mp3 +0 -0
  24. package/dist/public/lib/samples/epiano/C3.mp3 +0 -0
  25. package/dist/public/lib/samples/epiano/C4.mp3 +0 -0
  26. package/dist/public/lib/samples/epiano/C5.mp3 +0 -0
  27. package/dist/public/lib/samples/epiano/C6.mp3 +0 -0
  28. package/dist/public/lib/samples/epiano/G2.mp3 +0 -0
  29. package/dist/public/lib/samples/epiano/G3.mp3 +0 -0
  30. package/dist/public/lib/samples/epiano/G4.mp3 +0 -0
  31. package/dist/public/lib/samples/epiano/G5.mp3 +0 -0
  32. package/dist/public/lib/samples/guitar-steel/A2.mp3 +0 -0
  33. package/dist/public/lib/samples/guitar-steel/A4.mp3 +0 -0
  34. package/dist/public/lib/samples/guitar-steel/B3.mp3 +0 -0
  35. package/dist/public/lib/samples/guitar-steel/D3.mp3 +0 -0
  36. package/dist/public/lib/samples/guitar-steel/E2.mp3 +0 -0
  37. package/dist/public/lib/samples/guitar-steel/E4.mp3 +0 -0
  38. package/dist/public/lib/samples/guitar-steel/E5.mp3 +0 -0
  39. package/dist/public/lib/samples/guitar-steel/G3.mp3 +0 -0
  40. package/dist/public/lib/samples/organ/C2.mp3 +0 -0
  41. package/dist/public/lib/samples/organ/C3.mp3 +0 -0
  42. package/dist/public/lib/samples/organ/C4.mp3 +0 -0
  43. package/dist/public/lib/samples/organ/C5.mp3 +0 -0
  44. package/dist/public/lib/samples/organ/C6.mp3 +0 -0
  45. package/dist/public/lib/samples/organ/G2.mp3 +0 -0
  46. package/dist/public/lib/samples/organ/G3.mp3 +0 -0
  47. package/dist/public/lib/samples/organ/G4.mp3 +0 -0
  48. package/dist/public/lib/samples/organ/G5.mp3 +0 -0
  49. package/dist/public/lib/samples/strings/C2.mp3 +0 -0
  50. package/dist/public/lib/samples/strings/C3.mp3 +0 -0
  51. package/dist/public/lib/samples/strings/C4.mp3 +0 -0
  52. package/dist/public/lib/samples/strings/C5.mp3 +0 -0
  53. package/dist/public/lib/samples/strings/C6.mp3 +0 -0
  54. package/dist/public/lib/samples/strings/G2.mp3 +0 -0
  55. package/dist/public/lib/samples/strings/G3.mp3 +0 -0
  56. package/dist/public/lib/samples/strings/G4.mp3 +0 -0
  57. package/dist/public/lib/samples/strings/G5.mp3 +0 -0
  58. package/dist/public/lib/samples/trumpet/A3.mp3 +0 -0
  59. package/dist/public/lib/samples/trumpet/A4.mp3 +0 -0
  60. package/dist/public/lib/samples/trumpet/A5.mp3 +0 -0
  61. package/dist/public/lib/samples/trumpet/C4.mp3 +0 -0
  62. package/dist/public/lib/samples/trumpet/C5.mp3 +0 -0
  63. package/dist/public/lib/samples/trumpet/E3.mp3 +0 -0
  64. package/dist/public/lib/samples/trumpet/E4.mp3 +0 -0
  65. package/dist/public/lib/samples/trumpet/E5.mp3 +0 -0
  66. package/dist/public/spa.html +2 -0
  67. package/dist/public/views/app-grant.js +24 -4
  68. package/dist/public/views/portfolio.js +61 -6
  69. package/dist/public/views/profile/apps-tab.js +18 -1
  70. package/dist/public/views/profile/inbox-tab/ai-actions.js +86 -0
  71. package/dist/public/views/profile/inbox-tab/components.js +124 -9
  72. package/dist/public/views/profile/inbox-tab/helpers.js +26 -0
  73. package/dist/public/views/profile/inbox-tab/panels.js +18 -3
  74. package/dist/public/views/profile/inbox-tab/use-thread-ux.js +18 -1
  75. package/dist/public/views/profile/inbox-tab.js +46 -49
  76. package/dist/public/views/profile/landing-page.cards.js +7 -0
  77. package/dist/public/views/profile/landing-page.js +12 -1
  78. package/dist/public/views/profile/skills-tab.js +1 -1
  79. package/dist/public/views/public-workspace-viewer.js +1 -1
  80. package/dist/public/views/start.js +2 -1
  81. package/dist/scripts/build-sdk-libs.d.ts +16 -0
  82. package/dist/scripts/build-sdk-libs.d.ts.map +1 -0
  83. package/dist/scripts/build-sdk-libs.js +112 -0
  84. package/dist/scripts/build-sdk-libs.js.map +1 -0
  85. package/dist/src/cli/connect/auth.d.ts +15 -0
  86. package/dist/src/cli/connect/auth.d.ts.map +1 -1
  87. package/dist/src/cli/connect/auth.js +25 -6
  88. package/dist/src/cli/connect/auth.js.map +1 -1
  89. package/dist/src/cli/connect/keychain.d.ts +9 -0
  90. package/dist/src/cli/connect/keychain.d.ts.map +1 -1
  91. package/dist/src/cli/connect/keychain.js +21 -8
  92. package/dist/src/cli/connect/keychain.js.map +1 -1
  93. package/dist/src/cli/connect/mcp/tools/exchange.d.ts +18 -0
  94. package/dist/src/cli/connect/mcp/tools/exchange.d.ts.map +1 -0
  95. package/dist/src/cli/connect/mcp/tools/exchange.js +170 -0
  96. package/dist/src/cli/connect/mcp/tools/exchange.js.map +1 -0
  97. package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
  98. package/dist/src/cli/connect/mcp/tools/index.js +2 -0
  99. package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
  100. package/dist/src/cli/connect/tool-call-defs-exchange.d.ts +3 -0
  101. package/dist/src/cli/connect/tool-call-defs-exchange.d.ts.map +1 -0
  102. package/dist/src/cli/connect/tool-call-defs-exchange.js +146 -0
  103. package/dist/src/cli/connect/tool-call-defs-exchange.js.map +1 -0
  104. package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
  105. package/dist/src/cli/connect/tool-call.js +2 -0
  106. package/dist/src/cli/connect/tool-call.js.map +1 -1
  107. package/dist/src/commerce/session-service.d.ts +14 -0
  108. package/dist/src/commerce/session-service.d.ts.map +1 -1
  109. package/dist/src/commerce/session-service.js +1 -1
  110. package/dist/src/commerce/session-service.js.map +1 -1
  111. package/dist/src/data/app-templates/shells.d.ts +7 -3
  112. package/dist/src/data/app-templates/shells.d.ts.map +1 -1
  113. package/dist/src/data/app-templates/shells.js +10 -6
  114. package/dist/src/data/app-templates/shells.js.map +1 -1
  115. package/dist/src/data/appdev-pitfalls.d.ts +2 -0
  116. package/dist/src/data/appdev-pitfalls.d.ts.map +1 -1
  117. package/dist/src/data/appdev-pitfalls.js +24 -1
  118. package/dist/src/data/appdev-pitfalls.js.map +1 -1
  119. package/dist/src/generated/api-types.d.ts +1565 -9
  120. package/dist/src/generated/api-types.d.ts.map +1 -1
  121. package/dist/src/mcp/annotations.d.ts.map +1 -1
  122. package/dist/src/mcp/annotations.js +20 -0
  123. package/dist/src/mcp/annotations.js.map +1 -1
  124. package/dist/src/mcp/apps.d.ts.map +1 -1
  125. package/dist/src/mcp/apps.js +8 -4
  126. package/dist/src/mcp/apps.js.map +1 -1
  127. package/dist/src/mcp/catalog/definitions/exchange.d.ts +18 -0
  128. package/dist/src/mcp/catalog/definitions/exchange.d.ts.map +1 -0
  129. package/dist/src/mcp/catalog/definitions/exchange.js +182 -0
  130. package/dist/src/mcp/catalog/definitions/exchange.js.map +1 -0
  131. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  132. package/dist/src/mcp/catalog/definitions.js +2 -0
  133. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  134. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  135. package/dist/src/mcp/catalog/scopes.js +22 -0
  136. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  137. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  138. package/dist/src/mcp/catalog/surfaces.js +14 -0
  139. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  140. package/dist/src/mcp/exchange-run.d.ts +5 -0
  141. package/dist/src/mcp/exchange-run.d.ts.map +1 -0
  142. package/dist/src/mcp/exchange-run.js +226 -0
  143. package/dist/src/mcp/exchange-run.js.map +1 -0
  144. package/dist/src/mcp/exchange.d.ts +5 -0
  145. package/dist/src/mcp/exchange.d.ts.map +1 -0
  146. package/dist/src/mcp/exchange.js +351 -0
  147. package/dist/src/mcp/exchange.js.map +1 -0
  148. package/dist/src/mcp/extensions.d.ts.map +1 -1
  149. package/dist/src/mcp/extensions.js +11 -0
  150. package/dist/src/mcp/extensions.js.map +1 -1
  151. package/dist/src/mcp/index.d.ts.map +1 -1
  152. package/dist/src/mcp/index.js +6 -2
  153. package/dist/src/mcp/index.js.map +1 -1
  154. package/dist/src/models/app-tool-schemas.d.ts +55 -1
  155. package/dist/src/models/app-tool-schemas.d.ts.map +1 -1
  156. package/dist/src/models/app-tool-schemas.js +20 -1
  157. package/dist/src/models/app-tool-schemas.js.map +1 -1
  158. package/dist/src/models/offer-schemas.d.ts +2 -2
  159. package/dist/src/models/schemas.d.ts +1 -0
  160. package/dist/src/models/schemas.d.ts.map +1 -1
  161. package/dist/src/models/schemas.js +3 -0
  162. package/dist/src/models/schemas.js.map +1 -1
  163. package/dist/src/routes/agents/device-auth.d.ts +4 -0
  164. package/dist/src/routes/agents/device-auth.d.ts.map +1 -1
  165. package/dist/src/routes/agents/device-auth.js +19 -4
  166. package/dist/src/routes/agents/device-auth.js.map +1 -1
  167. package/dist/src/routes/app-grants.d.ts +6 -0
  168. package/dist/src/routes/app-grants.d.ts.map +1 -1
  169. package/dist/src/routes/app-grants.js +35 -2
  170. package/dist/src/routes/app-grants.js.map +1 -1
  171. package/dist/src/routes/apps/fork-manage.d.ts.map +1 -1
  172. package/dist/src/routes/apps/fork-manage.js +24 -1
  173. package/dist/src/routes/apps/fork-manage.js.map +1 -1
  174. package/dist/src/routes/apps/publish.d.ts +2 -0
  175. package/dist/src/routes/apps/publish.d.ts.map +1 -1
  176. package/dist/src/routes/apps/publish.js +27 -1
  177. package/dist/src/routes/apps/publish.js.map +1 -1
  178. package/dist/src/routes/apps-cost.d.ts +25 -0
  179. package/dist/src/routes/apps-cost.d.ts.map +1 -0
  180. package/dist/src/routes/apps-cost.js +107 -0
  181. package/dist/src/routes/apps-cost.js.map +1 -0
  182. package/dist/src/routes/exchange-market.d.ts +28 -0
  183. package/dist/src/routes/exchange-market.d.ts.map +1 -0
  184. package/dist/src/routes/exchange-market.js +454 -0
  185. package/dist/src/routes/exchange-market.js.map +1 -0
  186. package/dist/src/routes/exchange.d.ts +33 -0
  187. package/dist/src/routes/exchange.d.ts.map +1 -0
  188. package/dist/src/routes/exchange.js +395 -0
  189. package/dist/src/routes/exchange.js.map +1 -0
  190. package/dist/src/routes/extensions/entitlement-gate.d.ts +33 -0
  191. package/dist/src/routes/extensions/entitlement-gate.d.ts.map +1 -0
  192. package/dist/src/routes/extensions/entitlement-gate.js +143 -0
  193. package/dist/src/routes/extensions/entitlement-gate.js.map +1 -0
  194. package/dist/src/routes/extensions/manifest.d.ts +9 -0
  195. package/dist/src/routes/extensions/manifest.d.ts.map +1 -1
  196. package/dist/src/routes/extensions/manifest.js +33 -1
  197. package/dist/src/routes/extensions/manifest.js.map +1 -1
  198. package/dist/src/routes/extensions/paywall.d.ts.map +1 -1
  199. package/dist/src/routes/extensions/paywall.js +9 -0
  200. package/dist/src/routes/extensions/paywall.js.map +1 -1
  201. package/dist/src/routes/federation-sync/messaging.d.ts.map +1 -1
  202. package/dist/src/routes/federation-sync/messaging.js +3 -1
  203. package/dist/src/routes/federation-sync/messaging.js.map +1 -1
  204. package/dist/src/routes/ghii/recovery.d.ts +2 -0
  205. package/dist/src/routes/ghii/recovery.d.ts.map +1 -1
  206. package/dist/src/routes/ghii/recovery.js +7 -0
  207. package/dist/src/routes/ghii/recovery.js.map +1 -1
  208. package/dist/src/routes/ghii/register-login.d.ts +4 -0
  209. package/dist/src/routes/ghii/register-login.d.ts.map +1 -1
  210. package/dist/src/routes/ghii/register-login.js +45 -22
  211. package/dist/src/routes/ghii/register-login.js.map +1 -1
  212. package/dist/src/routes/ghii/web-verify.d.ts +5 -0
  213. package/dist/src/routes/ghii/web-verify.d.ts.map +1 -1
  214. package/dist/src/routes/ghii/web-verify.js +24 -3
  215. package/dist/src/routes/ghii/web-verify.js.map +1 -1
  216. package/dist/src/routes/libs/sdk-serve.d.ts +11 -0
  217. package/dist/src/routes/libs/sdk-serve.d.ts.map +1 -0
  218. package/dist/src/routes/libs/sdk-serve.js +53 -0
  219. package/dist/src/routes/libs/sdk-serve.js.map +1 -0
  220. package/dist/src/routes/libs.d.ts +7 -1
  221. package/dist/src/routes/libs.d.ts.map +1 -1
  222. package/dist/src/routes/libs.js +65 -146
  223. package/dist/src/routes/libs.js.map +1 -1
  224. package/dist/src/routes/messages.d.ts +3 -0
  225. package/dist/src/routes/messages.d.ts.map +1 -1
  226. package/dist/src/routes/messages.js +9 -0
  227. package/dist/src/routes/messages.js.map +1 -1
  228. package/dist/src/routes/notifications.d.ts +6 -3
  229. package/dist/src/routes/notifications.d.ts.map +1 -1
  230. package/dist/src/routes/notifications.js +32 -3
  231. package/dist/src/routes/notifications.js.map +1 -1
  232. package/dist/src/routes/organisms/workspace-access.d.ts.map +1 -1
  233. package/dist/src/routes/organisms/workspace-access.js +38 -20
  234. package/dist/src/routes/organisms/workspace-access.js.map +1 -1
  235. package/dist/src/routes/unfurl.d.ts +27 -0
  236. package/dist/src/routes/unfurl.d.ts.map +1 -0
  237. package/dist/src/routes/unfurl.js +180 -0
  238. package/dist/src/routes/unfurl.js.map +1 -0
  239. package/dist/src/routes/upload.d.ts +2 -0
  240. package/dist/src/routes/upload.d.ts.map +1 -1
  241. package/dist/src/routes/upload.js +7 -0
  242. package/dist/src/routes/upload.js.map +1 -1
  243. package/dist/src/routes/webmcp.d.ts +3 -0
  244. package/dist/src/routes/webmcp.d.ts.map +1 -1
  245. package/dist/src/routes/webmcp.js +55 -0
  246. package/dist/src/routes/webmcp.js.map +1 -1
  247. package/dist/src/server-bootstrap/routes-loader.d.ts +1 -0
  248. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  249. package/dist/src/server-bootstrap/routes-loader.js +8 -0
  250. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  251. package/dist/src/services/agent-face.d.ts +10 -2
  252. package/dist/src/services/agent-face.d.ts.map +1 -1
  253. package/dist/src/services/agent-face.js +10 -4
  254. package/dist/src/services/agent-face.js.map +1 -1
  255. package/dist/src/services/app-tool-interfaces.d.ts +37 -0
  256. package/dist/src/services/app-tool-interfaces.d.ts.map +1 -0
  257. package/dist/src/services/app-tool-interfaces.js +101 -0
  258. package/dist/src/services/app-tool-interfaces.js.map +1 -0
  259. package/dist/src/services/build-app-prompt.d.ts +3 -0
  260. package/dist/src/services/build-app-prompt.d.ts.map +1 -1
  261. package/dist/src/services/build-app-prompt.js +12 -1
  262. package/dist/src/services/build-app-prompt.js.map +1 -1
  263. package/dist/src/services/contacts.d.ts +25 -1
  264. package/dist/src/services/contacts.d.ts.map +1 -1
  265. package/dist/src/services/contacts.js +30 -0
  266. package/dist/src/services/contacts.js.map +1 -1
  267. package/dist/src/services/entitlement-money.d.ts +55 -0
  268. package/dist/src/services/entitlement-money.d.ts.map +1 -0
  269. package/dist/src/services/entitlement-money.js +68 -0
  270. package/dist/src/services/entitlement-money.js.map +1 -0
  271. package/dist/src/services/exchange-market.d.ts +247 -0
  272. package/dist/src/services/exchange-market.d.ts.map +1 -0
  273. package/dist/src/services/exchange-market.js +254 -0
  274. package/dist/src/services/exchange-market.js.map +1 -0
  275. package/dist/src/services/exchange-proposals.d.ts +47 -0
  276. package/dist/src/services/exchange-proposals.d.ts.map +1 -0
  277. package/dist/src/services/exchange-proposals.js +71 -0
  278. package/dist/src/services/exchange-proposals.js.map +1 -0
  279. package/dist/src/services/exchange-work.d.ts +30 -0
  280. package/dist/src/services/exchange-work.d.ts.map +1 -0
  281. package/dist/src/services/exchange-work.js +43 -0
  282. package/dist/src/services/exchange-work.js.map +1 -0
  283. package/dist/src/services/message-send.d.ts.map +1 -1
  284. package/dist/src/services/message-send.js +4 -1
  285. package/dist/src/services/message-send.js.map +1 -1
  286. package/dist/src/services/metered-entitlements.d.ts +203 -0
  287. package/dist/src/services/metered-entitlements.d.ts.map +1 -0
  288. package/dist/src/services/metered-entitlements.js +264 -0
  289. package/dist/src/services/metered-entitlements.js.map +1 -0
  290. package/dist/src/services/notify.d.ts +7 -0
  291. package/dist/src/services/notify.d.ts.map +1 -1
  292. package/dist/src/services/notify.js +35 -0
  293. package/dist/src/services/notify.js.map +1 -1
  294. package/dist/src/services/owner-memory.d.ts +16 -0
  295. package/dist/src/services/owner-memory.d.ts.map +1 -1
  296. package/dist/src/services/owner-memory.js +23 -0
  297. package/dist/src/services/owner-memory.js.map +1 -1
  298. package/dist/src/services/owner-provisioning.d.ts +5 -0
  299. package/dist/src/services/owner-provisioning.d.ts.map +1 -1
  300. package/dist/src/services/owner-provisioning.js +20 -2
  301. package/dist/src/services/owner-provisioning.js.map +1 -1
  302. package/dist/src/storage/providers/postgres-kysely/methods/apps.d.ts +1 -0
  303. package/dist/src/storage/providers/postgres-kysely/methods/apps.d.ts.map +1 -1
  304. package/dist/src/storage/providers/postgres-kysely/methods/apps.js +2 -0
  305. package/dist/src/storage/providers/postgres-kysely/methods/apps.js.map +1 -1
  306. package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts +1 -0
  307. package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts.map +1 -1
  308. package/dist/src/storage/providers/postgres-kysely/methods/identity.js +4 -0
  309. package/dist/src/storage/providers/postgres-kysely/methods/identity.js.map +1 -1
  310. package/dist/src/storage/providers/postgres-kysely/migrations/0011_unique_verified_email.sql +25 -0
  311. package/dist/src/storage/providers/sqlite/methods/apps.d.ts +1 -0
  312. package/dist/src/storage/providers/sqlite/methods/apps.d.ts.map +1 -1
  313. package/dist/src/storage/providers/sqlite/methods/apps.js +2 -0
  314. package/dist/src/storage/providers/sqlite/methods/apps.js.map +1 -1
  315. package/dist/src/storage/providers/sqlite/methods/identity-nodes.d.ts +1 -0
  316. package/dist/src/storage/providers/sqlite/methods/identity-nodes.d.ts.map +1 -1
  317. package/dist/src/storage/providers/sqlite/methods/identity-nodes.js +4 -0
  318. package/dist/src/storage/providers/sqlite/methods/identity-nodes.js.map +1 -1
  319. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  320. package/dist/src/storage/providers/sqlite/schema.js +20 -0
  321. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  322. package/dist/src/storage/repositories/app.repository.d.ts +0 -0
  323. package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
  324. package/dist/src/storage/repositories/identity.repository.d.ts +6 -0
  325. package/dist/src/storage/repositories/identity.repository.d.ts.map +1 -1
  326. package/dist/src/storage/types/apps.d.ts +1 -0
  327. package/dist/src/storage/types/apps.d.ts.map +1 -1
  328. package/dist/src/storage/types/organisms-federation.d.ts +21 -0
  329. package/dist/src/storage/types/organisms-federation.d.ts.map +1 -1
  330. package/dist/src/utils/app-mobile-lint.d.ts +17 -0
  331. package/dist/src/utils/app-mobile-lint.d.ts.map +1 -0
  332. package/dist/src/utils/app-mobile-lint.js +40 -0
  333. package/dist/src/utils/app-mobile-lint.js.map +1 -0
  334. package/dist/static/app-catalog/_template.html +12 -77
  335. package/dist/static/app-catalog/js/apps-io.js +51 -165
  336. package/dist/static/app-catalog/js/cost.js +100 -0
  337. package/dist/static/app-catalog/js/db.js +23 -93
  338. package/dist/static/app-catalog/js/detail.js +158 -24
  339. package/dist/static/app-catalog/js/favorites.js +71 -0
  340. package/dist/static/app-catalog/js/i18n-data.js +90 -16
  341. package/dist/static/app-catalog/js/main.js +23 -103
  342. package/dist/static/app-catalog/js/migrate.js +88 -0
  343. package/dist/static/app-catalog/js/monetize.js +1 -1
  344. package/dist/static/app-catalog/js/promote.js +70 -0
  345. package/dist/static/app-catalog/js/render.js +26 -139
  346. package/dist/static/app-catalog/js/server-io.js +126 -223
  347. package/dist/static/app-catalog/js/settings.js +11 -204
  348. package/dist/static/app-catalog/styles/app-catalog.css +40 -0
  349. package/dist/static/app-catalog.html +790 -912
  350. package/dist/static/sdk-libs/_core/config.js +52 -0
  351. package/dist/static/sdk-libs/_core/http.js +52 -0
  352. package/dist/static/sdk-libs/_core/namespace.js +33 -0
  353. package/dist/static/sdk-libs/_core/sdk-globals.d.ts +50 -0
  354. package/dist/static/sdk-libs/_core/session.js +66 -0
  355. package/dist/static/sdk-libs/agentface/index.js +102 -0
  356. package/dist/static/sdk-libs/agents/index.js +267 -0
  357. package/dist/static/sdk-libs/ai/index.js +153 -0
  358. package/dist/static/sdk-libs/audio/core.js +111 -0
  359. package/dist/static/sdk-libs/audio/index.js +380 -0
  360. package/dist/static/sdk-libs/audio/instruments.js +369 -0
  361. package/dist/static/sdk-libs/auth/config.js +46 -0
  362. package/dist/static/sdk-libs/auth/crypto.js +166 -0
  363. package/dist/static/sdk-libs/auth/events.js +27 -0
  364. package/dist/static/sdk-libs/auth/i18n.js +64 -0
  365. package/dist/static/sdk-libs/auth/index.js +37 -0
  366. package/dist/static/sdk-libs/auth/modal.js +545 -0
  367. package/dist/static/sdk-libs/auth/pill.js +167 -0
  368. package/dist/static/sdk-libs/auth/session.js +792 -0
  369. package/dist/static/sdk-libs/auth/signup.js +232 -0
  370. package/dist/static/sdk-libs/auth/theme.js +96 -0
  371. package/dist/static/sdk-libs/capabilities/index.js +179 -0
  372. package/dist/static/sdk-libs/commerce/index.js +243 -0
  373. package/dist/static/sdk-libs/data/index.js +188 -0
  374. package/dist/static/sdk-libs/dist/aimeat-agentface.js +91 -0
  375. package/dist/static/sdk-libs/dist/aimeat-agents.js +315 -0
  376. package/dist/static/sdk-libs/dist/aimeat-ai.js +168 -0
  377. package/dist/static/sdk-libs/dist/aimeat-audio.js +979 -0
  378. package/dist/static/sdk-libs/dist/aimeat-auth.js +1831 -0
  379. package/dist/static/sdk-libs/dist/aimeat-capabilities.js +177 -0
  380. package/dist/static/sdk-libs/dist/aimeat-commerce.js +265 -0
  381. package/dist/static/sdk-libs/dist/aimeat-data.js +205 -0
  382. package/dist/static/sdk-libs/dist/aimeat-editor.js +299 -0
  383. package/dist/static/sdk-libs/dist/aimeat-header.js +270 -0
  384. package/dist/static/sdk-libs/dist/aimeat-intake.js +94 -0
  385. package/dist/static/sdk-libs/dist/aimeat-live.js +265 -0
  386. package/dist/static/sdk-libs/dist/aimeat-markdown.js +602 -0
  387. package/dist/static/sdk-libs/dist/aimeat-organism.js +394 -0
  388. package/dist/static/sdk-libs/dist/aimeat-portfolio-standalone.js +90 -0
  389. package/dist/static/sdk-libs/dist/aimeat-social.js +157 -0
  390. package/dist/static/sdk-libs/dist/aimeat-speech.js +300 -0
  391. package/dist/static/sdk-libs/dist/aimeat-storage.js +210 -0
  392. package/dist/static/sdk-libs/dist/aimeat-tunnel.js +383 -0
  393. package/dist/static/sdk-libs/dist/aimeat-wallet.js +118 -0
  394. package/dist/static/sdk-libs/dist/aimeat-webmcp.js +187 -0
  395. package/dist/static/sdk-libs/dist/aimeat-work.js +213 -0
  396. package/dist/static/sdk-libs/dist/aimeat-workflows.js +147 -0
  397. package/dist/static/sdk-libs/editor/index.js +229 -0
  398. package/dist/static/sdk-libs/header/index.js +249 -0
  399. package/dist/static/sdk-libs/intake/index.js +75 -0
  400. package/dist/static/sdk-libs/live/index.js +156 -0
  401. package/dist/static/sdk-libs/markdown/index.js +522 -0
  402. package/dist/static/sdk-libs/organism/index.js +381 -0
  403. package/dist/static/sdk-libs/portfolio-standalone/index.js +78 -0
  404. package/dist/static/sdk-libs/social/index.js +140 -0
  405. package/dist/static/sdk-libs/speech/index.js +321 -0
  406. package/dist/static/sdk-libs/storage/index.js +189 -0
  407. package/dist/static/sdk-libs/tunnel/index.js +428 -0
  408. package/dist/static/sdk-libs/wallet/index.js +115 -0
  409. package/dist/static/sdk-libs/webmcp/index.js +185 -0
  410. package/dist/static/sdk-libs/work/index.js +191 -0
  411. package/dist/static/sdk-libs/workflows/index.js +113 -0
  412. package/package.json +5 -2
@@ -1,108 +1,38 @@
1
1
  /**
2
2
  * @file db.js
3
- * @description Local catalog data layer IndexedDB, per-mode (Global shared vs Personal
4
- * per-account). Encapsulates the DB handle + current mode as module-private state and exposes
5
- * the app CRUD + mode get/set/close, so UI actions (switchDbMode) live in the feature layer.
6
- * Carved from the former monolithic main.js.
7
- * @usage import { getAllApps, saveApp, deleteApp, getDbMode, setDbMode, closeDbInstance } from './db.js'
3
+ * @description Transient in-memory working-set for the catalog. The catalog is SERVER-ONLY
4
+ * there is no browser persistence (no IndexedDB). This module holds a page-session array of
5
+ * app records that the detail view materializes on demand for editing (downloaded from the
6
+ * server, edited in memory, then published back). The set is empty on every load and rebuilt
7
+ * from the server; nothing here survives a reload. Keeps the getAllApps/saveApp/deleteApp
8
+ * surface the rest of the catalog imports, so callers are unchanged.
9
+ * @usage import { getAllApps, saveApp, deleteApp } from './db.js'
8
10
  * @version-history
9
11
  * v1.0.0 — 2026-07-10 — Initial extraction (TARGET-021 Aalto 3 modularization, phase 2).
12
+ * v2.0.0 — 2026-07-20 — Server-only catalog: drop IndexedDB + the Shared/Mine DB-mode entirely;
13
+ * back the same CRUD surface with a transient in-memory working-set (no browser persistence).
10
14
  */
11
15
 
12
- const DB_NAME_GLOBAL = 'AppLauncherDB';
13
- const DB_VERSION = 2;
14
- const STORE_NAME = 'apps';
15
-
16
- let dbInstance = null;
17
- let currentDbMode = localStorage.getItem('appCatalogMode') || 'global';
18
-
19
- export function getDbMode() { return currentDbMode; }
20
-
21
- export function setDbMode(mode) {
22
- currentDbMode = mode;
23
- try { localStorage.setItem('appCatalogMode', mode); } catch (e) { /* private mode */ }
24
- }
25
-
26
- export function closeDbInstance() {
27
- if (dbInstance) { try { dbInstance.close(); } catch (e) {} dbInstance = null; }
28
- }
29
-
30
- /** DB name is per-account in Personal mode (falls back to the shared Global DB otherwise). */
31
- export function getDbName() {
32
- if (currentDbMode === 'personal') {
33
- var owner = null;
34
- try {
35
- if (window.AIMEAT && window.AIMEAT.auth && window.AIMEAT.auth.getSession()) {
36
- owner = window.AIMEAT.auth.getSession().owner || null;
37
- }
38
- if (!owner) {
39
- var stored = localStorage.getItem('aimeat_session');
40
- if (stored) owner = JSON.parse(stored).owner || null;
41
- }
42
- } catch (e) {}
43
- if (owner) return DB_NAME_GLOBAL + '_' + owner;
44
- }
45
- return DB_NAME_GLOBAL;
46
- }
47
-
48
- export function openDB() {
49
- var name = getDbName();
50
- if (dbInstance && dbInstance.name === name) return Promise.resolve(dbInstance);
51
- if (dbInstance) { dbInstance.close(); dbInstance = null; }
52
- return new Promise(function (resolve, reject) {
53
- const request = indexedDB.open(name, DB_VERSION);
54
- request.onupgradeneeded = function (event) {
55
- const db = event.target.result;
56
- var store;
57
- if (!db.objectStoreNames.contains(STORE_NAME)) {
58
- store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
59
- store.createIndex('tags', 'tags', { multiEntry: true });
60
- store.createIndex('source', 'source', { unique: false });
61
- store.createIndex('favorite', 'favorite', { unique: false });
62
- store.createIndex('sortOrder', 'sortOrder', { unique: false });
63
- } else {
64
- store = event.target.transaction.objectStore(STORE_NAME);
65
- if (!store.indexNames.contains('sortOrder')) {
66
- store.createIndex('sortOrder', 'sortOrder', { unique: false });
67
- }
68
- }
69
- };
70
- request.onsuccess = function (event) { dbInstance = event.target.result; resolve(dbInstance); };
71
- request.onerror = function (event) { reject(event.target.error); };
72
- });
73
- }
16
+ // Page-session working set. Holds only transient records the detail view materialized for editing;
17
+ // empty on load, discarded on unload. NOT a store — the server is the sole source of truth.
18
+ let workingSet = [];
74
19
 
20
+ /** Resolve the current working set (a copy so callers can't mutate our array by reference). */
75
21
  export function getAllApps() {
76
- return openDB().then(function (db) {
77
- return new Promise(function (resolve, reject) {
78
- const tx = db.transaction(STORE_NAME, 'readonly');
79
- const request = tx.objectStore(STORE_NAME).getAll();
80
- request.onsuccess = function () { resolve(request.result); };
81
- request.onerror = function () { reject(request.error); };
82
- });
83
- });
22
+ return Promise.resolve(workingSet.slice());
84
23
  }
85
24
 
25
+ /** Upsert a transient record by id (in memory only — persistence is publishing to the server). */
86
26
  export function saveApp(app) {
87
- return openDB().then(function (db) {
88
- return new Promise(function (resolve, reject) {
89
- const tx = db.transaction(STORE_NAME, 'readwrite');
90
- tx.objectStore(STORE_NAME).put(app);
91
- tx.oncomplete = function () { resolve(); };
92
- tx.onerror = function () { reject(tx.error); };
93
- tx.onabort = function () { reject(tx.error); };
94
- });
95
- });
27
+ if (app && app.id) {
28
+ var idx = workingSet.findIndex(function (a) { return a.id === app.id; });
29
+ if (idx >= 0) workingSet[idx] = app; else workingSet.push(app);
30
+ }
31
+ return Promise.resolve();
96
32
  }
97
33
 
34
+ /** Drop a transient record by id (does NOT delete the server app — that is deleteServerApp). */
98
35
  export function deleteApp(id) {
99
- return openDB().then(function (db) {
100
- return new Promise(function (resolve, reject) {
101
- const tx = db.transaction(STORE_NAME, 'readwrite');
102
- tx.objectStore(STORE_NAME).delete(id);
103
- tx.oncomplete = function () { resolve(); };
104
- tx.onerror = function () { reject(tx.error); };
105
- tx.onabort = function () { reject(tx.error); };
106
- });
107
- });
36
+ workingSet = workingSet.filter(function (a) { return a.id !== id; });
37
+ return Promise.resolve();
108
38
  }
@@ -8,8 +8,12 @@
8
8
  * injected once via initDetail(deps) — so there is no import cycle back through the entry module.
9
9
  * @usage import { initDetail, openDetailView, mountLoginPill, ... } from './detail.js'; initDetail({...})
10
10
  * @version-history
11
+ * v1.4.0 — 2026-07-20 — Server-only cutover: drop the local-only favourite star; the detail view
12
+ * always operates on a server app (materialized in memory on demand for editing).
11
13
  * v1.3.0 — 2026-07-16 — Add openStagingPreview(owner, filename): mint a preview token for an
12
14
  * EXISTING staging draft and open it top-level, driving a library card's "Open staging" button.
15
+ * v1.3.0 — 2026-07-20 — Cost & Contracts section (EXCHANGE G3 / TARGET-045): own published apps get a
16
+ * read-only per-app EXCHANGE entitlement surface (cost.js) after Monetize.
13
17
  * v1.2.0 — 2026-07-14 — Monetize section (TARGET-034 phase B): own published apps get the
14
18
  * apps.{appId}.tools tool editor (monetize.js) between Skills and Manage-on-server.
15
19
  * v1.1.0 — 2026-07-11 — Own-published apps: "Edit Access Code" in serverMgmtInner + "Attach skill"
@@ -18,11 +22,13 @@
18
22
  * v1.0.0 — 2026-07-10 — Initial extraction (TARGET-021 Aalto 3 modularization, phase 7).
19
23
  */
20
24
  import { escapeHtml, jsArg, sourceLabel, sourceLabelText, currentOwnerName } from './util.js';
21
- import { saveApp, deleteApp, getDbMode, setDbMode, closeDbInstance } from './db.js';
25
+ import { saveApp, deleteApp } from './db.js';
22
26
  import { dtlBtn, showConfirm, showNotice } from './ui.js';
23
27
  import { loadConfig } from './config.js';
24
- import { t } from './i18n.js';
28
+ import { t, getLang } from './i18n.js';
29
+ import { getPromotion, setPromotion, loadPromoted } from './promote.js';
25
30
  import { monetizeSectionInner, monetizeOnOpen } from './monetize.js';
31
+ import { costSectionInner, costOnOpen } from './cost.js';
26
32
  import { appManifestAgents } from './app-agents.js';
27
33
 
28
34
  // Injected once at bootstrap by main.js. Functions are main-local; the get* return main's LIVE
@@ -163,6 +169,8 @@ function openDetailView(appId) {
163
169
  // Monetize (TARGET-034): reset + async-load the apps.{appId}.tools manifest for OWN published
164
170
  // apps before the first render so the section shell picks up the loading state.
165
171
  monetizeOnOpen(detailServerOwner(app), app.publishedFilename || '', detailIsOwnPublished(app));
172
+ // Cost & contracts (EXCHANGE G3): async-load this app's EXCHANGE entitlements for OWN published apps.
173
+ costOnOpen(detailServerOwner(app), app.publishedFilename || '', detailIsOwnPublished(app));
166
174
  document.getElementById('detail-view').hidden = false;
167
175
  renderDetailView();
168
176
  // AI availability + published versions load asynchronously and re-render in place.
@@ -235,15 +243,8 @@ function loadScriptOnce(src) {
235
243
  }
236
244
 
237
245
  function onAuthChanged() {
238
- // Personal mode is per-account: if the user signed OUT while in it, fall back to Global — it
239
- // would otherwise silently show the shared Global DB.
240
- if (getDbMode() === 'personal' && !currentOwnerName()) {
241
- setDbMode('global');
242
- }
243
- // The per-account IndexedDB changes with the signed-in owner, so re-open it and re-render the
244
- // LOCAL grid + tags too — not just the server sections (the stale-grid-after-login bug).
245
- closeDbInstance();
246
- updateModeToggle();
246
+ // Server-only catalog: the grid is rebuilt from the server on every auth change, so a sign-in /
247
+ // sign-out just re-fetches the owner's apps (their parked/listed apps appear or drop away).
247
248
  try { refreshAll(); } catch (e) { try { loadPublishedApps(); } catch (e2) {} }
248
249
  var sub = document.getElementById('publish-submit-btn');
249
250
  var st = document.getElementById('publish-status');
@@ -339,10 +340,9 @@ function renderDetailView() {
339
340
  var tags = (app.tags && app.tags.length) ? app.tags.join(', ') : '—';
340
341
  var cortex = (app.usesCortex && app.usesCortex.length) ? app.usesCortex.join(', ') : '—';
341
342
  var created = app.addedAt ? new Date(app.addedAt).toLocaleString() : '—';
342
- var favBtn = app.blob
343
- ? '<button class="dtl-fav-btn" title="' + escapeHtml(t(app.favorite ? 'detail.unfavorite' : 'detail.favorite')) + '"'
344
- + ' onclick="window._launcher.detailToggleFavorite()">' + (app.favorite ? '' : '☆') + '</button>'
345
- : '';
343
+ // Favourites are a Phase-2 (server-backed) feature; the old local-only star was dropped with the
344
+ // server-only cutover so it isn't shown here anymore.
345
+ var favBtn = '';
346
346
  var aboutHeader =
347
347
  '<h3 style="display:flex;align-items:center;justify-content:space-between;gap:8px">' +
348
348
  '<span>' + t('detail.about') + '</span>' +
@@ -351,13 +351,26 @@ function renderDetailView() {
351
351
  ((canEditAbout && !detailEditingAbout) ? dtlBtn(t('detail.editDetails'), 'window._launcher.detailAboutEdit()') : '') +
352
352
  '</span>' +
353
353
  '</h3>';
354
+ // Per-locale descriptions: EN + FI (extensible). Seed from the manifest's descriptions map,
355
+ // falling back to the canonical description for the default language so an app that only ever had
356
+ // a single description pre-fills English.
357
+ var descs = app.descriptions || {};
358
+ var descEnVal = descs.en || (app.description || '');
359
+ var descFiVal = descs.fi || '';
354
360
  var aboutBody;
355
361
  if (detailEditingAbout) {
356
362
  aboutBody =
357
363
  '<label class="dtl-stat-label" for="detail-name-input">' + t('detail.nameLabel') + '</label>' +
358
364
  '<input id="detail-name-input" class="modal-input" maxlength="120" value="' + escapeHtml(app.name || '') + '" style="margin:4px 0 10px" />' +
359
- '<label class="dtl-stat-label" for="detail-desc-input">' + t('detail.descLabel') + '</label>' +
360
- '<textarea id="detail-desc-input" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 8px;resize:vertical">' + escapeHtml(app.description || '') + '</textarea>' +
365
+ '<label class="dtl-stat-label" for="detail-desc-en">' + t('detail.descEn') + '</label>' +
366
+ '<textarea id="detail-desc-en" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(descEnVal) + '</textarea>' +
367
+ '<label class="dtl-stat-label" for="detail-desc-fi">' + t('detail.descFi') + '</label>' +
368
+ '<textarea id="detail-desc-fi" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(descFiVal) + '</textarea>' +
369
+ '<div class="dtl-btn-row" style="margin:0 0 4px">' +
370
+ dtlBtn('🌐 ' + t('detail.translateEnFi'), 'window._launcher.detailTranslateDesc(\'en\',\'fi\')', {id:'detail-tr-enfi'}) +
371
+ dtlBtn('🌐 ' + t('detail.translateFiEn'), 'window._launcher.detailTranslateDesc(\'fi\',\'en\')', {id:'detail-tr-fien'}) +
372
+ '</div>' +
373
+ '<div class="dtl-ai-status" id="detail-tr-status" style="margin:0 0 8px"></div>' +
361
374
  '<label class="dtl-stat-label" for="detail-icon-input">' + t('detail.iconLabel') + '</label>' +
362
375
  '<input id="detail-icon-input" class="modal-input" maxlength="4" value="' + escapeHtml(app.icon || '') + '" style="margin:4px 0 10px;text-align:center;font-size:1.2rem" />' +
363
376
  '<label class="dtl-stat-label" for="detail-tags-input">' + t('detail.tagsLabel') + '</label>' +
@@ -368,8 +381,10 @@ function renderDetailView() {
368
381
  dtlBtn(t('detail.cancelEdit'), 'window._launcher.detailAboutCancel()') +
369
382
  '</div>';
370
383
  } else {
384
+ // Show the description in the current UI language, falling back to the canonical one.
385
+ var shownDesc = (app.descriptions && app.descriptions[getLang()]) || app.description || '';
371
386
  aboutBody =
372
- (app.description ? '<p class="dtl-desc">' + escapeHtml(app.description) + '</p>' : '') +
387
+ (shownDesc ? '<p class="dtl-desc">' + escapeHtml(shownDesc) + '</p>' : '') +
373
388
  '<div class="dtl-meta-grid">' +
374
389
  metaItem(t('detail.category'), app.category || 'utility') +
375
390
  metaItem(t('detail.tags'), tags) +
@@ -479,6 +494,17 @@ function renderDetailView() {
479
494
  ? '<div class="dtl-section" id="detail-monetize">' + monetizeSectionInner() + '</div>'
480
495
  : '';
481
496
 
497
+ // ── COST & CONTRACTS (EXCHANGE G3 / TARGET-045) — what this app SOURCES (own published apps only) ──
498
+ // Stable container: cost.js re-renders #detail-cost in place after the async load.
499
+ var costHtml = (app.published && detailIsOwnPublished(app))
500
+ ? '<div class="dtl-section" id="detail-cost">' + costSectionInner() + '</div>'
501
+ : '';
502
+
503
+ // ── PROMOTE (Phase 2c) — showcase this app on your public profile with EN/FI pitch copy.
504
+ // Own published apps only; persists to the PUBLIC app-catalog.promoted memory doc.
505
+ var promoteHtml = (app.published && detailIsOwnPublished(app))
506
+ ? buildPromoteSection(app) : '';
507
+
482
508
  // ── BUNDLED AGENTS (Agent-Bundled Apps) — crew-defs this app ships. The section lists the
483
509
  // agent names and opens the shared Bundled-agents modal (inspector + hosted instances with
484
510
  // prices + deploy-your-own). Manifest read from the server-manifest cache.
@@ -502,7 +528,56 @@ function renderDetailView() {
502
528
  }
503
529
 
504
530
  document.getElementById('detail-body').innerHTML =
505
- statusHtml + aboutHtml + aiHtml + versionsHtml + skillsHtml + agentsHtml + monetizeHtml + mgmtHtml + actionsHtml;
531
+ statusHtml + aboutHtml + aiHtml + versionsHtml + skillsHtml + agentsHtml + monetizeHtml + costHtml + promoteHtml + mgmtHtml + actionsHtml;
532
+ }
533
+
534
+ // The Promote section: a short EN/FI pitch that surfaces this app on the owner's public profile.
535
+ // Saving writes the PUBLIC app-catalog.promoted doc; clearing both fields un-promotes the app.
536
+ function promoteRef(app) { return detailServerOwner(app) + '/' + (app.publishedFilename || ''); }
537
+ function buildPromoteSection(app) {
538
+ var ref = promoteRef(app);
539
+ var cur = getPromotion(ref) || {};
540
+ var on = !!(cur.en || cur.fi);
541
+ return '<div class="dtl-section" id="detail-promote">' +
542
+ '<h3 style="display:flex;align-items:center;gap:8px">📣 ' + t('promote.title') +
543
+ (on ? ' <span class="dtl-badge-on">' + t('promote.on') + '</span>' : '') + '</h3>' +
544
+ '<p class="dtl-desc">' + t('promote.hint') + '</p>' +
545
+ '<label class="dtl-stat-label" for="detail-promo-en">' + t('promote.en') + '</label>' +
546
+ '<textarea id="detail-promo-en" class="modal-input" rows="2" maxlength="500" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(cur.en || '') + '</textarea>' +
547
+ '<label class="dtl-stat-label" for="detail-promo-fi">' + t('promote.fi') + '</label>' +
548
+ '<textarea id="detail-promo-fi" class="modal-input" rows="2" maxlength="500" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(cur.fi || '') + '</textarea>' +
549
+ '<div class="dtl-btn-row" style="margin:0 0 4px">' +
550
+ dtlBtn('🌐 ' + t('detail.translateEnFi'), 'window._launcher.detailTranslateDesc(\'en\',\'fi\',\'detail-promo-\',\'detail-promo-tr-status\')') +
551
+ dtlBtn('🌐 ' + t('detail.translateFiEn'), 'window._launcher.detailTranslateDesc(\'fi\',\'en\',\'detail-promo-\',\'detail-promo-tr-status\')') +
552
+ '</div>' +
553
+ '<div class="dtl-ai-status" id="detail-promo-tr-status" style="margin:0 0 8px"></div>' +
554
+ '<div class="dtl-btn-row">' +
555
+ dtlBtn(t('promote.save'), 'window._launcher.detailPromoteSave()', {variant:'primary'}) +
556
+ (on ? dtlBtn(t('promote.remove'), 'window._launcher.detailPromoteClear()') : '') +
557
+ '</div>' +
558
+ '<div class="dtl-ai-status" id="detail-promo-status"></div>' +
559
+ '</div>';
560
+ }
561
+
562
+ function detailPromoteSave() {
563
+ var app = detailGetApp();
564
+ if (!app || !detailIsOwnPublished(app)) return;
565
+ var en = (document.getElementById('detail-promo-en') || {}).value || '';
566
+ var fi = (document.getElementById('detail-promo-fi') || {}).value || '';
567
+ var statusEl = document.getElementById('detail-promo-status');
568
+ if (statusEl) { statusEl.style.color = 'var(--text-muted)'; statusEl.textContent = t('promote.saving'); }
569
+ setPromotion(promoteRef(app), { en: en, fi: fi }).then(function (nowOn) {
570
+ renderApps();
571
+ renderDetailView();
572
+ var s2 = document.getElementById('detail-promo-status');
573
+ if (s2) { s2.style.color = '#34d399'; s2.textContent = nowOn ? '✔ ' + t('promote.saved') : '✔ ' + t('promote.removed'); }
574
+ });
575
+ }
576
+
577
+ function detailPromoteClear() {
578
+ var en = document.getElementById('detail-promo-en'); if (en) en.value = '';
579
+ var fi = document.getElementById('detail-promo-fi'); if (fi) fi.value = '';
580
+ detailPromoteSave();
506
581
  }
507
582
 
508
583
  // Bound skills (skills registry): skills whose frontmatter metadata.binding names this app.
@@ -808,22 +883,31 @@ function detailAboutSave() {
808
883
  var app = detailGetApp();
809
884
  if (!app) return;
810
885
  var nameEl = document.getElementById('detail-name-input');
811
- var descEl = document.getElementById('detail-desc-input');
886
+ var enEl = document.getElementById('detail-desc-en');
887
+ var fiEl = document.getElementById('detail-desc-fi');
812
888
  var iconEl = document.getElementById('detail-icon-input');
813
889
  var tagsEl = document.getElementById('detail-tags-input');
814
890
  var newName = nameEl ? nameEl.value.trim() : '';
815
- var newDesc = descEl ? descEl.value.trim() : '';
891
+ var enDesc = enEl ? enEl.value.trim() : '';
892
+ var fiDesc = fiEl ? fiEl.value.trim() : '';
893
+ // Per-locale map (drop blanks); the canonical description is the English one, or Finnish when
894
+ // English is blank, so an app always has a non-empty fallback description.
895
+ var newDescriptions = {};
896
+ if (enDesc) newDescriptions.en = enDesc;
897
+ if (fiDesc) newDescriptions.fi = fiDesc;
898
+ var newDesc = enDesc || fiDesc || '';
816
899
  // icon + tags are LOCAL metadata (same as the context-menu Edit modal) — no server round-trip.
817
900
  var newIcon = iconEl ? iconEl.value.trim() : (app.icon || '');
818
901
  var newTags = tagsEl
819
902
  ? tagsEl.value.split(',').map(function (s) { return s.trim(); }).filter(Boolean)
820
903
  : (app.tags || []);
821
904
  if (!newName) { showNotice(t('detail.nameRequired') || 'Name cannot be empty.'); if (nameEl) nameEl.focus(); return; }
822
- if (app.published && !newDesc) { showNotice(t('detail.descRequired') || 'Description cannot be empty.'); if (descEl) descEl.focus(); return; }
905
+ if (app.published && !newDesc) { showNotice(t('detail.descRequired') || 'Description cannot be empty.'); if (enEl) enEl.focus(); return; }
823
906
 
824
907
  function finishLocal() {
825
908
  app.name = newName;
826
909
  app.description = newDesc;
910
+ app.descriptions = newDescriptions;
827
911
  app.icon = newIcon;
828
912
  app.tags = newTags;
829
913
  saveApp(app).then(function() {
@@ -846,16 +930,17 @@ function detailAboutSave() {
846
930
  fetch(aimeatUrl + '/v1/apps/' + encodeURIComponent(filename), {
847
931
  method: 'PATCH',
848
932
  headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
849
- body: JSON.stringify({ name: newName, description: newDesc })
933
+ body: JSON.stringify({ name: newName, description: newDesc, descriptions: newDescriptions })
850
934
  })
851
935
  .then(function(resp) { return resp.json().then(function(j) { return { ok: resp.ok, j: j }; }); })
852
936
  .then(function(res) {
853
937
  if (res.ok && res.j && res.j.ok !== false) {
854
- // Keep the cached server manifest in sync so a re-render shows the new name.
938
+ // Keep the cached server manifest in sync so a re-render shows the new name + descriptions.
855
939
  var key = owner + '\n' + filename;
856
940
  getServerManifests()[key] = getServerManifests()[key] || {};
857
941
  getServerManifests()[key].name = newName;
858
942
  getServerManifests()[key].description = newDesc;
943
+ getServerManifests()[key].descriptions = newDescriptions;
859
944
  finishLocal();
860
945
  } else {
861
946
  showNotice('Failed: ' + ((res.j && res.j.error && res.j.error.message) || 'Unknown error'));
@@ -864,6 +949,50 @@ function detailAboutSave() {
864
949
  .catch(function(err) { showNotice('Error: ' + (err.message || err)); });
865
950
  }
866
951
 
952
+ // Translate one language field to another via the owner's OpenRouter key (/v1/ai/complete).
953
+ // srcLang/dstLang are 'en'|'fi'; fills the destination textarea in place. `prefix` picks the field
954
+ // pair ('detail-desc-' for the description editor, 'detail-promo-' for the promotion editor);
955
+ // `statusId` picks the status line. Reused by both the description and promotion editors.
956
+ function detailTranslateDesc(srcLang, dstLang, prefix, statusId) {
957
+ prefix = prefix || 'detail-desc-';
958
+ var srcEl = document.getElementById(prefix + srcLang);
959
+ var dstEl = document.getElementById(prefix + dstLang);
960
+ var statusEl = document.getElementById(statusId || 'detail-tr-status');
961
+ if (!srcEl || !dstEl) return;
962
+ var text = (srcEl.value || '').trim();
963
+ if (!text) { if (statusEl) statusEl.textContent = t('detail.trNeedSource'); return; }
964
+ var token = getCortexOwnerToken();
965
+ if (!token) { if (statusEl) statusEl.textContent = t('detail.aiLoginNeeded'); return; }
966
+ var config = loadConfig();
967
+ var aimeatUrl = (config.aimeatUrl || '').replace(/\/+$/, '');
968
+ if (!aimeatUrl) { if (statusEl) statusEl.textContent = t('detail.aiUnavailable'); return; }
969
+ var langName = { en: 'English', fi: 'Finnish' };
970
+ if (statusEl) { statusEl.style.color = 'var(--text-muted)'; statusEl.textContent = t('detail.translating'); }
971
+ var systemPrompt = 'You are a professional translator for short app-store copy. '
972
+ + 'Translate the text from ' + (langName[srcLang] || srcLang) + ' to ' + (langName[dstLang] || dstLang) + '. '
973
+ + 'Return ONLY the translated text — no quotes, no notes, no explanation. Keep it concise and natural.';
974
+ fetch(aimeatUrl + '/v1/ai/complete', {
975
+ method: 'POST',
976
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
977
+ body: JSON.stringify({ prompt: text, systemPrompt: systemPrompt, app_id: 'app-catalog' })
978
+ })
979
+ .then(function(resp) { return resp.json(); })
980
+ .then(function(json) {
981
+ if (!json || !json.ok) {
982
+ var msg = (json && json.error && json.error.message) || 'Translation failed';
983
+ if (statusEl) { statusEl.style.color = 'var(--accent)'; statusEl.textContent = '✘ ' + msg; }
984
+ return;
985
+ }
986
+ var out = (json.data && json.data.content ? json.data.content : '').trim();
987
+ if (!out) { if (statusEl) { statusEl.style.color = 'var(--accent)'; statusEl.textContent = '✘ ' + t('detail.trEmpty'); } return; }
988
+ dstEl.value = out;
989
+ if (statusEl) { statusEl.style.color = '#34d399'; statusEl.textContent = '✔ ' + t('detail.trDone'); }
990
+ })
991
+ .catch(function(err) {
992
+ if (statusEl) { statusEl.style.color = 'var(--accent)'; statusEl.textContent = '✘ ' + (err.message || 'Translation failed'); }
993
+ });
994
+ }
995
+
867
996
  // Manual override: upload a custom image as this published app's thumbnail. (Bulk auto-capture is
868
997
  // the server-side `aimeat screenshot-worker`; the browser can't grab the cross-origin sandboxed app
869
998
  // itself, so this is a file picker, not an in-page capture.)
@@ -1326,6 +1455,7 @@ function openPublishedDetail(owner, filename, localId, versionNumber) {
1326
1455
  id: generateId(),
1327
1456
  name: meta.name || filename.replace(/\.html?$/i, ''),
1328
1457
  description: meta.description || '',
1458
+ descriptions: meta.descriptions || null,
1329
1459
  category: meta.category || 'utility',
1330
1460
  tags: meta.tags || [],
1331
1461
  usesCortex: meta.usesCortex || [],
@@ -1560,6 +1690,7 @@ async function restoreVersion(owner, filename, version) {
1560
1690
  mime_type: 'text/html',
1561
1691
  name: meta.name || filename.replace(/\.html?$/i, ''),
1562
1692
  description: meta.description || '',
1693
+ descriptions: meta.descriptions || null,
1563
1694
  category: meta.category || 'utility',
1564
1695
  tags: meta.tags || [],
1565
1696
  uses_cortex: meta.usesCortex || []
@@ -1647,6 +1778,9 @@ export {
1647
1778
  detailAboutEdit,
1648
1779
  detailAboutCancel,
1649
1780
  detailAboutSave,
1781
+ detailTranslateDesc,
1782
+ detailPromoteSave,
1783
+ detailPromoteClear,
1650
1784
  detailToggleFavorite,
1651
1785
  detailAccessCodeEdit,
1652
1786
  detailAccessCodeCancel,
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @file favorites.js
3
+ * @description Server-backed favourites for the catalog. A per-owner PRIVATE memory doc
4
+ * (`app-catalog.favorites` = { version, updatedAt, refs: ["owner/filename", …] }) read/written
5
+ * via /v1/memory with the owner token, so favourites follow the account across devices. Any app
6
+ * (own or community) can be favourited; the Library pins a "⭐ Favourites" group on top. Holds the
7
+ * in-memory Set + the star-button HTML helper; the grid/group rendering lives in render/server-io.
8
+ * @usage import { loadFavorites, toggleFavorite, isFavorite, getFavoriteRefs, favStarHtml } from './favorites.js'
9
+ * @version-history
10
+ * v1.0.0 — 2026-07-20 — initial (Phase 2b server-backed favourites).
11
+ */
12
+ import { loadConfig } from './config.js';
13
+ import { getCortexOwnerToken } from './cortex.js';
14
+ import { t } from './i18n.js';
15
+
16
+ const KEY = 'app-catalog.favorites';
17
+ let favSet = new Set();
18
+
19
+ function base() { return (loadConfig().aimeatUrl || '').replace(/\/+$/, ''); }
20
+
21
+ export function isFavorite(ref) { return favSet.has(ref); }
22
+ export function getFavoriteRefs() { return Array.from(favSet); }
23
+ export function hasFavorites() { return favSet.size > 0; }
24
+
25
+ // Load the owner's favourites doc. Requires a signed-in owner (favourites are per-account); logged
26
+ // out → empty set. Never throws — a missing doc (404) just means no favourites yet.
27
+ export function loadFavorites() {
28
+ var token = getCortexOwnerToken();
29
+ var b = base();
30
+ if (!token || !b) { favSet = new Set(); return Promise.resolve(); }
31
+ return fetch(b + '/v1/memory/' + encodeURIComponent(KEY), { headers: { 'Authorization': 'Bearer ' + token } })
32
+ .then(function (r) { return r.ok ? r.json() : null; })
33
+ .then(function (j) {
34
+ var refs = (j && j.data && j.data.value && Array.isArray(j.data.value.refs)) ? j.data.value.refs : [];
35
+ favSet = new Set(refs.filter(function (x) { return typeof x === 'string'; }));
36
+ })
37
+ .catch(function () { /* leave the set as-is on a transient error */ });
38
+ }
39
+
40
+ function persist() {
41
+ var token = getCortexOwnerToken();
42
+ var b = base();
43
+ if (!token || !b) return Promise.resolve();
44
+ return fetch(b + '/v1/memory', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
47
+ body: JSON.stringify({
48
+ key: KEY,
49
+ value: { version: 1, updatedAt: new Date().toISOString(), refs: Array.from(favSet) },
50
+ visibility: 'private',
51
+ }),
52
+ }).catch(function () { /* best-effort persist */ });
53
+ }
54
+
55
+ // Toggle a favourite (ref = "owner/filename"). Updates the set optimistically, persists, and
56
+ // resolves so the caller can re-render. Returns the new state (true = now a favourite).
57
+ export function toggleFavorite(ref) {
58
+ var now;
59
+ if (favSet.has(ref)) { favSet.delete(ref); now = false; } else { favSet.add(ref); now = true; }
60
+ return persist().then(function () { return now; });
61
+ }
62
+
63
+ // The ⭐/☆ toggle button for a card. `ref` is "owner/filename". Stops propagation so a click on the
64
+ // star never opens the card's detail view.
65
+ export function favStarHtml(ref) {
66
+ var on = favSet.has(ref);
67
+ var title = on ? (t('fav.remove') || 'Remove from favourites') : (t('fav.add') || 'Add to favourites');
68
+ return '<button class="fav-toggle' + (on ? ' on' : '') + '"'
69
+ + ' onclick="event.stopPropagation(); window._launcher.toggleFavorite(\'' + String(ref).replace(/'/g, "\\'") + '\')"'
70
+ + ' title="' + title + '">' + (on ? '⭐' : '☆') + '</button>';
71
+ }