aimeat 2.0.0 → 2.2.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 (722) hide show
  1. package/dist/.env.example +9 -4
  2. package/dist/locales/en.json +63 -1
  3. package/dist/locales/fi.json +63 -1
  4. package/dist/public/components/LinkPreview.js +101 -0
  5. package/dist/public/components/NotificationBell.js +18 -1
  6. package/dist/public/cortex-bundled/aimeat-charts.js +158 -10
  7. package/dist/public/cortex-bundled/aimeat-charts.yaml +1 -1
  8. package/dist/public/cortex-bundled/aimeat-dag.js +515 -173
  9. package/dist/public/cortex-bundled/aimeat-dag.yaml +1 -1
  10. package/dist/public/cortex-bundled/aimeat-i18n.js +6 -1
  11. package/dist/public/cortex-bundled/aimeat-i18n.yaml +1 -1
  12. package/dist/public/cortex-bundled/aimeat-surface.js +577 -0
  13. package/dist/public/cortex-bundled/aimeat-surface.yaml +107 -0
  14. package/dist/public/cortex-bundled/aimeat-viewport.js +405 -0
  15. package/dist/public/cortex-bundled/aimeat-viewport.yaml +100 -0
  16. package/dist/public/css/theme.css +57 -7
  17. package/dist/public/css/views/admin.css +8 -1
  18. package/dist/public/css/views/app-grant.css +18 -0
  19. package/dist/public/css/views/inbox.css +47 -1
  20. package/dist/public/css/views/portfolio.css +46 -0
  21. package/dist/public/css/views/profile.css +38 -4
  22. package/dist/public/js/services/apps.js +19 -3
  23. package/dist/public/js/services/messages-ai-prompts.js +38 -0
  24. package/dist/public/js/services/messages.js +26 -4
  25. package/dist/public/js/services/notebook.js +76 -0
  26. package/dist/public/js/services/unfurl.js +43 -0
  27. package/dist/public/lib/VENDORED.md +6 -3
  28. package/dist/public/lib/aimeat-daisyui-bridge.css +39 -2
  29. package/dist/public/lib/aimeat-theme.css +538 -0
  30. package/dist/public/lib/fonts/LICENSE.md +11 -4
  31. package/dist/public/lib/fonts/fraunces-var-latin-ext.woff2 +0 -0
  32. package/dist/public/lib/fonts/fraunces-var-latin.woff2 +0 -0
  33. package/dist/public/lib/fonts/inter-var-latin-ext.woff2 +0 -0
  34. package/dist/public/lib/fonts/inter-var-latin.woff2 +0 -0
  35. package/dist/public/lib/fonts/jetbrains-mono-var-latin-ext.woff2 +0 -0
  36. package/dist/public/lib/fonts/jetbrains-mono-var-latin.woff2 +0 -0
  37. package/dist/public/lib/fonts/space-grotesk-var-latin-ext.woff2 +0 -0
  38. package/dist/public/lib/fonts/space-grotesk-var-latin.woff2 +0 -0
  39. package/dist/public/lib/samples/LICENSE.md +11 -0
  40. package/dist/public/lib/samples/bass/A1.mp3 +0 -0
  41. package/dist/public/lib/samples/bass/B2.mp3 +0 -0
  42. package/dist/public/lib/samples/bass/D2.mp3 +0 -0
  43. package/dist/public/lib/samples/bass/E1.mp3 +0 -0
  44. package/dist/public/lib/samples/bass/E3.mp3 +0 -0
  45. package/dist/public/lib/samples/bass/G2.mp3 +0 -0
  46. package/dist/public/lib/samples/epiano/C2.mp3 +0 -0
  47. package/dist/public/lib/samples/epiano/C3.mp3 +0 -0
  48. package/dist/public/lib/samples/epiano/C4.mp3 +0 -0
  49. package/dist/public/lib/samples/epiano/C5.mp3 +0 -0
  50. package/dist/public/lib/samples/epiano/C6.mp3 +0 -0
  51. package/dist/public/lib/samples/epiano/G2.mp3 +0 -0
  52. package/dist/public/lib/samples/epiano/G3.mp3 +0 -0
  53. package/dist/public/lib/samples/epiano/G4.mp3 +0 -0
  54. package/dist/public/lib/samples/epiano/G5.mp3 +0 -0
  55. package/dist/public/lib/samples/guitar-steel/A2.mp3 +0 -0
  56. package/dist/public/lib/samples/guitar-steel/A4.mp3 +0 -0
  57. package/dist/public/lib/samples/guitar-steel/B3.mp3 +0 -0
  58. package/dist/public/lib/samples/guitar-steel/D3.mp3 +0 -0
  59. package/dist/public/lib/samples/guitar-steel/E2.mp3 +0 -0
  60. package/dist/public/lib/samples/guitar-steel/E4.mp3 +0 -0
  61. package/dist/public/lib/samples/guitar-steel/E5.mp3 +0 -0
  62. package/dist/public/lib/samples/guitar-steel/G3.mp3 +0 -0
  63. package/dist/public/lib/samples/organ/C2.mp3 +0 -0
  64. package/dist/public/lib/samples/organ/C3.mp3 +0 -0
  65. package/dist/public/lib/samples/organ/C4.mp3 +0 -0
  66. package/dist/public/lib/samples/organ/C5.mp3 +0 -0
  67. package/dist/public/lib/samples/organ/C6.mp3 +0 -0
  68. package/dist/public/lib/samples/organ/G2.mp3 +0 -0
  69. package/dist/public/lib/samples/organ/G3.mp3 +0 -0
  70. package/dist/public/lib/samples/organ/G4.mp3 +0 -0
  71. package/dist/public/lib/samples/organ/G5.mp3 +0 -0
  72. package/dist/public/lib/samples/strings/C2.mp3 +0 -0
  73. package/dist/public/lib/samples/strings/C3.mp3 +0 -0
  74. package/dist/public/lib/samples/strings/C4.mp3 +0 -0
  75. package/dist/public/lib/samples/strings/C5.mp3 +0 -0
  76. package/dist/public/lib/samples/strings/C6.mp3 +0 -0
  77. package/dist/public/lib/samples/strings/G2.mp3 +0 -0
  78. package/dist/public/lib/samples/strings/G3.mp3 +0 -0
  79. package/dist/public/lib/samples/strings/G4.mp3 +0 -0
  80. package/dist/public/lib/samples/strings/G5.mp3 +0 -0
  81. package/dist/public/lib/samples/theme-check.html +92 -0
  82. package/dist/public/lib/samples/themes-boot.js +20 -0
  83. package/dist/public/lib/samples/themes.html +154 -0
  84. package/dist/public/lib/samples/themes.js +60 -0
  85. package/dist/public/lib/samples/trumpet/A3.mp3 +0 -0
  86. package/dist/public/lib/samples/trumpet/A4.mp3 +0 -0
  87. package/dist/public/lib/samples/trumpet/A5.mp3 +0 -0
  88. package/dist/public/lib/samples/trumpet/C4.mp3 +0 -0
  89. package/dist/public/lib/samples/trumpet/C5.mp3 +0 -0
  90. package/dist/public/lib/samples/trumpet/E3.mp3 +0 -0
  91. package/dist/public/lib/samples/trumpet/E4.mp3 +0 -0
  92. package/dist/public/lib/samples/trumpet/E5.mp3 +0 -0
  93. package/dist/public/spa.html +2 -0
  94. package/dist/public/views/app-grant.js +24 -4
  95. package/dist/public/views/portfolio.js +61 -6
  96. package/dist/public/views/profile/access-tab/connected-apps.js +81 -5
  97. package/dist/public/views/profile/apps-tab.js +18 -1
  98. package/dist/public/views/profile/extensions-tab.maturity.js +4 -1
  99. package/dist/public/views/profile/inbox-tab/ai-actions.js +86 -0
  100. package/dist/public/views/profile/inbox-tab/components.js +141 -10
  101. package/dist/public/views/profile/inbox-tab/helpers.js +26 -0
  102. package/dist/public/views/profile/inbox-tab/panels.js +18 -3
  103. package/dist/public/views/profile/inbox-tab/use-thread-ux.js +47 -3
  104. package/dist/public/views/profile/inbox-tab.js +46 -49
  105. package/dist/public/views/profile/landing-page.cards.js +7 -0
  106. package/dist/public/views/profile/landing-page.js +12 -1
  107. package/dist/public/views/profile/libraries-tab.js +10 -2
  108. package/dist/public/views/profile/skills-tab.js +1 -1
  109. package/dist/public/views/profile/wallet-tab.js +64 -0
  110. package/dist/public/views/public-workspace-viewer.js +1 -1
  111. package/dist/public/views/start.js +2 -1
  112. package/dist/scripts/build-sdk-libs.d.ts +16 -0
  113. package/dist/scripts/build-sdk-libs.d.ts.map +1 -0
  114. package/dist/scripts/build-sdk-libs.js +112 -0
  115. package/dist/scripts/build-sdk-libs.js.map +1 -0
  116. package/dist/scripts/check-openapi.d.ts +2 -0
  117. package/dist/scripts/check-openapi.d.ts.map +1 -0
  118. package/dist/scripts/check-openapi.js +139 -0
  119. package/dist/scripts/check-openapi.js.map +1 -0
  120. package/dist/scripts/embed-viewport.d.ts +23 -0
  121. package/dist/scripts/embed-viewport.d.ts.map +1 -0
  122. package/dist/scripts/embed-viewport.js +116 -0
  123. package/dist/scripts/embed-viewport.js.map +1 -0
  124. package/dist/src/auth/sse-domain-scopes.d.ts +50 -0
  125. package/dist/src/auth/sse-domain-scopes.d.ts.map +1 -0
  126. package/dist/src/auth/sse-domain-scopes.js +111 -0
  127. package/dist/src/auth/sse-domain-scopes.js.map +1 -0
  128. package/dist/src/cli/connect/auth.d.ts +15 -0
  129. package/dist/src/cli/connect/auth.d.ts.map +1 -1
  130. package/dist/src/cli/connect/auth.js +25 -6
  131. package/dist/src/cli/connect/auth.js.map +1 -1
  132. package/dist/src/cli/connect/keychain.d.ts +9 -0
  133. package/dist/src/cli/connect/keychain.d.ts.map +1 -1
  134. package/dist/src/cli/connect/keychain.js +21 -8
  135. package/dist/src/cli/connect/keychain.js.map +1 -1
  136. package/dist/src/cli/connect/mcp/tools/agent-tasks.d.ts.map +1 -1
  137. package/dist/src/cli/connect/mcp/tools/agent-tasks.js +4 -1
  138. package/dist/src/cli/connect/mcp/tools/agent-tasks.js.map +1 -1
  139. package/dist/src/cli/connect/mcp/tools/core.d.ts.map +1 -1
  140. package/dist/src/cli/connect/mcp/tools/core.js +14 -3
  141. package/dist/src/cli/connect/mcp/tools/core.js.map +1 -1
  142. package/dist/src/cli/connect/mcp/tools/exchange.d.ts +18 -0
  143. package/dist/src/cli/connect/mcp/tools/exchange.d.ts.map +1 -0
  144. package/dist/src/cli/connect/mcp/tools/exchange.js +170 -0
  145. package/dist/src/cli/connect/mcp/tools/exchange.js.map +1 -0
  146. package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
  147. package/dist/src/cli/connect/mcp/tools/index.js +2 -0
  148. package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
  149. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts +3 -0
  150. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts.map +1 -1
  151. package/dist/src/cli/connect/mcp/tools/workspaces.js +4 -2
  152. package/dist/src/cli/connect/mcp/tools/workspaces.js.map +1 -1
  153. package/dist/src/cli/connect/tool-call-defs-core.d.ts.map +1 -1
  154. package/dist/src/cli/connect/tool-call-defs-core.js +13 -1
  155. package/dist/src/cli/connect/tool-call-defs-core.js.map +1 -1
  156. package/dist/src/cli/connect/tool-call-defs-exchange.d.ts +3 -0
  157. package/dist/src/cli/connect/tool-call-defs-exchange.d.ts.map +1 -0
  158. package/dist/src/cli/connect/tool-call-defs-exchange.js +146 -0
  159. package/dist/src/cli/connect/tool-call-defs-exchange.js.map +1 -0
  160. package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
  161. package/dist/src/cli/connect/tool-call.js +2 -0
  162. package/dist/src/cli/connect/tool-call.js.map +1 -1
  163. package/dist/src/commerce/__tests__/x402-registry.test.d.ts +2 -0
  164. package/dist/src/commerce/__tests__/x402-registry.test.d.ts.map +1 -0
  165. package/dist/src/commerce/__tests__/x402-registry.test.js +105 -0
  166. package/dist/src/commerce/__tests__/x402-registry.test.js.map +1 -0
  167. package/dist/src/commerce/evm-address.d.ts +21 -0
  168. package/dist/src/commerce/evm-address.d.ts.map +1 -0
  169. package/dist/src/commerce/evm-address.js +94 -0
  170. package/dist/src/commerce/evm-address.js.map +1 -0
  171. package/dist/src/commerce/money.d.ts +18 -5
  172. package/dist/src/commerce/money.d.ts.map +1 -1
  173. package/dist/src/commerce/money.js +27 -6
  174. package/dist/src/commerce/money.js.map +1 -1
  175. package/dist/src/commerce/session-service.d.ts +14 -0
  176. package/dist/src/commerce/session-service.d.ts.map +1 -1
  177. package/dist/src/commerce/session-service.js +1 -1
  178. package/dist/src/commerce/session-service.js.map +1 -1
  179. package/dist/src/commerce/x402-facilitator.d.ts +64 -13
  180. package/dist/src/commerce/x402-facilitator.d.ts.map +1 -1
  181. package/dist/src/commerce/x402-facilitator.js +71 -19
  182. package/dist/src/commerce/x402-facilitator.js.map +1 -1
  183. package/dist/src/commerce/x402-handler.d.ts +13 -7
  184. package/dist/src/commerce/x402-handler.d.ts.map +1 -1
  185. package/dist/src/commerce/x402-handler.js +23 -10
  186. package/dist/src/commerce/x402-handler.js.map +1 -1
  187. package/dist/src/commerce/x402.d.ts +11 -6
  188. package/dist/src/commerce/x402.d.ts.map +1 -1
  189. package/dist/src/commerce/x402.js +10 -7
  190. package/dist/src/commerce/x402.js.map +1 -1
  191. package/dist/src/config-types.d.ts +12 -0
  192. package/dist/src/config-types.d.ts.map +1 -1
  193. package/dist/src/config.d.ts.map +1 -1
  194. package/dist/src/config.js +8 -0
  195. package/dist/src/config.js.map +1 -1
  196. package/dist/src/data/app-templates/shells.d.ts +15 -3
  197. package/dist/src/data/app-templates/shells.d.ts.map +1 -1
  198. package/dist/src/data/app-templates/shells.js +97 -12
  199. package/dist/src/data/app-templates/shells.js.map +1 -1
  200. package/dist/src/data/app-templates.js +2 -2
  201. package/dist/src/data/app-templates.js.map +1 -1
  202. package/dist/src/data/appdev-pitfalls.d.ts +2 -0
  203. package/dist/src/data/appdev-pitfalls.d.ts.map +1 -1
  204. package/dist/src/data/appdev-pitfalls.js +24 -1
  205. package/dist/src/data/appdev-pitfalls.js.map +1 -1
  206. package/dist/src/data/library-packs/cortex.d.ts.map +1 -1
  207. package/dist/src/data/library-packs/cortex.js +108 -5
  208. package/dist/src/data/library-packs/cortex.js.map +1 -1
  209. package/dist/src/data/library-packs/sdk.d.ts.map +1 -1
  210. package/dist/src/data/library-packs/sdk.js +32 -8
  211. package/dist/src/data/library-packs/sdk.js.map +1 -1
  212. package/dist/src/data/library-packs/vendored.d.ts.map +1 -1
  213. package/dist/src/data/library-packs/vendored.js +83 -6
  214. package/dist/src/data/library-packs/vendored.js.map +1 -1
  215. package/dist/src/generated/api-types.d.ts +2039 -24
  216. package/dist/src/generated/api-types.d.ts.map +1 -1
  217. package/dist/src/mcp/agent-tasks.d.ts +4 -0
  218. package/dist/src/mcp/agent-tasks.d.ts.map +1 -1
  219. package/dist/src/mcp/agent-tasks.js +20 -2
  220. package/dist/src/mcp/agent-tasks.js.map +1 -1
  221. package/dist/src/mcp/annotations.d.ts.map +1 -1
  222. package/dist/src/mcp/annotations.js +20 -0
  223. package/dist/src/mcp/annotations.js.map +1 -1
  224. package/dist/src/mcp/apps.d.ts.map +1 -1
  225. package/dist/src/mcp/apps.js +8 -4
  226. package/dist/src/mcp/apps.js.map +1 -1
  227. package/dist/src/mcp/capabilities.d.ts +7 -1
  228. package/dist/src/mcp/capabilities.d.ts.map +1 -1
  229. package/dist/src/mcp/capabilities.js +5 -2
  230. package/dist/src/mcp/capabilities.js.map +1 -1
  231. package/dist/src/mcp/catalog/definitions/commerce.d.ts.map +1 -1
  232. package/dist/src/mcp/catalog/definitions/commerce.js +4 -2
  233. package/dist/src/mcp/catalog/definitions/commerce.js.map +1 -1
  234. package/dist/src/mcp/catalog/definitions/discovery-work-boards.d.ts.map +1 -1
  235. package/dist/src/mcp/catalog/definitions/discovery-work-boards.js +4 -3
  236. package/dist/src/mcp/catalog/definitions/discovery-work-boards.js.map +1 -1
  237. package/dist/src/mcp/catalog/definitions/exchange.d.ts +18 -0
  238. package/dist/src/mcp/catalog/definitions/exchange.d.ts.map +1 -0
  239. package/dist/src/mcp/catalog/definitions/exchange.js +182 -0
  240. package/dist/src/mcp/catalog/definitions/exchange.js.map +1 -0
  241. package/dist/src/mcp/catalog/definitions/organisms-workspaces-apps.js +1 -1
  242. package/dist/src/mcp/catalog/definitions/organisms-workspaces-apps.js.map +1 -1
  243. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.d.ts.map +1 -1
  244. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js +3 -2
  245. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js.map +1 -1
  246. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  247. package/dist/src/mcp/catalog/definitions.js +2 -0
  248. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  249. package/dist/src/mcp/catalog/output-schemas.d.ts +6 -1
  250. package/dist/src/mcp/catalog/output-schemas.d.ts.map +1 -1
  251. package/dist/src/mcp/catalog/output-schemas.js +6 -1
  252. package/dist/src/mcp/catalog/output-schemas.js.map +1 -1
  253. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  254. package/dist/src/mcp/catalog/scopes.js +22 -0
  255. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  256. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  257. package/dist/src/mcp/catalog/surfaces.js +14 -0
  258. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  259. package/dist/src/mcp/commerce.d.ts.map +1 -1
  260. package/dist/src/mcp/commerce.js +15 -3
  261. package/dist/src/mcp/commerce.js.map +1 -1
  262. package/dist/src/mcp/core-storage.d.ts +23 -0
  263. package/dist/src/mcp/core-storage.d.ts.map +1 -0
  264. package/dist/src/mcp/core-storage.js +157 -0
  265. package/dist/src/mcp/core-storage.js.map +1 -0
  266. package/dist/src/mcp/core.d.ts +16 -0
  267. package/dist/src/mcp/core.d.ts.map +1 -1
  268. package/dist/src/mcp/core.js +75 -119
  269. package/dist/src/mcp/core.js.map +1 -1
  270. package/dist/src/mcp/dm-messages.d.ts +4 -0
  271. package/dist/src/mcp/dm-messages.d.ts.map +1 -1
  272. package/dist/src/mcp/dm-messages.js +43 -2
  273. package/dist/src/mcp/dm-messages.js.map +1 -1
  274. package/dist/src/mcp/exchange-run.d.ts +5 -0
  275. package/dist/src/mcp/exchange-run.d.ts.map +1 -0
  276. package/dist/src/mcp/exchange-run.js +226 -0
  277. package/dist/src/mcp/exchange-run.js.map +1 -0
  278. package/dist/src/mcp/exchange.d.ts +5 -0
  279. package/dist/src/mcp/exchange.d.ts.map +1 -0
  280. package/dist/src/mcp/exchange.js +352 -0
  281. package/dist/src/mcp/exchange.js.map +1 -0
  282. package/dist/src/mcp/extensions.d.ts.map +1 -1
  283. package/dist/src/mcp/extensions.js +17 -2
  284. package/dist/src/mcp/extensions.js.map +1 -1
  285. package/dist/src/mcp/index.d.ts +5 -0
  286. package/dist/src/mcp/index.d.ts.map +1 -1
  287. package/dist/src/mcp/index.js +26 -3
  288. package/dist/src/mcp/index.js.map +1 -1
  289. package/dist/src/mcp/memory-namespace-hints.d.ts +33 -0
  290. package/dist/src/mcp/memory-namespace-hints.d.ts.map +1 -0
  291. package/dist/src/mcp/memory-namespace-hints.js +60 -0
  292. package/dist/src/mcp/memory-namespace-hints.js.map +1 -0
  293. package/dist/src/mcp/workspaces.d.ts +5 -0
  294. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  295. package/dist/src/mcp/workspaces.js +5 -3
  296. package/dist/src/mcp/workspaces.js.map +1 -1
  297. package/dist/src/models/agent-task-schemas.d.ts +25 -3
  298. package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
  299. package/dist/src/models/agent-task-schemas.js +16 -0
  300. package/dist/src/models/agent-task-schemas.js.map +1 -1
  301. package/dist/src/models/app-tool-schemas.d.ts +573 -2
  302. package/dist/src/models/app-tool-schemas.d.ts.map +1 -1
  303. package/dist/src/models/app-tool-schemas.js +73 -2
  304. package/dist/src/models/app-tool-schemas.js.map +1 -1
  305. package/dist/src/models/odps-schemas.d.ts +305 -0
  306. package/dist/src/models/odps-schemas.d.ts.map +1 -0
  307. package/dist/src/models/odps-schemas.js +182 -0
  308. package/dist/src/models/odps-schemas.js.map +1 -0
  309. package/dist/src/models/offer-schemas.d.ts +359 -4
  310. package/dist/src/models/offer-schemas.d.ts.map +1 -1
  311. package/dist/src/models/offer-schemas.js +37 -0
  312. package/dist/src/models/offer-schemas.js.map +1 -1
  313. package/dist/src/models/schemas.d.ts +7 -3
  314. package/dist/src/models/schemas.d.ts.map +1 -1
  315. package/dist/src/models/schemas.js +13 -1
  316. package/dist/src/models/schemas.js.map +1 -1
  317. package/dist/src/models/webhook-schemas.d.ts +8 -8
  318. package/dist/src/routes/agent-tasks/create-read.d.ts +4 -0
  319. package/dist/src/routes/agent-tasks/create-read.d.ts.map +1 -1
  320. package/dist/src/routes/agent-tasks/create-read.js +20 -1
  321. package/dist/src/routes/agent-tasks/create-read.js.map +1 -1
  322. package/dist/src/routes/agent-tasks/lifecycle.d.ts.map +1 -1
  323. package/dist/src/routes/agent-tasks/lifecycle.js +10 -0
  324. package/dist/src/routes/agent-tasks/lifecycle.js.map +1 -1
  325. package/dist/src/routes/agents/device-auth.d.ts +4 -0
  326. package/dist/src/routes/agents/device-auth.d.ts.map +1 -1
  327. package/dist/src/routes/agents/device-auth.js +19 -4
  328. package/dist/src/routes/agents/device-auth.js.map +1 -1
  329. package/dist/src/routes/agents/offers.d.ts +2 -0
  330. package/dist/src/routes/agents/offers.d.ts.map +1 -1
  331. package/dist/src/routes/agents/offers.js +0 -0
  332. package/dist/src/routes/agents/offers.js.map +1 -1
  333. package/dist/src/routes/app-grants.d.ts +12 -0
  334. package/dist/src/routes/app-grants.d.ts.map +1 -1
  335. package/dist/src/routes/app-grants.js +90 -27
  336. package/dist/src/routes/app-grants.js.map +1 -1
  337. package/dist/src/routes/apps/drafts.d.ts +3 -0
  338. package/dist/src/routes/apps/drafts.d.ts.map +1 -1
  339. package/dist/src/routes/apps/drafts.js +63 -1
  340. package/dist/src/routes/apps/drafts.js.map +1 -1
  341. package/dist/src/routes/apps/fork-manage.d.ts.map +1 -1
  342. package/dist/src/routes/apps/fork-manage.js +24 -1
  343. package/dist/src/routes/apps/fork-manage.js.map +1 -1
  344. package/dist/src/routes/apps/publish.d.ts +4 -0
  345. package/dist/src/routes/apps/publish.d.ts.map +1 -1
  346. package/dist/src/routes/apps/publish.js +44 -3
  347. package/dist/src/routes/apps/publish.js.map +1 -1
  348. package/dist/src/routes/apps-cost.d.ts +25 -0
  349. package/dist/src/routes/apps-cost.d.ts.map +1 -0
  350. package/dist/src/routes/apps-cost.js +107 -0
  351. package/dist/src/routes/apps-cost.js.map +1 -0
  352. package/dist/src/routes/commerce.d.ts.map +1 -1
  353. package/dist/src/routes/commerce.js +122 -2
  354. package/dist/src/routes/commerce.js.map +1 -1
  355. package/dist/src/routes/exchange-market.d.ts +32 -0
  356. package/dist/src/routes/exchange-market.d.ts.map +1 -0
  357. package/dist/src/routes/exchange-market.js +580 -0
  358. package/dist/src/routes/exchange-market.js.map +1 -0
  359. package/dist/src/routes/exchange.d.ts +33 -0
  360. package/dist/src/routes/exchange.d.ts.map +1 -0
  361. package/dist/src/routes/exchange.js +398 -0
  362. package/dist/src/routes/exchange.js.map +1 -0
  363. package/dist/src/routes/extensions/actions.d.ts.map +1 -1
  364. package/dist/src/routes/extensions/actions.js +18 -0
  365. package/dist/src/routes/extensions/actions.js.map +1 -1
  366. package/dist/src/routes/extensions/crud.d.ts.map +1 -1
  367. package/dist/src/routes/extensions/crud.js +36 -3
  368. package/dist/src/routes/extensions/crud.js.map +1 -1
  369. package/dist/src/routes/extensions/entitlement-gate.d.ts +33 -0
  370. package/dist/src/routes/extensions/entitlement-gate.d.ts.map +1 -0
  371. package/dist/src/routes/extensions/entitlement-gate.js +156 -0
  372. package/dist/src/routes/extensions/entitlement-gate.js.map +1 -0
  373. package/dist/src/routes/extensions/manifest.d.ts +15 -0
  374. package/dist/src/routes/extensions/manifest.d.ts.map +1 -1
  375. package/dist/src/routes/extensions/manifest.js +101 -2
  376. package/dist/src/routes/extensions/manifest.js.map +1 -1
  377. package/dist/src/routes/extensions/pacing.d.ts +31 -0
  378. package/dist/src/routes/extensions/pacing.d.ts.map +1 -0
  379. package/dist/src/routes/extensions/pacing.js +73 -0
  380. package/dist/src/routes/extensions/pacing.js.map +1 -0
  381. package/dist/src/routes/extensions/paywall.d.ts.map +1 -1
  382. package/dist/src/routes/extensions/paywall.js +20 -11
  383. package/dist/src/routes/extensions/paywall.js.map +1 -1
  384. package/dist/src/routes/federation-sync/messaging.d.ts.map +1 -1
  385. package/dist/src/routes/federation-sync/messaging.js +3 -1
  386. package/dist/src/routes/federation-sync/messaging.js.map +1 -1
  387. package/dist/src/routes/ghii/recovery.d.ts +2 -0
  388. package/dist/src/routes/ghii/recovery.d.ts.map +1 -1
  389. package/dist/src/routes/ghii/recovery.js +7 -0
  390. package/dist/src/routes/ghii/recovery.js.map +1 -1
  391. package/dist/src/routes/ghii/register-login.d.ts +4 -0
  392. package/dist/src/routes/ghii/register-login.d.ts.map +1 -1
  393. package/dist/src/routes/ghii/register-login.js +45 -22
  394. package/dist/src/routes/ghii/register-login.js.map +1 -1
  395. package/dist/src/routes/ghii/web-verify.d.ts +5 -0
  396. package/dist/src/routes/ghii/web-verify.d.ts.map +1 -1
  397. package/dist/src/routes/ghii/web-verify.js +24 -3
  398. package/dist/src/routes/ghii/web-verify.js.map +1 -1
  399. package/dist/src/routes/libs/sdk-serve.d.ts +11 -0
  400. package/dist/src/routes/libs/sdk-serve.d.ts.map +1 -0
  401. package/dist/src/routes/libs/sdk-serve.js +53 -0
  402. package/dist/src/routes/libs/sdk-serve.js.map +1 -0
  403. package/dist/src/routes/libs.d.ts +7 -1
  404. package/dist/src/routes/libs.d.ts.map +1 -1
  405. package/dist/src/routes/libs.js +65 -146
  406. package/dist/src/routes/libs.js.map +1 -1
  407. package/dist/src/routes/memory/crud.d.ts.map +1 -1
  408. package/dist/src/routes/memory/crud.js +10 -0
  409. package/dist/src/routes/memory/crud.js.map +1 -1
  410. package/dist/src/routes/messages.d.ts +3 -0
  411. package/dist/src/routes/messages.d.ts.map +1 -1
  412. package/dist/src/routes/messages.js +9 -0
  413. package/dist/src/routes/messages.js.map +1 -1
  414. package/dist/src/routes/notifications.d.ts +6 -3
  415. package/dist/src/routes/notifications.d.ts.map +1 -1
  416. package/dist/src/routes/notifications.js +32 -3
  417. package/dist/src/routes/notifications.js.map +1 -1
  418. package/dist/src/routes/organisms/crud.d.ts +3 -0
  419. package/dist/src/routes/organisms/crud.d.ts.map +1 -1
  420. package/dist/src/routes/organisms/crud.js +5 -2
  421. package/dist/src/routes/organisms/crud.js.map +1 -1
  422. package/dist/src/routes/organisms/workspace-access.d.ts.map +1 -1
  423. package/dist/src/routes/organisms/workspace-access.js +38 -20
  424. package/dist/src/routes/organisms/workspace-access.js.map +1 -1
  425. package/dist/src/routes/organisms/workspace-read.d.ts.map +1 -1
  426. package/dist/src/routes/organisms/workspace-read.js +7 -2
  427. package/dist/src/routes/organisms/workspace-read.js.map +1 -1
  428. package/dist/src/routes/public-stats.d.ts +7 -0
  429. package/dist/src/routes/public-stats.d.ts.map +1 -1
  430. package/dist/src/routes/public-stats.js +28 -7
  431. package/dist/src/routes/public-stats.js.map +1 -1
  432. package/dist/src/routes/sse.d.ts +7 -0
  433. package/dist/src/routes/sse.d.ts.map +1 -1
  434. package/dist/src/routes/sse.js +36 -6
  435. package/dist/src/routes/sse.js.map +1 -1
  436. package/dist/src/routes/storage-files.d.ts +11 -0
  437. package/dist/src/routes/storage-files.d.ts.map +1 -1
  438. package/dist/src/routes/storage-files.js +58 -51
  439. package/dist/src/routes/storage-files.js.map +1 -1
  440. package/dist/src/routes/subdomains.d.ts +4 -0
  441. package/dist/src/routes/subdomains.d.ts.map +1 -1
  442. package/dist/src/routes/subdomains.js +42 -5
  443. package/dist/src/routes/subdomains.js.map +1 -1
  444. package/dist/src/routes/unfurl.d.ts +27 -0
  445. package/dist/src/routes/unfurl.d.ts.map +1 -0
  446. package/dist/src/routes/unfurl.js +180 -0
  447. package/dist/src/routes/unfurl.js.map +1 -0
  448. package/dist/src/routes/upload.d.ts +13 -0
  449. package/dist/src/routes/upload.d.ts.map +1 -1
  450. package/dist/src/routes/upload.js +115 -28
  451. package/dist/src/routes/upload.js.map +1 -1
  452. package/dist/src/routes/webmcp.d.ts +3 -0
  453. package/dist/src/routes/webmcp.d.ts.map +1 -1
  454. package/dist/src/routes/webmcp.js +59 -0
  455. package/dist/src/routes/webmcp.js.map +1 -1
  456. package/dist/src/schemas/knowledge-package.d.ts +1 -1
  457. package/dist/src/server-bootstrap/routes-loader.d.ts +1 -0
  458. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  459. package/dist/src/server-bootstrap/routes-loader.js +8 -0
  460. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  461. package/dist/src/server-bootstrap/static-files.d.ts +4 -0
  462. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  463. package/dist/src/server-bootstrap/static-files.js +11 -0
  464. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  465. package/dist/src/services/agent-face.d.ts +10 -2
  466. package/dist/src/services/agent-face.d.ts.map +1 -1
  467. package/dist/src/services/agent-face.js +10 -4
  468. package/dist/src/services/agent-face.js.map +1 -1
  469. package/dist/src/services/app-tool-interfaces.d.ts +37 -0
  470. package/dist/src/services/app-tool-interfaces.d.ts.map +1 -0
  471. package/dist/src/services/app-tool-interfaces.js +101 -0
  472. package/dist/src/services/app-tool-interfaces.js.map +1 -0
  473. package/dist/src/services/build-app-prompt.d.ts +22 -0
  474. package/dist/src/services/build-app-prompt.d.ts.map +1 -1
  475. package/dist/src/services/build-app-prompt.js +108 -11
  476. package/dist/src/services/build-app-prompt.js.map +1 -1
  477. package/dist/src/services/call-timing.d.ts +51 -0
  478. package/dist/src/services/call-timing.d.ts.map +1 -0
  479. package/dist/src/services/call-timing.js +91 -0
  480. package/dist/src/services/call-timing.js.map +1 -0
  481. package/dist/src/services/capability-invoke.d.ts +2 -0
  482. package/dist/src/services/capability-invoke.d.ts.map +1 -1
  483. package/dist/src/services/capability-invoke.js +10 -0
  484. package/dist/src/services/capability-invoke.js.map +1 -1
  485. package/dist/src/services/config-schema.d.ts.map +1 -1
  486. package/dist/src/services/config-schema.js +6 -0
  487. package/dist/src/services/config-schema.js.map +1 -1
  488. package/dist/src/services/contacts.d.ts +25 -1
  489. package/dist/src/services/contacts.d.ts.map +1 -1
  490. package/dist/src/services/contacts.js +30 -0
  491. package/dist/src/services/contacts.js.map +1 -1
  492. package/dist/src/services/draft-token.d.ts +22 -2
  493. package/dist/src/services/draft-token.d.ts.map +1 -1
  494. package/dist/src/services/draft-token.js +52 -3
  495. package/dist/src/services/draft-token.js.map +1 -1
  496. package/dist/src/services/entitlement-money.d.ts +55 -0
  497. package/dist/src/services/entitlement-money.d.ts.map +1 -0
  498. package/dist/src/services/entitlement-money.js +68 -0
  499. package/dist/src/services/entitlement-money.js.map +1 -0
  500. package/dist/src/services/exchange-market.d.ts +280 -0
  501. package/dist/src/services/exchange-market.d.ts.map +1 -0
  502. package/dist/src/services/exchange-market.js +276 -0
  503. package/dist/src/services/exchange-market.js.map +1 -0
  504. package/dist/src/services/exchange-odps.d.ts +58 -0
  505. package/dist/src/services/exchange-odps.d.ts.map +1 -0
  506. package/dist/src/services/exchange-odps.js +400 -0
  507. package/dist/src/services/exchange-odps.js.map +1 -0
  508. package/dist/src/services/exchange-projection.d.ts +61 -0
  509. package/dist/src/services/exchange-projection.d.ts.map +1 -0
  510. package/dist/src/services/exchange-projection.js +504 -0
  511. package/dist/src/services/exchange-projection.js.map +1 -0
  512. package/dist/src/services/exchange-proposals.d.ts +47 -0
  513. package/dist/src/services/exchange-proposals.d.ts.map +1 -0
  514. package/dist/src/services/exchange-proposals.js +73 -0
  515. package/dist/src/services/exchange-proposals.js.map +1 -0
  516. package/dist/src/services/exchange-work.d.ts +30 -0
  517. package/dist/src/services/exchange-work.d.ts.map +1 -0
  518. package/dist/src/services/exchange-work.js +43 -0
  519. package/dist/src/services/exchange-work.js.map +1 -0
  520. package/dist/src/services/extension-files.d.ts +64 -0
  521. package/dist/src/services/extension-files.d.ts.map +1 -0
  522. package/dist/src/services/extension-files.js +88 -0
  523. package/dist/src/services/extension-files.js.map +1 -0
  524. package/dist/src/services/extension-runtime.d.ts +33 -0
  525. package/dist/src/services/extension-runtime.d.ts.map +1 -1
  526. package/dist/src/services/extension-runtime.js +52 -0
  527. package/dist/src/services/extension-runtime.js.map +1 -1
  528. package/dist/src/services/file-refs.d.ts +105 -0
  529. package/dist/src/services/file-refs.d.ts.map +1 -0
  530. package/dist/src/services/file-refs.js +106 -0
  531. package/dist/src/services/file-refs.js.map +1 -0
  532. package/dist/src/services/message-send.d.ts.map +1 -1
  533. package/dist/src/services/message-send.js +4 -1
  534. package/dist/src/services/message-send.js.map +1 -1
  535. package/dist/src/services/metered-entitlements.d.ts +210 -0
  536. package/dist/src/services/metered-entitlements.d.ts.map +1 -0
  537. package/dist/src/services/metered-entitlements.js +268 -0
  538. package/dist/src/services/metered-entitlements.js.map +1 -0
  539. package/dist/src/services/notify.d.ts +7 -0
  540. package/dist/src/services/notify.d.ts.map +1 -1
  541. package/dist/src/services/notify.js +35 -0
  542. package/dist/src/services/notify.js.map +1 -1
  543. package/dist/src/services/owner-memory.d.ts +30 -1
  544. package/dist/src/services/owner-memory.d.ts.map +1 -1
  545. package/dist/src/services/owner-memory.js +31 -14
  546. package/dist/src/services/owner-memory.js.map +1 -1
  547. package/dist/src/services/owner-provisioning.d.ts +5 -0
  548. package/dist/src/services/owner-provisioning.d.ts.map +1 -1
  549. package/dist/src/services/owner-provisioning.js +20 -2
  550. package/dist/src/services/owner-provisioning.js.map +1 -1
  551. package/dist/src/services/task-files.d.ts +51 -0
  552. package/dist/src/services/task-files.d.ts.map +1 -0
  553. package/dist/src/services/task-files.js +58 -0
  554. package/dist/src/services/task-files.js.map +1 -0
  555. package/dist/src/services/upload-token.d.ts +29 -0
  556. package/dist/src/services/upload-token.d.ts.map +1 -1
  557. package/dist/src/services/upload-token.js +37 -0
  558. package/dist/src/services/upload-token.js.map +1 -1
  559. package/dist/src/services/upload-zip.d.ts +2 -0
  560. package/dist/src/services/upload-zip.d.ts.map +1 -1
  561. package/dist/src/services/upload-zip.js +5 -1
  562. package/dist/src/services/upload-zip.js.map +1 -1
  563. package/dist/src/services/workspace-meta.d.ts +15 -0
  564. package/dist/src/services/workspace-meta.d.ts.map +1 -1
  565. package/dist/src/services/workspace-meta.js +17 -0
  566. package/dist/src/services/workspace-meta.js.map +1 -1
  567. package/dist/src/services/workspace-provision.d.ts +3 -0
  568. package/dist/src/services/workspace-provision.d.ts.map +1 -1
  569. package/dist/src/services/workspace-provision.js +5 -3
  570. package/dist/src/services/workspace-provision.js.map +1 -1
  571. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.d.ts +1 -0
  572. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.d.ts.map +1 -1
  573. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.js +10 -0
  574. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.js.map +1 -1
  575. package/dist/src/storage/providers/postgres-kysely/methods/apps.d.ts +1 -0
  576. package/dist/src/storage/providers/postgres-kysely/methods/apps.d.ts.map +1 -1
  577. package/dist/src/storage/providers/postgres-kysely/methods/apps.js +2 -0
  578. package/dist/src/storage/providers/postgres-kysely/methods/apps.js.map +1 -1
  579. package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts +1 -0
  580. package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts.map +1 -1
  581. package/dist/src/storage/providers/postgres-kysely/methods/identity.js +4 -0
  582. package/dist/src/storage/providers/postgres-kysely/methods/identity.js.map +1 -1
  583. package/dist/src/storage/providers/postgres-kysely/methods/memory.d.ts +2 -0
  584. package/dist/src/storage/providers/postgres-kysely/methods/memory.d.ts.map +1 -1
  585. package/dist/src/storage/providers/postgres-kysely/methods/memory.js +12 -1
  586. package/dist/src/storage/providers/postgres-kysely/methods/memory.js.map +1 -1
  587. package/dist/src/storage/providers/postgres-kysely/methods/organisms.d.ts.map +1 -1
  588. package/dist/src/storage/providers/postgres-kysely/methods/organisms.js +5 -1
  589. package/dist/src/storage/providers/postgres-kysely/methods/organisms.js.map +1 -1
  590. package/dist/src/storage/providers/postgres-kysely/migrations/0011_unique_verified_email.sql +25 -0
  591. package/dist/src/storage/providers/postgres-kysely/migrations/0012_unique_live_app_grant.sql +29 -0
  592. package/dist/src/storage/providers/sqlite/methods/apps.d.ts +4 -0
  593. package/dist/src/storage/providers/sqlite/methods/apps.d.ts.map +1 -1
  594. package/dist/src/storage/providers/sqlite/methods/apps.js +9 -0
  595. package/dist/src/storage/providers/sqlite/methods/apps.js.map +1 -1
  596. package/dist/src/storage/providers/sqlite/methods/governance.d.ts +1 -0
  597. package/dist/src/storage/providers/sqlite/methods/governance.d.ts.map +1 -1
  598. package/dist/src/storage/providers/sqlite/methods/governance.js +4 -1
  599. package/dist/src/storage/providers/sqlite/methods/governance.js.map +1 -1
  600. package/dist/src/storage/providers/sqlite/methods/identity-nodes.d.ts +1 -0
  601. package/dist/src/storage/providers/sqlite/methods/identity-nodes.d.ts.map +1 -1
  602. package/dist/src/storage/providers/sqlite/methods/identity-nodes.js +4 -0
  603. package/dist/src/storage/providers/sqlite/methods/identity-nodes.js.map +1 -1
  604. package/dist/src/storage/providers/sqlite/methods/owner.d.ts +5 -0
  605. package/dist/src/storage/providers/sqlite/methods/owner.d.ts.map +1 -1
  606. package/dist/src/storage/providers/sqlite/methods/owner.js +8 -0
  607. package/dist/src/storage/providers/sqlite/methods/owner.js.map +1 -1
  608. package/dist/src/storage/providers/sqlite/schema.d.ts +3 -0
  609. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  610. package/dist/src/storage/providers/sqlite/schema.js +40 -0
  611. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  612. package/dist/src/storage/repositories/app-grant.repository.d.ts +9 -1
  613. package/dist/src/storage/repositories/app-grant.repository.d.ts.map +1 -1
  614. package/dist/src/storage/repositories/app.repository.d.ts +0 -0
  615. package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
  616. package/dist/src/storage/repositories/identity.repository.d.ts +6 -0
  617. package/dist/src/storage/repositories/identity.repository.d.ts.map +1 -1
  618. package/dist/src/storage/repositories/memory.repository.d.ts +14 -0
  619. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  620. package/dist/src/storage/repositories-impl/memory-repository.d.ts +8 -1
  621. package/dist/src/storage/repositories-impl/memory-repository.d.ts.map +1 -1
  622. package/dist/src/storage/repositories-impl/memory-repository.js +15 -4
  623. package/dist/src/storage/repositories-impl/memory-repository.js.map +1 -1
  624. package/dist/src/storage/types/agents-messaging.d.ts +19 -0
  625. package/dist/src/storage/types/agents-messaging.d.ts.map +1 -1
  626. package/dist/src/storage/types/agents-messaging.js.map +1 -1
  627. package/dist/src/storage/types/apps.d.ts +1 -0
  628. package/dist/src/storage/types/apps.d.ts.map +1 -1
  629. package/dist/src/storage/types/organisms-federation.d.ts +50 -0
  630. package/dist/src/storage/types/organisms-federation.d.ts.map +1 -1
  631. package/dist/src/utils/app-badge.d.ts.map +1 -1
  632. package/dist/src/utils/app-badge.js +7 -3
  633. package/dist/src/utils/app-badge.js.map +1 -1
  634. package/dist/src/utils/app-mobile-lint.d.ts +17 -0
  635. package/dist/src/utils/app-mobile-lint.d.ts.map +1 -0
  636. package/dist/src/utils/app-mobile-lint.js +40 -0
  637. package/dist/src/utils/app-mobile-lint.js.map +1 -0
  638. package/dist/static/app-catalog/_template.html +18 -82
  639. package/dist/static/app-catalog/js/apps-io.js +51 -165
  640. package/dist/static/app-catalog/js/cortex.js +3 -1
  641. package/dist/static/app-catalog/js/cost.js +100 -0
  642. package/dist/static/app-catalog/js/db.js +23 -93
  643. package/dist/static/app-catalog/js/detail.js +568 -59
  644. package/dist/static/app-catalog/js/favorites.js +71 -0
  645. package/dist/static/app-catalog/js/i18n-data.js +369 -19
  646. package/dist/static/app-catalog/js/main.js +50 -139
  647. package/dist/static/app-catalog/js/migrate.js +88 -0
  648. package/dist/static/app-catalog/js/monetize.js +205 -17
  649. package/dist/static/app-catalog/js/odps.js +489 -0
  650. package/dist/static/app-catalog/js/promote.js +70 -0
  651. package/dist/static/app-catalog/js/render.js +29 -139
  652. package/dist/static/app-catalog/js/server-io.js +126 -223
  653. package/dist/static/app-catalog/js/settings.js +11 -204
  654. package/dist/static/app-catalog/js/workcopy.js +198 -0
  655. package/dist/static/app-catalog/styles/app-catalog.css +100 -0
  656. package/dist/static/app-catalog.html +2220 -1029
  657. package/dist/static/sdk-libs/_core/config.js +52 -0
  658. package/dist/static/sdk-libs/_core/http.js +52 -0
  659. package/dist/static/sdk-libs/_core/namespace.js +33 -0
  660. package/dist/static/sdk-libs/_core/sdk-globals.d.ts +50 -0
  661. package/dist/static/sdk-libs/_core/session.js +66 -0
  662. package/dist/static/sdk-libs/agentface/index.js +102 -0
  663. package/dist/static/sdk-libs/agents/index.js +305 -0
  664. package/dist/static/sdk-libs/ai/index.js +153 -0
  665. package/dist/static/sdk-libs/audio/core.js +111 -0
  666. package/dist/static/sdk-libs/audio/index.js +380 -0
  667. package/dist/static/sdk-libs/audio/instruments.js +369 -0
  668. package/dist/static/sdk-libs/auth/cluster.js +97 -0
  669. package/dist/static/sdk-libs/auth/config.js +46 -0
  670. package/dist/static/sdk-libs/auth/crypto.js +166 -0
  671. package/dist/static/sdk-libs/auth/events.js +27 -0
  672. package/dist/static/sdk-libs/auth/i18n.js +64 -0
  673. package/dist/static/sdk-libs/auth/index.js +61 -0
  674. package/dist/static/sdk-libs/auth/locale.js +151 -0
  675. package/dist/static/sdk-libs/auth/modal.js +554 -0
  676. package/dist/static/sdk-libs/auth/palette.js +172 -0
  677. package/dist/static/sdk-libs/auth/pill.js +198 -0
  678. package/dist/static/sdk-libs/auth/session.js +792 -0
  679. package/dist/static/sdk-libs/auth/signup.js +232 -0
  680. package/dist/static/sdk-libs/auth/theme.js +137 -0
  681. package/dist/static/sdk-libs/capabilities/index.js +179 -0
  682. package/dist/static/sdk-libs/commerce/index.js +243 -0
  683. package/dist/static/sdk-libs/data/index.js +250 -0
  684. package/dist/static/sdk-libs/dist/aimeat-agentface.js +91 -0
  685. package/dist/static/sdk-libs/dist/aimeat-agents.js +348 -0
  686. package/dist/static/sdk-libs/dist/aimeat-ai.js +168 -0
  687. package/dist/static/sdk-libs/dist/aimeat-audio.js +979 -0
  688. package/dist/static/sdk-libs/dist/aimeat-auth.js +2232 -0
  689. package/dist/static/sdk-libs/dist/aimeat-capabilities.js +177 -0
  690. package/dist/static/sdk-libs/dist/aimeat-commerce.js +265 -0
  691. package/dist/static/sdk-libs/dist/aimeat-data.js +247 -0
  692. package/dist/static/sdk-libs/dist/aimeat-editor.js +299 -0
  693. package/dist/static/sdk-libs/dist/aimeat-header.js +270 -0
  694. package/dist/static/sdk-libs/dist/aimeat-intake.js +94 -0
  695. package/dist/static/sdk-libs/dist/aimeat-live.js +317 -0
  696. package/dist/static/sdk-libs/dist/aimeat-markdown.js +633 -0
  697. package/dist/static/sdk-libs/dist/aimeat-organism.js +394 -0
  698. package/dist/static/sdk-libs/dist/aimeat-portfolio-standalone.js +90 -0
  699. package/dist/static/sdk-libs/dist/aimeat-social.js +157 -0
  700. package/dist/static/sdk-libs/dist/aimeat-speech.js +300 -0
  701. package/dist/static/sdk-libs/dist/aimeat-storage.js +210 -0
  702. package/dist/static/sdk-libs/dist/aimeat-tunnel.js +383 -0
  703. package/dist/static/sdk-libs/dist/aimeat-wallet.js +118 -0
  704. package/dist/static/sdk-libs/dist/aimeat-webmcp.js +187 -0
  705. package/dist/static/sdk-libs/dist/aimeat-work.js +213 -0
  706. package/dist/static/sdk-libs/dist/aimeat-workflows.js +147 -0
  707. package/dist/static/sdk-libs/editor/index.js +229 -0
  708. package/dist/static/sdk-libs/header/index.js +249 -0
  709. package/dist/static/sdk-libs/intake/index.js +75 -0
  710. package/dist/static/sdk-libs/live/index.js +223 -0
  711. package/dist/static/sdk-libs/markdown/index.js +570 -0
  712. package/dist/static/sdk-libs/organism/index.js +381 -0
  713. package/dist/static/sdk-libs/portfolio-standalone/index.js +78 -0
  714. package/dist/static/sdk-libs/social/index.js +140 -0
  715. package/dist/static/sdk-libs/speech/index.js +321 -0
  716. package/dist/static/sdk-libs/storage/index.js +189 -0
  717. package/dist/static/sdk-libs/tunnel/index.js +428 -0
  718. package/dist/static/sdk-libs/wallet/index.js +115 -0
  719. package/dist/static/sdk-libs/webmcp/index.js +185 -0
  720. package/dist/static/sdk-libs/work/index.js +191 -0
  721. package/dist/static/sdk-libs/workflows/index.js +113 -0
  722. package/package.json +10 -3
