dsh-mobile-plugin 0.5.27 → 0.5.29

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 (350) hide show
  1. package/extensions/catalog.json +10 -1
  2. package/extensions/magpie/agentcompact.js +104 -0
  3. package/extensions/magpie/app.css +4902 -0
  4. package/extensions/magpie/app.js +24474 -0
  5. package/extensions/magpie/autobackup.js +185 -0
  6. package/extensions/magpie/autoresetown.js +151 -0
  7. package/extensions/magpie/balfound.js +38 -0
  8. package/extensions/magpie/boot.js +23 -0
  9. package/extensions/magpie/cfgblocked.js +39 -0
  10. package/extensions/magpie/checkinnow.js +111 -0
  11. package/extensions/magpie/claudemode.js +92 -0
  12. package/extensions/magpie/compat.js +41 -0
  13. package/extensions/magpie/context.css +433 -0
  14. package/extensions/magpie/context.js +1576 -0
  15. package/extensions/magpie/ctxtext.js +223 -0
  16. package/extensions/magpie/fonts.js +42 -0
  17. package/extensions/magpie/i18n.js +18942 -0
  18. package/extensions/magpie/icons/ai21.svg +1 -0
  19. package/extensions/magpie/icons/ai302-color.svg +1 -0
  20. package/extensions/magpie/icons/ai360-color.svg +1 -0
  21. package/extensions/magpie/icons/aihubmix-color.svg +1 -0
  22. package/extensions/magpie/icons/aionui.svg +1 -0
  23. package/extensions/magpie/icons/air.svg +4 -0
  24. package/extensions/magpie/icons/alibaba-color.svg +1 -0
  25. package/extensions/magpie/icons/alibabacloud-color.svg +1 -0
  26. package/extensions/magpie/icons/alma.png +0 -0
  27. package/extensions/magpie/icons/anthropic.svg +1 -0
  28. package/extensions/magpie/icons/antigravity-color.svg +1 -0
  29. package/extensions/magpie/icons/anyscale-color.svg +1 -0
  30. package/extensions/magpie/icons/app-alma.png +0 -0
  31. package/extensions/magpie/icons/app-cc-switch.png +0 -0
  32. package/extensions/magpie/icons/apple-touch-icon.png +0 -0
  33. package/extensions/magpie/icons/aside.svg +28 -0
  34. package/extensions/magpie/icons/astrbot.svg +1 -0
  35. package/extensions/magpie/icons/atomcode.png +0 -0
  36. package/extensions/magpie/icons/aws.svg +1 -0
  37. package/extensions/magpie/icons/azure-color.svg +1 -0
  38. package/extensions/magpie/icons/azureai-color.svg +1 -0
  39. package/extensions/magpie/icons/baichuan-color.svg +1 -0
  40. package/extensions/magpie/icons/baidu-color.svg +1 -0
  41. package/extensions/magpie/icons/baiducloud-color.svg +1 -0
  42. package/extensions/magpie/icons/bailian-color.svg +1 -0
  43. package/extensions/magpie/icons/baseten.svg +1 -0
  44. package/extensions/magpie/icons/bedrock-color.svg +1 -0
  45. package/extensions/magpie/icons/burncloud-color.svg +1 -0
  46. package/extensions/magpie/icons/bytedance-color.svg +1 -0
  47. package/extensions/magpie/icons/cerebras.svg +1 -0
  48. package/extensions/magpie/icons/chatglm-color.svg +1 -0
  49. package/extensions/magpie/icons/cherryin-color.svg +1 -0
  50. package/extensions/magpie/icons/chutes.svg +1 -0
  51. package/extensions/magpie/icons/cindy.png +0 -0
  52. package/extensions/magpie/icons/claude-color.svg +1 -0
  53. package/extensions/magpie/icons/claudecode-color.svg +1 -0
  54. package/extensions/magpie/icons/cline.svg +1 -0
  55. package/extensions/magpie/icons/cloudflare-color.svg +1 -0
  56. package/extensions/magpie/icons/codebuddy-color.svg +1 -0
  57. package/extensions/magpie/icons/codex-color.svg +1 -0
  58. package/extensions/magpie/icons/cohere-color.svg +1 -0
  59. package/extensions/magpie/icons/cometapi-color.svg +1 -0
  60. package/extensions/magpie/icons/commandcode.svg +1 -0
  61. package/extensions/magpie/icons/copilot-color.svg +1 -0
  62. package/extensions/magpie/icons/coze.svg +1 -0
  63. package/extensions/magpie/icons/crush.png +0 -0
  64. package/extensions/magpie/icons/curl.svg +1 -0
  65. package/extensions/magpie/icons/cursor.svg +1 -0
  66. package/extensions/magpie/icons/deepinfra-color.svg +1 -0
  67. package/extensions/magpie/icons/deepmind-color.svg +1 -0
  68. package/extensions/magpie/icons/deepseek-color.svg +1 -0
  69. package/extensions/magpie/icons/deveco-color.svg +12 -0
  70. package/extensions/magpie/icons/devin.svg +1 -0
  71. package/extensions/magpie/icons/doubao-color.svg +1 -0
  72. package/extensions/magpie/icons/empryo-color.svg +1 -0
  73. package/extensions/magpie/icons/factory.svg +1 -0
  74. package/extensions/magpie/icons/favicon.svg +1 -0
  75. package/extensions/magpie/icons/featherless-color.svg +1 -0
  76. package/extensions/magpie/icons/fireworks-color.svg +1 -0
  77. package/extensions/magpie/icons/friendli.svg +1 -0
  78. package/extensions/magpie/icons/fx.svg +1 -0
  79. package/extensions/magpie/icons/gemini-color.svg +1 -0
  80. package/extensions/magpie/icons/geminicli-color.svg +1 -0
  81. package/extensions/magpie/icons/gemma-color.svg +1 -0
  82. package/extensions/magpie/icons/giteeai.svg +1 -0
  83. package/extensions/magpie/icons/github.svg +1 -0
  84. package/extensions/magpie/icons/githubcopilot.svg +1 -0
  85. package/extensions/magpie/icons/glama.svg +1 -0
  86. package/extensions/magpie/icons/gmicloud.svg +1 -0
  87. package/extensions/magpie/icons/google-color.svg +1 -0
  88. package/extensions/magpie/icons/googlecloud-color.svg +1 -0
  89. package/extensions/magpie/icons/goose.svg +1 -0
  90. package/extensions/magpie/icons/grok.svg +1 -0
  91. package/extensions/magpie/icons/groq.svg +1 -0
  92. package/extensions/magpie/icons/hanako.png +0 -0
  93. package/extensions/magpie/icons/hermes.svg +1 -0
  94. package/extensions/magpie/icons/huawei-color.svg +1 -0
  95. package/extensions/magpie/icons/huaweicloud-color.svg +1 -0
  96. package/extensions/magpie/icons/huggingface-color.svg +1 -0
  97. package/extensions/magpie/icons/hunyuan-color.svg +1 -0
  98. package/extensions/magpie/icons/hyperbolic-color.svg +1 -0
  99. package/extensions/magpie/icons/icon-512.png +0 -0
  100. package/extensions/magpie/icons/iflytekcloud-color.svg +1 -0
  101. package/extensions/magpie/icons/inference.svg +1 -0
  102. package/extensions/magpie/icons/infinigence-color.svg +1 -0
  103. package/extensions/magpie/icons/internlm-color.svg +1 -0
  104. package/extensions/magpie/icons/jcode.svg +1 -0
  105. package/extensions/magpie/icons/jina.svg +1 -0
  106. package/extensions/magpie/icons/kilo.svg +1 -0
  107. package/extensions/magpie/icons/kilocode.svg +1 -0
  108. package/extensions/magpie/icons/kimi-code-desktop.png +0 -0
  109. package/extensions/magpie/icons/kimi.svg +1 -0
  110. package/extensions/magpie/icons/kiro-color.svg +1 -0
  111. package/extensions/magpie/icons/kling-color.svg +1 -0
  112. package/extensions/magpie/icons/lambda.svg +1 -0
  113. package/extensions/magpie/icons/leptonai-color.svg +1 -0
  114. package/extensions/magpie/icons/liquid.svg +1 -0
  115. package/extensions/magpie/icons/liveagent-color.svg +1 -0
  116. package/extensions/magpie/icons/llmapi.svg +1 -0
  117. package/extensions/magpie/icons/lmstudio.svg +1 -0
  118. package/extensions/magpie/icons/lody.png +0 -0
  119. package/extensions/magpie/icons/longcat.svg +1 -0
  120. package/extensions/magpie/icons/magpie.svg +1 -0
  121. package/extensions/magpie/icons/meta-color.svg +1 -0
  122. package/extensions/magpie/icons/metaai-color.svg +1 -0
  123. package/extensions/magpie/icons/microsoft-color.svg +1 -0
  124. package/extensions/magpie/icons/mimocode.svg +1 -0
  125. package/extensions/magpie/icons/minimax-color.svg +1 -0
  126. package/extensions/magpie/icons/mistermorph-color.svg +1 -0
  127. package/extensions/magpie/icons/mistral-color.svg +1 -0
  128. package/extensions/magpie/icons/mlx-serve.png +0 -0
  129. package/extensions/magpie/icons/modelscope-color.svg +1 -0
  130. package/extensions/magpie/icons/monica-color.svg +1 -0
  131. package/extensions/magpie/icons/moonshot.svg +1 -0
  132. package/extensions/magpie/icons/morph-color.svg +1 -0
  133. package/extensions/magpie/icons/mothx.png +0 -0
  134. package/extensions/magpie/icons/nebius.svg +1 -0
  135. package/extensions/magpie/icons/newapi-color.svg +1 -0
  136. package/extensions/magpie/icons/nousresearch.svg +1 -0
  137. package/extensions/magpie/icons/nova-color.svg +1 -0
  138. package/extensions/magpie/icons/novita-color.svg +1 -0
  139. package/extensions/magpie/icons/nvidia-color.svg +1 -0
  140. package/extensions/magpie/icons/ollama.svg +1 -0
  141. package/extensions/magpie/icons/omlx.svg +1 -0
  142. package/extensions/magpie/icons/omo.svg +1 -0
  143. package/extensions/magpie/icons/omp.svg +1 -0
  144. package/extensions/magpie/icons/openai.svg +1 -0
  145. package/extensions/magpie/icons/openchamber.svg +11 -0
  146. package/extensions/magpie/icons/openclaw.svg +1 -0
  147. package/extensions/magpie/icons/opencode.svg +1 -0
  148. package/extensions/magpie/icons/openmausbot.svg +1 -0
  149. package/extensions/magpie/icons/openrouter.svg +1 -0
  150. package/extensions/magpie/icons/openwebui.svg +1 -0
  151. package/extensions/magpie/icons/parasail.svg +1 -0
  152. package/extensions/magpie/icons/penguin-color.svg +1 -0
  153. package/extensions/magpie/icons/perplexity-color.svg +1 -0
  154. package/extensions/magpie/icons/phala-color.svg +1 -0
  155. package/extensions/magpie/icons/pi.svg +1 -0
  156. package/extensions/magpie/icons/pipellm-color.svg +5 -0
  157. package/extensions/magpie/icons/poe.svg +1 -0
  158. package/extensions/magpie/icons/ppio-color.svg +1 -0
  159. package/extensions/magpie/icons/prime-agent.svg +1 -0
  160. package/extensions/magpie/icons/qiniu-color.svg +1 -0
  161. package/extensions/magpie/icons/qoder.svg +1 -0
  162. package/extensions/magpie/icons/qwen-color.svg +1 -0
  163. package/extensions/magpie/icons/qwenpaw.svg +1 -0
  164. package/extensions/magpie/icons/raycast.svg +1 -0
  165. package/extensions/magpie/icons/reasonix-color.svg +48 -0
  166. package/extensions/magpie/icons/reka.svg +1 -0
  167. package/extensions/magpie/icons/replicate.svg +1 -0
  168. package/extensions/magpie/icons/roocode.svg +1 -0
  169. package/extensions/magpie/icons/rtk.png +0 -0
  170. package/extensions/magpie/icons/sambanova-color.svg +1 -0
  171. package/extensions/magpie/icons/sensenova-color.svg +1 -0
  172. package/extensions/magpie/icons/siliconcloud-color.svg +1 -0
  173. package/extensions/magpie/icons/snow.png +0 -0
  174. package/extensions/magpie/icons/snowflake-color.svg +1 -0
  175. package/extensions/magpie/icons/sophnet-color.svg +1 -0
  176. package/extensions/magpie/icons/spark-color.svg +1 -0
  177. package/extensions/magpie/icons/stepfun-color.svg +1 -0
  178. package/extensions/magpie/icons/straico-color.svg +1 -0
  179. package/extensions/magpie/icons/submodel-color.svg +1 -0
  180. package/extensions/magpie/icons/t3code.svg +4 -0
  181. package/extensions/magpie/icons/targon-color.svg +1 -0
  182. package/extensions/magpie/icons/tencent-color.svg +1 -0
  183. package/extensions/magpie/icons/tencentcloud-color.svg +1 -0
  184. package/extensions/magpie/icons/tiangong-color.svg +1 -0
  185. package/extensions/magpie/icons/together-color.svg +1 -0
  186. package/extensions/magpie/icons/trae.svg +1 -0
  187. package/extensions/magpie/icons/typesafe.png +0 -0
  188. package/extensions/magpie/icons/upstage-color.svg +1 -0
  189. package/extensions/magpie/icons/venice-color.svg +1 -0
  190. package/extensions/magpie/icons/vercel.svg +1 -0
  191. package/extensions/magpie/icons/vertexai-color.svg +1 -0
  192. package/extensions/magpie/icons/vllm-color.svg +1 -0
  193. package/extensions/magpie/icons/volcengine-color.svg +1 -0
  194. package/extensions/magpie/icons/voyage-color.svg +1 -0
  195. package/extensions/magpie/icons/vscode.svg +1 -0
  196. package/extensions/magpie/icons/wenxin-color.svg +1 -0
  197. package/extensions/magpie/icons/windsurf.svg +1 -0
  198. package/extensions/magpie/icons/workbuddy-color.svg +1 -0
  199. package/extensions/magpie/icons/workersai-color.svg +1 -0
  200. package/extensions/magpie/icons/xai.svg +1 -0
  201. package/extensions/magpie/icons/xiaomimimo.svg +1 -0
  202. package/extensions/magpie/icons/xinference-color.svg +1 -0
  203. package/extensions/magpie/icons/yi.svg +1 -0
  204. package/extensions/magpie/icons/yuanbao-color.svg +1 -0
  205. package/extensions/magpie/icons/yylx.svg +5 -0
  206. package/extensions/magpie/icons/zai.svg +1 -0
  207. package/extensions/magpie/icons/zcode.png +0 -0
  208. package/extensions/magpie/icons/zed.svg +1 -0
  209. package/extensions/magpie/icons/zenmux.svg +1 -0
  210. package/extensions/magpie/icons/zeroone.svg +1 -0
  211. package/extensions/magpie/icons/zhipu-color.svg +1 -0
  212. package/extensions/magpie/icons/zoo-code.png +0 -0
  213. package/extensions/magpie/index.html +572 -0
  214. package/extensions/magpie/kernel.js +274 -0
  215. package/extensions/magpie/keyspend.js +63 -0
  216. package/extensions/magpie/layout.js +26 -0
  217. package/extensions/magpie/manifest.json +37 -0
  218. package/extensions/magpie/navlamp.js +47 -0
  219. package/extensions/magpie/omarchy.css +72 -0
  220. package/extensions/magpie/omarchy.js +46 -0
  221. package/extensions/magpie/passerrors.js +21 -0
  222. package/extensions/magpie/pluginpages.js +405 -0
  223. package/extensions/magpie/plugins/gateway/client.js +1194 -0
  224. package/extensions/magpie/plugins/gateway/keys.js +972 -0
  225. package/extensions/magpie/plugins/gateway/settings.js +459 -0
  226. package/extensions/magpie/plugins/library/client.js +6788 -0
  227. package/extensions/magpie/plugins/library/i18n.js +3228 -0
  228. package/extensions/magpie/plugins/library/library.css +848 -0
  229. package/extensions/magpie/plugins/otel/i18n.js +48 -0
  230. package/extensions/magpie/plugins/otel/page.js +189 -0
  231. package/extensions/magpie/plugins/redact/client.js +46 -0
  232. package/extensions/magpie/plugins/redact/demo.js +461 -0
  233. package/extensions/magpie/plugins/redact/i18n.js +432 -0
  234. package/extensions/magpie/plugins/redact/page.js +985 -0
  235. package/extensions/magpie/plugins/redact/redact.css +385 -0
  236. package/extensions/magpie/plugins/redact/settings.js +248 -0
  237. package/extensions/magpie/plugins/redact/xq_dark.jpg +0 -0
  238. package/extensions/magpie/plugins/redact/xq_dark.webm +0 -0
  239. package/extensions/magpie/plugins/redact/xq_light.jpg +0 -0
  240. package/extensions/magpie/plugins/redact/xq_light.webm +0 -0
  241. package/extensions/magpie/plugins/routing/client.js +8593 -0
  242. package/extensions/magpie/plugins/routing/i18n.js +2696 -0
  243. package/extensions/magpie/plugins/routing/routing.css +1111 -0
  244. package/extensions/magpie/plugins/search/settings.js +460 -0
  245. package/extensions/magpie/plugins/sessions/client.js +1622 -0
  246. package/extensions/magpie/plugins/sessions/i18n.js +360 -0
  247. package/extensions/magpie/plugins/sessions/sessions.css +181 -0
  248. package/extensions/magpie/plugins/sessions/settings.js +100 -0
  249. package/extensions/magpie/plugins/usage/client.js +150 -0
  250. package/extensions/magpie/plugins/usage/i18n.js +984 -0
  251. package/extensions/magpie/plugins/usage/page.js +4307 -0
  252. package/extensions/magpie/plugins.css +319 -0
  253. package/extensions/magpie/plugins.js +1926 -0
  254. package/extensions/magpie/profilerename.js +27 -0
  255. package/extensions/magpie/purposes.js +2 -0
  256. package/extensions/magpie/quitask.js +79 -0
  257. package/extensions/magpie/quotaall.js +142 -0
  258. package/extensions/magpie/ratewindows.js +123 -0
  259. package/extensions/magpie/registry.js +87 -0
  260. package/extensions/magpie/resetask.js +137 -0
  261. package/extensions/magpie/ruleedit.js +53 -0
  262. package/extensions/magpie/scrollends.js +91 -0
  263. package/extensions/magpie/settingshints.js +75 -0
  264. package/extensions/magpie/signsheets.js +86 -0
  265. package/extensions/magpie/start.js +26 -0
  266. package/extensions/magpie/syncfiles.js +216 -0
  267. package/extensions/magpie/theme.js +29 -0
  268. package/extensions/magpie/traywindows.js +41 -0
  269. package/extensions/magpie/webfonts.js +130 -0
  270. package/extensions/magpie/whatsnew.js +252 -0
  271. package/extensions/magpie.zip +0 -0
  272. package/knowledge/apps/baijiahao.md +30 -0
  273. package/knowledge/apps/bilibili.md +1 -0
  274. package/knowledge/apps/dedao.md +33 -0
  275. package/knowledge/apps/feishu.md +35 -0
  276. package/knowledge/apps/gmail.md +33 -0
  277. package/knowledge/apps/jike.md +31 -0
  278. package/knowledge/apps/kuaishou.md +34 -0
  279. package/knowledge/apps/linkedin.md +33 -0
  280. package/knowledge/apps/qqmail.md +30 -0
  281. package/knowledge/apps/telegram.md +27 -0
  282. package/knowledge/apps/toutiao.md +32 -0
  283. package/knowledge/apps/uber-eats.md +26 -0
  284. package/knowledge/apps/uber.md +27 -0
  285. package/knowledge/apps/whatsapp.md +27 -0
  286. package/knowledge/apps/x.md +32 -0
  287. package/knowledge/apps/xiaohongshu.md +1 -0
  288. package/knowledge/apps/zhihu.md +1 -0
  289. package/knowledge/sites/bilibili.com/SITE.md +50 -0
  290. package/knowledge/sites/bilibili.com/references/view-fields.md +17 -0
  291. package/knowledge/sites/github.com/SITE.md +51 -0
  292. package/knowledge/sites/github.com/references/repo-fields.md +13 -0
  293. package/knowledge/sites/x.com/SITE.md +47 -0
  294. package/knowledge/sites/xiaohongshu.com/SITE.md +48 -0
  295. package/knowledge/sites/xiaohongshu.com/references/note-fields.md +18 -0
  296. package/knowledge/sites/youtube.com/SITE.md +49 -0
  297. package/knowledge/sites/youtube.com/references/innertube-fields.md +14 -0
  298. package/knowledge/sites/zhihu.com/SITE.md +47 -0
  299. package/knowledge/sites/zhihu.com/references/api-fields.md +21 -0
  300. package/lib/mobile/extension-catalog.js +41 -1
  301. package/lib/mobile/extension-data.js +224 -0
  302. package/lib/mobile/extension-dev.js +3 -1
  303. package/lib/mobile/file-ops.js +252 -0
  304. package/lib/mobile/history-derive.js +80 -4
  305. package/lib/mobile/host-bridge/endpoints.js +48 -6
  306. package/lib/mobile/host-bridge/event-synthesis.js +33 -6
  307. package/lib/mobile/jobs-control.js +118 -0
  308. package/lib/mobile/memory.js +635 -0
  309. package/lib/mobile/model-badges.js +77 -0
  310. package/lib/mobile/pet-tools.js +50 -4
  311. package/lib/mobile/pets.js +214 -18
  312. package/lib/mobile/phone/access.js +85 -0
  313. package/lib/mobile/phone/evidence.js +228 -0
  314. package/lib/mobile/phone/gate.js +290 -0
  315. package/lib/mobile/phone/grounding.js +314 -0
  316. package/lib/mobile/phone/guard.js +465 -0
  317. package/lib/mobile/phone/irreversible.js +88 -8
  318. package/lib/mobile/phone/knowledge-tools.js +665 -0
  319. package/lib/mobile/phone/learn.js +329 -0
  320. package/lib/mobile/phone/redact.js +237 -0
  321. package/lib/mobile/phone/risk.js +95 -0
  322. package/lib/mobile/phone/routes.js +28 -1
  323. package/lib/mobile/phone/service.js +23 -0
  324. package/lib/mobile/phone/site-commands.js +271 -0
  325. package/lib/mobile/phone/step-summary.js +201 -0
  326. package/lib/mobile/phone/stop-summary.js +235 -0
  327. package/lib/mobile/phone/tool-groups.js +125 -0
  328. package/lib/mobile/phone/tools.js +589 -0
  329. package/lib/mobile/phone/untrusted.js +65 -0
  330. package/lib/mobile/phone-knowledge.js +20 -4
  331. package/lib/mobile/schedule-edit.js +500 -0
  332. package/lib/mobile/schedule.js +5 -0
  333. package/lib/mobile/send-continuation.js +52 -0
  334. package/lib/mobile/session-models.js +27 -31
  335. package/lib/mobile/session-send.js +12 -12
  336. package/lib/mobile/skill-import.js +338 -0
  337. package/lib/mobile/skill-store.js +524 -0
  338. package/lib/routes/mobile-api.js +42 -0
  339. package/package.json +7 -6
  340. package/skills/skill-authoring/SKILL.md +46 -0
  341. package/skills/skill-authoring/references/examples.md +82 -0
  342. package/skills/skill-authoring/references/frontmatter-and-fields.md +46 -0
  343. package/skills/skill-authoring/scripts/check_skill.py +167 -0
  344. package/skills/skill-authoring/scripts/manifest.json +3 -0
  345. package/knowledge/sites/bilibili.com.md +0 -27
  346. package/knowledge/sites/github.com.md +0 -27
  347. package/knowledge/sites/x.com.md +0 -28
  348. package/knowledge/sites/xiaohongshu.com.md +0 -27
  349. package/knowledge/sites/youtube.com.md +0 -28
  350. package/knowledge/sites/zhihu.com.md +0 -27
