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
@@ -2,9 +2,10 @@
2
2
  <!DOCTYPE html>
3
3
  <!--
4
4
  @file src/static/app-catalog.html
5
- @description Self-contained "My Apps" launcher SPA (IndexedDB-backed) for installing,
6
- publishing, and running user apps. Runs published app HTML only inside a sandboxed,
7
- opaque-origin iframe never as a top-level document on the apex origin (H-2).
5
+ @description Self-contained "My Apps" launcher SPA (SERVER-ONLY — no browser persistence) for
6
+ creating, publishing, and managing the owner's server apps. Creating an app publishes it to the
7
+ server (unlisted); nothing is stored in the browser. Runs published app HTML only inside a
8
+ sandboxed, opaque-origin iframe — never as a top-level document on the apex origin (H-2).
8
9
  @structure Inline <style> theme; markup; large inline <script> exposing window._launcher.
9
10
  @usage Served at GET /app-catalog.html (relaxed CSP) from server-bootstrap/static-files.ts.
10
11
  @version-history
@@ -598,6 +599,46 @@
598
599
  opacity: 1;
599
600
  }
600
601
 
602
+ /* ── Server-backed favourite toggle (⭐ on any app / published card) ─── */
603
+ .fav-toggle {
604
+ position: absolute;
605
+ top: 6px;
606
+ left: 8px;
607
+ background: none;
608
+ border: none;
609
+ cursor: pointer;
610
+ font-size: 1rem;
611
+ line-height: 1;
612
+ padding: 2px;
613
+ opacity: .5;
614
+ transition: opacity var(--transition), transform var(--transition);
615
+ z-index: 2;
616
+ }
617
+ .fav-toggle:hover { opacity: 1; transform: scale(1.15); }
618
+ .fav-toggle.on { opacity: 1; }
619
+ .published-card { position: relative; }
620
+ .published-card .fav-toggle { left: auto; right: 8px; top: 8px; }
621
+ .fav-section-title {
622
+ font-size: 1rem;
623
+ font-weight: 700;
624
+ color: var(--text);
625
+ display: flex;
626
+ align-items: center;
627
+ gap: 8px;
628
+ margin: 0 0 .6rem;
629
+ }
630
+ .fav-section-count { opacity: .6; font-weight: 400; }
631
+
632
+ /* "Promoted" badge in the detail Promote section */
633
+ .dtl-badge-on {
634
+ font-size: .7rem;
635
+ font-weight: 600;
636
+ color: #34d399;
637
+ border: 1px solid #34d399;
638
+ border-radius: 10px;
639
+ padding: 1px 8px;
640
+ }
641
+
601
642
  /* ── Card Menu Button ─────────────────────────────── */
602
643
 
603
644
  .card-menu-btn {
@@ -2160,13 +2201,6 @@
2160
2201
  <header>
2161
2202
  <a class="aimeat-cta aimeat-home" id="aimeat-home-link" href="/" data-i18n-title="cta.backHome" title="Back to home"><span class="aimeat-cta-spark">⚡</span><span id="aimeat-home-label">aimeat.io</span></a>
2162
2203
  <h1 data-i18n="header.title">My Apps</h1>
2163
- <div class="mode-toggle-wrap">
2164
- <span class="mode-caption" data-i18n="mode.caption" data-i18n-title="mode.caption.title" title="These toggle which browser-local apps you see — not server visibility">Local apps</span>
2165
- <div class="mode-toggle">
2166
- <button id="mode-global" class="mode-btn active" onclick="window._launcher.switchDbMode('global')" data-i18n="mode.global" data-i18n-title="mode.global.title" title="Shared across all accounts on this browser">&#x1F310; Shared</button>
2167
- <button id="mode-personal" class="mode-btn" onclick="window._launcher.switchDbMode('personal')" data-i18n="mode.personal" data-i18n-title="mode.personal.title" title="Private to your account">&#x1F464; Mine</button>
2168
- </div>
2169
- </div>
2170
2204
  <input type="text" id="search-input" placeholder="Search apps..." data-i18n-ph="search.ph" autocomplete="off" spellcheck="false"/>
2171
2205
  <div class="header-actions">
2172
2206
  <span id="headerAuth" class="header-auth-pill"></span>
@@ -2267,10 +2301,10 @@
2267
2301
  <p class="view-empty-hint" data-i18n="view.communityHint">Community apps are apps other people on this node have published. When someone publishes one, it shows up here.</p>
2268
2302
  </div>
2269
2303
 
2270
- <!-- Recently Opened Strip -->
2271
- <div id="recent-section" data-view="library" style="display:none;margin:0 24px 1rem">
2272
- <h3 style="margin:0 0 .5rem;font-size:.9rem;color:var(--text-muted)" data-i18n="sec.recent">&#x1F552; Recently Opened</h3>
2273
- <div id="recent-strip" style="display:flex;gap:.75rem;overflow-x:auto;padding-bottom:.5rem"></div>
2304
+ <!-- Favourites group (Library) — apps the owner favourited, pinned above "Your apps" -->
2305
+ <div id="favorites-section" data-view="library" style="display:none;margin:0 24px 1rem">
2306
+ <h2 class="fav-section-title"><span data-i18n="fav.title">⭐ Favourites</span> <span id="favorites-count" class="fav-section-count"></span></h2>
2307
+ <div id="favorites-grid" class="published-grid"></div>
2274
2308
  </div>
2275
2309
 
2276
2310
  <div id="local-apps-block" class="local-apps-block" data-view="library">
@@ -2291,18 +2325,10 @@
2291
2325
  <div class="modal" id="add-app-modal">
2292
2326
  <h2 id="modal-title" data-i18n="addModal.title">Add App</h2>
2293
2327
 
2294
- <!-- Source tabs -->
2328
+ <!-- Source tabs — server-only: paste HTML or upload an HTML/ZIP file; it publishes to your account. -->
2295
2329
  <div class="modal-tabs">
2296
- <button class="modal-tab active" data-tab="url" data-i18n="tab.url">URL</button>
2330
+ <button class="modal-tab active" data-tab="paste" data-i18n="tab.paste">Paste</button>
2297
2331
  <button class="modal-tab" data-tab="file" data-i18n="tab.file">File</button>
2298
- <button class="modal-tab" data-tab="paste" data-i18n="tab.paste">Paste</button>
2299
- <button class="modal-tab" data-tab="aimeat" data-i18n="tab.aimeat">AIMEAT</button>
2300
- </div>
2301
-
2302
- <!-- URL tab content -->
2303
- <div class="tab-content active" id="tab-url">
2304
- <label class="modal-label" for="app-url" data-i18n="addModal.url">App URL</label>
2305
- <input type="url" id="app-url" class="modal-input" placeholder="https://example.com/app.html" autocomplete="off"/>
2306
2332
  </div>
2307
2333
 
2308
2334
  <!-- File tab content -->
@@ -2318,22 +2344,12 @@
2318
2344
  </div>
2319
2345
  </div>
2320
2346
 
2321
- <!-- Paste tab content -->
2322
- <div class="tab-content" id="tab-paste">
2347
+ <!-- Paste tab content (default) -->
2348
+ <div class="tab-content active" id="tab-paste">
2323
2349
  <label class="modal-label" for="app-paste-code" data-i18n="addModal.paste">Paste HTML Source Code</label>
2324
2350
  <textarea id="app-paste-code" class="modal-input" rows="10" placeholder="Paste your HTML source code here..." data-i18n-ph="addModal.pastePh" style="font-family:'Consolas','Monaco','Courier New',monospace;font-size:.8rem;resize:vertical;min-height:160px;line-height:1.4"></textarea>
2325
2351
  </div>
2326
2352
 
2327
- <!-- AIMEAT tab content -->
2328
- <div class="tab-content" id="tab-aimeat">
2329
- <p style="font-size:.85rem;color:var(--text-muted);margin-bottom:14px;line-height:1.5" data-i18n="addModal.aimeatHelp">
2330
- Import apps from a connected AIMEAT server. Configure the server URL in Settings first.
2331
- </p>
2332
- <button type="button" class="modal-btn primary" onclick="window._launcher.importFromAimeat()" style="width:100%" data-i18n="addModal.fetch">
2333
- Fetch Available Apps
2334
- </button>
2335
- </div>
2336
-
2337
2353
  <!-- Common fields -->
2338
2354
  <div class="modal-fields">
2339
2355
  <div class="modal-row">
@@ -2468,21 +2484,6 @@
2468
2484
  </div>
2469
2485
  </div>
2470
2486
 
2471
- <div class="settings-section">
2472
- <div class="settings-section-title" data-i18n="settings.backup">Backup &amp; Restore</div>
2473
- <div class="settings-backup-actions">
2474
- <button type="button" class="modal-btn secondary" onclick="window._launcher.exportBackup()" data-i18n="settings.exportJson">Export JSON</button>
2475
- <button type="button" class="modal-btn secondary" onclick="document.getElementById('import-file-input').click()" data-i18n="settings.importJson">Import JSON</button>
2476
- <button type="button" class="modal-btn secondary" onclick="window._launcher.removeDuplicateApps()" data-i18n="dedup.btn">&#x1F9F9; Remove duplicate apps</button>
2477
- <input type="file" id="import-file-input" accept=".json" hidden/>
2478
- </div>
2479
- </div>
2480
-
2481
- <div class="settings-section" style="margin-bottom:0">
2482
- <div class="settings-section-title" data-i18n="settings.danger">Danger Zone</div>
2483
- <button type="button" class="modal-btn danger" onclick="window._launcher.clearAllData()" data-i18n="settings.clearAll">Clear All Data</button>
2484
- </div>
2485
-
2486
2487
  <div class="modal-actions" style="margin-top:24px">
2487
2488
  <button type="button" class="modal-btn secondary" id="settings-cancel-btn" data-i18n="common.cancel">Cancel</button>
2488
2489
  <button type="button" class="modal-btn primary" id="settings-save-btn" data-i18n="common.save">Save</button>
@@ -2509,18 +2510,6 @@
2509
2510
  </div>
2510
2511
  </div>
2511
2512
 
2512
- <!-- AIMEAT Import Dialog (dynamically populated) -->
2513
- <div id="aimeat-import-overlay" hidden>
2514
- <div class="modal" id="aimeat-import-modal">
2515
- <h2 data-i18n="import.title">Import from AIMEAT</h2>
2516
- <div class="aimeat-app-list" id="aimeat-app-list"></div>
2517
- <div class="modal-actions">
2518
- <button type="button" class="modal-btn secondary" onclick="document.getElementById('aimeat-import-overlay').hidden = true" data-i18n="common.cancel">Cancel</button>
2519
- <button type="button" class="modal-btn primary" onclick="window._launcher.processAimeatImport()" data-i18n="import.selected">Import Selected</button>
2520
- </div>
2521
- </div>
2522
- </div>
2523
-
2524
2513
  <!-- Publish Modal -->
2525
2514
  <div id="publish-overlay" hidden>
2526
2515
  <div class="modal" id="publish-modal">
@@ -2548,20 +2537,6 @@
2548
2537
  </div>
2549
2538
  </div>
2550
2539
 
2551
- <!-- JSON Backup Import Modal (user-selected, duplicate-aware) -->
2552
- <div id="json-import-overlay" hidden>
2553
- <div class="modal" id="json-import-modal">
2554
- <h2 data-i18n="jsonImport.title">&#x1F4E5; Import JSON backup</h2>
2555
- <div id="json-import-status" style="margin:4px 0;font-size:.85rem;min-height:1.1em"></div>
2556
- <div id="json-import-body" style="overflow-y:auto;max-height:55vh"></div>
2557
- <div class="modal-actions">
2558
- <button type="button" class="modal-btn secondary" onclick="document.getElementById('json-import-overlay').hidden = true" data-i18n="common.cancel">Cancel</button>
2559
- <button type="button" class="modal-btn primary" id="json-import-btn" onclick="window._launcher.submitJsonImport()" data-i18n="srvImport.import">Import selected</button>
2560
- </div>
2561
- </div>
2562
- </div>
2563
-
2564
-
2565
2540
  <!-- Backup Import Modal -->
2566
2541
  <div id="backup-overlay" hidden>
2567
2542
  <div class="modal" id="backup-modal">
@@ -2717,14 +2692,14 @@
2717
2692
  "action.settings": "Settings",
2718
2693
  "action.help": "Help",
2719
2694
  "help.title": "❓ How this catalog works",
2720
- "help.modesTitle": "Shared vs Mine (browser-local apps)",
2721
- "help.modes": 'The header toggle picks which BROWSER-LOCAL apps you see it is NOT about server visibility. "Shared" = local apps visible to every account signed in on this browser; "Mine" = local apps private to your account (needs you to be signed in). Your published/server apps are unaffected either way.',
2695
+ "help.modesTitle": "Creating an app",
2696
+ "help.modes": "Add app lets you paste HTML or upload an HTML/ZIP file (or Generate with AI). It publishes straight to your account as UNLISTED on the server but not yet public so you can review it before listing. Nothing is stored only in your browser.",
2722
2697
  "help.whereTitle": "Where your apps live",
2723
- "help.where": "Local apps live only in this browser (IndexedDB)they don't follow you to another browser or device. Publish an app to put it on the server, then you can manage it from anywhere. Each app shows as ONE card with a badge saying where it lives.",
2698
+ "help.where": "Every app lives on the server under your account the catalog is server-only, so nothing is kept in this browser. Your apps follow you to any browser or device automatically.",
2724
2699
  "help.sectionsTitle": "The two views",
2725
- "help.sections": "Two tabs. Library = your apps, each as one card whose badge shows its publication state — Listed (published, public in the catalogue), Unlisted (on the server but hidden from the catalogue, still usable by you and anyone with the link), or Local (a draft in this browser, not on the server yet). A purple Staging pill marks an app that also has an unpublished draft waiting. Community = apps other people have published.",
2700
+ "help.sections": "Two tabs. Library = your apps, each as one card whose badge shows its publication state — Listed (public in the catalogue) or Unlisted (on the server but hidden from the catalogue, still usable by you and anyone with the link). A purple Staging pill marks an app that also has an unpublished draft waiting. Community = apps other people have published.",
2726
2701
  "help.backupTitle": '"Backup" means two different things',
2727
- "help.backup": "The 💾 menu (top bar) exports/imports your SERVER apps + extensions as a .zip. Settings Export/Import JSON backs up this browser's LOCAL catalog. They are separate.",
2702
+ "help.backup": "The 💾 menu (top bar) exports/imports your SERVER apps + extensions as a .zip a full server-side backup you can restore selectively.",
2728
2703
  "help.standaloneTitle": "Which node this talks to",
2729
2704
  "help.standalone": "This catalog is served by its node and always talks to that node (same-origin) — the Server URL in Settings is fixed to it. To use your own, open /app-catalog.html on your node: localhost, aimeat-desktop, and federated nodes each serve their OWN catalog.",
2730
2705
  "action.theme": "Toggle light / dark",
@@ -2759,8 +2734,11 @@
2759
2734
  "sec.parkedBlurb": "Only you can see and use these — they stay usable but are hidden from the public catalogue. Press Publish to move one back to Published Apps.",
2760
2735
  "sec.recent": "🕒 Recently Opened",
2761
2736
  "sec.local": "📚 Your apps",
2762
- "sec.localNote": "One card per app. The badge shows its publication state — Listed (published, public in the catalogue), Unlisted (on the server but hidden from the public catalogue), or Local (a draft in this browser, not on the server yet). A purple Staging pill marks an app that also has an unpublished draft.",
2763
- "sec.localHint": "💡 Publish a local app to put it on the server then you can manage it from anywhere.",
2737
+ "sec.localNote": "One card per app. The badge shows its publication state — Listed (public in the catalogue) or Unlisted (on the server but hidden from the public catalogue). A purple Staging pill marks an app that also has an unpublished draft.",
2738
+ "sec.localHint": "💡 Add app publishes to your account as unlistedlist it when it's ready to be public.",
2739
+ "fav.title": "⭐ Favourites",
2740
+ "fav.add": "Add to favourites",
2741
+ "fav.remove": "Remove from favourites",
2764
2742
  "stats.apps": "apps",
2765
2743
  "stats.stored": "stored",
2766
2744
  "addModal.title": "Add App",
@@ -2826,6 +2804,8 @@
2826
2804
  "publish.accessCodeHint": "4-64 chars. If set, only people with the code can download.",
2827
2805
  "publish.submit": "Publish",
2828
2806
  "publish.loginRequired": "Sign in to publish to AIMEAT. Anonymous publishing is disabled — use the Sign in button in the top bar.",
2807
+ "publish.savedUnlisted": "Saved — your app is on the server as unlisted. Open it and press List when you want it public.",
2808
+ "migrate.found": "You have {n} app(s) saved only in this browser from the old local catalog. The catalog is now server-only, so they no longer appear here. Download them as a JSON backup now? You can re-publish any you still want.",
2829
2809
  "auth.signIn": "❤️ Sign In",
2830
2810
  "auth.loggedIn": "logged in",
2831
2811
  "auth.logout": "Logout",
@@ -2954,6 +2934,24 @@
2954
2934
  "detail.iconLabel": "Icon (emoji)",
2955
2935
  "detail.tagsLabel": "Tags (comma separated)",
2956
2936
  "detail.descLabel": "Description",
2937
+ "detail.descEn": "Description (English)",
2938
+ "detail.descFi": "Description (Finnish)",
2939
+ "detail.translateEnFi": "Translate EN → FI",
2940
+ "detail.translateFiEn": "Translate FI → EN",
2941
+ "detail.translating": "Translating…",
2942
+ "detail.trDone": "Translated",
2943
+ "detail.trEmpty": "The AI returned nothing — try again.",
2944
+ "detail.trNeedSource": "Write the source-language description first.",
2945
+ "promote.title": "Promote",
2946
+ "promote.hint": "Showcase this app on your public profile with a short pitch. Fill English and/or Finnish; visitors see the one matching their language.",
2947
+ "promote.en": "Promotion text (English)",
2948
+ "promote.fi": "Promotion text (Finnish)",
2949
+ "promote.save": "Save promotion",
2950
+ "promote.remove": "Stop promoting",
2951
+ "promote.on": "Promoted",
2952
+ "promote.saving": "Saving…",
2953
+ "promote.saved": "Promoted on your profile.",
2954
+ "promote.removed": "No longer promoted.",
2957
2955
  "detail.renameHint": "The app link stays the same — only the displayed name and description change.",
2958
2956
  "detail.nameRequired": "Name cannot be empty.",
2959
2957
  "detail.descRequired": "Description cannot be empty.",
@@ -3028,6 +3026,20 @@
3028
3026
  "monetize.saved": "Tool manifest saved.",
3029
3027
  "monetize.saveFailed": "Saving the tool manifest failed",
3030
3028
  "monetize.needLogin": "Sign in to edit the tool manifest.",
3029
+ "cost.title": "📊 Cost & contracts — what this app sources",
3030
+ "cost.hint": "This app’s EXCHANGE contracts: durable metered entitlements it calls, with live spend against each budget and the platform rake. The per-call price comes from the provider; you set only the budget cap.",
3031
+ "cost.empty": "No EXCHANGE contracts yet — this app sources nothing priced.",
3032
+ "cost.summary": "{n} contract(s) · {spent} morsels spent · {calls} call(s)",
3033
+ "cost.capabilityCol": "Capability",
3034
+ "cost.providerCol": "Provider",
3035
+ "cost.priceCol": "Price / call",
3036
+ "cost.rakeCol": "Rake",
3037
+ "cost.budgetCol": "Budget (spent / cap)",
3038
+ "cost.stateCol": "State",
3039
+ "cost.uncapped": "uncapped",
3040
+ "cost.remaining": "{n} left",
3041
+ "cost.needLogin": "Sign in to view this app’s cost & contracts.",
3042
+ "cost.loadFailed": "Could not load cost & contracts",
3031
3043
  "detail.editAccess": "Access code",
3032
3044
  "detail.accessCodeHint": "Set or remove an optional code required to open this app.",
3033
3045
  "detail.accessCodeLabel": "Access code",
@@ -3124,7 +3136,7 @@
3124
3136
  "tag.favorites": "⭐ Favorites",
3125
3137
  "empty.noApps": "No apps yet",
3126
3138
  "empty.noAppsDesc": "Add your first app using the + button above",
3127
- "empty.formats": "Supports: HTML files, URLs, ZIP packages, AIMEAT imports",
3139
+ "empty.formats": "Supports: HTML files and ZIP packages",
3128
3140
  "empty.noMatch": "No matches",
3129
3141
  "empty.noMatchDesc": "No apps match the current filter. Try a different search term or tag."
3130
3142
  },