@@ -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,53 @@
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
+ /* Library/community cards already own the top-LEFT corner with the Listed/Unlisted badge, so
622
+ the star sits top-RIGHT instead of on top of it. Keep clear of the ⋮ menu button, which only
623
+ local cards render: :has() pulls the star back to the edge when there is no menu. Where
624
+ :has() is unsupported that selector is simply dropped and the star keeps the wider offset —
625
+ a little gap, never an overlap. */
626
+ .app-card .fav-toggle { left: auto; right: 40px; top: 6px; }
627
+ .app-card:not(:has(.card-menu-btn)) .fav-toggle { right: 8px; }
628
+ .fav-section-title {
629
+ font-size: 1rem;
630
+ font-weight: 700;
631
+ color: var(--text);
632
+ display: flex;
633
+ align-items: center;
634
+ gap: 8px;
635
+ margin: 0 0 .6rem;
636
+ }
637
+ .fav-section-count { opacity: .6; font-weight: 400; }
638
+
639
+ /* "Promoted" badge in the detail Promote section */
640
+ .dtl-badge-on {
641
+ font-size: .7rem;
642
+ font-weight: 600;
643
+ color: #34d399;
644
+ border: 1px solid #34d399;
645
+ border-radius: 10px;
646
+ padding: 1px 8px;
647
+ }
648
+
601
649
  /* ── Card Menu Button ─────────────────────────────── */
602
650
 
