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.
- package/extensions/catalog.json +10 -1
- package/extensions/magpie/agentcompact.js +104 -0
- package/extensions/magpie/app.css +4902 -0
- package/extensions/magpie/app.js +24474 -0
- package/extensions/magpie/autobackup.js +185 -0
- package/extensions/magpie/autoresetown.js +151 -0
- package/extensions/magpie/balfound.js +38 -0
- package/extensions/magpie/boot.js +23 -0
- package/extensions/magpie/cfgblocked.js +39 -0
- package/extensions/magpie/checkinnow.js +111 -0
- package/extensions/magpie/claudemode.js +92 -0
- package/extensions/magpie/compat.js +41 -0
- package/extensions/magpie/context.css +433 -0
- package/extensions/magpie/context.js +1576 -0
- package/extensions/magpie/ctxtext.js +223 -0
- package/extensions/magpie/fonts.js +42 -0
- package/extensions/magpie/i18n.js +18942 -0
- package/extensions/magpie/icons/ai21.svg +1 -0
- package/extensions/magpie/icons/ai302-color.svg +1 -0
- package/extensions/magpie/icons/ai360-color.svg +1 -0
- package/extensions/magpie/icons/aihubmix-color.svg +1 -0
- package/extensions/magpie/icons/aionui.svg +1 -0
- package/extensions/magpie/icons/air.svg +4 -0
- package/extensions/magpie/icons/alibaba-color.svg +1 -0
- package/extensions/magpie/icons/alibabacloud-color.svg +1 -0
- package/extensions/magpie/icons/alma.png +0 -0
- package/extensions/magpie/icons/anthropic.svg +1 -0
- package/extensions/magpie/icons/antigravity-color.svg +1 -0
- package/extensions/magpie/icons/anyscale-color.svg +1 -0
- package/extensions/magpie/icons/app-alma.png +0 -0
- package/extensions/magpie/icons/app-cc-switch.png +0 -0
- package/extensions/magpie/icons/apple-touch-icon.png +0 -0
- package/extensions/magpie/icons/aside.svg +28 -0
- package/extensions/magpie/icons/astrbot.svg +1 -0
- package/extensions/magpie/icons/atomcode.png +0 -0
- package/extensions/magpie/icons/aws.svg +1 -0
- package/extensions/magpie/icons/azure-color.svg +1 -0
- package/extensions/magpie/icons/azureai-color.svg +1 -0
- package/extensions/magpie/icons/baichuan-color.svg +1 -0
- package/extensions/magpie/icons/baidu-color.svg +1 -0
- package/extensions/magpie/icons/baiducloud-color.svg +1 -0
- package/extensions/magpie/icons/bailian-color.svg +1 -0
- package/extensions/magpie/icons/baseten.svg +1 -0
- package/extensions/magpie/icons/bedrock-color.svg +1 -0
- package/extensions/magpie/icons/burncloud-color.svg +1 -0
- package/extensions/magpie/icons/bytedance-color.svg +1 -0
- package/extensions/magpie/icons/cerebras.svg +1 -0
- package/extensions/magpie/icons/chatglm-color.svg +1 -0
- package/extensions/magpie/icons/cherryin-color.svg +1 -0
- package/extensions/magpie/icons/chutes.svg +1 -0
- package/extensions/magpie/icons/cindy.png +0 -0
- package/extensions/magpie/icons/claude-color.svg +1 -0
- package/extensions/magpie/icons/claudecode-color.svg +1 -0
- package/extensions/magpie/icons/cline.svg +1 -0
- package/extensions/magpie/icons/cloudflare-color.svg +1 -0
- package/extensions/magpie/icons/codebuddy-color.svg +1 -0
- package/extensions/magpie/icons/codex-color.svg +1 -0
- package/extensions/magpie/icons/cohere-color.svg +1 -0
- package/extensions/magpie/icons/cometapi-color.svg +1 -0
- package/extensions/magpie/icons/commandcode.svg +1 -0
- package/extensions/magpie/icons/copilot-color.svg +1 -0
- package/extensions/magpie/icons/coze.svg +1 -0
- package/extensions/magpie/icons/crush.png +0 -0
- package/extensions/magpie/icons/curl.svg +1 -0
- package/extensions/magpie/icons/cursor.svg +1 -0
- package/extensions/magpie/icons/deepinfra-color.svg +1 -0
- package/extensions/magpie/icons/deepmind-color.svg +1 -0
- package/extensions/magpie/icons/deepseek-color.svg +1 -0
- package/extensions/magpie/icons/deveco-color.svg +12 -0
- package/extensions/magpie/icons/devin.svg +1 -0
- package/extensions/magpie/icons/doubao-color.svg +1 -0
- package/extensions/magpie/icons/empryo-color.svg +1 -0
- package/extensions/magpie/icons/factory.svg +1 -0
- package/extensions/magpie/icons/favicon.svg +1 -0
- package/extensions/magpie/icons/featherless-color.svg +1 -0
- package/extensions/magpie/icons/fireworks-color.svg +1 -0
- package/extensions/magpie/icons/friendli.svg +1 -0
- package/extensions/magpie/icons/fx.svg +1 -0
- package/extensions/magpie/icons/gemini-color.svg +1 -0
- package/extensions/magpie/icons/geminicli-color.svg +1 -0
- package/extensions/magpie/icons/gemma-color.svg +1 -0
- package/extensions/magpie/icons/giteeai.svg +1 -0
- package/extensions/magpie/icons/github.svg +1 -0
- package/extensions/magpie/icons/githubcopilot.svg +1 -0
- package/extensions/magpie/icons/glama.svg +1 -0
- package/extensions/magpie/icons/gmicloud.svg +1 -0
- package/extensions/magpie/icons/google-color.svg +1 -0
- package/extensions/magpie/icons/googlecloud-color.svg +1 -0
- package/extensions/magpie/icons/goose.svg +1 -0
- package/extensions/magpie/icons/grok.svg +1 -0
- package/extensions/magpie/icons/groq.svg +1 -0
- package/extensions/magpie/icons/hanako.png +0 -0
- package/extensions/magpie/icons/hermes.svg +1 -0
- package/extensions/magpie/icons/huawei-color.svg +1 -0
- package/extensions/magpie/icons/huaweicloud-color.svg +1 -0
- package/extensions/magpie/icons/huggingface-color.svg +1 -0
- package/extensions/magpie/icons/hunyuan-color.svg +1 -0
- package/extensions/magpie/icons/hyperbolic-color.svg +1 -0
- package/extensions/magpie/icons/icon-512.png +0 -0
- package/extensions/magpie/icons/iflytekcloud-color.svg +1 -0
- package/extensions/magpie/icons/inference.svg +1 -0
- package/extensions/magpie/icons/infinigence-color.svg +1 -0
- package/extensions/magpie/icons/internlm-color.svg +1 -0
- package/extensions/magpie/icons/jcode.svg +1 -0
- package/extensions/magpie/icons/jina.svg +1 -0
- package/extensions/magpie/icons/kilo.svg +1 -0
- package/extensions/magpie/icons/kilocode.svg +1 -0
- package/extensions/magpie/icons/kimi-code-desktop.png +0 -0
- package/extensions/magpie/icons/kimi.svg +1 -0
- package/extensions/magpie/icons/kiro-color.svg +1 -0
- package/extensions/magpie/icons/kling-color.svg +1 -0
- package/extensions/magpie/icons/lambda.svg +1 -0
- package/extensions/magpie/icons/leptonai-color.svg +1 -0
- package/extensions/magpie/icons/liquid.svg +1 -0
- package/extensions/magpie/icons/liveagent-color.svg +1 -0
- package/extensions/magpie/icons/llmapi.svg +1 -0
- package/extensions/magpie/icons/lmstudio.svg +1 -0
- package/extensions/magpie/icons/lody.png +0 -0
- package/extensions/magpie/icons/longcat.svg +1 -0
- package/extensions/magpie/icons/magpie.svg +1 -0
- package/extensions/magpie/icons/meta-color.svg +1 -0
- package/extensions/magpie/icons/metaai-color.svg +1 -0
- package/extensions/magpie/icons/microsoft-color.svg +1 -0
- package/extensions/magpie/icons/mimocode.svg +1 -0
- package/extensions/magpie/icons/minimax-color.svg +1 -0
- package/extensions/magpie/icons/mistermorph-color.svg +1 -0
- package/extensions/magpie/icons/mistral-color.svg +1 -0
- package/extensions/magpie/icons/mlx-serve.png +0 -0
- package/extensions/magpie/icons/modelscope-color.svg +1 -0
- package/extensions/magpie/icons/monica-color.svg +1 -0
- package/extensions/magpie/icons/moonshot.svg +1 -0
- package/extensions/magpie/icons/morph-color.svg +1 -0
- package/extensions/magpie/icons/mothx.png +0 -0
- package/extensions/magpie/icons/nebius.svg +1 -0
- package/extensions/magpie/icons/newapi-color.svg +1 -0
- package/extensions/magpie/icons/nousresearch.svg +1 -0
- package/extensions/magpie/icons/nova-color.svg +1 -0
- package/extensions/magpie/icons/novita-color.svg +1 -0
- package/extensions/magpie/icons/nvidia-color.svg +1 -0
- package/extensions/magpie/icons/ollama.svg +1 -0
- package/extensions/magpie/icons/omlx.svg +1 -0
- package/extensions/magpie/icons/omo.svg +1 -0
- package/extensions/magpie/icons/omp.svg +1 -0
- package/extensions/magpie/icons/openai.svg +1 -0
- package/extensions/magpie/icons/openchamber.svg +11 -0
- package/extensions/magpie/icons/openclaw.svg +1 -0
- package/extensions/magpie/icons/opencode.svg +1 -0
- package/extensions/magpie/icons/openmausbot.svg +1 -0
- package/extensions/magpie/icons/openrouter.svg +1 -0
- package/extensions/magpie/icons/openwebui.svg +1 -0
- package/extensions/magpie/icons/parasail.svg +1 -0
- package/extensions/magpie/icons/penguin-color.svg +1 -0
- package/extensions/magpie/icons/perplexity-color.svg +1 -0
- package/extensions/magpie/icons/phala-color.svg +1 -0
- package/extensions/magpie/icons/pi.svg +1 -0
- package/extensions/magpie/icons/pipellm-color.svg +5 -0
- package/extensions/magpie/icons/poe.svg +1 -0
- package/extensions/magpie/icons/ppio-color.svg +1 -0
- package/extensions/magpie/icons/prime-agent.svg +1 -0
- package/extensions/magpie/icons/qiniu-color.svg +1 -0
- package/extensions/magpie/icons/qoder.svg +1 -0
- package/extensions/magpie/icons/qwen-color.svg +1 -0
- package/extensions/magpie/icons/qwenpaw.svg +1 -0
- package/extensions/magpie/icons/raycast.svg +1 -0
- package/extensions/magpie/icons/reasonix-color.svg +48 -0
- package/extensions/magpie/icons/reka.svg +1 -0
- package/extensions/magpie/icons/replicate.svg +1 -0
- package/extensions/magpie/icons/roocode.svg +1 -0
- package/extensions/magpie/icons/rtk.png +0 -0
- package/extensions/magpie/icons/sambanova-color.svg +1 -0
- package/extensions/magpie/icons/sensenova-color.svg +1 -0
- package/extensions/magpie/icons/siliconcloud-color.svg +1 -0
- package/extensions/magpie/icons/snow.png +0 -0
- package/extensions/magpie/icons/snowflake-color.svg +1 -0
- package/extensions/magpie/icons/sophnet-color.svg +1 -0
- package/extensions/magpie/icons/spark-color.svg +1 -0
- package/extensions/magpie/icons/stepfun-color.svg +1 -0
- package/extensions/magpie/icons/straico-color.svg +1 -0
- package/extensions/magpie/icons/submodel-color.svg +1 -0
- package/extensions/magpie/icons/t3code.svg +4 -0
- package/extensions/magpie/icons/targon-color.svg +1 -0
- package/extensions/magpie/icons/tencent-color.svg +1 -0
- package/extensions/magpie/icons/tencentcloud-color.svg +1 -0
- package/extensions/magpie/icons/tiangong-color.svg +1 -0
- package/extensions/magpie/icons/together-color.svg +1 -0
- package/extensions/magpie/icons/trae.svg +1 -0
- package/extensions/magpie/icons/typesafe.png +0 -0
- package/extensions/magpie/icons/upstage-color.svg +1 -0
- package/extensions/magpie/icons/venice-color.svg +1 -0
- package/extensions/magpie/icons/vercel.svg +1 -0
- package/extensions/magpie/icons/vertexai-color.svg +1 -0
- package/extensions/magpie/icons/vllm-color.svg +1 -0
- package/extensions/magpie/icons/volcengine-color.svg +1 -0
- package/extensions/magpie/icons/voyage-color.svg +1 -0
- package/extensions/magpie/icons/vscode.svg +1 -0
- package/extensions/magpie/icons/wenxin-color.svg +1 -0
- package/extensions/magpie/icons/windsurf.svg +1 -0
- package/extensions/magpie/icons/workbuddy-color.svg +1 -0
- package/extensions/magpie/icons/workersai-color.svg +1 -0
- package/extensions/magpie/icons/xai.svg +1 -0
- package/extensions/magpie/icons/xiaomimimo.svg +1 -0
- package/extensions/magpie/icons/xinference-color.svg +1 -0
- package/extensions/magpie/icons/yi.svg +1 -0
- package/extensions/magpie/icons/yuanbao-color.svg +1 -0
- package/extensions/magpie/icons/yylx.svg +5 -0
- package/extensions/magpie/icons/zai.svg +1 -0
- package/extensions/magpie/icons/zcode.png +0 -0
- package/extensions/magpie/icons/zed.svg +1 -0
- package/extensions/magpie/icons/zenmux.svg +1 -0
- package/extensions/magpie/icons/zeroone.svg +1 -0
- package/extensions/magpie/icons/zhipu-color.svg +1 -0
- package/extensions/magpie/icons/zoo-code.png +0 -0
- package/extensions/magpie/index.html +572 -0
- package/extensions/magpie/kernel.js +274 -0
- package/extensions/magpie/keyspend.js +63 -0
- package/extensions/magpie/layout.js +26 -0
- package/extensions/magpie/manifest.json +37 -0
- package/extensions/magpie/navlamp.js +47 -0
- package/extensions/magpie/omarchy.css +72 -0
- package/extensions/magpie/omarchy.js +46 -0
- package/extensions/magpie/passerrors.js +21 -0
- package/extensions/magpie/pluginpages.js +405 -0
- package/extensions/magpie/plugins/gateway/client.js +1194 -0
- package/extensions/magpie/plugins/gateway/keys.js +972 -0
- package/extensions/magpie/plugins/gateway/settings.js +459 -0
- package/extensions/magpie/plugins/library/client.js +6788 -0
- package/extensions/magpie/plugins/library/i18n.js +3228 -0
- package/extensions/magpie/plugins/library/library.css +848 -0
- package/extensions/magpie/plugins/otel/i18n.js +48 -0
- package/extensions/magpie/plugins/otel/page.js +189 -0
- package/extensions/magpie/plugins/redact/client.js +46 -0
- package/extensions/magpie/plugins/redact/demo.js +461 -0
- package/extensions/magpie/plugins/redact/i18n.js +432 -0
- package/extensions/magpie/plugins/redact/page.js +985 -0
- package/extensions/magpie/plugins/redact/redact.css +385 -0
- package/extensions/magpie/plugins/redact/settings.js +248 -0
- package/extensions/magpie/plugins/redact/xq_dark.jpg +0 -0
- package/extensions/magpie/plugins/redact/xq_dark.webm +0 -0
- package/extensions/magpie/plugins/redact/xq_light.jpg +0 -0
- package/extensions/magpie/plugins/redact/xq_light.webm +0 -0
- package/extensions/magpie/plugins/routing/client.js +8593 -0
- package/extensions/magpie/plugins/routing/i18n.js +2696 -0
- package/extensions/magpie/plugins/routing/routing.css +1111 -0
- package/extensions/magpie/plugins/search/settings.js +460 -0
- package/extensions/magpie/plugins/sessions/client.js +1622 -0
- package/extensions/magpie/plugins/sessions/i18n.js +360 -0
- package/extensions/magpie/plugins/sessions/sessions.css +181 -0
- package/extensions/magpie/plugins/sessions/settings.js +100 -0
- package/extensions/magpie/plugins/usage/client.js +150 -0
- package/extensions/magpie/plugins/usage/i18n.js +984 -0
- package/extensions/magpie/plugins/usage/page.js +4307 -0
- package/extensions/magpie/plugins.css +319 -0
- package/extensions/magpie/plugins.js +1926 -0
- package/extensions/magpie/profilerename.js +27 -0
- package/extensions/magpie/purposes.js +2 -0
- package/extensions/magpie/quitask.js +79 -0
- package/extensions/magpie/quotaall.js +142 -0
- package/extensions/magpie/ratewindows.js +123 -0
- package/extensions/magpie/registry.js +87 -0
- package/extensions/magpie/resetask.js +137 -0
- package/extensions/magpie/ruleedit.js +53 -0
- package/extensions/magpie/scrollends.js +91 -0
- package/extensions/magpie/settingshints.js +75 -0
- package/extensions/magpie/signsheets.js +86 -0
- package/extensions/magpie/start.js +26 -0
- package/extensions/magpie/syncfiles.js +216 -0
- package/extensions/magpie/theme.js +29 -0
- package/extensions/magpie/traywindows.js +41 -0
- package/extensions/magpie/webfonts.js +130 -0
- package/extensions/magpie/whatsnew.js +252 -0
- package/extensions/magpie.zip +0 -0
- package/knowledge/apps/baijiahao.md +30 -0
- package/knowledge/apps/bilibili.md +1 -0
- package/knowledge/apps/dedao.md +33 -0
- package/knowledge/apps/feishu.md +35 -0
- package/knowledge/apps/gmail.md +33 -0
- package/knowledge/apps/jike.md +31 -0
- package/knowledge/apps/kuaishou.md +34 -0
- package/knowledge/apps/linkedin.md +33 -0
- package/knowledge/apps/qqmail.md +30 -0
- package/knowledge/apps/telegram.md +27 -0
- package/knowledge/apps/toutiao.md +32 -0
- package/knowledge/apps/uber-eats.md +26 -0
- package/knowledge/apps/uber.md +27 -0
- package/knowledge/apps/whatsapp.md +27 -0
- package/knowledge/apps/x.md +32 -0
- package/knowledge/apps/xiaohongshu.md +1 -0
- package/knowledge/apps/zhihu.md +1 -0
- package/knowledge/sites/bilibili.com/SITE.md +50 -0
- package/knowledge/sites/bilibili.com/references/view-fields.md +17 -0
- package/knowledge/sites/github.com/SITE.md +51 -0
- package/knowledge/sites/github.com/references/repo-fields.md +13 -0
- package/knowledge/sites/x.com/SITE.md +47 -0
- package/knowledge/sites/xiaohongshu.com/SITE.md +48 -0
- package/knowledge/sites/xiaohongshu.com/references/note-fields.md +18 -0
- package/knowledge/sites/youtube.com/SITE.md +49 -0
- package/knowledge/sites/youtube.com/references/innertube-fields.md +14 -0
- package/knowledge/sites/zhihu.com/SITE.md +47 -0
- package/knowledge/sites/zhihu.com/references/api-fields.md +21 -0
- package/lib/mobile/extension-catalog.js +41 -1
- package/lib/mobile/extension-data.js +224 -0
- package/lib/mobile/extension-dev.js +3 -1
- package/lib/mobile/file-ops.js +252 -0
- package/lib/mobile/history-derive.js +80 -4
- package/lib/mobile/host-bridge/endpoints.js +48 -6
- package/lib/mobile/host-bridge/event-synthesis.js +33 -6
- package/lib/mobile/jobs-control.js +118 -0
- package/lib/mobile/memory.js +635 -0
- package/lib/mobile/model-badges.js +77 -0
- package/lib/mobile/pet-tools.js +50 -4
- package/lib/mobile/pets.js +214 -18
- package/lib/mobile/phone/access.js +85 -0
- package/lib/mobile/phone/evidence.js +228 -0
- package/lib/mobile/phone/gate.js +290 -0
- package/lib/mobile/phone/grounding.js +314 -0
- package/lib/mobile/phone/guard.js +465 -0
- package/lib/mobile/phone/irreversible.js +88 -8
- package/lib/mobile/phone/knowledge-tools.js +665 -0
- package/lib/mobile/phone/learn.js +329 -0
- package/lib/mobile/phone/redact.js +237 -0
- package/lib/mobile/phone/risk.js +95 -0
- package/lib/mobile/phone/routes.js +28 -1
- package/lib/mobile/phone/service.js +23 -0
- package/lib/mobile/phone/site-commands.js +271 -0
- package/lib/mobile/phone/step-summary.js +201 -0
- package/lib/mobile/phone/stop-summary.js +235 -0
- package/lib/mobile/phone/tool-groups.js +125 -0
- package/lib/mobile/phone/tools.js +589 -0
- package/lib/mobile/phone/untrusted.js +65 -0
- package/lib/mobile/phone-knowledge.js +20 -4
- package/lib/mobile/schedule-edit.js +500 -0
- package/lib/mobile/schedule.js +5 -0
- package/lib/mobile/send-continuation.js +52 -0
- package/lib/mobile/session-models.js +27 -31
- package/lib/mobile/session-send.js +12 -12
- package/lib/mobile/skill-import.js +338 -0
- package/lib/mobile/skill-store.js +524 -0
- package/lib/routes/mobile-api.js +42 -0
- package/package.json +7 -6
- package/skills/skill-authoring/SKILL.md +46 -0
- package/skills/skill-authoring/references/examples.md +82 -0
- package/skills/skill-authoring/references/frontmatter-and-fields.md +46 -0
- package/skills/skill-authoring/scripts/check_skill.py +167 -0
- package/skills/skill-authoring/scripts/manifest.json +3 -0
- package/knowledge/sites/bilibili.com.md +0 -27
- package/knowledge/sites/github.com.md +0 -27
- package/knowledge/sites/x.com.md +0 -28
- package/knowledge/sites/xiaohongshu.com.md +0 -27
- package/knowledge/sites/youtube.com.md +0 -28
- package/knowledge/sites/zhihu.com.md +0 -27
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// The window's plugin kernel (docs/subsystems/plugin-runtime.md, P1). A
|
|
2
|
+
// plugin's page is a part it adds to the "page" slot: its markup, put in the
|
|
3
|
+
// page where index.html's <template data-slot="page"> is, and its hooks,
|
|
4
|
+
// which the shell calls through magpie.call. A hook that throws breaks only
|
|
5
|
+
// its own page: the page says so, with a Try again, and the other pages go
|
|
6
|
+
// on (the error boundary). ctx.ui is the kit a plugin draws with, and
|
|
7
|
+
// ui.form draws a plugin's settings from the schema the Go side made of
|
|
8
|
+
// them (pluginrt.SchemaOf).
|
|
9
|
+
//
|
|
10
|
+
// Loaded after i18n.js and before the plugins' scripts and app.js. A
|
|
11
|
+
// plugin's words come with it (its i18n.js, magpie.strings). Its scripts
|
|
12
|
+
// register at once, so its page is in the page before app.js looks for
|
|
13
|
+
// it, but run nothing of app.js's then: mount runs at magpie.start, which
|
|
14
|
+
// app.js calls once it is set up.
|
|
15
|
+
(() => {
|
|
16
|
+
const plugins = new Map(); // id → { id, config, onConfig: [], disposers: [] }
|
|
17
|
+
const pages = new Map(); // key → { plugin, part, main, mounted, broken }
|
|
18
|
+
let started = false;
|
|
19
|
+
|
|
20
|
+
function context(p) {
|
|
21
|
+
return {
|
|
22
|
+
id: p.id,
|
|
23
|
+
get config() { return p.config; },
|
|
24
|
+
// a change of the plugin's settings (Plugins › Built-in), after
|
|
25
|
+
// ctx.config has it
|
|
26
|
+
onConfig(fn) { p.onConfig.push(fn); },
|
|
27
|
+
onDispose(fn) { p.disposers.push(fn); },
|
|
28
|
+
// a page of the plugin's that broke after its hook returned (a
|
|
29
|
+
// frame's own error, one that never answered): it shows what broke,
|
|
30
|
+
// with a Try again, as a hook that throws does
|
|
31
|
+
broke(key, err) {
|
|
32
|
+
const page = pages.get(key);
|
|
33
|
+
if (page && page.plugin === p && !page.broken) fail(page, "open", [], err);
|
|
34
|
+
},
|
|
35
|
+
slots: { add: (slot, part) => add(p, slot, part) },
|
|
36
|
+
ui,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function add(p, slot, part) {
|
|
41
|
+
if (slot !== "page") throw new Error(`no slot ${slot}`);
|
|
42
|
+
if (!/^[a-z][a-z0-9-]*$/.test(part.key || "") || pages.has(part.key) || document.getElementById("view-" + part.key)) {
|
|
43
|
+
throw new Error(`page ${part.key} is taken`);
|
|
44
|
+
}
|
|
45
|
+
const main = document.createElement("main");
|
|
46
|
+
main.className = "view";
|
|
47
|
+
main.id = "view-" + part.key;
|
|
48
|
+
main.hidden = true;
|
|
49
|
+
main.dataset.feature = p.id;
|
|
50
|
+
main.innerHTML = part.html || "";
|
|
51
|
+
window.magpie?.fillSlots?.(main);
|
|
52
|
+
const anchor = document.querySelector('template[data-slot="page"]');
|
|
53
|
+
if (anchor) anchor.before(main);
|
|
54
|
+
else document.body.append(main);
|
|
55
|
+
const page = { plugin: p, part, main, mounted: false, broken: null };
|
|
56
|
+
pages.set(part.key, page);
|
|
57
|
+
if (started) run(page, "mount", [main]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The error boundary: a hook that throws (or whose promise fails) is
|
|
61
|
+
// caught here, and the page shows what broke instead of what it had.
|
|
62
|
+
function fail(page, hook, args, err) {
|
|
63
|
+
console.error(`[magpie] ${page.plugin.id}: ${page.part.key}.${hook}`, err);
|
|
64
|
+
page.broken = { hook, args };
|
|
65
|
+
const card = document.createElement("div");
|
|
66
|
+
card.className = "list slot-error";
|
|
67
|
+
card.setAttribute("role", "alert");
|
|
68
|
+
const row = document.createElement("div");
|
|
69
|
+
row.className = "row";
|
|
70
|
+
const who = document.createElement("div");
|
|
71
|
+
who.className = "who";
|
|
72
|
+
const name = document.createElement("div");
|
|
73
|
+
name.className = "name";
|
|
74
|
+
name.textContent = t("This page stopped working");
|
|
75
|
+
const sub = document.createElement("div");
|
|
76
|
+
sub.className = "sub";
|
|
77
|
+
sub.textContent = String(err?.message || err);
|
|
78
|
+
who.append(name, sub);
|
|
79
|
+
const again = document.createElement("button");
|
|
80
|
+
again.type = "button";
|
|
81
|
+
again.className = "text slot-again";
|
|
82
|
+
again.textContent = t("Try again");
|
|
83
|
+
again.onclick = () => retry(page);
|
|
84
|
+
row.append(who, again);
|
|
85
|
+
card.append(row);
|
|
86
|
+
page.main.classList.remove("loading");
|
|
87
|
+
page.main.removeAttribute("aria-busy");
|
|
88
|
+
page.main.replaceChildren(card);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Try again: the page's markup as it came, mounted again, and the hook
|
|
92
|
+
// that broke run again with what it was given.
|
|
93
|
+
function retry(page) {
|
|
94
|
+
const { hook, args } = page.broken;
|
|
95
|
+
page.broken = null;
|
|
96
|
+
page.mounted = false;
|
|
97
|
+
page.main.innerHTML = page.part.html || "";
|
|
98
|
+
window.magpie?.fillSlots?.(page.main);
|
|
99
|
+
window.translateStatic?.();
|
|
100
|
+
if (hook !== "mount") run(page, "mount", [page.main]);
|
|
101
|
+
run(page, hook, args);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function run(page, hook, args) {
|
|
105
|
+
if (page.broken) return undefined;
|
|
106
|
+
if (hook === "mount") {
|
|
107
|
+
if (page.mounted) return undefined;
|
|
108
|
+
page.mounted = true;
|
|
109
|
+
} else if (!page.mounted && started) {
|
|
110
|
+
run(page, "mount", [page.main]);
|
|
111
|
+
if (page.broken) return undefined;
|
|
112
|
+
}
|
|
113
|
+
const fn = page.part[hook];
|
|
114
|
+
if (typeof fn !== "function") return undefined;
|
|
115
|
+
try {
|
|
116
|
+
const out = fn.apply(page.part, args);
|
|
117
|
+
if (out && typeof out.then === "function") {
|
|
118
|
+
return out.catch((err) => { if (!page.broken) fail(page, hook, args, err); });
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
fail(page, hook, args, err);
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------- the kit ----------
|
|
128
|
+
// What a plugin draws with, as the rest of the window draws: el, the app
|
|
129
|
+
// menu (never a native <select>), the switch, the settings row.
|
|
130
|
+
const ui = {
|
|
131
|
+
el: (tag, cls, text) => el(tag, cls, text),
|
|
132
|
+
button(label, onClick, cls = "text") {
|
|
133
|
+
const b = el("button", cls, label);
|
|
134
|
+
b.type = "button";
|
|
135
|
+
b.onclick = onClick;
|
|
136
|
+
return b;
|
|
137
|
+
},
|
|
138
|
+
toggle(on, label, onChange) {
|
|
139
|
+
const b = el("button", "lib-switch use-credits" + (on ? " on" : ""));
|
|
140
|
+
b.type = "button";
|
|
141
|
+
b.setAttribute("role", "switch");
|
|
142
|
+
b.setAttribute("aria-checked", String(on));
|
|
143
|
+
b.setAttribute("aria-label", label);
|
|
144
|
+
b.append(el("i"));
|
|
145
|
+
b.onclick = () => {
|
|
146
|
+
on = !on;
|
|
147
|
+
b.classList.toggle("on", on);
|
|
148
|
+
b.setAttribute("aria-checked", String(on));
|
|
149
|
+
onChange(on);
|
|
150
|
+
};
|
|
151
|
+
return b;
|
|
152
|
+
},
|
|
153
|
+
// a pick of one of opts ([{ v, name }]), in the app's menu
|
|
154
|
+
menu(opts, value, head, onPick) {
|
|
155
|
+
const b = el("button", "proto pick");
|
|
156
|
+
b.type = "button";
|
|
157
|
+
const paint = () => {
|
|
158
|
+
const o = opts.find((x) => x.v === value);
|
|
159
|
+
b.replaceChildren(el("span", "", o ? o.name : String(value)), svg(CHEV, 11, 1.6));
|
|
160
|
+
};
|
|
161
|
+
paint();
|
|
162
|
+
b.onclick = (ev) => {
|
|
163
|
+
ev.stopPropagation();
|
|
164
|
+
openProtoMenu(b, opts.map((o) => ({ ...o, literalName: true, note: "" })), value, (v) => {
|
|
165
|
+
value = v;
|
|
166
|
+
paint();
|
|
167
|
+
onPick(v);
|
|
168
|
+
}, head, "", "right");
|
|
169
|
+
};
|
|
170
|
+
return b;
|
|
171
|
+
},
|
|
172
|
+
// a settings row: what it is, and its control at the end
|
|
173
|
+
row(title, description, control) {
|
|
174
|
+
const r = el("div", "row pref");
|
|
175
|
+
const who = el("div", "who");
|
|
176
|
+
who.append(el("div", "name", title));
|
|
177
|
+
if (description) who.append(el("div", "sub", description));
|
|
178
|
+
r.append(who, control);
|
|
179
|
+
return r;
|
|
180
|
+
},
|
|
181
|
+
// The rows of a plugin's settings, drawn from its schema: true or false
|
|
182
|
+
// a switch, one of a few a menu, a number a field kept to its range,
|
|
183
|
+
// text a field. onChange gets { key: value } for the one changed.
|
|
184
|
+
form(schema, values, onChange) {
|
|
185
|
+
const rows = [];
|
|
186
|
+
for (const key of schema?.["x-order"] || Object.keys(schema?.properties || {})) {
|
|
187
|
+
const p = schema.properties[key];
|
|
188
|
+
if (!p) continue;
|
|
189
|
+
const value = values?.[key] ?? p.default;
|
|
190
|
+
const title = t(p.title), say = p.description ? t(p.description) : "";
|
|
191
|
+
let control;
|
|
192
|
+
if (p.type === "boolean") control = ui.toggle(!!value, title, (v) => onChange({ [key]: v }));
|
|
193
|
+
else if (p.enum?.length) {
|
|
194
|
+
control = ui.menu(p.enum.map((v) => ({ v, name: typeof v === "number" ? v.toLocaleString(locale) : t(String(v)) })), value, p.title, (v) => onChange({ [key]: v }));
|
|
195
|
+
} else {
|
|
196
|
+
const number = p.type === "integer" || p.type === "number";
|
|
197
|
+
control = el("input", "set-field");
|
|
198
|
+
control.type = number ? "number" : "text";
|
|
199
|
+
control.value = value ?? "";
|
|
200
|
+
control.setAttribute("aria-label", title);
|
|
201
|
+
if (number) {
|
|
202
|
+
if (p.minimum !== undefined) control.min = p.minimum;
|
|
203
|
+
if (p.maximum !== undefined) control.max = p.maximum;
|
|
204
|
+
if (p.type === "integer") control.step = 1;
|
|
205
|
+
}
|
|
206
|
+
control.onchange = () => {
|
|
207
|
+
let v = control.value;
|
|
208
|
+
if (number) {
|
|
209
|
+
v = Number(v);
|
|
210
|
+
const bad = control.value === "" || !Number.isFinite(v) || (p.type === "integer" && !Number.isInteger(v))
|
|
211
|
+
|| (p.minimum !== undefined && v < p.minimum) || (p.maximum !== undefined && v > p.maximum);
|
|
212
|
+
control.setAttribute("aria-invalid", String(bad));
|
|
213
|
+
if (bad) return;
|
|
214
|
+
}
|
|
215
|
+
onChange({ [key]: v });
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const r = ui.row(title, say, control);
|
|
219
|
+
r.dataset.setting = key;
|
|
220
|
+
rows.push(r);
|
|
221
|
+
}
|
|
222
|
+
return rows;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
window.magpie = {
|
|
227
|
+
ui,
|
|
228
|
+
plugin(id, setup) {
|
|
229
|
+
if (plugins.has(id)) throw new Error(`plugin ${id} is registered twice`);
|
|
230
|
+
const p = { id, config: { ...(window.bootPrefs?.pluginConfig?.[id] || {}) }, onConfig: [], disposers: [] };
|
|
231
|
+
plugins.set(id, p);
|
|
232
|
+
try {
|
|
233
|
+
setup(context(p));
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error(`[magpie] ${id}: setup`, err);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
// app.js, once it is set up: every page is mounted
|
|
239
|
+
start() {
|
|
240
|
+
if (started) return;
|
|
241
|
+
started = true;
|
|
242
|
+
for (const page of pages.values()) run(page, "mount", [page.main]);
|
|
243
|
+
},
|
|
244
|
+
// a page's hook, behind the boundary; nothing for a page no plugin has
|
|
245
|
+
call(key, hook, ...args) {
|
|
246
|
+
const page = pages.get(key);
|
|
247
|
+
return page ? run(page, hook, args) : undefined;
|
|
248
|
+
},
|
|
249
|
+
// a plugin's words in each language ({ zh: { English: 中文 }, … }),
|
|
250
|
+
// added to i18n.js's; one already there is a mistake, said and not
|
|
251
|
+
// taken, as the later one would silently win
|
|
252
|
+
strings(dict) {
|
|
253
|
+
for (const [lang, words] of Object.entries(dict)) {
|
|
254
|
+
const to = (I18N[lang] ||= {});
|
|
255
|
+
for (const [k, v] of Object.entries(words)) {
|
|
256
|
+
if (k in to) console.error(`[magpie] ${lang} ${JSON.stringify(k)} is said twice`);
|
|
257
|
+
else to[k] = v;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
page: (key) => pages.get(key)?.part,
|
|
262
|
+
pages: () => [...pages.keys()],
|
|
263
|
+
// the plugins' settings as Plugins › Built-in or boot.js has them
|
|
264
|
+
configure(id, config) {
|
|
265
|
+
const p = plugins.get(id);
|
|
266
|
+
if (!p || !config) return;
|
|
267
|
+
if (JSON.stringify(p.config) === JSON.stringify(config)) return;
|
|
268
|
+
p.config = { ...config };
|
|
269
|
+
for (const fn of p.onConfig) {
|
|
270
|
+
try { fn(p.config); } catch (err) { console.error(`[magpie] ${id}: config`, err); }
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
})();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// A key's spend cap (sparkling_lemur_62868 on Discord: a team's OpenAI
|
|
2
|
+
// key, with credits other services spend from too): how much magpie may
|
|
3
|
+
// spend on it, in USD at the Usage page's prices, a day, a month or in
|
|
4
|
+
// all. Past it the gateway passes the key over, as a usage cap holds an
|
|
5
|
+
// account (provider/key_spend.go). app.js imports this when the provider
|
|
6
|
+
// editor draws a key's row (keySpend), so it weighs nothing on the core.
|
|
7
|
+
const usdText = (v) => "$" + (v >= 100 ? v.toFixed(0) : v.toFixed(2));
|
|
8
|
+
const SPEND_PERIODS = { day: "a day", month: "a month", total: "in all" };
|
|
9
|
+
function keySpendPill(p, k) {
|
|
10
|
+
const sc = k.spend, name = k.name || k.masked;
|
|
11
|
+
const pill = el("button", "aspend" + (sc ? " set" : "") + (sc?.held ? " at-cap" : ""));
|
|
12
|
+
pill.type = "button";
|
|
13
|
+
const v = sc && { spent: usdText(sc.spent || 0), cap: usdText(sc.usd) };
|
|
14
|
+
pill.textContent = !sc ? t("No spend cap") : t(sc.period === "month" ? "{spent} of {cap} this month" : sc.period === "total" ? "{spent} of {cap} in all" : "{spent} of {cap} today", v);
|
|
15
|
+
pill.title = sc ? t("Stops at {cap} {period}, counted at the prices the Usage page shows for the calls magpie sends on this key. Past it, magpie goes on to the provider's other keys and its fallbacks. Requests under way still finish, so it can go a little past the cap, and a model with no known price counts nothing. Click to change", { cap: v.cap, period: t(SPEND_PERIODS[sc.period] || "a day") })
|
|
16
|
+
: t("No spend cap: magpie sends this key requests whatever they cost. Click to set how much it may spend, a day, a month or in all; past it, magpie goes on to the provider's other keys and its fallbacks");
|
|
17
|
+
pill.setAttribute("aria-haspopup", "menu");
|
|
18
|
+
pill.setAttribute("aria-expanded", "false");
|
|
19
|
+
const set = (usd, period) => accountAction("provider/keyspendcap", { id: p.id, account: k.id, spend: usd ? { usd, period } : null },
|
|
20
|
+
!usd ? t("{who} has no spend cap", { who: name })
|
|
21
|
+
: t(period === "month" ? "{who} stops at {usd} a month" : period === "total" ? "{who} stops at {usd} in all" : "{who} stops at {usd} a day", { who: name, usd: usdText(usd) }));
|
|
22
|
+
const amount = (s) => { const n = Number(s.replace(/^\$/, "").trim()); return n > 0 && n <= 1e6 ? n : null; };
|
|
23
|
+
pill.onclick = (e) => {
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
if (pill.classList.contains("open")) return closeProtoMenu();
|
|
26
|
+
const opts = [
|
|
27
|
+
{ v: "off", name: "No spend cap", note: "Spend as much as the key has" },
|
|
28
|
+
{ v: "day", name: "Per day…", note: "Counts again each day" },
|
|
29
|
+
{ v: "month", name: "Per month…", note: "Counts again each month" },
|
|
30
|
+
{ v: "total", name: "In all…", note: "Counted from when it is set" },
|
|
31
|
+
];
|
|
32
|
+
openProtoMenu(pill, opts, sc?.period || "off", (per) => {
|
|
33
|
+
if (per === "off") return sc && set(0);
|
|
34
|
+
pillNumber(pill, sc ? String(sc.usd) : "", "USD", "aspend-in", t("Spend cap, in USD"), amount, t("A spend cap is an amount in USD above 0, up to 1,000,000"), sc?.period === per ? sc.usd : null, (usd) => set(usd, per));
|
|
35
|
+
}, "Spend cap", "sess-menu");
|
|
36
|
+
};
|
|
37
|
+
return pill;
|
|
38
|
+
}
|
|
39
|
+
// keySpendLine: a key held at its spend cap, on a line under its row
|
|
40
|
+
function keySpendLine(k) {
|
|
41
|
+
const sc = k.spend;
|
|
42
|
+
if (!sc?.held) return null;
|
|
43
|
+
const s = el("span", "key-rest", sc.reset ? t("At its spend cap · back {in}", { in: untilText(new Date(sc.reset)) }) : t("At its spend cap · until it is raised"));
|
|
44
|
+
s.title = t("This key has spent its cap, so the gateway passes it over and tries the next key or a fallback. With none, requests are refused with a spend-cap error")
|
|
45
|
+
+ (sc.reset ? "\n" + resetText(new Date(sc.reset), new Date(sc.reset).toLocaleString(intlLang())) : "");
|
|
46
|
+
const line = el("div", "key-rest-line key-spend-line");
|
|
47
|
+
line.append(s);
|
|
48
|
+
return line;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// keySpendFill puts key k's spend cap pill in its row, before the gap, and
|
|
52
|
+
// its held line under the row's other lines
|
|
53
|
+
window.keySpendFill = function (row, p, k) {
|
|
54
|
+
row.querySelector(":scope > .aspend")?.remove();
|
|
55
|
+
row.querySelector(":scope > .key-spend-line")?.remove();
|
|
56
|
+
const pill = keySpendPill(p, k), gap = row.querySelector(":scope > .grow");
|
|
57
|
+
gap ? gap.before(pill) : row.append(pill);
|
|
58
|
+
const line = keySpendLine(k);
|
|
59
|
+
if (!line) return;
|
|
60
|
+
const at = row.querySelector(":scope > .key-rest-line") || row.querySelector(":scope > .key-acts");
|
|
61
|
+
at ? at.after(line) : row.append(line);
|
|
62
|
+
row.classList.add("resting");
|
|
63
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Run at the start of the body, before any UI can paint. app.js may still
|
|
2
|
+
// be downloading when the header appears, so its layout must already match
|
|
3
|
+
// the browser, desktop window or tray panel.
|
|
4
|
+
(function () {
|
|
5
|
+
var mode = new URLSearchParams(location.search).get("mode") || "window";
|
|
6
|
+
var web = !!(window.bootPrefs && window.bootPrefs.web);
|
|
7
|
+
document.body.classList.add(mode);
|
|
8
|
+
if (window.bootPrefs && window.bootPrefs.reduceMotion) window.holdStill(true);
|
|
9
|
+
if (web) document.body.classList.add("web");
|
|
10
|
+
// Preserve the existing iOS viewport policy before app.js runs.
|
|
11
|
+
if (web && (/iP(hone|ad|od)/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1))) {
|
|
12
|
+
var viewport = document.querySelector('meta[name="viewport"]');
|
|
13
|
+
if (viewport) viewport.setAttribute("content", "width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover");
|
|
14
|
+
}
|
|
15
|
+
if (!web && /^Mac/.test(navigator.platform)) document.body.classList.add("mac");
|
|
16
|
+
if (!web && /^Linux/.test(navigator.platform)) document.body.classList.add("linux");
|
|
17
|
+
if (/^Win/.test(navigator.platform)) document.documentElement.classList.add("win");
|
|
18
|
+
// the panel opens the main window from the edge nearest the tray: its
|
|
19
|
+
// foot over Windows' bottom taskbar, its header elsewhere, until the app
|
|
20
|
+
// says where the panel was placed (panelside.go, magpiePanelOpenAt)
|
|
21
|
+
if (mode === "panel") document.body.dataset.openAt = /^Win/.test(navigator.platform) ? "foot" : "head";
|
|
22
|
+
// --bar: see app.css, .view
|
|
23
|
+
function bar() { var p = document.createElement("div"); p.className = "view"; p.style.cssText = "position:absolute;visibility:hidden;height:9px"; document.body.appendChild(p); document.documentElement.style.setProperty("--bar", p.offsetWidth - p.clientWidth + "px"); p.remove(); }
|
|
24
|
+
bar();
|
|
25
|
+
addEventListener("resize", bar);
|
|
26
|
+
})();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "magpie",
|
|
3
|
+
"short_name": "magpie",
|
|
4
|
+
"start_url": "./",
|
|
5
|
+
"scope": "./",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#f4f4f6",
|
|
8
|
+
"theme_color": "#f4f4f6",
|
|
9
|
+
"icons": [
|
|
10
|
+
{
|
|
11
|
+
"src": "icons/apple-touch-icon.png",
|
|
12
|
+
"sizes": "180x180",
|
|
13
|
+
"type": "image/png"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"src": "icons/icon-512.png",
|
|
17
|
+
"sizes": "512x512",
|
|
18
|
+
"type": "image/png"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"id": "com.dsh.mobile.extension.magpie",
|
|
22
|
+
"version": "1.0.0",
|
|
23
|
+
"apiVersion": "1",
|
|
24
|
+
"main": "index.html",
|
|
25
|
+
"description": "在手机上打开 magpie 的完整界面(Agents / Providers / Routing / Usage / Settings)。按需下载,数据经宿主取。",
|
|
26
|
+
"capabilities": [
|
|
27
|
+
"content.read",
|
|
28
|
+
"card.write",
|
|
29
|
+
"host.data"
|
|
30
|
+
],
|
|
31
|
+
"hostData": {
|
|
32
|
+
"service": "magpie",
|
|
33
|
+
"match": [
|
|
34
|
+
"/api/"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// The sidebar lights the row under the pointer as a torch would, in its
|
|
2
|
+
// pages (Providers, Routing…, Settings), their sub-lists and the
|
|
3
|
+
// gateway's address at its foot alike:
|
|
4
|
+
// brightest where it points, the rest of that row a step up from the
|
|
5
|
+
// others, which keep their own ink (app.css, span.lit). The light
|
|
6
|
+
// follows the pointer with a little lag and fades out as it moves on; with
|
|
7
|
+
// reduced motion it is where the pointer is at once. Imported by app.js
|
|
8
|
+
// when the pointer first comes over the sidebar (layoutNav), not at start,
|
|
9
|
+
// where it held up the open page's row; a touch never lights a row.
|
|
10
|
+
document.documentElement.classList.add("navlamp");
|
|
11
|
+
const lit = new Map(); // a row's word -> how lit it is, 0 to 1
|
|
12
|
+
let at = null, tx = 0, x = 0, raf = 0;
|
|
13
|
+
|
|
14
|
+
function frame() {
|
|
15
|
+
raf = 0;
|
|
16
|
+
const still = calm(), row = at && at.isConnected && !at.parentElement.matches(".cur, .on") ? at : null;
|
|
17
|
+
if (row && !lit.has(row)) lit.set(row, 0);
|
|
18
|
+
x = still ? tx : x + (tx - x) * 0.22;
|
|
19
|
+
let busy = Math.abs(tx - x) > 0.3;
|
|
20
|
+
for (const [s, v] of lit) {
|
|
21
|
+
const to = s === row ? 1 : 0, now = still ? to : v + (to - v) * (to > v ? 0.22 : 0.1);
|
|
22
|
+
if (!to && (now < 0.01 || !s.isConnected)) {
|
|
23
|
+
lit.delete(s);
|
|
24
|
+
s.classList.remove("lit");
|
|
25
|
+
s.style.removeProperty("--si");
|
|
26
|
+
s.style.removeProperty("--lx");
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (Math.abs(to - now) > 0.002) busy = true;
|
|
30
|
+
lit.set(s, now);
|
|
31
|
+
s.classList.add("lit");
|
|
32
|
+
s.style.setProperty("--si", now.toFixed(3));
|
|
33
|
+
s.style.setProperty("--lx", (x - s.getBoundingClientRect().left).toFixed(1) + "px");
|
|
34
|
+
}
|
|
35
|
+
if (busy) raf = requestAnimationFrame(frame);
|
|
36
|
+
}
|
|
37
|
+
const go = () => { if (!raf) raf = requestAnimationFrame(frame); };
|
|
38
|
+
|
|
39
|
+
document.addEventListener("pointermove", (e) => {
|
|
40
|
+
const row = e.pointerType === "touch" || !e.target.closest ? null : e.target.closest(".nav-sub > .nav-s, body.side-nav :is(#nav > button[data-view], #prefs, #sideGw)");
|
|
41
|
+
const word = row && row.querySelector(":scope > span");
|
|
42
|
+
if (word && !at) x = e.clientX;
|
|
43
|
+
tx = e.clientX;
|
|
44
|
+
if (word !== at) at = word;
|
|
45
|
+
if (at || lit.size) go();
|
|
46
|
+
}, { passive: true });
|
|
47
|
+
document.documentElement.addEventListener("pointerleave", () => { at = null; go(); });
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* Omarchy: magpie drawn in Omarchy's own design language, only on Omarchy.
|
|
2
|
+
omarchy.js puts the class on <html> and the theme's colours, font, border
|
|
3
|
+
and rounding (from ~/.local/state/omarchy/current/theme and Hyprland) in
|
|
4
|
+
the variables; everything here is scoped under html.omarchy, so no other
|
|
5
|
+
system sees any of it. Omarchy's shell draws flat: no shadows, no blur, no
|
|
6
|
+
rounding (Hyprland's decoration:rounding, 0 by default), popups framed by
|
|
7
|
+
the window border's colour, uppercase letter-spaced labels, and a row
|
|
8
|
+
picked out by a faint fill with its text in the accent. */
|
|
9
|
+
|
|
10
|
+
/* what only Omarchy shows, and what it hides: the Appearance setting follows
|
|
11
|
+
Omarchy's theme instead of offering light and dark */
|
|
12
|
+
.om-only { display: none !important; }
|
|
13
|
+
html.omarchy .om-hide { display: none !important; }
|
|
14
|
+
html.omarchy .om-only { display: revert !important; }
|
|
15
|
+
html.omarchy .om-theme-name { color: var(--accent); font-size: 12px; white-space: nowrap; }
|
|
16
|
+
|
|
17
|
+
html.omarchy, html.omarchy body { font-family: var(--ui-font, var(--font)); font-size: var(--om-size); }
|
|
18
|
+
html:where([data-fonts]).omarchy, html:where([data-fonts]).omarchy body { font-style: var(--ui-style, inherit); font-weight: var(--ui-weight, inherit); font-stretch: var(--ui-stretch, 100%); }
|
|
19
|
+
html.omarchy body { -webkit-font-smoothing: antialiased; }
|
|
20
|
+
|
|
21
|
+
/* square, or rounded as far as Hyprland rounds its windows; what spins or
|
|
22
|
+
draws a dial stays round, as does a slider.s knob; a status dot goes square, like Omarchy.s workspace marks */
|
|
23
|
+
html.omarchy *, html.omarchy *::before, html.omarchy *::after { border-radius: var(--om-radius) !important; }
|
|
24
|
+
html.omarchy :is(.pq-dial, .pq-dial b, .sk-ring, .si-spin, .mk-spin, .signing .spinner, .eslide .knob, .effort-ticks i),
|
|
25
|
+
html.omarchy input[type="range"]::-webkit-slider-thumb { border-radius: 50% !important; }
|
|
26
|
+
|
|
27
|
+
/* no shadows or frosted glass: surfaces are the theme's background */
|
|
28
|
+
html.omarchy * { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
|
|
29
|
+
html.omarchy :is(.pop, .modal .dialog, .status.lift, body.panel .profiles.open, .list, body.panel .list, .seg > .thumb) { box-shadow: none; }
|
|
30
|
+
html.omarchy body.panel { background: var(--bg); border: var(--om-border) solid var(--om-edge); }
|
|
31
|
+
|
|
32
|
+
/* popups, menus and dialogs: framed by the window border's colour */
|
|
33
|
+
html.omarchy :is(.pop, body.panel .profiles.open, .status.lift) { background: var(--pop-bg); border: var(--om-border) solid var(--om-edge) !important; }
|
|
34
|
+
html.omarchy .modal { background: color-mix(in srgb, var(--bg) 55%, transparent); }
|
|
35
|
+
html.omarchy .modal .dialog { background: var(--bg); border: var(--om-border) solid var(--om-edge); }
|
|
36
|
+
|
|
37
|
+
/* labels as Omarchy's shell sets its headings: uppercase, spaced out */
|
|
38
|
+
html.omarchy :is(.label, .pop li.group, .kpi span, .editor .ehead + * .k) { letter-spacing: .12em; text-transform: uppercase; font-weight: 500; }
|
|
39
|
+
html.omarchy .brand { letter-spacing: 0; font-weight: 700; }
|
|
40
|
+
|
|
41
|
+
/* the tabs: no track, the chosen one a faint square with its text in the accent */
|
|
42
|
+
html.omarchy .seg { background: transparent; }
|
|
43
|
+
html.omarchy .seg > .thumb { background: var(--om-sel); }
|
|
44
|
+
html.omarchy :is(.seg button.on, .ptabs button.on, .opt.on) { color: var(--accent); }
|
|
45
|
+
html.omarchy :is(.segs, .ptabs) { background: transparent; }
|
|
46
|
+
html.omarchy :is(.segs, .ptabs) > .thumb { background: var(--om-sel); box-shadow: inset 0 0 0 1px var(--om-sel-line); }
|
|
47
|
+
|
|
48
|
+
/* lists: a hairline frame, rows picked out by a faint fill */
|
|
49
|
+
html.omarchy .list { border: 1px solid var(--line); background: transparent; }
|
|
50
|
+
html.omarchy .row:hover { background: var(--om-hover); }
|
|
51
|
+
|
|
52
|
+
/* controls: Omarchy's buttons are a faint fill under a thin edge */
|
|
53
|
+
html.omarchy .field, html.omarchy body.panel .field, html.omarchy body.panel .field:hover { background: var(--om-hover); box-shadow: inset 0 0 0 1px var(--om-hover-line); }
|
|
54
|
+
html.omarchy .field:hover { background: var(--om-sel); }
|
|
55
|
+
html.omarchy .field.open, html.omarchy body.panel .field.open { box-shadow: inset 0 0 0 1px var(--accent); }
|
|
56
|
+
html.omarchy .icon:hover { background: var(--om-sel); color: var(--accent); }
|
|
57
|
+
html.omarchy .text.primary { background: var(--accent); color: var(--accent-fg); }
|
|
58
|
+
|
|
59
|
+
/* a menu's row under the cursor or keys: the fill and the accent, like Walker's */
|
|
60
|
+
html.omarchy :is(.pop li.sel, .pop li:hover:not(.group), .row-menu .rm-item:not(:disabled):is(:hover, :focus)) { background: var(--om-sel); color: var(--accent); }
|
|
61
|
+
html.omarchy :is(.pop li.sel, .pop li:hover) :is(.v, .n) { color: inherit; }
|
|
62
|
+
|
|
63
|
+
/* Linux's close button: square like the rest */
|
|
64
|
+
html.omarchy .icon.winclose { background: transparent; }
|
|
65
|
+
html.omarchy .icon.winclose:hover { background: var(--red-soft); color: var(--red); }
|
|
66
|
+
|
|
67
|
+
html.omarchy ::selection { background: var(--om-sel); color: var(--accent); }
|
|
68
|
+
html.omarchy body.side-nav :is(#nav button[data-view], #prefs).on { background: var(--om-sel); box-shadow: inset 0 0 0 1px var(--om-sel-line); color: var(--accent); }
|
|
69
|
+
html.omarchy .nav-hl { background: var(--om-sel); }
|
|
70
|
+
html.omarchy body.side-nav #nav > .nav-top-hl { background: var(--om-sel); box-shadow: inset 0 0 0 1px var(--om-sel-line); }
|
|
71
|
+
html.omarchy body.side-nav #nav.hl-on button[data-view].on { background: transparent; box-shadow: none; }
|
|
72
|
+
html.omarchy .nav-s.cur { color: var(--accent); }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// On Omarchy (omarchy.org) magpie's windows take the look of Omarchy's own
|
|
2
|
+
// panels: the current theme's palette and font, square corners and the
|
|
3
|
+
// active border's colour (omarchy.css, under html.omarchy). The theme comes
|
|
4
|
+
// with boot.js before the first paint and is asked again every few seconds
|
|
5
|
+
// while the page shows, so one picked in Omarchy's menu is taken at once.
|
|
6
|
+
// Everywhere else nothing here does anything.
|
|
7
|
+
(() => {
|
|
8
|
+
const root = document.documentElement;
|
|
9
|
+
let stamp = "";
|
|
10
|
+
let style = null;
|
|
11
|
+
function apply(th) {
|
|
12
|
+
if (!th || !th.vars) return;
|
|
13
|
+
root.classList.add("omarchy");
|
|
14
|
+
root.dataset.omarchyMode = th.mode || "dark";
|
|
15
|
+
if (th.stamp === stamp) return;
|
|
16
|
+
stamp = th.stamp;
|
|
17
|
+
const decls = Object.entries(th.vars).map(([k, v]) => `${k}: ${v} !important;`).join(" ");
|
|
18
|
+
if (!style) {
|
|
19
|
+
style = document.createElement("style");
|
|
20
|
+
style.id = "omarchy-theme";
|
|
21
|
+
document.head.append(style);
|
|
22
|
+
}
|
|
23
|
+
// on the root and the body both, so neither the light/dark palettes nor
|
|
24
|
+
// the panel's own tints (set on the body) win over the theme
|
|
25
|
+
style.textContent = `:root.omarchy, :root.omarchy body { ${decls} color-scheme: ${th.mode === "light" ? "light" : "dark"}; }`;
|
|
26
|
+
for (const e of document.querySelectorAll(".om-theme-name")) e.textContent = th.name || "";
|
|
27
|
+
window.omarchyTheme = th;
|
|
28
|
+
window.dispatchEvent(new CustomEvent("omarchy-theme", { detail: th }));
|
|
29
|
+
}
|
|
30
|
+
const boot = window.bootPrefs && window.bootPrefs.omarchy;
|
|
31
|
+
if (!boot) return;
|
|
32
|
+
apply(boot);
|
|
33
|
+
// the settings' theme name is drawn after this script runs
|
|
34
|
+
document.addEventListener("DOMContentLoaded", () => { for (const e of document.querySelectorAll(".om-theme-name")) e.textContent = th().name || ""; });
|
|
35
|
+
const th = () => window.omarchyTheme || boot;
|
|
36
|
+
async function poll() {
|
|
37
|
+
if (document.visibilityState === "visible") {
|
|
38
|
+
try {
|
|
39
|
+
const r = await fetch("/api/omarchy", { cache: "no-store" });
|
|
40
|
+
if (r.ok) apply(await r.json());
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
setTimeout(poll, 2500);
|
|
44
|
+
}
|
|
45
|
+
setTimeout(poll, 2500);
|
|
46
|
+
})();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// A provider of a key's Errors menu in its editor's Advanced (ne9ko9 on
|
|
2
|
+
// Discord): magpie handles them (asked again, the next provider tried,
|
|
3
|
+
// said as the agent's API says it), or they pass through as the vendor
|
|
4
|
+
// sent them, asked once. app.js imports this when the editor draws the
|
|
5
|
+
// Errors row, so it weighs nothing on the core.
|
|
6
|
+
window.passErrorsFill = function fill(b, d) {
|
|
7
|
+
const ERR = [["", "magpie handles"], ["pass", "Pass through"]];
|
|
8
|
+
const now = () => ERR[d.passErrors ? 1 : 0];
|
|
9
|
+
b.type = "button";
|
|
10
|
+
b.setAttribute("aria-haspopup", "menu");
|
|
11
|
+
b.setAttribute("aria-expanded", "false");
|
|
12
|
+
b.replaceChildren(el("span", "", t(now()[1])), svg(CHEV, 11, 1.6));
|
|
13
|
+
b.onclick = (e) => {
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
if (b.classList.contains("open")) return closeProtoMenu();
|
|
16
|
+
openProtoMenu(b, ERR.map(([v, name]) => ({ v, name })), now()[0], (v) => {
|
|
17
|
+
d.passErrors = v === "pass";
|
|
18
|
+
b.firstChild.textContent = t(now()[1]);
|
|
19
|
+
}, "Errors", "sess-menu");
|
|
20
|
+
};
|
|
21
|
+
};
|