@@ -3147,14 +3159,14 @@
3147
3159
  "action.settings": "Asetukset",
3148
3160
  "action.help": "Ohje",
3149
3161
  "help.title": "❓ Miten tämä katalogi toimii",
3150
- "help.modesTitle": "Jaetut vs Omat (selain-paikalliset appit)",
3151
- "help.modes": 'Yläpalkin valitsin päättää mitkä SELAIN-PAIKALLISET appit näet se EI liity server-näkyvyyteen. "Jaetut" = paikalliset appit näkyvät kaikille tähän selaimeen kirjautuneille tileille; "Omat" = paikalliset appit yksityisiä tilillesi (vaatii sisäänkirjautumisen). Julkaistut/server-appisi eivät muutu kummassakaan.',
3162
+ "help.modesTitle": "Sovelluksen luominen",
3163
+ "help.modes": "Lisää sovellus antaa liittää HTML:ää tai ladata HTML/ZIP-tiedoston (tai luoda sen tekoälyllä). Se julkaistaan suoraan tilillesi EI LISTALLA palvelimelle mutta ei vielä julkiseksi joten voit tarkistaa sen ennen listaamista. Mitään ei tallenneta vain selaimeesi.",
3152
3164
  "help.whereTitle": "Missä appisi asuvat",
3153
- "help.where": "Paikalliset appit asuvat vain tässä selaimessa (IndexedDB) ne eivät seuraa toiseen selaimeen tai laitteeseen. Julkaise appi siirtääksesi sen palvelimelle, minkä jälkeen hallitset sitä mistä tahansa. Jokainen appi näkyy YHTENÄ korttina, jonka merkki kertoo missä se sijaitsee.",
3165
+ "help.where": "Jokainen sovellus sijaitsee palvelimella tililläsikatalogi on vain palvelinpohjainen, joten mitään ei säilytetä tässä selaimessa. Sovelluksesi seuraavat sinua mihin tahansa selaimeen tai laitteeseen automaattisesti.",
3154
3166
  "help.sectionsTitle": "Kaksi näkymää",
3155
- "help.sections": "Kaksi välilehteä. Kirjasto = sinun appisi, jokainen yhtenä korttina jonka merkki kertoo julkaisutilan — Listalla (julkaistu, näkyy katalogissa), Ei listalla (palvelimella mutta piilossa katalogista, sinä ja kuka tahansa linkillä voitte yhä käyttää), tai Paikallinen (luonnos tässä selaimessa, ei vielä palvelimella). Violetti Staging-merkki kertoo että apilla on myös julkaisematon luonnos odottamassa. Yhteisö = muiden julkaisemat appit.",
3167
+ "help.sections": "Kaksi välilehteä. Kirjasto = sinun appisi, jokainen yhtenä korttina jonka merkki kertoo julkaisutilan — Listalla (näkyy julkisessa katalogissa) tai Ei listalla (palvelimella mutta piilossa katalogista, sinä ja kuka tahansa linkillä voitte yhä käyttää). Violetti Staging-merkki kertoo että apilla on myös julkaisematon luonnos odottamassa. Yhteisö = muiden julkaisemat appit.",
3156
3168
  "help.backupTitle": '"Varmuuskopio" tarkoittaa kahta eri asiaa',
3157
- "help.backup": "💾-valikko (yläpalkki) vie/tuo PALVELIN-appisi + laajennukset .zip-tiedostona. Asetukset Vie/Tuo JSON varmuuskopioi tämän selaimen PAIKALLISEN katalogin. Ne ovat erillisiä.",
3169
+ "help.backup": "💾-valikko (yläpalkki) vie/tuo PALVELIN-appisi + laajennukset .zip-tiedostona täysi palvelinpuolen varmuuskopio, jonka voit palauttaa valikoiden.",
3158
3170
  "help.standaloneTitle": "Mille nodelle tämä puhuu",
3159
3171
  "help.standalone": "Tämän katalogin tarjoilee sen node, ja se puhuu aina sille nodelle (same-origin) — Asetusten Palvelin-URL on kiinnitetty siihen. Käyttääksesi omaa: avaa /app-catalog.html omalla nodellasi. localhost, aimeat-desktop ja federoidut nodet tarjoilevat kukin OMAN kataloginsa.",
3160
3172
  "action.theme": "Vaihda vaalea / tumma",
@@ -3189,8 +3201,11 @@
3189
3201
  "sec.parkedBlurb": "Vain sinä näet ja voit käyttää näitä — ne pysyvät käytettävissä mutta eivät näy julkisessa katalogissa. Paina Julkaise siirtääksesi takaisin julkaistuihin.",
3190
3202
  "sec.recent": "🕒 Viimeksi avatut",
3191
3203
  "sec.local": "📚 Sinun sovelluksesi",
3192
- "sec.localNote": "Yksi kortti per sovellus. Merkki kertoo julkaisutilan — Listalla (julkaistu, näkyy katalogissa), Ei listalla (palvelimella mutta piilossa julkisesta katalogista) tai Paikallinen (luonnos tässä selaimessa, ei vielä palvelimella). Violetti Staging-merkki kertoo että apilla on myös julkaisematon luonnos.",
3193
- "sec.localHint": "💡 Julkaise paikallinen sovellus viedäksesi sen palvelimellesen jälkeen voit hallita sitä mistä tahansa.",
3204
+ "sec.localNote": "Yksi kortti per sovellus. Merkki kertoo julkaisutilan — Listalla (näkyy julkisessa katalogissa) tai Ei listalla (palvelimella mutta piilossa julkisesta katalogista). Violetti Staging-merkki kertoo että apilla on myös julkaisematon luonnos.",
3205
+ "sec.localHint": "💡 Lisää sovellus julkaisee tilillesi ei-listallalistaa se kun se on valmis julkiseksi.",
3206
+ "fav.title": "⭐ Suosikit",
3207
+ "fav.add": "Lisää suosikkeihin",
3208
+ "fav.remove": "Poista suosikeista",
3194
3209
  "stats.apps": "sovellusta",
3195
3210
  "stats.stored": "tallennettu",
3196
3211
  "addModal.title": "Lisää sovellus",
@@ -3256,6 +3271,8 @@
3256
3271
  "publish.accessCodeHint": "4–64 merkkiä. Jos asetettu, vain koodin tietävät voivat ladata.",
3257
3272
  "publish.submit": "Julkaise",
3258
3273
  "publish.loginRequired": "Kirjaudu sisään julkaistaksesi AIMEATiin. Anonyymi julkaisu on poistettu käytöstä — käytä yläpalkin Kirjaudu-painiketta.",
3274
+ "publish.savedUnlisted": "Tallennettu — sovelluksesi on palvelimella ei-listalla. Avaa se ja paina Listaa kun haluat sen julkiseksi.",
3275
+ "migrate.found": "Sinulla on {n} sovellus(ta) tallennettuna vain tähän selaimeen vanhasta paikallisesta katalogista. Katalogi on nyt vain palvelinpohjainen, joten ne eivät enää näy täällä. Ladataanko ne nyt JSON-varmuuskopiona? Voit julkaista uudelleen ne jotka haluat säilyttää.",
3259
3276
  "auth.signIn": "❤️ Kirjaudu",
3260
3277
  "auth.loggedIn": "kirjautunut",
3261
3278
  "auth.logout": "Kirjaudu ulos",
@@ -3384,6 +3401,24 @@
3384
3401
  "detail.iconLabel": "Kuvake (emoji)",
3385
3402
  "detail.tagsLabel": "Tunnisteet (pilkulla eroteltuna)",
3386
3403
  "detail.descLabel": "Kuvaus",
3404
+ "detail.descEn": "Kuvaus (englanti)",
3405
+ "detail.descFi": "Kuvaus (suomi)",
3406
+ "detail.translateEnFi": "Käännä EN → FI",
3407
+ "detail.translateFiEn": "Käännä FI → EN",
3408
+ "detail.translating": "Käännetään…",
3409
+ "detail.trDone": "Käännetty",
3410
+ "detail.trEmpty": "Tekoäly ei palauttanut mitään — yritä uudelleen.",
3411
+ "detail.trNeedSource": "Kirjoita ensin lähdekielinen kuvaus.",
3412
+ "promote.title": "Nosto",
3413
+ "promote.hint": "Nosta tämä sovellus julkiseen profiiliisi lyhyellä myyntipuheella. Täytä englanti ja/tai suomi; vierailijat näkevät kieltään vastaavan.",
3414
+ "promote.en": "Nostoteksti (englanti)",
3415
+ "promote.fi": "Nostoteksti (suomi)",
3416
+ "promote.save": "Tallenna nosto",
3417
+ "promote.remove": "Lopeta nostaminen",
3418
+ "promote.on": "Nostettu",
3419
+ "promote.saving": "Tallennetaan…",
3420
+ "promote.saved": "Nostettu profiiliisi.",
3421
+ "promote.removed": "Ei enää nostettu.",
3387
3422
  "detail.renameHint": "Sovelluksen linkki pysyy samana — vain näytettävä nimi ja kuvaus muuttuvat.",
3388
3423
  "detail.nameRequired": "Nimi ei voi olla tyhjä.",
3389
3424
  "detail.descRequired": "Kuvaus ei voi olla tyhjä.",
@@ -3456,6 +3491,20 @@
3456
3491
  "monetize.saved": "Työkalumanifesti tallennettu.",
3457
3492
  "monetize.saveFailed": "Työkalumanifestin tallennus epäonnistui",
3458
3493
  "monetize.needLogin": "Kirjaudu sisään muokataksesi työkalumanifestia.",
3494
+ "cost.title": "📊 Kulut & sopimukset — mitä tämä appi hankkii",
3495
+ "cost.hint": "Tämän appin EXCHANGE-sopimukset: pysyvät mitatut käyttöoikeudet joita se kutsuu, live-kulutus kutakin budjettia vasten ja alustan siivu. Kutsukohtainen hinta tulee tarjoajalta; sinä asetat vain budjettikaton.",
3496
+ "cost.empty": "Ei vielä EXCHANGE-sopimuksia — tämä appi ei hanki mitään hinnoiteltua.",
3497
+ "cost.summary": "{n} sopimus(ta) · {spent} morselia käytetty · {calls} kutsu(a)",
3498
+ "cost.capabilityCol": "Kyvykkyys",
3499
+ "cost.providerCol": "Tarjoaja",
3500
+ "cost.priceCol": "Hinta / kutsu",
3501
+ "cost.rakeCol": "Siivu",
3502
+ "cost.budgetCol": "Budjetti (käytetty / katto)",
3503
+ "cost.stateCol": "Tila",
3504
+ "cost.uncapped": "ei kattoa",
3505
+ "cost.remaining": "{n} jäljellä",
3506
+ "cost.needLogin": "Kirjaudu sisään nähdäksesi tämän appin kulut & sopimukset.",
3507
+ "cost.loadFailed": "Kulujen & sopimusten lataus epäonnistui",
3459
3508
  "detail.skillDetach": "Irrota",
3460
3509
  "detail.skillAttachError": "Taidon liittäminen epäonnistui",
3461
3510
  "detail.editAccess": "Pääsykoodi",
@@ -3554,7 +3603,7 @@
3554
3603
  "tag.favorites": "⭐ Suosikit",
3555
3604
  "empty.noApps": "Ei vielä sovelluksia",
3556
3605
  "empty.noAppsDesc": "Lisää ensimmäinen sovellus yllä olevalla +-painikkeella",
3557
- "empty.formats": "Tukee: HTML-tiedostot, URL-osoitteet, ZIP-paketit, AIMEAT-tuonnit",
3606
+ "empty.formats": "Tukee: HTML-tiedostot ja ZIP-paketit",
3558
3607
  "empty.noMatch": "Ei osumia",
3559
3608
  "empty.noMatchDesc": "Mikään sovellus ei vastaa nykyistä suodatinta. Kokeile eri hakusanaa tai tunnistetta."
3560
3609
  }
@@ -3677,128 +3726,25 @@
3677
3726
  }
3678
3727
 
3679
3728
  // src/static/app-catalog/js/db.js
3680
- var DB_NAME_GLOBAL = "AppLauncherDB";
3681
- var DB_VERSION = 2;
3682
- var STORE_NAME2 = "apps";
3683
- var dbInstance = null;
3684
- var currentDbMode = localStorage.getItem("appCatalogMode") || "global";
3685
- function getDbMode() {
3686
- return currentDbMode;
3687
- }
3688
- function setDbMode(mode) {
3689
- currentDbMode = mode;
3690
- try {
3691
- localStorage.setItem("appCatalogMode", mode);
3692
- } catch (e) {
3693
- }
3694
- }
3695
- function closeDbInstance() {
3696
- if (dbInstance) {
3697
- try {
3698
- dbInstance.close();
3699
- } catch (e) {
3700
- }
3701
- dbInstance = null;
3702
- }
3703
- }
3704
- function getDbName() {
3705
- if (currentDbMode === "personal") {
3706
- var owner = null;
3707
- try {
3708
- if (window.AIMEAT && window.AIMEAT.auth && window.AIMEAT.auth.getSession()) {
3709
- owner = window.AIMEAT.auth.getSession().owner || null;
3710
- }
3711
- if (!owner) {
3712
- var stored = localStorage.getItem("aimeat_session");
3713
- if (stored) owner = JSON.parse(stored).owner || null;
3714
- }
3715
- } catch (e) {
3716
- }
3717
- if (owner) return DB_NAME_GLOBAL + "_" + owner;
3718
- }
3719
- return DB_NAME_GLOBAL;
3720
- }
3721
- function openDB() {
3722
- var name = getDbName();
3723
- if (dbInstance && dbInstance.name === name) return Promise.resolve(dbInstance);
3724
- if (dbInstance) {
3725
- dbInstance.close();
3726
- dbInstance = null;
3727
- }
3728
- return new Promise(function(resolve, reject) {
3729
- const request = indexedDB.open(name, DB_VERSION);
3730
- request.onupgradeneeded = function(event) {
3731
- const db = event.target.result;
3732
- var store;
3733
- if (!db.objectStoreNames.contains(STORE_NAME2)) {
3734
- store = db.createObjectStore(STORE_NAME2, { keyPath: "id" });
3735
- store.createIndex("tags", "tags", { multiEntry: true });
3736
- store.createIndex("source", "source", { unique: false });
3737
- store.createIndex("favorite", "favorite", { unique: false });
3738
- store.createIndex("sortOrder", "sortOrder", { unique: false });
3739
- } else {
3740
- store = event.target.transaction.objectStore(STORE_NAME2);
3741
- if (!store.indexNames.contains("sortOrder")) {
3742
- store.createIndex("sortOrder", "sortOrder", { unique: false });
3743
- }
3744
- }
3745
- };
3746
- request.onsuccess = function(event) {
3747
- dbInstance = event.target.result;
3748
- resolve(dbInstance);
3749
- };
3750
- request.onerror = function(event) {
3751
- reject(event.target.error);
3752
- };
3753
- });
3754
- }
3729
+ var workingSet = [];
3755
3730
  function getAllApps() {
3756
- return openDB().then(function(db) {
3757
- return new Promise(function(resolve, reject) {
3758
- const tx = db.transaction(STORE_NAME2, "readonly");
3759
- const request = tx.objectStore(STORE_NAME2).getAll();
3760
- request.onsuccess = function() {
3761
- resolve(request.result);
3762
- };
3763
- request.onerror = function() {
3764
- reject(request.error);
3765
- };
3766
- });
3767
- });
3731
+ return Promise.resolve(workingSet.slice());
3768
3732
  }