603
651
  .card-menu-btn {
@@ -1111,6 +1159,33 @@
1111
1159
  .dtl-sync.diff { background: rgba(245,158,11,.14); color: #f59e0b; }
1112
1160
  .dtl-sync.none { background: var(--bg-subtle); color: var(--text-muted); }
1113
1161
 
1162
+ /* ── Working copy → published lifecycle band (TARGET-048) ──────────────────
1163
+ Replaces the old two-number STATUS card: two labelled stops that say, in plain words, who
1164
+ can see what. The active stop is highlighted so "where is my work" is answerable at a glance. */
1165
+ .wc-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
1166
+ .wc-band { flex: 1; min-width: 240px; display: flex; align-items: stretch; gap: 10px; flex-wrap: wrap; }
1167
+ .wc-stop {
1168
+ display: flex; flex-direction: column; gap: 2px;
1169
+ padding: 10px 12px; border-radius: 10px;
1170
+ border: 1px solid var(--border-subtle); background: var(--bg-subtle);
1171
+ min-width: 150px; flex: 1;
1172
+ }
1173
+ .wc-stop.is-active { border-color: rgba(245, 158, 11, .55); background: rgba(245, 158, 11, .10); }
1174
+ .wc-stop.is-live { border-color: rgba(52, 211, 153, .45); background: rgba(52, 211, 153, .10); }
1175
+ .wc-stop-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
1176
+ .wc-stop-val { font-size: .95rem; font-weight: 700; color: var(--text); }
1177
+ .wc-stop-note { font-size: .75rem; color: var(--text-muted); }
1178
+ .wc-arrow { align-self: center; color: var(--text-muted); font-size: 1.1rem; }
1179
+ .wc-explain { font-size: .88rem; color: var(--text); line-height: 1.5; margin: 12px 0 10px; }
1180
+ .wc-size { font-size: .75rem; color: var(--text-muted); margin: 8px 0 0; }
1181
+ .wc-muted { color: var(--text-muted); font-size: .85rem; }
1182
+ /* One-line reminder of what the three verbs do — shown under both button rows so the source
1183
+ editor and the AI loop explain themselves identically. */
1184
+ .wc-verbs-hint { font-size: .78rem; color: var(--text-muted); line-height: 1.45; margin: 10px 0 0; }
1185
+ .wc-proposal-head { margin: 0 0 8px; }
1186
+ .wc-ckpt-when { display: block; font-size: .85rem; font-weight: 600; color: var(--text); }
1187
+ .wc-ckpt-note { display: block; font-size: .78rem; color: var(--text-muted); }
1188
+
1114
1189
  .dtl-meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
1115
1190
  .dtl-meta-item .dtl-meta-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
1116
1191
  .dtl-meta-item .dtl-meta-val { font-size: .9rem; color: var(--text); word-break: break-word; }
@@ -2153,6 +2228,32 @@
2153
2228
  .aga-deploy-btn { white-space: nowrap; }
2154
2229
  .aga-btn-sm { padding: 2px 10px; font-size: .75rem; margin-left: auto; }
2155
2230
  .published-card-badgerow .pcb-agent { cursor: default; }
2231
+
2232
+ /* ── EXCHANGE & ODPS (app detail) ──────────────────────────────────────────────
2233
+ The app-level marketplace status plus the ODPS authoring forms (app defaults and the per-tool
2234
+ descriptor). Themed through the same variables as the rest of the detail view; the attestation
2235
+ block is set apart on purpose, because those fields are legal promises rather than marketing copy. */
2236
+ .od-status { font-size: .86rem; font-weight: 600; padding: 6px 10px; border-radius: 8px; display: inline-block; }
2237
+ .od-status-on { background: rgba(52,211,153,.12); color: #34d399; }
2238
+ .od-status-none { background: var(--bg-subtle); color: var(--text-muted); }
2239
+ .od-note { margin: 6px 0 8px; }
2240
+ .od-muted { color: var(--text-muted); font-size: .85rem; }
2241
+ .od-actions { margin-top: 10px; }
2242
+ .od-form { margin-top: 10px; }
2243
+ .od-input { margin: 4px 0 8px; }
2244
+ .od-grid { display: flex; gap: 10px; flex-wrap: wrap; }
2245
+ .od-col { flex: 1; min-width: 150px; }
2246
+ .od-col-narrow { flex: 0 0 110px; min-width: 90px; }
2247
+ .od-attest { border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; margin: 8px 0; background: var(--bg-subtle); }
2248
+ .od-attest-head { font-weight: 600; margin-bottom: 2px; }
2249
+ .od-tool-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 4px; }
2250
+ .od-link { font-size: .8rem; color: var(--accent); text-decoration: none; }
2251
+ .od-link:hover { text-decoration: underline; }
2252
+ .od-blocked { flex-basis: 100%; font-size: .8rem; color: #f59e0b; margin-top: 4px; }
2253
+ .od-ok { color: #34d399; }
2254
+ .od-err { color: var(--accent); }
2255
+ /* Measured delivery time offered as the basis for a commitment (never as the commitment itself). */
2256
+ .od-measured { font-size: 12px; color: var(--text-muted); margin: 4px 0 8px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
2156
2257
  </style>
2157
2258
  </head>
2158
2259
  <body data-active-view="library">
@@ -2160,13 +2261,6 @@
2160
2261
  <header>
2161
2262
  <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
2263
  <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
2264
  <input type="text" id="search-input" placeholder="Search apps..." data-i18n-ph="search.ph" autocomplete="off" spellcheck="false"/>
2171
2265
  <div class="header-actions">
2172
2266
  <span id="headerAuth" class="header-auth-pill"></span>
@@ -2267,10 +2361,10 @@
2267
2361
  <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
2362
  </div>
2269
2363
 
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>
2364
+ <!-- Favourites group (Library) — apps the owner favourited, pinned above "Your apps" -->
2365
+ <div id="favorites-section" data-view="library" style="display:none;margin:0 24px 1rem">
2366
+ <h2 class="fav-section-title"><span data-i18n="fav.title">⭐ Favourites</span> <span id="favorites-count" class="fav-section-count"></span></h2>
2367
+ <div id="favorites-grid" class="published-grid"></div>
2274
2368
  </div>
2275
2369
 
2276
2370
  <div id="local-apps-block" class="local-apps-block" data-view="library">
@@ -2291,18 +2385,10 @@
2291
2385
  <div class="modal" id="add-app-modal">
2292
2386
  <h2 id="modal-title" data-i18n="addModal.title">Add App</h2>
2293
2387
 
2294
- <!-- Source tabs -->
2388
+ <!-- Source tabs — server-only: paste HTML or upload an HTML/ZIP file; it publishes to your account. -->
2295
2389
  <div class="modal-tabs">
2296
- <button class="modal-tab active" data-tab="url" data-i18n="tab.url">URL</button>
2390
+ <button class="modal-tab active" data-tab="paste" data-i18n="tab.paste">Paste</button>
2297
2391
  <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
2392
  </div>
2307
2393
 
2308
2394
  <!-- File tab content -->
@@ -2318,22 +2404,12 @@
2318
2404
  </div>
2319
2405
  </div>
2320
2406
 
2321
- <!-- Paste tab content -->
2322
- <div class="tab-content" id="tab-paste">
2407
+ <!-- Paste tab content (default) -->
2408
+ <div class="tab-content active" id="tab-paste">
2323
2409
  <label class="modal-label" for="app-paste-code" data-i18n="addModal.paste">Paste HTML Source Code</label>
2324
2410
  <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
2411
  </div>
2326
2412
 
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
2413
  <!-- Common fields -->
2338
2414
  <div class="modal-fields">
2339
2415
  <div class="modal-row">
@@ -2468,21 +2544,6 @@
2468
2544
  </div>
2469
2545
  </div>
2470
2546
 
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
2547
  <div class="modal-actions" style="margin-top:24px">
2487
2548
  <button type="button" class="modal-btn secondary" id="settings-cancel-btn" data-i18n="common.cancel">Cancel</button>
2488
2549
  <button type="button" class="modal-btn primary" id="settings-save-btn" data-i18n="common.save">Save</button>
@@ -2499,25 +2560,14 @@
2499
2560
  <div class="modal-actions">
2500
2561
  <button class="modal-btn secondary" id="copy-source-btn" data-i18n="source.copy">Copy Source</button>
2501
2562
  <button class="modal-btn primary" id="copy-prompt-btn" data-i18n="source.copyPrompt">Copy with AI Prompt</button>
2502
- <button class="modal-btn secondary" id="save-source-btn" disabled data-i18n="source.save">Save working copy</button>
2503
- <!-- Real-origin staging (published apps only; toggled in viewSource). Same server-draft
2504
- path as the AI loop: test the edited source on a real origin, then publish it. -->
2505
- <button class="modal-btn primary" id="source-test-live-btn" hidden data-i18n="detail.testLive">Test on real origin</button>
2506
- <button class="modal-btn success" id="source-publish-tested-btn" hidden data-i18n="detail.publishTested">Publish tested version</button>
2563
+ <!-- The three canonical verbs, identical to the detail view's AI loop (TARGET-048):
2564
+ save (private, checkpointed) → try (real origin, live untouched) publish (public). -->
2565
+ <button class="modal-btn primary" id="save-source-btn" disabled data-i18n="source.save">Save working copy</button>
2566
+ <button class="modal-btn secondary" id="source-test-live-btn" hidden data-i18n="wc.try">Try it safely</button>
2567
+ <button class="modal-btn success" id="source-publish-tested-btn" hidden data-i18n="wc.publishNew">Publish as a new version</button>
2507
2568
  <button class="modal-btn secondary" id="close-source-btn" data-i18n="common.close">Close</button>
2508
2569
  </div>
2509
- </div>
2510
- </div>
2511
-
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>
2570
+ <p class="wc-verbs-hint" data-i18n="wc.verbsHint">Save keeps it for you only. Try opens it on its real address without changing the live app. Publish is what other people see.</p>
2521
2571
  </div>
2522
2572
  </div>
2523
2573
 
@@ -2548,20 +2598,6 @@
2548
2598
  </div>
2549
2599
  </div>
2550
2600
 
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
2601
  <!-- Backup Import Modal -->
2566
2602
  <div id="backup-overlay" hidden>
2567
2603
  <div class="modal" id="backup-modal">
@@ -2717,14 +2753,14 @@
2717
2753
  "action.settings": "Settings",
2718
2754
  "action.help": "Help",
2719
2755
  "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.',
2756
+ "help.modesTitle": "Creating an app",
2757
+ "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
2758
  "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.",
2759
+ "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
2760
  "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.",
2761
+ "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
2762
  "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.",
2763
+ "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
2764
  "help.standaloneTitle": "Which node this talks to",
2729
2765
  "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
2766
  "action.theme": "Toggle light / dark",
@@ -2759,8 +2795,11 @@
2759
2795
  "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
2796
  "sec.recent": "🕒 Recently Opened",
2761
2797
  "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.",
2798
+ "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.",
2799
+ "sec.localHint": "💡 Add app publishes to your account as unlistedlist it when it's ready to be public.",
2800
+ "fav.title": "⭐ Favourites",
2801
+ "fav.add": "Add to favourites",
2802
+ "fav.remove": "Remove from favourites",
2764
2803
  "stats.apps": "apps",
2765
2804
  "stats.stored": "stored",
2766
2805
  "addModal.title": "Add App",
@@ -2826,6 +2865,8 @@
2826
2865
  "publish.accessCodeHint": "4-64 chars. If set, only people with the code can download.",
2827
2866
  "publish.submit": "Publish",
2828
2867
  "publish.loginRequired": "Sign in to publish to AIMEAT. Anonymous publishing is disabled — use the Sign in button in the top bar.",
2868
+ "publish.savedUnlisted": "Saved — your app is on the server as unlisted. Open it and press List when you want it public.",
2869
+ "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
2870
  "auth.signIn": "❤️ Sign In",
2830
2871
  "auth.loggedIn": "logged in",
2831
2872
  "auth.logout": "Logout",
@@ -2847,6 +2888,9 @@
2847
2888
  "pb.promptPreview": "Prompt preview",
2848
2889
  "pb.addBtn": "➕ Add your app — paste it or upload the file",
2849
2890
  "pb.improving": "Improving",
2891
+ "pb.tier.any": "ANY MODEL",
2892
+ "pb.tier.frontier": "VERSION TRAP",
2893
+ "pb.tier.needs-doc": "READ THE DOC",
2850
2894
  "pb.copied": "Copied!",
2851
2895
  "pb.new": "New App",
2852
2896
  "pb.improve": "Improve This App",
@@ -2954,6 +2998,24 @@
2954
2998
  "detail.iconLabel": "Icon (emoji)",
2955
2999
  "detail.tagsLabel": "Tags (comma separated)",
2956
3000
  "detail.descLabel": "Description",
3001
+ "detail.descEn": "Description (English)",
3002
+ "detail.descFi": "Description (Finnish)",
3003
+ "detail.translateEnFi": "Translate EN → FI",
3004
+ "detail.translateFiEn": "Translate FI → EN",
3005
+ "detail.translating": "Translating…",
3006
+ "detail.trDone": "Translated",
3007
+ "detail.trEmpty": "The AI returned nothing — try again.",
3008
+ "detail.trNeedSource": "Write the source-language description first.",
3009
+ "promote.title": "Promote",
3010
+ "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.",
3011
+ "promote.en": "Promotion text (English)",
3012
+ "promote.fi": "Promotion text (Finnish)",
3013
+ "promote.save": "Save promotion",
3014
+ "promote.remove": "Stop promoting",
3015
+ "promote.on": "Promoted",
3016
+ "promote.saving": "Saving…",
3017
+ "promote.saved": "Promoted on your profile.",
3018
+ "promote.removed": "No longer promoted.",
2957
3019
  "detail.renameHint": "The app link stays the same — only the displayed name and description change.",
2958
3020
  "detail.nameRequired": "Name cannot be empty.",
2959
3021
  "detail.descRequired": "Description cannot be empty.",
@@ -3012,6 +3074,90 @@
3012
3074
  "monetize.priceMorsels": "Price (morsels)",
3013
3075
  "monetize.priceMoney": "Money price",
3014
3076
  "monetize.currency": "Currency",
3077
+ "monetize.money2": "Second money price",
3078
+ "monetize.currency2": "Currency",
3079
+ "monetize.money2Hint": "Optional: sell the same call in a second currency. Buyers pick one when they open a contract.",
3080
+ "odps.title": "EXCHANGE & ODPS",
3081
+ "odps.hint": "Whether this app sells anything on the marketplace, and the product description every listing carries. A listed tool is published as an Open Data Product Specification v4.1 document that outside catalogues and buying agents can read.",
3082
+ "odps.statusOn": "On the marketplace: {live} of {total} tools",
3083
+ "odps.statusOff": "Not on the marketplace ({total} tools declared)",
3084
+ "odps.statusBlocked": "{n} tool(s) are flagged for EXCHANGE but cannot be listed yet — see the reason on the tool.",
3085
+ "odps.noTools": "This app declares no sellable tools yet. Add one under Monetize to sell a capability on the marketplace.",
3086
+ "odps.editDefaults": "ODPS defaults for this app",
3087
+ "odps.close": "Close",
3088
+ "odps.defaultsHint": "Filled once per app and inherited by every tool it sells. A tool can override any of it.",
3089
+ "odps.legalName": "Legal entity (data holder)",
3090
+ "odps.businessId": "Business ID",
3091
+ "odps.holderEmail": "Contact email",
3092
+ "odps.holderUrl": "Website",
3093
+ "odps.country": "Country",
3094
+ "odps.logoUrl": "Logo URL",
3095
+ "odps.brandSlogan": "Brand slogan",
3096
+ "odps.governance": "Governance profile",
3097
+ "odps.priority": "Portfolio priority",
3098
+ "odps.language": "Language",
3099
+ "odps.geoArea": "Licence area",
3100
+ "odps.applicableLaws": "Applicable law",
3101
+ "odps.attestation": "Provenance (your attestation)",
3102
+ "odps.attestationHint": "Where the material comes from and on what basis you may sell it. These are promises with legal weight, so AI never fills them — you state them.",
3103
+ "odps.source": "Source",
3104
+ "odps.legalBasis": "Legal basis",
3105
+ "odps.consentStatus": "Consent status",
3106
+ "odps.retention": "Retention",
3107
+ "odps.saveDefaults": "Save defaults",
3108
+ "odps.defaultsSaved": "ODPS defaults saved",
3109
+ "odps.toolTitle": "ODPS description",
3110
+ "odps.toolHint": "What a buyer sees before contracting. The node fills price, access, licence rights and measured use by itself.",
3111
+ "odps.viewYaml": "View odps.yaml",
3112
+ "odps.productType": "Product type",
3113
+ "odps.valueProposition": "Value proposition",
3114
+ "odps.valuePlaceholder": "One sentence: what does the buyer get?",
3115
+ "odps.categories": "Categories (comma separated)",
3116
+ "odps.standards": "Standards (comma separated)",
3117
+ "odps.useCases": "Use cases (one per line: title | description | url)",
3118
+ "odps.useCasesPlaceholder": "KYB onboarding | Verify a counterparty before opening an account",
3119
+ "odps.contentSample": "Sample output URL",
3120
+ "odps.sla": "Service commitments (one per line: dimension value unit — description)",
3121
+ "odps.slaPlaceholder": "uptime 99.5 percent — Monthly availability",
3122
+ "odps.quality": "Quality commitments (one per line: dimension value unit — description)",
3123
+ "odps.qualityPlaceholder": "accuracy 99 percentage — Names match the register",
3124
+ "odps.toolAttestation": "Provenance for this capability",
3125
+ "odps.toolAttestationHint": "Only what differs from the app defaults. Left empty, the app-level attestation applies.",
3126
+ "odps.inherited": "inherited from the app",
3127
+ "odps.transformations": "What was done to the raw material",
3128
+ "odps.transformationsPlaceholder": "Normalised names, joined municipality codes, dropped discontinued companies.",
3129
+ "odps.suggest": "Draft with AI",
3130
+ "odps.suggesting": "Drafting…",
3131
+ "odps.suggestDone": "Draft ready — review it before saving",
3132
+ "odps.suggestFailed": "Draft failed",
3133
+ "odps.suggestUnparseable": "The model did not return usable JSON",
3134
+ "odps.needLogin": "Sign in to use your own AI key",
3135
+ "odps.aiUnavailable": "AI is not available here",
3136
+ "odps.genSample": "Generate from a real call",
3137
+ "odps.genSampleHint": "Runs this capability once as you (free on your own capability), stores the answer as a public file, and puts its URL above. A buyer can then open the sample instead of taking your word for it.",
3138
+ "odps.genSampleInput": '{ "query": "Nokia" } — the input to call with',
3139
+ "odps.genSampleRunning": "Calling the capability…",
3140
+ "odps.genSampleDone": "Sample stored and linked",
3141
+ "odps.genSampleFailed": "The call did not produce a sample",
3142
+ "odps.genSampleBadJson": "That input is not valid JSON.",
3143
+ "odps.genSampleNoBinding": "This tool has no capability binding to call.",
3144
+ "odps.measured": "Measured: {n} calls, median {p50} ms, p95 {p95} ms.",
3145
+ "odps.measuredNote": "measured p95 with headroom",
3146
+ "odps.useMeasured": "Use measured + 30%",
3147
+ "odps.noMeasurement": "No delivery time measured yet. Serve some calls first, then commit to a number you can keep.",
3148
+ "odps.inheritedIs": "inherited:",
3149
+ "odps.inheritedNone": "no app-level attestation set yet",
3150
+ "odps.openAppDefaults": "Edit app-level defaults",
3151
+ "odps.blockedSchema": "Cannot be listed: the tool needs a non-empty input AND output schema.",
3152
+ "odps.blockedBinding": "Cannot be listed: the tool needs a capability binding (action_id).",
3153
+ "odps.blockedPrice": "Cannot be listed: the tool needs a price.",
3154
+ "monetize.exchange": "List in EXCHANGE",
3155
+ "monetize.toll": "Pacing burn per call (morsels)",
3156
+ "monetize.tollHint": "Not a price: morsels burned per call to cap how fast this tool can be consumed. Nobody is credited them, and your own calls are free. Leave empty to use the node default; set 0 to switch pacing off for this tool.",
3157
+ "monetize.tollInvalid": "Pacing burn must be a whole number between 0 and 100.",
3158
+ "monetize.exchangeHint": "On: this tool is offered publicly on the marketplace and its listing follows the price you set here. Off: it is removed from the marketplace. Contracts already agreed are unaffected.",
3159
+ "monetize.exchangeOn": "in EXCHANGE",
3160
+ "monetize.exchangeNeedsSchema": "To list in EXCHANGE the tool needs an input and output schema plus a capability binding. Publish those first (MCP: aimeat_app_tools_publish).",
3015
3161
  "monetize.actionId": "Capability binding (action id)",
3016
3162
  "monetize.actionIdHint": "Optional. A capability id (e.g. ext:my-extension:summarize) invoked instantly on purchase. Leave empty to fulfill as an agent task instead.",
3017
3163
  "monetize.agent": "Fulfilling agent",
@@ -3028,6 +3174,20 @@
3028
3174
  "monetize.saved": "Tool manifest saved.",
3029
3175
  "monetize.saveFailed": "Saving the tool manifest failed",
3030
3176
  "monetize.needLogin": "Sign in to edit the tool manifest.",
3177
+ "cost.title": "📊 Cost & contracts — what this app sources",
3178
+ "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.",
3179
+ "cost.empty": "No EXCHANGE contracts yet — this app sources nothing priced.",
3180
+ "cost.summary": "{n} contract(s) · {spent} morsels spent · {calls} call(s)",
3181
+ "cost.capabilityCol": "Capability",
3182
+ "cost.providerCol": "Provider",
3183
+ "cost.priceCol": "Price / call",
3184
+ "cost.rakeCol": "Rake",
3185
+ "cost.budgetCol": "Budget (spent / cap)",
3186
+ "cost.stateCol": "State",
3187
+ "cost.uncapped": "uncapped",
3188
+ "cost.remaining": "{n} left",
3189
+ "cost.needLogin": "Sign in to view this app’s cost & contracts.",
3190
+ "cost.loadFailed": "Could not load cost & contracts",
3031
3191
  "detail.editAccess": "Access code",
3032
3192
  "detail.accessCodeHint": "Set or remove an optional code required to open this app.",
3033
3193
  "detail.accessCodeLabel": "Access code",
@@ -3039,7 +3199,7 @@
3039
3199
  "detail.accessCodeCleared": "Access code removed — this app is public again.",
3040
3200
  "detail.needServerVersions": "Set the AIMEAT server URL in Settings to see published versions.",
3041
3201
  "detail.editAi": "✨ Edit with AI",
3042
- "detail.editAiHint": "Describe a change. The AI rewrites the app using your own OpenRouter key, saves a draft you can test, then keep or discard.",
3202
+ "detail.editAiHint": "Describe a change. The AI rewrites the app with your own OpenRouter key and proposes it — nothing changes until you save it to your working copy.",
3043
3203
  "detail.editAiPh": "e.g. add a dark mode toggle in the top-right corner",
3044
3204
  "detail.run": "✨ Generate change",
3045
3205
  "detail.running": "Generating…",
@@ -3048,7 +3208,7 @@
3048
3208
  "detail.publishTested": "🚀 Publish tested version",
3049
3209
  "detail.keep": "✔ Keep draft",
3050
3210
  "detail.discard": "✕ Discard",
3051
- "detail.draftReady": "Draft ready — test it, then keep or discard.",
3211
+ "detail.draftReady": "AI proposal ready — nothing is saved yet.",
3052
3212
  "detail.draftUploading": "Preparing your test version…",
3053
3213
  "detail.draftOpened": "Draft opened in a new tab — it runs just like the published app. Your live app is unchanged.",
3054
3214
  "detail.draftNeedsPublished": "Publish this app first, then you can test edits before they go live.",
@@ -3056,6 +3216,57 @@
3056
3216
  "detail.draftPublishConfirm": "Publish the tested draft as the new live version?",
3057
3217
  "detail.draftPublished": "✔ Published as v{v}. It is now the live app.",
3058
3218
  "detail.kept": "Draft kept as your local copy.",
3219
+ // ── Working copy → published (TARGET-048). One vocabulary for the whole edit loop: the
3220
+ // word "draft" is deliberately gone — it used to mean three different things here. ──
3221
+ "wc.lifecycle": "Where your work is",
3222
+ "wc.title": "Working copy",
3223
+ "wc.published": "Published",
3224
+ "wc.privateNote": "only you can see it",
3225
+ "wc.visibleToOthers": "everyone can see it",
3226
+ "wc.notVisibleYet": "nobody can see it yet",
3227
+ "wc.notPublishedYet": "not published",
3228
+ "wc.sameAsPublished": "same as published",
3229
+ "wc.nothingYet": "nothing saved yet",
3230
+ "wc.savedAt": "saved {t}",
3231
+ "wc.savedEarlier": "saved earlier",
3232
+ "wc.pending": "AI proposal waiting",
3233
+ "wc.explainPending": "The AI wrote a change but nothing is saved yet. Save it to your working copy — you can always get back to where you are now.",
3234
+ "wc.explainSaved": "You have a saved working copy that nobody else can see. Publish it as v{v} when it is ready.",
3235
+ "wc.explainSavedUnpublished": "You have a saved working copy. Publish it when it is ready.",
3236
+ "wc.explainClean": "Your working copy matches the published app. Edit it below — every save keeps the previous state, so nothing gets lost.",
3237
+ "wc.explainUnpublished": "This app is not published yet. Publish it to give it a version other people can open.",
3238
+ "wc.save": "💾 Save to working copy",
3239
+ "wc.saving": "Saving…",
3240
+ "wc.saved": "Saved to your working copy — it survives a reload and stays private until you publish.",
3241
+ "wc.savedShort": "Saved!",
3242
+ "wc.saveFailed": "Could not save",
3243
+ "wc.keptLocalOnly": "Kept in this tab only — publish the app first to get a working copy that lasts.",
3244
+ "wc.try": "▶ Try it safely",
3245
+ "wc.tryLocal": "▶ Quick preview",
3246
+ "wc.publishAs": "🚀 Publish as v{v}",
3247
+ "wc.publishNew": "🚀 Publish as a new version",
3248
+ "wc.discardProposal": "✕ Discard proposal",
3249
+ "wc.discardWork": "✕ Discard working copy",
3250
+ "wc.confirmDiscardWork": "Throw away your saved working copy and go back to the published app?",
3251
+ "wc.discardedWork": "Working copy discarded — you are back on the published app.",
3252
+ "wc.verbsHint": "Save keeps it for you only. Try opens it on its real address without changing the live app. Publish is what other people see.",
3253
+ "wc.history": "Working-copy history",
3254
+ "wc.historyHint": "Every save leaves a checkpoint here, so you can always go back. These are private to you — the published versions below are the ones other people see.",
3255
+ "wc.none": "No checkpoints yet — your first save creates one.",
3256
+ "wc.before": "before: {note}",
3257
+ "wc.beforeUnnamed": "before an edit",
3258
+ "wc.preview": "Preview",
3259
+ "wc.restore": "Restore",
3260
+ "wc.delete": "Delete",
3261
+ "wc.previewTitle": "checkpoint preview",
3262
+ "wc.confirmRestore": "Restore this checkpoint into your working copy? The current working copy is checkpointed first, so you can undo this.",
3263
+ "wc.confirmDelete": "Delete this checkpoint permanently?",
3264
+ "wc.restored": "Restored into your working copy. The published app is unchanged.",
3265
+ "wc.gone": "That checkpoint is no longer stored.",
3266
+ "wc.loginNeeded": "Sign in to save a working copy.",
3267
+ "wc.noteManual": "manual source edit",
3268
+ "wc.noteRestore": "restored an earlier checkpoint",
3269
+ "versions.publishedHint": "Published versions — these are what other people see.",
3059
3270
  "detail.discarded": "Draft discarded.",
3060
3271
  "detail.aiUnavailable": "AI editing needs an OpenRouter key. Add one in AIMEAT settings, then reload.",
3061
3272
  "detail.aiLoginNeeded": "Sign in to your AIMEAT node to use AI editing.",
@@ -3124,7 +3335,7 @@
3124
3335
  "tag.favorites": "⭐ Favorites",
3125
3336
  "empty.noApps": "No apps yet",
3126
3337
  "empty.noAppsDesc": "Add your first app using the + button above",
3127
- "empty.formats": "Supports: HTML files, URLs, ZIP packages, AIMEAT imports",
3338
+ "empty.formats": "Supports: HTML files and ZIP packages",
3128
3339
  "empty.noMatch": "No matches",
3129
3340
  "empty.noMatchDesc": "No apps match the current filter. Try a different search term or tag."
3130
3341
  },
@@ -3147,14 +3358,14 @@
3147
3358
  "action.settings": "Asetukset",
3148
3359
  "action.help": "Ohje",
3149
3360
  "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.',
3361
+ "help.modesTitle": "Sovelluksen luominen",
3362
+ "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
3363
  "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.",
3364
+ "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
3365
  "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.",
3366
+ "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
3367
  "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ä.",
3368
+ "help.backup": "💾-valikko (yläpalkki) vie/tuo PALVELIN-appisi + laajennukset .zip-tiedostona täysi palvelinpuolen varmuuskopio, jonka voit palauttaa valikoiden.",
3158
3369
  "help.standaloneTitle": "Mille nodelle tämä puhuu",
3159
3370
  "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
3371
  "action.theme": "Vaihda vaalea / tumma",
@@ -3189,8 +3400,11 @@
3189
3400
  "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
3401
  "sec.recent": "🕒 Viimeksi avatut",
3191
3402
  "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.",
3403
+ "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.",
3404
+ "sec.localHint": "💡 Lisää sovellus julkaisee tilillesi ei-listallalistaa se kun se on valmis julkiseksi.",
3405
+ "fav.title": "⭐ Suosikit",
3406
+ "fav.add": "Lisää suosikkeihin",
3407
+ "fav.remove": "Poista suosikeista",
3194
3408
  "stats.apps": "sovellusta",
3195
3409
  "stats.stored": "tallennettu",
3196
3410
  "addModal.title": "Lisää sovellus",
@@ -3256,6 +3470,8 @@
3256
3470
  "publish.accessCodeHint": "4–64 merkkiä. Jos asetettu, vain koodin tietävät voivat ladata.",
3257
3471
  "publish.submit": "Julkaise",
3258
3472
  "publish.loginRequired": "Kirjaudu sisään julkaistaksesi AIMEATiin. Anonyymi julkaisu on poistettu käytöstä — käytä yläpalkin Kirjaudu-painiketta.",
3473
+ "publish.savedUnlisted": "Tallennettu — sovelluksesi on palvelimella ei-listalla. Avaa se ja paina Listaa kun haluat sen julkiseksi.",
3474
+ "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
3475
  "auth.signIn": "❤️ Kirjaudu",
3260
3476
  "auth.loggedIn": "kirjautunut",
3261
3477
  "auth.logout": "Kirjaudu ulos",
@@ -3277,6 +3493,9 @@
3277
3493
  "pb.promptPreview": "Promptin esikatselu",
3278
3494
  "pb.addBtn": "➕ Lisää sovelluksesi — liitä se tai lataa tiedosto",
3279
3495
  "pb.improving": "Parannetaan",
3496
+ "pb.tier.any": "KAIKILLE MALLEILLE",
3497
+ "pb.tier.frontier": "VERSIOANSA",
3498
+ "pb.tier.needs-doc": "LUE DOKUMENTTI",
3280
3499
  "pb.copied": "Kopioitu!",
3281
3500
  "pb.new": "Uusi sovellus",
3282
3501
  "pb.improve": "Paranna tätä sovellusta",
@@ -3384,6 +3603,24 @@
3384
3603
  "detail.iconLabel": "Kuvake (emoji)",
3385
3604
  "detail.tagsLabel": "Tunnisteet (pilkulla eroteltuna)",
3386
3605
  "detail.descLabel": "Kuvaus",
3606
+ "detail.descEn": "Kuvaus (englanti)",
3607
+ "detail.descFi": "Kuvaus (suomi)",
3608
+ "detail.translateEnFi": "Käännä EN → FI",
3609
+ "detail.translateFiEn": "Käännä FI → EN",
3610
+ "detail.translating": "Käännetään…",
3611
+ "detail.trDone": "Käännetty",
3612
+ "detail.trEmpty": "Tekoäly ei palauttanut mitään — yritä uudelleen.",
3613
+ "detail.trNeedSource": "Kirjoita ensin lähdekielinen kuvaus.",
3614
+ "promote.title": "Nosto",
3615
+ "promote.hint": "Nosta tämä sovellus julkiseen profiiliisi lyhyellä myyntipuheella. Täytä englanti ja/tai suomi; vierailijat näkevät kieltään vastaavan.",
3616
+ "promote.en": "Nostoteksti (englanti)",
3617
+ "promote.fi": "Nostoteksti (suomi)",
3618
+ "promote.save": "Tallenna nosto",
3619
+ "promote.remove": "Lopeta nostaminen",
3620
+ "promote.on": "Nostettu",
3621
+ "promote.saving": "Tallennetaan…",
3622
+ "promote.saved": "Nostettu profiiliisi.",
3623
+ "promote.removed": "Ei enää nostettu.",
3387
3624
  "detail.renameHint": "Sovelluksen linkki pysyy samana — vain näytettävä nimi ja kuvaus muuttuvat.",
3388
3625
  "detail.nameRequired": "Nimi ei voi olla tyhjä.",
3389
3626
  "detail.descRequired": "Kuvaus ei voi olla tyhjä.",
@@ -3440,6 +3677,90 @@
3440
3677
  "monetize.priceMorsels": "Hinta (morselit)",
3441
3678
  "monetize.priceMoney": "Rahahinta",
3442
3679
  "monetize.currency": "Valuutta",
3680
+ "monetize.money2": "Toinen rahahinta",
3681
+ "monetize.currency2": "Valuutta",
3682
+ "monetize.money2Hint": "Valinnainen: myy sama kutsu toisessa valuutassa. Ostaja valitsee valuutan sopimusta avatessaan.",
3683
+ "odps.title": "EXCHANGE ja ODPS",
3684
+ "odps.hint": "Myykö tämä appi jotain markkinalla, ja mikä tuotekuvaus jokaisen listauksen mukana kulkee. Listattu työkalu julkaistaan Open Data Product Specification v4.1 -dokumenttina, jota ulkoiset katalogit ja ostavat agentit osaavat lukea.",
3685
+ "odps.statusOn": "Markkinalla: {live} / {total} työkalua",
3686
+ "odps.statusOff": "Ei markkinalla ({total} työkalua määritelty)",
3687
+ "odps.statusBlocked": "{n} työkalua on merkitty EXCHANGEen mutta ei vielä listaudu — syy näkyy työkalun kohdalla.",
3688
+ "odps.noTools": "Tällä apilla ei ole vielä myytäviä työkaluja. Lisää työkalu Monetize-osiossa, niin voit myydä kyvykkyyden markkinalla.",
3689
+ "odps.editDefaults": "Appin ODPS-oletukset",
3690
+ "odps.close": "Sulje",
3691
+ "odps.defaultsHint": "Täytetään kerran appille ja periytyy jokaiselle sen myymälle työkalulle. Työkalu voi ylikirjoittaa minkä tahansa niistä.",
3692
+ "odps.legalName": "Juridinen yhtiö (datan haltija)",
3693
+ "odps.businessId": "Y-tunnus",
3694
+ "odps.holderEmail": "Yhteyssähköposti",
3695
+ "odps.holderUrl": "Verkkosivu",
3696
+ "odps.country": "Maa",
3697
+ "odps.logoUrl": "Logon URL",
3698
+ "odps.brandSlogan": "Brändislogan",
3699
+ "odps.governance": "Hallintaprofiili",
3700
+ "odps.priority": "Portfolioprioriteetti",
3701
+ "odps.language": "Kieli",
3702
+ "odps.geoArea": "Lisenssialue",
3703
+ "odps.applicableLaws": "Sovellettava laki",
3704
+ "odps.attestation": "Provenienssi (sinun vakuutuksesi)",
3705
+ "odps.attestationHint": "Mistä aineisto tulee ja millä perusteella saat myydä sitä. Nämä ovat lupauksia joilla on oikeudellinen paino, joten tekoäly ei täytä niitä koskaan — sinä ilmoitat ne.",
3706
+ "odps.source": "Lähde",
3707
+ "odps.legalBasis": "Oikeusperuste",
3708
+ "odps.consentStatus": "Suostumuksen tila",
3709
+ "odps.retention": "Säilytysaika",
3710
+ "odps.saveDefaults": "Tallenna oletukset",
3711
+ "odps.defaultsSaved": "ODPS-oletukset tallennettu",
3712
+ "odps.toolTitle": "ODPS-kuvaus",
3713
+ "odps.toolHint": "Se mitä ostaja näkee ennen sopimusta. Hinnan, pääsyn, lisenssioikeudet ja mitatun käytön node täyttää itse.",
3714
+ "odps.viewYaml": "Näytä odps.yaml",
3715
+ "odps.productType": "Tuotetyyppi",
3716
+ "odps.valueProposition": "Arvolupaus",
3717
+ "odps.valuePlaceholder": "Yksi lause: mitä ostaja saa?",
3718
+ "odps.categories": "Kategoriat (pilkulla eroteltuna)",
3719
+ "odps.standards": "Standardit (pilkulla eroteltuna)",
3720
+ "odps.useCases": "Käyttötapaukset (yksi riviä kohti: otsikko | kuvaus | url)",
3721
+ "odps.useCasesPlaceholder": "KYB-onboarding | Varmenna vastapuoli ennen tilin avaamista",
3722
+ "odps.contentSample": "Näytetulosteen URL",
3723
+ "odps.sla": "Palvelulupaukset (yksi riviä kohti: ulottuvuus arvo yksikkö — kuvaus)",
3724
+ "odps.slaPlaceholder": "uptime 99.5 percent — Kuukausittainen saatavuus",
3725
+ "odps.quality": "Laatulupaukset (yksi riviä kohti: ulottuvuus arvo yksikkö — kuvaus)",
3726
+ "odps.qualityPlaceholder": "accuracy 99 percentage — Nimet täsmäävät rekisteriin",
3727
+ "odps.toolAttestation": "Tämän kyvykkyyden provenienssi",
3728
+ "odps.toolAttestationHint": "Vain se mikä poikkeaa appin oletuksista. Tyhjänä appitason vakuutus pätee.",
3729
+ "odps.inherited": "peritään apilta",
3730
+ "odps.transformations": "Mitä raaka-aineelle tehtiin",
3731
+ "odps.transformationsPlaceholder": "Nimet normalisoitu, kuntakoodit liitetty, lakanneet yritykset poistettu.",
3732
+ "odps.suggest": "Luonnostele tekoälyllä",
3733
+ "odps.suggesting": "Luonnostellaan…",
3734
+ "odps.suggestDone": "Luonnos valmis — tarkista ennen tallennusta",
3735
+ "odps.suggestFailed": "Luonnostelu epäonnistui",
3736
+ "odps.suggestUnparseable": "Malli ei palauttanut käyttökelpoista JSONia",
3737
+ "odps.needLogin": "Kirjaudu sisään käyttääksesi omaa AI-avaintasi",
3738
+ "odps.aiUnavailable": "Tekoäly ei ole käytettävissä täällä",
3739
+ "odps.genSample": "Luo oikeasta kutsusta",
3740
+ "odps.genSampleHint": "Ajaa tämän kyvykkyyden kerran sinuna (oma kyvykkyys on maksuton), tallentaa vastauksen julkiseksi tiedostoksi ja asettaa sen osoitteen yllä olevaan kenttään. Ostaja voi silloin avata näytteen sen sijaan että uskoisi sanaasi.",
3741
+ "odps.genSampleInput": '{ "query": "Nokia" } — syöte jolla kutsutaan',
3742
+ "odps.genSampleRunning": "Kutsutaan kyvykkyyttä…",
3743
+ "odps.genSampleDone": "Näyte tallennettu ja linkitetty",
3744
+ "odps.genSampleFailed": "Kutsu ei tuottanut näytettä",
3745
+ "odps.genSampleBadJson": "Syöte ei ole kelvollista JSONia.",
3746
+ "odps.genSampleNoBinding": "Tällä työkalulla ei ole kyvykkyyssidontaa jota kutsua.",
3747
+ "odps.measured": "Mitattu: {n} kutsua, mediaani {p50} ms, p95 {p95} ms.",
3748
+ "odps.measuredNote": "mitattu p95 pelivaralla",
3749
+ "odps.useMeasured": "Käytä mitattua + 30 %",
3750
+ "odps.noMeasurement": "Toimitusaikaa ei ole vielä mitattu. Palvele ensin kutsuja, sitoudu vasta sitten lukuun jonka pystyt pitämään.",
3751
+ "odps.inheritedIs": "peritään:",
3752
+ "odps.inheritedNone": "appitason vakuutusta ei ole vielä asetettu",
3753
+ "odps.openAppDefaults": "Muokkaa appitason oletuksia",
3754
+ "odps.blockedSchema": "Ei listaudu: työkalu tarvitsee ei-tyhjän syöte- JA tulosskeeman.",
3755
+ "odps.blockedBinding": "Ei listaudu: työkalu tarvitsee kyvykkyyssidonnan (action_id).",
3756
+ "odps.blockedPrice": "Ei listaudu: työkalu tarvitsee hinnan.",
3757
+ "monetize.exchange": "Listaa EXCHANGEssa",
3758
+ "monetize.toll": "Tahdistuspoltto per kutsu (morselia)",
3759
+ "monetize.tollHint": "Ei hinta: morselit jotka palavat jokaisella kutsulla ja rajaavat kuinka nopeasti työkalua voi kuluttaa. Niitä ei hyvitetä kenellekään, ja omat kutsusi ovat ilmaisia. Jätä tyhjäksi niin noden oletus pätee; 0 poistaa tahdistuksen tästä työkalusta.",
3760
+ "monetize.tollInvalid": "Tahdistuspoltto on kokonaisluku väliltä 0-100.",
3761
+ "monetize.exchangeHint": "Päällä: työkalu on julkisesti markkinalla ja listaus seuraa tässä asetettua hintaa. Pois: se poistuu markkinalta. Jo sovitut sopimukset eivät muutu.",
3762
+ "monetize.exchangeOn": "EXCHANGEssa",
3763
+ "monetize.exchangeNeedsSchema": "EXCHANGE-listaus vaatii työkalulta syöte- ja tulosskeeman sekä kyvykkyyssidonnan. Julkaise ne ensin (MCP: aimeat_app_tools_publish).",
3443
3764
  "monetize.actionId": "Kyvykkyyssidonta (action id)",
3444
3765
  "monetize.actionIdHint": "Valinnainen. Kyvykkyyden id (esim. ext:oma-laajennus:summarize), joka ajetaan heti oston yhteydessä. Jätä tyhjäksi, jos haluat toimittaa agenttitehtävänä.",
3445
3766
  "monetize.agent": "Toimittava agentti",
@@ -3456,6 +3777,20 @@
3456
3777
  "monetize.saved": "Työkalumanifesti tallennettu.",
3457
3778
  "monetize.saveFailed": "Työkalumanifestin tallennus epäonnistui",
3458
3779
  "monetize.needLogin": "Kirjaudu sisään muokataksesi työkalumanifestia.",
3780
+ "cost.title": "📊 Kulut & sopimukset — mitä tämä appi hankkii",
3781
+ "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.",
3782
+ "cost.empty": "Ei vielä EXCHANGE-sopimuksia — tämä appi ei hanki mitään hinnoiteltua.",
3783
+ "cost.summary": "{n} sopimus(ta) · {spent} morselia käytetty · {calls} kutsu(a)",
3784
+ "cost.capabilityCol": "Kyvykkyys",
3785
+ "cost.providerCol": "Tarjoaja",
3786
+ "cost.priceCol": "Hinta / kutsu",
3787
+ "cost.rakeCol": "Siivu",
3788
+ "cost.budgetCol": "Budjetti (käytetty / katto)",
3789
+ "cost.stateCol": "Tila",
3790
+ "cost.uncapped": "ei kattoa",
3791
+ "cost.remaining": "{n} jäljellä",
3792
+ "cost.needLogin": "Kirjaudu sisään nähdäksesi tämän appin kulut & sopimukset.",
3793
+ "cost.loadFailed": "Kulujen & sopimusten lataus epäonnistui",
3459
3794
  "detail.skillDetach": "Irrota",
3460
3795
  "detail.skillAttachError": "Taidon liittäminen epäonnistui",
3461
3796
  "detail.editAccess": "Pääsykoodi",
@@ -3478,7 +3813,7 @@
3478
3813
  "detail.publishTested": "🚀 Julkaise testattu versio",
3479
3814
  "detail.keep": "✔ Säilytä luonnos",
3480
3815
  "detail.discard": "✕ Hylkää",
3481
- "detail.draftReady": "Luonnos valmis — testaa, sitten säilytä tai hylkää.",
3816
+ "detail.draftReady": "AI-ehdotus valmis — mitään ei ole vielä tallennettu.",
3482
3817
  "detail.draftUploading": "Valmistellaan testiversiota…",
3483
3818
  "detail.draftOpened": "Luonnos avattu uuteen välilehteen — toimii kuten julkaistu appi. Live-appi pysyy ennallaan.",
3484
3819
  "detail.draftNeedsPublished": "Julkaise appi ensin, niin voit testata muutoksia ennen kuin ne menevät liveksi.",
@@ -3486,6 +3821,57 @@
3486
3821
  "detail.draftPublishConfirm": "Julkaistaanko testattu luonnos uutena live-versiona?",
3487
3822
  "detail.draftPublished": "✔ Julkaistu versiona v{v}. Se on nyt live-appi.",
3488
3823
  "detail.kept": "Luonnos säilytetty paikallisena kopiona.",
3824
+ // ── Työkopio → julkaistu (TARGET-048). Yksi sanasto koko muokkaussilmukalle: sana
3825
+ // "luonnos" on tarkoituksella poissa — se tarkoitti täällä kolmea eri asiaa. ──
3826
+ "wc.lifecycle": "Missä työsi on",
3827
+ "wc.title": "Työkopio",
3828
+ "wc.published": "Julkaistu",
3829
+ "wc.privateNote": "vain sinä näet",
3830
+ "wc.visibleToOthers": "kaikki näkevät",
3831
+ "wc.notVisibleYet": "kukaan ei näe vielä",
3832
+ "wc.notPublishedYet": "ei julkaistu",
3833
+ "wc.sameAsPublished": "sama kuin julkaistu",
3834
+ "wc.nothingYet": "ei vielä tallennettu",
3835
+ "wc.savedAt": "tallennettu {t}",
3836
+ "wc.savedEarlier": "tallennettu aiemmin",
3837
+ "wc.pending": "AI-ehdotus odottaa",
3838
+ "wc.explainPending": "Tekoäly kirjoitti muutoksen, mutta mitään ei ole vielä tallennettu. Tallenna se työkopioon — pääset aina takaisin nykyiseen tilaan.",
3839
+ "wc.explainSaved": "Sinulla on tallennettu työkopio, jota kukaan muu ei näe. Julkaise se versiona v{v}, kun se on valmis.",
3840
+ "wc.explainSavedUnpublished": "Sinulla on tallennettu työkopio. Julkaise se, kun se on valmis.",
3841
+ "wc.explainClean": "Työkopiosi on sama kuin julkaistu appi. Muokkaa alla — jokainen tallennus säilyttää edellisen tilan, joten mikään ei katoa.",
3842
+ "wc.explainUnpublished": "Tätä appia ei ole vielä julkaistu. Julkaise se, niin sillä on versio jonka muut voivat avata.",
3843
+ "wc.save": "💾 Tallenna työkopioon",
3844
+ "wc.saving": "Tallennetaan…",
3845
+ "wc.saved": "Tallennettu työkopioon — säilyy sivun päivityksen yli ja pysyy yksityisenä kunnes julkaiset.",
3846
+ "wc.savedShort": "Tallennettu!",
3847
+ "wc.saveFailed": "Tallennus ei onnistunut",
3848
+ "wc.keptLocalOnly": "Säilytetty vain tässä välilehdessä — julkaise appi ensin, niin saat pysyvän työkopion.",
3849
+ "wc.try": "▶ Kokeile turvallisesti",
3850
+ "wc.tryLocal": "▶ Pikaesikatselu",
3851
+ "wc.publishAs": "🚀 Julkaise versiona v{v}",
3852
+ "wc.publishNew": "🚀 Julkaise uutena versiona",
3853
+ "wc.discardProposal": "✕ Hylkää ehdotus",
3854
+ "wc.discardWork": "✕ Hylkää työkopio",
3855
+ "wc.confirmDiscardWork": "Heitetäänkö tallennettu työkopio pois ja palataan julkaistuun appiin?",
3856
+ "wc.discardedWork": "Työkopio hylätty — olet takaisin julkaistussa apissa.",
3857
+ "wc.verbsHint": "Tallenna säilyttää muutoksen vain sinulle. Kokeile avaa sen oikeassa osoitteessa muuttamatta live-appia. Julkaise on se minkä muut näkevät.",
3858
+ "wc.history": "Työkopion historia",
3859
+ "wc.historyHint": "Jokainen tallennus jättää tänne tarkistuspisteen, joten voit aina palata taaksepäin. Nämä ovat vain sinun — julkaistut versiot alempana ovat ne jotka muut näkevät.",
3860
+ "wc.none": "Ei vielä tarkistuspisteitä — ensimmäinen tallennus luo sellaisen.",
3861
+ "wc.before": "ennen muutosta: {note}",
3862
+ "wc.beforeUnnamed": "ennen muokkausta",
3863
+ "wc.preview": "Esikatsele",
3864
+ "wc.restore": "Palauta",
3865
+ "wc.delete": "Poista",
3866
+ "wc.previewTitle": "tarkistuspisteen esikatselu",
3867
+ "wc.confirmRestore": "Palautetaanko tämä tarkistuspiste työkopioon? Nykyisestä työkopiosta tehdään ensin tarkistuspiste, joten voit kumota tämän.",
3868
+ "wc.confirmDelete": "Poistetaanko tämä tarkistuspiste pysyvästi?",
3869
+ "wc.restored": "Palautettu työkopioon. Julkaistu appi on ennallaan.",
3870
+ "wc.gone": "Tätä tarkistuspistettä ei ole enää tallessa.",
3871
+ "wc.loginNeeded": "Kirjaudu sisään tallentaaksesi työkopion.",
3872
+ "wc.noteManual": "käsin muokattu lähdekoodi",
3873
+ "wc.noteRestore": "palautettu aiempi tarkistuspiste",
3874
+ "versions.publishedHint": "Julkaistut versiot — nämä muut näkevät.",
3489
3875
  "detail.discarded": "Luonnos hylätty.",
3490
3876
  "detail.aiUnavailable": "Tekoälymuokkaus vaatii OpenRouter-avaimen. Lisää se AIMEAT-asetuksiin ja lataa sivu uudelleen.",
3491
3877
  "detail.aiLoginNeeded": "Kirjaudu AIMEAT-nodeen käyttääksesi tekoälymuokkausta.",
@@ -3554,7 +3940,7 @@
3554
3940
  "tag.favorites": "⭐ Suosikit",
3555
3941
  "empty.noApps": "Ei vielä sovelluksia",
3556
3942
  "empty.noAppsDesc": "Lisää ensimmäinen sovellus yllä olevalla +-painikkeella",
3557
- "empty.formats": "Tukee: HTML-tiedostot, URL-osoitteet, ZIP-paketit, AIMEAT-tuonnit",
3943
+ "empty.formats": "Tukee: HTML-tiedostot ja ZIP-paketit",
3558
3944
  "empty.noMatch": "Ei osumia",
3559
3945
  "empty.noMatchDesc": "Mikään sovellus ei vastaa nykyistä suodatinta. Kokeile eri hakusanaa tai tunnistetta."
3560
3946
  }
@@ -3677,128 +4063,25 @@
3677
4063
  }
3678
4064
 
3679
4065
  // 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
- }
4066
+ var workingSet = [];
3755
4067
  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
- });
4068
+ return Promise.resolve(workingSet.slice());
3768
4069
  }
