myaiforone 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/agents/_template/CLAUDE.md +18 -0
- package/agents/_template/agent.json +7 -0
- package/agents/platform/agentcreator/CLAUDE.md +300 -0
- package/agents/platform/appcreator/CLAUDE.md +158 -0
- package/agents/platform/gym/CLAUDE.md +486 -0
- package/agents/platform/gym/agent.json +40 -0
- package/agents/platform/gym/programs/agent-building/program.json +160 -0
- package/agents/platform/gym/programs/automations-mastery/program.json +129 -0
- package/agents/platform/gym/programs/getting-started/program.json +124 -0
- package/agents/platform/gym/programs/mcp-integrations/program.json +116 -0
- package/agents/platform/gym/programs/multi-model-strategy/program.json +115 -0
- package/agents/platform/gym/programs/prompt-engineering/program.json +136 -0
- package/agents/platform/gym/souls/alex.md +12 -0
- package/agents/platform/gym/souls/jordan.md +12 -0
- package/agents/platform/gym/souls/morgan.md +12 -0
- package/agents/platform/gym/souls/riley.md +12 -0
- package/agents/platform/gym/souls/sam.md +12 -0
- package/agents/platform/hub/CLAUDE.md +372 -0
- package/agents/platform/promptcreator/CLAUDE.md +130 -0
- package/agents/platform/skillcreator/CLAUDE.md +163 -0
- package/bin/cli.js +566 -0
- package/config.example.json +310 -0
- package/dist/agent-registry.d.ts +32 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +144 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/channels/discord.d.ts +17 -0
- package/dist/channels/discord.d.ts.map +1 -0
- package/dist/channels/discord.js +114 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/imessage.d.ts +23 -0
- package/dist/channels/imessage.d.ts.map +1 -0
- package/dist/channels/imessage.js +214 -0
- package/dist/channels/imessage.js.map +1 -0
- package/dist/channels/slack.d.ts +19 -0
- package/dist/channels/slack.d.ts.map +1 -0
- package/dist/channels/slack.js +167 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/telegram.d.ts +19 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +274 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +44 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +18 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +23 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +189 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/config.d.ts +134 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +127 -0
- package/dist/config.js.map +1 -0
- package/dist/cron.d.ts +8 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +35 -0
- package/dist/cron.js.map +1 -0
- package/dist/decrypt-keys.d.ts +7 -0
- package/dist/decrypt-keys.d.ts.map +1 -0
- package/dist/decrypt-keys.js +53 -0
- package/dist/decrypt-keys.js.map +1 -0
- package/dist/encrypt-keys.d.ts +8 -0
- package/dist/encrypt-keys.d.ts.map +1 -0
- package/dist/encrypt-keys.js +62 -0
- package/dist/encrypt-keys.js.map +1 -0
- package/dist/executor.d.ts +31 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +2009 -0
- package/dist/executor.js.map +1 -0
- package/dist/gemini-executor.d.ts +27 -0
- package/dist/gemini-executor.d.ts.map +1 -0
- package/dist/gemini-executor.js +160 -0
- package/dist/gemini-executor.js.map +1 -0
- package/dist/goals.d.ts +24 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/goals.js +189 -0
- package/dist/goals.js.map +1 -0
- package/dist/gym/activity-digest.d.ts +30 -0
- package/dist/gym/activity-digest.d.ts.map +1 -0
- package/dist/gym/activity-digest.js +506 -0
- package/dist/gym/activity-digest.js.map +1 -0
- package/dist/gym/dimension-scorer.d.ts +76 -0
- package/dist/gym/dimension-scorer.d.ts.map +1 -0
- package/dist/gym/dimension-scorer.js +236 -0
- package/dist/gym/dimension-scorer.js.map +1 -0
- package/dist/gym/gym-router.d.ts +7 -0
- package/dist/gym/gym-router.d.ts.map +1 -0
- package/dist/gym/gym-router.js +718 -0
- package/dist/gym/gym-router.js.map +1 -0
- package/dist/gym/index.d.ts +11 -0
- package/dist/gym/index.d.ts.map +1 -0
- package/dist/gym/index.js +11 -0
- package/dist/gym/index.js.map +1 -0
- package/dist/heartbeat.d.ts +21 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +163 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/keystore.d.ts +22 -0
- package/dist/keystore.d.ts.map +1 -0
- package/dist/keystore.js +178 -0
- package/dist/keystore.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/daily.d.ts +22 -0
- package/dist/memory/daily.d.ts.map +1 -0
- package/dist/memory/daily.js +82 -0
- package/dist/memory/daily.js.map +1 -0
- package/dist/memory/embeddings.d.ts +15 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +154 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +159 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/search.d.ts +21 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +77 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +23 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +144 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/ollama-executor.d.ts +17 -0
- package/dist/ollama-executor.d.ts.map +1 -0
- package/dist/ollama-executor.js +112 -0
- package/dist/ollama-executor.js.map +1 -0
- package/dist/openai-executor.d.ts +38 -0
- package/dist/openai-executor.d.ts.map +1 -0
- package/dist/openai-executor.js +197 -0
- package/dist/openai-executor.js.map +1 -0
- package/dist/router.d.ts +11 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +185 -0
- package/dist/router.js.map +1 -0
- package/dist/test-message.d.ts +2 -0
- package/dist/test-message.d.ts.map +1 -0
- package/dist/test-message.js +60 -0
- package/dist/test-message.js.map +1 -0
- package/dist/utils/imsg-db-reader.d.ts +24 -0
- package/dist/utils/imsg-db-reader.d.ts.map +1 -0
- package/dist/utils/imsg-db-reader.js +92 -0
- package/dist/utils/imsg-db-reader.js.map +1 -0
- package/dist/utils/imsg-rpc.d.ts +25 -0
- package/dist/utils/imsg-rpc.d.ts.map +1 -0
- package/dist/utils/imsg-rpc.js +149 -0
- package/dist/utils/imsg-rpc.js.map +1 -0
- package/dist/utils/message-formatter.d.ts +3 -0
- package/dist/utils/message-formatter.d.ts.map +1 -0
- package/dist/utils/message-formatter.js +69 -0
- package/dist/utils/message-formatter.js.map +1 -0
- package/dist/web-ui.d.ts +12 -0
- package/dist/web-ui.d.ts.map +1 -0
- package/dist/web-ui.js +5784 -0
- package/dist/web-ui.js.map +1 -0
- package/dist/whatsapp-chats.d.ts +2 -0
- package/dist/whatsapp-chats.d.ts.map +1 -0
- package/dist/whatsapp-chats.js +76 -0
- package/dist/whatsapp-chats.js.map +1 -0
- package/dist/whatsapp-login.d.ts +2 -0
- package/dist/whatsapp-login.d.ts.map +1 -0
- package/dist/whatsapp-login.js +90 -0
- package/dist/whatsapp-login.js.map +1 -0
- package/dist/wiki-sync.d.ts +21 -0
- package/dist/wiki-sync.d.ts.map +1 -0
- package/dist/wiki-sync.js +147 -0
- package/dist/wiki-sync.js.map +1 -0
- package/docs/AddNewAgentGuide.md +100 -0
- package/docs/AddNewMcpGuide.md +72 -0
- package/docs/Architecture.md +795 -0
- package/docs/CLAUDE-AI-SETUP.md +166 -0
- package/docs/Setup.md +297 -0
- package/docs/ai-gym-architecture.md +1040 -0
- package/docs/ai-gym-build-plan.md +343 -0
- package/docs/ai-gym-onboarding.md +122 -0
- package/docs/appcreator_plan.md +348 -0
- package/docs/platform-mcp-audit.md +320 -0
- package/docs/server-deployment-plan.md +503 -0
- package/docs/superpowers/plans/2026-03-25-marketplace.md +1281 -0
- package/docs/superpowers/specs/2026-03-25-marketplace-design.md +287 -0
- package/docs/user-guide.md +2016 -0
- package/mcp-catalog.json +628 -0
- package/package.json +63 -0
- package/public/MyAIforOne-logomark-512.svg +16 -0
- package/public/MyAIforOne-logomark-transparent.svg +15 -0
- package/public/activity.html +314 -0
- package/public/admin.html +1674 -0
- package/public/agent-dashboard.html +670 -0
- package/public/api-docs.html +1106 -0
- package/public/automations.html +722 -0
- package/public/canvas.css +223 -0
- package/public/canvas.js +588 -0
- package/public/changelog.html +231 -0
- package/public/gym.html +2766 -0
- package/public/home.html +1930 -0
- package/public/index.html +2809 -0
- package/public/lab.html +1643 -0
- package/public/library.html +1442 -0
- package/public/marketplace.html +1101 -0
- package/public/mcp-docs.html +441 -0
- package/public/mini.html +390 -0
- package/public/monitor.html +584 -0
- package/public/org.html +4304 -0
- package/public/projects.html +734 -0
- package/public/settings.html +645 -0
- package/public/tasks.html +932 -0
- package/public/trainers/alex.svg +12 -0
- package/public/trainers/jordan.svg +12 -0
- package/public/trainers/morgan.svg +12 -0
- package/public/trainers/riley.svg +12 -0
- package/public/trainers/sam.svg +12 -0
- package/public/user-guide.html +218 -0
- package/registry/agents.json +3 -0
- package/registry/apps.json +20 -0
- package/registry/installed-drafts.json +3 -0
- package/registry/mcps.json +1084 -0
- package/registry/prompts/personal/mcp-test-prompt.md +6 -0
- package/registry/prompts/personal/memory-recall.md +6 -0
- package/registry/prompts/platform/brainstorm.md +15 -0
- package/registry/prompts/platform/code-review.md +16 -0
- package/registry/prompts/platform/explain.md +16 -0
- package/registry/prompts.json +58 -0
- package/registry/skills/external/brainstorming.md +5 -0
- package/registry/skills/external/code-review.md +40 -0
- package/registry/skills/external/frontend-patterns.md +642 -0
- package/registry/skills/external/frontend-slides.md +184 -0
- package/registry/skills/external/systematic-debugging.md +5 -0
- package/registry/skills/external/tdd.md +328 -0
- package/registry/skills/external/verification-before-completion.md +5 -0
- package/registry/skills/external/writing-plans.md +5 -0
- package/registry/skills/platform/ai41_app_build.md +930 -0
- package/registry/skills/platform/ai41_app_deploy.md +168 -0
- package/registry/skills/platform/ai41_app_orchestrator.md +239 -0
- package/registry/skills/platform/ai41_app_patterns.md +359 -0
- package/registry/skills/platform/ai41_app_register.md +85 -0
- package/registry/skills/platform/ai41_app_scaffold.md +421 -0
- package/registry/skills/platform/ai41_app_verify.md +107 -0
- package/registry/skills/platform/opProjectCreate.md +239 -0
- package/registry/skills/platform/op_devbrowser.md +136 -0
- package/registry/skills/platform/sop_brandguidelines.md +103 -0
- package/registry/skills/platform/sop_docx.md +117 -0
- package/registry/skills/platform/sop_frontenddesign.md +44 -0
- package/registry/skills/platform/sop_frontenddesign_v2.md +659 -0
- package/registry/skills/platform/sop_mcpbuilder.md +133 -0
- package/registry/skills/platform/sop_pdf.md +172 -0
- package/registry/skills/platform/sop_pptx.md +133 -0
- package/registry/skills/platform/sop_skillcreator.md +104 -0
- package/registry/skills/platform/sop_themefactory.md +128 -0
- package/registry/skills/platform/sop_webapptesting.md +75 -0
- package/registry/skills/platform/sop_webartifactsbuilder.md +97 -0
- package/registry/skills/platform/sop_xlsx.md +134 -0
- package/registry/skills.json +1055 -0
- package/scripts/discover-chats.sh +11 -0
- package/scripts/install-service-windows.ps1 +87 -0
- package/scripts/install-service.sh +52 -0
- package/scripts/seed-registry.ts +195 -0
- package/scripts/test-send.sh +5 -0
- package/scripts/tray-indicator.ps1 +35 -0
- package/scripts/uninstall-service-windows.ps1 +23 -0
- package/scripts/uninstall-service.sh +15 -0
- package/scripts/xbar-myagent.5s.sh +32 -0
- package/server/mcp-server/dist/index.d.ts +11 -0
- package/server/mcp-server/dist/index.js +1332 -0
- package/server/mcp-server/dist/lib/api-client.d.ts +165 -0
- package/server/mcp-server/dist/lib/api-client.js +241 -0
- package/server/mcp-server/index.ts +1545 -0
- package/server/mcp-server/lib/api-client.ts +366 -0
- package/server/mcp-server/tsconfig.json +14 -0
- package/src/agent-registry.ts +180 -0
- package/src/channels/discord.ts +129 -0
- package/src/channels/imessage.ts +261 -0
- package/src/channels/slack.ts +208 -0
- package/src/channels/telegram.ts +307 -0
- package/src/channels/types.ts +62 -0
- package/src/channels/whatsapp.ts +227 -0
- package/src/config.ts +281 -0
- package/src/cron.ts +43 -0
- package/src/decrypt-keys.ts +60 -0
- package/src/encrypt-keys.ts +70 -0
- package/src/executor.ts +2190 -0
- package/src/gemini-executor.ts +212 -0
- package/src/goals.ts +240 -0
- package/src/gym/activity-digest.ts +546 -0
- package/src/gym/dimension-scorer.ts +297 -0
- package/src/gym/gym-router.ts +801 -0
- package/src/gym/index.ts +19 -0
- package/src/heartbeat.ts +220 -0
- package/src/index.ts +275 -0
- package/src/keystore.ts +190 -0
- package/src/logger.ts +51 -0
- package/src/memory/daily.ts +101 -0
- package/src/memory/embeddings.ts +185 -0
- package/src/memory/index.ts +218 -0
- package/src/memory/search.ts +124 -0
- package/src/memory/store.ts +189 -0
- package/src/ollama-executor.ts +126 -0
- package/src/openai-executor.ts +259 -0
- package/src/router.ts +230 -0
- package/src/test-message.ts +72 -0
- package/src/utils/imsg-db-reader.ts +109 -0
- package/src/utils/imsg-rpc.ts +178 -0
- package/src/utils/message-formatter.ts +90 -0
- package/src/web-ui.ts +5778 -0
- package/src/whatsapp-chats.ts +91 -0
- package/src/whatsapp-login.ts +110 -0
- package/src/wiki-sync.ts +199 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,1101 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>MyAIforOne — Marketplace</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
:root{
|
|
11
|
+
--bg-deep:#060a13;--bg-surface:rgba(12,18,33,0.92);--bg-card:rgba(16,22,40,0.85);
|
|
12
|
+
--bg-input:rgba(0,0,0,0.35);--border-dim:rgba(56,189,248,0.08);--border-glow:rgba(56,189,248,0.18);
|
|
13
|
+
--border-active:rgba(56,189,248,0.45);--text-primary:rgba(255,255,255,0.92);
|
|
14
|
+
--text-secondary:rgba(255,255,255,0.68);--text-muted:rgba(148,163,184,0.55);
|
|
15
|
+
--accent:#22d3ee;--accent-soft:#38bdf8;--accent-bg:rgba(6,182,212,0.15);
|
|
16
|
+
--accent-glow:rgba(34,211,238,0.12);--purple:rgba(139,92,246,0.7);--purple-bg:rgba(139,92,246,0.12);
|
|
17
|
+
--green:#4ade80;--green-bg:rgba(74,222,128,0.1);--amber:#fbbf24;--amber-bg:rgba(251,191,36,0.1);
|
|
18
|
+
--red:#f87171;--red-bg:rgba(248,113,113,0.1);--shadow:0 2px 12px rgba(0,0,0,0.3);
|
|
19
|
+
--shadow-glow:0 0 20px rgba(34,211,238,0.08);--radius:12px;
|
|
20
|
+
--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'IBM Plex Mono',monospace;--font-display:'Syne',sans-serif;
|
|
21
|
+
}
|
|
22
|
+
[data-theme="light"]{
|
|
23
|
+
--bg-deep:#f4f6f9;--bg-surface:rgba(255,255,255,0.95);--bg-card:rgba(255,255,255,0.9);
|
|
24
|
+
--bg-input:rgba(0,0,0,0.04);--border-dim:rgba(0,0,0,0.08);--border-glow:rgba(14,116,144,0.18);
|
|
25
|
+
--border-active:rgba(14,116,144,0.45);--text-primary:rgba(15,23,42,0.92);
|
|
26
|
+
--text-secondary:rgba(51,65,85,0.8);--text-muted:rgba(100,116,139,0.6);
|
|
27
|
+
--accent:#0891b2;--accent-soft:#0e7490;--accent-bg:rgba(14,116,144,0.08);
|
|
28
|
+
--accent-glow:rgba(14,116,144,0.06);--purple:rgba(109,40,217,0.75);--purple-bg:rgba(139,92,246,0.08);
|
|
29
|
+
--green:#16a34a;--green-bg:rgba(22,163,74,0.08);--amber:#d97706;--amber-bg:rgba(217,119,6,0.08);
|
|
30
|
+
--red:#dc2626;--red-bg:rgba(220,38,38,0.08);--shadow:0 1px 8px rgba(0,0,0,0.06);--shadow-glow:none;
|
|
31
|
+
}
|
|
32
|
+
html,body{width:100%;height:100%;overflow:hidden;max-width:100vw;background:var(--bg-deep);font-family:var(--font-sans);color:var(--text-primary);transition:background .3s,color .3s}
|
|
33
|
+
|
|
34
|
+
/* ── Topbar ── */
|
|
35
|
+
.topbar{height:56px;display:flex;align-items:center;padding:0 24px;background:var(--bg-surface);border-bottom:1px solid var(--border-dim);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);position:fixed;top:0;left:0;right:0;z-index:100;overflow:hidden;max-width:100vw}
|
|
36
|
+
.topbar-logo{display:flex;align-items:center;gap:10px;margin-right:32px;flex-shrink:0}
|
|
37
|
+
.logo-mark{width:28px;height:28px;border-radius:8px;object-fit:contain}
|
|
38
|
+
.logo-text{font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--accent)}
|
|
39
|
+
.tab-group{display:flex;gap:0;flex-shrink:1;min-width:0;overflow-x:auto}
|
|
40
|
+
.tab-btn{font-family:var(--font-sans);font-size:13px;font-weight:600;color:var(--text-muted);background:none;border:none;padding:16px 14px;cursor:pointer;position:relative;transition:color .2s;text-decoration:none;display:block;white-space:nowrap;flex-shrink:0}
|
|
41
|
+
.tab-btn:hover{color:var(--text-secondary)}
|
|
42
|
+
.tab-btn.active{color:var(--accent)}
|
|
43
|
+
.tab-btn.active::after{content:'';position:absolute;bottom:0;left:12px;right:12px;height:2px;background:var(--accent);border-radius:1px}
|
|
44
|
+
.sub-nav{position:fixed;top:56px;left:0;right:0;z-index:99;height:36px;display:flex;align-items:center;gap:2px;padding:0 24px;background:var(--bg-surface);border-bottom:1px solid var(--border-dim);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}
|
|
45
|
+
.sub-nav-link{font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--text-muted);text-decoration:none;padding:4px 10px;border-radius:5px;transition:color .2s}
|
|
46
|
+
.sub-nav-link:hover{color:var(--text-secondary)}
|
|
47
|
+
.sub-nav-link.active{color:var(--accent)}
|
|
48
|
+
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:12px;flex-shrink:0}
|
|
49
|
+
.gym-nav-btn{font-family:var(--font-sans);font-size:12px;font-weight:700;color:#fff;background:var(--accent);border:none;border-radius:8px;padding:6px 14px;cursor:pointer;text-decoration:none;letter-spacing:.02em;transition:opacity .2s,transform .2s;white-space:nowrap}
|
|
50
|
+
.gym-nav-btn:hover{opacity:.88;transform:translateY(-1px)}
|
|
51
|
+
.theme-toggle,.gear-btn{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .2stext-decoration:none;}
|
|
52
|
+
.theme-toggle:hover,.gear-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
53
|
+
.docs-dropdown{position:relative;display:inline-block}
|
|
54
|
+
.docs-menu{display:none;position:absolute;top:40px;right:0;min-width:160px;background:var(--bg-surface);border:1px solid var(--border-glow);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.4);z-index:200;padding:4px;backdrop-filter:blur(20px)}
|
|
55
|
+
.docs-menu.open{display:block}
|
|
56
|
+
.docs-menu a{display:flex;align-items:center;gap:8px;padding:7px 12px;font-size:12px;font-weight:500;color:var(--text-secondary);text-decoration:none;border-radius:6px;transition:all .15s}
|
|
57
|
+
.docs-menu a:hover{background:var(--accent-bg);color:var(--accent)}
|
|
58
|
+
.docs-menu a .dm-icon{font-size:14px;width:18px;text-align:center}
|
|
59
|
+
|
|
60
|
+
/* ── Canvas ── */
|
|
61
|
+
.canvas{position:fixed;top:56px;left:0;right:0;bottom:0;overflow:auto;padding:32px 40px}
|
|
62
|
+
|
|
63
|
+
/* ── Toolbar ── */
|
|
64
|
+
.toolbar{display:flex;align-items:center;gap:12px;margin-bottom:28px;flex-wrap:wrap}
|
|
65
|
+
.type-tabs{display:flex;gap:4px;background:var(--bg-card);border:1px solid var(--border-dim);border-radius:10px;padding:4px}
|
|
66
|
+
.type-tab{font-family:var(--font-sans);font-size:12px;font-weight:600;padding:6px 18px;border-radius:7px;border:none;background:transparent;color:var(--text-muted);cursor:pointer;transition:all .2s}
|
|
67
|
+
.type-tab:hover{color:var(--text-secondary)}
|
|
68
|
+
.type-tab.active{background:var(--accent-bg);color:var(--accent);border:1px solid var(--border-active)}
|
|
69
|
+
.search-wrap{flex:1;max-width:320px;position:relative}
|
|
70
|
+
.search-input{width:100%;background:var(--bg-input);border:1px solid var(--border-dim);border-radius:8px;padding:8px 12px 8px 34px;font-family:var(--font-sans);font-size:13px;color:var(--text-primary);outline:none;transition:border-color .2s}
|
|
71
|
+
.search-input::placeholder{color:var(--text-muted)}
|
|
72
|
+
.search-input:focus{border-color:var(--border-active)}
|
|
73
|
+
.search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:14px;color:var(--text-muted);pointer-events:none}
|
|
74
|
+
.filter-pills{display:flex;gap:6px;flex-wrap:wrap}
|
|
75
|
+
.pill{font-family:var(--font-mono);font-size:10px;padding:4px 10px;border-radius:6px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;transition:all .2s;white-space:nowrap}
|
|
76
|
+
.pill:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
77
|
+
.pill.active{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
|
|
78
|
+
|
|
79
|
+
/* ── Sections ── */
|
|
80
|
+
.sections{display:flex;flex-direction:column;gap:36px}
|
|
81
|
+
.section-header{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--border-dim)}
|
|
82
|
+
.section-title{font-family:var(--font-display);font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted)}
|
|
83
|
+
.section-count{font-family:var(--font-mono);font-size:10px;padding:2px 7px;border-radius:4px;background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim)}
|
|
84
|
+
.section-action{margin-left:auto}
|
|
85
|
+
|
|
86
|
+
/* ── Grid ── */
|
|
87
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
|
88
|
+
|
|
89
|
+
/* ── Card ── */
|
|
90
|
+
.card{background:var(--bg-card);border:1px solid var(--border-dim);border-radius:var(--radius);padding:18px;display:flex;flex-direction:column;gap:10px;transition:all .25s;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);height:200px}
|
|
91
|
+
|
|
92
|
+
/* ── App Card (rich tile from /apps) ── */
|
|
93
|
+
.app-card{background:var(--bg-card);border:1px solid var(--border-dim);border-radius:var(--radius);padding:20px;display:flex;flex-direction:column;gap:12px;transition:all .25s;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
|
|
94
|
+
.app-card:hover{border-color:var(--border-glow);box-shadow:var(--shadow),var(--shadow-glow);transform:translateY(-1px)}
|
|
95
|
+
.app-card-top{display:flex;align-items:flex-start;gap:12px}
|
|
96
|
+
.app-icon{width:44px;height:44px;border-radius:10px;background:var(--accent-bg);border:1px solid var(--border-active);display:flex;align-items:center;justify-content:center;font-family:var(--font-display);font-size:16px;font-weight:800;color:var(--accent);flex-shrink:0}
|
|
97
|
+
.app-name{font-family:var(--font-display);font-size:15px;font-weight:700;line-height:1.2}
|
|
98
|
+
.app-url{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}
|
|
99
|
+
.app-badges{display:flex;flex-direction:column;align-items:flex-end;gap:4px;margin-left:auto;flex-shrink:0}
|
|
100
|
+
.status-badge{font-family:var(--font-mono);font-size:9px;font-weight:600;padding:3px 8px;border-radius:5px;white-space:nowrap;letter-spacing:.03em}
|
|
101
|
+
.status-live{background:var(--green-bg);color:var(--green);border:1px solid rgba(74,222,128,0.3)}
|
|
102
|
+
.status-draft{background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim)}
|
|
103
|
+
.status-maintenance{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(251,191,36,0.3)}
|
|
104
|
+
.status-down{background:var(--red-bg);color:var(--red);border:1px solid rgba(248,113,113,0.3)}
|
|
105
|
+
.deploy-badge{font-family:var(--font-mono);font-size:9px;padding:2px 7px;border-radius:4px;background:var(--purple-bg);color:var(--purple);border:1px solid rgba(139,92,246,0.2)}
|
|
106
|
+
.app-desc{font-size:12px;color:var(--text-secondary);line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
107
|
+
.app-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
108
|
+
.app-tag{font-family:var(--font-mono);font-size:9px;padding:2px 7px;border-radius:4px;background:var(--purple-bg);color:var(--purple);border:1px solid rgba(139,92,246,0.2)}
|
|
109
|
+
.agent-dev-badge{display:flex;align-items:center;gap:4px;font-family:var(--font-mono);font-size:9px;padding:2px 7px;border-radius:4px;background:var(--accent-bg);color:var(--accent);border:1px solid rgba(34,211,238,0.2);cursor:pointer;transition:all .2s;text-decoration:none}
|
|
110
|
+
.agent-dev-badge:hover{background:var(--accent);color:#000}
|
|
111
|
+
.app-actions{display:flex;gap:8px;margin-top:2px;align-items:center}
|
|
112
|
+
.btn-launch{font-family:var(--font-sans);font-size:11px;font-weight:600;padding:6px 14px;border-radius:7px;border:none;background:var(--accent-bg);color:var(--accent);border:1px solid var(--border-active);cursor:pointer;transition:all .2s;text-decoration:none;display:inline-flex;align-items:center;gap:4px}
|
|
113
|
+
.btn-launch:hover{background:var(--accent);color:#000}
|
|
114
|
+
.btn-icon{width:28px;height:28px;border-radius:7px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s}
|
|
115
|
+
.btn-icon:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
116
|
+
.btn-icon.danger:hover{border-color:rgba(248,113,113,0.4);color:var(--red)}
|
|
117
|
+
.health-chip{margin-left:auto;font-family:var(--font-mono);font-size:9px;display:flex;align-items:center;gap:4px;color:var(--text-muted);cursor:pointer;border:1px solid var(--border-dim);padding:2px 8px;border-radius:5px;background:transparent;transition:all .2s}
|
|
118
|
+
.health-chip:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
119
|
+
.health-dot{width:6px;height:6px;border-radius:50%;background:var(--text-muted)}
|
|
120
|
+
.health-dot.healthy{background:var(--green)}
|
|
121
|
+
.health-dot.down{background:var(--red)}
|
|
122
|
+
.card:hover{border-color:var(--border-glow);box-shadow:var(--shadow),var(--shadow-glow);transform:translateY(-1px)}
|
|
123
|
+
.card.installed{border-color:rgba(74,222,128,0.2)}
|
|
124
|
+
.card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
|
|
125
|
+
.card-name{font-family:var(--font-display);font-size:14px;font-weight:700;color:var(--text-primary);line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
126
|
+
.card-badges{display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex-shrink:0}
|
|
127
|
+
.badge{font-family:var(--font-mono);font-size:9px;font-weight:600;padding:3px 7px;border-radius:4px;white-space:nowrap;letter-spacing:.03em}
|
|
128
|
+
.badge-verified{background:var(--accent-bg);color:var(--accent);border:1px solid rgba(34,211,238,0.3)}
|
|
129
|
+
.badge-external{background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim)}
|
|
130
|
+
.badge-personal{background:var(--purple-bg);color:var(--purple);border:1px solid rgba(139,92,246,0.3)}
|
|
131
|
+
.badge-installed{background:var(--green-bg);color:var(--green);border:1px solid rgba(74,222,128,0.3)}
|
|
132
|
+
.card-provider{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px}
|
|
133
|
+
.card-desc{font-size:12px;color:var(--text-secondary);line-height:1.5;flex:1;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
|
134
|
+
.card-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
135
|
+
.tag{font-family:var(--font-mono);font-size:9px;padding:2px 7px;border-radius:4px;background:var(--purple-bg);color:var(--purple);border:1px solid rgba(139,92,246,0.2)}
|
|
136
|
+
.assigned-label{font-family:var(--font-mono);font-size:9px;color:var(--text-muted)}
|
|
137
|
+
.card-actions{display:flex;gap:8px;margin-top:4px}
|
|
138
|
+
.btn{font-family:var(--font-sans);font-size:12px;font-weight:600;padding:7px 14px;border-radius:8px;border:none;cursor:pointer;transition:all .2s;flex:1;text-align:center}
|
|
139
|
+
.btn-install{background:var(--accent-bg);color:var(--accent);border:1px solid var(--border-active)}
|
|
140
|
+
.btn-install:hover{background:var(--accent);color:#000}
|
|
141
|
+
.btn-install:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}
|
|
142
|
+
.btn-manage{background:var(--bg-input);color:var(--text-secondary);border:1px solid var(--border-dim)}
|
|
143
|
+
.btn-manage:hover{border-color:var(--border-glow);color:var(--text-primary)}
|
|
144
|
+
.btn-installing{background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim);pointer-events:none}
|
|
145
|
+
|
|
146
|
+
/* ── Small buttons ── */
|
|
147
|
+
.btn-sm{font-family:var(--font-sans);font-size:11px;font-weight:600;padding:5px 12px;border-radius:7px;border:none;cursor:pointer;transition:all .2s;white-space:nowrap}
|
|
148
|
+
.btn-sm-accent{background:var(--accent-bg);color:var(--accent);border:1px solid var(--border-active)}
|
|
149
|
+
.btn-sm-accent:hover{background:var(--accent);color:#000}
|
|
150
|
+
.btn-sm-ghost{background:transparent;color:var(--text-muted);border:1px solid var(--border-dim)}
|
|
151
|
+
.btn-sm-ghost:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
152
|
+
.btn-sm-green{background:var(--green-bg);color:var(--green);border:1px solid rgba(74,222,128,0.3)}
|
|
153
|
+
.btn-sm-green:hover{background:var(--green);color:#000}
|
|
154
|
+
.btn-sm-green:disabled{opacity:.4;cursor:not-allowed}
|
|
155
|
+
|
|
156
|
+
/* ── Scan / Add panels ── */
|
|
157
|
+
.panel{background:var(--bg-input);border:1px solid var(--border-dim);border-radius:10px;padding:16px;margin-bottom:16px}
|
|
158
|
+
.panel.hidden{display:none}
|
|
159
|
+
.panel-row{display:flex;gap:8px;align-items:center;margin-bottom:10px}
|
|
160
|
+
.panel-input{flex:1;background:var(--bg-card);border:1px solid var(--border-dim);border-radius:7px;padding:7px 11px;font-family:var(--font-mono);font-size:11px;color:var(--text-primary);outline:none;transition:border-color .2s}
|
|
161
|
+
.panel-input::placeholder{color:var(--text-muted)}
|
|
162
|
+
.panel-input:focus{border-color:var(--border-active)}
|
|
163
|
+
.scan-results{display:flex;flex-direction:column;gap:4px;margin-bottom:10px;max-height:200px;overflow-y:auto}
|
|
164
|
+
.scan-result-item{display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:7px;border:1px solid var(--border-dim);background:var(--bg-card);cursor:pointer;transition:border-color .2s}
|
|
165
|
+
.scan-result-item:hover{border-color:var(--border-glow)}
|
|
166
|
+
.scan-result-item input{accent-color:var(--accent);flex-shrink:0}
|
|
167
|
+
.scan-result-name{font-size:12px;font-weight:600;color:var(--text-primary);flex:1}
|
|
168
|
+
.scan-result-desc{font-size:10px;color:var(--text-muted);font-family:var(--font-mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px}
|
|
169
|
+
.panel-label{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.04em}
|
|
170
|
+
.panel-hint{font-size:11px;color:var(--text-muted);margin-top:8px}
|
|
171
|
+
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px}
|
|
172
|
+
.form-grid-full{grid-column:1/-1}
|
|
173
|
+
.type-toggle{display:flex;gap:4px;margin-bottom:10px}
|
|
174
|
+
.type-toggle-btn{font-family:var(--font-mono);font-size:11px;padding:5px 14px;border-radius:6px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;transition:all .2s}
|
|
175
|
+
.type-toggle-btn.active{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
|
|
176
|
+
|
|
177
|
+
/* ── Empty state ── */
|
|
178
|
+
.empty-state{padding:32px 20px;text-align:center;color:var(--text-muted)}
|
|
179
|
+
.empty-icon{font-size:28px;margin-bottom:8px}
|
|
180
|
+
.empty-title{font-family:var(--font-display);font-size:14px;font-weight:700;color:var(--text-secondary);margin-bottom:4px}
|
|
181
|
+
.empty-sub{font-size:12px}
|
|
182
|
+
|
|
183
|
+
/* ── Toast ── */
|
|
184
|
+
#toast-container{position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
|
185
|
+
.toast{font-family:var(--font-sans);font-size:12px;font-weight:500;padding:10px 16px;border-radius:8px;border:1px solid;backdrop-filter:blur(20px);pointer-events:none;animation:toast-in .2s ease;max-width:320px}
|
|
186
|
+
.toast-success{background:var(--green-bg);color:var(--green);border-color:rgba(74,222,128,0.3)}
|
|
187
|
+
.toast-error{background:var(--red-bg);color:var(--red);border-color:rgba(248,113,113,0.3)}
|
|
188
|
+
.toast-info{background:var(--accent-bg);color:var(--accent);border-color:rgba(34,211,238,0.3)}
|
|
189
|
+
@keyframes toast-in{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:translateX(0)}}
|
|
190
|
+
|
|
191
|
+
/* ── Modal ── */
|
|
192
|
+
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.7);backdrop-filter:blur(4px);z-index:500;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .2s}
|
|
193
|
+
.modal-overlay.open{opacity:1;pointer-events:all}
|
|
194
|
+
.modal{background:var(--bg-surface);border:1px solid var(--border-glow);border-radius:16px;padding:28px;width:100%;max-width:440px;box-shadow:0 20px 60px rgba(0,0,0,0.5);transform:translateY(8px);transition:transform .2s}
|
|
195
|
+
.modal-overlay.open .modal{transform:translateY(0)}
|
|
196
|
+
.modal-title{font-family:var(--font-display);font-size:16px;font-weight:700;color:var(--text-primary);margin-bottom:4px}
|
|
197
|
+
.modal-sub{font-size:12px;color:var(--text-muted);margin-bottom:18px}
|
|
198
|
+
.agent-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto;margin-bottom:18px}
|
|
199
|
+
.agent-check-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;border:1px solid var(--border-dim);cursor:pointer;transition:all .2s;background:var(--bg-card)}
|
|
200
|
+
.agent-check-item:hover{border-color:var(--border-glow)}
|
|
201
|
+
.agent-check-item input[type="checkbox"]{accent-color:var(--accent);width:14px;height:14px;cursor:pointer}
|
|
202
|
+
.agent-check-name{font-size:12px;font-weight:600;color:var(--text-primary);flex:1}
|
|
203
|
+
.agent-check-id{font-family:var(--font-mono);font-size:10px;color:var(--text-muted)}
|
|
204
|
+
.modal-actions{display:flex;gap:8px}
|
|
205
|
+
.modal-btn{font-family:var(--font-sans);font-size:13px;font-weight:600;padding:9px 18px;border-radius:9px;border:none;cursor:pointer;transition:all .2s;flex:1}
|
|
206
|
+
.modal-btn-primary{background:var(--accent);color:#000}
|
|
207
|
+
.modal-btn-primary:hover{opacity:.9}
|
|
208
|
+
.modal-btn-primary:disabled{opacity:.4;cursor:not-allowed}
|
|
209
|
+
.modal-btn-secondary{background:var(--bg-input);color:var(--text-secondary);border:1px solid var(--border-dim)}
|
|
210
|
+
.modal-btn-secondary:hover{border-color:var(--border-glow);color:var(--text-primary)}
|
|
211
|
+
.missing-keys-alert{background:var(--amber-bg);border:1px solid rgba(251,191,36,0.3);border-radius:8px;padding:10px 12px;font-size:11px;color:var(--amber);margin-top:12px;line-height:1.5}
|
|
212
|
+
/* ── Platform default toggle ── */
|
|
213
|
+
.pill-platform{border-color:rgba(251,191,36,0.4);color:var(--amber)}
|
|
214
|
+
.pill-platform.active{border-color:var(--amber);color:var(--amber);background:var(--amber-bg)}
|
|
215
|
+
.badge-platform{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(251,191,36,0.35)}
|
|
216
|
+
.btn-default-on{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(251,191,36,0.4);font-size:11px;padding:5px 10px;border-radius:7px;cursor:pointer;font-family:var(--font-mono);font-weight:600;transition:all .2s;white-space:nowrap}
|
|
217
|
+
.btn-default-on:hover{background:var(--amber);color:#000}
|
|
218
|
+
.btn-default-off{background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim);font-size:11px;padding:5px 10px;border-radius:7px;cursor:pointer;font-family:var(--font-mono);font-weight:600;transition:all .2s;white-space:nowrap}
|
|
219
|
+
.btn-default-off:hover{border-color:rgba(251,191,36,0.4);color:var(--amber)}
|
|
220
|
+
.card-platform-default{border-color:rgba(251,191,36,0.25)}
|
|
221
|
+
</style>
|
|
222
|
+
</head>
|
|
223
|
+
<body>
|
|
224
|
+
|
|
225
|
+
<div class="topbar">
|
|
226
|
+
<a href="/" style="display:flex;align-items:center;gap:10px;margin-right:28px;text-decoration:none">
|
|
227
|
+
<img class="logo-mark" src="/MyAIforOne-logomark-transparent.svg" alt="MyAIforOne">
|
|
228
|
+
<span class="logo-text">MyAIforOne</span>
|
|
229
|
+
</a>
|
|
230
|
+
<nav class="tab-group">
|
|
231
|
+
<a class="tab-btn" href="/">Home</a>
|
|
232
|
+
<a class="tab-btn" href="/org">Agents</a>
|
|
233
|
+
<a class="tab-btn" href="/ui">Chat</a>
|
|
234
|
+
<a class="tab-btn" href="/library">Library</a>
|
|
235
|
+
<a class="tab-btn" href="/lab">Lab</a>
|
|
236
|
+
</nav>
|
|
237
|
+
<div class="topbar-right">
|
|
238
|
+
<a class="gym-nav-btn gym-tab-link" href="/gym" style="display:none">Gym</a>
|
|
239
|
+
<a href="/marketplace" class="gear-btn" title="Marketplace" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Marketplace</a>
|
|
240
|
+
<a href="/monitor" class="gear-btn" title="Monitor" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Monitor</a>
|
|
241
|
+
<a href="/admin" class="gear-btn" title="Admin" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Admin</a>
|
|
242
|
+
<a href="/user-guide" class="gear-btn" title="User Guide" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">User Guide</a>
|
|
243
|
+
<button onclick="window.open('/mini','minibar','width=440,height=460,resizable=yes,scrollbars=no')" title="Open Mini Bar" style="display:flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;font-size:15px;transition:all .2s" onmouseover="this.style.borderColor='var(--border-glow)'" onmouseout="this.style.borderColor='var(--border-dim)'">⊡</button>
|
|
244
|
+
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme">☀</button>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div class="canvas" id="canvas">
|
|
249
|
+
<div class="toolbar">
|
|
250
|
+
<div class="type-tabs">
|
|
251
|
+
<button class="type-tab active" data-type="apps" onclick="switchType('apps')">Apps</button>
|
|
252
|
+
<button class="type-tab" data-type="skills" onclick="switchType('skills')">Skills</button>
|
|
253
|
+
<button class="type-tab" data-type="agents" onclick="switchType('agents')">Agents</button>
|
|
254
|
+
<button class="type-tab" data-type="mcps" onclick="switchType('mcps')">MCPs</button>
|
|
255
|
+
<button class="type-tab" data-type="prompts" onclick="switchType('prompts')">Prompts</button>
|
|
256
|
+
</div>
|
|
257
|
+
<div id="trigger-config" style="display:none;align-items:center;gap:6px;background:var(--bg-input);border:1px solid var(--border-dim);border-radius:8px;padding:5px 10px">
|
|
258
|
+
<span style="font-family:var(--font-mono);font-size:10px;color:var(--text-muted)">trigger:</span>
|
|
259
|
+
<span id="trigger-display" style="font-family:var(--font-mono);font-size:13px;font-weight:700;color:var(--accent);min-width:16px;text-align:center"></span>
|
|
260
|
+
<button class="btn-sm btn-sm-ghost" style="padding:3px 8px;font-size:10px" onclick="editTrigger()">✎</button>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="search-wrap">
|
|
263
|
+
<span class="search-icon">⌕</span>
|
|
264
|
+
<input class="search-input" id="search-input" placeholder="Search…" oninput="render()">
|
|
265
|
+
</div>
|
|
266
|
+
<div class="filter-pills" id="filter-pills"></div>
|
|
267
|
+
</div>
|
|
268
|
+
<div id="content"></div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div id="toast-container"></div>
|
|
272
|
+
|
|
273
|
+
<!-- Add App Panel (sits above canvas content, shown on demand) -->
|
|
274
|
+
<div class="panel hidden" id="add-app-panel" style="position:fixed;top:68px;left:40px;right:40px;z-index:200;max-width:700px;margin:0 auto">
|
|
275
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
|
276
|
+
<span class="panel-label" style="margin:0">Register <span id="add-app-provider-label">External Source</span> App</span>
|
|
277
|
+
<button class="btn-sm btn-sm-ghost" style="padding:3px 8px;font-size:10px" onclick="toggleAddAppPanel()">✕</button>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="form-grid" style="margin-bottom:10px">
|
|
280
|
+
<div>
|
|
281
|
+
<div class="panel-label">Name</div>
|
|
282
|
+
<input class="panel-input" id="app-name" placeholder="My App" style="width:100%" oninput="syncAppId()">
|
|
283
|
+
</div>
|
|
284
|
+
<div>
|
|
285
|
+
<div class="panel-label">ID <span style="font-size:9px;color:var(--text-muted)">(auto)</span></div>
|
|
286
|
+
<input class="panel-input" id="app-id" placeholder="my-app" style="width:100%">
|
|
287
|
+
</div>
|
|
288
|
+
<div class="form-grid-full">
|
|
289
|
+
<div class="panel-label">URL</div>
|
|
290
|
+
<input class="panel-input" id="app-url" placeholder="https://myapp.example.com" style="width:100%">
|
|
291
|
+
</div>
|
|
292
|
+
<div class="form-grid-full">
|
|
293
|
+
<div class="panel-label">Short description</div>
|
|
294
|
+
<input class="panel-input" id="app-desc" placeholder="What does this app do?" style="width:100%">
|
|
295
|
+
</div>
|
|
296
|
+
<div>
|
|
297
|
+
<div class="panel-label">Category</div>
|
|
298
|
+
<input class="panel-input" id="app-category" placeholder="Finance, Compliance…" style="width:100%">
|
|
299
|
+
</div>
|
|
300
|
+
<div>
|
|
301
|
+
<div class="panel-label">GitHub Repo (optional)</div>
|
|
302
|
+
<input class="panel-input" id="app-repo" placeholder="owner/repo" style="width:100%">
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
<div style="display:flex;gap:8px">
|
|
306
|
+
<button class="btn-sm btn-sm-green" onclick="doAddApp()">Register App</button>
|
|
307
|
+
<button class="btn-sm btn-sm-ghost" onclick="toggleAddAppPanel()">Cancel</button>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<!-- Assign Modal -->
|
|
312
|
+
<div class="modal-overlay" id="assign-modal">
|
|
313
|
+
<div class="modal">
|
|
314
|
+
<div class="modal-title" id="modal-title">Assign to agents</div>
|
|
315
|
+
<div class="modal-sub" id="modal-sub">Select agents (optional)</div>
|
|
316
|
+
<div class="agent-list" id="modal-agent-list"></div>
|
|
317
|
+
<div id="modal-missing-keys"></div>
|
|
318
|
+
<div class="modal-actions">
|
|
319
|
+
<button class="modal-btn modal-btn-primary" id="modal-assign-btn" onclick="doAssign()">Assign selected</button>
|
|
320
|
+
<button class="modal-btn modal-btn-secondary" onclick="closeModal()">Skip for now</button>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<script>
|
|
326
|
+
// ─── State ────────────────────────────────────────────────────────
|
|
327
|
+
let currentType = 'apps';
|
|
328
|
+
let allItems = [];
|
|
329
|
+
let activeCategory = 'all';
|
|
330
|
+
let showPlatformDefaultsOnly = false;
|
|
331
|
+
let modalItem = null;
|
|
332
|
+
let modalType = null;
|
|
333
|
+
let agentsList = [];
|
|
334
|
+
let promptTrigger = '!';
|
|
335
|
+
|
|
336
|
+
// ─── Theme ────────────────────────────────────────────────────────
|
|
337
|
+
(function(){
|
|
338
|
+
const s = localStorage.getItem('theme')||'dark';
|
|
339
|
+
document.documentElement.setAttribute('data-theme', s);
|
|
340
|
+
document.querySelector('.theme-toggle').textContent = s==='dark'?'☀':'☾';
|
|
341
|
+
})();
|
|
342
|
+
function toggleTheme(){
|
|
343
|
+
const c = document.documentElement.getAttribute('data-theme')||'dark';
|
|
344
|
+
const n = c==='dark'?'light':'dark';
|
|
345
|
+
document.documentElement.setAttribute('data-theme',n);
|
|
346
|
+
localStorage.setItem('theme',n);
|
|
347
|
+
document.querySelector('.theme-toggle').textContent = n==='dark'?'☀':'☾';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ─── Toast ────────────────────────────────────────────────────────
|
|
351
|
+
function toast(msg, type='info'){
|
|
352
|
+
const el = document.createElement('div');
|
|
353
|
+
el.className = `toast toast-${type}`;
|
|
354
|
+
el.textContent = msg;
|
|
355
|
+
document.getElementById('toast-container').appendChild(el);
|
|
356
|
+
setTimeout(()=>el.remove(), 3500);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ─── Escape ───────────────────────────────────────────────────────
|
|
360
|
+
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') }
|
|
361
|
+
|
|
362
|
+
// ─── Slug ─────────────────────────────────────────────────────────
|
|
363
|
+
function slugify(s){ return s.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'') }
|
|
364
|
+
|
|
365
|
+
// ─── Load agents ──────────────────────────────────────────────────
|
|
366
|
+
async function loadAgents(){
|
|
367
|
+
try{ const d = await (await fetch('/api/agents')).json(); agentsList = d.agents||[]; } catch{ agentsList=[]; }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ─── Prompt trigger config ────────────────────────────────────────
|
|
371
|
+
async function loadTrigger(){
|
|
372
|
+
try{ const d = await (await fetch('/api/marketplace/prompt-trigger')).json(); promptTrigger = d.trigger||'!'; } catch{ promptTrigger='!'; }
|
|
373
|
+
document.getElementById('trigger-display').textContent = promptTrigger;
|
|
374
|
+
}
|
|
375
|
+
function editTrigger(){
|
|
376
|
+
const c = prompt(`Set prompt trigger character (current: "${promptTrigger}"):`, promptTrigger);
|
|
377
|
+
if(!c || c.length !== 1){ if(c!==null) toast('Must be exactly 1 character','error'); return; }
|
|
378
|
+
fetch('/api/marketplace/prompt-trigger',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trigger:c})})
|
|
379
|
+
.then(r=>r.json()).then(d=>{ if(d.ok){ promptTrigger=c; document.getElementById('trigger-display').textContent=c; toast(`Trigger set to "${c}"`,'success'); } else toast(d.error||'Failed','error'); })
|
|
380
|
+
.catch(()=>toast('Failed to set trigger','error'));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── Type switch ──────────────────────────────────────────────────
|
|
384
|
+
function switchType(type){
|
|
385
|
+
currentType = type; activeCategory = 'all'; showPlatformDefaultsOnly = false;
|
|
386
|
+
document.querySelectorAll('.type-tab').forEach(t=>t.classList.toggle('active', t.dataset.type===type));
|
|
387
|
+
document.getElementById('search-input').value = '';
|
|
388
|
+
// Show trigger config only on Prompts tab
|
|
389
|
+
const tc = document.getElementById('trigger-config');
|
|
390
|
+
tc.style.display = type==='prompts' ? 'flex' : 'none';
|
|
391
|
+
loadItems();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ─── Load items ───────────────────────────────────────────────────
|
|
395
|
+
async function loadItems(){
|
|
396
|
+
document.getElementById('content').innerHTML = '<div style="padding:60px;text-align:center;color:var(--text-muted)">Loading…</div>';
|
|
397
|
+
try{
|
|
398
|
+
const d = await (await fetch(`/api/marketplace/${currentType}?source=platform`)).json();
|
|
399
|
+
allItems = d.items||[];
|
|
400
|
+
buildPills();
|
|
401
|
+
render();
|
|
402
|
+
} catch{
|
|
403
|
+
document.getElementById('content').innerHTML = '<div style="padding:60px;text-align:center;color:var(--text-muted)">Failed to load registry.</div>';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── Category pills ───────────────────────────────────────────────
|
|
408
|
+
function buildPills(){
|
|
409
|
+
const cats = [...new Set(allItems.map(i=>i.category).filter(Boolean))].sort();
|
|
410
|
+
const el = document.getElementById('filter-pills');
|
|
411
|
+
el.innerHTML = '';
|
|
412
|
+
const all = mkPill('all', true); el.appendChild(all);
|
|
413
|
+
cats.forEach(c=>el.appendChild(mkPill(c, false)));
|
|
414
|
+
// Platform Defaults pill (skills and MCPs only)
|
|
415
|
+
if(currentType !== 'agents' && currentType !== 'apps'){
|
|
416
|
+
const pd = document.createElement('button');
|
|
417
|
+
pd.id = 'pill-platform-defaults';
|
|
418
|
+
pd.className = 'pill pill-platform' + (showPlatformDefaultsOnly ? ' active' : '');
|
|
419
|
+
pd.textContent = '★ Platform Defaults';
|
|
420
|
+
pd.onclick = ()=>{
|
|
421
|
+
showPlatformDefaultsOnly = !showPlatformDefaultsOnly;
|
|
422
|
+
pd.classList.toggle('active', showPlatformDefaultsOnly);
|
|
423
|
+
render();
|
|
424
|
+
};
|
|
425
|
+
el.appendChild(pd);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function mkPill(cat, active){
|
|
429
|
+
const b = document.createElement('button');
|
|
430
|
+
b.className = 'pill'+(active?' active':''); b.textContent = cat;
|
|
431
|
+
b.onclick = ()=>{ activeCategory=cat; document.querySelectorAll('.pill').forEach(p=>p.classList.toggle('active', p.textContent===cat)); render(); };
|
|
432
|
+
return b;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ─── Main render ──────────────────────────────────────────────────
|
|
436
|
+
function render(){
|
|
437
|
+
const q = (document.getElementById('search-input').value||'').toLowerCase().trim();
|
|
438
|
+
let items = allItems;
|
|
439
|
+
if(activeCategory!=='all') items = items.filter(i=>i.category===activeCategory);
|
|
440
|
+
if(q) items = items.filter(i=>(i.name||'').toLowerCase().includes(q)||(i.description||'').toLowerCase().includes(q)||(i.tags||[]).some(t=>t.toLowerCase().includes(q)));
|
|
441
|
+
if(showPlatformDefaultsOnly) items = items.filter(i=>i.isPlatformDefault);
|
|
442
|
+
items = [...items].sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
443
|
+
|
|
444
|
+
if(currentType==='agents'){
|
|
445
|
+
renderFlat(items);
|
|
446
|
+
} else {
|
|
447
|
+
renderSections(items);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function renderFlat(items){
|
|
453
|
+
const el = document.getElementById('content');
|
|
454
|
+
if(!items.length){ el.innerHTML = emptyHtml('Nothing found'); return; }
|
|
455
|
+
el.innerHTML = `<div class="grid">${items.map(i=>cardHtml(i, currentType==='mcps'?'mcp':'agent')).join('')}</div>`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function renderSections(items){
|
|
459
|
+
const typeSingular = currentType==='mcps'?'mcp':currentType==='prompts'?'prompt':currentType==='apps'?'app':'skill';
|
|
460
|
+
// Marketplace: show everything except personal (those are in My Library)
|
|
461
|
+
const available = items.filter(i=>i.provider!=='me');
|
|
462
|
+
|
|
463
|
+
const actionBtn = '';
|
|
464
|
+
|
|
465
|
+
let html = '<div class="sections">';
|
|
466
|
+
html += `<div>
|
|
467
|
+
<div class="section-header">
|
|
468
|
+
<span class="section-title">All ${typeSingular.charAt(0).toUpperCase()+typeSingular.slice(1)}s</span>
|
|
469
|
+
<span class="section-count">${available.length}</span>
|
|
470
|
+
${actionBtn}
|
|
471
|
+
</div>
|
|
472
|
+
${available.length
|
|
473
|
+
? `<div class="grid">${available.map(i=>cardHtml(i,typeSingular)).join('')}</div>`
|
|
474
|
+
: `<div class="empty-state"><div class="empty-icon">◎</div><div class="empty-title">None available</div><div class="empty-sub">Check back later for new ${typeSingular}s.</div></div>`}
|
|
475
|
+
</div>`;
|
|
476
|
+
html += '</div>';
|
|
477
|
+
document.getElementById('content').innerHTML = html;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function section(title, items, typeSingular, count, actionBtn=''){
|
|
481
|
+
return `
|
|
482
|
+
<div>
|
|
483
|
+
<div class="section-header">
|
|
484
|
+
<span class="section-title">${esc(title)}</span>
|
|
485
|
+
<span class="section-count">${count}</span>
|
|
486
|
+
${actionBtn}
|
|
487
|
+
</div>
|
|
488
|
+
${items.length
|
|
489
|
+
? `<div class="grid">${items.map(i=>cardHtml(i,typeSingular)).join('')}</div>`
|
|
490
|
+
: `<div class="empty-state"><div class="empty-sub">None available.</div></div>`}
|
|
491
|
+
</div>`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function sectionPersonal(items, typeSingular){
|
|
495
|
+
const actionBtn = typeSingular==='skill'
|
|
496
|
+
? `<button class="btn-sm btn-sm-accent section-action" onclick="toggleScanPanel()">⊕ Scan my skills</button>`
|
|
497
|
+
: typeSingular==='prompt'
|
|
498
|
+
? `<button class="btn-sm btn-sm-accent section-action" onclick="toggleCreatePromptPanel()">⊕ Create prompt</button>`
|
|
499
|
+
: typeSingular==='app'
|
|
500
|
+
? `<button class="btn-sm btn-sm-accent section-action" onclick="toggleRegisterAppPanel()">⊕ Register App</button>`
|
|
501
|
+
: `<button class="btn-sm btn-sm-accent section-action" onclick="toggleAddMcpPanel()">⊕ Add my MCP</button>`;
|
|
502
|
+
|
|
503
|
+
const panelHtml = typeSingular==='skill' ? scanPanelHtml() : typeSingular==='prompt' ? createPromptPanelHtml() : typeSingular==='app' ? registerAppPanelHtml() : addMcpPanelHtml();
|
|
504
|
+
|
|
505
|
+
const emptyMsg = typeSingular==='skill'?'Scan your skills folder to import personal skills.'
|
|
506
|
+
:typeSingular==='prompt'?'Paste in a prompt template to save it.'
|
|
507
|
+
:typeSingular==='app'?'Register an app to track it here.'
|
|
508
|
+
:'Add a custom HTTP or stdio MCP you built.';
|
|
509
|
+
|
|
510
|
+
return `
|
|
511
|
+
<div>
|
|
512
|
+
<div class="section-header">
|
|
513
|
+
<span class="section-title">My ${typeSingular.charAt(0).toUpperCase()+typeSingular.slice(1)}s</span>
|
|
514
|
+
<span class="section-count">${items.length}</span>
|
|
515
|
+
${actionBtn}
|
|
516
|
+
</div>
|
|
517
|
+
${panelHtml}
|
|
518
|
+
${items.length
|
|
519
|
+
? `<div class="grid">${items.map(i=>cardHtml(i,typeSingular)).join('')}</div>`
|
|
520
|
+
: `<div class="empty-state"><div class="empty-icon">◎</div><div class="empty-title">None yet</div><div class="empty-sub">${emptyMsg}</div></div>`}
|
|
521
|
+
</div>`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ─── Scan panel (skills) ──────────────────────────────────────────
|
|
525
|
+
function scanPanelHtml(){
|
|
526
|
+
return `
|
|
527
|
+
<div class="panel hidden" id="scan-panel">
|
|
528
|
+
<div class="panel-label">Scan folder</div>
|
|
529
|
+
<div class="panel-row">
|
|
530
|
+
<input class="panel-input" id="scan-dir-input" placeholder="~/.claude/commands" value="">
|
|
531
|
+
<button class="btn-sm btn-sm-accent" onclick="doScan()">Scan</button>
|
|
532
|
+
</div>
|
|
533
|
+
<div class="panel-hint">Default: ~/.claude/commands — change to any folder containing .md skill files.</div>
|
|
534
|
+
<div id="scan-results" style="margin-top:10px"></div>
|
|
535
|
+
</div>`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function toggleScanPanel(){
|
|
539
|
+
const p = document.getElementById('scan-panel');
|
|
540
|
+
if(!p) return;
|
|
541
|
+
const open = !p.classList.contains('hidden');
|
|
542
|
+
p.classList.toggle('hidden', open);
|
|
543
|
+
if(!open && !document.getElementById('scan-dir-input').value){
|
|
544
|
+
document.getElementById('scan-dir-input').value = '~/.claude/commands';
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async function doScan(){
|
|
549
|
+
const dir = document.getElementById('scan-dir-input').value.trim() || '~/.claude/commands';
|
|
550
|
+
const el = document.getElementById('scan-results');
|
|
551
|
+
el.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">Scanning…</div>';
|
|
552
|
+
try{
|
|
553
|
+
const res = await fetch(`/api/marketplace/scan-skills?dir=${encodeURIComponent(dir)}`);
|
|
554
|
+
const data = await res.json();
|
|
555
|
+
if(!res.ok) throw new Error(data.error||'Scan failed');
|
|
556
|
+
if(!data.files.length){
|
|
557
|
+
el.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No new skills found — all .md files in this folder are already in the registry.</div>';
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
let html = `<div class="scan-results">`;
|
|
561
|
+
for(const f of data.files){
|
|
562
|
+
html += `<label class="scan-result-item">
|
|
563
|
+
<input type="checkbox" value="${esc(JSON.stringify(f))}" checked>
|
|
564
|
+
<span class="scan-result-name">${esc(f.name||f.id)}</span>
|
|
565
|
+
<span class="scan-result-desc">${esc(f.description||f.filename)}</span>
|
|
566
|
+
</label>`;
|
|
567
|
+
}
|
|
568
|
+
html += `</div>`;
|
|
569
|
+
html += `<button class="btn-sm btn-sm-green" id="import-btn" onclick="doImport(${esc(JSON.stringify(data.files))})">Import selected (${data.files.length})</button>`;
|
|
570
|
+
el.innerHTML = html;
|
|
571
|
+
// Update import btn count on checkbox change
|
|
572
|
+
el.querySelectorAll('input[type=checkbox]').forEach(cb=>{
|
|
573
|
+
cb.addEventListener('change', ()=>{
|
|
574
|
+
const n = el.querySelectorAll('input[type=checkbox]:checked').length;
|
|
575
|
+
const btn = document.getElementById('import-btn');
|
|
576
|
+
if(btn){ btn.textContent = `Import selected (${n})`; btn.disabled = n===0; }
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
} catch(e){ el.innerHTML = `<div style="font-size:11px;color:var(--red)">${esc(e.message)}</div>`; }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async function doImport(allFiles){
|
|
583
|
+
const el = document.getElementById('scan-results');
|
|
584
|
+
const checked = el.querySelectorAll('input[type=checkbox]:checked');
|
|
585
|
+
const files = [...checked].map(cb=>{ try{ return JSON.parse(cb.value); } catch{ return null; } }).filter(Boolean);
|
|
586
|
+
if(!files.length) return;
|
|
587
|
+
const btn = document.getElementById('import-btn');
|
|
588
|
+
if(btn){ btn.disabled=true; btn.textContent='Importing…'; }
|
|
589
|
+
try{
|
|
590
|
+
const res = await fetch('/api/marketplace/import-skills',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({files})});
|
|
591
|
+
const data = await res.json();
|
|
592
|
+
if(!res.ok) throw new Error(data.error||'Import failed');
|
|
593
|
+
toast(`${data.imported.length} skill${data.imported.length!==1?'s':''} imported`, 'success');
|
|
594
|
+
document.getElementById('scan-panel').classList.add('hidden');
|
|
595
|
+
await loadItems();
|
|
596
|
+
} catch(e){ toast(`Import failed: ${e.message}`,'error'); if(btn){btn.disabled=false;btn.textContent='Import selected';} }
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ─── Create prompt panel ──────────────────────────────────────────
|
|
600
|
+
function createPromptPanelHtml(){
|
|
601
|
+
return `
|
|
602
|
+
<div class="panel hidden" id="create-prompt-panel">
|
|
603
|
+
<div class="form-grid" style="margin-bottom:10px">
|
|
604
|
+
<div>
|
|
605
|
+
<div class="panel-label">Name</div>
|
|
606
|
+
<input class="panel-input" id="prompt-name" placeholder="Code Review" style="width:100%" oninput="syncPromptId()">
|
|
607
|
+
</div>
|
|
608
|
+
<div>
|
|
609
|
+
<div class="panel-label">ID <span style="font-size:9px;color:var(--text-muted)">(auto)</span></div>
|
|
610
|
+
<input class="panel-input" id="prompt-id" placeholder="code-review" style="width:100%">
|
|
611
|
+
</div>
|
|
612
|
+
<div class="form-grid-full">
|
|
613
|
+
<div class="panel-label">Description</div>
|
|
614
|
+
<input class="panel-input" id="prompt-desc" placeholder="What this prompt does…" style="width:100%">
|
|
615
|
+
</div>
|
|
616
|
+
<div class="form-grid-full">
|
|
617
|
+
<div class="panel-label">Prompt content <span style="font-size:9px;color:var(--text-muted)">(paste it here)</span></div>
|
|
618
|
+
<textarea class="panel-input" id="prompt-content" placeholder="You are a… / Act as… / etc." style="width:100%;height:120px;resize:vertical;font-family:var(--font-mono);font-size:11px;line-height:1.5"></textarea>
|
|
619
|
+
</div>
|
|
620
|
+
</div>
|
|
621
|
+
<div style="display:flex;gap:8px">
|
|
622
|
+
<button class="btn-sm btn-sm-green" onclick="doCreatePrompt()">Save prompt</button>
|
|
623
|
+
<button class="btn-sm btn-sm-ghost" onclick="toggleCreatePromptPanel()">Cancel</button>
|
|
624
|
+
</div>
|
|
625
|
+
</div>`;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function toggleCreatePromptPanel(){
|
|
629
|
+
const p = document.getElementById('create-prompt-panel');
|
|
630
|
+
if(p) p.classList.toggle('hidden');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function syncPromptId(){
|
|
634
|
+
document.getElementById('prompt-id').value = slugify(document.getElementById('prompt-name').value);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async function doCreatePrompt(){
|
|
638
|
+
const id = document.getElementById('prompt-id').value.trim();
|
|
639
|
+
const name = document.getElementById('prompt-name').value.trim();
|
|
640
|
+
const desc = document.getElementById('prompt-desc').value.trim();
|
|
641
|
+
const content = document.getElementById('prompt-content').value.trim();
|
|
642
|
+
if(!id||!name){ toast('Name and ID are required','error'); return; }
|
|
643
|
+
if(!content){ toast('Prompt content is required','error'); return; }
|
|
644
|
+
try{
|
|
645
|
+
const res = await fetch('/api/marketplace/create-prompt',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,name,description:desc,content})});
|
|
646
|
+
const data = await res.json();
|
|
647
|
+
if(!res.ok) throw new Error(data.error||'Failed to create prompt');
|
|
648
|
+
toast(`"${name}" saved`,'success');
|
|
649
|
+
document.getElementById('create-prompt-panel').classList.add('hidden');
|
|
650
|
+
document.getElementById('prompt-name').value='';
|
|
651
|
+
document.getElementById('prompt-id').value='';
|
|
652
|
+
document.getElementById('prompt-desc').value='';
|
|
653
|
+
document.getElementById('prompt-content').value='';
|
|
654
|
+
await loadItems();
|
|
655
|
+
} catch(e){ toast(`Error: ${e.message}`,'error'); }
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// ─── Add MCP panel ────────────────────────────────────────────────
|
|
659
|
+
let addMcpType = 'http';
|
|
660
|
+
|
|
661
|
+
function addMcpPanelHtml(){
|
|
662
|
+
return `
|
|
663
|
+
<div class="panel hidden" id="add-mcp-panel">
|
|
664
|
+
<div class="form-grid" style="margin-bottom:10px">
|
|
665
|
+
<div>
|
|
666
|
+
<div class="panel-label">Name</div>
|
|
667
|
+
<input class="panel-input" id="mcp-name" placeholder="My MCP" style="width:100%" oninput="syncMcpId()">
|
|
668
|
+
</div>
|
|
669
|
+
<div>
|
|
670
|
+
<div class="panel-label">ID <span style="font-size:9px;color:var(--text-muted)">(auto)</span></div>
|
|
671
|
+
<input class="panel-input" id="mcp-id" placeholder="my-mcp" style="width:100%">
|
|
672
|
+
</div>
|
|
673
|
+
<div class="form-grid-full">
|
|
674
|
+
<div class="panel-label">Description (optional)</div>
|
|
675
|
+
<input class="panel-input" id="mcp-desc" placeholder="What does this MCP do?" style="width:100%">
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="panel-label" style="margin-bottom:6px">Type</div>
|
|
679
|
+
<div class="type-toggle">
|
|
680
|
+
<button class="type-toggle-btn active" id="mcp-type-http" onclick="setMcpType('http')">HTTP</button>
|
|
681
|
+
<button class="type-toggle-btn" id="mcp-type-stdio" onclick="setMcpType('stdio')">stdio</button>
|
|
682
|
+
</div>
|
|
683
|
+
<div id="mcp-http-fields">
|
|
684
|
+
<div class="panel-label">URL</div>
|
|
685
|
+
<input class="panel-input" id="mcp-url" placeholder="https://mymcp.example.com/mcp" style="width:100%;margin-bottom:10px">
|
|
686
|
+
</div>
|
|
687
|
+
<div id="mcp-stdio-fields" style="display:none">
|
|
688
|
+
<div class="form-grid" style="margin-bottom:10px">
|
|
689
|
+
<div>
|
|
690
|
+
<div class="panel-label">Command</div>
|
|
691
|
+
<input class="panel-input" id="mcp-command" placeholder="node" style="width:100%">
|
|
692
|
+
</div>
|
|
693
|
+
<div>
|
|
694
|
+
<div class="panel-label">Args (space-separated)</div>
|
|
695
|
+
<input class="panel-input" id="mcp-args" placeholder="./server.js" style="width:100%">
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
</div>
|
|
699
|
+
<div style="display:flex;gap:8px">
|
|
700
|
+
<button class="btn-sm btn-sm-green" onclick="doAddMcp()">Add MCP</button>
|
|
701
|
+
<button class="btn-sm btn-sm-ghost" onclick="toggleAddMcpPanel()">Cancel</button>
|
|
702
|
+
</div>
|
|
703
|
+
</div>`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function toggleAddMcpPanel(){
|
|
707
|
+
const p = document.getElementById('add-mcp-panel');
|
|
708
|
+
if(p) p.classList.toggle('hidden');
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function setMcpType(type){
|
|
712
|
+
addMcpType = type;
|
|
713
|
+
document.getElementById('mcp-type-http').classList.toggle('active', type==='http');
|
|
714
|
+
document.getElementById('mcp-type-stdio').classList.toggle('active', type==='stdio');
|
|
715
|
+
document.getElementById('mcp-http-fields').style.display = type==='http'?'':'none';
|
|
716
|
+
document.getElementById('mcp-stdio-fields').style.display = type==='stdio'?'':'none';
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function syncMcpId(){
|
|
720
|
+
const name = document.getElementById('mcp-name').value;
|
|
721
|
+
document.getElementById('mcp-id').value = slugify(name);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
async function doAddMcp(){
|
|
725
|
+
const id = document.getElementById('mcp-id').value.trim();
|
|
726
|
+
const name = document.getElementById('mcp-name').value.trim();
|
|
727
|
+
const desc = document.getElementById('mcp-desc').value.trim();
|
|
728
|
+
if(!id||!name){ toast('Name and ID are required','error'); return; }
|
|
729
|
+
const body = { id, name, description: desc, mcpType: addMcpType };
|
|
730
|
+
if(addMcpType==='http'){
|
|
731
|
+
const url = document.getElementById('mcp-url').value.trim();
|
|
732
|
+
if(!url){ toast('URL is required for HTTP MCP','error'); return; }
|
|
733
|
+
Object.assign(body, { url });
|
|
734
|
+
} else {
|
|
735
|
+
const command = document.getElementById('mcp-command').value.trim();
|
|
736
|
+
const argsRaw = document.getElementById('mcp-args').value.trim();
|
|
737
|
+
if(!command){ toast('Command is required for stdio MCP','error'); return; }
|
|
738
|
+
Object.assign(body, { command, args: argsRaw ? argsRaw.split(/\s+/) : [] });
|
|
739
|
+
}
|
|
740
|
+
try{
|
|
741
|
+
const res = await fetch('/api/marketplace/add-mcp',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
742
|
+
const data = await res.json();
|
|
743
|
+
if(!res.ok) throw new Error(data.error||'Failed to add MCP');
|
|
744
|
+
toast(`${name} added`, 'success');
|
|
745
|
+
document.getElementById('add-mcp-panel').classList.add('hidden');
|
|
746
|
+
await loadItems();
|
|
747
|
+
} catch(e){ toast(`Error: ${e.message}`,'error'); }
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ─── Card ─────────────────────────────────────────────────────────
|
|
751
|
+
function cardHtml(item, typeSingular){
|
|
752
|
+
if(typeSingular === 'app') return appCardHtml(item);
|
|
753
|
+
|
|
754
|
+
const providerBadge = (item.provider==='AgenticLedger' || item.provider==='platform')
|
|
755
|
+
? `<span class="badge badge-verified">✓ verified</span>`
|
|
756
|
+
: item.provider==='me'
|
|
757
|
+
? `<span class="badge badge-personal">built by me</span>`
|
|
758
|
+
: `<span class="badge badge-external">community</span>`;
|
|
759
|
+
const installedBadge = item.installed ? `<span class="badge badge-installed">✓ installed</span>` : '';
|
|
760
|
+
const platformBadge = item.isPlatformDefault ? `<span class="badge badge-platform">★ default</span>` : '';
|
|
761
|
+
const assignedLabel = (item.assignedTo?.length)
|
|
762
|
+
? `<span class="assigned-label">→ ${item.assignedTo.slice(0,3).join(', ')}${item.assignedTo.length>3?` +${item.assignedTo.length-3}`:''}</span>`
|
|
763
|
+
: '';
|
|
764
|
+
const tagsHtml = (item.tags||[]).map(t=>`<span class="tag">${esc(t)}</span>`).join('');
|
|
765
|
+
|
|
766
|
+
let actionBtn;
|
|
767
|
+
if(item.installed){
|
|
768
|
+
actionBtn = `<button class="btn btn-manage" disabled style="opacity:.6;cursor:default">✓ Installed</button>`;
|
|
769
|
+
} else {
|
|
770
|
+
actionBtn = `<button class="btn btn-install" id="install-btn-${esc(item.id)}" onclick='installItem(${JSON.stringify(item)}, "${typeSingular}")'>+ Install</button>`;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Platform default toggle (skills and MCPs only)
|
|
774
|
+
const defaultToggle = typeSingular !== 'agent'
|
|
775
|
+
? `<button class="${item.isPlatformDefault ? 'btn-default-on' : 'btn-default-off'}" id="pd-btn-${esc(item.id)}" onclick='togglePlatformDefault(${JSON.stringify(item)}, "${typeSingular}")'>${item.isPlatformDefault ? '★ Default' : '☆ Set Default'}</button>`
|
|
776
|
+
: '';
|
|
777
|
+
|
|
778
|
+
return `<div class="card${item.installed?' installed':''}${item.isPlatformDefault?' card-platform-default':''}" id="card-${esc(item.id)}">
|
|
779
|
+
<div class="card-top">
|
|
780
|
+
<div><div class="card-name">${esc(item.name)}</div><div class="card-provider">${esc(item.provider||'')}</div></div>
|
|
781
|
+
<div class="card-badges">${providerBadge}${installedBadge}${platformBadge}</div>
|
|
782
|
+
</div>
|
|
783
|
+
<div class="card-desc">${esc(item.description||'')}</div>
|
|
784
|
+
<div class="card-meta">${tagsHtml}${assignedLabel}</div>
|
|
785
|
+
<div class="card-actions">${actionBtn}${defaultToggle}</div>
|
|
786
|
+
</div>`;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ─── App Card (rich tile design) ──────────────────────────────────
|
|
790
|
+
function appCardHtml(item){
|
|
791
|
+
const initials = (item.name||'?').slice(0,2).toUpperCase();
|
|
792
|
+
const statusClass = 'status-'+(item.status||'draft');
|
|
793
|
+
const statusLabel = (item.status||'draft').charAt(0).toUpperCase()+(item.status||'draft').slice(1);
|
|
794
|
+
|
|
795
|
+
const tags = (item.tags||[]).slice(0,3).map(t=>`<span class="app-tag">${esc(t)}</span>`).join('');
|
|
796
|
+
|
|
797
|
+
const agentDev = item.agentDeveloper
|
|
798
|
+
? `<a class="agent-dev-badge" href="/ui#${esc(item.agentDeveloper)}" title="Open in Chat">\uD83E\uDD16 ${esc(item.agentAlias||item.agentDeveloper)}</a>`
|
|
799
|
+
: '';
|
|
800
|
+
|
|
801
|
+
const deployBadge = item.deployPlatform
|
|
802
|
+
? `<span class="deploy-badge">${esc(deployLabel(item.deployPlatform))}</span>`
|
|
803
|
+
: '';
|
|
804
|
+
|
|
805
|
+
const categoryTag = item.category
|
|
806
|
+
? `<span class="app-tag">${esc(item.category)}</span>`
|
|
807
|
+
: '';
|
|
808
|
+
|
|
809
|
+
const githubBtn = item.githubRepo
|
|
810
|
+
? `<a class="btn-icon" href="${esc(item.githubRepo.startsWith('http')?item.githubRepo:'https://github.com/'+item.githubRepo)}" target="_blank" rel="noopener" title="GitHub">⌂</a>`
|
|
811
|
+
: '';
|
|
812
|
+
|
|
813
|
+
const healthDot = item.healthStatus==='healthy' ? 'healthy' : item.healthStatus==='down' ? 'down' : '';
|
|
814
|
+
const healthLabel = item.lastHealthCheck
|
|
815
|
+
? (item.healthStatus==='healthy' ? 'Healthy' : item.healthStatus==='down' ? 'Down' : 'Check')
|
|
816
|
+
: 'Check';
|
|
817
|
+
|
|
818
|
+
return `<div class="app-card" id="card-${esc(item.id)}">
|
|
819
|
+
<div class="app-card-top">
|
|
820
|
+
<div class="app-icon">${initials}</div>
|
|
821
|
+
<div style="flex:1;min-width:0">
|
|
822
|
+
<div class="app-name">${esc(item.name)}</div>
|
|
823
|
+
<div class="app-url">${esc(item.url||'')}</div>
|
|
824
|
+
</div>
|
|
825
|
+
<div class="app-badges">
|
|
826
|
+
<span class="status-badge ${statusClass}">${statusLabel}</span>
|
|
827
|
+
${deployBadge}
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
${item.shortDescription||item.description ? `<div class="app-desc">${esc(item.shortDescription||item.description)}</div>` : ''}
|
|
831
|
+
<div class="app-meta">
|
|
832
|
+
${categoryTag}${tags}${agentDev}
|
|
833
|
+
</div>
|
|
834
|
+
<div class="app-actions">
|
|
835
|
+
${item.url ? `<a class="btn-launch" href="${esc(item.url)}" target="_blank" rel="noopener">↗ Launch</a>` : ''}
|
|
836
|
+
${githubBtn}
|
|
837
|
+
<button class="btn-icon danger" onclick="deleteAppFromRegistry('${esc(item.id)}')" title="Delete">✕</button>
|
|
838
|
+
<button class="health-chip" onclick="checkAppHealth('${esc(item.id)}','${esc(item.url||'')}')" title="Ping app URL">
|
|
839
|
+
<span class="health-dot ${healthDot}" id="hdot-${esc(item.id)}"></span>
|
|
840
|
+
<span id="hlabel-${esc(item.id)}">${healthLabel}</span>
|
|
841
|
+
</button>
|
|
842
|
+
</div>
|
|
843
|
+
</div>`;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function deployLabel(p){
|
|
847
|
+
return {railway:'Railway',vercel:'Vercel',netlify:'Netlify',render:'Render',local:'Local',other:'Other'}[p]||p;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ─── Register App panel (inline in My Apps section) ───────────────
|
|
851
|
+
function registerAppPanelHtml(){
|
|
852
|
+
return `
|
|
853
|
+
<div class="panel hidden" id="register-app-panel">
|
|
854
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
|
855
|
+
<span class="panel-label" style="margin:0">Register App</span>
|
|
856
|
+
<button class="btn-sm btn-sm-ghost" style="padding:3px 8px;font-size:10px" onclick="toggleRegisterAppPanel()">✕</button>
|
|
857
|
+
</div>
|
|
858
|
+
<div class="form-grid" style="margin-bottom:10px">
|
|
859
|
+
<div>
|
|
860
|
+
<div class="panel-label">Name *</div>
|
|
861
|
+
<input class="panel-input" id="reg-app-name" placeholder="My App" style="width:100%" oninput="syncRegAppId()">
|
|
862
|
+
</div>
|
|
863
|
+
<div>
|
|
864
|
+
<div class="panel-label">URL *</div>
|
|
865
|
+
<input class="panel-input" id="reg-app-url" placeholder="https://myapp.example.com" style="width:100%">
|
|
866
|
+
</div>
|
|
867
|
+
<div class="form-grid-full">
|
|
868
|
+
<div class="panel-label">Short description</div>
|
|
869
|
+
<input class="panel-input" id="reg-app-desc" placeholder="One-liner about this app" style="width:100%">
|
|
870
|
+
</div>
|
|
871
|
+
<div>
|
|
872
|
+
<div class="panel-label">Category</div>
|
|
873
|
+
<select class="panel-input" id="reg-app-category" style="width:100%;cursor:pointer">
|
|
874
|
+
<option value="">— none —</option>
|
|
875
|
+
<option value="productivity">Productivity</option>
|
|
876
|
+
<option value="development">Development</option>
|
|
877
|
+
<option value="finance">Finance</option>
|
|
878
|
+
<option value="analytics">Analytics</option>
|
|
879
|
+
<option value="communication">Communication</option>
|
|
880
|
+
<option value="ai">AI / Agents</option>
|
|
881
|
+
<option value="other">Other</option>
|
|
882
|
+
</select>
|
|
883
|
+
</div>
|
|
884
|
+
<div>
|
|
885
|
+
<div class="panel-label">Status</div>
|
|
886
|
+
<select class="panel-input" id="reg-app-status" style="width:100%;cursor:pointer">
|
|
887
|
+
<option value="draft">Draft</option>
|
|
888
|
+
<option value="live">Live</option>
|
|
889
|
+
<option value="maintenance">Maintenance</option>
|
|
890
|
+
</select>
|
|
891
|
+
</div>
|
|
892
|
+
<div>
|
|
893
|
+
<div class="panel-label">GitHub Repo (optional)</div>
|
|
894
|
+
<input class="panel-input" id="reg-app-repo" placeholder="https://github.com/you/repo" style="width:100%">
|
|
895
|
+
</div>
|
|
896
|
+
<div>
|
|
897
|
+
<div class="panel-label">Deploy Platform</div>
|
|
898
|
+
<select class="panel-input" id="reg-app-deploy" style="width:100%;cursor:pointer">
|
|
899
|
+
<option value="">— unknown —</option>
|
|
900
|
+
<option value="railway">Railway</option>
|
|
901
|
+
<option value="vercel">Vercel</option>
|
|
902
|
+
<option value="netlify">Netlify</option>
|
|
903
|
+
<option value="render">Render</option>
|
|
904
|
+
<option value="local">Local</option>
|
|
905
|
+
<option value="other">Other</option>
|
|
906
|
+
</select>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
<div style="display:flex;gap:8px">
|
|
910
|
+
<button class="btn-sm btn-sm-green" onclick="doRegisterApp()">Register App</button>
|
|
911
|
+
<button class="btn-sm btn-sm-ghost" onclick="toggleRegisterAppPanel()">Cancel</button>
|
|
912
|
+
</div>
|
|
913
|
+
</div>`;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function toggleRegisterAppPanel(){
|
|
917
|
+
const p = document.getElementById('register-app-panel');
|
|
918
|
+
if(p) p.classList.toggle('hidden');
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function syncRegAppId(){
|
|
922
|
+
// ID auto-generated server-side from name
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
async function doRegisterApp(){
|
|
926
|
+
const name = document.getElementById('reg-app-name').value.trim();
|
|
927
|
+
const url = document.getElementById('reg-app-url').value.trim();
|
|
928
|
+
const desc = document.getElementById('reg-app-desc').value.trim();
|
|
929
|
+
const category = document.getElementById('reg-app-category').value;
|
|
930
|
+
const status = document.getElementById('reg-app-status').value;
|
|
931
|
+
const repo = document.getElementById('reg-app-repo').value.trim();
|
|
932
|
+
const deploy = document.getElementById('reg-app-deploy').value;
|
|
933
|
+
if(!name){ toast('App name is required','error'); return; }
|
|
934
|
+
if(!url){ toast('App URL is required','error'); return; }
|
|
935
|
+
const body = { name, url, shortDescription: desc, category, status: status||'draft',
|
|
936
|
+
githubRepo: repo, deployPlatform: deploy||null, provider: 'me', tags: [] };
|
|
937
|
+
try{
|
|
938
|
+
const res = await fetch('/api/apps',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
939
|
+
const data = await res.json();
|
|
940
|
+
if(!res.ok) throw new Error(data.error||'Failed to register app');
|
|
941
|
+
toast(`"${name}" registered`,'success');
|
|
942
|
+
document.getElementById('register-app-panel').classList.add('hidden');
|
|
943
|
+
['reg-app-name','reg-app-url','reg-app-desc','reg-app-repo'].forEach(id=>{ const el=document.getElementById(id); if(el) el.value=''; });
|
|
944
|
+
document.getElementById('reg-app-category').value='';
|
|
945
|
+
document.getElementById('reg-app-status').value='draft';
|
|
946
|
+
document.getElementById('reg-app-deploy').value='';
|
|
947
|
+
await loadItems();
|
|
948
|
+
} catch(e){ toast(`Error: ${e.message}`,'error'); }
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async function deleteAppFromRegistry(id){
|
|
952
|
+
if(!confirm('Delete this app? This cannot be undone.')) return;
|
|
953
|
+
try{
|
|
954
|
+
const res = await fetch(`/api/apps/${encodeURIComponent(id)}`,{method:'DELETE'});
|
|
955
|
+
if(!res.ok){ const d = await res.json(); throw new Error(d.error||'Delete failed'); }
|
|
956
|
+
toast('App deleted','success');
|
|
957
|
+
await loadItems();
|
|
958
|
+
} catch(e){ toast(`Error: ${e.message}`,'error'); }
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
async function checkAppHealth(id, url){
|
|
962
|
+
if(!url) return;
|
|
963
|
+
const dot = document.getElementById('hdot-'+id);
|
|
964
|
+
const label = document.getElementById('hlabel-'+id);
|
|
965
|
+
if(!dot||!label) return;
|
|
966
|
+
label.textContent = '…';
|
|
967
|
+
dot.className = 'health-dot';
|
|
968
|
+
try{
|
|
969
|
+
const res = await fetch(`/api/apps/${encodeURIComponent(id)}/health`,{method:'POST'});
|
|
970
|
+
const data = await res.json();
|
|
971
|
+
if(data.healthy){ dot.className='health-dot healthy'; label.textContent='Healthy'; }
|
|
972
|
+
else { dot.className='health-dot down'; label.textContent='Down'; }
|
|
973
|
+
} catch{ dot.className='health-dot down'; label.textContent='Error'; }
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// ─── Platform default toggle ──────────────────────────────────────
|
|
977
|
+
async function togglePlatformDefault(item, typeSingular){
|
|
978
|
+
const newEnabled = !item.isPlatformDefault;
|
|
979
|
+
const btn = document.getElementById(`pd-btn-${item.id}`);
|
|
980
|
+
if(btn){ btn.textContent = 'Saving…'; btn.disabled = true; }
|
|
981
|
+
try{
|
|
982
|
+
const res = await fetch('/api/marketplace/platform-default',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:typeSingular,id:item.id,enabled:newEnabled})});
|
|
983
|
+
const data = await res.json();
|
|
984
|
+
if(!res.ok) throw new Error(data.error||'Failed');
|
|
985
|
+
toast(`${item.name} ${newEnabled ? 'set as platform default' : 'removed from platform defaults'}`, 'success');
|
|
986
|
+
await loadItems();
|
|
987
|
+
} catch(e){ toast(`Error: ${e.message}`,'error'); await loadItems(); }
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function emptyHtml(msg){ return `<div class="empty-state"><div class="empty-icon">◎</div><div class="empty-title">${esc(msg)}</div></div>`; }
|
|
991
|
+
|
|
992
|
+
// ─── Install ──────────────────────────────────────────────────────
|
|
993
|
+
async function installItem(item, typeSingular){
|
|
994
|
+
const btn = document.getElementById(`install-btn-${item.id}`);
|
|
995
|
+
if(btn){ btn.textContent='Installing…'; btn.className='btn btn-installing'; }
|
|
996
|
+
try{
|
|
997
|
+
const res = await fetch('/api/marketplace/install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:typeSingular,id:item.id})});
|
|
998
|
+
const data = await res.json();
|
|
999
|
+
if(!res.ok) throw new Error(data.error||'Install failed');
|
|
1000
|
+
toast(`${item.name} installed`,'success');
|
|
1001
|
+
await loadItems();
|
|
1002
|
+
if(typeSingular!=='agent') openAssignModal({...item,installed:true}, typeSingular, true, data.requiresKeys);
|
|
1003
|
+
} catch(e){ toast(`Install failed: ${e.message}`,'error'); await loadItems(); }
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// ─── Assign modal ─────────────────────────────────────────────────
|
|
1007
|
+
function openAssignModal(item, typeSingular, justInstalled, requiresKeys){
|
|
1008
|
+
modalItem=item; modalType=typeSingular;
|
|
1009
|
+
document.getElementById('modal-title').textContent = justInstalled ? `✓ ${item.name} installed` : `Manage — ${item.name}`;
|
|
1010
|
+
document.getElementById('modal-sub').textContent = justInstalled ? 'Assign to agents now? (optional)' : 'Update agent assignments';
|
|
1011
|
+
const listEl = document.getElementById('modal-agent-list');
|
|
1012
|
+
listEl.innerHTML = agentsList.length
|
|
1013
|
+
? agentsList.map(a=>`<label class="agent-check-item"><input type="checkbox" value="${esc(a.id)}" ${(item.assignedTo||[]).includes(a.id)?'checked':''}><span class="agent-check-name">${esc(a.name||a.id)}</span><span class="agent-check-id">${esc(a.id)}</span></label>`).join('')
|
|
1014
|
+
: '<div style="font-size:12px;color:var(--text-muted);text-align:center;padding:12px">No agents configured</div>';
|
|
1015
|
+
document.getElementById('modal-missing-keys').innerHTML='';
|
|
1016
|
+
document.getElementById('modal-assign-btn').disabled=false;
|
|
1017
|
+
document.getElementById('modal-assign-btn').textContent='Assign selected';
|
|
1018
|
+
document.getElementById('assign-modal').classList.add('open');
|
|
1019
|
+
}
|
|
1020
|
+
function closeModal(){ document.getElementById('assign-modal').classList.remove('open'); modalItem=null; modalType=null; }
|
|
1021
|
+
document.getElementById('assign-modal').addEventListener('click', function(e){ if(e.target===this) closeModal(); });
|
|
1022
|
+
|
|
1023
|
+
async function doAssign(){
|
|
1024
|
+
if(!modalItem||!modalType) return;
|
|
1025
|
+
const agentIds = [...document.querySelectorAll('#modal-agent-list input:checked')].map(c=>c.value);
|
|
1026
|
+
if(!agentIds.length){ closeModal(); return; }
|
|
1027
|
+
document.getElementById('modal-assign-btn').disabled=true;
|
|
1028
|
+
document.getElementById('modal-assign-btn').textContent='Assigning…';
|
|
1029
|
+
try{
|
|
1030
|
+
const res = await fetch('/api/marketplace/assign',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:modalType,id:modalItem.id,agentIds})});
|
|
1031
|
+
const data = await res.json();
|
|
1032
|
+
if(!res.ok) throw new Error(data.error||'Assign failed');
|
|
1033
|
+
if(data.missingKeys?.length){
|
|
1034
|
+
document.getElementById('modal-missing-keys').innerHTML=`<div class="missing-keys-alert">⚠ API key needed for: ${data.missingKeys.map(a=>`<strong>${a}</strong>`).join(', ')} — add in Org to activate.</div>`;
|
|
1035
|
+
document.getElementById('modal-assign-btn').textContent='Assigned';
|
|
1036
|
+
} else {
|
|
1037
|
+
toast(`Assigned to ${agentIds.length} agent${agentIds.length!==1?'s':''}`,'success');
|
|
1038
|
+
closeModal();
|
|
1039
|
+
}
|
|
1040
|
+
await loadItems();
|
|
1041
|
+
} catch(e){ toast(`Assign failed: ${e.message}`,'error'); document.getElementById('modal-assign-btn').disabled=false; document.getElementById('modal-assign-btn').textContent='Assign selected'; }
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// ─── Add App (External / Platform) ───────────────────────────────
|
|
1045
|
+
let addAppProvider = 'me';
|
|
1046
|
+
|
|
1047
|
+
function openAddAppPanel(provider){
|
|
1048
|
+
addAppProvider = provider;
|
|
1049
|
+
const panel = document.getElementById('add-app-panel');
|
|
1050
|
+
if(!panel) return;
|
|
1051
|
+
const label = (provider==='AgenticLedger' || provider==='platform') ? 'Platform' : provider==='me' ? 'My App' : 'External Source';
|
|
1052
|
+
document.getElementById('add-app-provider-label').textContent = label;
|
|
1053
|
+
panel.classList.remove('hidden');
|
|
1054
|
+
panel.scrollIntoView({behavior:'smooth', block:'nearest'});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function toggleAddAppPanel(){
|
|
1058
|
+
document.getElementById('add-app-panel')?.classList.toggle('hidden');
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function syncAppId(){
|
|
1062
|
+
const name = document.getElementById('app-name').value;
|
|
1063
|
+
document.getElementById('app-id').value = slugify(name);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async function doAddApp(){
|
|
1067
|
+
const id = document.getElementById('app-id').value.trim();
|
|
1068
|
+
const name = document.getElementById('app-name').value.trim();
|
|
1069
|
+
const url = document.getElementById('app-url').value.trim();
|
|
1070
|
+
const desc = document.getElementById('app-desc').value.trim();
|
|
1071
|
+
const category = document.getElementById('app-category').value.trim();
|
|
1072
|
+
const repo = document.getElementById('app-repo').value.trim();
|
|
1073
|
+
if(!id||!name){ toast('Name and ID are required','error'); return; }
|
|
1074
|
+
const body = { id, name, url, shortDescription: desc, category,
|
|
1075
|
+
githubRepo: repo, provider: addAppProvider,
|
|
1076
|
+
status: 'live', tags: [], deployPlatform: 'other' };
|
|
1077
|
+
try{
|
|
1078
|
+
const res = await fetch('/api/apps',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
1079
|
+
const data = await res.json();
|
|
1080
|
+
if(!res.ok) throw new Error(data.error||'Failed to register app');
|
|
1081
|
+
toast(`"${name}" registered`,'success');
|
|
1082
|
+
document.getElementById('add-app-panel').classList.add('hidden');
|
|
1083
|
+
['app-name','app-id','app-url','app-desc','app-category','app-repo'].forEach(id=>{ const el=document.getElementById(id); if(el) el.value=''; });
|
|
1084
|
+
await loadItems();
|
|
1085
|
+
} catch(e){ toast(`Error: ${e.message}`,'error'); }
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// ─── Init ─────────────────────────────────────────────────────────
|
|
1089
|
+
const _typeParam = new URLSearchParams(window.location.search).get('type');
|
|
1090
|
+
if(_typeParam) switchType(_typeParam);
|
|
1091
|
+
loadAgents();
|
|
1092
|
+
loadTrigger();
|
|
1093
|
+
loadItems();
|
|
1094
|
+
document.addEventListener('click', function(e){
|
|
1095
|
+
const dd = document.getElementById('docsDropdown');
|
|
1096
|
+
if(dd && !dd.contains(e.target)) document.getElementById('docsMenu')?.classList.remove('open');
|
|
1097
|
+
});
|
|
1098
|
+
</script>
|
|
1099
|
+
<script>(function(){fetch("/api/config/service").then(function(r){return r.json()}).then(function(d){if(d.gymEnabled)document.querySelectorAll(".gym-tab-link").forEach(function(el){el.style.display=""});if(d.gymOnlyMode){var keep=["/gym","/org","/admin"];document.querySelectorAll(".tab-group .tab-btn").forEach(function(el){var h=el.getAttribute("href");if(h&&keep.indexOf(h)===-1&&!el.classList.contains("gym-tab-link"))el.style.display="none"});document.querySelectorAll(".topbar-right .gear-btn").forEach(function(el){var h=el.getAttribute("href");if(h&&["/marketplace","/monitor","/user-guide"].indexOf(h)!==-1)el.style.display="none"})}}).catch(function(){})})()</script>
|
|
1100
|
+
</body>
|
|
1101
|
+
</html>
|