3769
3733
  function saveApp(app) {
3770
- return openDB().then(function(db) {
3771
- return new Promise(function(resolve, reject) {
3772
- const tx = db.transaction(STORE_NAME2, "readwrite");
3773
- tx.objectStore(STORE_NAME2).put(app);
3774
- tx.oncomplete = function() {
3775
- resolve();
3776
- };
3777
- tx.onerror = function() {
3778
- reject(tx.error);
3779
- };
3780
- tx.onabort = function() {
3781
- reject(tx.error);
3782
- };
3734
+ if (app && app.id) {
3735
+ var idx = workingSet.findIndex(function(a) {
3736
+ return a.id === app.id;
3783
3737
  });
3784
- });
3738
+ if (idx >= 0) workingSet[idx] = app;
3739
+ else workingSet.push(app);
3740
+ }
3741
+ return Promise.resolve();
3785
3742
  }
3786
3743
  function deleteApp(id) {
3787
- return openDB().then(function(db) {
3788
- return new Promise(function(resolve, reject) {
3789
- const tx = db.transaction(STORE_NAME2, "readwrite");
3790
- tx.objectStore(STORE_NAME2).delete(id);
3791
- tx.oncomplete = function() {
3792
- resolve();
3793
- };
3794
- tx.onerror = function() {
3795
- reject(tx.error);
3796
- };
3797
- tx.onabort = function() {
3798
- reject(tx.error);
3799
- };
3800
- });
3744
+ workingSet = workingSet.filter(function(a) {
3745
+ return a.id !== id;
3801
3746
  });
3747
+ return Promise.resolve();
3802
3748
  }
3803
3749
 
3804
3750
  // src/static/app-catalog/js/ui.js
@@ -4372,9 +4318,9 @@
4372
4318
  function loadPbCore() {
4373
4319
  var config = loadConfig();
4374
4320
  if (!config.aimeatUrl) return;
4375
- var base = config.aimeatUrl.replace(/\/+$/, "");
4321
+ var base3 = config.aimeatUrl.replace(/\/+$/, "");
4376
4322
  ["new", "improve"].forEach(function(mode) {
4377
- fetch(base + "/v1/prompts/build-app?mode=" + mode + "&lang=" + encodeURIComponent(getLang())).then(function(r) {
4323
+ fetch(base3 + "/v1/prompts/build-app?mode=" + mode + "&lang=" + encodeURIComponent(getLang())).then(function(r) {
4378
4324
  return r.json();
4379
4325
  }).then(function(d) {
4380
4326
  var body = d && d.data && d.data.body;
@@ -4391,8 +4337,8 @@
4391
4337
  if (!sel) return;
4392
4338
  var config = loadConfig();
4393
4339
  if (!config.aimeatUrl) return;
4394
- var base = config.aimeatUrl.replace(/\/+$/, "");
4395
- fetch(base + "/v1/app-templates?lang=" + encodeURIComponent(getLang())).then(function(r) {
4340
+ var base3 = config.aimeatUrl.replace(/\/+$/, "");
4341
+ fetch(base3 + "/v1/app-templates?lang=" + encodeURIComponent(getLang())).then(function(r) {
4396
4342
  return r.json();
4397
4343
  }).then(function(d) {
4398
4344
  var list = (d.data && d.data.templates || []).filter(function(t2) {
@@ -4416,7 +4362,7 @@
4416
4362
  updatePbPreview();
4417
4363
  return;
4418
4364
  }
4419
- fetch(base + "/v1/app-templates/" + encodeURIComponent(id)).then(function(r) {
4365
+ fetch(base3 + "/v1/app-templates/" + encodeURIComponent(id)).then(function(r) {
4420
4366
  return r.json();
4421
4367
  }).then(function(d2) {
4422
4368
  window.pbTemplate = d2.data && d2.data.template || null;
@@ -4442,8 +4388,8 @@
4442
4388
  if (!wrap) return;
4443
4389
  var config = loadConfig();
4444
4390
  if (!config.aimeatUrl) return;
4445
- var base = config.aimeatUrl.replace(/\/+$/, "");
4446
- fetch(base + "/v1/library-packs?lang=" + encodeURIComponent(getLang())).then(function(r) {
4391
+ var base3 = config.aimeatUrl.replace(/\/+$/, "");
4392
+ fetch(base3 + "/v1/library-packs?lang=" + encodeURIComponent(getLang())).then(function(r) {
4447
4393
  return r.json();
4448
4394
  }).then(function(d) {
4449
4395
  var all = d.data && d.data.packs || [];
@@ -4464,7 +4410,7 @@
4464
4410
  cb.value = p.id;
4465
4411
  cb.onchange = function() {
4466
4412
  pbPackTouched[p.id] = true;
4467
- if (cb.checked) loadPbPackDoc(base, p.id);
4413
+ if (cb.checked) loadPbPackDoc(base3, p.id);
4468
4414
  else updatePbPreview();
4469
4415
  };
4470
4416
  var span = document.createElement("span");
@@ -4487,14 +4433,14 @@
4487
4433
  if (desc && !desc._pbPackListener) {
4488
4434
  desc._pbPackListener = true;
4489
4435
  desc.addEventListener("input", function() {
4490
- pbMatchPacksToIdea(base);
4436
+ pbMatchPacksToIdea(base3);
4491
4437
  });
4492
4438
  }
4493
- pbMatchPacksToIdea(base);
4439
+ pbMatchPacksToIdea(base3);
4494
4440
  }).catch(function() {
4495
4441
  });
4496
4442
  }
4497
- function pbMatchPacksToIdea(base) {
4443
+ function pbMatchPacksToIdea(base3) {
4498
4444
  var desc = document.getElementById("pb-description");
4499
4445
  var text = (desc && desc.value || "").toLowerCase();
4500
4446
  pbPacks.forEach(function(p) {
@@ -4507,19 +4453,19 @@
4507
4453
  });
4508
4454
  if (hit && !cb.checked) {
4509
4455
  cb.checked = true;
4510
- loadPbPackDoc(base, p.id);
4456
+ loadPbPackDoc(base3, p.id);
4511
4457
  } else if (!hit && cb.checked) {
4512
4458
  cb.checked = false;
4513
4459
  updatePbPreview();
4514
4460
  }
4515
4461
  });
4516
4462
  }
4517
- function loadPbPackDoc(base, id) {
4463
+ function loadPbPackDoc(base3, id) {
4518
4464
  if (pbPackDocs[id]) {
4519
4465
  updatePbPreview();
4520
4466
  return;
4521
4467
  }
4522
- fetch(base + "/v1/library-packs/" + encodeURIComponent(id)).then(function(r) {
4468
+ fetch(base3 + "/v1/library-packs/" + encodeURIComponent(id)).then(function(r) {
4523
4469
  return r.json();
4524
4470
  }).then(function(d) {
4525
4471
  var pack = d.data && d.data.pack;
@@ -4710,6 +4656,71 @@
4710
4656
  preview.textContent = buildPromptFromBuilder();
4711
4657
  }
4712
4658
 
4659
+ // src/static/app-catalog/js/promote.js
4660
+ var KEY = "app-catalog.promoted";
4661
+ var promoted = {};
4662
+ function base() {
4663
+ return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
4664
+ }
4665
+ function getPromotion(ref) {
4666
+ return promoted[ref] || null;
4667
+ }
4668
+ function getPromotedItems() {
4669
+ return Object.keys(promoted).map(function(ref) {
4670
+ return { ref, text: promoted[ref] || {} };
4671
+ });
4672
+ }
4673
+ function loadPromoted() {
4674
+ var token = getCortexOwnerToken();
4675
+ var b = base();
4676
+ if (!token || !b) {
4677
+ promoted = {};
4678
+ return Promise.resolve();
4679
+ }
4680
+ return fetch(b + "/v1/memory/" + encodeURIComponent(KEY), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
4681
+ return r.ok ? r.json() : null;
4682
+ }).then(function(j) {
4683
+ var items = j && j.data && j.data.value && Array.isArray(j.data.value.items) ? j.data.value.items : [];
4684
+ var map = {};
4685
+ items.forEach(function(it) {
4686
+ if (it && typeof it.ref === "string" && it.text && typeof it.text === "object") map[it.ref] = { en: it.text.en || "", fi: it.text.fi || "" };
4687
+ });
4688
+ promoted = map;
4689
+ }).catch(function() {
4690
+ });
4691
+ }
4692
+ function persist() {
4693
+ var token = getCortexOwnerToken();
4694
+ var b = base();
4695
+ if (!token || !b) return Promise.resolve();
4696
+ var items = getPromotedItems();
4697
+ return fetch(b + "/v1/memory", {
4698
+ method: "POST",
4699
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
4700
+ body: JSON.stringify({
4701
+ key: KEY,
4702
+ value: { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items },
4703
+ visibility: "public"
4704
+ })
4705
+ }).catch(function() {
4706
+ });
4707
+ }
4708
+ function setPromotion(ref, text) {
4709
+ var en = text && text.en ? String(text.en).trim() : "";
4710
+ var fi = text && text.fi ? String(text.fi).trim() : "";
4711
+ var now;
4712
+ if (!en && !fi) {
4713
+ delete promoted[ref];
4714
+ now = false;
4715
+ } else {
4716
+ promoted[ref] = { en, fi };
4717
+ now = true;
4718
+ }
4719
+ return persist().then(function() {
4720
+ return now;
4721
+ });
4722
+ }
4723
+
4713
4724
  // src/static/app-catalog/js/monetize.js
4714
4725
  var NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
4715
4726
  var MONEY_UNIT = 1e6;
@@ -4761,7 +4772,7 @@
4761
4772
  }
4762
4773
  function priceLabel(tool) {
4763
4774
  var parts = [];
4764
- if (tool.price && tool.price.morsels > 0) parts.push(tool.price.morsels + " 🥩");
4775
+ if (tool.price && tool.price.morsels > 0) parts.push(tool.price.morsels + " morsels");
4765
4776
  if (tool.priceMoney && tool.priceMoney.amount > 0) {
4766
4777
  parts.push(tool.priceMoney.amount / MONEY_UNIT + " " + tool.priceMoney.currency);
4767
4778
  }
@@ -4902,6 +4913,79 @@
4902
4913
  });
4903
4914
  }
4904
4915
 
4916
+ // src/static/app-catalog/js/cost.js
4917
+ var cState = "off";
4918
+ var cData = null;
4919
+ var cAppId = "";
4920
+ function apiBase2() {
4921
+ var cfg = loadConfig();
4922
+ return (cfg.aimeatUrl || "").replace(/\/+$/, "");
4923
+ }
4924
+ function rerender2() {
4925
+ var el = document.getElementById("detail-cost");
4926
+ if (el) el.innerHTML = costSectionInner();
4927
+ }
4928
+ function costOnOpen(owner, appId, isOwn) {
4929
+ cData = null;
4930
+ if (!isOwn || !owner || !appId) {
4931
+ cState = "off";
4932
+ cAppId = "";
4933
+ return;
4934
+ }
4935
+ cAppId = owner + "/" + appId;
4936
+ cState = "loading";
4937
+ var token = getCortexOwnerToken();
4938
+ if (!token) {
4939
+ cState = "error";
4940
+ cData = { _needLogin: true };
4941
+ return;
4942
+ }
4943
+ fetch(apiBase2() + "/v1/apps/cost?app_id=" + encodeURIComponent(cAppId), {
4944
+ headers: { "Authorization": "Bearer " + token }
4945
+ }).then(function(r) {
4946
+ return r.json();
4947
+ }).then(function(res) {
4948
+ if (!res || !res.ok || !res.data) throw new Error("bad response");
4949
+ cData = res.data;
4950
+ cState = "ready";
4951
+ rerender2();
4952
+ }).catch(function() {
4953
+ cState = "error";
4954
+ cData = null;
4955
+ rerender2();
4956
+ });
4957
+ }
4958
+ function stateBadge(state) {
4959
+ var cls = state === "active" ? "ok" : state === "paused" ? "diff" : "none";
4960
+ return '<span class="dtl-sync ' + cls + '" style="margin:0">' + escapeHtml(state) + "</span>";
4961
+ }
4962
+ function contractRow(c) {
4963
+ var b = c.budget || {};
4964
+ var cap = b.cap_units === null || b.cap_units === void 0 ? t("cost.uncapped") : b.spent_units + " / " + b.cap_units;
4965
+ var remaining = b.remaining_units === null || b.remaining_units === void 0 ? "" : ' <span class="dtl-stat-label">(' + t("cost.remaining").replace("{n}", b.remaining_units) + ")</span>";
4966
+ var providerShort = String(c.provider || "").split("@")[0];
4967
+ return '<div class="dtl-status-row" style="align-items:center;gap:10px;flex-wrap:wrap"><div class="dtl-stat" style="flex:1;min-width:150px"><span class="dtl-stat-val">' + escapeHtml(c.capability || "") + '</span><span class="dtl-stat-label">' + escapeHtml(t("cost.providerCol")) + ": " + escapeHtml(providerShort) + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + t("cost.priceCol") + '</span><span class="dtl-stat-val">' + escapeHtml(String(c.price_per_call)) + ' morsels</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + t("cost.rakeCol") + '</span><span class="dtl-stat-val">' + escapeHtml(String(c.rake_per_call)) + " morsels (" + escapeHtml(String(c.rake_percent)) + '%)</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + t("cost.budgetCol") + '</span><span class="dtl-stat-val">' + escapeHtml(cap) + remaining + '</span></div><div class="dtl-stat">' + stateBadge(c.state) + "</div></div>";
4968
+ }
4969
+ function costSectionInner() {
4970
+ if (cState === "off") return "";
4971
+ var html = "<h3>" + t("cost.title") + '</h3><p class="dtl-desc">' + t("cost.hint") + "</p>";
4972
+ if (cState === "loading") {
4973
+ return html + '<span style="color:var(--text-muted);font-size:.85rem">…</span>';
4974
+ }
4975
+ if (cState === "error") {
4976
+ var msg = cData && cData._needLogin ? t("cost.needLogin") : t("cost.loadFailed");
4977
+ return html + '<span class="dtl-sync none">' + escapeHtml(msg) + "</span>";
4978
+ }
4979
+ var contracts = cData && cData.contracts || [];
4980
+ if (!contracts.length) {
4981
+ return html + '<span class="dtl-sync none">' + t("cost.empty") + "</span>";
4982
+ }
4983
+ var m = cData.totals && cData.totals.morsels || { spent_units: 0, calls: 0 };
4984
+ html += '<div class="dtl-sync ok" style="margin:0 0 10px">' + t("cost.summary").replace("{n}", cData.total_contracts).replace("{spent}", m.spent_units).replace("{calls}", m.calls) + "</div>";
4985
+ for (var i = 0; i < contracts.length; i++) html += contractRow(contracts[i]);
4986
+ return html;
4987
+ }
4988
+
4905
4989
  // src/static/app-catalog/js/app-agents.js
4906
4990
  var getServerManifests;
4907
4991
  function initAppAgents(deps) {
@@ -4957,8 +5041,8 @@
4957
5041
  showNotice(t("agents.none"));
4958
5042
  return;
4959
5043
  }
4960
- var base = aimeatBase();
4961
- if (!base) {
5044
+ var base3 = aimeatBase();
5045
+ if (!base3) {
4962
5046
  showNotice("Set AIMEAT server URL in Settings first");
4963
5047
  return;
4964
5048
  }
@@ -4974,7 +5058,7 @@
4974
5058
  document.getElementById("agents-overlay").hidden = false;
4975
5059
  defs.forEach(function(def2, i2) {
4976
5060
  var headers = token ? { "Authorization": "Bearer " + token } : {};
4977
- fetch(base + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/agents/" + encodeURIComponent(def2.agent_name) + "/instances", { headers }).then(function(r) {
5061
+ fetch(base3 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/agents/" + encodeURIComponent(def2.agent_name) + "/instances", { headers }).then(function(r) {
4978
5062
  return r.json();
4979
5063
  }).then(function(json) {
4980
5064
  var el = document.getElementById("aga-instances-" + i2);
@@ -4990,7 +5074,7 @@
4990
5074
  });
4991
5075
  });
4992
5076
  if (token) {
4993
- fetch(base + "/v1/agents", { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5077
+ fetch(base3 + "/v1/agents", { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
4994
5078
  return r.json();
4995
5079
  }).then(function(json) {
4996
5080
  var agents = json.data && json.data.agents || [];
@@ -5017,7 +5101,7 @@
5017
5101
  } catch (e) {
5018
5102
  }
5019
5103
  if (me) {
5020
- fetch(base + "/v1/organisms?member=" + encodeURIComponent(me), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5104
+ fetch(base3 + "/v1/organisms?member=" + encodeURIComponent(me), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5021
5105
  return r.json();
5022
5106
  }).then(function(json) {
5023
5107
  var orgs = json.data && json.data.organisms || [];
@@ -5169,6 +5253,7 @@
5169
5253
  var app = detailGetApp();
5170
5254
  if (!app) return;
5171
5255
  monetizeOnOpen(detailServerOwner(app), app.publishedFilename || "", detailIsOwnPublished(app));
5256
+ costOnOpen(detailServerOwner(app), app.publishedFilename || "", detailIsOwnPublished(app));
5172
5257
  document.getElementById("detail-view").hidden = false;
5173
5258
  renderDetailView();
5174
5259
  detailCheckAiAvailability();
@@ -5195,14 +5280,14 @@
5195
5280
  var app = detailGetApp();
5196
5281
  if (!app) return;
5197
5282
  if (app.published && (app.publishedUrl || app.publishedFilename)) {
5198
- var base = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
5283
+ var base3 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
5199
5284
  var url;
5200
5285
  if (app.publishedUrl) {
5201
- url = base + app.publishedUrl;
5286
+ url = base3 + app.publishedUrl;
5202
5287
  } else {
5203
5288
  var st = getServerState()[app.publishedFilename] || {};
5204
5289
  var owner = st.owner || currentOwnerName() || "";
5205
- url = base + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app.publishedFilename);
5290
+ url = base3 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app.publishedFilename);
5206
5291
  }
5207
5292
  viewPublished(url + "?mode=inline", app.name);
5208
5293
  } else {
@@ -5224,11 +5309,6 @@
5224
5309
  });
5225
5310
  }
5226
5311
  function onAuthChanged() {
5227
- if (getDbMode() === "personal" && !currentOwnerName()) {
5228
- setDbMode("global");
5229
- }
5230
- closeDbInstance();
5231
- updateModeToggle();
5232
5312
  try {
5233
5313
  refreshAll();
5234
5314
  } catch (e) {
@@ -5310,13 +5390,17 @@
5310
5390
  var tags = app.tags && app.tags.length ? app.tags.join(", ") : "—";
5311
5391
  var cortex = app.usesCortex && app.usesCortex.length ? app.usesCortex.join(", ") : "—";
5312
5392
  var created = app.addedAt ? new Date(app.addedAt).toLocaleString() : "—";
5313
- var favBtn = app.blob ? '<button class="dtl-fav-btn" title="' + escapeHtml(t(app.favorite ? "detail.unfavorite" : "detail.favorite")) + '" onclick="window._launcher.detailToggleFavorite()">' + (app.favorite ? "⭐" : "☆") + "</button>" : "";
5393
+ var favBtn = "";
5314
5394
  var aboutHeader = '<h3 style="display:flex;align-items:center;justify-content:space-between;gap:8px"><span>' + t("detail.about") + '</span><span style="display:flex;align-items:center;gap:8px">' + favBtn + (canEditAbout && !detailEditingAbout ? dtlBtn(t("detail.editDetails"), "window._launcher.detailAboutEdit()") : "") + "</span></h3>";
5395
+ var descs = app.descriptions || {};
5396
+ var descEnVal = descs.en || (app.description || "");
5397
+ var descFiVal = descs.fi || "";
5315
5398
  var aboutBody;
5316
5399
  if (detailEditingAbout) {
5317
- aboutBody = '<label class="dtl-stat-label" for="detail-name-input">' + t("detail.nameLabel") + '</label><input id="detail-name-input" class="modal-input" maxlength="120" value="' + escapeHtml(app.name || "") + '" style="margin:4px 0 10px" /><label class="dtl-stat-label" for="detail-desc-input">' + t("detail.descLabel") + '</label><textarea id="detail-desc-input" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 8px;resize:vertical">' + escapeHtml(app.description || "") + '</textarea><label class="dtl-stat-label" for="detail-icon-input">' + t("detail.iconLabel") + '</label><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" /><label class="dtl-stat-label" for="detail-tags-input">' + t("detail.tagsLabel") + '</label><input id="detail-tags-input" class="modal-input" value="' + escapeHtml((app.tags || []).join(", ")) + '" placeholder="tools, productivity" style="margin:4px 0 8px" /><div class="dtl-sync none" style="margin:0 0 10px">' + t("detail.renameHint") + '</div><div class="dtl-btn-row">' + dtlBtn(t("detail.saveDetails"), "window._launcher.detailAboutSave()", { variant: "primary" }) + dtlBtn(t("detail.cancelEdit"), "window._launcher.detailAboutCancel()") + "</div>";
5400
+ aboutBody = '<label class="dtl-stat-label" for="detail-name-input">' + t("detail.nameLabel") + '</label><input id="detail-name-input" class="modal-input" maxlength="120" value="' + escapeHtml(app.name || "") + '" style="margin:4px 0 10px" /><label class="dtl-stat-label" for="detail-desc-en">' + t("detail.descEn") + '</label><textarea id="detail-desc-en" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(descEnVal) + '</textarea><label class="dtl-stat-label" for="detail-desc-fi">' + t("detail.descFi") + '</label><textarea id="detail-desc-fi" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(descFiVal) + '</textarea><div class="dtl-btn-row" style="margin:0 0 4px">' + dtlBtn("🌐 " + t("detail.translateEnFi"), "window._launcher.detailTranslateDesc('en','fi')", { id: "detail-tr-enfi" }) + dtlBtn("🌐 " + t("detail.translateFiEn"), "window._launcher.detailTranslateDesc('fi','en')", { id: "detail-tr-fien" }) + '</div><div class="dtl-ai-status" id="detail-tr-status" style="margin:0 0 8px"></div><label class="dtl-stat-label" for="detail-icon-input">' + t("detail.iconLabel") + '</label><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" /><label class="dtl-stat-label" for="detail-tags-input">' + t("detail.tagsLabel") + '</label><input id="detail-tags-input" class="modal-input" value="' + escapeHtml((app.tags || []).join(", ")) + '" placeholder="tools, productivity" style="margin:4px 0 8px" /><div class="dtl-sync none" style="margin:0 0 10px">' + t("detail.renameHint") + '</div><div class="dtl-btn-row">' + dtlBtn(t("detail.saveDetails"), "window._launcher.detailAboutSave()", { variant: "primary" }) + dtlBtn(t("detail.cancelEdit"), "window._launcher.detailAboutCancel()") + "</div>";
5318
5401
  } else {
5319
- aboutBody = (app.description ? '<p class="dtl-desc">' + escapeHtml(app.description) + "</p>" : "") + '<div class="dtl-meta-grid">' + metaItem(t("detail.category"), app.category || "utility") + metaItem(t("detail.tags"), tags) + metaItem(t("detail.sourceLabel"), sourceLabelText(app.source)) + metaItem(t("detail.size"), app.blob ? fmtSize(localBytes) : "—") + metaItem(t("detail.created"), created) + metaItem(t("detail.usesCortex"), cortex) + // Provenance: if this app was forked from another, credit the source app.
5402
+ var shownDesc = app.descriptions && app.descriptions[getLang()] || app.description || "";
5403
+ aboutBody = (shownDesc ? '<p class="dtl-desc">' + escapeHtml(shownDesc) + "</p>" : "") + '<div class="dtl-meta-grid">' + metaItem(t("detail.category"), app.category || "utility") + metaItem(t("detail.tags"), tags) + metaItem(t("detail.sourceLabel"), sourceLabelText(app.source)) + metaItem(t("detail.size"), app.blob ? fmtSize(localBytes) : "—") + metaItem(t("detail.created"), created) + metaItem(t("detail.usesCortex"), cortex) + // Provenance: if this app was forked from another, credit the source app.
5320
5404
  (app.forkedFrom && app.forkedFrom.owner && app.forkedFrom.filename ? metaItem(t("detail.forkedFrom"), app.forkedFrom.owner + "/" + app.forkedFrom.filename + (app.forkedFrom.version ? " v" + app.forkedFrom.version : "")) : "") + "</div>";
5321
5405
  }
5322
5406
  var aboutHtml = '<div class="dtl-section">' + aboutHeader + aboutBody + "</div>";
@@ -5342,6 +5426,8 @@
5342
5426
  (ownPub ? '<div id="detail-skill-attach" style="margin-top:8px">' + detailSkillAttachInner() + "</div>" : "") + "</div>";
5343
5427
  }
5344
5428
  var monetizeHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-monetize">' + monetizeSectionInner() + "</div>" : "";
5429
+ var costHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-cost">' + costSectionInner() + "</div>" : "";
5430
+ var promoteHtml = app.published && detailIsOwnPublished(app) ? buildPromoteSection(app) : "";
5345
5431
  var agentsHtml = "";
5346
5432
  if (app.published) {
5347
5433
  var agOwner = detailServerOwner(app);
@@ -5356,7 +5442,43 @@
5356
5442
  ) + "</div>";
5357
5443
  }
5358
5444
  }
5359
- document.getElementById("detail-body").innerHTML = statusHtml + aboutHtml + aiHtml + versionsHtml + skillsHtml + agentsHtml + monetizeHtml + mgmtHtml + actionsHtml;
5445
+ document.getElementById("detail-body").innerHTML = statusHtml + aboutHtml + aiHtml + versionsHtml + skillsHtml + agentsHtml + monetizeHtml + costHtml + promoteHtml + mgmtHtml + actionsHtml;
5446
+ }
5447
+ function promoteRef(app) {
5448
+ return detailServerOwner(app) + "/" + (app.publishedFilename || "");
5449
+ }
5450
+ function buildPromoteSection(app) {
5451
+ var ref = promoteRef(app);
5452
+ var cur = getPromotion(ref) || {};
5453
+ var on = !!(cur.en || cur.fi);
5454
+ return '<div class="dtl-section" id="detail-promote"><h3 style="display:flex;align-items:center;gap:8px">📣 ' + t("promote.title") + (on ? ' <span class="dtl-badge-on">' + t("promote.on") + "</span>" : "") + '</h3><p class="dtl-desc">' + t("promote.hint") + '</p><label class="dtl-stat-label" for="detail-promo-en">' + t("promote.en") + '</label><textarea id="detail-promo-en" class="modal-input" rows="2" maxlength="500" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(cur.en || "") + '</textarea><label class="dtl-stat-label" for="detail-promo-fi">' + t("promote.fi") + '</label><textarea id="detail-promo-fi" class="modal-input" rows="2" maxlength="500" style="margin:4px 0 6px;resize:vertical">' + escapeHtml(cur.fi || "") + '</textarea><div class="dtl-btn-row" style="margin:0 0 4px">' + dtlBtn("🌐 " + t("detail.translateEnFi"), "window._launcher.detailTranslateDesc('en','fi','detail-promo-','detail-promo-tr-status')") + dtlBtn("🌐 " + t("detail.translateFiEn"), "window._launcher.detailTranslateDesc('fi','en','detail-promo-','detail-promo-tr-status')") + '</div><div class="dtl-ai-status" id="detail-promo-tr-status" style="margin:0 0 8px"></div><div class="dtl-btn-row">' + dtlBtn(t("promote.save"), "window._launcher.detailPromoteSave()", { variant: "primary" }) + (on ? dtlBtn(t("promote.remove"), "window._launcher.detailPromoteClear()") : "") + '</div><div class="dtl-ai-status" id="detail-promo-status"></div></div>';
5455
+ }
5456
+ function detailPromoteSave() {
5457
+ var app = detailGetApp();
5458
+ if (!app || !detailIsOwnPublished(app)) return;
5459
+ var en = (document.getElementById("detail-promo-en") || {}).value || "";
5460
+ var fi = (document.getElementById("detail-promo-fi") || {}).value || "";
5461
+ var statusEl = document.getElementById("detail-promo-status");
5462
+ if (statusEl) {
5463
+ statusEl.style.color = "var(--text-muted)";
5464
+ statusEl.textContent = t("promote.saving");
5465
+ }
5466
+ setPromotion(promoteRef(app), { en, fi }).then(function(nowOn) {
5467
+ renderApps();
5468
+ renderDetailView();
5469
+ var s2 = document.getElementById("detail-promo-status");
5470
+ if (s2) {
5471
+ s2.style.color = "#34d399";
5472
+ s2.textContent = nowOn ? "✔ " + t("promote.saved") : "✔ " + t("promote.removed");
5473
+ }
5474
+ });
5475
+ }
5476
+ function detailPromoteClear() {
5477
+ var en = document.getElementById("detail-promo-en");
5478
+ if (en) en.value = "";
5479
+ var fi = document.getElementById("detail-promo-fi");
5480
+ if (fi) fi.value = "";
5481
+ detailPromoteSave();
5360
5482
  }
5361
5483
  function detailLoadSkills(owner, filename) {
5362
5484
  var config = loadConfig();
@@ -5568,27 +5690,27 @@
5568
5690
  return h;
5569
5691
  }
5570
5692
  function skillListMine() {
5571
- var base = skillApiBase();
5572
- if (!base) return Promise.reject(new Error("No server configured"));
5573
- return fetch(base + "/v1/skills?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5693
+ var base3 = skillApiBase();
5694
+ if (!base3) return Promise.reject(new Error("No server configured"));
5695
+ return fetch(base3 + "/v1/skills?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5574
5696
  return r.json();
5575
5697
  }).then(function(j) {
5576
5698
  return j.data && j.data.skills ? j.data.skills : [];
5577
5699
  });
5578
5700
  }
5579
5701
  function skillGetMine(name) {
5580
- var base = skillApiBase();
5581
- return fetch(base + "/v1/skills/" + encodeURIComponent(name) + "?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5702
+ var base3 = skillApiBase();
5703
+ return fetch(base3 + "/v1/skills/" + encodeURIComponent(name) + "?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5582
5704
  return r.json();
5583
5705
  }).then(function(j) {
5584
5706
  return j.data && j.data.skill || null;
5585
5707
  });
5586
5708
  }
5587
5709
  function skillPublish(skillMd, files) {
5588
- var base = skillApiBase();
5710
+ var base3 = skillApiBase();
5589
5711
  var body = { skill_md: skillMd };
5590
5712
  if (files && Object.keys(files).length) body.files = files;
5591
- return fetch(base + "/v1/skills", { method: "POST", headers: skillAuthHeaders(true), body: JSON.stringify(body) }).then(function(r) {
5713
+ return fetch(base3 + "/v1/skills", { method: "POST", headers: skillAuthHeaders(true), body: JSON.stringify(body) }).then(function(r) {
5592
5714
  return r.json().then(function(j) {
5593
5715
  if (!j.ok) throw new Error(j.error && (j.error.message || j.error.code) || "HTTP " + r.status);
5594
5716
  return j.data && j.data.skill || null;
@@ -5646,11 +5768,17 @@
5646
5768
  var app = detailGetApp();
5647
5769
  if (!app) return;
5648
5770
  var nameEl = document.getElementById("detail-name-input");
5649
- var descEl = document.getElementById("detail-desc-input");
5771
+ var enEl = document.getElementById("detail-desc-en");
5772
+ var fiEl = document.getElementById("detail-desc-fi");
5650
5773
  var iconEl = document.getElementById("detail-icon-input");
5651
5774
  var tagsEl = document.getElementById("detail-tags-input");
5652
5775
  var newName = nameEl ? nameEl.value.trim() : "";
5653
- var newDesc = descEl ? descEl.value.trim() : "";
5776
+ var enDesc = enEl ? enEl.value.trim() : "";
5777
+ var fiDesc = fiEl ? fiEl.value.trim() : "";
5778
+ var newDescriptions = {};
5779
+ if (enDesc) newDescriptions.en = enDesc;
5780
+ if (fiDesc) newDescriptions.fi = fiDesc;
5781
+ var newDesc = enDesc || fiDesc || "";
5654
5782
  var newIcon = iconEl ? iconEl.value.trim() : app.icon || "";
5655
5783
  var newTags = tagsEl ? tagsEl.value.split(",").map(function(s) {
5656
5784
  return s.trim();
@@ -5662,12 +5790,13 @@
5662
5790
  }
5663
5791
  if (app.published && !newDesc) {
5664
5792
  showNotice(t("detail.descRequired") || "Description cannot be empty.");
5665
- if (descEl) descEl.focus();
5793
+ if (enEl) enEl.focus();
5666
5794
  return;
5667
5795
  }
5668
5796
  function finishLocal() {
5669
5797
  app.name = newName;
5670
5798
  app.description = newDesc;
5799
+ app.descriptions = newDescriptions;
5671
5800
  app.icon = newIcon;
5672
5801
  app.tags = newTags;
5673
5802
  saveApp(app).then(function() {
@@ -5693,7 +5822,7 @@
5693
5822
  fetch(aimeatUrl + "/v1/apps/" + encodeURIComponent(filename), {
5694
5823
  method: "PATCH",
5695
5824
  headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5696
- body: JSON.stringify({ name: newName, description: newDesc })
5825
+ body: JSON.stringify({ name: newName, description: newDesc, descriptions: newDescriptions })
5697
5826
  }).then(function(resp) {
5698
5827
  return resp.json().then(function(j) {
5699
5828
  return { ok: resp.ok, j };
@@ -5704,6 +5833,7 @@
5704
5833
  getServerManifests2()[key] = getServerManifests2()[key] || {};
5705
5834
  getServerManifests2()[key].name = newName;
5706
5835
  getServerManifests2()[key].description = newDesc;
5836
+ getServerManifests2()[key].descriptions = newDescriptions;
5707
5837
  finishLocal();
5708
5838
  } else {
5709
5839
  showNotice("Failed: " + (res.j && res.j.error && res.j.error.message || "Unknown error"));
@@ -5712,6 +5842,69 @@
5712
5842
  showNotice("Error: " + (err.message || err));
5713
5843
  });
5714
5844
  }
5845
+ function detailTranslateDesc(srcLang, dstLang, prefix, statusId) {
5846
+ prefix = prefix || "detail-desc-";
5847
+ var srcEl = document.getElementById(prefix + srcLang);
5848
+ var dstEl = document.getElementById(prefix + dstLang);
5849
+ var statusEl = document.getElementById(statusId || "detail-tr-status");
5850
+ if (!srcEl || !dstEl) return;
5851
+ var text = (srcEl.value || "").trim();
5852
+ if (!text) {
5853
+ if (statusEl) statusEl.textContent = t("detail.trNeedSource");
5854
+ return;
5855
+ }
5856
+ var token = getCortexOwnerToken2();
5857
+ if (!token) {
5858
+ if (statusEl) statusEl.textContent = t("detail.aiLoginNeeded");
5859
+ return;
5860
+ }
5861
+ var config = loadConfig();
5862
+ var aimeatUrl = (config.aimeatUrl || "").replace(/\/+$/, "");
5863
+ if (!aimeatUrl) {
5864
+ if (statusEl) statusEl.textContent = t("detail.aiUnavailable");
5865
+ return;
5866
+ }
5867
+ var langName = { en: "English", fi: "Finnish" };
5868
+ if (statusEl) {
5869
+ statusEl.style.color = "var(--text-muted)";
5870
+ statusEl.textContent = t("detail.translating");
5871
+ }
5872
+ var systemPrompt = "You are a professional translator for short app-store copy. Translate the text from " + (langName[srcLang] || srcLang) + " to " + (langName[dstLang] || dstLang) + ". Return ONLY the translated text — no quotes, no notes, no explanation. Keep it concise and natural.";
5873
+ fetch(aimeatUrl + "/v1/ai/complete", {
5874
+ method: "POST",
5875
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
5876
+ body: JSON.stringify({ prompt: text, systemPrompt, app_id: "app-catalog" })
5877
+ }).then(function(resp) {
5878
+ return resp.json();
5879
+ }).then(function(json) {
5880
+ if (!json || !json.ok) {
5881
+ var msg = json && json.error && json.error.message || "Translation failed";
5882
+ if (statusEl) {
5883
+ statusEl.style.color = "var(--accent)";
5884
+ statusEl.textContent = "✘ " + msg;
5885
+ }
5886
+ return;
5887
+ }
5888
+ var out = (json.data && json.data.content ? json.data.content : "").trim();
5889
+ if (!out) {
5890
+ if (statusEl) {
5891
+ statusEl.style.color = "var(--accent)";
5892
+ statusEl.textContent = "✘ " + t("detail.trEmpty");
5893
+ }
5894
+ return;
5895
+ }
5896
+ dstEl.value = out;
5897
+ if (statusEl) {
5898
+ statusEl.style.color = "#34d399";
5899
+ statusEl.textContent = "✔ " + t("detail.trDone");
5900
+ }
5901
+ }).catch(function(err) {
5902
+ if (statusEl) {
5903
+ statusEl.style.color = "var(--accent)";
5904
+ statusEl.textContent = "✘ " + (err.message || "Translation failed");
5905
+ }
5906
+ });
5907
+ }
5715
5908
  function detailSetScreenshot() {
5716
5909
  var app = detailGetApp();
5717
5910
  if (!app) return;
@@ -6004,9 +6197,9 @@
6004
6197
  function draftApi(method, owner, filename, sub, body) {
6005
6198
  var cfg = loadConfig();
6006
6199
  if (!cfg.aimeatUrl) return Promise.reject(new Error("Set the AIMEAT server URL in Settings first"));
6007
- var base = cfg.aimeatUrl.replace(/\/+$/, "");
6200
+ var base3 = cfg.aimeatUrl.replace(/\/+$/, "");
6008
6201
  var path = "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/" + sub;
6009
- return fetch(base + path, {
6202
+ return fetch(base3 + path, {
6010
6203
  method,
6011
6204
  headers: { "Authorization": "Bearer " + getCortexOwnerToken2(), "Content-Type": "application/json" },
6012
6205
  body: body ? JSON.stringify(body) : void 0
@@ -6196,6 +6389,7 @@
6196
6389
  id: generateId2(),
6197
6390
  name: meta.name || filename.replace(/\.html?$/i, ""),
6198
6391
  description: meta.description || "",
6392
+ descriptions: meta.descriptions || null,
6199
6393
  category: meta.category || "utility",
6200
6394
  tags: meta.tags || [],
6201
6395
  usesCortex: meta.usesCortex || [],
@@ -6429,6 +6623,7 @@
6429
6623
  mime_type: "text/html",
6430
6624
  name: meta.name || filename.replace(/\.html?$/i, ""),
6431
6625
  description: meta.description || "",
6626
+ descriptions: meta.descriptions || null,
6432
6627
  category: meta.category || "utility",
6433
6628
  tags: meta.tags || [],
6434
6629
  uses_cortex: meta.usesCortex || []
@@ -6464,8 +6659,8 @@
6464
6659
  showNotice(t("common.loginRequired") || "You must be logged in to fork an app into your own catalogue. Sign in first.");
6465
6660
  return;
6466
6661
  }
6467
- var base = (filename || "app").replace(/\.html?$/i, "");
6468
- var suggested = base + "-fork.html";
6662
+ var base3 = (filename || "app").replace(/\.html?$/i, "");
6663
+ var suggested = base3 + "-fork.html";
6469
6664
  var newName = prompt(t("fork.prompt") || 'Fork "' + filename + '" into a new app.\n\nNew filename:', suggested);
6470
6665
  if (!newName) return;
6471
6666
  newName = newName.trim();
@@ -6514,11 +6709,7 @@
6514
6709
  }
6515
6710
 
6516
6711
  // src/static/app-catalog/js/settings.js
6517
- var generateId3;
6518
- var renderApps2;
6519
- var loadPublishedApps2;
6520
- function initSettings(deps) {
6521
- ({ generateId: generateId3, renderApps: renderApps2, loadPublishedApps: loadPublishedApps2 } = deps);
6712
+ function initSettings() {
6522
6713
  }
6523
6714
  function applyTheme(theme) {
6524
6715
  if (theme === "dark") {
@@ -6665,260 +6856,36 @@
6665
6856
  function closeHelp() {
6666
6857
  document.getElementById("help-overlay").hidden = true;
6667
6858
  }
6668
- function exportBackup() {
6669
- getAllApps().then(function(apps) {
6670
- var config = loadConfig();
6671
- var backup = {
6672
- version: 1,
6673
- exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
6674
- config,
6675
- apps
6676
- };
6677
- var json = JSON.stringify(backup, null, 2);
6678
- var blob = new Blob([json], { type: "application/json" });
6679
- var url = URL.createObjectURL(blob);
6680
- var a = document.createElement("a");
6681
- a.href = url;
6682
- a.download = "app-catalog-backup-" + (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".json";
6683
- document.body.appendChild(a);
6684
- a.click();
6685
- document.body.removeChild(a);
6686
- URL.revokeObjectURL(url);
6687
- });
6688
- }
6689
- function localAppKey(a) {
6690
- if (a.publishedFilename) return "pf:" + a.publishedFilename;
6691
- return "nm:" + (a.name || "") + "|" + (a.blob ? a.blob.length : a.url || "");
6692
- }
6693
- var jsonImportState = null;
6694
- function handleImportBackup(file) {
6695
- if (!file) return;
6696
- var reader = new FileReader();
6697
- reader.onload = function() {
6698
- var backup;
6699
- try {
6700
- backup = JSON.parse(reader.result);
6701
- } catch (e) {
6702
- showNotice("Failed to parse backup file: " + (e.message || e));
6703
- return;
6704
- }
6705
- if (!backup.apps || !Array.isArray(backup.apps)) {
6706
- showNotice("Invalid backup file: missing apps array");
6707
- return;
6708
- }
6709
- getAllApps().then(function(existing) {
6710
- var existingKeys = {};
6711
- existing.forEach(function(a) {
6712
- existingKeys[localAppKey(a)] = true;
6713
- });
6714
- jsonImportState = {
6715
- backup,
6716
- rows: backup.apps.map(function(a) {
6717
- return { app: a, isDup: !!existingKeys[localAppKey(a)] };
6718
- })
6719
- };
6720
- renderJsonImportModal();
6721
- });
6722
- };
6723
- reader.readAsText(file);
6724
- }
6725
- function renderJsonImportModal() {
6726
- var rows = jsonImportState.rows;
6727
- var body = document.getElementById("json-import-body");
6728
- var html = '<p style="font-size:.8rem;color:var(--text-muted);margin:4px 0 8px">' + t("jsonImport.desc") + '</p><div class="backup-toolbar-row"><div><button class="backup-link-btn" onclick="window._launcher.jsonImportSelectAll(true)">' + t("backup.selectAll") + '</button> / <button class="backup-link-btn" onclick="window._launcher.jsonImportSelectAll(false)">' + t("backup.selectNone") + '</button></div></div><table class="backup-table"><tbody>';
6729
- for (var i = 0; i < rows.length; i++) {
6730
- var a = rows[i].app;
6731
- html += '<tr><td><input type="checkbox" class="json-import-cb" data-i="' + i + '"' + (rows[i].isDup ? "" : " checked") + "/></td><td>" + escapeHtml(a.name || a.publishedFilename || "Untitled") + (a.publishedFilename ? '<div class="mono-cell">' + escapeHtml(a.publishedFilename) + "</div>" : "") + "</td><td>" + sourceLabel(a.source) + "</td><td>" + (rows[i].isDup ? '<span class="backup-status-badge backup-status-exists">' + t("jsonImport.statusDup") + "</span>" : '<span class="backup-status-badge backup-status-new">' + t("backup.statusNew") + "</span>") + "</td></tr>";
6732
- }
6733
- html += "</tbody></table>";
6734
- if (jsonImportState.backup.config && typeof jsonImportState.backup.config === "object") {
6735
- html += '<label style="display:flex;align-items:center;gap:8px;margin-top:10px;font-size:.85rem"><input type="checkbox" id="json-import-config"/> ' + t("jsonImport.config") + "</label>";
6736
- }
6737
- body.innerHTML = html;
6738
- document.getElementById("json-import-status").textContent = "";
6739
- document.getElementById("json-import-btn").style.display = "";
6740
- document.getElementById("json-import-overlay").hidden = false;
6741
- }
6742
- function jsonImportSelectAll(checked) {
6743
- document.querySelectorAll(".json-import-cb").forEach(function(b) {
6744
- b.checked = checked;
6745
- });
6746
- }
6747
- function submitJsonImport() {
6748
- var statusEl = document.getElementById("json-import-status");
6749
- var selected = [];
6750
- document.querySelectorAll(".json-import-cb").forEach(function(b) {
6751
- if (b.checked) selected.push(jsonImportState.rows[parseInt(b.getAttribute("data-i"), 10)].app);
6752
- });
6753
- var configCb = document.getElementById("json-import-config");
6754
- var importConfig = !!(configCb && configCb.checked);
6755
- if (selected.length === 0 && !importConfig) {
6756
- statusEl.style.color = "#ef4444";
6757
- statusEl.textContent = t("backup.nothingSelected");
6758
- return;
6759
- }
6760
- if (importConfig) {
6761
- var merged = Object.assign({}, loadConfig(), jsonImportState.backup.config);
6762
- saveConfig(merged);
6763
- applyTheme(merged.theme);
6764
- }
6765
- Promise.all(selected.map(function(a) {
6766
- var app = Object.assign({}, a);
6767
- app.id = generateId3();
6768
- return saveApp(app);
6769
- })).then(function() {
6770
- statusEl.style.color = "#34d399";
6771
- statusEl.textContent = "✔ " + t("jsonImport.done") + ": " + selected.length;
6772
- document.getElementById("json-import-btn").style.display = "none";
6773
- renderApps2();
6774
- loadPublishedApps2();
6775
- });
6776
- }
6777
- function removeDuplicateApps() {
6778
- getAllApps().then(async function(apps) {
6779
- var groups = {};
6780
- apps.forEach(function(a) {
6781
- var k = localAppKey(a);
6782
- (groups[k] = groups[k] || []).push(a);
6783
- });
6784
- var toDelete = [];
6785
- Object.keys(groups).forEach(function(k) {
6786
- var g = groups[k];
6787
- if (g.length < 2) return;
6788
- g.sort(function(x, y) {
6789
- if (!!y.favorite !== !!x.favorite) return (y.favorite ? 1 : 0) - (x.favorite ? 1 : 0);
6790
- return (x.addedAt || "") < (y.addedAt || "") ? -1 : 1;
6791
- });
6792
- toDelete = toDelete.concat(g.slice(1));
6793
- });
6794
- if (toDelete.length === 0) {
6795
- showNotice(t("dedup.none"));
6796
- return;
6797
- }
6798
- if (!await showConfirm(t("dedup.confirm") + " " + toDelete.length)) return;
6799
- Promise.all(toDelete.map(function(a) {
6800
- return deleteApp(a.id);
6801
- })).then(function() {
6802
- renderApps2();
6803
- loadPublishedApps2();
6804
- showNotice(t("dedup.done") + ": " + toDelete.length);
6805
- });
6806
- });
6807
- }
6808
- async function clearAllData() {
6809
- if (!await showConfirm(t("confirm.clearAll1"))) return;
6810
- if (!await showConfirm(t("confirm.clearAll2"))) return;
6811
- openDB().then(function(db) {
6812
- return new Promise(function(resolve, reject) {
6813
- var tx = db.transaction(STORE_NAME, "readwrite");
6814
- tx.objectStore(STORE_NAME).clear();
6815
- tx.oncomplete = function() {
6816
- resolve();
6817
- };
6818
- tx.onerror = function() {
6819
- reject(tx.error);
6820
- };
6821
- });
6822
- }).then(function() {
6823
- localStorage.removeItem("appLauncherConfig");
6824
- applyTheme("dark");
6825
- renderApps2();
6826
- document.getElementById("settings-overlay").hidden = true;
6827
- showNotice("All data cleared");
6828
- });
6829
- }
6830
6859
 
6831
6860
  // src/static/app-catalog/js/apps-io.js
6832
- var generateId4;
6861
+ var generateId3;
6833
6862
  var readFileAsText2;
6834
- var renderApps3;
6863
+ var renderApps2;
6835
6864
  var getMainApps2;
6865
+ var showPublishModal2;
6836
6866
  function initAppsIo(deps) {
6837
- ({ generateId: generateId4, readFileAsText: readFileAsText2, renderApps: renderApps3, getMainApps: getMainApps2 } = deps);
6838
- }
6839
- async function addAppFromZip(name, arrayBuffer, icon, tags, openMode) {
6840
- var files = await extractZip(arrayBuffer);
6841
- var bundledHtml = await bundleZip(files);
6842
- var encoded = btoa(unescape(encodeURIComponent(bundledHtml)));
6843
- var app = {
6844
- id: generateId4(),
6845
- name,
6846
- description: "",
6847
- source: "zip",
6848
- url: null,
6849
- blob: encoded,
6850
- tags,
6851
- openMode,
6852
- icon: icon || "📦",
6853
- screenshot: null,
6854
- favorite: false,
6855
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
6856
- lastOpenedAt: null
6857
- };
6858
- await saveApp(app);
6859
- return app;
6860
- }
6861
- function addAppFromUrl(name, url, icon, tags, openMode) {
6862
- var app = {
6863
- id: generateId4(),
6864
- name,
6865
- description: "",
6866
- source: "url",
6867
- url,
6868
- blob: null,
6869
- tags,
6870
- openMode,
6871
- icon: icon || "📝",
6872
- screenshot: null,
6873
- favorite: false,
6874
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
6875
- lastOpenedAt: null
6876
- };
6877
- return saveApp(app).then(function() {
6878
- return app;
6879
- });
6880
- }
6881
- function addAppFromFile(name, file, icon, tags, openMode) {
6882
- return readFileAsText2(file).then(function(content) {
6883
- var encoded = btoa(unescape(encodeURIComponent(content)));
6884
- var app = {
6885
- id: generateId4(),
6886
- name,
6887
- description: "",
6888
- source: "local",
6889
- url: null,
6890
- blob: encoded,
6891
- tags,
6892
- openMode,
6893
- icon: icon || "📝",
6894
- screenshot: null,
6895
- favorite: false,
6896
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
6897
- lastOpenedAt: null
6898
- };
6899
- return saveApp(app).then(function() {
6900
- return app;
6901
- });
6902
- });
6867
+ ({ generateId: generateId3, readFileAsText: readFileAsText2, renderApps: renderApps2, getMainApps: getMainApps2, showPublishModal: showPublishModal2 } = deps);
6903
6868
  }
6904
- function addAppFromSource(name, sourceCode, icon, tags, openMode) {
6905
- var encoded = btoa(unescape(encodeURIComponent(sourceCode)));
6869
+ function createAppFromHtml(name, html, icon, tags, description) {
6870
+ var encoded = btoa(unescape(encodeURIComponent(html)));
6906
6871
  var app = {
6907
- id: generateId4(),
6872
+ id: generateId3(),
6908
6873
  name,
6909
- description: parseAppMeta(sourceCode).description || "",
6910
- source: "local",
6874
+ description: description || "",
6875
+ source: "create",
6911
6876
  url: null,
6912
6877
  blob: encoded,
6913
- tags,
6914
- openMode,
6878
+ tags: tags || [],
6915
6879
  icon: icon || "📝",
6916
- screenshot: null,
6917
- favorite: false,
6918
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
6919
- lastOpenedAt: null
6880
+ published: false,
6881
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
6920
6882
  };
6921
6883
  return saveApp(app).then(function() {
6884
+ try {
6885
+ getMainApps2().push(app);
6886
+ } catch (e) {
6887
+ }
6888
+ if (typeof showPublishModal2 === "function") showPublishModal2(app.id, { unlisted: true });
6922
6889
  return app;
6923
6890
  });
6924
6891
  }
@@ -6927,7 +6894,7 @@
6927
6894
  function showModal() {
6928
6895
  document.getElementById("modal-overlay").hidden = false;
6929
6896
  document.getElementById("modal-title").textContent = t("addModal.title");
6930
- switchTab("url");
6897
+ switchTab("paste");
6931
6898
  }
6932
6899
  function requireSignInThen(next) {
6933
6900
  if (getCortexOwnerToken()) {
@@ -6975,7 +6942,6 @@
6975
6942
  }
6976
6943
  function closeModal() {
6977
6944
  document.getElementById("modal-overlay").hidden = true;
6978
- document.getElementById("app-url").value = "";
6979
6945
  document.getElementById("app-name").value = "";
6980
6946
  document.getElementById("app-icon").value = "";
6981
6947
  document.getElementById("app-tags").value = "";
@@ -7029,7 +6995,6 @@
7029
6995
  var name = document.getElementById("app-name").value.trim();
7030
6996
  var icon = document.getElementById("app-icon").value.trim();
7031
6997
  var tagsRaw = document.getElementById("app-tags").value.trim();
7032
- var openMode = "tab";
7033
6998
  var tags = tagsRaw ? tagsRaw.split(",").map(function(t2) {
7034
6999
  return t2.trim();
7035
7000
  }).filter(Boolean) : [];
@@ -7052,76 +7017,110 @@
7052
7017
  app.name = name;
7053
7018
  app.icon = icon || app.icon;
7054
7019
  app.tags = tags;
7055
- app.openMode = openMode;
7056
7020
  saveApp(app).then(function() {
7057
7021
  closeModal();
7058
- renderApps3();
7022
+ renderApps2();
7059
7023
  });
7060
7024
  return;
7061
7025
  }
7062
7026
  var activeTab = document.querySelector(".modal-tab.active");
7063
- var tabName = activeTab ? activeTab.getAttribute("data-tab") : "url";
7064
- if (tabName === "url") {
7065
- var url = document.getElementById("app-url").value.trim();
7066
- if (!url) {
7067
- showNotice("Please enter a URL");
7068
- return;
7069
- }
7070
- if (!name) {
7071
- try {
7072
- name = new URL(url).hostname;
7073
- } catch (e) {
7074
- name = url;
7075
- }
7076
- }
7077
- addAppFromUrl(name, url, icon, tags, openMode).then(function() {
7078
- closeModal();
7079
- renderApps3();
7080
- });
7081
- } else if (tabName === "paste") {
7027
+ var tabName = activeTab ? activeTab.getAttribute("data-tab") : "paste";
7028
+ if (tabName === "paste") {
7082
7029
  var pastedCode = document.getElementById("app-paste-code").value.trim();
7083
7030
  if (!pastedCode) {
7084
7031
  showNotice("Please paste your HTML source code");
7085
7032
  return;
7086
7033
  }
7087
- if (!name) {
7088
- var titleMatch = pastedCode.match(/<title[^>]*>([^<]+)<\/title>/i);
7089
- name = titleMatch ? titleMatch[1].trim() : "Pasted App";
7090
- }
7091
- addAppFromSource(name, pastedCode, icon, tags, openMode).then(function() {
7092
- closeModal();
7093
- renderApps3();
7094
- });
7034
+ var meta = parseAppMeta(pastedCode);
7035
+ if (!name) name = meta.name || "Pasted App";
7036
+ closeModal();
7037
+ createAppFromHtml(name, pastedCode, icon, tags, meta.description);
7095
7038
  } else if (tabName === "file") {
7096
7039
  if (!selectedFile) {
7097
7040
  showNotice("Please select a file");
7098
7041
  return;
7099
7042
  }
7100
- if (selectedFile.name.toLowerCase().endsWith(".zip")) {
7101
- if (!name) {
7102
- name = selectedFile.name.replace(/\.zip$/i, "");
7103
- }
7104
- selectedFile.arrayBuffer().then(function(arrayBuffer) {
7105
- return addAppFromZip(name, arrayBuffer, icon, tags, openMode);
7106
- }).then(function() {
7043
+ var file = selectedFile;
7044
+ if (file.name.toLowerCase().endsWith(".zip")) {
7045
+ if (!name) name = file.name.replace(/\.zip$/i, "");
7046
+ file.arrayBuffer().then(function(arrayBuffer) {
7047
+ return extractZip(arrayBuffer);
7048
+ }).then(function(files) {
7049
+ return bundleZip(files);
7050
+ }).then(function(html) {
7107
7051
  closeModal();
7108
- renderApps3();
7052
+ return createAppFromHtml(name, html, icon, tags, parseAppMeta(html).description);
7109
7053
  }).catch(function(err) {
7110
7054
  showNotice("ZIP import failed: " + (err.message || err));
7111
7055
  });
7112
7056
  } else {
7113
- if (!name) {
7114
- name = selectedFile.name.replace(/\.html?$/i, "");
7115
- }
7116
- addAppFromFile(name, selectedFile, icon, tags, openMode).then(function() {
7057
+ if (!name) name = file.name.replace(/\.html?$/i, "");
7058
+ readFileAsText2(file).then(function(html) {
7117
7059
  closeModal();
7118
- renderApps3();
7060
+ createAppFromHtml(name, html, icon, tags, parseAppMeta(html).description);
7119
7061
  });
7120
7062
  }
7121
7063
  }
7122
7064
  }
7123
- function setEditingAppId(v) {
7124
- editingAppId = v;
7065
+
7066
+ // src/static/app-catalog/js/favorites.js
7067
+ var KEY2 = "app-catalog.favorites";
7068
+ var favSet = /* @__PURE__ */ new Set();
7069
+ function base2() {
7070
+ return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
7071
+ }
7072
+ function isFavorite(ref) {
7073
+ return favSet.has(ref);
7074
+ }
7075
+ function loadFavorites() {
7076
+ var token = getCortexOwnerToken();
7077
+ var b = base2();
7078
+ if (!token || !b) {
7079
+ favSet = /* @__PURE__ */ new Set();
7080
+ return Promise.resolve();
7081
+ }
7082
+ return fetch(b + "/v1/memory/" + encodeURIComponent(KEY2), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
7083
+ return r.ok ? r.json() : null;
7084
+ }).then(function(j) {
7085
+ var refs = j && j.data && j.data.value && Array.isArray(j.data.value.refs) ? j.data.value.refs : [];
7086
+ favSet = new Set(refs.filter(function(x) {
7087
+ return typeof x === "string";
7088
+ }));
7089
+ }).catch(function() {
7090
+ });
7091
+ }
7092
+ function persist2() {
7093
+ var token = getCortexOwnerToken();
7094
+ var b = base2();
7095
+ if (!token || !b) return Promise.resolve();
7096
+ return fetch(b + "/v1/memory", {
7097
+ method: "POST",
7098
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
7099
+ body: JSON.stringify({
7100
+ key: KEY2,
7101
+ value: { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), refs: Array.from(favSet) },
7102
+ visibility: "private"
7103
+ })
7104
+ }).catch(function() {
7105
+ });
7106
+ }
7107
+ function toggleFavorite(ref) {
7108
+ var now;
7109
+ if (favSet.has(ref)) {
7110
+ favSet.delete(ref);
7111
+ now = false;
7112
+ } else {
7113
+ favSet.add(ref);
7114
+ now = true;
7115
+ }
7116
+ return persist2().then(function() {
7117
+ return now;
7118
+ });
7119
+ }
7120
+ function favStarHtml(ref) {
7121
+ var on = favSet.has(ref);
7122
+ var title = on ? t("fav.remove") || "Remove from favourites" : t("fav.add") || "Add to favourites";
7123
+ return '<button class="fav-toggle' + (on ? " on" : "") + `" onclick="event.stopPropagation(); window._launcher.toggleFavorite('` + String(ref).replace(/'/g, "\\'") + `')" title="` + title + '">' + (on ? "⭐" : "☆") + "</button>";
7125
7124
  }
7126
7125
 
7127
7126
  // src/static/app-catalog/js/server-io.js
@@ -7133,137 +7132,18 @@
7133
7132
  var setOwnServerApps;
7134
7133
  var getActiveTag;
7135
7134
  var getSearchQuery;
7136
- var generateId5;
7137
- var renderApps4;
7135
+ var generateId4;
7136
+ var renderApps3;
7138
7137
  var refreshAll2;
7139
7138
  function initServerIo(deps) {
7140
- ({ getMainApps: getMainApps3, getServerState: getServerState2, getServerManifests: getServerManifests3, setServerManifests, getOwnServerApps, setOwnServerApps, getActiveTag, getSearchQuery, generateId: generateId5, renderApps: renderApps4, refreshAll: refreshAll2 } = deps);
7141
- }
7142
- var aimeatAppsCache = [];
7143
- function formatFileSize(bytes) {
7144
- if (bytes < 1024) return bytes + " B";
7145
- if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
7146
- return (bytes / 1048576).toFixed(1) + " MB";
7147
- }
7148
- function importFromAimeat() {
7149
- var config = loadConfig();
7150
- if (!config.aimeatUrl) {
7151
- showNotice("Set AIMEAT server URL in Settings first");
7152
- return;
7153
- }
7154
- var aimeatUrl = config.aimeatUrl.replace(/\/+$/, "");
7155
- closeModal();
7156
- fetch(aimeatUrl + "/v1/apps?include_peers=true").then(function(resp) {
7157
- if (!resp.ok) throw new Error("Server returned " + resp.status);
7158
- return resp.json();
7159
- }).then(function(json) {
7160
- var apps = json.data && json.data.apps ? json.data.apps : [];
7161
- var peerApps = json.data && json.data.peer_apps ? json.data.peer_apps : [];
7162
- for (var p = 0; p < peerApps.length; p++) {
7163
- peerApps[p]._from_peer = true;
7164
- }
7165
- var allFetched = apps.concat(peerApps);
7166
- if (allFetched.length === 0) {
7167
- showNotice("No apps found on the AIMEAT server");
7168
- return;
7169
- }
7170
- aimeatAppsCache = allFetched;
7171
- showAimeatImportDialog(allFetched, aimeatUrl);
7172
- }).catch(function(err) {
7173
- var msg = "Failed to fetch apps from AIMEAT server.\n\n" + (err.message || err);
7174
- if (err.message && err.message.indexOf("Failed to fetch") !== -1) {
7175
- msg += "\n\nIf the launcher is opened via file://, CORS will block the request. Open it from the AIMEAT server instead.";
7176
- }
7177
- showNotice(msg);
7178
- });
7179
- }
7180
- function showAimeatImportDialog(apps, aimeatUrl) {
7181
- var listEl = document.getElementById("aimeat-app-list");
7182
- var html = "";
7183
- for (var i = 0; i < apps.length; i++) {
7184
- var app = apps[i];
7185
- var displayName = app.manifest && app.manifest.name ? app.manifest.name : app.filename;
7186
- var displayAuthor = app.manifest && app.manifest.authorDisplay ? app.manifest.authorDisplay : app.owner || "";
7187
- var displayDesc = app.manifest && app.manifest.description ? app.manifest.description : "";
7188
- var displayVer = app.version_number ? "v" + app.version_number : "";
7189
- var displayCat = app.manifest && app.manifest.category ? app.manifest.category : "";
7190
- var peerLabel = app._from_peer && app._peer_node ? " · 🌐 " + escapeHtml(app._peer_node) : "";
7191
- html += '<div class="aimeat-app-item"><input type="checkbox" data-index="' + i + '" checked/><div class="aimeat-app-info"><div class="aimeat-app-name">' + escapeHtml(displayName) + (displayVer ? ' <span style="opacity:.5;font-size:.85em">' + escapeHtml(displayVer) + "</span>" : "") + '</div><div class="aimeat-app-size">' + formatFileSize(app.size) + (displayAuthor ? " · " + escapeHtml(displayAuthor) : "") + (displayCat ? " · " + escapeHtml(displayCat) : "") + peerLabel + "</div>" + (displayDesc ? '<div class="aimeat-app-size" style="opacity:.6">' + escapeHtml(displayDesc.substring(0, 100)) + "</div>" : "") + '</div><select data-index="' + i + '"><option value="link">Link (online only)</option><option value="download">Download (offline)</option></select></div>';
7192
- }
7193
- listEl.innerHTML = html;
7194
- listEl.dataset.aimeatUrl = aimeatUrl;
7195
- document.getElementById("aimeat-import-overlay").hidden = false;
7196
- }
7197
- function processAimeatImport() {
7198
- var listEl = document.getElementById("aimeat-app-list");
7199
- var aimeatUrl = listEl.dataset.aimeatUrl;
7200
- var checkboxes = listEl.querySelectorAll('input[type="checkbox"]');
7201
- var selects = listEl.querySelectorAll("select");
7202
- var config = loadConfig();
7203
- var defaultOpenMode = config.defaultOpenMode || "tab";
7204
- var toImport = [];
7205
- for (var i = 0; i < checkboxes.length; i++) {
7206
- if (checkboxes[i].checked) {
7207
- var idx = parseInt(checkboxes[i].getAttribute("data-index"), 10);
7208
- var mode = selects[i].value;
7209
- toImport.push({ app: aimeatAppsCache[idx], mode });
7210
- }
7211
- }
7212
- if (toImport.length === 0) {
7213
- showNotice("No apps selected");
7214
- return;
7215
- }
7216
- var promises = [];
7217
- for (var j = 0; j < toImport.length; j++) {
7218
- (function(item) {
7219
- var app = item.app;
7220
- var name = app.manifest && app.manifest.name ? app.manifest.name : app.filename.replace(/\.html?$/i, "");
7221
- var importedVersion = app.version_number || 1;
7222
- var baseUrl = app._from_peer && app._peer_url ? app._peer_url.replace(/\/+$/, "") : aimeatUrl;
7223
- if (item.mode === "link") {
7224
- var inlineUrl = baseUrl + app.download_url + "?mode=inline";
7225
- promises.push(addAppFromUrl(name, inlineUrl, "📦", ["aimeat"], defaultOpenMode));
7226
- } else {
7227
- var downloadUrl = baseUrl + app.download_url;
7228
- promises.push(
7229
- fetch(downloadUrl).then(function(resp) {
7230
- if (!resp.ok) throw new Error("Download failed: " + resp.status);
7231
- return resp.text();
7232
- }).then(function(content) {
7233
- var encoded = btoa(unescape(encodeURIComponent(content)));
7234
- var record = {
7235
- id: generateId5(),
7236
- name,
7237
- description: app.manifest && app.manifest.description ? app.manifest.description : "",
7238
- source: "aimeat",
7239
- aimeatOwner: app.owner || null,
7240
- aimeatFilename: app.filename || null,
7241
- aimeatVersion: importedVersion,
7242
- url: null,
7243
- blob: encoded,
7244
- tags: ["aimeat"],
7245
- openMode: defaultOpenMode,
7246
- icon: "📦",
7247
- screenshot: null,
7248
- favorite: false,
7249
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
7250
- lastOpenedAt: null
7251
- };
7252
- return saveApp(record);
7253
- })
7254
- );
7255
- }
7256
- })(toImport[j]);
7257
- }
7258
- Promise.all(promises).then(function() {
7259
- document.getElementById("aimeat-import-overlay").hidden = true;
7260
- renderApps4();
7261
- }).catch(function(err) {
7262
- showNotice("Import error: " + (err.message || err));
7263
- });
7139
+ ({ getMainApps: getMainApps3, getServerState: getServerState2, getServerManifests: getServerManifests3, setServerManifests, getOwnServerApps, setOwnServerApps, getActiveTag, getSearchQuery, generateId: generateId4, renderApps: renderApps3, refreshAll: refreshAll2 } = deps);
7264
7140
  }
7141
+ var lastAllServerApps = [];
7142
+ var lastAimeatUrl = "";
7265
7143
  var publishAppId = null;
7266
- function showPublishModal2(appId) {
7144
+ var publishUnlisted = false;
7145
+ function showPublishModal3(appId, opts) {
7146
+ publishUnlisted = !!(opts && opts.unlisted);
7267
7147
  var app = null;
7268
7148
  for (var i = 0; i < getMainApps3().length; i++) {
7269
7149
  if (getMainApps3()[i].id === appId) {
@@ -7385,7 +7265,16 @@
7385
7265
  app.publishedVersionNumber = json.data.version_number || 1;
7386
7266
  app.publishedVersionsUrl = json.data.versions_url || null;
7387
7267
  saveApp(app).then(function() {
7388
- loadPublishedApps3();
7268
+ if (publishUnlisted) {
7269
+ app.parked = true;
7270
+ statusEl.textContent = "✔ " + (t("publish.savedUnlisted") || "Saved (unlisted)");
7271
+ try {
7272
+ toggleParkApp(filename, true);
7273
+ } catch (e) {
7274
+ }
7275
+ }
7276
+ publishUnlisted = false;
7277
+ loadPublishedApps2();
7389
7278
  setTimeout(function() {
7390
7279
  var ov = document.getElementById("publish-overlay");
7391
7280
  if (ov) ov.hidden = true;
@@ -7539,7 +7428,7 @@
7539
7428
  statusEl.textContent = "✔ " + t("subModal.assigned") + " — " + sub + "." + apexHostLabel();
7540
7429
  return loadSubdomainSites();
7541
7430
  }).then(function() {
7542
- loadPublishedApps3();
7431
+ loadPublishedApps2();
7543
7432
  }).catch(function(err) {
7544
7433
  statusEl.style.color = "#ef4444";
7545
7434
  statusEl.textContent = err.message || String(err);
@@ -7557,7 +7446,7 @@
7557
7446
  subdomainModalState.existingSub = null;
7558
7447
  return loadSubdomainSites();
7559
7448
  }).then(function() {
7560
- loadPublishedApps3();
7449
+ loadPublishedApps2();
7561
7450
  }).catch(function(err) {
7562
7451
  statusEl.style.color = "#ef4444";
7563
7452
  statusEl.textContent = err.message || String(err);
@@ -7882,7 +7771,7 @@
7882
7771
  }
7883
7772
  return page(0);
7884
7773
  }
7885
- function loadPublishedApps3() {
7774
+ function loadPublishedApps2() {
7886
7775
  var config = loadConfig();
7887
7776
  var aimeatUrl = config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, "") : "";
7888
7777
  var communitySection = document.getElementById("community-section");
@@ -7901,10 +7790,10 @@
7901
7790
  }
7902
7791
  } catch (e) {
7903
7792
  }
7904
- getAllApps().then(function() {
7793
+ Promise.all([getAllApps(), loadFavorites(), loadPromoted()]).then(function() {
7905
7794
  if (!aimeatUrl) {
7906
7795
  setOwnServerApps([]);
7907
- renderApps4();
7796
+ renderApps3();
7908
7797
  if (communitySection) communitySection.style.display = "none";
7909
7798
  return;
7910
7799
  }
@@ -7924,15 +7813,18 @@
7924
7813
  var communityApps = currentOwner ? serverApps.filter(function(a) {
7925
7814
  return !sameOwner(a.owner, currentOwner);
7926
7815
  }) : serverApps;
7816
+ lastAllServerApps = serverApps;
7817
+ lastAimeatUrl = aimeatUrl;
7927
7818
  return loadSubdomainSites().then(function() {
7928
7819
  setOwnServerApps(currentOwner ? ownApps : []);
7929
- renderApps4();
7820
+ renderApps3();
7930
7821
  renderCommunityApps(communityApps, aimeatUrl, communitySection, communityGrid, communityCountEl, currentOwner);
7822
+ renderFavorites(serverApps, aimeatUrl);
7931
7823
  applyServerFilter();
7932
7824
  });
7933
7825
  }).catch(function() {
7934
7826
  setOwnServerApps([]);
7935
- renderApps4();
7827
+ renderApps3();
7936
7828
  if (communitySection) communitySection.style.display = "none";
7937
7829
  });
7938
7830
  });
@@ -7962,6 +7854,21 @@
7962
7854
  var sectionEl = document.getElementById("community-section");
7963
7855
  if (sectionEl) sectionEl.style.display = filtering && shown === 0 ? "none" : "";
7964
7856
  }
7857
+ function publishedCardHtml(sa, aimeatUrl) {
7858
+ var owner = sa.owner || "";
7859
+ var fn = sa.filename || "";
7860
+ var ref = owner + "/" + fn;
7861
+ var m = sa.manifest || {};
7862
+ var name = m.name || fn;
7863
+ var version = sa.version_number ? "v" + sa.version_number : "";
7864
+ var description = m.descriptions && m.descriptions[getLang()] || m.description || "";
7865
+ var date = sa.created_at ? new Date(sa.created_at).toLocaleDateString() : "";
7866
+ var author = m.authorDisplay || owner;
7867
+ var viewUrl = aimeatUrl + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(fn);
7868
+ var shipsAgent = !!(m.cortex && m.cortex.agents && m.cortex.agents.length);
7869
+ return '<div class="published-card"' + filterAttr(name, m.tags || []) + ">" + favStarHtml(ref) + '<div class="published-card-name">' + escapeHtml(name) + "</div>" + (description ? '<div class="published-card-desc">' + escapeHtml(description) + "</div>" : "") + '<div class="published-card-footer"><div class="published-card-metaline"><span class="pcm-main" style="color:var(--accent)">&#x1F464; ' + escapeHtml(author) + "</span>" + (date ? '<span class="pcm-date">' + date + "</span>" : "") + `</div><div class="published-card-actions"><button onclick="window._launcher.viewPublished('` + escapeHtml(viewUrl) + "?mode=inline', '" + jsArg(name) + `')">` + t("card.view") + "</button>" + // Fork is offered on a community app only when its owner marked it forkable.
7870
+ (sa.forkable ? `<button onclick="window._launcher.forkVersion('` + escapeHtml(owner) + "', '" + escapeHtml(fn) + "', " + (sa.version_number || 0) + ')" title="' + escapeHtml(t("card.forkHint")) + '">' + t("card.fork") + "</button>" : "") + (shipsAgent ? `<button onclick="window._launcher.showAppAgentsModal('` + jsArg(owner) + "', '" + jsArg(fn) + `')" title="` + escapeHtml(t("card.agentHint")) + '">' + t("card.agent") + "</button>" : "") + "</div>" + (version || sa.forks && sa.forks > 0 || shipsAgent ? '<div class="published-card-badgerow">' + (shipsAgent ? '<span class="pcb-agent" title="' + escapeHtml(t("card.agentHint")) + '">&#x1F916;</span>' : "") + (sa.forks && sa.forks > 0 ? '<span class="pcb-forks" title="' + escapeHtml(t("card.forksHint")) + `" onclick="window._launcher.showLineageModal('` + escapeHtml(owner) + "', '" + escapeHtml(fn) + `')">⑂ ` + sa.forks + "</span>" : "") + (version ? '<span class="pcb-version">' + escapeHtml(version) + "</span>" : "") + "</div>" : "") + "</div></div>";
7871
+ }
7965
7872
  function renderCommunityApps(serverApps, aimeatUrl, section, grid, countEl, currentOwner) {
7966
7873
  if (!section || !grid || !countEl) return;
7967
7874
  if (serverApps.length === 0) {
@@ -7973,22 +7880,51 @@
7973
7880
  section.style.display = "";
7974
7881
  countEl.textContent = "(" + serverApps.length + ")";
7975
7882
  var html = "";
7976
- for (var i = 0; i < serverApps.length; i++) {
7977
- var sa = serverApps[i];
7978
- var name = sa.manifest && sa.manifest.name ? sa.manifest.name : sa.filename || "";
7979
- var version = sa.version_number ? "v" + sa.version_number : "";
7980
- var description = sa.manifest && sa.manifest.description ? sa.manifest.description : "";
7981
- var date = sa.created_at ? new Date(sa.created_at).toLocaleDateString() : "";
7982
- var author = sa.manifest && sa.manifest.authorDisplay ? sa.manifest.authorDisplay : sa.owner || "";
7983
- var viewUrl = aimeatUrl + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(sa.filename || "");
7984
- var shipsAgent = !!(sa.manifest && sa.manifest.cortex && sa.manifest.cortex.agents && sa.manifest.cortex.agents.length);
7985
- html += '<div class="published-card"' + filterAttr(name, sa.manifest && sa.manifest.tags || []) + '><div class="published-card-name">' + escapeHtml(name) + "</div>" + (description ? '<div class="published-card-desc">' + escapeHtml(description) + "</div>" : "") + '<div class="published-card-footer"><div class="published-card-metaline"><span class="pcm-main" style="color:var(--accent)">&#x1F464; ' + escapeHtml(author) + "</span>" + (date ? '<span class="pcm-date">' + date + "</span>" : "") + `</div><div class="published-card-actions"><button onclick="window._launcher.viewPublished('` + escapeHtml(viewUrl) + "?mode=inline', '" + jsArg(name) + `')">` + t("card.view") + "</button>" + // Fork is offered on a community (someone else's) app only when its owner
7986
- // marked it forkable; otherwise the server would reject the fork (403).
7987
- (sa.forkable ? `<button onclick="window._launcher.forkVersion('` + escapeHtml(sa.owner || "") + "', '" + escapeHtml(sa.filename || "") + "', " + (sa.version_number || 0) + ')" title="' + escapeHtml(t("card.forkHint")) + '">' + t("card.fork") + "</button>" : "") + (shipsAgent ? `<button onclick="window._launcher.showAppAgentsModal('` + jsArg(sa.owner || "") + "', '" + jsArg(sa.filename || "") + `')" title="` + escapeHtml(t("card.agentHint")) + '">' + t("card.agent") + "</button>" : "") + "</div>" + (version || sa.forks && sa.forks > 0 || shipsAgent ? '<div class="published-card-badgerow">' + (shipsAgent ? '<span class="pcb-agent" title="' + escapeHtml(t("card.agentHint")) + '">&#x1F916;</span>' : "") + (sa.forks && sa.forks > 0 ? '<span class="pcb-forks" title="' + escapeHtml(t("card.forksHint")) + `" onclick="window._launcher.showLineageModal('` + escapeHtml(sa.owner || "") + "', '" + escapeHtml(sa.filename || "") + `')">⑂ ` + sa.forks + "</span>" : "") + (version ? '<span class="pcb-version">' + escapeHtml(version) + "</span>" : "") + "</div>" : "") + "</div></div>";
7988
- }
7883
+ for (var i = 0; i < serverApps.length; i++) html += publishedCardHtml(serverApps[i], aimeatUrl);
7989
7884
  grid.innerHTML = html;
7990
7885
  updateCommunityEmpty();
7991
7886
  }
7887
+ function renderFavorites(allServerApps, aimeatUrl) {
7888
+ var section = document.getElementById("favorites-section");
7889
+ var grid = document.getElementById("favorites-grid");
7890
+ var countEl = document.getElementById("favorites-count");
7891
+ if (!section || !grid) return;
7892
+ var favs = (allServerApps || []).filter(function(sa) {
7893
+ return isFavorite((sa.owner || "") + "/" + (sa.filename || ""));
7894
+ });
7895
+ if (favs.length === 0) {
7896
+ section.style.display = "none";
7897
+ grid.innerHTML = "";
7898
+ return;
7899
+ }
7900
+ section.style.display = "";
7901
+ if (countEl) countEl.textContent = "(" + favs.length + ")";
7902
+ var html = "";
7903
+ for (var i = 0; i < favs.length; i++) html += publishedCardHtml(favs[i], aimeatUrl);
7904
+ grid.innerHTML = html;
7905
+ }
7906
+ function refreshFavoritesUI() {
7907
+ renderFavorites(lastAllServerApps, lastAimeatUrl);
7908
+ var currentOwner = null;
7909
+ try {
7910
+ var s = window.AIMEAT && window.AIMEAT.auth && window.AIMEAT.auth.getSession();
7911
+ currentOwner = s && s.owner || JSON.parse(localStorage.getItem("aimeat_session") || "{}").owner || null;
7912
+ } catch (e) {
7913
+ }
7914
+ var communityApps = currentOwner ? lastAllServerApps.filter(function(a) {
7915
+ return !sameOwner(a.owner, currentOwner);
7916
+ }) : lastAllServerApps;
7917
+ renderCommunityApps(
7918
+ communityApps,
7919
+ lastAimeatUrl,
7920
+ document.getElementById("community-section"),
7921
+ document.getElementById("community-grid"),
7922
+ document.getElementById("community-count"),
7923
+ currentOwner
7924
+ );
7925
+ applyServerFilter();
7926
+ renderApps3();
7927
+ }
7992
7928
  async function unpublishApp(appId) {
7993
7929
  if (!await showConfirm(t("confirm.removePublished"))) return;
7994
7930
  var app = null;
@@ -8020,7 +7956,7 @@
8020
7956
  delete app.publishedVersionNumber;
8021
7957
  delete app.publishedVersionsUrl;
8022
7958
  saveApp(app).then(function() {
8023
- loadPublishedApps3();
7959
+ loadPublishedApps2();
8024
7960
  });
8025
7961
  } else {
8026
7962
  showNotice("Failed to remove: " + (json.error && json.error.message || "Unknown error"));
@@ -8046,7 +7982,7 @@
8046
7982
  }).then(function(json) {
8047
7983
  if (json.ok) {
8048
7984
  if (getServerState2()[filename]) getServerState2()[filename].parked = !!parked;
8049
- loadPublishedApps3();
7985
+ loadPublishedApps2();
8050
7986
  refreshServerMgmt();
8051
7987
  } else {
8052
7988
  showNotice("Failed: " + (json.error && json.error.message ? json.error.message : "Unknown error"));
@@ -8072,7 +8008,7 @@
8072
8008
  }).then(function(json) {
8073
8009
  if (json.ok) {
8074
8010
  if (getServerState2()[filename]) getServerState2()[filename].forkable = !!forkable;
8075
- loadPublishedApps3();
8011
+ loadPublishedApps2();
8076
8012
  refreshServerMgmt();
8077
8013
  } else {
8078
8014
  showNotice("Failed: " + (json.error && json.error.message ? json.error.message : "Unknown error"));
@@ -8099,7 +8035,7 @@
8099
8035
  return resp.json();
8100
8036
  }).then(function(json) {
8101
8037
  if (json.ok) {
8102
- loadPublishedApps3();
8038
+ loadPublishedApps2();
8103
8039
  } else {
8104
8040
  showNotice("Failed to delete: " + (json.error && json.error.message ? json.error.message : "Unknown error"));
8105
8041
  }
@@ -8158,7 +8094,7 @@
8158
8094
  }
8159
8095
  function filterByTag(tag) {
8160
8096
  setActiveTag(tag);
8161
- renderApps5();
8097
+ renderApps4();
8162
8098
  applyServerFilter();
8163
8099
  }
8164
8100
  function launchApp2(id, mode) {
@@ -8232,7 +8168,7 @@
8232
8168
  }
8233
8169
  }
8234
8170
  function buildLibraryEntries(localApps, serverApps) {
8235
- var base = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
8171
+ var base3 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
8236
8172
  var byFilename = {};
8237
8173
  var entries = [];
8238
8174
  serverStateByFilename = {};
@@ -8262,7 +8198,7 @@
8262
8198
  filename: la.publishedFilename || null,
8263
8199
  owner: null,
8264
8200
  versionNumber: la.publishedVersionNumber || null,
8265
- viewUrl: la.publishedUrl ? base + la.publishedUrl : "",
8201
+ viewUrl: la.publishedUrl ? base3 + la.publishedUrl : "",
8266
8202
  forkable: false,
8267
8203
  forks: 0
8268
8204
  };
@@ -8285,7 +8221,7 @@
8285
8221
  m.hasDraft = !!sa.has_draft;
8286
8222
  m.hasAgents = shipsAgent;
8287
8223
  m.protection = prot;
8288
- m.viewUrl = base + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn);
8224
+ m.viewUrl = base3 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn);
8289
8225
  if (!m.description && sa.manifest && sa.manifest.description) m.description = sa.manifest.description;
8290
8226
  } else {
8291
8227
  var se = {
@@ -8294,6 +8230,7 @@
8294
8230
  name: sa.manifest && sa.manifest.name || fn,
8295
8231
  icon: "🌐",
8296
8232
  description: sa.manifest && sa.manifest.description || "",
8233
+ descriptions: sa.manifest && sa.manifest.descriptions || null,
8297
8234
  tags: sa.manifest && sa.manifest.tags || [],
8298
8235
  favorite: false,
8299
8236
  sortOrder: void 0,
@@ -8309,7 +8246,7 @@
8309
8246
  filename: fn,
8310
8247
  owner: sa.owner || "",
8311
8248
  versionNumber: sa.version_number || null,
8312
- viewUrl: base && fn ? base + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn) : "",
8249
+ viewUrl: base3 && fn ? base3 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn) : "",
8313
8250
  forkable: !!sa.forkable,
8314
8251
  forks: sa.forks || 0,
8315
8252
  protection: prot,
@@ -8335,20 +8272,12 @@
8335
8272
  }
8336
8273
  return entries;
8337
8274
  }
8338
- function renderApps5() {
8275
+ function renderApps4() {
8339
8276
  getAllApps().then(function(apps) {
8340
8277
  setAllApps(apps);
8341
- var entries = buildLibraryEntries(apps, ownServerApps);
8278
+ var entries = buildLibraryEntries([], ownServerApps);
8342
8279
  entries.sort(function(a, b) {
8343
- if (a.favorite && !b.favorite) return -1;
8344
- if (!a.favorite && b.favorite) return 1;
8345
- var oA = typeof a.sortOrder === "number" ? a.sortOrder : Infinity;
8346
- var oB = typeof b.sortOrder === "number" ? b.sortOrder : Infinity;
8347
- if (oA !== oB) return oA - oB;
8348
- var dateA = a.lastOpenedAt || a.addedAt || "";
8349
- var dateB = b.lastOpenedAt || b.addedAt || "";
8350
- if (dateA > dateB) return -1;
8351
- if (dateA < dateB) return 1;
8280
+ if (!!a.parked !== !!b.parked) return a.parked ? 1 : -1;
8352
8281
  return 0;
8353
8282
  });
8354
8283
  var filtered = entries;
@@ -8397,14 +8326,8 @@
8397
8326
  grid.innerHTML = html;
8398
8327
  }
8399
8328
  var statsEl = document.getElementById("stats");
8400
- var totalSize = 0;
8401
- for (var sz = 0; sz < entries.length; sz++) {
8402
- if (entries[sz].blob) totalSize += entries[sz].blob.length;
8403
- }
8404
- var sizeLabel = totalSize < 1024 ? totalSize + " B" : totalSize < 1048576 ? (totalSize / 1024).toFixed(1) + " KB" : (totalSize / 1048576).toFixed(1) + " MB";
8405
- statsEl.textContent = entries.length + " " + t("stats.apps") + " · " + sizeLabel + " " + t("stats.stored");
8329
+ statsEl.textContent = entries.length + " " + t("stats.apps");
8406
8330
  renderTags();
8407
- renderRecentlyOpened();
8408
8331
  });
8409
8332
  }
8410
8333
  function libraryCardHtml(e, i) {
@@ -8416,25 +8339,11 @@
8416
8339
  var st = libStatus(e);
8417
8340
  var dragAttrs = e.hasLocal ? ' draggable="true" ondragstart="window._launcher.onCardDragStart(event)" ondragend="window._launcher.onCardDragEnd(event)" ondragover="window._launcher.onCardDragOver(event)" ondrop="window._launcher.onCardDrop(event)"' : "";
8418
8341
  var menuBtn = e.hasLocal ? `<button class="card-menu-btn" onclick="event.stopPropagation(); window._launcher.showContextMenu(event, '` + jsArg(e.localId) + `')" title="` + escapeHtml(t("common.menu")) + '">⋮</button>' : "";
8419
- return '<div class="app-card' + (e.hasLocal ? "" : " server-only") + '" data-id="' + idAttr + '"' + dragAttrs + filterAttr(e.name, e.tags) + ' onclick="' + detailCall + '"' + (e.hasLocal ? ` oncontextmenu="window._launcher.showContextMenu(event, '` + jsArg(e.localId) + `')"` : "") + ' style="animation-delay:' + i * 0.04 + 's"><span class="app-status-badge st-' + st + '">' + escapeHtml(libStatusLabel(e)) + "</span>" + (e.hasDraft ? '<span class="app-staging-badge" title="' + escapeHtml(t("card.stagingHint")) + '">' + escapeHtml(t("card.stagingBadge")) + "</span>" : "") + menuBtn + '<div class="app-icon">' + escapeHtml(e.icon || "📝") + '</div><div class="app-name">' + escapeHtml(e.name) + (e.origin === "ai-published" ? ' <span class="ai-origin-badge">AI</span>' : "") + (e.hasAgents ? ' <span class="pcb-agent" title="' + escapeHtml(t("card.agentHint")) + '">🤖</span>' : "") + "</div>" + (e.description ? '<div class="app-source">' + escapeHtml(e.description) + "</div>" : '<div class="app-source">' + sourceLabel(e.source) + "</div>") + '<div class="app-actions">' + openBtns + '<button onclick="event.stopPropagation(); ' + detailCall + '">' + escapeHtml(t("ctx.details")) + "</button></div>" + (e.favorite ? '<span class="fav-star visible">⭐</span>' : "") + "</div>";
8420
- }
8421
- function renderRecentlyOpened() {
8422
- var section = document.getElementById("recent-section");
8423
- var strip = document.getElementById("recent-strip");
8424
- var recent = getMainApps4().filter(function(a) {
8425
- return a.lastOpenedAt;
8426
- }).sort(function(a, b) {
8427
- return (b.lastOpenedAt || "") > (a.lastOpenedAt || "") ? 1 : -1;
8428
- }).slice(0, 5);
8429
- if (recent.length === 0) {
8430
- section.style.display = "none";
8431
- return;
8432
- }
8433
- section.style.display = "block";
8434
- strip.innerHTML = recent.map(function(app) {
8435
- var icon = app.icon || "📝";
8436
- return `<div style="background:var(--surface-glass);border:1px solid var(--border-subtle);border-radius:10px;padding:.6rem .8rem;cursor:pointer;min-width:120px;flex-shrink:0;transition:all .15s" onclick="window._launcher.launchApp('` + escapeHtml(app.id) + "', '" + escapeHtml(app.openMode || "tab") + `')" onmouseover="this.style.borderColor='var(--border-hover)'" onmouseout="this.style.borderColor='var(--border-subtle)'"><div style="font-size:1.3rem;text-align:center">` + escapeHtml(icon) + '</div><div style="font-size:.75rem;font-weight:600;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100px">' + escapeHtml(app.name) + "</div></div>";
8437
- }).join("");
8342
+ return '<div class="app-card' + (e.hasLocal ? "" : " server-only") + '" data-id="' + idAttr + '"' + dragAttrs + filterAttr(e.name, e.tags) + ' onclick="' + detailCall + '"' + (e.hasLocal ? ` oncontextmenu="window._launcher.showContextMenu(event, '` + jsArg(e.localId) + `')"` : "") + ' style="animation-delay:' + i * 0.04 + 's"><span class="app-status-badge st-' + st + '">' + escapeHtml(libStatusLabel(e)) + "</span>" + (e.hasDraft ? '<span class="app-staging-badge" title="' + escapeHtml(t("card.stagingHint")) + '">' + escapeHtml(t("card.stagingBadge")) + "</span>" : "") + menuBtn + '<div class="app-icon">' + escapeHtml(e.icon || "📝") + '</div><div class="app-name">' + escapeHtml(e.name) + (e.origin === "ai-published" ? ' <span class="ai-origin-badge">AI</span>' : "") + (e.hasAgents ? ' <span class="pcb-agent" title="' + escapeHtml(t("card.agentHint")) + '">🤖</span>' : "") + "</div>" + (function() {
8343
+ var d = e.descriptions && e.descriptions[getLang()] || e.description || "";
8344
+ return d ? '<div class="app-source">' + escapeHtml(d) + "</div>" : '<div class="app-source">' + sourceLabel(e.source) + "</div>";
8345
+ })() + '<div class="app-actions">' + openBtns + '<button onclick="event.stopPropagation(); ' + detailCall + '">' + escapeHtml(t("ctx.details")) + "</button></div>" + // Server-backed favourite toggle (owner/filename); only for server apps with a filename.
8346
+ (e.filename ? favStarHtml((e.owner || e.aimeatOwner || "") + "/" + e.filename) : "") + "</div>";
8438
8347
  }
8439
8348
  var currentIframeUrl = "";
8440
8349
  function closeIframe() {
@@ -8496,24 +8405,12 @@
8496
8405
  openDetailView(appId);
8497
8406
  break;
8498
8407
  case "edit":
8499
- setEditingAppId(appId);
8500
- document.getElementById("modal-title").textContent = "Edit App";
8501
- document.getElementById("app-name").value = app.name || "";
8502
- document.getElementById("app-icon").value = app.icon || "";
8503
- document.getElementById("app-tags").value = (app.tags || []).join(", ");
8504
- if (app.source === "url") {
8505
- switchTab("url");
8506
- document.getElementById("app-url").value = app.url || "";
8507
- } else {
8508
- switchTab("file");
8509
- document.getElementById("selected-file-name").textContent = "(existing file)";
8510
- }
8511
- document.getElementById("modal-overlay").hidden = false;
8408
+ openDetailView(appId);
8512
8409
  break;
8513
8410
  case "favorite":
8514
8411
  app.favorite = !app.favorite;
8515
8412
  saveApp(app).then(function() {
8516
- renderApps5();
8413
+ renderApps4();
8517
8414
  });
8518
8415
  break;
8519
8416
  case "view-source":
@@ -8524,13 +8421,13 @@
8524
8421
  return app.name || "this app";
8525
8422
  }))) {
8526
8423
  deleteApp(appId).then(function() {
8527
- renderApps5();
8528
- loadPublishedApps3();
8424
+ renderApps4();
8425
+ loadPublishedApps2();
8529
8426
  });
8530
8427
  }
8531
8428
  break;
8532
8429
  case "publish":
8533
- showPublishModal2(appId);
8430
+ showPublishModal3(appId);
8534
8431
  break;
8535
8432
  case "improve-ai":
8536
8433
  openPromptBuilder(app);
@@ -8622,58 +8519,107 @@
8622
8519
  overlay.dataset.appName = "Homepage";
8623
8520
  overlay.hidden = false;
8624
8521
  }
8625
- var dragSourceId = null;
8626
- function onCardDragStart(e) {
8627
- var card = e.target.closest(".app-card");
8628
- if (!card) return;
8629
- dragSourceId = card.dataset.id;
8630
- card.classList.add("dragging");
8631
- e.dataTransfer.effectAllowed = "move";
8632
- e.dataTransfer.setData("text/plain", dragSourceId);
8633
- }
8634
- function onCardDragEnd(e) {
8635
- var card = e.target.closest(".app-card");
8636
- if (card) card.classList.remove("dragging");
8637
- var cards = document.querySelectorAll(".app-card.drag-over");
8638
- for (var i = 0; i < cards.length; i++) cards[i].classList.remove("drag-over");
8639
- dragSourceId = null;
8640
- }
8641
- function onCardDragOver(e) {
8642
- e.preventDefault();
8643
- e.dataTransfer.dropEffect = "move";
8644
- var card = e.target.closest(".app-card");
8645
- if (!card || card.dataset.id === dragSourceId) return;
8646
- var cards = document.querySelectorAll(".app-card.drag-over");
8647
- for (var i = 0; i < cards.length; i++) cards[i].classList.remove("drag-over");
8648
- card.classList.add("drag-over");
8649
- }
8650
- function onCardDrop(e) {
8651
- e.preventDefault();
8652
- var targetCard = e.target.closest(".app-card");
8653
- if (!targetCard) return;
8654
- var targetId = targetCard.dataset.id;
8655
- if (!dragSourceId || dragSourceId === targetId) return;
8656
- var srcIdx = -1, tgtIdx = -1;
8657
- for (var i = 0; i < getMainApps4().length; i++) {
8658
- if (getMainApps4()[i].id === dragSourceId) srcIdx = i;
8659
- if (getMainApps4()[i].id === targetId) tgtIdx = i;
8522
+
8523
+ // src/static/app-catalog/js/migrate.js
8524
+ var DONE_KEY = "appCatalogLocalMigrated";
8525
+ async function legacyDbNames() {
8526
+ var names = ["AppLauncherDB"];
8527
+ try {
8528
+ if (indexedDB.databases) {
8529
+ var list = await indexedDB.databases();
8530
+ list.forEach(function(d) {
8531
+ if (d && d.name && d.name.indexOf("AppLauncherDB") === 0 && names.indexOf(d.name) < 0) names.push(d.name);
8532
+ });
8533
+ }
8534
+ } catch (e) {
8660
8535
  }
8661
- if (srcIdx === -1 || tgtIdx === -1) return;
8662
- var moved = getMainApps4().splice(srcIdx, 1)[0];
8663
- getMainApps4().splice(tgtIdx, 0, moved);
8664
- var saves = [];
8665
- for (var j = 0; j < getMainApps4().length; j++) {
8666
- getMainApps4()[j].sortOrder = j;
8667
- saves.push(saveApp(getMainApps4()[j]));
8536
+ return names;
8537
+ }
8538
+ function openExisting(name) {
8539
+ return new Promise(function(resolve) {
8540
+ var req;
8541
+ try {
8542
+ req = indexedDB.open(name);
8543
+ } catch (e) {
8544
+ resolve(null);
8545
+ return;
8546
+ }
8547
+ req.onsuccess = function() {
8548
+ resolve(req.result);
8549
+ };
8550
+ req.onerror = function() {
8551
+ resolve(null);
8552
+ };
8553
+ req.onupgradeneeded = function() {
8554
+ };
8555
+ });
8556
+ }
8557
+ function readApps(db) {
8558
+ return new Promise(function(resolve) {
8559
+ if (!db || !db.objectStoreNames || !db.objectStoreNames.contains("apps")) {
8560
+ resolve([]);
8561
+ return;
8562
+ }
8563
+ try {
8564
+ var tx = db.transaction("apps", "readonly");
8565
+ var r = tx.objectStore("apps").getAll();
8566
+ r.onsuccess = function() {
8567
+ resolve(r.result || []);
8568
+ };
8569
+ r.onerror = function() {
8570
+ resolve([]);
8571
+ };
8572
+ } catch (e) {
8573
+ resolve([]);
8574
+ }
8575
+ });
8576
+ }
8577
+ function downloadJson(apps) {
8578
+ var json = JSON.stringify({ version: 1, exportedAt: (/* @__PURE__ */ new Date()).toISOString(), apps }, null, 2);
8579
+ var blob = new Blob([json], { type: "application/json" });
8580
+ var url = URL.createObjectURL(blob);
8581
+ var a = document.createElement("a");
8582
+ a.href = url;
8583
+ a.download = "app-catalog-local-apps.json";
8584
+ document.body.appendChild(a);
8585
+ a.click();
8586
+ document.body.removeChild(a);
8587
+ URL.revokeObjectURL(url);
8588
+ }
8589
+ async function checkLegacyLocalApps() {
8590
+ try {
8591
+ if (localStorage.getItem(DONE_KEY)) return;
8592
+ } catch (e) {
8593
+ return;
8594
+ }
8595
+ var apps = [];
8596
+ try {
8597
+ var names = await legacyDbNames();
8598
+ for (var i = 0; i < names.length; i++) {
8599
+ var db = await openExisting(names[i]);
8600
+ var found = await readApps(db);
8601
+ if (found && found.length) apps = apps.concat(found);
8602
+ try {
8603
+ if (db) db.close();
8604
+ } catch (e) {
8605
+ }
8606
+ }
8607
+ } catch (e) {
8668
8608
  }
8669
- var grid = document.getElementById("app-grid");
8670
- var cards = grid.querySelectorAll(".app-card");
8671
- for (var k = 0; k < cards.length; k++) {
8672
- cards[k].classList.remove("drag-over", "dragging");
8609
+ if (!apps.length) {
8610
+ try {
8611
+ localStorage.setItem(DONE_KEY, "1");
8612
+ } catch (e) {
8613
+ }
8614
+ return;
8615
+ }
8616
+ var msg = (t("migrate.found") || "You have {n} local app(s) from the old catalog. Download them as a backup?").replace("{n}", String(apps.length));
8617
+ var wants = await showConfirm(msg);
8618
+ if (wants) downloadJson(apps);
8619
+ try {
8620
+ localStorage.setItem(DONE_KEY, "1");
8621
+ } catch (e) {
8673
8622
  }
8674
- Promise.all(saves).then(function() {
8675
- renderApps5();
8676
- });
8677
8623
  }
8678
8624
 
8679
8625
  // src/static/app-catalog/js/main.js
@@ -8687,7 +8633,7 @@
8687
8633
  }
8688
8634
  applyI18n();
8689
8635
  try {
8690
- renderApps5();
8636
+ renderApps4();
8691
8637
  } catch (e) {
8692
8638
  }
8693
8639
  try {
@@ -8695,11 +8641,7 @@
8695
8641
  } catch (e) {
8696
8642
  }
8697
8643
  try {
8698
- loadPublishedApps3();
8699
- } catch (e) {
8700
- }
8701
- try {
8702
- renderRecentlyOpened();
8644
+ loadPublishedApps2();
8703
8645
  } catch (e) {
8704
8646
  }
8705
8647
  try {
@@ -8711,41 +8653,16 @@
8711
8653
  } catch (e) {
8712
8654
  }
8713
8655
  }
8714
- function switchDbMode(mode) {
8715
- if (mode === getDbMode()) return;
8716
- if (mode === "personal" && !currentOwnerName()) {
8717
- updateModeToggle2();
8718
- requireSignInThen(function() {
8719
- switchDbMode("personal");
8720
- });
8721
- return;
8722
- }
8723
- setDbMode(mode);
8724
- closeDbInstance();
8725
- updateModeToggle2();
8726
- refreshAll3();
8727
- }
8728
- function updateModeToggle2() {
8729
- var globalBtn = document.getElementById("mode-global");
8730
- var personalBtn = document.getElementById("mode-personal");
8731
- if (!globalBtn || !personalBtn) return;
8732
- globalBtn.classList.toggle("active", getDbMode() === "global");
8733
- personalBtn.classList.toggle("active", getDbMode() === "personal");
8734
- var signedIn = !!currentOwnerName();
8735
- personalBtn.classList.toggle("mode-btn-locked", !signedIn);
8736
- personalBtn.title = signedIn ? t("mode.personal.title") : t("mode.personal.needsLogin");
8737
- }
8738
8656
  var allApps = [];
8739
8657
  var activeTag = null;
8740
8658
  var searchQuery = "";
8741
8659
  window._launcher = {
8742
- openDB,
8743
8660
  getAllApps,
8744
8661
  saveApp,
8745
8662
  deleteApp,
8746
8663
  loadConfig,
8747
8664
  saveConfig,
8748
- renderApps: renderApps5,
8665
+ renderApps: renderApps4,
8749
8666
  renderTags,
8750
8667
  filterByTag,
8751
8668
  launchApp: launchApp2,
@@ -8762,10 +8679,6 @@
8762
8679
  handleContextAction,
8763
8680
  generateId,
8764
8681
  readFileAsText,
8765
- addAppFromUrl,
8766
- addAppFromFile,
8767
- addAppFromSource,
8768
- addAppFromZip,
8769
8682
  showModal,
8770
8683
  closeModal,
8771
8684
  openSettings,
@@ -8774,18 +8687,22 @@
8774
8687
  saveSettings,
8775
8688
  closeSettings,
8776
8689
  applyTheme,
8777
- exportBackup,
8778
- clearAllData,
8779
- importFromAimeat,
8780
- processAimeatImport,
8781
8690
  viewSource: viewSource2,
8782
8691
  generateHomepagePrompt,
8783
- showPublishModal: showPublishModal2,
8692
+ showPublishModal: showPublishModal3,
8784
8693
  submitPublish,
8785
- loadPublishedApps: loadPublishedApps3,
8694
+ loadPublishedApps: loadPublishedApps2,
8695
+ // Toggle a server-backed favourite (ref = "owner/filename"), then re-render the ⭐ group + stars.
8696
+ toggleFavorite: function(ref) {
8697
+ toggleFavorite(ref).then(function() {
8698
+ try {
8699
+ refreshFavoritesUI();
8700
+ } catch (e) {
8701
+ }
8702
+ });
8703
+ },
8786
8704
  toggleCommunity,
8787
8705
  switchView,
8788
- switchDbMode,
8789
8706
  unpublishApp,
8790
8707
  deleteServerApp,
8791
8708
  toggleParkApp,
@@ -8793,6 +8710,9 @@
8793
8710
  detailAboutEdit,
8794
8711
  detailAboutCancel,
8795
8712
  detailAboutSave,
8713
+ detailTranslateDesc,
8714
+ detailPromoteSave,
8715
+ detailPromoteClear,
8796
8716
  detailToggleFavorite,
8797
8717
  detailAccessCodeEdit,
8798
8718
  detailAccessCodeCancel,
@@ -8812,9 +8732,6 @@
8812
8732
  revokeConsent,
8813
8733
  submitSubdomainAssign,
8814
8734
  unassignSubdomain,
8815
- jsonImportSelectAll,
8816
- submitJsonImport,
8817
- removeDuplicateApps,
8818
8735
  toggleBackupMenu,
8819
8736
  toggleCreateMenu,
8820
8737
  toggleCortexBar,
@@ -8850,7 +8767,6 @@
8850
8767
  detailSetScreenshot,
8851
8768
  detailRefreshScreenshot,
8852
8769
  openPublishedDetail,
8853
- renderRecentlyOpened,
8854
8770
  loadCortexExtensions,
8855
8771
  showCortexPopup,
8856
8772
  cortexCopy,
@@ -8861,10 +8777,6 @@
8861
8777
  cortexEditorExport,
8862
8778
  openPromptBuilder,
8863
8779
  closePbPanel,
8864
- onCardDragStart,
8865
- onCardDragEnd,
8866
- onCardDragOver,
8867
- onCardDrop,
8868
8780
  syncConfigToServer,
8869
8781
  loadConfigFromServer,
8870
8782
  setLanguage,
@@ -8875,16 +8787,16 @@
8875
8787
  window.cortexCopy = cortexCopy;
8876
8788
  function refreshAll3() {
8877
8789
  allApps = [];
8878
- renderApps5();
8879
- loadPublishedApps3();
8880
- renderRecentlyOpened();
8790
+ renderApps4();
8791
+ loadPublishedApps2();
8881
8792
  }
8882
8793
  document.addEventListener("DOMContentLoaded", function() {
8883
8794
  initDetail({
8884
8795
  refreshAll: refreshAll3,
8885
- loadPublishedApps: loadPublishedApps3,
8886
- renderApps: renderApps5,
8887
- updateModeToggle: updateModeToggle2,
8796
+ loadPublishedApps: loadPublishedApps2,
8797
+ renderApps: renderApps4,
8798
+ updateModeToggle: function() {
8799
+ },
8888
8800
  getCortexOwnerToken,
8889
8801
  launchApp: launchApp2,
8890
8802
  viewPublished: viewPublished2,
@@ -8892,7 +8804,7 @@
8892
8804
  generateId,
8893
8805
  generateSharePrompt: generateSharePrompt2,
8894
8806
  openPromptBuilder,
8895
- showPublishModal: showPublishModal2,
8807
+ showPublishModal: showPublishModal3,
8896
8808
  getMainApps: function() {
8897
8809
  return allApps;
8898
8810
  },
@@ -8911,10 +8823,10 @@
8911
8823
  initAppAgents({ getServerManifests: function() {
8912
8824
  return serverAppManifests;
8913
8825
  } });
8914
- initSettings({ generateId, renderApps: renderApps5, loadPublishedApps: loadPublishedApps3 });
8915
- initAppsIo({ generateId, readFileAsText, renderApps: renderApps5, getMainApps: function() {
8826
+ initSettings({ generateId, renderApps: renderApps4, loadPublishedApps: loadPublishedApps2 });
8827
+ initAppsIo({ generateId, readFileAsText, renderApps: renderApps4, getMainApps: function() {
8916
8828
  return allApps;
8917
- } });
8829
+ }, showPublishModal: showPublishModal3 });
8918
8830
  initServerIo({
8919
8831
  getMainApps: function() {
8920
8832
  return allApps;
@@ -8937,7 +8849,7 @@
8937
8849
  return searchQuery;
8938
8850
  },
8939
8851
  generateId,
8940
- renderApps: renderApps5,
8852
+ renderApps: renderApps4,
8941
8853
  refreshAll: refreshAll3
8942
8854
  });
8943
8855
  initRender({
@@ -8969,7 +8881,6 @@
8969
8881
  var _urlLang = _params.get("lang");
8970
8882
  setLang(_urlLang === "fi" || _urlLang === "en" ? _urlLang : loadConfig().language === "fi" ? "fi" : "en");
8971
8883
  applyI18n();
8972
- updateModeToggle2();
8973
8884
  var _savedView = "library";
8974
8885
  try {
8975
8886
  _savedView = localStorage.getItem("appCatalogView") || "library";
@@ -8982,8 +8893,8 @@
8982
8893
  if (_homeLabel && location.host) _homeLabel.textContent = location.host.replace(/^apps\./, "");
8983
8894
  } catch (e) {
8984
8895
  }
8985
- renderApps5();
8986
- loadPublishedApps3();
8896
+ renderApps4();
8897
+ loadPublishedApps2();
8987
8898
  loadCortexExtensions();
8988
8899
  loadConfigFromServer();
8989
8900
  document.getElementById("confirm-ok-btn").addEventListener("click", function() {
@@ -8995,6 +8906,10 @@
8995
8906
  document.getElementById("confirm-overlay").addEventListener("click", function(e) {
8996
8907
  if (e.target === this) closeConfirm(false);
8997
8908
  });
8909
+ try {
8910
+ checkLegacyLocalApps();
8911
+ } catch (e) {
8912
+ }
8998
8913
  if (_params.get("create") === "1") {
8999
8914
  try {
9000
8915
  openPromptBuilder(null);
@@ -9003,7 +8918,7 @@
9003
8918
  }
9004
8919
  document.getElementById("search-input").addEventListener("input", function(e) {
9005
8920
  searchQuery = e.target.value.trim();
9006
- renderApps5();
8921
+ renderApps4();
9007
8922
  applyServerFilter();
9008
8923
  });
9009
8924
  document.getElementById("add-btn").addEventListener("click", function() {
@@ -9028,17 +8943,6 @@
9028
8943
  closeSettings();
9029
8944
  }
9030
8945
  });
9031
- document.getElementById("import-file-input").addEventListener("change", function() {
9032
- if (this.files && this.files.length > 0) {
9033
- handleImportBackup(this.files[0]);
9034
- this.value = "";
9035
- }
9036
- });
9037
- document.getElementById("aimeat-import-overlay").addEventListener("click", function(e) {
9038
- if (e.target === this) {
9039
- this.hidden = true;
9040
- }
9041
- });
9042
8946
  document.getElementById("publish-overlay").addEventListener("click", function(e) {
9043
8947
  if (e.target === this) {
9044
8948
  this.hidden = true;
@@ -9063,11 +8967,6 @@
9063
8967
  this.hidden = true;
9064
8968
  }
9065
8969
  });
9066
- document.getElementById("json-import-overlay").addEventListener("click", function(e) {
9067
- if (e.target === this) {
9068
- this.hidden = true;
9069
- }
9070
- });
9071
8970
  document.addEventListener("click", function(e) {
9072
8971
  var menu = document.getElementById("backup-menu");
9073
8972
  if (!menu.hidden && !menu.contains(e.target) && e.target.id !== "backup-btn") {
@@ -9198,16 +9097,12 @@
9198
9097
  document.getElementById("source-overlay").hidden = true;
9199
9098
  } else if (!document.getElementById("context-menu").hidden) {
9200
9099
  hideContextMenu();
9201
- } else if (!document.getElementById("aimeat-import-overlay").hidden) {
9202
- document.getElementById("aimeat-import-overlay").hidden = true;
9203
9100
  } else if (!document.getElementById("publish-overlay").hidden) {
9204
9101
  document.getElementById("publish-overlay").hidden = true;
9205
9102
  } else if (!document.getElementById("subdomain-overlay").hidden) {
9206
9103
  document.getElementById("subdomain-overlay").hidden = true;
9207
9104
  } else if (!document.getElementById("backup-overlay").hidden) {
9208
9105
  document.getElementById("backup-overlay").hidden = true;
9209
- } else if (!document.getElementById("json-import-overlay").hidden) {
9210
- document.getElementById("json-import-overlay").hidden = true;
9211
9106
  } else if (!document.getElementById("settings-overlay").hidden) {
9212
9107
  closeSettings();
9213
9108
  } else if (!document.getElementById("modal-overlay").hidden) {
@@ -9227,23 +9122,6 @@
9227
9122
  document.getElementById("search-input").focus();
9228
9123
  }
9229
9124
  });
9230
- document.getElementById("app-grid").addEventListener("touchstart", function(e) {
9231
- var card = e.target.closest(".app-card");
9232
- if (!card) return;
9233
- var appId = card.getAttribute("data-id");
9234
- card._longPressTimer = setTimeout(function() {
9235
- e.preventDefault();
9236
- window._launcher.showContextMenu(e.touches[0], appId);
9237
- }, 500);
9238
- }, { passive: false });
9239
- document.getElementById("app-grid").addEventListener("touchend", function(e) {
9240
- var card = e.target.closest(".app-card");
9241
- if (card && card._longPressTimer) clearTimeout(card._longPressTimer);
9242
- });
9243
- document.getElementById("app-grid").addEventListener("touchmove", function(e) {
9244
- var card = e.target.closest(".app-card");
9245
- if (card && card._longPressTimer) clearTimeout(card._longPressTimer);
9246
- });
9247
9125
  document.getElementById("copy-source-btn").addEventListener("click", function() {
9248
9126
  var code = document.getElementById("source-code").value;
9249
9127
  var btn = this;
@@ -9314,7 +9192,7 @@
9314
9192
  btn.textContent = "Save Changes";
9315
9193
  btn.disabled = true;
9316
9194
  }, 1500);
9317
- renderApps5();
9195
+ renderApps4();
9318
9196
  }).catch(function(err) {
9319
9197
  btn.textContent = "Save Changes";
9320
9198
  btn.disabled = false;