3769
4070
  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
- };
4071
+ if (app && app.id) {
4072
+ var idx = workingSet.findIndex(function(a) {
4073
+ return a.id === app.id;
3783
4074
  });
3784
- });
4075
+ if (idx >= 0) workingSet[idx] = app;
4076
+ else workingSet.push(app);
4077
+ }
4078
+ return Promise.resolve();
3785
4079
  }
3786
4080
  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
- });
4081
+ workingSet = workingSet.filter(function(a) {
4082
+ return a.id !== id;
3801
4083
  });
4084
+ return Promise.resolve();
3802
4085
  }
3803
4086
 
3804
4087
  // src/static/app-catalog/js/ui.js
@@ -4372,9 +4655,9 @@
4372
4655
  function loadPbCore() {
4373
4656
  var config = loadConfig();
4374
4657
  if (!config.aimeatUrl) return;
4375
- var base = config.aimeatUrl.replace(/\/+$/, "");
4658
+ var base4 = config.aimeatUrl.replace(/\/+$/, "");
4376
4659
  ["new", "improve"].forEach(function(mode) {
4377
- fetch(base + "/v1/prompts/build-app?mode=" + mode + "&lang=" + encodeURIComponent(getLang())).then(function(r) {
4660
+ fetch(base4 + "/v1/prompts/build-app?mode=" + mode + "&lang=" + encodeURIComponent(getLang())).then(function(r) {
4378
4661
  return r.json();
4379
4662
  }).then(function(d) {
4380
4663
  var body = d && d.data && d.data.body;
@@ -4391,8 +4674,8 @@
4391
4674
  if (!sel) return;
4392
4675
  var config = loadConfig();
4393
4676
  if (!config.aimeatUrl) return;
4394
- var base = config.aimeatUrl.replace(/\/+$/, "");
4395
- fetch(base + "/v1/app-templates?lang=" + encodeURIComponent(getLang())).then(function(r) {
4677
+ var base4 = config.aimeatUrl.replace(/\/+$/, "");
4678
+ fetch(base4 + "/v1/app-templates?lang=" + encodeURIComponent(getLang())).then(function(r) {
4396
4679
  return r.json();
4397
4680
  }).then(function(d) {
4398
4681
  var list = (d.data && d.data.templates || []).filter(function(t2) {
@@ -4416,7 +4699,7 @@
4416
4699
  updatePbPreview();
4417
4700
  return;
4418
4701
  }
4419
- fetch(base + "/v1/app-templates/" + encodeURIComponent(id)).then(function(r) {
4702
+ fetch(base4 + "/v1/app-templates/" + encodeURIComponent(id)).then(function(r) {
4420
4703
  return r.json();
4421
4704
  }).then(function(d2) {
4422
4705
  window.pbTemplate = d2.data && d2.data.template || null;
@@ -4442,8 +4725,8 @@
4442
4725
  if (!wrap) return;
4443
4726
  var config = loadConfig();
4444
4727
  if (!config.aimeatUrl) return;
4445
- var base = config.aimeatUrl.replace(/\/+$/, "");
4446
- fetch(base + "/v1/library-packs?lang=" + encodeURIComponent(getLang())).then(function(r) {
4728
+ var base4 = config.aimeatUrl.replace(/\/+$/, "");
4729
+ fetch(base4 + "/v1/library-packs?lang=" + encodeURIComponent(getLang())).then(function(r) {
4447
4730
  return r.json();
4448
4731
  }).then(function(d) {
4449
4732
  var all = d.data && d.data.packs || [];
@@ -4464,7 +4747,7 @@
4464
4747
  cb.value = p.id;
4465
4748
  cb.onchange = function() {
4466
4749
  pbPackTouched[p.id] = true;
4467
- if (cb.checked) loadPbPackDoc(base, p.id);
4750
+ if (cb.checked) loadPbPackDoc(base4, p.id);
4468
4751
  else updatePbPreview();
4469
4752
  };
4470
4753
  var span = document.createElement("span");
@@ -4474,7 +4757,7 @@
4474
4757
  if (p.modelTier) {
4475
4758
  var badge = document.createElement("span");
4476
4759
  badge.className = "pb-pack-tier pb-tier-" + p.modelTier;
4477
- badge.textContent = p.modelTier;
4760
+ badge.textContent = t("pb.tier." + p.modelTier) || p.modelTier;
4478
4761
  var proven = (p.proofs || []).map(function(pr) {
4479
4762
  return pr.model + "→" + pr.verdict;
4480
4763
  }).join(", ");
@@ -4487,14 +4770,14 @@
4487
4770
  if (desc && !desc._pbPackListener) {
4488
4771
  desc._pbPackListener = true;
4489
4772
  desc.addEventListener("input", function() {
4490
- pbMatchPacksToIdea(base);
4773
+ pbMatchPacksToIdea(base4);
4491
4774
  });
4492
4775
  }
4493
- pbMatchPacksToIdea(base);
4776
+ pbMatchPacksToIdea(base4);
4494
4777
  }).catch(function() {
4495
4778
  });
4496
4779
  }
4497
- function pbMatchPacksToIdea(base) {
4780
+ function pbMatchPacksToIdea(base4) {
4498
4781
  var desc = document.getElementById("pb-description");
4499
4782
  var text = (desc && desc.value || "").toLowerCase();
4500
4783
  pbPacks.forEach(function(p) {
@@ -4507,19 +4790,19 @@
4507
4790
  });
4508
4791
  if (hit && !cb.checked) {
4509
4792
  cb.checked = true;
4510
- loadPbPackDoc(base, p.id);
4793
+ loadPbPackDoc(base4, p.id);
4511
4794
  } else if (!hit && cb.checked) {
4512
4795
  cb.checked = false;
4513
4796
  updatePbPreview();
4514
4797
  }
4515
4798
  });
4516
4799
  }
4517
- function loadPbPackDoc(base, id) {
4800
+ function loadPbPackDoc(base4, id) {
4518
4801
  if (pbPackDocs[id]) {
4519
4802
  updatePbPreview();
4520
4803
  return;
4521
4804
  }
4522
- fetch(base + "/v1/library-packs/" + encodeURIComponent(id)).then(function(r) {
4805
+ fetch(base4 + "/v1/library-packs/" + encodeURIComponent(id)).then(function(r) {
4523
4806
  return r.json();
4524
4807
  }).then(function(d) {
4525
4808
  var pack = d.data && d.data.pack;
@@ -4710,16 +4993,485 @@
4710
4993
  preview.textContent = buildPromptFromBuilder();
4711
4994
  }
4712
4995
 
4996
+ // src/static/app-catalog/js/promote.js
4997
+ var KEY = "app-catalog.promoted";
4998
+ var promoted = {};
4999
+ function base() {
5000
+ return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
5001
+ }
5002
+ function getPromotion(ref) {
5003
+ return promoted[ref] || null;
5004
+ }
5005
+ function getPromotedItems() {
5006
+ return Object.keys(promoted).map(function(ref) {
5007
+ return { ref, text: promoted[ref] || {} };
5008
+ });
5009
+ }
5010
+ function loadPromoted() {
5011
+ var token = getCortexOwnerToken();
5012
+ var b = base();
5013
+ if (!token || !b) {
5014
+ promoted = {};
5015
+ return Promise.resolve();
5016
+ }
5017
+ return fetch(b + "/v1/memory/" + encodeURIComponent(KEY), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5018
+ return r.ok ? r.json() : null;
5019
+ }).then(function(j) {
5020
+ var items = j && j.data && j.data.value && Array.isArray(j.data.value.items) ? j.data.value.items : [];
5021
+ var map = {};
5022
+ items.forEach(function(it) {
5023
+ if (it && typeof it.ref === "string" && it.text && typeof it.text === "object") map[it.ref] = { en: it.text.en || "", fi: it.text.fi || "" };
5024
+ });
5025
+ promoted = map;
5026
+ }).catch(function() {
5027
+ });
5028
+ }
5029
+ function persist() {
5030
+ var token = getCortexOwnerToken();
5031
+ var b = base();
5032
+ if (!token || !b) return Promise.resolve();
5033
+ var items = getPromotedItems();
5034
+ return fetch(b + "/v1/memory", {
5035
+ method: "POST",
5036
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
5037
+ body: JSON.stringify({
5038
+ key: KEY,
5039
+ value: { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items },
5040
+ visibility: "public"
5041
+ })
5042
+ }).catch(function() {
5043
+ });
5044
+ }
5045
+ function setPromotion(ref, text) {
5046
+ var en = text && text.en ? String(text.en).trim() : "";
5047
+ var fi = text && text.fi ? String(text.fi).trim() : "";
5048
+ var now;
5049
+ if (!en && !fi) {
5050
+ delete promoted[ref];
5051
+ now = false;
5052
+ } else {
5053
+ promoted[ref] = { en, fi };
5054
+ now = true;
5055
+ }
5056
+ return persist().then(function() {
5057
+ return now;
5058
+ });
5059
+ }
5060
+
5061
+ // src/static/app-catalog/js/odps.js
5062
+ var PRODUCT_TYPES = [
5063
+ "",
5064
+ "raw data",
5065
+ "derived data",
5066
+ "dataset",
5067
+ "reports",
5068
+ "analytic view",
5069
+ "algorithm",
5070
+ "decision support",
5071
+ "automated decision-making",
5072
+ "data-enhanced product",
5073
+ "data-driven service",
5074
+ "data-enabled performance",
5075
+ "bi-directional"
5076
+ ];
5077
+ var GOVERNANCE = ["", "structured", "enforced", "automated", "audit_ready"];
5078
+ var PRIORITIES = ["", "critical", "high", "medium", "low"];
5079
+ var SLA_DIMENSIONS = [
5080
+ "latency",
5081
+ "uptime",
5082
+ "responseTime",
5083
+ "errorRate",
5084
+ "endOfSupport",
5085
+ "endOfLife",
5086
+ "updateFrequency",
5087
+ "timeToDetect",
5088
+ "timeToNotify",
5089
+ "timeToRepair",
5090
+ "emailResponseTime"
5091
+ ];
5092
+ var SLA_UNITS = ["percent", "milliseconds", "seconds", "minutes", "days", "weeks", "months", "years", "never", "date"];
5093
+ var QUALITY_DIMENSIONS = ["accuracy", "completeness", "conformity", "consistency", "coverage", "timeliness", "validity", "uniqueness"];
5094
+ var QUALITY_UNITS = ["percentage", "number"];
5095
+ var odDefaultsOpen = false;
5096
+ var odToolOpen = false;
5097
+ var odBusy = false;
5098
+ function apiBase() {
5099
+ var cfg = loadConfig();
5100
+ return (cfg.aimeatUrl || "").replace(/\/+$/, "");
5101
+ }
5102
+ function val(id) {
5103
+ var el = document.getElementById(id);
5104
+ return el ? (el.value || "").trim() : "";
5105
+ }
5106
+ function setVal(id, v) {
5107
+ var el = document.getElementById(id);
5108
+ if (el && v !== void 0 && v !== null) el.value = String(v);
5109
+ }
5110
+ function listOf(id) {
5111
+ return val(id).split(",").map(function(x) {
5112
+ return x.trim();
5113
+ }).filter(Boolean);
5114
+ }
5115
+ function input(id, label, value, placeholder, type) {
5116
+ return '<label class="dtl-stat-label" for="' + id + '">' + escapeHtml(label) + '</label><input id="' + id + '" class="modal-input od-input" ' + (type ? 'type="' + type + '" ' : "") + 'value="' + escapeHtml(value || "") + '" placeholder="' + escapeHtml(placeholder || "") + '" />';
5117
+ }
5118
+ function textarea(id, label, value, placeholder, rows) {
5119
+ return '<label class="dtl-stat-label" for="' + id + '">' + escapeHtml(label) + '</label><textarea id="' + id + '" class="modal-input od-input" rows="' + (rows || 2) + '" placeholder="' + escapeHtml(placeholder || "") + '">' + escapeHtml(value || "") + "</textarea>";
5120
+ }
5121
+ function select(id, label, options, selected) {
5122
+ var html = '<label class="dtl-stat-label" for="' + id + '">' + escapeHtml(label) + '</label><select id="' + id + '" class="modal-input od-input">';
5123
+ for (var i = 0; i < options.length; i++) {
5124
+ var o = options[i];
5125
+ html += '<option value="' + escapeHtml(o) + '"' + (selected === o ? " selected" : "") + ">" + escapeHtml(o || "—") + "</option>";
5126
+ }
5127
+ return html + "</select>";
5128
+ }
5129
+ function dimsToText(dims) {
5130
+ return (dims || []).map(function(d) {
5131
+ return d.dimension + " " + d.objective + " " + d.unit + (d.description ? " — " + d.description : "");
5132
+ }).join("\n");
5133
+ }
5134
+ function textToDims(text, dimensions, units) {
5135
+ var out = [];
5136
+ var lines = (text || "").split("\n");
5137
+ for (var i = 0; i < lines.length; i++) {
5138
+ var line = lines[i].trim();
5139
+ if (!line) continue;
5140
+ var parts = line.split("—");
5141
+ var head = parts[0].trim().split(/\s+/);
5142
+ var desc = parts.length > 1 ? parts.slice(1).join("—").trim() : "";
5143
+ if (head.length < 3) continue;
5144
+ var dim = head[0], objective = parseFloat(String(head[1]).replace(",", ".")), unit = head[2];
5145
+ if (dimensions.indexOf(dim) === -1 || units.indexOf(unit) === -1 || !Number.isFinite(objective)) continue;
5146
+ var entry = { dimension: dim, objective, unit };
5147
+ if (desc) entry.description = desc;
5148
+ out.push(entry);
5149
+ }
5150
+ return out;
5151
+ }
5152
+ function odpsBlockedReason(tool) {
5153
+ if (!tool || !tool.exchange) return "";
5154
+ var hasIn = tool.inputSchema && Object.keys(tool.inputSchema).length;
5155
+ var hasOut = tool.outputSchema && Object.keys(tool.outputSchema).length;
5156
+ if (!hasIn || !hasOut) return t("odps.blockedSchema");
5157
+ if (!tool.action_id) return t("odps.blockedBinding");
5158
+ var priced = tool.price && tool.price.morsels > 0 || tool.priceMoney && tool.priceMoney.amount > 0;
5159
+ if (!priced) return t("odps.blockedPrice");
5160
+ return "";
5161
+ }
5162
+ function statusLine(doc) {
5163
+ var tools = doc && doc.tools || [];
5164
+ var flagged = tools.filter(function(x) {
5165
+ return x.exchange;
5166
+ });
5167
+ var blocked = flagged.filter(function(x) {
5168
+ return !!odpsBlockedReason(x);
5169
+ });
5170
+ var live = flagged.length - blocked.length;
5171
+ if (!tools.length) {
5172
+ return '<div class="od-status od-status-none">' + escapeHtml(t("odps.noTools")) + "</div>";
5173
+ }
5174
+ var cls = live > 0 ? "od-status-on" : "od-status-none";
5175
+ var label = live > 0 ? t("odps.statusOn").replace("{live}", String(live)).replace("{total}", String(tools.length)) : t("odps.statusOff").replace("{total}", String(tools.length));
5176
+ var html = '<div class="od-status ' + cls + '">' + escapeHtml(label) + "</div>";
5177
+ if (blocked.length) {
5178
+ html += '<div class="dtl-sync none od-note">' + escapeHtml(t("odps.statusBlocked").replace("{n}", String(blocked.length))) + "</div>";
5179
+ }
5180
+ return html;
5181
+ }
5182
+ function defaultsForm(doc) {
5183
+ var o = doc && doc.odps || {};
5184
+ var h = o.dataHolder || {};
5185
+ var lic = o.license || {};
5186
+ var p = doc && doc.provenance || {};
5187
+ return '<div class="od-form"><div class="dtl-sync none od-note">' + escapeHtml(t("odps.defaultsHint")) + '</div><div class="od-grid"><div class="od-col">' + input("od-legalname", t("odps.legalName"), h.legalName, "Overscale Solutions Oy") + '</div><div class="od-col">' + input("od-businessid", t("odps.businessId"), h.businessID, "3312345-6") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-holderemail", t("odps.holderEmail"), h.email, "sales@example.org", "email") + '</div><div class="od-col">' + input("od-holderurl", t("odps.holderUrl"), h.URL, "https://example.org", "url") + '</div><div class="od-col od-col-narrow">' + input("od-country", t("odps.country"), h.addressCountry, "FI") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-logo", t("odps.logoUrl"), o.logoURL, "https://example.org/logo.png", "url") + '</div><div class="od-col">' + input("od-slogan", t("odps.brandSlogan"), o.brandSlogan, "") + '</div></div><div class="od-grid"><div class="od-col">' + select("od-governance", t("odps.governance"), GOVERNANCE, o.governanceProfile || "") + '</div><div class="od-col">' + select("od-priority", t("odps.priority"), PRIORITIES, o.portfolioPriority || "") + '</div><div class="od-col od-col-narrow">' + input("od-language", t("odps.language"), o.language || "en", "en") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-geo", t("odps.geoArea"), (lic.geographicalArea || []).join(", "), "EU, EEA") + '</div><div class="od-col">' + input("od-laws", t("odps.applicableLaws"), lic.applicableLaws, "Finnish law") + '</div></div><div class="od-attest"><div class="dtl-stat-label od-attest-head">' + escapeHtml(t("odps.attestation")) + '</div><div class="dtl-sync none od-note">' + escapeHtml(t("odps.attestationHint")) + '</div><div class="od-grid"><div class="od-col">' + input("od-source", t("odps.source"), p.source, "PRH open company register (YTJ v3)") + '</div><div class="od-col">' + input("od-legalbasis", t("odps.legalBasis"), p.legalBasis, "Public register") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-consent", t("odps.consentStatus"), p.consentStatus, "not applicable") + '</div><div class="od-col">' + input("od-retention", t("odps.retention"), p.retention, "30 days") + '</div></div></div><div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.saveDefaults"), "window._launcher.odpsSaveDefaults()", { variant: "primary" }) + dtlBtn(t("odps.close"), "window._launcher.odpsToggleDefaults()") + '</div><div class="dtl-ai-status" id="od-status"></div></div>';
5188
+ }
5189
+ function odpsStatusInner(doc, state) {
5190
+ if (state === "off") return "";
5191
+ var html = "<h3>" + escapeHtml(t("odps.title")) + '</h3><p class="dtl-desc">' + escapeHtml(t("odps.hint")) + "</p>";
5192
+ if (state === "loading") return html + '<span class="od-muted">…</span>';
5193
+ html += statusLine(doc);
5194
+ html += '<div class="dtl-btn-row od-actions">' + dtlBtn(odDefaultsOpen ? t("odps.close") : t("odps.editDefaults"), "window._launcher.odpsToggleDefaults()") + "</div>";
5195
+ if (odDefaultsOpen) html += defaultsForm(doc);
5196
+ return html;
5197
+ }
5198
+ function readOdpsAppDefaults() {
5199
+ var holder = {};
5200
+ if (val("od-legalname")) holder.legalName = val("od-legalname");
5201
+ if (val("od-businessid")) holder.businessID = val("od-businessid");
5202
+ if (val("od-holderemail")) holder.email = val("od-holderemail");
5203
+ if (val("od-holderurl")) holder.URL = val("od-holderurl");
5204
+ if (val("od-country")) holder.addressCountry = val("od-country");
5205
+ var license = {};
5206
+ if (listOf("od-geo").length) license.geographicalArea = listOf("od-geo");
5207
+ if (val("od-laws")) license.applicableLaws = val("od-laws");
5208
+ var odps = {};
5209
+ if (holder.legalName) odps.dataHolder = holder;
5210
+ if (Object.keys(license).length) odps.license = license;
5211
+ if (val("od-logo")) odps.logoURL = val("od-logo");
5212
+ if (val("od-slogan")) odps.brandSlogan = val("od-slogan");
5213
+ if (val("od-governance")) odps.governanceProfile = val("od-governance");
5214
+ if (val("od-priority")) odps.portfolioPriority = val("od-priority");
5215
+ if (/^[a-z]{2}$/.test(val("od-language"))) odps.language = val("od-language");
5216
+ var prov = {};
5217
+ if (val("od-source")) prov.source = val("od-source");
5218
+ if (val("od-legalbasis")) prov.legalBasis = val("od-legalbasis");
5219
+ if (val("od-consent")) prov.consentStatus = val("od-consent");
5220
+ if (val("od-retention")) prov.retention = val("od-retention");
5221
+ return {
5222
+ odps: Object.keys(odps).length ? odps : void 0,
5223
+ provenance: Object.keys(prov).length ? prov : void 0
5224
+ };
5225
+ }
5226
+ function odpsToolFieldsHtml(tool, offeringId, ctx) {
5227
+ ctx = ctx || {};
5228
+ var o = tool && tool.odps || {};
5229
+ var p = tool && tool.provenance || {};
5230
+ var inherited = ctx.appProvenance || {};
5231
+ var timing = ctx.timing || null;
5232
+ var head = '<div class="od-tool-head">' + dtlBtn((odToolOpen ? "▾ " : "▸ ") + t("odps.toolTitle"), "window._launcher.odpsToggleTool()") + (offeringId ? '<a class="od-link" href="' + apiBase() + "/v1/exchange/offerings/" + encodeURIComponent(offeringId) + '/odps.yaml" target="_blank" rel="noopener">' + escapeHtml(t("odps.viewYaml")) + "</a>" : "") + "</div>";
5233
+ if (!odToolOpen) return head;
5234
+ return head + '<div class="od-form"><div class="dtl-sync none od-note">' + escapeHtml(t("odps.toolHint")) + '</div><div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.suggest"), "window._launcher.odpsSuggest()", { variant: "primary", disabled: odBusy }) + '</div><div class="dtl-ai-status" id="od-tool-status"></div>' + select("odt-type", t("odps.productType"), PRODUCT_TYPES, o.productType || "") + textarea("odt-value", t("odps.valueProposition"), o.valueProposition, t("odps.valuePlaceholder"), 2) + '<div class="od-grid"><div class="od-col">' + input("odt-categories", t("odps.categories"), (o.categories || []).join(", "), "company data, finland") + '</div><div class="od-col">' + input("odt-standards", t("odps.standards"), (o.standards || []).join(", "), "ISO 8000") + "</div></div>" + textarea("odt-usecases", t("odps.useCases"), (o.useCases || []).map(function(u) {
5235
+ return u.title + (u.description ? " | " + u.description : "") + (u.url ? " | " + u.url : "");
5236
+ }).join("\n"), t("odps.useCasesPlaceholder"), 2) + input("odt-sample", t("odps.contentSample"), o.contentSample, "https://example.org/sample.json", "url") + // A sample a buyer can open beats a sentence claiming one exists. Run the capability once, store the
5237
+ // answer as a public file, put its URL here. Nothing is invented: it IS the output.
5238
+ (ctx.actionId ? '<div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.genSample"), "window._launcher.odpsGenerateSample()", { disabled: odBusy }) + '</div><div class="dtl-sync none od-note">' + escapeHtml(t("odps.genSampleHint")) + '</div><textarea id="odt-sample-input" class="modal-input od-input" rows="2" placeholder="' + escapeHtml(t("odps.genSampleInput")) + '">' + escapeHtml(ctx.sampleInput || "") + '</textarea><div class="dtl-ai-status" id="od-sample-status"></div>' : "") + textarea("odt-sla", t("odps.sla"), dimsToText(o.sla), t("odps.slaPlaceholder"), 2) + // Measured, then committed — in that order. The node reports what this capability actually does;
5239
+ // the number that becomes a promise is still chosen by a person.
5240
+ (timing && timing.count > 0 ? '<div class="od-measured">' + escapeHtml(t("odps.measured").replace("{n}", String(timing.count)).replace("{p50}", String(timing.p50Ms)).replace("{p95}", String(timing.p95Ms))) + " " + dtlBtn(t("odps.useMeasured"), "window._launcher.odpsUseMeasured(" + Math.ceil(timing.p95Ms * 1.3) + ")") + "</div>" : '<div class="dtl-sync none od-note">' + escapeHtml(t("odps.noMeasurement")) + "</div>") + textarea("odt-quality", t("odps.quality"), dimsToText(o.dataQuality), t("odps.qualityPlaceholder"), 2) + '<div class="od-attest"><div class="dtl-stat-label od-attest-head">' + escapeHtml(t("odps.toolAttestation")) + '</div><div class="dtl-sync none od-note">' + escapeHtml(t("odps.toolAttestationHint")) + "</div>" + input(
5241
+ "odt-source",
5242
+ t("odps.source"),
5243
+ p.source,
5244
+ inherited.source ? t("odps.inheritedIs") + " " + inherited.source : t("odps.inheritedNone")
5245
+ ) + textarea("odt-transformations", t("odps.transformations"), p.transformations, t("odps.transformationsPlaceholder"), 2) + '<div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.openAppDefaults"), "window._launcher.odpsToggleDefaults()") + "</div></div></div>";
5246
+ }
5247
+ function readOdpsToolFields(base4) {
5248
+ if (!odToolOpen) {
5249
+ return { odps: base4 && base4.odps, provenance: base4 && base4.provenance };
5250
+ }
5251
+ var odps = Object.assign({}, base4 && base4.odps || {});
5252
+ var prov = Object.assign({}, base4 && base4.provenance || {});
5253
+ var set = function(obj, key, v) {
5254
+ if (v) obj[key] = v;
5255
+ else delete obj[key];
5256
+ };
5257
+ set(odps, "productType", val("odt-type"));
5258
+ set(odps, "valueProposition", val("odt-value"));
5259
+ var cats = listOf("odt-categories");
5260
+ if (cats.length) odps.categories = cats;
5261
+ else delete odps.categories;
5262
+ var stds = listOf("odt-standards");
5263
+ if (stds.length) odps.standards = stds;
5264
+ else delete odps.standards;
5265
+ var uc = val("odt-usecases").split("\n").map(function(line) {
5266
+ var parts = line.split("|").map(function(x) {
5267
+ return x.trim();
5268
+ });
5269
+ if (!parts[0]) return null;
5270
+ var entry = { title: parts[0] };
5271
+ if (parts[1]) entry.description = parts[1];
5272
+ if (parts[2]) entry.url = parts[2];
5273
+ return entry;
5274
+ }).filter(Boolean);
5275
+ if (uc.length) odps.useCases = uc;
5276
+ else delete odps.useCases;
5277
+ set(odps, "contentSample", val("odt-sample"));
5278
+ var sla = textToDims(val("odt-sla"), SLA_DIMENSIONS, SLA_UNITS);
5279
+ if (sla.length) odps.sla = sla;
5280
+ else delete odps.sla;
5281
+ var dq = textToDims(val("odt-quality"), QUALITY_DIMENSIONS, QUALITY_UNITS);
5282
+ if (dq.length) odps.dataQuality = dq;
5283
+ else delete odps.dataQuality;
5284
+ set(prov, "source", val("odt-source"));
5285
+ set(prov, "transformations", val("odt-transformations"));
5286
+ return {
5287
+ odps: Object.keys(odps).length ? odps : void 0,
5288
+ provenance: Object.keys(prov).length ? prov : void 0
5289
+ };
5290
+ }
5291
+ function odpsUseMeasured(ms) {
5292
+ var el = document.getElementById("odt-sla");
5293
+ if (!el) return;
5294
+ var line = "responseTime " + ms + " milliseconds — " + t("odps.measuredNote");
5295
+ el.value = el.value.trim() ? el.value.trim() + "\n" + line : line;
5296
+ }
5297
+ function odpsGenerateSample(ctx, rerender3) {
5298
+ var statusEl = document.getElementById("od-sample-status");
5299
+ var token = getCortexOwnerToken();
5300
+ if (!token) {
5301
+ if (statusEl) statusEl.textContent = t("odps.needLogin");
5302
+ return;
5303
+ }
5304
+ var base4 = apiBase();
5305
+ var raw = (document.getElementById("odt-sample-input") || {}).value || "{}";
5306
+ var input2;
5307
+ try {
5308
+ input2 = JSON.parse(raw);
5309
+ } catch (e) {
5310
+ if (statusEl) {
5311
+ statusEl.className = "dtl-ai-status od-err";
5312
+ statusEl.textContent = t("odps.genSampleBadJson");
5313
+ }
5314
+ return;
5315
+ }
5316
+ var m = /^ext:([^:]+):(.+)$/.exec(ctx.actionId || "");
5317
+ if (!m) {
5318
+ if (statusEl) statusEl.textContent = t("odps.genSampleNoBinding");
5319
+ return;
5320
+ }
5321
+ odBusy = true;
5322
+ if (statusEl) {
5323
+ statusEl.className = "dtl-ai-status";
5324
+ statusEl.textContent = t("odps.genSampleRunning");
5325
+ }
5326
+ fetch(base4 + "/v1/ext/" + encodeURIComponent(m[1]) + "/" + encodeURIComponent(m[2]), {
5327
+ method: "POST",
5328
+ headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5329
+ body: JSON.stringify(input2)
5330
+ }).then(function(r) {
5331
+ return r.json().then(function(j) {
5332
+ return { ok: r.ok, j };
5333
+ });
5334
+ }).then(function(res) {
5335
+ if (!res.ok || !res.j || res.j.ok === false) {
5336
+ var msg = res.j && res.j.error && res.j.error.message || t("odps.genSampleFailed");
5337
+ throw new Error(msg);
5338
+ }
5339
+ var payload = JSON.stringify(res.j.data !== void 0 ? res.j.data : res.j, null, 2);
5340
+ var key = "odps.sample." + (ctx.appId || "app") + "." + (ctx.toolName || "tool") + ".json";
5341
+ return fetch(base4 + "/v1/storage", {
5342
+ method: "POST",
5343
+ headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5344
+ body: JSON.stringify({
5345
+ key,
5346
+ visibility: "public",
5347
+ mime_type: "application/json",
5348
+ data: btoa(unescape(encodeURIComponent(payload)))
5349
+ })
5350
+ }).then(function(r) {
5351
+ return r.json();
5352
+ }).then(function(up) {
5353
+ if (!up || up.ok === false) throw new Error(up && up.error && up.error.message || t("odps.genSampleFailed"));
5354
+ var url = base4 + "/v1/pub/" + encodeURIComponent(ctx.ownerGhii || "") + "/" + key;
5355
+ var field = document.getElementById("odt-sample");
5356
+ if (field) field.value = url;
5357
+ var st = document.getElementById("od-sample-status");
5358
+ if (st) {
5359
+ st.className = "dtl-ai-status od-ok";
5360
+ st.textContent = "✔ " + t("odps.genSampleDone");
5361
+ }
5362
+ odBusy = false;
5363
+ });
5364
+ }).catch(function(e) {
5365
+ odBusy = false;
5366
+ var st = document.getElementById("od-sample-status");
5367
+ if (st) {
5368
+ st.className = "dtl-ai-status od-err";
5369
+ st.textContent = "✘ " + (e.message || t("odps.genSampleFailed"));
5370
+ }
5371
+ });
5372
+ }
5373
+ function odpsToggleDefaults(rerender3) {
5374
+ odDefaultsOpen = !odDefaultsOpen;
5375
+ if (rerender3) rerender3();
5376
+ }
5377
+ function odpsToggleTool(rerender3) {
5378
+ odToolOpen = !odToolOpen;
5379
+ if (rerender3) rerender3();
5380
+ }
5381
+ function odpsSuggestForTool(ctx, rerender3) {
5382
+ var statusEl = document.getElementById("od-tool-status");
5383
+ var token = getCortexOwnerToken();
5384
+ if (!token) {
5385
+ if (statusEl) statusEl.textContent = t("odps.needLogin");
5386
+ return;
5387
+ }
5388
+ var base4 = apiBase();
5389
+ if (!base4) {
5390
+ if (statusEl) statusEl.textContent = t("odps.aiUnavailable");
5391
+ return;
5392
+ }
5393
+ odBusy = true;
5394
+ if (statusEl) {
5395
+ statusEl.className = "dtl-ai-status";
5396
+ statusEl.textContent = t("odps.suggesting");
5397
+ }
5398
+ var system = `You write the descriptive half of an Open Data Product Specification (ODPS v4.1) entry for a capability sold on a marketplace. Return ONLY a JSON object with these keys: "productType" (one of: raw data, derived data, dataset, reports, analytic view, algorithm, decision support, automated decision-making, data-enhanced product, data-driven service, data-enabled performance, bi-directional), "valueProposition" (one sentence, max 400 characters, what a buyer gets), "categories" (2-4 short strings), "standards" (0-3 recognised standards that genuinely apply, else []), "useCases" (1-3 objects with "title" and "description"). Base every word on the material given. Do NOT state where the data comes from, its legal basis, consent, retention, service levels, uptime, accuracy or the selling company — those are the owner's own attestations and inventing them would be a false claim. No markdown, no commentary, JSON only.`;
5399
+ var prompt2 = "App: " + (ctx.appName || ctx.appId) + "\n" + (ctx.appDescription ? "App description: " + ctx.appDescription + "\n" : "") + "Capability (tool) name: " + (ctx.toolName || "") + "\n" + (ctx.toolDescription ? "Capability description: " + ctx.toolDescription + "\n" : "") + (ctx.inputSchema ? "Input schema: " + JSON.stringify(ctx.inputSchema).slice(0, 1500) + "\n" : "") + (ctx.outputSchema ? "Output schema: " + JSON.stringify(ctx.outputSchema).slice(0, 1500) + "\n" : "") + (ctx.price ? "Price: " + ctx.price + "\n" : "") + "Write the ODPS descriptive fields for this capability.";
5400
+ fetch(base4 + "/v1/ai/complete", {
5401
+ method: "POST",
5402
+ headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5403
+ body: JSON.stringify({ prompt: prompt2, systemPrompt: system, app_id: "app-catalog" })
5404
+ }).then(function(r) {
5405
+ return r.json();
5406
+ }).then(function(json) {
5407
+ odBusy = false;
5408
+ var st = document.getElementById("od-tool-status");
5409
+ if (!json || !json.ok) {
5410
+ var msg = json && json.error && json.error.message || t("odps.suggestFailed");
5411
+ if (st) {
5412
+ st.className = "dtl-ai-status od-err";
5413
+ st.textContent = "✘ " + msg;
5414
+ }
5415
+ return;
5416
+ }
5417
+ var raw = json.data && json.data.content || "";
5418
+ var m = raw.match(/\{[\s\S]*\}/);
5419
+ var parsed = null;
5420
+ try {
5421
+ parsed = m ? JSON.parse(m[0]) : null;
5422
+ } catch (e) {
5423
+ parsed = null;
5424
+ }
5425
+ if (!parsed) {
5426
+ if (st) {
5427
+ st.className = "dtl-ai-status od-err";
5428
+ st.textContent = "✘ " + t("odps.suggestUnparseable");
5429
+ }
5430
+ return;
5431
+ }
5432
+ if (PRODUCT_TYPES.indexOf(parsed.productType) !== -1) setVal("odt-type", parsed.productType);
5433
+ if (parsed.valueProposition) setVal("odt-value", String(parsed.valueProposition).slice(0, 512));
5434
+ if (Array.isArray(parsed.categories)) setVal("odt-categories", parsed.categories.join(", "));
5435
+ if (Array.isArray(parsed.standards)) setVal("odt-standards", parsed.standards.join(", "));
5436
+ if (Array.isArray(parsed.useCases)) {
5437
+ setVal("odt-usecases", parsed.useCases.map(function(u) {
5438
+ return (u.title || "") + (u.description ? " | " + u.description : "");
5439
+ }).filter(Boolean).join("\n"));
5440
+ }
5441
+ if (st) {
5442
+ st.className = "dtl-ai-status od-ok";
5443
+ st.textContent = "✔ " + t("odps.suggestDone");
5444
+ }
5445
+ showNotice(t("odps.suggestDone"), "success");
5446
+ if (rerender3) {
5447
+ }
5448
+ }).catch(function(e) {
5449
+ odBusy = false;
5450
+ var st = document.getElementById("od-tool-status");
5451
+ if (st) {
5452
+ st.className = "dtl-ai-status od-err";
5453
+ st.textContent = "✘ " + (e.message || t("odps.suggestFailed"));
5454
+ }
5455
+ });
5456
+ }
5457
+
4713
5458
  // src/static/app-catalog/js/monetize.js
4714
5459
  var NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
4715
5460
  var MONEY_UNIT = 1e6;
5461
+ var CURRENCIES = ["EUR", "USD"];
4716
5462
  var mzOwner = "";
4717
5463
  var mzAppId = "";
4718
5464
  var mzDoc = null;
4719
5465
  var mzState = "off";
4720
5466
  var mzEditing = -1;
4721
5467
  var mzBusy = false;
4722
- function apiBase() {
5468
+ var mzOfferings = {};
5469
+ var mzTiming = {};
5470
+ var mzNodeId = "";
5471
+ function mzOwnerGhiiOf() {
5472
+ return mzOwner && mzNodeId ? mzOwner + "@" + mzNodeId : "";
5473
+ }
5474
+ function apiBase2() {
4723
5475
  var cfg = loadConfig();
4724
5476
  return (cfg.aimeatUrl || "").replace(/\/+$/, "");
4725
5477
  }
@@ -4729,6 +5481,8 @@
4729
5481
  function rerender() {
4730
5482
  var el = document.getElementById("detail-monetize");
4731
5483
  if (el) el.innerHTML = monetizeSectionInner();
5484
+ var od = document.getElementById("detail-odps");
5485
+ if (od) od.innerHTML = odpsStatusInner(mzDoc, mzState);
4732
5486
  }
4733
5487
  function monetizeOnOpen(owner, appId, isOwn) {
4734
5488
  mzEditing = -1;
@@ -4744,7 +5498,7 @@
4744
5498
  mzAppId = appId;
4745
5499
  mzState = "loading";
4746
5500
  var token = getCortexOwnerToken();
4747
- fetch(apiBase() + "/v1/memory/" + encodeURIComponent(toolsKey()), {
5501
+ fetch(apiBase2() + "/v1/memory/" + encodeURIComponent(toolsKey()), {
4748
5502
  headers: token ? { "Authorization": "Bearer " + token } : {}
4749
5503
  }).then(function(r) {
4750
5504
  return r.json();
@@ -4753,29 +5507,77 @@
4753
5507
  mzDoc = value && Array.isArray(value.tools) ? value : { tools: [] };
4754
5508
  mzState = "ready";
4755
5509
  rerender();
5510
+ loadOfferings();
4756
5511
  }).catch(function() {
4757
5512
  mzDoc = { tools: [] };
4758
5513
  mzState = "ready";
4759
5514
  rerender();
4760
5515
  });
4761
5516
  }
5517
+ function loadOfferings() {
5518
+ var ext = "apptool:" + mzOwner + "/" + mzAppId;
5519
+ fetch(apiBase2() + "/v1/exchange/offerings?stats=1&q=" + encodeURIComponent(mzAppId)).then(function(r) {
5520
+ return r.json();
5521
+ }).then(function(res) {
5522
+ if (res && res.node) mzNodeId = res.node;
5523
+ var list = res && res.ok && res.data && res.data.offerings || [];
5524
+ var next = {};
5525
+ var timing = {};
5526
+ for (var i = 0; i < list.length; i++) {
5527
+ if (list[i].ext === ext && list[i].state === "listed") {
5528
+ next[list[i].action] = list[i].offeringId;
5529
+ if (list[i].stats && list[i].stats.timing) timing[list[i].action] = list[i].stats.timing;
5530
+ }
5531
+ }
5532
+ mzOfferings = next;
5533
+ mzTiming = timing;
5534
+ rerender();
5535
+ }).catch(function() {
5536
+ });
5537
+ }
4762
5538
  function priceLabel(tool) {
4763
5539
  var parts = [];
4764
- if (tool.price && tool.price.morsels > 0) parts.push(tool.price.morsels + " 🥩");
4765
- if (tool.priceMoney && tool.priceMoney.amount > 0) {
4766
- parts.push(tool.priceMoney.amount / MONEY_UNIT + " " + tool.priceMoney.currency);
5540
+ var seen = {};
5541
+ if (tool.price && tool.price.morsels > 0) parts.push(tool.price.morsels + " morsels");
5542
+ var money = [tool.priceMoney].concat(tool.pricesMoney || []);
5543
+ for (var i = 0; i < money.length; i++) {
5544
+ var m = money[i];
5545
+ if (!m || !(m.amount > 0) || seen[m.currency]) continue;
5546
+ seen[m.currency] = true;
5547
+ parts.push(m.amount / MONEY_UNIT + " " + m.currency);
4767
5548
  }
4768
5549
  return parts.length ? parts.join(" · ") : t("monetize.notForSale");
4769
5550
  }
4770
5551
  function toolRow(tool, i) {
4771
5552
  var mode = tool.action_id ? t("monetize.fulfillCall") : t("monetize.fulfillTask");
4772
5553
  var binding = tool.action_id ? escapeHtml(tool.action_id) : tool.agent ? "→ " + escapeHtml(tool.agent) : t("monetize.taskToOwner");
4773
- return '<div class="dtl-status-row" style="align-items:center;gap:10px;flex-wrap:wrap"><div class="dtl-stat" style="flex:1;min-width:160px"><span class="dtl-stat-val">' + escapeHtml(tool.name) + "</span>" + (tool.description ? '<span class="dtl-stat-label">' + escapeHtml(tool.description) + "</span>" : "") + '</div><div class="dtl-stat"><span class="dtl-stat-label">' + t("monetize.priceCol") + '</span><span class="dtl-stat-val">' + escapeHtml(priceLabel(tool)) + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + mode + '</span><span class="dtl-stat-val" style="font-size:.8rem">' + binding + '</span></div><div class="dtl-btn-row" style="margin:0">' + dtlBtn(t("detail.editDetails"), "window._launcher.monetizeEditTool(" + i + ")") + dtlBtn("✕", "window._launcher.monetizeDeleteTool(" + i + ")", { variant: "danger", title: t("monetize.deleteHint") }) + "</div></div>";
5554
+ return '<div class="dtl-status-row" style="align-items:center;gap:10px;flex-wrap:wrap"><div class="dtl-stat" style="flex:1;min-width:160px"><span class="dtl-stat-val">' + escapeHtml(tool.name) + "</span>" + (tool.description ? '<span class="dtl-stat-label">' + escapeHtml(tool.description) + "</span>" : "") + '</div><div class="dtl-stat"><span class="dtl-stat-label">' + t("monetize.priceCol") + '</span><span class="dtl-stat-val">' + escapeHtml(priceLabel(tool)) + (tool.exchange ? ' <span class="dtl-sync ok" style="display:inline">' + t("monetize.exchangeOn") + "</span>" : "") + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + mode + '</span><span class="dtl-stat-val" style="font-size:.8rem">' + binding + '</span></div><div class="dtl-btn-row" style="margin:0">' + dtlBtn(t("detail.editDetails"), "window._launcher.monetizeEditTool(" + i + ")") + dtlBtn("✕", "window._launcher.monetizeDeleteTool(" + i + ")", { variant: "danger", title: t("monetize.deleteHint") }) + "</div>" + // A tool flagged for EXCHANGE that the projection will skip: say so here instead of leaving the
5555
+ // owner with a ticked box and an empty marketplace.
5556
+ (odpsBlockedReason(tool) ? '<div class="od-blocked">⚠ ' + escapeHtml(odpsBlockedReason(tool)) + "</div>" : "") + "</div>";
5557
+ }
5558
+ function currencySelect(id, selected) {
5559
+ var html = '<select id="' + id + '" class="modal-input" style="margin:4px 0 8px">';
5560
+ for (var i = 0; i < CURRENCIES.length; i++) {
5561
+ html += '<option value="' + CURRENCIES[i] + '"' + (selected === CURRENCIES[i] ? " selected" : "") + ">" + CURRENCIES[i] + "</option>";
5562
+ }
5563
+ return html + "</select>";
4774
5564
  }
4775
5565
  function editorHtml(tool) {
4776
5566
  var moneyMajor = tool.priceMoney && tool.priceMoney.amount > 0 ? String(tool.priceMoney.amount / MONEY_UNIT) : "";
4777
5567
  var cur = tool.priceMoney && tool.priceMoney.currency || "EUR";
4778
- return '<div class="dtl-ac-editor" style="margin-top:10px"><label class="dtl-stat-label" for="mz-name">' + t("monetize.name") + '</label><input id="mz-name" class="modal-input" maxlength="80" value="' + escapeHtml(tool.name || "") + '" placeholder="summarize" style="margin:4px 0 8px" /><label class="dtl-stat-label" for="mz-desc">' + t("monetize.desc") + '</label><input id="mz-desc" class="modal-input" maxlength="500" value="' + escapeHtml(tool.description || "") + '" style="margin:4px 0 8px" /><div style="display:flex;gap:10px;flex-wrap:wrap"><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-morsels">' + t("monetize.priceMorsels") + '</label><input id="mz-morsels" class="modal-input" type="number" min="0" step="1" value="' + (tool.price && tool.price.morsels || "") + '" placeholder="0" style="margin:4px 0 8px" /></div><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-money">' + t("monetize.priceMoney") + '</label><input id="mz-money" class="modal-input" inputmode="decimal" value="' + escapeHtml(moneyMajor) + '" placeholder="0.002" style="margin:4px 0 8px" /></div><div style="min-width:90px"><label class="dtl-stat-label" for="mz-currency">' + t("monetize.currency") + '</label><select id="mz-currency" class="modal-input" style="margin:4px 0 8px"><option value="EUR"' + (cur === "EUR" ? " selected" : "") + '>EUR</option><option value="USD"' + (cur === "USD" ? " selected" : "") + '>USD</option></select></div></div><label class="dtl-stat-label" for="mz-action">' + t("monetize.actionId") + '</label><input id="mz-action" class="modal-input" maxlength="200" value="' + escapeHtml(tool.action_id || "") + '" placeholder="ext:my-extension:summarize" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.actionIdHint") + '</div><label class="dtl-stat-label" for="mz-agent">' + t("monetize.agent") + '</label><input id="mz-agent" class="modal-input" maxlength="100" value="' + escapeHtml(tool.agent || "") + '" placeholder="assistant" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 10px">' + t("monetize.agentHint") + '</div><div class="dtl-btn-row">' + dtlBtn(t("detail.saveDetails"), "window._launcher.monetizeSaveTool()", { variant: "primary", disabled: mzBusy }) + dtlBtn(t("detail.cancelEdit"), "window._launcher.monetizeCancelEdit()") + '</div><div class="dtl-ai-status" id="mz-status"></div></div>';
5568
+ var second = (tool.pricesMoney || []).filter(function(p) {
5569
+ return p && p.currency !== cur && p.amount > 0;
5570
+ })[0];
5571
+ var money2Major = second ? String(second.amount / MONEY_UNIT) : "";
5572
+ var cur2 = second && second.currency || (cur === "EUR" ? "USD" : "EUR");
5573
+ return '<div class="dtl-ac-editor" style="margin-top:10px"><label class="dtl-stat-label" for="mz-name">' + t("monetize.name") + '</label><input id="mz-name" class="modal-input" maxlength="80" value="' + escapeHtml(tool.name || "") + '" placeholder="summarize" style="margin:4px 0 8px" /><label class="dtl-stat-label" for="mz-desc">' + t("monetize.desc") + '</label><input id="mz-desc" class="modal-input" maxlength="500" value="' + escapeHtml(tool.description || "") + '" style="margin:4px 0 8px" /><div style="display:flex;gap:10px;flex-wrap:wrap"><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-morsels">' + t("monetize.priceMorsels") + '</label><input id="mz-morsels" class="modal-input" type="number" min="0" step="1" value="' + (tool.price && tool.price.morsels || "") + '" placeholder="0" style="margin:4px 0 8px" /></div><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-money">' + t("monetize.priceMoney") + '</label><input id="mz-money" class="modal-input" inputmode="decimal" value="' + escapeHtml(moneyMajor) + '" placeholder="0.002" style="margin:4px 0 8px" /></div><div style="min-width:90px"><label class="dtl-stat-label" for="mz-currency">' + t("monetize.currency") + "</label>" + currencySelect("mz-currency", cur) + '</div></div><div style="display:flex;gap:10px;flex-wrap:wrap"><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-money2">' + t("monetize.money2") + '</label><input id="mz-money2" class="modal-input" inputmode="decimal" value="' + escapeHtml(money2Major) + '" placeholder="0.002" style="margin:4px 0 4px" /></div><div style="min-width:90px"><label class="dtl-stat-label" for="mz-currency2">' + t("monetize.currency2") + "</label>" + currencySelect("mz-currency2", cur2) + '</div></div><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.money2Hint") + '</div><label class="dtl-stat-label" style="display:flex;align-items:center;gap:8px;cursor:pointer"><input id="mz-exchange" type="checkbox"' + (tool.exchange ? " checked" : "") + " />" + t("monetize.exchange") + '</label><div class="dtl-sync none" style="margin:4px 0 8px">' + t("monetize.exchangeHint") + '</div><label class="dtl-stat-label" for="mz-toll">' + t("monetize.toll") + '</label><input id="mz-toll" class="modal-input" type="number" min="0" max="100" step="1" value="' + (typeof tool.tollMorsels === "number" ? tool.tollMorsels : "") + '" placeholder="0" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.tollHint") + "</div>" + odpsToolFieldsHtml(tool, mzOfferings[tool.name || ""] || "", {
5574
+ appId: mzAppId,
5575
+ ownerGhii: mzOwnerGhiiOf(),
5576
+ toolName: tool.name || "",
5577
+ actionId: tool.action_id || "",
5578
+ appProvenance: mzDoc && mzDoc.provenance || null,
5579
+ timing: mzTiming[tool.name || ""] || null
5580
+ }) + '<label class="dtl-stat-label" for="mz-action">' + t("monetize.actionId") + '</label><input id="mz-action" class="modal-input" maxlength="200" value="' + escapeHtml(tool.action_id || "") + '" placeholder="ext:my-extension:summarize" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.actionIdHint") + '</div><label class="dtl-stat-label" for="mz-agent">' + t("monetize.agent") + '</label><input id="mz-agent" class="modal-input" maxlength="100" value="' + escapeHtml(tool.agent || "") + '" placeholder="assistant" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 10px">' + t("monetize.agentHint") + '</div><div class="dtl-btn-row">' + dtlBtn(t("detail.saveDetails"), "window._launcher.monetizeSaveTool()", { variant: "primary", disabled: mzBusy }) + dtlBtn(t("detail.cancelEdit"), "window._launcher.monetizeCancelEdit()") + '</div><div class="dtl-ai-status" id="mz-status"></div></div>';
4779
5581
  }
4780
5582
  function monetizeSectionInner() {
4781
5583
  if (mzState === "off") return "";
@@ -4798,6 +5600,62 @@
4798
5600
  }
4799
5601
  return html;
4800
5602
  }
5603
+ function odpsSectionInner() {
5604
+ return odpsStatusInner(mzDoc, mzState);
5605
+ }
5606
+ function odpsToggleDefaultsUi() {
5607
+ odpsToggleDefaults(rerender);
5608
+ }
5609
+ function odpsToggleToolUi() {
5610
+ odpsToggleTool(rerender);
5611
+ }
5612
+ function odpsGenerateSampleUi() {
5613
+ var tool = mzEditing >= 0 && mzDoc && mzDoc.tools[mzEditing] ? mzDoc.tools[mzEditing] : {};
5614
+ odpsGenerateSample({
5615
+ appId: mzAppId,
5616
+ ownerGhii: mzOwnerGhiiOf(),
5617
+ toolName: (document.getElementById("mz-name") || {}).value || tool.name || "",
5618
+ actionId: (document.getElementById("mz-action") || {}).value || tool.action_id || ""
5619
+ }, rerender);
5620
+ }
5621
+ function odpsUseMeasuredUi(ms) {
5622
+ odpsUseMeasured(ms);
5623
+ }
5624
+ function odpsSuggestUi() {
5625
+ var tool = mzEditing >= 0 && mzDoc && mzDoc.tools[mzEditing] ? mzDoc.tools[mzEditing] : {};
5626
+ odpsSuggestForTool({
5627
+ appId: mzAppId,
5628
+ appName: mzAppId,
5629
+ toolName: (document.getElementById("mz-name") || {}).value || tool.name || "",
5630
+ toolDescription: (document.getElementById("mz-desc") || {}).value || tool.description || "",
5631
+ inputSchema: tool.inputSchema,
5632
+ outputSchema: tool.outputSchema,
5633
+ price: priceLabel(tool)
5634
+ }, rerender);
5635
+ }
5636
+ function odpsSaveDefaults() {
5637
+ if (mzBusy || !mzDoc) return;
5638
+ var vals = readOdpsAppDefaults();
5639
+ var next = Object.assign({}, mzDoc, { tools: mzDoc.tools.slice() });
5640
+ if (vals.odps) next.odps = vals.odps;
5641
+ else delete next.odps;
5642
+ if (vals.provenance) next.provenance = vals.provenance;
5643
+ else delete next.provenance;
5644
+ mzBusy = true;
5645
+ var el = document.getElementById("od-status");
5646
+ if (el) el.textContent = "…";
5647
+ writeManifest(next).then(function() {
5648
+ mzDoc = next;
5649
+ mzBusy = false;
5650
+ rerender();
5651
+ showNotice(t("odps.defaultsSaved"), "success");
5652
+ }).catch(function(e) {
5653
+ mzBusy = false;
5654
+ var st = document.getElementById("od-status");
5655
+ if (st) st.textContent = t("monetize.saveFailed") + ": " + e.message;
5656
+ showNotice(t("monetize.saveFailed") + ": " + e.message, "error");
5657
+ });
5658
+ }
4801
5659
  function monetizeAddTool() {
4802
5660
  mzEditing = -2;
4803
5661
  rerender();
@@ -4810,17 +5668,20 @@
4810
5668
  mzEditing = -1;
4811
5669
  rerender();
4812
5670
  }
4813
- function readEditor() {
5671
+ function readEditor(base4) {
4814
5672
  var name = (document.getElementById("mz-name").value || "").trim();
4815
5673
  if (!NAME_RE.test(name)) {
4816
5674
  showNotice(t("monetize.nameInvalid"), "error");
4817
5675
  return null;
4818
5676
  }
4819
- var tool = { name };
5677
+ var tool = Object.assign({}, base4 || {});
5678
+ tool.name = name;
4820
5679
  var desc = (document.getElementById("mz-desc").value || "").trim();
4821
5680
  if (desc) tool.description = desc;
5681
+ else delete tool.description;
4822
5682
  var morsels = parseInt(document.getElementById("mz-morsels").value, 10);
4823
5683
  if (Number.isFinite(morsels) && morsels > 0) tool.price = { morsels, unit: "per-call" };
5684
+ else delete tool.price;
4824
5685
  var moneyRaw = (document.getElementById("mz-money").value || "").trim();
4825
5686
  if (moneyRaw) {
4826
5687
  var major = parseFloat(moneyRaw.replace(",", "."));
@@ -4829,11 +5690,47 @@
4829
5690
  return null;
4830
5691
  }
4831
5692
  tool.priceMoney = { amount: Math.round(major * MONEY_UNIT), currency: document.getElementById("mz-currency").value };
5693
+ } else delete tool.priceMoney;
5694
+ var money2Raw = (document.getElementById("mz-money2").value || "").trim();
5695
+ var cur2 = document.getElementById("mz-currency2").value;
5696
+ var money2 = null;
5697
+ if (money2Raw) {
5698
+ var major2 = parseFloat(money2Raw.replace(",", "."));
5699
+ if (!Number.isFinite(major2) || major2 <= 0) {
5700
+ showNotice(t("monetize.moneyInvalid"), "error");
5701
+ return null;
5702
+ }
5703
+ money2 = { amount: Math.round(major2 * MONEY_UNIT), currency: cur2 };
5704
+ }
5705
+ var moneySet = [];
5706
+ if (tool.priceMoney) moneySet.push(tool.priceMoney);
5707
+ if (money2 && (!tool.priceMoney || tool.priceMoney.currency !== money2.currency)) moneySet.push(money2);
5708
+ if (moneySet.length > 1) tool.pricesMoney = moneySet;
5709
+ else delete tool.pricesMoney;
5710
+ var ex = document.getElementById("mz-exchange");
5711
+ if (ex && ex.checked) tool.exchange = true;
5712
+ else delete tool.exchange;
5713
+ var tollRaw = (document.getElementById("mz-toll").value || "").trim();
5714
+ if (tollRaw === "") delete tool.tollMorsels;
5715
+ else {
5716
+ var toll = parseInt(tollRaw, 10);
5717
+ if (!Number.isFinite(toll) || toll < 0 || toll > 100) {
5718
+ showNotice(t("monetize.tollInvalid"), "error");
5719
+ return null;
5720
+ }
5721
+ tool.tollMorsels = toll;
4832
5722
  }
4833
5723
  var actionId = (document.getElementById("mz-action").value || "").trim();
4834
5724
  if (actionId) tool.action_id = actionId;
5725
+ else delete tool.action_id;
4835
5726
  var agent = (document.getElementById("mz-agent").value || "").trim();
4836
5727
  if (agent) tool.agent = agent;
5728
+ else delete tool.agent;
5729
+ var od = readOdpsToolFields(base4);
5730
+ if (od.odps) tool.odps = od.odps;
5731
+ else delete tool.odps;
5732
+ if (od.provenance) tool.provenance = od.provenance;
5733
+ else delete tool.provenance;
4837
5734
  return tool;
4838
5735
  }
4839
5736
  function writeManifest(doc) {
@@ -4841,7 +5738,7 @@
4841
5738
  if (!token) return Promise.reject(new Error(t("monetize.needLogin")));
4842
5739
  doc.version = (doc.version || 0) + 1;
4843
5740
  doc.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4844
- return fetch(apiBase() + "/v1/memory", {
5741
+ return fetch(apiBase2() + "/v1/memory", {
4845
5742
  method: "POST",
4846
5743
  headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
4847
5744
  body: JSON.stringify({ key: toolsKey(), value: doc, visibility: "public" })
@@ -4854,7 +5751,7 @@
4854
5751
  }
4855
5752
  function monetizeSaveTool() {
4856
5753
  if (mzBusy || !mzDoc) return;
4857
- var tool = readEditor();
5754
+ var tool = readEditor(mzEditing >= 0 ? mzDoc.tools[mzEditing] : null);
4858
5755
  if (!tool) return;
4859
5756
  var tools = mzDoc.tools.slice();
4860
5757
  var dup = tools.findIndex(function(x, i) {
@@ -4869,7 +5766,7 @@
4869
5766
  mzBusy = true;
4870
5767
  var el = document.getElementById("mz-status");
4871
5768
  if (el) el.textContent = "…";
4872
- var next = { version: mzDoc.version, updatedAt: mzDoc.updatedAt, tools };
5769
+ var next = Object.assign({}, mzDoc, { tools });
4873
5770
  writeManifest(next).then(function() {
4874
5771
  mzDoc = next;
4875
5772
  mzEditing = -1;
@@ -4889,7 +5786,7 @@
4889
5786
  var tools = mzDoc.tools.slice();
4890
5787
  tools.splice(i, 1);
4891
5788
  mzBusy = true;
4892
- var next = { version: mzDoc.version, updatedAt: mzDoc.updatedAt, tools };
5789
+ var next = Object.assign({}, mzDoc, { tools });
4893
5790
  writeManifest(next).then(function() {
4894
5791
  mzDoc = next;
4895
5792
  mzEditing = -1;
@@ -4902,21 +5799,94 @@
4902
5799
  });
4903
5800
  }
4904
5801
 
4905
- // src/static/app-catalog/js/app-agents.js
4906
- var getServerManifests;
4907
- function initAppAgents(deps) {
4908
- ({ getServerManifests } = deps);
4909
- }
4910
- function aimeatBase() {
4911
- var config = loadConfig();
4912
- return config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, "") : "";
5802
+ // src/static/app-catalog/js/cost.js
5803
+ var cState = "off";
5804
+ var cData = null;
5805
+ var cAppId = "";
5806
+ function apiBase3() {
5807
+ var cfg = loadConfig();
5808
+ return (cfg.aimeatUrl || "").replace(/\/+$/, "");
4913
5809
  }
4914
- function appManifestAgents(owner, filename) {
4915
- var m = getServerManifests ? getServerManifests()[(owner || "") + "\n" + (filename || "")] : null;
4916
- return m && m.cortex && Array.isArray(m.cortex.agents) ? m.cortex.agents : [];
5810
+ function rerender2() {
5811
+ var el = document.getElementById("detail-cost");
5812
+ if (el) el.innerHTML = costSectionInner();
4917
5813
  }
4918
- function fmtOfferPrice(o) {
4919
- var parts = [];
5814
+ function costOnOpen(owner, appId, isOwn) {
5815
+ cData = null;
5816
+ if (!isOwn || !owner || !appId) {
5817
+ cState = "off";
5818
+ cAppId = "";
5819
+ return;
5820
+ }
5821
+ cAppId = owner + "/" + appId;
5822
+ cState = "loading";
5823
+ var token = getCortexOwnerToken();
5824
+ if (!token) {
5825
+ cState = "error";
5826
+ cData = { _needLogin: true };
5827
+ return;
5828
+ }
5829
+ fetch(apiBase3() + "/v1/apps/cost?app_id=" + encodeURIComponent(cAppId), {
5830
+ headers: { "Authorization": "Bearer " + token }
5831
+ }).then(function(r) {
5832
+ return r.json();
5833
+ }).then(function(res) {
5834
+ if (!res || !res.ok || !res.data) throw new Error("bad response");
5835
+ cData = res.data;
5836
+ cState = "ready";
5837
+ rerender2();
5838
+ }).catch(function() {
5839
+ cState = "error";
5840
+ cData = null;
5841
+ rerender2();
5842
+ });
5843
+ }
5844
+ function stateBadge(state) {
5845
+ var cls = state === "active" ? "ok" : state === "paused" ? "diff" : "none";
5846
+ return '<span class="dtl-sync ' + cls + '" style="margin:0">' + escapeHtml(state) + "</span>";
5847
+ }
5848
+ function contractRow(c) {
5849
+ var b = c.budget || {};
5850
+ var cap = b.cap_units === null || b.cap_units === void 0 ? t("cost.uncapped") : b.spent_units + " / " + b.cap_units;
5851
+ 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>";
5852
+ var providerShort = String(c.provider || "").split("@")[0];
5853
+ 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>";
5854
+ }
5855
+ function costSectionInner() {
5856
+ if (cState === "off") return "";
5857
+ var html = "<h3>" + t("cost.title") + '</h3><p class="dtl-desc">' + t("cost.hint") + "</p>";
5858
+ if (cState === "loading") {
5859
+ return html + '<span style="color:var(--text-muted);font-size:.85rem">…</span>';
5860
+ }
5861
+ if (cState === "error") {
5862
+ var msg = cData && cData._needLogin ? t("cost.needLogin") : t("cost.loadFailed");
5863
+ return html + '<span class="dtl-sync none">' + escapeHtml(msg) + "</span>";
5864
+ }
5865
+ var contracts = cData && cData.contracts || [];
5866
+ if (!contracts.length) {
5867
+ return html + '<span class="dtl-sync none">' + t("cost.empty") + "</span>";
5868
+ }
5869
+ var m = cData.totals && cData.totals.morsels || { spent_units: 0, calls: 0 };
5870
+ 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>";
5871
+ for (var i = 0; i < contracts.length; i++) html += contractRow(contracts[i]);
5872
+ return html;
5873
+ }
5874
+
5875
+ // src/static/app-catalog/js/app-agents.js
5876
+ var getServerManifests;
5877
+ function initAppAgents(deps) {
5878
+ ({ getServerManifests } = deps);
5879
+ }
5880
+ function aimeatBase() {
5881
+ var config = loadConfig();
5882
+ return config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, "") : "";
5883
+ }
5884
+ function appManifestAgents(owner, filename) {
5885
+ var m = getServerManifests ? getServerManifests()[(owner || "") + "\n" + (filename || "")] : null;
5886
+ return m && m.cortex && Array.isArray(m.cortex.agents) ? m.cortex.agents : [];
5887
+ }
5888
+ function fmtOfferPrice(o) {
5889
+ var parts = [];
4920
5890
  if (o.price && typeof o.price.morsels === "number") {
4921
5891
  parts.push(o.price.morsels + " 🥩" + (o.price.unit ? "/" + escapeHtml(o.price.unit) : ""));
4922
5892
  }
@@ -4957,8 +5927,8 @@
4957
5927
  showNotice(t("agents.none"));
4958
5928
  return;
4959
5929
  }
4960
- var base = aimeatBase();
4961
- if (!base) {
5930
+ var base4 = aimeatBase();
5931
+ if (!base4) {
4962
5932
  showNotice("Set AIMEAT server URL in Settings first");
4963
5933
  return;
4964
5934
  }
@@ -4974,7 +5944,7 @@
4974
5944
  document.getElementById("agents-overlay").hidden = false;
4975
5945
  defs.forEach(function(def2, i2) {
4976
5946
  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) {
5947
+ fetch(base4 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/agents/" + encodeURIComponent(def2.agent_name) + "/instances", { headers }).then(function(r) {
4978
5948
  return r.json();
4979
5949
  }).then(function(json) {
4980
5950
  var el = document.getElementById("aga-instances-" + i2);
@@ -4990,7 +5960,7 @@
4990
5960
  });
4991
5961
  });
4992
5962
  if (token) {
4993
- fetch(base + "/v1/agents", { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5963
+ fetch(base4 + "/v1/agents", { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
4994
5964
  return r.json();
4995
5965
  }).then(function(json) {
4996
5966
  var agents = json.data && json.data.agents || [];
@@ -5017,7 +5987,7 @@
5017
5987
  } catch (e) {
5018
5988
  }
5019
5989
  if (me) {
5020
- fetch(base + "/v1/organisms?member=" + encodeURIComponent(me), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5990
+ fetch(base4 + "/v1/organisms?member=" + encodeURIComponent(me), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5021
5991
  return r.json();
5022
5992
  }).then(function(json) {
5023
5993
  var orgs = json.data && json.data.organisms || [];
@@ -5072,6 +6042,157 @@
5072
6042
  });
5073
6043
  }
5074
6044
 
6045
+ // src/static/app-catalog/js/workcopy.js
6046
+ var MAX_CHECKPOINTS = 8;
6047
+ var PREFIX = "app-catalog.wc";
6048
+ var cache = {};
6049
+ function base2() {
6050
+ return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
6051
+ }
6052
+ function tok() {
6053
+ return getCortexOwnerToken();
6054
+ }
6055
+ function slugFor(owner, filename) {
6056
+ return (String(owner || "") + "-" + String(filename || "")).replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase().slice(0, 80);
6057
+ }
6058
+ function indexKey(slug) {
6059
+ return PREFIX + "." + slug;
6060
+ }
6061
+ function bodyKey(slug, id) {
6062
+ return PREFIX + "." + slug + ".b" + id;
6063
+ }
6064
+ function memGet(key) {
6065
+ var b = base2(), token = tok();
6066
+ if (!b || !token) return Promise.resolve(null);
6067
+ return fetch(b + "/v1/memory/" + encodeURIComponent(key), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
6068
+ return r.ok ? r.json() : null;
6069
+ }).then(function(j) {
6070
+ return j && j.data && j.data.value || null;
6071
+ }).catch(function() {
6072
+ return null;
6073
+ });
6074
+ }
6075
+ function memPut(key, value) {
6076
+ var b = base2(), token = tok();
6077
+ if (!b || !token) return Promise.reject(new Error("not signed in"));
6078
+ return fetch(b + "/v1/memory", {
6079
+ method: "POST",
6080
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
6081
+ body: JSON.stringify({ key, value, visibility: "private" })
6082
+ }).then(function(r) {
6083
+ return r.json().then(function(j) {
6084
+ if (!r.ok || j && j.ok === false) throw new Error(j && j.error && j.error.message || "HTTP " + r.status);
6085
+ return j;
6086
+ });
6087
+ });
6088
+ }
6089
+ function memDelete(key) {
6090
+ var b = base2(), token = tok();
6091
+ if (!b || !token) return Promise.resolve();
6092
+ return fetch(b + "/v1/memory/" + encodeURIComponent(key), {
6093
+ method: "DELETE",
6094
+ headers: { "Authorization": "Bearer " + token }
6095
+ }).catch(function() {
6096
+ });
6097
+ }
6098
+ function getCheckpoints(owner, filename) {
6099
+ return cache[slugFor(owner, filename)] || [];
6100
+ }
6101
+ function loadCheckpoints(owner, filename) {
6102
+ var slug = slugFor(owner, filename);
6103
+ return memGet(indexKey(slug)).then(function(v) {
6104
+ var items = v && Array.isArray(v.items) ? v.items : [];
6105
+ cache[slug] = items;
6106
+ return items;
6107
+ });
6108
+ }
6109
+ function pushCheckpoint(slug, b64, note) {
6110
+ var id = String(Date.now());
6111
+ return memPut(bodyKey(slug, id), { html: b64 }).then(function() {
6112
+ var entry = {
6113
+ id,
6114
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6115
+ note: String(note || "").slice(0, 160),
6116
+ size: Math.round(b64.length * 0.75)
6117
+ };
6118
+ var all = [entry].concat(cache[slug] || []);
6119
+ var keep = all.slice(0, MAX_CHECKPOINTS);
6120
+ var prune = all.slice(MAX_CHECKPOINTS);
6121
+ cache[slug] = keep;
6122
+ return memPut(indexKey(slug), { version: 1, updatedAt: entry.at, items: keep }).then(function() {
6123
+ return Promise.all(prune.map(function(p) {
6124
+ return memDelete(bodyKey(slug, p.id));
6125
+ }));
6126
+ }).then(function() {
6127
+ return entry;
6128
+ });
6129
+ });
6130
+ }
6131
+ function getDraft(owner, filename) {
6132
+ var b = base2(), token = tok();
6133
+ if (!b || !token) return Promise.resolve(null);
6134
+ return fetch(b + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/draft", {
6135
+ headers: { "Authorization": "Bearer " + token }
6136
+ }).then(function(r) {
6137
+ return r.ok ? r.json() : null;
6138
+ }).then(function(j) {
6139
+ return j && j.data && typeof j.data.content === "string" ? j.data : null;
6140
+ }).catch(function() {
6141
+ return null;
6142
+ });
6143
+ }
6144
+ function putDraft(owner, filename, b64) {
6145
+ var b = base2(), token = tok();
6146
+ if (!b || !token) return Promise.reject(new Error("not signed in"));
6147
+ return fetch(b + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/draft", {
6148
+ method: "PUT",
6149
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
6150
+ body: JSON.stringify({ content: b64 })
6151
+ }).then(function(r) {
6152
+ return r.json().then(function(j) {
6153
+ if (!j.ok) throw new Error(j.error && (j.error.message || j.error.code) || "HTTP " + r.status);
6154
+ return j.data;
6155
+ });
6156
+ });
6157
+ }
6158
+ function discardWorkingCopy(owner, filename) {
6159
+ var b = base2(), token = tok();
6160
+ if (!b || !token) return Promise.resolve();
6161
+ return fetch(b + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/draft", {
6162
+ method: "DELETE",
6163
+ headers: { "Authorization": "Bearer " + token }
6164
+ }).catch(function() {
6165
+ });
6166
+ }
6167
+ function saveWorkingCopy(opts) {
6168
+ var owner = opts.owner, filename = opts.filename;
6169
+ var slug = slugFor(owner, filename);
6170
+ var shouldCheckpoint = !!opts.previousB64 && opts.previousB64 !== opts.nextB64;
6171
+ var chain = shouldCheckpoint ? pushCheckpoint(slug, opts.previousB64, opts.note).catch(function() {
6172
+ return null;
6173
+ }) : Promise.resolve(null);
6174
+ return chain.then(function(entry) {
6175
+ return putDraft(owner, filename, opts.nextB64).then(function(draft) {
6176
+ return { checkpointed: !!entry, draft };
6177
+ });
6178
+ });
6179
+ }
6180
+ function readCheckpoint(owner, filename, id) {
6181
+ return memGet(bodyKey(slugFor(owner, filename), id)).then(function(v) {
6182
+ return v && typeof v.html === "string" ? v.html : null;
6183
+ });
6184
+ }
6185
+ function deleteCheckpoint(owner, filename, id) {
6186
+ var slug = slugFor(owner, filename);
6187
+ var items = (cache[slug] || []).filter(function(c) {
6188
+ return c.id !== id;
6189
+ });
6190
+ cache[slug] = items;
6191
+ return memPut(indexKey(slug), { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items }).then(function() {
6192
+ return memDelete(bodyKey(slug, id));
6193
+ });
6194
+ }
6195
+
5075
6196
  // src/static/app-catalog/js/detail.js
5076
6197
  var refreshAll;
5077
6198
  var loadPublishedApps;
@@ -5106,6 +6227,12 @@
5106
6227
  var detailMySkills = null;
5107
6228
  var detailBoundSkills = [];
5108
6229
  var detailSkillBusy = false;
6230
+ var detailWorkSavedAt = null;
6231
+ var detailHasWorkCopy = false;
6232
+ var detailCheckpointsHtml = null;
6233
+ var detailCheckpointBusy = false;
6234
+ var detailLastChangeNote = "";
6235
+ var detailEpoch = 0;
5109
6236
  function detailGetApp() {
5110
6237
  for (var i = 0; i < getMainApps().length; i++) {
5111
6238
  if (getMainApps()[i].id === detailAppId) return getMainApps()[i];
@@ -5166,9 +6293,16 @@
5166
6293
  detailSkillPickerOpen = false;
5167
6294
  detailMySkills = null;
5168
6295
  detailBoundSkills = [];
6296
+ detailWorkSavedAt = null;
6297
+ detailCheckpointsHtml = null;
6298
+ detailCheckpointBusy = false;
6299
+ detailEpoch++;
5169
6300
  var app = detailGetApp();
5170
6301
  if (!app) return;
6302
+ var wcState = app.publishedFilename && getServerState()[app.publishedFilename] || null;
6303
+ detailHasWorkCopy = !!(wcState && wcState.hasDraft);
5171
6304
  monetizeOnOpen(detailServerOwner(app), app.publishedFilename || "", detailIsOwnPublished(app));
6305
+ costOnOpen(detailServerOwner(app), app.publishedFilename || "", detailIsOwnPublished(app));
5172
6306
  document.getElementById("detail-view").hidden = false;
5173
6307
  renderDetailView();
5174
6308
  detailCheckAiAvailability();
@@ -5176,6 +6310,8 @@
5176
6310
  if (app.published && app.publishedFilename && owner) {
5177
6311
  detailLoadVersions(owner, app.publishedFilename);
5178
6312
  detailLoadSkills(detailServerOwner(app) || owner, app.publishedFilename);
6313
+ detailLoadCheckpoints(detailServerOwner(app) || owner, app.publishedFilename);
6314
+ if (detailHasWorkCopy) detailLoadWorkingCopy(app, detailServerOwner(app) || owner);
5179
6315
  }
5180
6316
  if (detailEditAboutOnOpen) {
5181
6317
  detailEditAboutOnOpen = false;
@@ -5195,14 +6331,14 @@
5195
6331
  var app = detailGetApp();
5196
6332
  if (!app) return;
5197
6333
  if (app.published && (app.publishedUrl || app.publishedFilename)) {
5198
- var base = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
6334
+ var base4 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
5199
6335
  var url;
5200
6336
  if (app.publishedUrl) {
5201
- url = base + app.publishedUrl;
6337
+ url = base4 + app.publishedUrl;
5202
6338
  } else {
5203
6339
  var st = getServerState()[app.publishedFilename] || {};
5204
6340
  var owner = st.owner || currentOwnerName() || "";
5205
- url = base + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app.publishedFilename);
6341
+ url = base4 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app.publishedFilename);
5206
6342
  }
5207
6343
  viewPublished(url + "?mode=inline", app.name);
5208
6344
  } else {
@@ -5224,11 +6360,6 @@
5224
6360
  });
5225
6361
  }
5226
6362
  function onAuthChanged() {
5227
- if (getDbMode() === "personal" && !currentOwnerName()) {
5228
- setDbMode("global");
5229
- }
5230
- closeDbInstance();
5231
- updateModeToggle();
5232
6363
  try {
5233
6364
  refreshAll();
5234
6365
  } catch (e) {
@@ -5289,34 +6420,41 @@
5289
6420
  var canEditAbout = !!app.id && !isUrlApp;
5290
6421
  document.getElementById("detail-title").innerHTML = '<span style="font-size:1.3rem">' + escapeHtml(icon) + "</span> " + escapeHtml(app.name || "App") + (canEditAbout && !detailEditingAbout ? ' <button class="rename-pencil" style="font-size:1rem" title="' + escapeHtml(t("detail.editDetails")) + '" onclick="window._launcher.detailAboutEdit()">✏️</button>' : "");
5291
6422
  var localBytes = app.blob ? Math.round(app.blob.length * 0.75) : 0;
5292
- var publishedV = app.publishedVersionNumber ? "v" + app.publishedVersionNumber : app.published ? "v?" : t("detail.notPublished");
5293
- var syncClass, syncText;
5294
- if (!app.published) {
5295
- syncClass = "none";
5296
- syncText = t("detail.notPublished");
5297
- } else if (detailDraftBlob) {
5298
- syncClass = "diff";
5299
- syncText = t("detail.localNewer");
6423
+ var publishedV = app.publishedVersionNumber ? "v" + app.publishedVersionNumber : "";
6424
+ var wcState = detailDraftBlob ? "pending" : detailHasWorkCopy ? "saved" : "clean";
6425
+ var wcValue, wcExplain;
6426
+ if (wcState === "pending") {
6427
+ wcValue = t("wc.pending");
6428
+ wcExplain = t("wc.explainPending");
6429
+ } else if (wcState === "saved") {
6430
+ wcValue = detailWorkSavedAt ? t("wc.savedAt").replace("{t}", new Date(detailWorkSavedAt).toLocaleTimeString()) : t("wc.savedEarlier");
6431
+ wcExplain = app.published ? t("wc.explainSaved").replace("{v}", String((app.publishedVersionNumber || 0) + 1)) : t("wc.explainSavedUnpublished");
5300
6432
  } else {
5301
- syncClass = "ok";
5302
- syncText = t("detail.inSync");
6433
+ wcValue = app.published ? t("wc.sameAsPublished") : t("wc.nothingYet");
6434
+ wcExplain = app.published ? t("wc.explainClean") : t("wc.explainUnpublished");
5303
6435
  }
5304
6436
  var aimeatBase2 = config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, "") : "";
5305
6437
  var shotOwner = detailServerOwner(app);
5306
6438
  var shotFile = app.publishedFilename || "";
5307
6439
  var shotUrl = app.published && shotOwner && shotFile && aimeatBase2 ? aimeatBase2 + "/v1/apps/" + encodeURIComponent(shotOwner) + "/" + encodeURIComponent(shotFile) + "/screenshot?t=" + Date.now() : "";
5308
6440
  var shotImg = shotUrl ? '<img src="' + escapeHtml(shotUrl) + `" alt="App screenshot" loading="lazy" onerror="this.style.display='none'" style="max-width:260px;max-height:160px;border-radius:8px;border:1px solid var(--border-subtle);object-fit:cover;object-position:top center;flex:none" />` : "";
5309
- var statusHtml = '<div class="dtl-section"><h3>' + t("detail.status") + '</h3><div style="display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap"><div style="flex:1;min-width:200px"><div class="dtl-status-row"><div class="dtl-stat"><span class="dtl-stat-label">' + t("detail.statusLocal") + '</span><span class="dtl-stat-val">' + (app.blob ? fmtSize(localBytes) : isUrlApp ? "URL" : "") + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + t("detail.statusPublished") + '</span><span class="dtl-stat-val">' + escapeHtml(publishedV) + '</span></div></div><span class="dtl-sync ' + syncClass + '">' + escapeHtml(syncText) + "</span></div>" + shotImg + "</div></div>";
6441
+ var statusHtml = '<div class="dtl-section"><h3>' + t("wc.lifecycle") + '</h3><div class="wc-row"><div class="wc-band"><div class="wc-stop ' + (wcState === "clean" ? "is-idle" : "is-active") + '"><span class="wc-stop-label">' + t("wc.title") + '</span><span class="wc-stop-val">' + escapeHtml(wcValue) + '</span><span class="wc-stop-note">' + t("wc.privateNote") + '</span></div><span class="wc-arrow" aria-hidden="true">→</span><div class="wc-stop ' + (app.published ? "is-live" : "is-idle") + '"><span class="wc-stop-label">' + t("wc.published") + '</span><span class="wc-stop-val">' + escapeHtml(app.published ? publishedV || "v?" : t("wc.notPublishedYet")) + '</span><span class="wc-stop-note">' + (app.published ? t("wc.visibleToOthers") : t("wc.notVisibleYet")) + "</span></div></div>" + shotImg + '</div><p class="wc-explain">' + escapeHtml(wcExplain) + "</p>" + // A saved working copy must never be a dead end: the same three verbs are available right
6442
+ // here, so you can try it, publish it, or throw it away without hunting through other menus.
6443
+ (wcState === "saved" && app.published && !isUrlApp ? '<div class="dtl-btn-row">' + dtlBtn(t("wc.try"), "window._launcher.detailWorkTry()") + dtlBtn(t("wc.publishAs").replace("{v}", String((app.publishedVersionNumber || 0) + 1)), "window._launcher.detailWorkPublish()", { variant: "success" }) + dtlBtn(t("wc.discardWork"), "window._launcher.detailWorkDiscard()") + "</div>" : "") + (app.blob && !isUrlApp ? '<p class="wc-size">' + escapeHtml(t("detail.size") + ": " + fmtSize(localBytes)) + "</p>" : "") + "</div>";
5310
6444
  var tags = app.tags && app.tags.length ? app.tags.join(", ") : "—";
5311
6445
  var cortex = app.usesCortex && app.usesCortex.length ? app.usesCortex.join(", ") : "—";
5312
6446
  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>" : "";
6447
+ var favBtn = "";
5314
6448
  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>";
6449
+ var descs = app.descriptions || {};
6450
+ var descEnVal = descs.en || (app.description || "");
6451
+ var descFiVal = descs.fi || "";
5315
6452
  var aboutBody;
5316
6453
  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>";
6454
+ 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
6455
  } 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.
6456
+ var shownDesc = app.descriptions && app.descriptions[getLang()] || app.description || "";
6457
+ 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
6458
  (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
6459
  }
5322
6460
  var aboutHtml = '<div class="dtl-section">' + aboutHeader + aboutBody + "</div>";
@@ -5324,13 +6462,14 @@
5324
6462
  if (isUrlApp) {
5325
6463
  aiHtml += '<span class="dtl-sync none">' + t("detail.urlCantEdit") + "</span>";
5326
6464
  } else {
5327
- aiHtml += '<div class="dtl-ai-row"><textarea id="detail-ai-input" placeholder="' + escapeHtml(t("detail.editAiPh")) + '"' + (detailAiAvailable ? "" : " disabled") + "></textarea>" + dtlBtn(t("detail.run"), "window._launcher.detailAiRun()", { variant: "primary", id: "detail-ai-run", disabled: !detailAiAvailable }) + '</div><div class="dtl-ai-status" id="detail-ai-status">' + (detailAiAvailable ? "" : escapeHtml(detailAiUnavailableMsg())) + '</div><div class="dtl-ai-draft" id="detail-ai-draft"' + (detailDraftBlob ? "" : " hidden") + '><div class="dtl-ai-status" style="margin:0 0 8px">' + t("detail.draftReady") + '</div><div class="dtl-btn-row">' + // PUBLISHED app the real-origin staging path (mic/camera work); the old opaque-
5328
- // sandbox "Test" is dropped here to avoid two near-identical "Test" buttons. An
5329
- // UNPUBLISHED app has no server draft slot yet, so it keeps the quick sandbox preview.
5330
- (app.published ? dtlBtn(t("detail.testLive"), "window._launcher.detailTestDraftLive()", { variant: "primary" }) + dtlBtn(t("detail.publishTested"), "window._launcher.detailPublishTestedDraft()", { variant: "success" }) : dtlBtn(t("detail.test"), "window._launcher.detailAiTest()", { variant: "primary" })) + dtlBtn(t("detail.keep"), "window._launcher.detailAiKeep()") + dtlBtn(t("detail.discard"), "window._launcher.detailAiDiscard()") + "</div></div>";
6465
+ aiHtml += '<div class="dtl-ai-row"><textarea id="detail-ai-input" placeholder="' + escapeHtml(t("detail.editAiPh")) + '"' + (detailAiAvailable ? "" : " disabled") + "></textarea>" + dtlBtn(t("detail.run"), "window._launcher.detailAiRun()", { variant: "primary", id: "detail-ai-run", disabled: !detailAiAvailable }) + '</div><div class="dtl-ai-status" id="detail-ai-status">' + (detailAiAvailable ? "" : escapeHtml(detailAiUnavailableMsg())) + '</div><div class="dtl-ai-draft" id="detail-ai-draft"' + (detailDraftBlob ? "" : " hidden") + '><div class="dtl-ai-status wc-proposal-head">' + t("detail.draftReady") + '</div><div class="dtl-btn-row">' + dtlBtn(t("wc.save"), "window._launcher.detailAiKeep()", { variant: "primary" }) + (app.published ? dtlBtn(t("wc.try"), "window._launcher.detailTestDraftLive()") + dtlBtn(t("wc.publishAs").replace("{v}", String((app.publishedVersionNumber || 0) + 1)), "window._launcher.detailPublishTestedDraft()", { variant: "success" }) : dtlBtn(t("wc.tryLocal"), "window._launcher.detailAiTest()")) + dtlBtn(t("wc.discardProposal"), "window._launcher.detailAiDiscard()") + '</div><p class="wc-verbs-hint">' + t("wc.verbsHint") + "</p></div>";
5331
6466
  }
5332
6467
  aiHtml += "</div>";
5333
- var versionsHtml = '<div class="dtl-section"><h3>' + t("detail.versions") + '</h3><div id="detail-versions-list">' + (detailVersionsHtml !== null ? detailVersionsHtml : !hasServer ? '<span class="dtl-sync none">' + t("detail.needServerVersions") + "</span>" : app.published ? '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.loadingVersions") + "</span>" : '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.noVersions") + "</span>") + "</div></div>";
6468
+ var historyHtml = "";
6469
+ if (!isUrlApp && app.published) {
6470
+ historyHtml = '<div class="dtl-section"><h3>' + t("wc.history") + '</h3><p class="dtl-desc">' + t("wc.historyHint") + '</p><div id="detail-checkpoints">' + (detailCheckpointsHtml !== null ? detailCheckpointsHtml : '<span class="wc-muted">…</span>') + "</div></div>";
6471
+ }
6472
+ var versionsHtml = '<div class="dtl-section"><h3>' + t("detail.versions") + '</h3><p class="dtl-desc">' + t("versions.publishedHint") + '</p><div id="detail-versions-list">' + (detailVersionsHtml !== null ? detailVersionsHtml : !hasServer ? '<span class="dtl-sync none">' + t("detail.needServerVersions") + "</span>" : app.published ? '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.loadingVersions") + "</span>" : '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.noVersions") + "</span>") + "</div></div>";
5334
6473
  var publishLabel = app.published && app.publishedVersionNumber ? t("detail.publishAs") + " v" + (app.publishedVersionNumber + 1) : t("detail.publish");
5335
6474
  var actionsHtml = '<div class="dtl-section"><h3>' + t("detail.actions") + '</h3><div class="dtl-btn-row">' + (isUrlApp ? "" : dtlBtn(t("ctx.viewSource"), "window._launcher.detailEditSource()")) + (isUrlApp ? "" : dtlBtn(t("ctx.improveAi"), "window._launcher.detailImproveExternal()")) + dtlBtn(t("ctx.sharePrompt"), "window._launcher.detailSharePrompt()") + (isUrlApp ? "" : dtlBtn(escapeHtml(publishLabel), "window._launcher.detailPublish()", { variant: "primary" })) + (app.published && !isUrlApp ? dtlBtn("📷 " + t("detail.setScreenshot"), "window._launcher.detailSetScreenshot()", { title: "Upload a custom thumbnail for this app" }) : "") + (app.published && !isUrlApp ? dtlBtn("🔄 " + t("detail.refreshScreenshot"), "window._launcher.detailRefreshScreenshot()", { title: "Clear the screenshot; the node re-takes it on its next scheduled run" }) : "") + dtlBtn(t("ctx.delete"), "window._launcher.detailDelete()", { variant: "danger" }) + "</div></div>";
5336
6475
  var mgmtHtml = '<div id="detail-server-mgmt">' + serverMgmtInner(app) + "</div>";
@@ -5341,7 +6480,10 @@
5341
6480
  // on each user-scope skill is rendered by detailLoadSkills into the list above.
5342
6481
  (ownPub ? '<div id="detail-skill-attach" style="margin-top:8px">' + detailSkillAttachInner() + "</div>" : "") + "</div>";
5343
6482
  }
6483
+ var odpsHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-odps">' + odpsSectionInner() + "</div>" : "";
5344
6484
  var monetizeHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-monetize">' + monetizeSectionInner() + "</div>" : "";
6485
+ var costHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-cost">' + costSectionInner() + "</div>" : "";
6486
+ var promoteHtml = app.published && detailIsOwnPublished(app) ? buildPromoteSection(app) : "";
5345
6487
  var agentsHtml = "";
5346
6488
  if (app.published) {
5347
6489
  var agOwner = detailServerOwner(app);
@@ -5356,7 +6498,151 @@
5356
6498
  ) + "</div>";
5357
6499
  }
5358
6500
  }
5359
- document.getElementById("detail-body").innerHTML = statusHtml + aboutHtml + aiHtml + versionsHtml + skillsHtml + agentsHtml + monetizeHtml + mgmtHtml + actionsHtml;
6501
+ document.getElementById("detail-body").innerHTML = statusHtml + aboutHtml + aiHtml + historyHtml + versionsHtml + skillsHtml + agentsHtml + odpsHtml + monetizeHtml + costHtml + promoteHtml + mgmtHtml + actionsHtml;
6502
+ }
6503
+ function detailCheckpointRows(app) {
6504
+ var owner = detailServerOwner(app);
6505
+ var list = getCheckpoints(owner, app.publishedFilename || "");
6506
+ if (!list.length) return '<span class="wc-muted">' + t("wc.none") + "</span>";
6507
+ var out = "";
6508
+ for (var i = 0; i < list.length; i++) {
6509
+ var c = list[i];
6510
+ var when = c.at ? new Date(c.at).toLocaleString() : "";
6511
+ var kb = c.size ? Math.round(c.size / 102.4) / 10 + " KB" : "";
6512
+ var note = c.note ? t("wc.before").replace("{note}", c.note) : t("wc.beforeUnnamed");
6513
+ out += '<div class="dtl-version-row"><div class="version-meta"><span class="wc-ckpt-when">' + escapeHtml(when) + '</span><span class="wc-ckpt-note">' + escapeHtml(note) + (kb ? " · " + kb : "") + '</span></div><div class="dtl-btn-row">' + dtlBtn(t("wc.preview"), "window._launcher.detailCheckpointPreview('" + jsArg(c.id) + "')") + dtlBtn(t("wc.restore"), "window._launcher.detailCheckpointRestore('" + jsArg(c.id) + "')", { variant: "primary", disabled: detailCheckpointBusy }) + dtlBtn(t("wc.delete"), "window._launcher.detailCheckpointDelete('" + jsArg(c.id) + "')", { disabled: detailCheckpointBusy }) + "</div></div>";
6514
+ }
6515
+ return out;
6516
+ }
6517
+ function refreshCheckpoints() {
6518
+ var app = detailGetApp();
6519
+ if (!app) return;
6520
+ detailCheckpointsHtml = detailCheckpointRows(app);
6521
+ var el = document.getElementById("detail-checkpoints");
6522
+ if (el) el.innerHTML = detailCheckpointsHtml;
6523
+ }
6524
+ function detailLoadWorkingCopy(app, owner) {
6525
+ var epoch = detailEpoch;
6526
+ getDraft(owner, app.publishedFilename).then(function(d) {
6527
+ if (!d || !d.content || detailAppId !== app.id || epoch !== detailEpoch) return;
6528
+ app.blob = d.content;
6529
+ if (d.updated_at) detailWorkSavedAt = d.updated_at;
6530
+ saveApp(app).then(function() {
6531
+ if (detailAppId === app.id) renderDetailView();
6532
+ });
6533
+ });
6534
+ }
6535
+ function detailLoadCheckpoints(owner, filename) {
6536
+ loadCheckpoints(owner, filename).then(function() {
6537
+ if (detailAppId) refreshCheckpoints();
6538
+ }).catch(function() {
6539
+ if (detailAppId) refreshCheckpoints();
6540
+ });
6541
+ }
6542
+ function detailCheckpointPreview(id) {
6543
+ var app = detailGetApp();
6544
+ if (!app) return;
6545
+ readCheckpoint(detailServerOwner(app), app.publishedFilename || "", id).then(function(b64) {
6546
+ if (!b64) {
6547
+ showNotice(t("wc.gone"));
6548
+ return;
6549
+ }
6550
+ var view = document.getElementById("iframe-view");
6551
+ var iframe = document.getElementById("app-iframe");
6552
+ document.getElementById("iframe-title").textContent = (app.name || "App") + " — " + t("wc.previewTitle");
6553
+ iframe.removeAttribute("src");
6554
+ iframe.srcdoc = blobToHtml(b64);
6555
+ setIframeUrl("");
6556
+ delete iframe.dataset.appId;
6557
+ view.hidden = false;
6558
+ });
6559
+ }
6560
+ async function detailCheckpointRestore(id) {
6561
+ var app = detailGetApp();
6562
+ if (!app || detailCheckpointBusy) return;
6563
+ if (!getCortexOwnerToken2()) {
6564
+ showNotice(t("wc.loginNeeded"));
6565
+ return;
6566
+ }
6567
+ if (!await showConfirm(t("wc.confirmRestore"))) return;
6568
+ var owner = detailServerOwner(app);
6569
+ var filename = app.publishedFilename || "";
6570
+ detailCheckpointBusy = true;
6571
+ refreshCheckpoints();
6572
+ readCheckpoint(owner, filename, id).then(function(b64) {
6573
+ if (!b64) throw new Error(t("wc.gone"));
6574
+ return saveWorkingCopy({
6575
+ owner,
6576
+ filename,
6577
+ previousB64: app.blob,
6578
+ nextB64: b64,
6579
+ note: t("wc.noteRestore")
6580
+ }).then(function() {
6581
+ return b64;
6582
+ });
6583
+ }).then(function(b64) {
6584
+ app.blob = b64;
6585
+ detailCheckpointBusy = false;
6586
+ detailHasWorkCopy = true;
6587
+ detailWorkSavedAt = (/* @__PURE__ */ new Date()).toISOString();
6588
+ return saveApp(app).then(function() {
6589
+ renderDetailView();
6590
+ refreshCheckpoints();
6591
+ showNotice(t("wc.restored"));
6592
+ });
6593
+ }).catch(function(err) {
6594
+ detailCheckpointBusy = false;
6595
+ refreshCheckpoints();
6596
+ showNotice(err && err.message || t("wc.saveFailed"));
6597
+ });
6598
+ }
6599
+ async function detailCheckpointDelete(id) {
6600
+ var app = detailGetApp();
6601
+ if (!app || detailCheckpointBusy) return;
6602
+ if (!await showConfirm(t("wc.confirmDelete"))) return;
6603
+ detailCheckpointBusy = true;
6604
+ refreshCheckpoints();
6605
+ deleteCheckpoint(detailServerOwner(app), app.publishedFilename || "", id).catch(function() {
6606
+ }).then(function() {
6607
+ detailCheckpointBusy = false;
6608
+ refreshCheckpoints();
6609
+ });
6610
+ }
6611
+ function promoteRef(app) {
6612
+ return detailServerOwner(app) + "/" + (app.publishedFilename || "");
6613
+ }
6614
+ function buildPromoteSection(app) {
6615
+ var ref = promoteRef(app);
6616
+ var cur = getPromotion(ref) || {};
6617
+ var on = !!(cur.en || cur.fi);
6618
+ 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>';
6619
+ }
6620
+ function detailPromoteSave() {
6621
+ var app = detailGetApp();
6622
+ if (!app || !detailIsOwnPublished(app)) return;
6623
+ var en = (document.getElementById("detail-promo-en") || {}).value || "";
6624
+ var fi = (document.getElementById("detail-promo-fi") || {}).value || "";
6625
+ var statusEl = document.getElementById("detail-promo-status");
6626
+ if (statusEl) {
6627
+ statusEl.style.color = "var(--text-muted)";
6628
+ statusEl.textContent = t("promote.saving");
6629
+ }
6630
+ setPromotion(promoteRef(app), { en, fi }).then(function(nowOn) {
6631
+ renderApps();
6632
+ renderDetailView();
6633
+ var s2 = document.getElementById("detail-promo-status");
6634
+ if (s2) {
6635
+ s2.style.color = "#34d399";
6636
+ s2.textContent = nowOn ? "✔ " + t("promote.saved") : "✔ " + t("promote.removed");
6637
+ }
6638
+ });
6639
+ }
6640
+ function detailPromoteClear() {
6641
+ var en = document.getElementById("detail-promo-en");
6642
+ if (en) en.value = "";
6643
+ var fi = document.getElementById("detail-promo-fi");
6644
+ if (fi) fi.value = "";
6645
+ detailPromoteSave();
5360
6646
  }
5361
6647
  function detailLoadSkills(owner, filename) {
5362
6648
  var config = loadConfig();
@@ -5419,9 +6705,9 @@
5419
6705
  showNotice(t("common.loginRequired") || "You must be logged in. Sign in first.");
5420
6706
  return;
5421
6707
  }
5422
- var input = document.getElementById("detail-ac-input");
6708
+ var input2 = document.getElementById("detail-ac-input");
5423
6709
  var statusEl = document.getElementById("detail-ac-status");
5424
- var code = input ? input.value.trim() : "";
6710
+ var code = input2 ? input2.value.trim() : "";
5425
6711
  if (code && (code.length < 4 || code.length > 64)) {
5426
6712
  if (statusEl) {
5427
6713
  statusEl.style.color = "var(--accent)";
@@ -5562,33 +6848,33 @@
5562
6848
  }
5563
6849
  function skillAuthHeaders(withJson) {
5564
6850
  var h = {};
5565
- var tok = getCortexOwnerToken2();
5566
- if (tok) h["Authorization"] = "Bearer " + tok;
6851
+ var tok2 = getCortexOwnerToken2();
6852
+ if (tok2) h["Authorization"] = "Bearer " + tok2;
5567
6853
  if (withJson) h["Content-Type"] = "application/json";
5568
6854
  return h;
5569
6855
  }
5570
6856
  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) {
6857
+ var base4 = skillApiBase();
6858
+ if (!base4) return Promise.reject(new Error("No server configured"));
6859
+ return fetch(base4 + "/v1/skills?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5574
6860
  return r.json();
5575
6861
  }).then(function(j) {
5576
6862
  return j.data && j.data.skills ? j.data.skills : [];
5577
6863
  });
5578
6864
  }
5579
6865
  function skillGetMine(name) {
5580
- var base = skillApiBase();
5581
- return fetch(base + "/v1/skills/" + encodeURIComponent(name) + "?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
6866
+ var base4 = skillApiBase();
6867
+ return fetch(base4 + "/v1/skills/" + encodeURIComponent(name) + "?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5582
6868
  return r.json();
5583
6869
  }).then(function(j) {
5584
6870
  return j.data && j.data.skill || null;
5585
6871
  });
5586
6872
  }
5587
6873
  function skillPublish(skillMd, files) {
5588
- var base = skillApiBase();
6874
+ var base4 = skillApiBase();
5589
6875
  var body = { skill_md: skillMd };
5590
6876
  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) {
6877
+ return fetch(base4 + "/v1/skills", { method: "POST", headers: skillAuthHeaders(true), body: JSON.stringify(body) }).then(function(r) {
5592
6878
  return r.json().then(function(j) {
5593
6879
  if (!j.ok) throw new Error(j.error && (j.error.message || j.error.code) || "HTTP " + r.status);
5594
6880
  return j.data && j.data.skill || null;
@@ -5646,11 +6932,17 @@
5646
6932
  var app = detailGetApp();
5647
6933
  if (!app) return;
5648
6934
  var nameEl = document.getElementById("detail-name-input");
5649
- var descEl = document.getElementById("detail-desc-input");
6935
+ var enEl = document.getElementById("detail-desc-en");
6936
+ var fiEl = document.getElementById("detail-desc-fi");
5650
6937
  var iconEl = document.getElementById("detail-icon-input");
5651
6938
  var tagsEl = document.getElementById("detail-tags-input");
5652
6939
  var newName = nameEl ? nameEl.value.trim() : "";
5653
- var newDesc = descEl ? descEl.value.trim() : "";
6940
+ var enDesc = enEl ? enEl.value.trim() : "";
6941
+ var fiDesc = fiEl ? fiEl.value.trim() : "";
6942
+ var newDescriptions = {};
6943
+ if (enDesc) newDescriptions.en = enDesc;
6944
+ if (fiDesc) newDescriptions.fi = fiDesc;
6945
+ var newDesc = enDesc || fiDesc || "";
5654
6946
  var newIcon = iconEl ? iconEl.value.trim() : app.icon || "";
5655
6947
  var newTags = tagsEl ? tagsEl.value.split(",").map(function(s) {
5656
6948
  return s.trim();
@@ -5662,12 +6954,13 @@
5662
6954
  }
5663
6955
  if (app.published && !newDesc) {
5664
6956
  showNotice(t("detail.descRequired") || "Description cannot be empty.");
5665
- if (descEl) descEl.focus();
6957
+ if (enEl) enEl.focus();
5666
6958
  return;
5667
6959
  }
5668
6960
  function finishLocal() {
5669
6961
  app.name = newName;
5670
6962
  app.description = newDesc;
6963
+ app.descriptions = newDescriptions;
5671
6964
  app.icon = newIcon;
5672
6965
  app.tags = newTags;
5673
6966
  saveApp(app).then(function() {
@@ -5693,7 +6986,7 @@
5693
6986
  fetch(aimeatUrl + "/v1/apps/" + encodeURIComponent(filename), {
5694
6987
  method: "PATCH",
5695
6988
  headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5696
- body: JSON.stringify({ name: newName, description: newDesc })
6989
+ body: JSON.stringify({ name: newName, description: newDesc, descriptions: newDescriptions })
5697
6990
  }).then(function(resp) {
5698
6991
  return resp.json().then(function(j) {
5699
6992
  return { ok: resp.ok, j };
@@ -5704,6 +6997,7 @@
5704
6997
  getServerManifests2()[key] = getServerManifests2()[key] || {};
5705
6998
  getServerManifests2()[key].name = newName;
5706
6999
  getServerManifests2()[key].description = newDesc;
7000
+ getServerManifests2()[key].descriptions = newDescriptions;
5707
7001
  finishLocal();
5708
7002
  } else {
5709
7003
  showNotice("Failed: " + (res.j && res.j.error && res.j.error.message || "Unknown error"));
@@ -5712,6 +7006,69 @@
5712
7006
  showNotice("Error: " + (err.message || err));
5713
7007
  });
5714
7008
  }
7009
+ function detailTranslateDesc(srcLang, dstLang, prefix, statusId) {
7010
+ prefix = prefix || "detail-desc-";
7011
+ var srcEl = document.getElementById(prefix + srcLang);
7012
+ var dstEl = document.getElementById(prefix + dstLang);
7013
+ var statusEl = document.getElementById(statusId || "detail-tr-status");
7014
+ if (!srcEl || !dstEl) return;
7015
+ var text = (srcEl.value || "").trim();
7016
+ if (!text) {
7017
+ if (statusEl) statusEl.textContent = t("detail.trNeedSource");
7018
+ return;
7019
+ }
7020
+ var token = getCortexOwnerToken2();
7021
+ if (!token) {
7022
+ if (statusEl) statusEl.textContent = t("detail.aiLoginNeeded");
7023
+ return;
7024
+ }
7025
+ var config = loadConfig();
7026
+ var aimeatUrl = (config.aimeatUrl || "").replace(/\/+$/, "");
7027
+ if (!aimeatUrl) {
7028
+ if (statusEl) statusEl.textContent = t("detail.aiUnavailable");
7029
+ return;
7030
+ }
7031
+ var langName = { en: "English", fi: "Finnish" };
7032
+ if (statusEl) {
7033
+ statusEl.style.color = "var(--text-muted)";
7034
+ statusEl.textContent = t("detail.translating");
7035
+ }
7036
+ 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.";
7037
+ fetch(aimeatUrl + "/v1/ai/complete", {
7038
+ method: "POST",
7039
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
7040
+ body: JSON.stringify({ prompt: text, systemPrompt, app_id: "app-catalog" })
7041
+ }).then(function(resp) {
7042
+ return resp.json();
7043
+ }).then(function(json) {
7044
+ if (!json || !json.ok) {
7045
+ var msg = json && json.error && json.error.message || "Translation failed";
7046
+ if (statusEl) {
7047
+ statusEl.style.color = "var(--accent)";
7048
+ statusEl.textContent = "✘ " + msg;
7049
+ }
7050
+ return;
7051
+ }
7052
+ var out = (json.data && json.data.content ? json.data.content : "").trim();
7053
+ if (!out) {
7054
+ if (statusEl) {
7055
+ statusEl.style.color = "var(--accent)";
7056
+ statusEl.textContent = "✘ " + t("detail.trEmpty");
7057
+ }
7058
+ return;
7059
+ }
7060
+ dstEl.value = out;
7061
+ if (statusEl) {
7062
+ statusEl.style.color = "#34d399";
7063
+ statusEl.textContent = "✔ " + t("detail.trDone");
7064
+ }
7065
+ }).catch(function(err) {
7066
+ if (statusEl) {
7067
+ statusEl.style.color = "var(--accent)";
7068
+ statusEl.textContent = "✘ " + (err.message || "Translation failed");
7069
+ }
7070
+ });
7071
+ }
5715
7072
  function detailSetScreenshot() {
5716
7073
  var app = detailGetApp();
5717
7074
  if (!app) return;
@@ -5737,11 +7094,11 @@
5737
7094
  showNotice("No server configured.");
5738
7095
  return;
5739
7096
  }
5740
- var input = document.createElement("input");
5741
- input.type = "file";
5742
- input.accept = "image/*";
5743
- input.onchange = function() {
5744
- var file = input.files && input.files[0];
7097
+ var input2 = document.createElement("input");
7098
+ input2.type = "file";
7099
+ input2.accept = "image/*";
7100
+ input2.onchange = function() {
7101
+ var file = input2.files && input2.files[0];
5745
7102
  if (!file) return;
5746
7103
  if (file.size > 2 * 1024 * 1024) {
5747
7104
  showNotice("Image too large (max 2 MB).");
@@ -5771,7 +7128,7 @@
5771
7128
  };
5772
7129
  reader.readAsDataURL(file);
5773
7130
  };
5774
- input.click();
7131
+ input2.click();
5775
7132
  }
5776
7133
  async function detailRefreshScreenshot() {
5777
7134
  var app = detailGetApp();
@@ -5816,8 +7173,8 @@
5816
7173
  showNotice("Failed: " + e.message);
5817
7174
  });
5818
7175
  }
5819
- function metaItem(label, val) {
5820
- return '<div class="dtl-meta-item"><div class="dtl-meta-label">' + escapeHtml(label) + '</div><div class="dtl-meta-val">' + escapeHtml(val || "—") + "</div></div>";
7176
+ function metaItem(label, val2) {
7177
+ return '<div class="dtl-meta-item"><div class="dtl-meta-label">' + escapeHtml(label) + '</div><div class="dtl-meta-val">' + escapeHtml(val2 || "—") + "</div></div>";
5821
7178
  }
5822
7179
  function detailAiUnavailableMsg() {
5823
7180
  return getCortexOwnerToken2() ? t("detail.aiUnavailable") : t("detail.aiLoginNeeded");
@@ -5950,6 +7307,7 @@
5950
7307
  return;
5951
7308
  }
5952
7309
  detailDraftBlob = htmlToBlob(newHtml);
7310
+ detailLastChangeNote = change;
5953
7311
  statusEl.style.color = "#34d399";
5954
7312
  var usage = json.data && json.data.budget && typeof json.data.budget.spent_today_usd !== "undefined" ? " · " + t("detail.aiUsage") + ": $" + Number(json.data.budget.spent_today_usd).toFixed(3) : "";
5955
7313
  statusEl.textContent = "✔ " + t("detail.draftReady") + usage;
@@ -5981,17 +7339,55 @@
5981
7339
  function detailAiKeep() {
5982
7340
  var app = detailGetApp();
5983
7341
  if (!app || !detailDraftBlob) return;
5984
- app.blob = detailDraftBlob;
5985
- app.source = app.source === "url" ? "paste" : app.source || "paste";
5986
- app.url = app.url || null;
5987
- detailDraftBlob = null;
5988
- saveApp(app).then(function() {
5989
- renderApps();
5990
- renderDetailView();
7342
+ var next = detailDraftBlob;
7343
+ var prev = app.blob;
7344
+ var statusEl = document.getElementById("detail-ai-status");
7345
+ function finishLocal() {
7346
+ app.blob = next;
7347
+ app.source = app.source === "url" ? "paste" : app.source || "paste";
7348
+ app.url = app.url || null;
7349
+ detailDraftBlob = null;
7350
+ return saveApp(app).then(function() {
7351
+ renderApps();
7352
+ renderDetailView();
7353
+ });
7354
+ }
7355
+ if (!app.published || !app.publishedFilename || !getCortexOwnerToken2()) {
7356
+ finishLocal().then(function() {
7357
+ var s2 = document.getElementById("detail-ai-status");
7358
+ if (s2) {
7359
+ s2.style.color = "var(--text-muted)";
7360
+ s2.textContent = t("wc.keptLocalOnly");
7361
+ }
7362
+ });
7363
+ return;
7364
+ }
7365
+ if (statusEl) {
7366
+ statusEl.style.color = "var(--text-muted)";
7367
+ statusEl.textContent = t("wc.saving");
7368
+ }
7369
+ saveWorkingCopy({
7370
+ owner: detailServerOwner(app),
7371
+ filename: app.publishedFilename,
7372
+ previousB64: prev,
7373
+ nextB64: next,
7374
+ note: detailLastChangeNote
7375
+ }).then(function() {
7376
+ detailHasWorkCopy = true;
7377
+ detailWorkSavedAt = (/* @__PURE__ */ new Date()).toISOString();
7378
+ return finishLocal();
7379
+ }).then(function() {
7380
+ refreshCheckpoints();
5991
7381
  var s2 = document.getElementById("detail-ai-status");
5992
7382
  if (s2) {
5993
7383
  s2.style.color = "#34d399";
5994
- s2.textContent = "✔ " + t("detail.kept");
7384
+ s2.textContent = "✔ " + t("wc.saved");
7385
+ }
7386
+ }).catch(function(err) {
7387
+ var s2 = document.getElementById("detail-ai-status");
7388
+ if (s2) {
7389
+ s2.style.color = "var(--accent)";
7390
+ s2.textContent = "✘ " + (err && err.message || t("wc.saveFailed"));
5995
7391
  }
5996
7392
  });
5997
7393
  }
@@ -6004,9 +7400,9 @@
6004
7400
  function draftApi(method, owner, filename, sub, body) {
6005
7401
  var cfg = loadConfig();
6006
7402
  if (!cfg.aimeatUrl) return Promise.reject(new Error("Set the AIMEAT server URL in Settings first"));
6007
- var base = cfg.aimeatUrl.replace(/\/+$/, "");
7403
+ var base4 = cfg.aimeatUrl.replace(/\/+$/, "");
6008
7404
  var path = "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/" + sub;
6009
- return fetch(base + path, {
7405
+ return fetch(base4 + path, {
6010
7406
  method,
6011
7407
  headers: { "Authorization": "Bearer " + getCortexOwnerToken2(), "Content-Type": "application/json" },
6012
7408
  body: body ? JSON.stringify(body) : void 0
@@ -6098,6 +7494,55 @@
6098
7494
  });
6099
7495
  });
6100
7496
  }
7497
+ function detailWorkTry() {
7498
+ var app = detailGetApp();
7499
+ if (!app || !app.publishedFilename) return;
7500
+ openStagingPreview(detailServerOwner(app), app.publishedFilename);
7501
+ }
7502
+ async function detailWorkPublish() {
7503
+ var app = detailGetApp();
7504
+ if (!app || !app.publishedFilename) return;
7505
+ if (!getCortexOwnerToken2()) {
7506
+ showNotice(t("wc.loginNeeded"));
7507
+ return;
7508
+ }
7509
+ if (!await showConfirm(t("detail.draftPublishConfirm"))) return;
7510
+ var owner = detailServerOwner(app);
7511
+ draftApi("POST", owner, app.publishedFilename, "publish-draft").then(function(data) {
7512
+ detailEpoch++;
7513
+ detailHasWorkCopy = false;
7514
+ detailWorkSavedAt = null;
7515
+ app.publishedVersionNumber = data.version_number || app.publishedVersionNumber;
7516
+ showNotice(t("detail.draftPublished").replace("{v}", data.version_number));
7517
+ renderDetailView();
7518
+ detailLoadVersions(owner, app.publishedFilename);
7519
+ refreshAll();
7520
+ }).catch(function(err) {
7521
+ showNotice("✘ " + (err && err.message || "Publish failed"));
7522
+ });
7523
+ }
7524
+ async function detailWorkDiscard() {
7525
+ var app = detailGetApp();
7526
+ if (!app || !app.publishedFilename) return;
7527
+ if (!await showConfirm(t("wc.confirmDiscardWork"))) return;
7528
+ var owner = detailServerOwner(app);
7529
+ var cfg = loadConfig();
7530
+ var aimeatUrl = (cfg.aimeatUrl || "").replace(/\/+$/, "");
7531
+ discardWorkingCopy(owner, app.publishedFilename).then(function() {
7532
+ return fetchAppContentBase64(aimeatUrl, owner, app.publishedFilename);
7533
+ }).then(function(b64) {
7534
+ app.blob = b64;
7535
+ return saveApp(app);
7536
+ }).catch(function() {
7537
+ }).then(function() {
7538
+ detailEpoch++;
7539
+ detailHasWorkCopy = false;
7540
+ detailWorkSavedAt = null;
7541
+ renderDetailView();
7542
+ refreshAll();
7543
+ showNotice(t("wc.discardedWork"));
7544
+ });
7545
+ }
6101
7546
  function detailTestDraftLive() {
6102
7547
  var app = detailGetApp();
6103
7548
  if (!app || !detailDraftBlob) return;
@@ -6106,10 +7551,16 @@
6106
7551
  function detailPublishTestedDraft() {
6107
7552
  var app = detailGetApp();
6108
7553
  if (!app || !detailDraftBlob) return;
6109
- publishDraftBytes(app, detailDraftBlob, document.getElementById("detail-ai-status"), function() {
7554
+ publishDraftBytes(app, detailDraftBlob, document.getElementById("detail-ai-status"), function(data) {
7555
+ detailEpoch++;
7556
+ app.blob = detailDraftBlob;
6110
7557
  detailDraftBlob = null;
6111
- refreshAll();
7558
+ detailHasWorkCopy = false;
7559
+ detailWorkSavedAt = null;
7560
+ if (data && data.version_number) app.publishedVersionNumber = data.version_number;
7561
+ saveApp(app);
6112
7562
  renderDetailView();
7563
+ refreshAll();
6113
7564
  });
6114
7565
  }
6115
7566
  function sourceOverlayApp() {
@@ -6129,6 +7580,60 @@
6129
7580
  return null;
6130
7581
  }
6131
7582
  }
7583
+ function saveSourceAsWorkingCopy() {
7584
+ var app = sourceOverlayApp();
7585
+ var b64 = sourceCurrentB64();
7586
+ if (!app || b64 == null) return Promise.reject(new Error("No app"));
7587
+ var statusEl = document.getElementById("source-draft-status");
7588
+ var prev = app.blob;
7589
+ var isOpenInDetail = detailAppId === app.id;
7590
+ function localOnly() {
7591
+ app.blob = b64;
7592
+ return saveApp(app).then(function() {
7593
+ renderApps();
7594
+ });
7595
+ }
7596
+ if (!app.published || !app.publishedFilename || !getCortexOwnerToken2()) {
7597
+ return localOnly().then(function() {
7598
+ if (statusEl) {
7599
+ statusEl.style.color = "var(--text-muted)";
7600
+ statusEl.textContent = t("wc.keptLocalOnly");
7601
+ }
7602
+ return { persisted: false };
7603
+ });
7604
+ }
7605
+ if (statusEl) {
7606
+ statusEl.style.color = "var(--text-muted)";
7607
+ statusEl.textContent = t("wc.saving");
7608
+ }
7609
+ return saveWorkingCopy({
7610
+ owner: detailServerOwner(app),
7611
+ filename: app.publishedFilename,
7612
+ previousB64: prev,
7613
+ nextB64: b64,
7614
+ note: t("wc.noteManual")
7615
+ }).then(function() {
7616
+ return localOnly();
7617
+ }).then(function() {
7618
+ if (isOpenInDetail) {
7619
+ detailHasWorkCopy = true;
7620
+ detailWorkSavedAt = (/* @__PURE__ */ new Date()).toISOString();
7621
+ renderDetailView();
7622
+ refreshCheckpoints();
7623
+ }
7624
+ if (statusEl) {
7625
+ statusEl.style.color = "#34d399";
7626
+ statusEl.textContent = "✔ " + t("wc.saved");
7627
+ }
7628
+ return { persisted: true };
7629
+ }).catch(function(err) {
7630
+ if (statusEl) {
7631
+ statusEl.style.color = "var(--accent)";
7632
+ statusEl.textContent = "✘ " + (err && err.message || t("wc.saveFailed"));
7633
+ }
7634
+ throw err;
7635
+ });
7636
+ }
6132
7637
  function sourceTestDraftLive() {
6133
7638
  var app = sourceOverlayApp();
6134
7639
  var c = sourceCurrentB64();
@@ -6196,6 +7701,7 @@
6196
7701
  id: generateId2(),
6197
7702
  name: meta.name || filename.replace(/\.html?$/i, ""),
6198
7703
  description: meta.description || "",
7704
+ descriptions: meta.descriptions || null,
6199
7705
  category: meta.category || "utility",
6200
7706
  tags: meta.tags || [],
6201
7707
  usesCortex: meta.usesCortex || [],
@@ -6429,6 +7935,7 @@
6429
7935
  mime_type: "text/html",
6430
7936
  name: meta.name || filename.replace(/\.html?$/i, ""),
6431
7937
  description: meta.description || "",
7938
+ descriptions: meta.descriptions || null,
6432
7939
  category: meta.category || "utility",
6433
7940
  tags: meta.tags || [],
6434
7941
  uses_cortex: meta.usesCortex || []
@@ -6464,8 +7971,8 @@
6464
7971
  showNotice(t("common.loginRequired") || "You must be logged in to fork an app into your own catalogue. Sign in first.");
6465
7972
  return;
6466
7973
  }
6467
- var base = (filename || "app").replace(/\.html?$/i, "");
6468
- var suggested = base + "-fork.html";
7974
+ var base4 = (filename || "app").replace(/\.html?$/i, "");
7975
+ var suggested = base4 + "-fork.html";
6469
7976
  var newName = prompt(t("fork.prompt") || 'Fork "' + filename + '" into a new app.\n\nNew filename:', suggested);
6470
7977
  if (!newName) return;
6471
7978
  newName = newName.trim();
@@ -6514,11 +8021,7 @@
6514
8021
  }
6515
8022
 
6516
8023
  // 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);
8024
+ function initSettings() {
6522
8025
  }
6523
8026
  function applyTheme(theme) {
6524
8027
  if (theme === "dark") {
@@ -6651,274 +8154,50 @@
6651
8154
  }
6652
8155
  if (changed) {
6653
8156
  saveConfig(local);
6654
- applyTheme(local.theme);
6655
- }
6656
- }).catch(function() {
6657
- });
6658
- }
6659
- function closeSettings() {
6660
- document.getElementById("settings-overlay").hidden = true;
6661
- }
6662
- function openHelp() {
6663
- document.getElementById("help-overlay").hidden = false;
6664
- }
6665
- function closeHelp() {
6666
- document.getElementById("help-overlay").hidden = true;
6667
- }
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
- });
8157
+ applyTheme(local.theme);
8158
+ }
8159
+ }).catch(function() {
6806
8160
  });
6807
8161
  }
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
- });
8162
+ function closeSettings() {
8163
+ document.getElementById("settings-overlay").hidden = true;
8164
+ }
8165
+ function openHelp() {
8166
+ document.getElementById("help-overlay").hidden = false;
8167
+ }
8168
+ function closeHelp() {
8169
+ document.getElementById("help-overlay").hidden = true;
6829
8170
  }
6830
8171
 
6831
8172
  // src/static/app-catalog/js/apps-io.js
6832
- var generateId4;
8173
+ var generateId3;
6833
8174
  var readFileAsText2;
6834
- var renderApps3;
8175
+ var renderApps2;
6835
8176
  var getMainApps2;
8177
+ var showPublishModal2;
6836
8178
  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
- });
8179
+ ({ generateId: generateId3, readFileAsText: readFileAsText2, renderApps: renderApps2, getMainApps: getMainApps2, showPublishModal: showPublishModal2 } = deps);
6903
8180
  }
6904
- function addAppFromSource(name, sourceCode, icon, tags, openMode) {
6905
- var encoded = btoa(unescape(encodeURIComponent(sourceCode)));
8181
+ function createAppFromHtml(name, html, icon, tags, description) {
8182
+ var encoded = btoa(unescape(encodeURIComponent(html)));
6906
8183
  var app = {
6907
- id: generateId4(),
8184
+ id: generateId3(),
6908
8185
  name,
6909
- description: parseAppMeta(sourceCode).description || "",
6910
- source: "local",
8186
+ description: description || "",
8187
+ source: "create",
6911
8188
  url: null,
6912
8189
  blob: encoded,
6913
- tags,
6914
- openMode,
8190
+ tags: tags || [],
6915
8191
  icon: icon || "📝",
6916
- screenshot: null,
6917
- favorite: false,
6918
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
6919
- lastOpenedAt: null
8192
+ published: false,
8193
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
6920
8194
  };
6921
8195
  return saveApp(app).then(function() {
8196
+ try {
8197
+ getMainApps2().push(app);
8198
+ } catch (e) {
8199
+ }
8200
+ if (typeof showPublishModal2 === "function") showPublishModal2(app.id, { unlisted: true });
6922
8201
  return app;
6923
8202
  });
6924
8203
  }
@@ -6927,7 +8206,7 @@
6927
8206
  function showModal() {
6928
8207
  document.getElementById("modal-overlay").hidden = false;
6929
8208
  document.getElementById("modal-title").textContent = t("addModal.title");
6930
- switchTab("url");
8209
+ switchTab("paste");
6931
8210
  }
6932
8211
  function requireSignInThen(next) {
6933
8212
  if (getCortexOwnerToken()) {
@@ -6975,7 +8254,6 @@
6975
8254
  }
6976
8255
  function closeModal() {
6977
8256
  document.getElementById("modal-overlay").hidden = true;
6978
- document.getElementById("app-url").value = "";
6979
8257
  document.getElementById("app-name").value = "";
6980
8258
  document.getElementById("app-icon").value = "";
6981
8259
  document.getElementById("app-tags").value = "";
@@ -7029,7 +8307,6 @@
7029
8307
  var name = document.getElementById("app-name").value.trim();
7030
8308
  var icon = document.getElementById("app-icon").value.trim();
7031
8309
  var tagsRaw = document.getElementById("app-tags").value.trim();
7032
- var openMode = "tab";
7033
8310
  var tags = tagsRaw ? tagsRaw.split(",").map(function(t2) {
7034
8311
  return t2.trim();
7035
8312
  }).filter(Boolean) : [];
@@ -7052,76 +8329,110 @@
7052
8329
  app.name = name;
7053
8330
  app.icon = icon || app.icon;
7054
8331
  app.tags = tags;
7055
- app.openMode = openMode;
7056
8332
  saveApp(app).then(function() {
7057
8333
  closeModal();
7058
- renderApps3();
8334
+ renderApps2();
7059
8335
  });
7060
8336
  return;
7061
8337
  }
7062
8338
  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") {
8339
+ var tabName = activeTab ? activeTab.getAttribute("data-tab") : "paste";
8340
+ if (tabName === "paste") {
7082
8341
  var pastedCode = document.getElementById("app-paste-code").value.trim();
7083
8342
  if (!pastedCode) {
7084
8343
  showNotice("Please paste your HTML source code");
7085
8344
  return;
7086
8345
  }
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
- });
8346
+ var meta = parseAppMeta(pastedCode);
8347
+ if (!name) name = meta.name || "Pasted App";
8348
+ closeModal();
8349
+ createAppFromHtml(name, pastedCode, icon, tags, meta.description);
7095
8350
  } else if (tabName === "file") {
7096
8351
  if (!selectedFile) {
7097
8352
  showNotice("Please select a file");
7098
8353
  return;
7099
8354
  }
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() {
8355
+ var file = selectedFile;
8356
+ if (file.name.toLowerCase().endsWith(".zip")) {
8357
+ if (!name) name = file.name.replace(/\.zip$/i, "");
8358
+ file.arrayBuffer().then(function(arrayBuffer) {
8359
+ return extractZip(arrayBuffer);
8360
+ }).then(function(files) {
8361
+ return bundleZip(files);
8362
+ }).then(function(html) {
7107
8363
  closeModal();
7108
- renderApps3();
8364
+ return createAppFromHtml(name, html, icon, tags, parseAppMeta(html).description);
7109
8365
  }).catch(function(err) {
7110
8366
  showNotice("ZIP import failed: " + (err.message || err));
7111
8367
  });
7112
8368
  } else {
7113
- if (!name) {
7114
- name = selectedFile.name.replace(/\.html?$/i, "");
7115
- }
7116
- addAppFromFile(name, selectedFile, icon, tags, openMode).then(function() {
8369
+ if (!name) name = file.name.replace(/\.html?$/i, "");
8370
+ readFileAsText2(file).then(function(html) {
7117
8371
  closeModal();
7118
- renderApps3();
8372
+ createAppFromHtml(name, html, icon, tags, parseAppMeta(html).description);
7119
8373
  });
7120
8374
  }
7121
8375
  }
7122
8376
  }
7123
- function setEditingAppId(v) {
7124
- editingAppId = v;
8377
+
8378
+ // src/static/app-catalog/js/favorites.js
8379
+ var KEY2 = "app-catalog.favorites";
8380
+ var favSet = /* @__PURE__ */ new Set();
8381
+ function base3() {
8382
+ return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
8383
+ }
8384
+ function isFavorite(ref) {
8385
+ return favSet.has(ref);
8386
+ }
8387
+ function loadFavorites() {
8388
+ var token = getCortexOwnerToken();
8389
+ var b = base3();
8390
+ if (!token || !b) {
8391
+ favSet = /* @__PURE__ */ new Set();
8392
+ return Promise.resolve();
8393
+ }
8394
+ return fetch(b + "/v1/memory/" + encodeURIComponent(KEY2), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
8395
+ return r.ok ? r.json() : null;
8396
+ }).then(function(j) {
8397
+ var refs = j && j.data && j.data.value && Array.isArray(j.data.value.refs) ? j.data.value.refs : [];
8398
+ favSet = new Set(refs.filter(function(x) {
8399
+ return typeof x === "string";
8400
+ }));
8401
+ }).catch(function() {
8402
+ });
8403
+ }
8404
+ function persist2() {
8405
+ var token = getCortexOwnerToken();
8406
+ var b = base3();
8407
+ if (!token || !b) return Promise.resolve();
8408
+ return fetch(b + "/v1/memory", {
8409
+ method: "POST",
8410
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
8411
+ body: JSON.stringify({
8412
+ key: KEY2,
8413
+ value: { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), refs: Array.from(favSet) },
8414
+ visibility: "private"
8415
+ })
8416
+ }).catch(function() {
8417
+ });
8418
+ }
8419
+ function toggleFavorite(ref) {
8420
+ var now;
8421
+ if (favSet.has(ref)) {
8422
+ favSet.delete(ref);
8423
+ now = false;
8424
+ } else {
8425
+ favSet.add(ref);
8426
+ now = true;
8427
+ }
8428
+ return persist2().then(function() {
8429
+ return now;
8430
+ });
8431
+ }
8432
+ function favStarHtml(ref) {
8433
+ var on = favSet.has(ref);
8434
+ var title = on ? t("fav.remove") || "Remove from favourites" : t("fav.add") || "Add to favourites";
8435
+ return '<button class="fav-toggle' + (on ? " on" : "") + `" onclick="event.stopPropagation(); window._launcher.toggleFavorite('` + String(ref).replace(/'/g, "\\'") + `')" title="` + title + '">' + (on ? "⭐" : "☆") + "</button>";
7125
8436
  }
7126
8437
 
7127
8438
  // src/static/app-catalog/js/server-io.js
@@ -7133,137 +8444,18 @@
7133
8444
  var setOwnServerApps;
7134
8445
  var getActiveTag;
7135
8446
  var getSearchQuery;
7136
- var generateId5;
7137
- var renderApps4;
8447
+ var generateId4;
8448
+ var renderApps3;
7138
8449
  var refreshAll2;
7139
8450
  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
- });
8451
+ ({ getMainApps: getMainApps3, getServerState: getServerState2, getServerManifests: getServerManifests3, setServerManifests, getOwnServerApps, setOwnServerApps, getActiveTag, getSearchQuery, generateId: generateId4, renderApps: renderApps3, refreshAll: refreshAll2 } = deps);
7264
8452
  }
8453
+ var lastAllServerApps = [];
8454
+ var lastAimeatUrl = "";
7265
8455
  var publishAppId = null;
7266
- function showPublishModal2(appId) {
8456
+ var publishUnlisted = false;
8457
+ function showPublishModal3(appId, opts) {
8458
+ publishUnlisted = !!(opts && opts.unlisted);
7267
8459
  var app = null;
7268
8460
  for (var i = 0; i < getMainApps3().length; i++) {
7269
8461
  if (getMainApps3()[i].id === appId) {
@@ -7385,7 +8577,16 @@
7385
8577
  app.publishedVersionNumber = json.data.version_number || 1;
7386
8578
  app.publishedVersionsUrl = json.data.versions_url || null;
7387
8579
  saveApp(app).then(function() {
7388
- loadPublishedApps3();
8580
+ if (publishUnlisted) {
8581
+ app.parked = true;
8582
+ statusEl.textContent = "✔ " + (t("publish.savedUnlisted") || "Saved (unlisted)");
8583
+ try {
8584
+ toggleParkApp(filename, true);
8585
+ } catch (e) {
8586
+ }
8587
+ }
8588
+ publishUnlisted = false;
8589
+ loadPublishedApps2();
7389
8590
  setTimeout(function() {
7390
8591
  var ov = document.getElementById("publish-overlay");
7391
8592
  if (ov) ov.hidden = true;
@@ -7539,7 +8740,7 @@
7539
8740
  statusEl.textContent = "✔ " + t("subModal.assigned") + " — " + sub + "." + apexHostLabel();
7540
8741
  return loadSubdomainSites();
7541
8742
  }).then(function() {
7542
- loadPublishedApps3();
8743
+ loadPublishedApps2();
7543
8744
  }).catch(function(err) {
7544
8745
  statusEl.style.color = "#ef4444";
7545
8746
  statusEl.textContent = err.message || String(err);
@@ -7557,7 +8758,7 @@
7557
8758
  subdomainModalState.existingSub = null;
7558
8759
  return loadSubdomainSites();
7559
8760
  }).then(function() {
7560
- loadPublishedApps3();
8761
+ loadPublishedApps2();
7561
8762
  }).catch(function(err) {
7562
8763
  statusEl.style.color = "#ef4444";
7563
8764
  statusEl.textContent = err.message || String(err);
@@ -7882,7 +9083,7 @@
7882
9083
  }
7883
9084
  return page(0);
7884
9085
  }
7885
- function loadPublishedApps3() {
9086
+ function loadPublishedApps2() {
7886
9087
  var config = loadConfig();
7887
9088
  var aimeatUrl = config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, "") : "";
7888
9089
  var communitySection = document.getElementById("community-section");
@@ -7901,10 +9102,10 @@
7901
9102
  }
7902
9103
  } catch (e) {
7903
9104
  }
7904
- getAllApps().then(function() {
9105
+ Promise.all([getAllApps(), loadFavorites(), loadPromoted()]).then(function() {
7905
9106
  if (!aimeatUrl) {
7906
9107
  setOwnServerApps([]);
7907
- renderApps4();
9108
+ renderApps3();
7908
9109
  if (communitySection) communitySection.style.display = "none";
7909
9110
  return;
7910
9111
  }
@@ -7924,15 +9125,18 @@
7924
9125
  var communityApps = currentOwner ? serverApps.filter(function(a) {
7925
9126
  return !sameOwner(a.owner, currentOwner);
7926
9127
  }) : serverApps;
9128
+ lastAllServerApps = serverApps;
9129
+ lastAimeatUrl = aimeatUrl;
7927
9130
  return loadSubdomainSites().then(function() {
7928
9131
  setOwnServerApps(currentOwner ? ownApps : []);
7929
- renderApps4();
9132
+ renderApps3();
7930
9133
  renderCommunityApps(communityApps, aimeatUrl, communitySection, communityGrid, communityCountEl, currentOwner);
9134
+ renderFavorites(serverApps, aimeatUrl);
7931
9135
  applyServerFilter();
7932
9136
  });
7933
9137
  }).catch(function() {
7934
9138
  setOwnServerApps([]);
7935
- renderApps4();
9139
+ renderApps3();
7936
9140
  if (communitySection) communitySection.style.display = "none";
7937
9141
  });
7938
9142
  });
@@ -7962,6 +9166,21 @@
7962
9166
  var sectionEl = document.getElementById("community-section");
7963
9167
  if (sectionEl) sectionEl.style.display = filtering && shown === 0 ? "none" : "";
7964
9168
  }
9169
+ function publishedCardHtml(sa, aimeatUrl) {
9170
+ var owner = sa.owner || "";
9171
+ var fn = sa.filename || "";
9172
+ var ref = owner + "/" + fn;
9173
+ var m = sa.manifest || {};
9174
+ var name = m.name || fn;
9175
+ var version = sa.version_number ? "v" + sa.version_number : "";
9176
+ var description = m.descriptions && m.descriptions[getLang()] || m.description || "";
9177
+ var date = sa.created_at ? new Date(sa.created_at).toLocaleDateString() : "";
9178
+ var author = m.authorDisplay || owner;
9179
+ var viewUrl = aimeatUrl + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(fn);
9180
+ var shipsAgent = !!(m.cortex && m.cortex.agents && m.cortex.agents.length);
9181
+ 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.
9182
+ (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>";
9183
+ }
7965
9184
  function renderCommunityApps(serverApps, aimeatUrl, section, grid, countEl, currentOwner) {
7966
9185
  if (!section || !grid || !countEl) return;
7967
9186
  if (serverApps.length === 0) {
@@ -7973,22 +9192,51 @@
7973
9192
  section.style.display = "";
7974
9193
  countEl.textContent = "(" + serverApps.length + ")";
7975
9194
  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
- }
9195
+ for (var i = 0; i < serverApps.length; i++) html += publishedCardHtml(serverApps[i], aimeatUrl);
7989
9196
  grid.innerHTML = html;
7990
9197
  updateCommunityEmpty();
7991
9198
  }
9199
+ function renderFavorites(allServerApps, aimeatUrl) {
9200
+ var section = document.getElementById("favorites-section");
9201
+ var grid = document.getElementById("favorites-grid");
9202
+ var countEl = document.getElementById("favorites-count");
9203
+ if (!section || !grid) return;
9204
+ var favs = (allServerApps || []).filter(function(sa) {
9205
+ return isFavorite((sa.owner || "") + "/" + (sa.filename || ""));
9206
+ });
9207
+ if (favs.length === 0) {
9208
+ section.style.display = "none";
9209
+ grid.innerHTML = "";
9210
+ return;
9211
+ }
9212
+ section.style.display = "";
9213
+ if (countEl) countEl.textContent = "(" + favs.length + ")";
9214
+ var html = "";
9215
+ for (var i = 0; i < favs.length; i++) html += publishedCardHtml(favs[i], aimeatUrl);
9216
+ grid.innerHTML = html;
9217
+ }
9218
+ function refreshFavoritesUI() {
9219
+ renderFavorites(lastAllServerApps, lastAimeatUrl);
9220
+ var currentOwner = null;
9221
+ try {
9222
+ var s = window.AIMEAT && window.AIMEAT.auth && window.AIMEAT.auth.getSession();
9223
+ currentOwner = s && s.owner || JSON.parse(localStorage.getItem("aimeat_session") || "{}").owner || null;
9224
+ } catch (e) {
9225
+ }
9226
+ var communityApps = currentOwner ? lastAllServerApps.filter(function(a) {
9227
+ return !sameOwner(a.owner, currentOwner);
9228
+ }) : lastAllServerApps;
9229
+ renderCommunityApps(
9230
+ communityApps,
9231
+ lastAimeatUrl,
9232
+ document.getElementById("community-section"),
9233
+ document.getElementById("community-grid"),
9234
+ document.getElementById("community-count"),
9235
+ currentOwner
9236
+ );
9237
+ applyServerFilter();
9238
+ renderApps3();
9239
+ }
7992
9240
  async function unpublishApp(appId) {
7993
9241
  if (!await showConfirm(t("confirm.removePublished"))) return;
7994
9242
  var app = null;
@@ -8020,7 +9268,7 @@
8020
9268
  delete app.publishedVersionNumber;
8021
9269
  delete app.publishedVersionsUrl;
8022
9270
  saveApp(app).then(function() {
8023
- loadPublishedApps3();
9271
+ loadPublishedApps2();
8024
9272
  });
8025
9273
  } else {
8026
9274
  showNotice("Failed to remove: " + (json.error && json.error.message || "Unknown error"));
@@ -8046,7 +9294,7 @@
8046
9294
  }).then(function(json) {
8047
9295
  if (json.ok) {
8048
9296
  if (getServerState2()[filename]) getServerState2()[filename].parked = !!parked;
8049
- loadPublishedApps3();
9297
+ loadPublishedApps2();
8050
9298
  refreshServerMgmt();
8051
9299
  } else {
8052
9300
  showNotice("Failed: " + (json.error && json.error.message ? json.error.message : "Unknown error"));
@@ -8072,7 +9320,7 @@
8072
9320
  }).then(function(json) {
8073
9321
  if (json.ok) {
8074
9322
  if (getServerState2()[filename]) getServerState2()[filename].forkable = !!forkable;
8075
- loadPublishedApps3();
9323
+ loadPublishedApps2();
8076
9324
  refreshServerMgmt();
8077
9325
  } else {
8078
9326
  showNotice("Failed: " + (json.error && json.error.message ? json.error.message : "Unknown error"));
@@ -8099,7 +9347,7 @@
8099
9347
  return resp.json();
8100
9348
  }).then(function(json) {
8101
9349
  if (json.ok) {
8102
- loadPublishedApps3();
9350
+ loadPublishedApps2();
8103
9351
  } else {
8104
9352
  showNotice("Failed to delete: " + (json.error && json.error.message ? json.error.message : "Unknown error"));
8105
9353
  }
@@ -8158,7 +9406,7 @@
8158
9406
  }
8159
9407
  function filterByTag(tag) {
8160
9408
  setActiveTag(tag);
8161
- renderApps5();
9409
+ renderApps4();
8162
9410
  applyServerFilter();
8163
9411
  }
8164
9412
  function launchApp2(id, mode) {
@@ -8232,7 +9480,7 @@
8232
9480
  }
8233
9481
  }
8234
9482
  function buildLibraryEntries(localApps, serverApps) {
8235
- var base = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
9483
+ var base4 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
8236
9484
  var byFilename = {};
8237
9485
  var entries = [];
8238
9486
  serverStateByFilename = {};
@@ -8262,7 +9510,7 @@
8262
9510
  filename: la.publishedFilename || null,
8263
9511
  owner: null,
8264
9512
  versionNumber: la.publishedVersionNumber || null,
8265
- viewUrl: la.publishedUrl ? base + la.publishedUrl : "",
9513
+ viewUrl: la.publishedUrl ? base4 + la.publishedUrl : "",
8266
9514
  forkable: false,
8267
9515
  forks: 0
8268
9516
  };
@@ -8285,7 +9533,7 @@
8285
9533
  m.hasDraft = !!sa.has_draft;
8286
9534
  m.hasAgents = shipsAgent;
8287
9535
  m.protection = prot;
8288
- m.viewUrl = base + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn);
9536
+ m.viewUrl = base4 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn);
8289
9537
  if (!m.description && sa.manifest && sa.manifest.description) m.description = sa.manifest.description;
8290
9538
  } else {
8291
9539
  var se = {
@@ -8294,6 +9542,7 @@
8294
9542
  name: sa.manifest && sa.manifest.name || fn,
8295
9543
  icon: "🌐",
8296
9544
  description: sa.manifest && sa.manifest.description || "",
9545
+ descriptions: sa.manifest && sa.manifest.descriptions || null,
8297
9546
  tags: sa.manifest && sa.manifest.tags || [],
8298
9547
  favorite: false,
8299
9548
  sortOrder: void 0,
@@ -8309,7 +9558,7 @@
8309
9558
  filename: fn,
8310
9559
  owner: sa.owner || "",
8311
9560
  versionNumber: sa.version_number || null,
8312
- viewUrl: base && fn ? base + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn) : "",
9561
+ viewUrl: base4 && fn ? base4 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn) : "",
8313
9562
  forkable: !!sa.forkable,
8314
9563
  forks: sa.forks || 0,
8315
9564
  protection: prot,
@@ -8326,6 +9575,9 @@
8326
9575
  owner: sa.owner || "",
8327
9576
  versionNumber: sa.version_number || null,
8328
9577
  protection: prot,
9578
+ // A saved (unpublished) working copy from ANY earlier session — the detail view's
9579
+ // lifecycle band reads this to say "working copy saved" instead of "same as published".
9580
+ hasDraft: !!sa.has_draft,
8329
9581
  // `protected` is the ACCESS-CODE flag (distinct from copy-`protection`); the detail view's
8330
9582
  // access-code editor reads it to show whether the app currently requires a code.
8331
9583
  accessCode: !!sa.protected
@@ -8335,20 +9587,12 @@
8335
9587
  }
8336
9588
  return entries;
8337
9589
  }
8338
- function renderApps5() {
9590
+ function renderApps4() {
8339
9591
  getAllApps().then(function(apps) {
8340
9592
  setAllApps(apps);
8341
- var entries = buildLibraryEntries(apps, ownServerApps);
9593
+ var entries = buildLibraryEntries([], ownServerApps);
8342
9594
  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;
9595
+ if (!!a.parked !== !!b.parked) return a.parked ? 1 : -1;
8352
9596
  return 0;
8353
9597
  });
8354
9598
  var filtered = entries;
@@ -8397,14 +9641,8 @@
8397
9641
  grid.innerHTML = html;
8398
9642
  }
8399
9643
  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");
9644
+ statsEl.textContent = entries.length + " " + t("stats.apps");
8406
9645
  renderTags();
8407
- renderRecentlyOpened();
8408
9646
  });
8409
9647
  }
8410
9648
  function libraryCardHtml(e, i) {
@@ -8416,25 +9654,11 @@
8416
9654
  var st = libStatus(e);
8417
9655
  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
9656
  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("");
9657
+ 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() {
9658
+ var d = e.descriptions && e.descriptions[getLang()] || e.description || "";
9659
+ return d ? '<div class="app-source">' + escapeHtml(d) + "</div>" : '<div class="app-source">' + sourceLabel(e.source) + "</div>";
9660
+ })() + '<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.
9661
+ (e.filename ? favStarHtml((e.owner || e.aimeatOwner || "") + "/" + e.filename) : "") + "</div>";
8438
9662
  }
8439
9663
  var currentIframeUrl = "";
8440
9664
  function closeIframe() {
@@ -8496,24 +9720,12 @@
8496
9720
  openDetailView(appId);
8497
9721
  break;
8498
9722
  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;
9723
+ openDetailView(appId);
8512
9724
  break;
8513
9725
  case "favorite":
8514
9726
  app.favorite = !app.favorite;
8515
9727
  saveApp(app).then(function() {
8516
- renderApps5();
9728
+ renderApps4();
8517
9729
  });
8518
9730
  break;
8519
9731
  case "view-source":
@@ -8524,13 +9736,13 @@
8524
9736
  return app.name || "this app";
8525
9737
  }))) {
8526
9738
  deleteApp(appId).then(function() {
8527
- renderApps5();
8528
- loadPublishedApps3();
9739
+ renderApps4();
9740
+ loadPublishedApps2();
8529
9741
  });
8530
9742
  }
8531
9743
  break;
8532
9744
  case "publish":
8533
- showPublishModal2(appId);
9745
+ showPublishModal3(appId);
8534
9746
  break;
8535
9747
  case "improve-ai":
8536
9748
  openPromptBuilder(app);
@@ -8542,19 +9754,19 @@
8542
9754
  }
8543
9755
  function viewSource2(app) {
8544
9756
  var overlay = document.getElementById("source-overlay");
8545
- var textarea = document.getElementById("source-code");
9757
+ var textarea2 = document.getElementById("source-code");
8546
9758
  var title = document.getElementById("source-title");
8547
9759
  var saveBtn = document.getElementById("save-source-btn");
8548
9760
  title.textContent = "View / Edit Source: " + (app.name || "App");
8549
9761
  var isEditable = !!app.blob;
8550
9762
  if (app.blob) {
8551
- textarea.value = decodeURIComponent(escape(atob(app.blob)));
9763
+ textarea2.value = decodeURIComponent(escape(atob(app.blob)));
8552
9764
  } else if (app.url) {
8553
- textarea.value = "// This app is URL-based (" + app.url + ")\n// Source code is not stored locally.\n// Open the URL to view the app.";
9765
+ textarea2.value = "// This app is URL-based (" + app.url + ")\n// Source code is not stored locally.\n// Open the URL to view the app.";
8554
9766
  } else {
8555
- textarea.value = "// No source available";
9767
+ textarea2.value = "// No source available";
8556
9768
  }
8557
- textarea.readOnly = !isEditable;
9769
+ textarea2.readOnly = !isEditable;
8558
9770
  saveBtn.disabled = true;
8559
9771
  saveBtn.style.display = isEditable ? "" : "none";
8560
9772
  var canStage = isEditable && !!app.published;
@@ -8567,7 +9779,7 @@
8567
9779
  overlay.hidden = false;
8568
9780
  overlay.dataset.appName = app.name || "App";
8569
9781
  overlay.dataset.appId = app.id || "";
8570
- overlay.dataset.originalSource = textarea.value;
9782
+ overlay.dataset.originalSource = textarea2.value;
8571
9783
  }
8572
9784
  function generateSharePrompt2(app) {
8573
9785
  if (!app || !app.blob) {
@@ -8587,12 +9799,12 @@
8587
9799
  showNotice("Share prompt copied! Paste it into any AI chat to recreate this app.");
8588
9800
  }).catch(function() {
8589
9801
  var overlay = document.getElementById("source-overlay");
8590
- var textarea = document.getElementById("source-code");
9802
+ var textarea2 = document.getElementById("source-code");
8591
9803
  var title = document.getElementById("source-title");
8592
9804
  var saveBtn = document.getElementById("save-source-btn");
8593
9805
  title.textContent = "Share Prompt: " + (app.name || "App");
8594
- textarea.value = prompt2;
8595
- textarea.readOnly = true;
9806
+ textarea2.value = prompt2;
9807
+ textarea2.readOnly = true;
8596
9808
  saveBtn.style.display = "none";
8597
9809
  overlay.hidden = false;
8598
9810
  overlay.dataset.appId = "";
@@ -8615,65 +9827,114 @@
8615
9827
  }).join("\n");
8616
9828
  var prompt2 = "Create a single HTML file that serves as my personal homepage/dashboard.\n\nMy apps:\n" + appList + "\n\nRequirements:\n- Show each app as a clickable card with its icon and name\n- For URL-based apps, clicking opens the URL in a new tab\n- For local apps, show a note that they can be opened from the App Launcher\n- Modern, responsive design with light theme\n- Group apps by their tags if they have tags\n- Everything in one self-contained HTML file, no external dependencies\n- Add a header with my name/title (I will customize this)\n- Make it visually distinctive and professional";
8617
9829
  var overlay = document.getElementById("source-overlay");
8618
- var textarea = document.getElementById("source-code");
9830
+ var textarea2 = document.getElementById("source-code");
8619
9831
  var title = document.getElementById("source-title");
8620
9832
  title.textContent = "Generate Homepage - Copy this prompt to AI";
8621
- textarea.value = prompt2;
9833
+ textarea2.value = prompt2;
8622
9834
  overlay.dataset.appName = "Homepage";
8623
9835
  overlay.hidden = false;
8624
9836
  }
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;
9837
+
9838
+ // src/static/app-catalog/js/migrate.js
9839
+ var DONE_KEY = "appCatalogLocalMigrated";
9840
+ async function legacyDbNames() {
9841
+ var names = ["AppLauncherDB"];
9842
+ try {
9843
+ if (indexedDB.databases) {
9844
+ var list = await indexedDB.databases();
9845
+ list.forEach(function(d) {
9846
+ if (d && d.name && d.name.indexOf("AppLauncherDB") === 0 && names.indexOf(d.name) < 0) names.push(d.name);
9847
+ });
9848
+ }
9849
+ } catch (e) {
9850
+ }
9851
+ return names;
9852
+ }
9853
+ function openExisting(name) {
9854
+ return new Promise(function(resolve) {
9855
+ var req;
9856
+ try {
9857
+ req = indexedDB.open(name);
9858
+ } catch (e) {
9859
+ resolve(null);
9860
+ return;
9861
+ }
9862
+ req.onsuccess = function() {
9863
+ resolve(req.result);
9864
+ };
9865
+ req.onerror = function() {
9866
+ resolve(null);
9867
+ };
9868
+ req.onupgradeneeded = function() {
9869
+ };
9870
+ });
9871
+ }
9872
+ function readApps(db) {
9873
+ return new Promise(function(resolve) {
9874
+ if (!db || !db.objectStoreNames || !db.objectStoreNames.contains("apps")) {
9875
+ resolve([]);
9876
+ return;
9877
+ }
9878
+ try {
9879
+ var tx = db.transaction("apps", "readonly");
9880
+ var r = tx.objectStore("apps").getAll();
9881
+ r.onsuccess = function() {
9882
+ resolve(r.result || []);
9883
+ };
9884
+ r.onerror = function() {
9885
+ resolve([]);
9886
+ };
9887
+ } catch (e) {
9888
+ resolve([]);
9889
+ }
9890
+ });
9891
+ }
9892
+ function downloadJson(apps) {
9893
+ var json = JSON.stringify({ version: 1, exportedAt: (/* @__PURE__ */ new Date()).toISOString(), apps }, null, 2);
9894
+ var blob = new Blob([json], { type: "application/json" });
9895
+ var url = URL.createObjectURL(blob);
9896
+ var a = document.createElement("a");
9897
+ a.href = url;
9898
+ a.download = "app-catalog-local-apps.json";
9899
+ document.body.appendChild(a);
9900
+ a.click();
9901
+ document.body.removeChild(a);
9902
+ URL.revokeObjectURL(url);
9903
+ }
9904
+ async function checkLegacyLocalApps() {
9905
+ try {
9906
+ if (localStorage.getItem(DONE_KEY)) return;
9907
+ } catch (e) {
9908
+ return;
8660
9909
  }
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]));
9910
+ var apps = [];
9911
+ try {
9912
+ var names = await legacyDbNames();
9913
+ for (var i = 0; i < names.length; i++) {
9914
+ var db = await openExisting(names[i]);
9915
+ var found = await readApps(db);
9916
+ if (found && found.length) apps = apps.concat(found);
9917
+ try {
9918
+ if (db) db.close();
9919
+ } catch (e) {
9920
+ }
9921
+ }
9922
+ } catch (e) {
8668
9923
  }
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");
9924
+ if (!apps.length) {
9925
+ try {
9926
+ localStorage.setItem(DONE_KEY, "1");
9927
+ } catch (e) {
9928
+ }
9929
+ return;
9930
+ }
9931
+ 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));
9932
+ var wants = await showConfirm(msg);
9933
+ if (wants) downloadJson(apps);
9934
+ try {
9935
+ localStorage.setItem(DONE_KEY, "1");
9936
+ } catch (e) {
8673
9937
  }
8674
- Promise.all(saves).then(function() {
8675
- renderApps5();
8676
- });
8677
9938
  }
8678
9939
 
8679
9940
  // src/static/app-catalog/js/main.js
@@ -8687,7 +9948,7 @@
8687
9948
  }
8688
9949
  applyI18n();
8689
9950
  try {
8690
- renderApps5();
9951
+ renderApps4();
8691
9952
  } catch (e) {
8692
9953
  }
8693
9954
  try {
@@ -8695,11 +9956,7 @@
8695
9956
  } catch (e) {
8696
9957
  }
8697
9958
  try {
8698
- loadPublishedApps3();
8699
- } catch (e) {
8700
- }
8701
- try {
8702
- renderRecentlyOpened();
9959
+ loadPublishedApps2();
8703
9960
  } catch (e) {
8704
9961
  }
8705
9962
  try {
@@ -8711,41 +9968,16 @@
8711
9968
  } catch (e) {
8712
9969
  }
8713
9970
  }
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
9971
  var allApps = [];
8739
9972
  var activeTag = null;
8740
9973
  var searchQuery = "";
8741
9974
  window._launcher = {
8742
- openDB,
8743
9975
  getAllApps,
8744
9976
  saveApp,
8745
9977
  deleteApp,
8746
9978
  loadConfig,
8747
9979
  saveConfig,
8748
- renderApps: renderApps5,
9980
+ renderApps: renderApps4,
8749
9981
  renderTags,
8750
9982
  filterByTag,
8751
9983
  launchApp: launchApp2,
@@ -8762,10 +9994,6 @@
8762
9994
  handleContextAction,
8763
9995
  generateId,
8764
9996
  readFileAsText,
8765
- addAppFromUrl,
8766
- addAppFromFile,
8767
- addAppFromSource,
8768
- addAppFromZip,
8769
9997
  showModal,
8770
9998
  closeModal,
8771
9999
  openSettings,
@@ -8774,18 +10002,22 @@
8774
10002
  saveSettings,
8775
10003
  closeSettings,
8776
10004
  applyTheme,
8777
- exportBackup,
8778
- clearAllData,
8779
- importFromAimeat,
8780
- processAimeatImport,
8781
10005
  viewSource: viewSource2,
8782
10006
  generateHomepagePrompt,
8783
- showPublishModal: showPublishModal2,
10007
+ showPublishModal: showPublishModal3,
8784
10008
  submitPublish,
8785
- loadPublishedApps: loadPublishedApps3,
10009
+ loadPublishedApps: loadPublishedApps2,
10010
+ // Toggle a server-backed favourite (ref = "owner/filename"), then re-render the ⭐ group + stars.
10011
+ toggleFavorite: function(ref) {
10012
+ toggleFavorite(ref).then(function() {
10013
+ try {
10014
+ refreshFavoritesUI();
10015
+ } catch (e) {
10016
+ }
10017
+ });
10018
+ },
8786
10019
  toggleCommunity,
8787
10020
  switchView,
8788
- switchDbMode,
8789
10021
  unpublishApp,
8790
10022
  deleteServerApp,
8791
10023
  toggleParkApp,
@@ -8793,6 +10025,9 @@
8793
10025
  detailAboutEdit,
8794
10026
  detailAboutCancel,
8795
10027
  detailAboutSave,
10028
+ detailTranslateDesc,
10029
+ detailPromoteSave,
10030
+ detailPromoteClear,
8796
10031
  detailToggleFavorite,
8797
10032
  detailAccessCodeEdit,
8798
10033
  detailAccessCodeCancel,
@@ -8805,6 +10040,12 @@
8805
10040
  monetizeCancelEdit,
8806
10041
  monetizeSaveTool,
8807
10042
  monetizeDeleteTool,
10043
+ odpsToggleDefaults: odpsToggleDefaultsUi,
10044
+ odpsToggleTool: odpsToggleToolUi,
10045
+ odpsSuggest: odpsSuggestUi,
10046
+ odpsSaveDefaults,
10047
+ odpsGenerateSample: odpsGenerateSampleUi,
10048
+ odpsUseMeasured: odpsUseMeasuredUi,
8808
10049
  editAppDetails,
8809
10050
  showSubdomainModal,
8810
10051
  openConsents,
@@ -8812,9 +10053,6 @@
8812
10053
  revokeConsent,
8813
10054
  submitSubdomainAssign,
8814
10055
  unassignSubdomain,
8815
- jsonImportSelectAll,
8816
- submitJsonImport,
8817
- removeDuplicateApps,
8818
10056
  toggleBackupMenu,
8819
10057
  toggleCreateMenu,
8820
10058
  toggleCortexBar,
@@ -8841,6 +10079,12 @@
8841
10079
  detailAiDiscard,
8842
10080
  detailTestDraftLive,
8843
10081
  detailPublishTestedDraft,
10082
+ detailWorkTry,
10083
+ detailWorkPublish,
10084
+ detailWorkDiscard,
10085
+ detailCheckpointPreview,
10086
+ detailCheckpointRestore,
10087
+ detailCheckpointDelete,
8844
10088
  openStagingPreview,
8845
10089
  detailEditSource,
8846
10090
  detailImproveExternal,
@@ -8850,7 +10094,6 @@
8850
10094
  detailSetScreenshot,
8851
10095
  detailRefreshScreenshot,
8852
10096
  openPublishedDetail,
8853
- renderRecentlyOpened,
8854
10097
  loadCortexExtensions,
8855
10098
  showCortexPopup,
8856
10099
  cortexCopy,
@@ -8861,10 +10104,6 @@
8861
10104
  cortexEditorExport,
8862
10105
  openPromptBuilder,
8863
10106
  closePbPanel,
8864
- onCardDragStart,
8865
- onCardDragEnd,
8866
- onCardDragOver,
8867
- onCardDrop,
8868
10107
  syncConfigToServer,
8869
10108
  loadConfigFromServer,
8870
10109
  setLanguage,
@@ -8875,16 +10114,16 @@
8875
10114
  window.cortexCopy = cortexCopy;
8876
10115
  function refreshAll3() {
8877
10116
  allApps = [];
8878
- renderApps5();
8879
- loadPublishedApps3();
8880
- renderRecentlyOpened();
10117
+ renderApps4();
10118
+ loadPublishedApps2();
8881
10119
  }
8882
10120
  document.addEventListener("DOMContentLoaded", function() {
8883
10121
  initDetail({
8884
10122
  refreshAll: refreshAll3,
8885
- loadPublishedApps: loadPublishedApps3,
8886
- renderApps: renderApps5,
8887
- updateModeToggle: updateModeToggle2,
10123
+ loadPublishedApps: loadPublishedApps2,
10124
+ renderApps: renderApps4,
10125
+ updateModeToggle: function() {
10126
+ },
8888
10127
  getCortexOwnerToken,
8889
10128
  launchApp: launchApp2,
8890
10129
  viewPublished: viewPublished2,
@@ -8892,7 +10131,7 @@
8892
10131
  generateId,
8893
10132
  generateSharePrompt: generateSharePrompt2,
8894
10133
  openPromptBuilder,
8895
- showPublishModal: showPublishModal2,
10134
+ showPublishModal: showPublishModal3,
8896
10135
  getMainApps: function() {
8897
10136
  return allApps;
8898
10137
  },
@@ -8911,10 +10150,10 @@
8911
10150
  initAppAgents({ getServerManifests: function() {
8912
10151
  return serverAppManifests;
8913
10152
  } });
8914
- initSettings({ generateId, renderApps: renderApps5, loadPublishedApps: loadPublishedApps3 });
8915
- initAppsIo({ generateId, readFileAsText, renderApps: renderApps5, getMainApps: function() {
10153
+ initSettings({ generateId, renderApps: renderApps4, loadPublishedApps: loadPublishedApps2 });
10154
+ initAppsIo({ generateId, readFileAsText, renderApps: renderApps4, getMainApps: function() {
8916
10155
  return allApps;
8917
- } });
10156
+ }, showPublishModal: showPublishModal3 });
8918
10157
  initServerIo({
8919
10158
  getMainApps: function() {
8920
10159
  return allApps;
@@ -8937,7 +10176,7 @@
8937
10176
  return searchQuery;
8938
10177
  },
8939
10178
  generateId,
8940
- renderApps: renderApps5,
10179
+ renderApps: renderApps4,
8941
10180
  refreshAll: refreshAll3
8942
10181
  });
8943
10182
  initRender({
@@ -8969,7 +10208,6 @@
8969
10208
  var _urlLang = _params.get("lang");
8970
10209
  setLang(_urlLang === "fi" || _urlLang === "en" ? _urlLang : loadConfig().language === "fi" ? "fi" : "en");
8971
10210
  applyI18n();
8972
- updateModeToggle2();
8973
10211
  var _savedView = "library";
8974
10212
  try {
8975
10213
  _savedView = localStorage.getItem("appCatalogView") || "library";
@@ -8982,8 +10220,8 @@
8982
10220
  if (_homeLabel && location.host) _homeLabel.textContent = location.host.replace(/^apps\./, "");
8983
10221
  } catch (e) {
8984
10222
  }
8985
- renderApps5();
8986
- loadPublishedApps3();
10223
+ renderApps4();
10224
+ loadPublishedApps2();
8987
10225
  loadCortexExtensions();
8988
10226
  loadConfigFromServer();
8989
10227
  document.getElementById("confirm-ok-btn").addEventListener("click", function() {
@@ -8995,6 +10233,10 @@
8995
10233
  document.getElementById("confirm-overlay").addEventListener("click", function(e) {
8996
10234
  if (e.target === this) closeConfirm(false);
8997
10235
  });
10236
+ try {
10237
+ checkLegacyLocalApps();
10238
+ } catch (e) {
10239
+ }
8998
10240
  if (_params.get("create") === "1") {
8999
10241
  try {
9000
10242
  openPromptBuilder(null);
@@ -9003,7 +10245,7 @@
9003
10245
  }
9004
10246
  document.getElementById("search-input").addEventListener("input", function(e) {
9005
10247
  searchQuery = e.target.value.trim();
9006
- renderApps5();
10248
+ renderApps4();
9007
10249
  applyServerFilter();
9008
10250
  });
9009
10251
  document.getElementById("add-btn").addEventListener("click", function() {
@@ -9028,17 +10270,6 @@
9028
10270
  closeSettings();
9029
10271
  }
9030
10272
  });
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
10273
  document.getElementById("publish-overlay").addEventListener("click", function(e) {
9043
10274
  if (e.target === this) {
9044
10275
  this.hidden = true;
@@ -9063,11 +10294,6 @@
9063
10294
  this.hidden = true;
9064
10295
  }
9065
10296
  });
9066
- document.getElementById("json-import-overlay").addEventListener("click", function(e) {
9067
- if (e.target === this) {
9068
- this.hidden = true;
9069
- }
9070
- });
9071
10297
  document.addEventListener("click", function(e) {
9072
10298
  var menu = document.getElementById("backup-menu");
9073
10299
  if (!menu.hidden && !menu.contains(e.target) && e.target.id !== "backup-btn") {
@@ -9198,16 +10424,12 @@
9198
10424
  document.getElementById("source-overlay").hidden = true;
9199
10425
  } else if (!document.getElementById("context-menu").hidden) {
9200
10426
  hideContextMenu();
9201
- } else if (!document.getElementById("aimeat-import-overlay").hidden) {
9202
- document.getElementById("aimeat-import-overlay").hidden = true;
9203
10427
  } else if (!document.getElementById("publish-overlay").hidden) {
9204
10428
  document.getElementById("publish-overlay").hidden = true;
9205
10429
  } else if (!document.getElementById("subdomain-overlay").hidden) {
9206
10430
  document.getElementById("subdomain-overlay").hidden = true;
9207
10431
  } else if (!document.getElementById("backup-overlay").hidden) {
9208
10432
  document.getElementById("backup-overlay").hidden = true;
9209
- } else if (!document.getElementById("json-import-overlay").hidden) {
9210
- document.getElementById("json-import-overlay").hidden = true;
9211
10433
  } else if (!document.getElementById("settings-overlay").hidden) {
9212
10434
  closeSettings();
9213
10435
  } else if (!document.getElementById("modal-overlay").hidden) {
@@ -9227,23 +10449,6 @@
9227
10449
  document.getElementById("search-input").focus();
9228
10450
  }
9229
10451
  });
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
10452
  document.getElementById("copy-source-btn").addEventListener("click", function() {
9248
10453
  var code = document.getElementById("source-code").value;
9249
10454
  var btn = this;
@@ -9284,41 +10489,27 @@
9284
10489
  });
9285
10490
  document.getElementById("save-source-btn").addEventListener("click", function() {
9286
10491
  var overlay = document.getElementById("source-overlay");
9287
- var textarea = document.getElementById("source-code");
9288
- var appId = overlay.dataset.appId;
10492
+ var textarea2 = document.getElementById("source-code");
9289
10493
  var btn = this;
9290
- if (!appId) {
10494
+ if (!overlay.dataset.appId) {
9291
10495
  showNotice("Cannot save: no app ID found.");
9292
10496
  return;
9293
10497
  }
9294
- var newSource = textarea.value;
9295
- var newBlob = btoa(unescape(encodeURIComponent(newSource)));
9296
- var app = null;
9297
- for (var i = 0; i < allApps.length; i++) {
9298
- if (allApps[i].id === appId) {
9299
- app = allApps[i];
9300
- break;
9301
- }
9302
- }
9303
- if (!app) {
9304
- showNotice("App not found in library.");
9305
- return;
9306
- }
9307
- app.blob = newBlob;
9308
- btn.textContent = "Saving...";
10498
+ var newSource = textarea2.value;
10499
+ var idle = t("source.save");
10500
+ btn.textContent = t("wc.saving");
9309
10501
  btn.disabled = true;
9310
- saveApp(app).then(function() {
9311
- btn.textContent = "Saved!";
10502
+ saveSourceAsWorkingCopy().then(function() {
10503
+ btn.textContent = t("wc.savedShort");
9312
10504
  overlay.dataset.originalSource = newSource;
9313
10505
  setTimeout(function() {
9314
- btn.textContent = "Save Changes";
10506
+ btn.textContent = idle;
9315
10507
  btn.disabled = true;
9316
10508
  }, 1500);
9317
- renderApps5();
9318
10509
  }).catch(function(err) {
9319
- btn.textContent = "Save Changes";
10510
+ btn.textContent = idle;
9320
10511
  btn.disabled = false;
9321
- showNotice("Failed to save: " + (err.message || err));
10512
+ showNotice((t("wc.saveFailed") || "Failed to save") + ": " + (err && err.message || err));
9322
10513
  });
9323
10514
  });
9324
10515
  async function closeSourceModal() {