@@ -0,0 +1,27 @@
1
+ // A profile's Rename (#2721, erlinerd): the details' name becomes a field
2
+ // holding it, Enter renames through profileAction (its pending chip and
3
+ // rollback), Escape or a click away puts the name back. app.js imports
4
+ // this when Rename is pressed, so it weighs nothing on the core.
5
+ window.profRename = function (d, p) {
6
+ const name = d.querySelector(".pd-name");
7
+ if (!name) return;
8
+ const input = el("input", "chip-input pd-rename-input");
9
+ input.value = p.name;
10
+ input.setAttribute("aria-label", t("Profile name"));
11
+ // while it is open the field has the head, × aside: In use, Apply and
12
+ // Rename left a narrow panel's field no room to type in
13
+ const rest = [...d.querySelectorAll(".pd-cur, .pd-apply, .pd-rename")];
14
+ const show = (on) => rest.forEach((b) => (b.hidden = !on));
15
+ show(false);
16
+ const back = () => { if (input.isConnected) { input.replaceWith(name); show(true); } };
17
+ input.onkeydown = (e) => {
18
+ e.stopPropagation(); // Escape is the field's, not the details'
19
+ const to = input.value.trim();
20
+ if (e.key === "Enter" && to && to !== p.name) { input.onblur = null; profileAction("rename", p.name, false, to); }
21
+ else if (e.key === "Enter" || e.key === "Escape") back();
22
+ };
23
+ input.onblur = () => setTimeout(back, 100);
24
+ name.replaceWith(input);
25
+ input.focus(NO_SCROLL);
26
+ input.select();
27
+ };
@@ -0,0 +1,2 @@
1
+ // Code generated by go generate ./internal/usage; DO NOT EDIT.
2
+ const REQUEST_KINDS = {"agent_job":{"purpose":"kind:collab_spawn","name":"Subagent"},"ambient_suggestion_safety":{"purpose":"kind:ambient_suggestions","name":"Suggestions"},"ambient_suggestions":{"purpose":"kind:ambient_suggestions","name":"Suggestions"},"api_detect":{"purpose":"kind:api_detect","name":"Detect APIs"},"auto_review":{"purpose":"kind:guardian","name":"Approval check"},"cache_keepalive":{"purpose":"kind:cache_keepalive","name":"Cache keep-alive"},"collab_spawn":{"purpose":"kind:collab_spawn","name":"Subagent"},"compact":{"purpose":"kind:compact","name":"Compaction"},"composer_predictions":{"purpose":"kind:composer_predictions","name":"Prediction"},"connection_check":{"purpose":"kind:connection_check","name":"Connection check"},"guardian":{"purpose":"kind:guardian","name":"Approval check"},"guardian_review":{"purpose":"kind:guardian","name":"Approval check"},"luna_reserve":{"purpose":"kind:luna_reserve","name":"Luna Reserve"},"mcp":{"purpose":"kind:mcp","name":"MCP tool"},"memgen":{"purpose":"kind:memory_consolidation","name":"Memory"},"memory":{"purpose":"kind:memory_consolidation","name":"Memory"},"memory_consolidation":{"purpose":"kind:memory_consolidation","name":"Memory"},"model_list":{"purpose":"kind:model_list","name":"Model list"},"provider_test":{"purpose":"kind:provider_test","name":"Test"},"review":{"purpose":"kind:review","name":"Review"},"routing":{"purpose":"kind:routing","name":"Routing classifier"},"thread_description":{"purpose":"kind:thread_description","name":"Description"},"thread_spawn":{"purpose":"kind:collab_spawn","name":"Subagent"},"thread_title":{"purpose":"kind:thread_title","name":"Title"},"thread_title_reconsideration":{"purpose":"kind:thread_title","name":"Title"},"title":{"purpose":"kind:thread_title","name":"Title"},"title_generation":{"purpose":"kind:thread_title","name":"Title"},"vision":{"purpose":"kind:vision","name":"Image description"},"web_search":{"purpose":"kind:web_search","name":"Web search"}};
@@ -0,0 +1,79 @@
1
+ // The question a Quit the user chose puts while agents are connected to
2
+ // magpie's gateway (quitask.go; Aisiverto on X: Codex failed with 502s
3
+ // after magpie was quit, and nothing said why). It names the agents that
4
+ // stop working, says that closing the window keeps magpie running, and
5
+ // offers Quit, Disconnect and quit (each agent's own settings put back,
6
+ // as its row's Disconnect does), or Cancel, with Don't ask again above
7
+ // them (zhujiu on Discord), which Settings › General › Ask before quitting
8
+ // turns back on. app.js imports this when the
9
+ // question is put, so it weighs nothing on the core.
10
+ window.askQuitSheet = async function () {
11
+ let agents;
12
+ try { ({ agents } = await api("quit")); } catch { agents = null; }
13
+ // nothing connected any more (disconnected meanwhile): the Quit goes on
14
+ if (agents && !agents.length) return api("window/quit?now=1", {});
15
+ const ed = el("div", "editor quit-ask");
16
+ const head = el("div", "ehead");
17
+ head.append(el("b", "", t("Quit magpie?")));
18
+ ed.append(head);
19
+ const one = agents?.length === 1;
20
+ ed.append(el("p", "lib-confirm", one
21
+ ? t("{agent} goes through magpie, so it stops working while magpie is closed.", { agent: agents[0].name })
22
+ : t("These agents go through magpie, so they stop working while magpie is closed.")));
23
+ if (agents) {
24
+ const list = el("ul", "quit-agents");
25
+ for (const a of agents) {
26
+ const li = el("li");
27
+ li.append(icon(a.icon), el("span", "", a.name));
28
+ list.append(li);
29
+ }
30
+ ed.append(list);
31
+ }
32
+ const mac = document.body.classList.contains("mac");
33
+ ed.append(el("p", "ag-note", mac ? t("Closing the window keeps magpie running in the menu bar.")
34
+ : t("Closing the window keeps magpie running in the system tray.")));
35
+ // Don't ask again goes with the Quit chosen (noAsk=1 saves it), not with a Cancel
36
+ const [never, box] = tick(t("Don't ask again"), false);
37
+ never.classList.add("quit-never");
38
+ never.title = t("Settings › General › Ask before quitting turns it back on");
39
+ ed.append(never);
40
+ const bye = () => api("window/quit?now=1" + (box.checked ? "&noAsk=1" : ""), {});
41
+ const bar = el("div", "bar");
42
+ const quit = el("button", "text primary", t("Quit"));
43
+ const off = el("button", "text", t("Disconnect and quit"));
44
+ off.title = t("Puts back each agent's own settings first, so they work without magpie");
45
+ const cancel = el("button", "text", t("Cancel"));
46
+ const busy = (b, on) => {
47
+ for (const x of [quit, off, cancel, box]) x.disabled = on;
48
+ b.classList.toggle("busy", on);
49
+ };
50
+ quit.onclick = async (e) => {
51
+ e.stopPropagation();
52
+ busy(quit, true);
53
+ try { await bye(); } catch (err) { busy(quit, false); statusErr(err); }
54
+ };
55
+ // each agent put back as its row's Disconnect puts it back; one that
56
+ // can't be leaves magpie open, with the rest still listed
57
+ off.onclick = async (e) => {
58
+ e.stopPropagation();
59
+ busy(off, true);
60
+ for (const a of agents || []) {
61
+ try {
62
+ state = await api("agents/disconnect/" + a.id, {});
63
+ } catch (err) {
64
+ busy(off, false);
65
+ status(a.name + ": " + t(err.message), "err");
66
+ return;
67
+ }
68
+ }
69
+ try { await bye(); } catch (err) { busy(off, false); statusErr(err); }
70
+ };
71
+ cancel.onclick = cancelAsk;
72
+ if (agents) bar.append(off);
73
+ bar.append(el("span", "grow"), cancel, quit);
74
+ ed.append(bar);
75
+ confirmAsk = ed;
76
+ openModal(ed);
77
+ $("#modal").classList.add("lib");
78
+ quit.focus(NO_SCROLL);
79
+ };
@@ -0,0 +1,142 @@
1
+ // A provider's accounts together (CryptoSarom on X: how much of a model is
2
+ // left matters more than which account it is on). app.js imports this once
3
+ // some provider has two accounts (quotaAllJS), and the Routing page once a
4
+ // group's row says what its accounts have left, so it weighs nothing on
5
+ // the core.
6
+ //
7
+ // mergeQuotas: one provider's accounts as one figure a window. Only what
8
+ // reads alike is put together: a counted window (WorkBuddy's credits) adds
9
+ // its amounts and limits, a wallet its balances, each in one unit; a window
10
+ // said only as a share is averaged, and only over accounts on one plan,
11
+ // whose windows are the same size, so the mean is the share of their
12
+ // allowance together. A share is never added to a count. An account whose
13
+ // reading failed is not 0 but unknown: it is left out and counted as that;
14
+ // so is a window an earlier reading (asOf) stands in for whose reset has
15
+ // passed. A window only the vendor's text says has nothing to add up and is
16
+ // left out. A window the account can't spend, held by another used up that
17
+ // starts again after it (heldBy: 0% of 7 days left, 100% of 5 hours), has
18
+ // nothing left to count: it adds its whole as used (#4791). Unlimited only where every account read says so, and one
19
+ // account's window alone is that account's, already below: only what two
20
+ // or more have goes together (a Plus and a Pro account have no 5 hours in
21
+ // common). The windows keep their family and pool, so familyWindows folds
22
+ // them as an account's. accounts: those with a reading; unknown: those
23
+ // without.
24
+ //
25
+ // quotaAll: the strip of them under a Usage card's or the tray panel's
26
+ // head: "All 3 accounts", each window's figure and a thin bar, and how many
27
+ // couldn't be read. Nothing when fewer than two accounts read, or nothing
28
+ // of theirs adds up. No account is named in it (Hide accounts). A click on
29
+ // it, or its chevron, folds the accounts under it away (justfreefall on X),
30
+ // remembered by provider for the window and the panel alike
31
+ // (magpie.usageAllFolded): quotaAllFolded says whether a provider's are.
32
+ function mergeQuotas(subs, now = Date.now()) {
33
+ const read = subs.filter((q) => !q.error && (q.windows?.length || q.balance));
34
+ const unknown = subs.length - read.length;
35
+ const plans = new Set(read.map((q) => q.plan || ""));
36
+ const by = new Map();
37
+ for (const q of read) {
38
+ for (const w of q.windows || []) {
39
+ if (w.text) continue;
40
+ const kind = w.unlimited ? "u" : w.limit > 0 ? "c" : w.balance != null ? "b" : w.used != null ? "p:" + (q.plan || "") : "";
41
+ if (!kind) continue;
42
+ const k = JSON.stringify([w.name, w.family || "", w.pool || "", w.unit || "", kind]);
43
+ let m = by.get(k);
44
+ if (!m) by.set(k, (m = { w, kind, plan: q.plan || "", n: 0, stale: 0, held: 0, amount: 0, limit: 0, balance: 0, used: 0, soon: null }));
45
+ if (q.asOf && w.resetsAt && Date.parse(w.resetsAt) <= now) { m.stale++; continue; }
46
+ m.n++;
47
+ const held = w.heldBy && kind !== "u" && kind !== "b";
48
+ if (held) m.held++;
49
+ if (kind === "c") { m.amount += held ? w.limit : Math.max(0, w.amount || 0); m.limit += w.limit; }
50
+ else if (kind === "b") m.balance += w.balance;
51
+ else if (kind !== "u") m.used += held ? 100 : to100(w.used);
52
+ const at = w.resetsAt && Date.parse(w.resetsAt);
53
+ if (at > now && (m.soon == null || at < m.soon)) m.soon = at;
54
+ }
55
+ }
56
+ const windows = [];
57
+ for (const m of by.values()) {
58
+ if (m.n < 2) continue; // one account's is already below
59
+ if (m.kind === "u" && m.n < read.length) continue;
60
+ const { w } = m;
61
+ const out = { name: w.name, family: w.family, pool: w.pool, unit: w.unit, accounts: m.n, unknown: m.stale, held: m.held, how: m.kind[0] };
62
+ if (m.kind === "u") out.unlimited = true;
63
+ else if (m.kind === "c") Object.assign(out, { amount: m.amount, limit: m.limit, used: (100 * m.amount) / m.limit });
64
+ else if (m.kind === "b") out.balance = m.balance;
65
+ else {
66
+ out.used = Math.round((10 * m.used) / m.n) / 10;
67
+ if (plans.size > 1) out.plan = m.plan;
68
+ }
69
+ if (m.soon) out.firstReset = new Date(m.soon).toISOString();
70
+ windows.push(out);
71
+ }
72
+ return { accounts: read.length, unknown, windows };
73
+ }
74
+ // quotaAll: the strip (above)
75
+ function quotaAll(subs, cls = "", max = Infinity) {
76
+ if (subs.length < 2) return null;
77
+ const all = mergeQuotas(subs);
78
+ if (all.accounts < 2 || !all.windows.length) return null;
79
+ const box = el("div", "quota-all" + (cls ? " " + cls : ""));
80
+ const lead = el("span", "quota-all-head", t("All {n} accounts", { n: subs.length }));
81
+ lead.title = t("These accounts' windows together: amounts of one unit are added up; a window said only as a share is the mean of the accounts on one plan, which have the same allowance. Each account is below.");
82
+ const p = subs[0].provider, open = !quotaAllFolded(p);
83
+ const fold = el("button", "quota-acct-fold quota-all-fold");
84
+ fold.type = "button";
85
+ fold.append(svg(CHEV, 11, 1.6));
86
+ fold.setAttribute("aria-expanded", String(open));
87
+ fold.title = t(open ? "Fold" : "Unfold");
88
+ box.dataset.fold = p;
89
+ box.append(lead, fold);
90
+ box.onclick = () => {
91
+ // text being selected in it is not a click on it
92
+ if (String(getSelection()).trim() && box.contains(getSelection().anchorNode)) return;
93
+ const had = document.activeElement === fold;
94
+ setQuotaAllFolded(p, open);
95
+ if (had) document.querySelector(`.quota-all[data-fold="${CSS.escape(p)}"] .quota-all-fold`)?.focus(NO_SCROLL);
96
+ if (mode !== "panel") backToReader($("#view-usage"));
97
+ };
98
+ const ws = familyWindows(all.windows);
99
+ for (const w of ws.slice(0, max)) {
100
+ const item = el("span", "quota-all-w");
101
+ const name = t(w.name) + (w.plan ? " · " + w.plan : "");
102
+ item.append(el("span", "quota-all-name", name), el("span", "quota-all-n", quotaText(w)));
103
+ if (!w.unlimited && !quotaNoShare(w)) {
104
+ const bar = el("span", "quota-all-bar" + (to100(w.used) >= 90 ? " full" : ""));
105
+ const fill = el("i");
106
+ fill.style.width = quotaFill(w) + "%";
107
+ bar.append(fill);
108
+ item.append(bar);
109
+ }
110
+ item.title = tipLines(
111
+ name + " · " + quotaText(w),
112
+ t(w.how === "p" ? "The mean of {n} accounts on one plan" : w.how === "u" ? "Unlimited on each of {n} accounts" : "Added up over {n} accounts", { n: w.accounts }),
113
+ w.unknown && t("{n} more read before it started again: left out", { n: w.unknown }),
114
+ w.held && t("{n} of them can't spend theirs before a longer window used up starts again: counted as none left", { n: w.held }),
115
+ w.firstReset && t("The first of them starts again {when}", { when: resetClock(new Date(w.firstReset)) }),
116
+ w.tiers && "\n" + tiersText(w));
117
+ box.append(item);
118
+ }
119
+ if (all.unknown) {
120
+ const u = el("span", "quota-all-unknown", t("{n} couldn't be read", { n: all.unknown }));
121
+ u.title = t("An account whose reading failed isn't counted as empty or full: it is left out of these figures");
122
+ box.append(u);
123
+ }
124
+ return box;
125
+ }
126
+ // read on first use: loading this file touches nothing of app.js's
127
+ let allFolded;
128
+ const allFolds = () => (allFolded ||= new Set(kept("usageAllFolded", [])));
129
+ globalThis.addEventListener?.("storage", (e) => { if (e.key === "magpie.usageAllFolded") { allFolded = null; renderQuotas(); } });
130
+ const quotaAllFolded = (p) => allFolds().has(p);
131
+ // setQuotaAllFolded: true when it changed
132
+ function setQuotaAllFolded(p, fold) {
133
+ if (fold === allFolds().has(p)) return false;
134
+ if (fold) allFolded.add(p); else allFolded.delete(p);
135
+ keep("usageAllFolded", [...allFolded]);
136
+ renderQuotas();
137
+ return true;
138
+ }
139
+ window.mergeQuotas = mergeQuotas;
140
+ window.quotaAllFolded = quotaAllFolded;
141
+ window.setQuotaAllFolded = setQuotaAllFolded;
142
+ window.quotaAll = quotaAll;
@@ -0,0 +1,123 @@
1
+ // A provider's rate windows (#2463) in its editor: the hours it bills at
2
+ // its own rate, a peak at 2x or a discount off peak. app.js imports this
3
+ // when an editor draws the field (rateWindowsField), so it weighs nothing
4
+ // on the core. Each window is its days (none ticked: every day), a span
5
+ // that may cross midnight, and the rate the price is times inside it, on
6
+ // top of the price rate; the first window holding a call wins.
7
+
8
+ // rateWindowsPaint draws d's windows into box, and Add a window under
9
+ // them while there is room for one more.
10
+ window.rateWindowsPaint = function paint(box, d) {
11
+ box.replaceChildren();
12
+ if (!box.nextElementSibling) {
13
+ const help = el("div", "hint", t("Inside a window a call costs the official price times its rate, on top of the price rate; outside every window the price rate alone counts. The first window that holds a call wins. Times are on the clock of the computer magpie runs on, and 22:00–6:00 crosses midnight. No day ticked is every day. A price you set for a model is scaled by the window too. Changing the windows prices the calls already counted again, as a changed price rate does."));
14
+ // a list price with the vendor's own peak has it already (#2463,
15
+ // the author): a window over the same hours would double it again
16
+ help.append(el("div", "", t("DeepSeek's own API, Command Code, OpenCode Go and Ollama Cloud already count their DeepSeek models' peak hours at double: don't add those hours as a window, or they count twice.")));
17
+ help.id = "rate-windows-help";
18
+ box.after(help);
19
+ box.setAttribute("role", "group");
20
+ box.setAttribute("aria-describedby", help.id);
21
+ }
22
+ for (const w of d.rateWindows || []) box.append(rateWindowRow(w, d, () => paint(box, d)));
23
+ if ((d.rateWindows || []).length < 8) {
24
+ const add = el("button", "text", t("Add a window"));
25
+ add.type = "button";
26
+ add.onclick = () => { (d.rateWindows ??= []).push({ days: [], start: "", end: "", rate: "" }); paint(box, d); };
27
+ box.append(add);
28
+ }
29
+ };
30
+
31
+ // rateWindowRow is one window of the editor, its draft w kept as typed.
32
+ function rateWindowRow(w, d, repaint) {
33
+ const row = el("div", "rate-win"), chips = el("div", "day-chips");
34
+ const names = [t("Sun"), t("Mon"), t("Tue"), t("Wed"), t("Thu"), t("Fri"), t("Sat")];
35
+ for (let i = 0; i < 7; i++) {
36
+ // a new array each press: the draft's window is a shallow copy of the
37
+ // saved one, which keeps its own days
38
+ const mark = () => {
39
+ const on = (w.days || []).includes(i);
40
+ b.classList.toggle("on", on);
41
+ b.setAttribute("aria-pressed", String(on));
42
+ };
43
+ const b = el("button", "day-chip", names[i]);
44
+ b.type = "button";
45
+ mark();
46
+ b.onclick = () => {
47
+ const days = (w.days || []).filter((x) => x !== i);
48
+ w.days = days.length < (w.days || []).length ? days : [...days, i].sort((a, z) => a - z);
49
+ mark();
50
+ };
51
+ chips.append(b);
52
+ }
53
+ const rate = input(w.rate, "3");
54
+ rate.classList.add("rate-rate");
55
+ rate.inputMode = "decimal";
56
+ rate.setAttribute("aria-label", t("Rate in this window"));
57
+ rate.oninput = () => { w.rate = rate.value; };
58
+ const del = el("button", "text rate-win-del", "×");
59
+ del.type = "button";
60
+ del.title = t("Remove this window");
61
+ del.setAttribute("aria-label", del.title);
62
+ del.onclick = () => {
63
+ const at = (d.rateWindows || []).indexOf(w);
64
+ if (at >= 0) d.rateWindows.splice(at, 1);
65
+ repaint();
66
+ };
67
+ row.append(chips, timePick(w, "start", t("Window starts at")), el("span", "rate-dash", "–"),
68
+ timePick(w, "end", t("Window ends at")), rate, del);
69
+ return row;
70
+ }
71
+
72
+ // timePick is a window's start or end as the app's own menu of half
73
+ // hours, not typed; only an end may be 24:00.
74
+ function timePick(w, key, label) {
75
+ const b = el("button", "sess-pick rate-time");
76
+ b.type = "button";
77
+ b.setAttribute("aria-haspopup", "menu");
78
+ b.setAttribute("aria-expanded", "false");
79
+ b.setAttribute("aria-label", label);
80
+ const paint = () => {
81
+ b.dataset.value = w[key] || "";
82
+ b.replaceChildren(el("span", "", w[key] || "--:--"), svg(CHEV, 11, 1.6));
83
+ };
84
+ b.onclick = (e) => {
85
+ e.preventDefault();
86
+ e.stopPropagation();
87
+ if (b.classList.contains("open")) return closeProtoMenu();
88
+ const times = [];
89
+ for (let m = 0; m < 1440; m += 30) times.push(String(Math.floor(m / 60)).padStart(2, "0") + ":" + String(m % 60).padStart(2, "0"));
90
+ if (key === "end") times.push("24:00");
91
+ openProtoMenu(b, times.map((v) => ({ v, name: v, note: "", literalName: true })), w[key], (v) => { w[key] = v; paint(); }, label, "sess-menu");
92
+ };
93
+ paint();
94
+ return b;
95
+ }
96
+
97
+ // clockOf reads "H:MM" or "HH:MM", 24:00 included; null for what isn't one.
98
+ function clockOf(v) {
99
+ const m = String(v ?? "").trim().match(/^(\d{1,2}):(\d{2})$/);
100
+ if (!m) return null;
101
+ const min = +m[1] * 60 + +m[2];
102
+ return +m[1] > 24 || +m[2] > 59 || min > 1440 ? null : min;
103
+ }
104
+
105
+ // rateWindowsOfDraft is the draft's windows as saved, null for none, or
106
+ // false when one is typed wrong (provider.CheckRateWindows' rules). Not
107
+ // loaded yet, the save leaves them out, which keeps them as they are.
108
+ window.rateWindowsOfDraft = function () {
109
+ const out = [];
110
+ for (const w of draft.rateWindows || []) {
111
+ const start = clockOf(w.start), end = clockOf(w.end);
112
+ if (start === null || start === 1440 || end === null || start === end) return false;
113
+ const n = decimalOf(String(w.rate ?? "").trim());
114
+ if (!(n > 0 && n <= 1000) || Math.abs(n * 1000 - Math.round(n * 1000)) > 1e-6) return false;
115
+ out.push({ days: [...(w.days || [])], start: String(w.start).trim(), end: String(w.end).trim(), rate: n });
116
+ }
117
+ return out.length ? out : null;
118
+ };
119
+
120
+ window.rateWindowsError = function (ed) {
121
+ shown(ed.querySelector(".rate-windows input"))?.focus({ preventScroll: true });
122
+ return editorError(t("Rate windows: every window is days, a time like 09:00, another time for it to end, and a rate from 0 to 1000 with at most three decimals"), "warn");
123
+ };
@@ -0,0 +1,87 @@
1
+ // The Plugins page's npm registry row: the registry plugins are installed
2
+ // from and npm is asked of, and where it came from (the user's .npmrc,
3
+ // bunfig or shell, the 「国内镜像」 switch, npm's own); one set here comes
4
+ // before all of them (timwangdev on X). plugins.js imports this when
5
+ // Discover draws the row, so it weighs nothing on the core. The row is
6
+ // plugins.js's box, kept across its redraws, and drawn here alone: a draft
7
+ // and its error stay while the page is drawn again.
8
+ (() => {
9
+ let box = null, mine = null, askNPM = null;
10
+ let editing = false, draft = "", err = "", saving = false;
11
+
12
+ function from(r) {
13
+ switch (r.from) {
14
+ case "magpie": return t("set here");
15
+ case "env": return t("from {name}", { name: r.var });
16
+ case "shell": return t("from your shell's {name}", { name: r.var });
17
+ case "bunfig": case "npmrc": return t("from {name}", { name: r.path });
18
+ case "china": return t("Mirrors in China");
19
+ }
20
+ return t("npm's own");
21
+ }
22
+
23
+ function button(label, cls, fn) {
24
+ const b = el("button", cls, label);
25
+ b.type = "button";
26
+ b.disabled = saving;
27
+ b.onclick = fn;
28
+ return b;
29
+ }
30
+
31
+ async function set(url) {
32
+ saving = true;
33
+ draw();
34
+ try {
35
+ const r = await api("plugins/registry", { url });
36
+ mine.registry = r.registry;
37
+ editing = false;
38
+ draft = err = "";
39
+ // npm's answers again, now from this registry
40
+ askNPM();
41
+ } catch (e) {
42
+ err = t(e.message);
43
+ statusTErr(e);
44
+ }
45
+ saving = false;
46
+ draw();
47
+ }
48
+
49
+ function draw() {
50
+ const r = mine.registry;
51
+ box.replaceChildren(el("span", "pm-mlabel", t("npm registry")));
52
+ if (!editing) {
53
+ box.append(el("code", "pm-reg-url", r.url), el("span", "pm-reg-from", from(r)), el("span", "grow"),
54
+ button(t("Change"), "text", () => { editing = true; draft = r.from === "magpie" ? r.url : ""; draw(); }));
55
+ if (r.from === "magpie") {
56
+ const def = button(t("Use default"), "text", () => set(""));
57
+ def.title = t("Use the registry your .npmrc, .bunfig.toml or shell names, or npm's own");
58
+ box.append(def);
59
+ }
60
+ return;
61
+ }
62
+ const i = el("input", "pm-minput pm-reg-input");
63
+ i.value = draft;
64
+ i.placeholder = "https://registry.npmmirror.com";
65
+ i.spellcheck = false;
66
+ i.autocomplete = "off";
67
+ i.setAttribute("aria-label", t("npm registry"));
68
+ i.oninput = () => { draft = i.value; };
69
+ const save = button(t("Save"), "text primary", () => {
70
+ const v = i.value.trim();
71
+ if (v) set(v); else i.focus(NO_SCROLL);
72
+ });
73
+ const cancel = button(t("Cancel"), "text", () => { editing = false; draft = err = ""; draw(); });
74
+ cancel.disabled = false;
75
+ i.onkeydown = (ev) => { ev.stopPropagation(); if (ev.key === "Enter") save.onclick(); else if (ev.key === "Escape") cancel.onclick(); };
76
+ box.append(i, cancel, save);
77
+ if (err) box.append(el("div", "pm-reg-err", err));
78
+ i.focus(NO_SCROLL);
79
+ }
80
+
81
+ // plugins.js calls this at each redraw; an open field is left as it is
82
+ window.pmRegistry = (b, m, ask) => {
83
+ const fresh = box !== b;
84
+ box = b; mine = m; askNPM = ask;
85
+ if (fresh || !editing) draw();
86
+ };
87
+ })();
@@ -0,0 +1,137 @@
1
+ // A Codex reset's confirmation (askReset in app.js): spending a reset
2
+ // can't be taken back, so it asks first, names the account and which of
3
+ // its resets is used, and lets the user pick another (01huadalang on
4
+ // Discord). app.js imports this when a reset's Use is pressed, so it
5
+ // weighs nothing on the core.
6
+ window.askResetSheet = function (q) {
7
+ const ed = el("div", "editor reset-ask");
8
+ const head = el("div", "ehead");
9
+ head.append(icon(q.icon || "codex"), el("b", "", t("Use a Codex reset?")));
10
+ ed.append(head);
11
+ const who = q.alias || q.user || q.name;
12
+ ed.append(el("p", "lib-confirm", t(q.resets.count === 1
13
+ ? "{who} has 1 reset. Using it starts its windows again at once, as if none of them had been used. It can't be undone."
14
+ : "{who} has {n} resets. Using one starts its windows again at once, as if none of them had been used. It can't be undone.", { who, n: q.resets.count })));
15
+ // a remote magpie's card (office/codex, #3850): that magpie spends it,
16
+ // on the account signed in there
17
+ const remote = q.provider !== "codex" && q.from;
18
+ if (remote) ed.append(el("p", "lib-confirm reset-remote", t("The magpie on {computer}, where this account is signed in, uses it.", { computer: q.from })));
19
+ // which of them is used: the one that runs out first unless the user
20
+ // picks another of those listed one by one (01huadalang on Discord),
21
+ // and the dialog names the one it spends
22
+ const cards = q.resets.each?.length > 1 && q.resets.each.every((c) => c.id) ? q.resets.each : null;
23
+ let chosen = 0;
24
+ const which = el("p", "lib-confirm reset-which");
25
+ const sooner = el("p", "lib-confirm reset-sooner");
26
+ const cardName = (i, at = resetClock) => {
27
+ const c = cards[i];
28
+ return c.until ? t("Reset {n} runs out {when}", { n: i + 1, when: at(new Date(c.until)) }) : t("Reset {n} never runs out", { n: i + 1 });
29
+ };
30
+ const sayWhich = () => {
31
+ const full = (d) => d.toLocaleString(intlLang());
32
+ if (!chosen) {
33
+ which.textContent = q.resets.until ? t("The one used is the one that runs out first, {when}.", { when: full(new Date(q.resets.until)) }) : "";
34
+ which.hidden = !q.resets.until;
35
+ sooner.hidden = true;
36
+ return;
37
+ }
38
+ const c = cards[chosen];
39
+ which.textContent = c.until ? t("The one used is {who}'s reset that runs out {when}.", { who, when: full(new Date(c.until)) })
40
+ : t("The one used is {who}'s reset that never runs out.", { who });
41
+ which.hidden = false;
42
+ // one that runs out sooner is left, and lost if it isn't used in time
43
+ sooner.textContent = cards[0].until ? t("The one that runs out first, {when}, is kept, and is lost if it isn't used by then.", { when: full(new Date(cards[0].until)) }) : "";
44
+ sooner.hidden = !cards[0].until || cards[0].until === c.until;
45
+ };
46
+ if (cards) {
47
+ const row = el("div", "reset-pick");
48
+ const pick = el("button", "sess-pick reset-card");
49
+ pick.type = "button";
50
+ pick.setAttribute("aria-haspopup", "menu");
51
+ pick.setAttribute("aria-expanded", "false");
52
+ pick.append(el("span", "", cardName(0)), svg(CHEV, 11, 1.6));
53
+ pick.title = cardName(0, (d) => d.toLocaleString(intlLang()));
54
+ pick.onclick = (e) => {
55
+ e.stopPropagation();
56
+ if (pick.classList.contains("open")) return closeProtoMenu();
57
+ openProtoMenu(pick, cards.map((c, i) => ({ v: String(i), name: cardName(i), note: i ? "" : t("Used unless you pick another"), literalName: true })), String(chosen), (v) => {
58
+ chosen = Number(v);
59
+ pick.firstChild.textContent = cardName(chosen);
60
+ pick.title = cardName(chosen, (d) => d.toLocaleString(intlLang()));
61
+ pick.classList.toggle("set", chosen > 0);
62
+ sayWhich();
63
+ }, "Reset to use", "sess-menu");
64
+ };
65
+ row.append(el("span", "reset-pick-label", t("Reset to use")), pick);
66
+ ed.append(row);
67
+ }
68
+ sayWhich();
69
+ ed.append(which, sooner);
70
+ // nothing used yet: a reset would start nothing again
71
+ if (!q.windows?.some((w) => w.used > 0)) ed.append(el("p", "lib-confirm reset-idle", t("None of its windows has been used yet, so there is nothing to start again.")));
72
+ const bar = el("div", "bar");
73
+ const go = el("button", "text primary", t("Use a reset"));
74
+ go.onclick = async (e) => {
75
+ e.stopPropagation();
76
+ go.disabled = true;
77
+ go.classList.add("busy");
78
+ try {
79
+ const ask = { user: q.user || "" };
80
+ if (remote) ask.provider = q.provider;
81
+ if (chosen) ask.credit = cards[chosen].id;
82
+ const out = await api("usage/codex-reset", ask);
83
+ // the providers' editor and Settings read the account's windows
84
+ // and resets on their own, kept a minute: read again, as Usage is
85
+ // (#1491); asked from the editor, closing goes back to it
86
+ delete loginUsage.codex;
87
+ closeConfirmAsk();
88
+ status(who + ": " + resetOutcome(out), out.code === "reset" ? "ok" : "err");
89
+ if (editing && view === "providers") renderProviders();
90
+ if (window.autoResetOwnPaint && $("#codexAutoResetRow")) autoResetOwnPaint(prefs);
91
+ loadQuotas();
92
+ } catch (err) {
93
+ go.disabled = false;
94
+ go.classList.remove("busy");
95
+ status(remote ? remoteResetError(err.message) : err.message, "err");
96
+ }
97
+ };
98
+ const cancel = el("button", "text", t("Cancel"));
99
+ cancel.onclick = cancelAsk;
100
+ bar.append(el("span", "grow"), cancel, go);
101
+ ed.append(bar);
102
+ // asked from a provider's editor (roamlog on X): Cancel goes back to it
103
+ if (editing && view === "providers") ed.back = renderProviders;
104
+ confirmAsk = ed;
105
+ openModal(ed);
106
+ $("#modal").classList.add("lib");
107
+ go.focus();
108
+ };
109
+
110
+ // remoteResetError says why a remote magpie used no reset (remote_quotas.go)
111
+ function remoteResetError(err) {
112
+ if (/^this key is read-only on the remote magpie/.test(err)) return t("This key is read-only on that computer's magpie — give this provider a key with full access to use a reset from here");
113
+ if (/didn't say in time whether the reset was used/.test(err)) return t("That computer didn't say in time whether the reset was used — it may have been. Refresh the card before asking again");
114
+ if (/^remote magpie doesn't share its quotas/.test(err)) return t("That computer's magpie can't use a reset from here yet — update magpie there");
115
+ if (/^remote magpie holds this key/.test(err)) return t("That computer's magpie holds this key to a budget, models or accounts, so it uses none of its resets — give this provider a key with full access");
116
+ if (/^remote magpie isn't shared/.test(err)) return t("That computer's magpie isn't shared — turn on Settings → Network and sharing → Share on local network there");
117
+ if (/^remote magpie didn't take this key/.test(err)) return t("That computer's magpie didn't take this key — use one of its gateway keys in this provider");
118
+ if (/^couldn't reach the remote magpie/.test(err)) return t("Couldn't reach that computer's magpie — check that it is running and its address");
119
+ return err;
120
+ }
121
+
122
+ // what a reset came to, in words (moved here from app.js; only this sheet says it)
123
+ function resetOutcome(out) {
124
+ switch (out.code) {
125
+ case "reset": return t(out.windows === 1 ? "1 window started again" : "{n} windows started again", { n: out.windows });
126
+ case "nothing_to_reset": return t("nothing to start again — no window has been used, and the reset is kept");
127
+ case "no_credit": return t("no reset left on the account");
128
+ case "already_redeemed":
129
+ case "already_used": return t("that reset was already used");
130
+ // Anthropic's
131
+ case "not_limited": return t("nothing to reset — no window is used up yet, and the reset is kept");
132
+ case "cooldown": return t("a reset was used a short while ago — try again later");
133
+ case "ineligible": return t("the account can't use a reset");
134
+ case "unavailable": return t("resets can't be used right now — try again later");
135
+ }
136
+ return out.code;
137
+ }