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,1281 @@
|
|
|
1
|
+
# Marketplace Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Build a `/marketplace` page where users can browse, install, and assign MCPs, skills, and agent templates to their agents.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Registry JSON files in `/registry/` define all available items. Three backend endpoints handle listing (with installed/assigned status), installing (writing to config.mcps or copying skill files), and assigning (writing to agents[id].mcps/skills). A standalone `public/marketplace.html` page renders cards with search/filter and a post-install multi-agent assign modal.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript/Express (backend), vanilla JS HTML page matching existing design system (DM Sans + IBM Plex Mono + Syne fonts, CSS variables from org.html), Node.js `fs` for registry reads/writes.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-03-25-marketplace-design.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## File Map
|
|
16
|
+
|
|
17
|
+
| File | Action | Purpose |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `scripts/seed-registry.ts` | Create | One-time seed: migrate mcp-catalog.json, scan skills, fetch external |
|
|
20
|
+
| `registry/mcps.json` | Create (generated) | All MCP entries |
|
|
21
|
+
| `registry/skills.json` | Create (generated) | All skill entries |
|
|
22
|
+
| `registry/agents.json` | Create | Agent template catalog (minimal) |
|
|
23
|
+
| `registry/skills/platform/*.md` | Create (generated) | Skill files copied from ~/.claude/commands/ |
|
|
24
|
+
| `registry/skills/external/*.md` | Create (fetched) | affaan-m skills downloaded |
|
|
25
|
+
| `registry/installed-drafts.json` | Create | Draft agent tracking (not config.json) |
|
|
26
|
+
| `src/web-ui.ts` | Modify | Add 3 marketplace API endpoints + serve /marketplace route |
|
|
27
|
+
| `public/marketplace.html` | Create | Marketplace UI page |
|
|
28
|
+
| `public/org.html` | Modify | Add Marketplace nav link |
|
|
29
|
+
| `public/activity.html` | Modify | Add Marketplace nav link |
|
|
30
|
+
| `public/home.html` | Modify | Add Marketplace nav link |
|
|
31
|
+
| `public/index.html` | Modify | Add Marketplace nav link |
|
|
32
|
+
| `Comprehensive Test Suite/web-ui/api.test.ts` | Modify | Add marketplace API tests |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Task 1: Seed Script — Migrate MCPs + Generate Skills Registry
|
|
37
|
+
|
|
38
|
+
**Files:**
|
|
39
|
+
- Create: `scripts/seed-registry.ts`
|
|
40
|
+
- Create: `registry/mcps.json` (output)
|
|
41
|
+
- Create: `registry/skills.json` (output)
|
|
42
|
+
- Create: `registry/agents.json` (output)
|
|
43
|
+
- Create: `registry/skills/platform/*.md` (output)
|
|
44
|
+
- Create: `registry/skills/external/*.md` (output)
|
|
45
|
+
|
|
46
|
+
- [ ] **Step 1: Create registry folder structure**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
mkdir -p ~/Desktop/APPs/channelToAgentToClaude/registry/skills/platform
|
|
50
|
+
mkdir -p ~/Desktop/APPs/channelToAgentToClaude/registry/skills/external
|
|
51
|
+
mkdir -p ~/Desktop/APPs/channelToAgentToClaude/registry/agents/platform
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- [ ] **Step 2: Create the seed script**
|
|
55
|
+
|
|
56
|
+
Create `scripts/seed-registry.ts`:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
#!/usr/bin/env npx ts-node
|
|
60
|
+
/**
|
|
61
|
+
* scripts/seed-registry.ts
|
|
62
|
+
* One-time seed: generates registry/mcps.json, registry/skills.json, registry/agents.json
|
|
63
|
+
* and copies/fetches all source files into registry/skills/
|
|
64
|
+
*
|
|
65
|
+
* Run: npx ts-node scripts/seed-registry.ts
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, copyFileSync, mkdirSync } from "node:fs";
|
|
69
|
+
import { join, basename } from "node:path";
|
|
70
|
+
import { homedir } from "node:os";
|
|
71
|
+
import { execSync } from "node:child_process";
|
|
72
|
+
|
|
73
|
+
const BASE = join(import.meta.dirname ?? __dirname, "..");
|
|
74
|
+
const COMMANDS_DIR = join(homedir(), ".claude", "commands");
|
|
75
|
+
|
|
76
|
+
// ── 1. Migrate mcp-catalog.json → registry/mcps.json ──────────────
|
|
77
|
+
|
|
78
|
+
const catalog = JSON.parse(readFileSync(join(BASE, "mcp-catalog.json"), "utf-8"));
|
|
79
|
+
|
|
80
|
+
const mcpEntries = Object.entries(catalog.mcps as Record<string, any>).map(([id, mcp]) => ({
|
|
81
|
+
id,
|
|
82
|
+
name: mcp.name,
|
|
83
|
+
provider: "AgenticLedger",
|
|
84
|
+
description: mcp.description,
|
|
85
|
+
category: mcp.category,
|
|
86
|
+
verified: true,
|
|
87
|
+
source: "agenticledger/platform",
|
|
88
|
+
tags: [mcp.category],
|
|
89
|
+
requiredKeys: mcp.requiredKeys || [],
|
|
90
|
+
fetch: {
|
|
91
|
+
type: "http",
|
|
92
|
+
url: mcp.url,
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
writeFileSync(
|
|
97
|
+
join(BASE, "registry", "mcps.json"),
|
|
98
|
+
JSON.stringify({ mcps: mcpEntries }, null, 2)
|
|
99
|
+
);
|
|
100
|
+
console.log(`✓ registry/mcps.json — ${mcpEntries.length} MCPs`);
|
|
101
|
+
|
|
102
|
+
// ── 2. Scan ~/.claude/commands/ → registry/skills.json ─────────────
|
|
103
|
+
|
|
104
|
+
function extractFrontmatter(content: string): { name?: string; description?: string } {
|
|
105
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
106
|
+
if (!match) return {};
|
|
107
|
+
const fm: Record<string, string> = {};
|
|
108
|
+
for (const line of match[1].split("\n")) {
|
|
109
|
+
const [key, ...rest] = line.split(":");
|
|
110
|
+
if (key && rest.length) fm[key.trim()] = rest.join(":").trim();
|
|
111
|
+
}
|
|
112
|
+
return { name: fm.name, description: fm.description };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function titleCase(id: string): string {
|
|
116
|
+
return id.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function guessCategory(id: string, description: string): string {
|
|
120
|
+
const text = (id + " " + description).toLowerCase();
|
|
121
|
+
if (text.match(/pdf|docx|pptx|xlsx|word|excel|powerpoint/)) return "documents";
|
|
122
|
+
if (text.match(/deploy|railway|mcp|skill|agent|build/)) return "devtools";
|
|
123
|
+
if (text.match(/bastion|soc|audit|compliance/)) return "compliance";
|
|
124
|
+
if (text.match(/client|bastion/)) return "client";
|
|
125
|
+
if (text.match(/test|tdd/)) return "testing";
|
|
126
|
+
if (text.match(/crypto|blockchain|wallet|token/)) return "crypto";
|
|
127
|
+
if (text.match(/slack|telegram|email|calendar/)) return "productivity";
|
|
128
|
+
if (text.match(/frontend|design|ui|css/)) return "design";
|
|
129
|
+
return "general";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
mkdirSync(join(BASE, "registry", "skills", "platform"), { recursive: true });
|
|
133
|
+
|
|
134
|
+
const skillEntries: any[] = [];
|
|
135
|
+
|
|
136
|
+
if (existsSync(COMMANDS_DIR)) {
|
|
137
|
+
const files = readdirSync(COMMANDS_DIR).filter(f => f.endsWith(".md"));
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
const id = file.replace(".md", "");
|
|
140
|
+
const srcPath = join(COMMANDS_DIR, file);
|
|
141
|
+
const destPath = join(BASE, "registry", "skills", "platform", file);
|
|
142
|
+
const content = readFileSync(srcPath, "utf-8");
|
|
143
|
+
const { name: fmName, description: fmDesc } = extractFrontmatter(content);
|
|
144
|
+
const name = fmName || titleCase(id);
|
|
145
|
+
const description = fmDesc || "";
|
|
146
|
+
const category = guessCategory(id, description);
|
|
147
|
+
|
|
148
|
+
copyFileSync(srcPath, destPath);
|
|
149
|
+
|
|
150
|
+
skillEntries.push({
|
|
151
|
+
id,
|
|
152
|
+
name,
|
|
153
|
+
provider: "AgenticLedger",
|
|
154
|
+
description,
|
|
155
|
+
category,
|
|
156
|
+
verified: true,
|
|
157
|
+
source: "agenticledger/platform",
|
|
158
|
+
tags: [category],
|
|
159
|
+
localPath: `registry/skills/platform/${file}`,
|
|
160
|
+
fetch: { type: "file" },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
console.log(`✓ registry/skills/platform/ — ${files.length} skills copied`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── 3. Fetch external skills from affaan-m/everything-claude-code ──
|
|
167
|
+
|
|
168
|
+
const EXTERNAL_SKILLS: Array<{ id: string; name: string; description: string; file: string }> = [
|
|
169
|
+
{ id: "tdd", name: "Test-Driven Development", description: "Write failing tests first, then implement.", file: "tdd.md" },
|
|
170
|
+
{ id: "code-review", name: "Code Review", description: "Systematic code review workflow.", file: "code-review.md" },
|
|
171
|
+
{ id: "systematic-debugging", name: "Systematic Debugging", description: "Structured debugging process.", file: "systematic-debugging.md" },
|
|
172
|
+
{ id: "writing-plans", name: "Write Plans", description: "Create detailed implementation plans.", file: "writing-plans.md" },
|
|
173
|
+
{ id: "verification-before-completion", name: "Verify Before Completing", description: "Run checks before marking work done.", file: "verification-before-completion.md" },
|
|
174
|
+
{ id: "brainstorming", name: "Brainstorming", description: "Design features through collaborative dialogue.", file: "brainstorming.md" },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const RAW_BASE = "https://raw.githubusercontent.com/affaan-m/everything-claude-code/main/commands";
|
|
178
|
+
|
|
179
|
+
mkdirSync(join(BASE, "registry", "skills", "external"), { recursive: true });
|
|
180
|
+
|
|
181
|
+
for (const skill of EXTERNAL_SKILLS) {
|
|
182
|
+
const destPath = join(BASE, "registry", "skills", "external", skill.file);
|
|
183
|
+
if (existsSync(destPath)) {
|
|
184
|
+
console.log(` skip (exists): ${skill.file}`);
|
|
185
|
+
} else {
|
|
186
|
+
try {
|
|
187
|
+
const content = execSync(`curl -sf "${RAW_BASE}/${skill.file}"`, { timeout: 10_000 }).toString();
|
|
188
|
+
writeFileSync(destPath, content);
|
|
189
|
+
console.log(` fetched: ${skill.file}`);
|
|
190
|
+
} catch {
|
|
191
|
+
// Write a placeholder if fetch fails
|
|
192
|
+
writeFileSync(destPath, `# ${skill.name}\n\n${skill.description}\n\n(Source: ${RAW_BASE}/${skill.file})\n`);
|
|
193
|
+
console.log(` placeholder: ${skill.file} (fetch failed)`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
skillEntries.push({
|
|
198
|
+
id: `ext-${skill.id}`,
|
|
199
|
+
name: skill.name,
|
|
200
|
+
provider: "affaan-m",
|
|
201
|
+
description: skill.description,
|
|
202
|
+
category: "devtools",
|
|
203
|
+
verified: false,
|
|
204
|
+
source: `github:affaan-m/everything-claude-code/commands/${skill.file}`,
|
|
205
|
+
tags: ["devtools", "external"],
|
|
206
|
+
localPath: `registry/skills/external/${skill.file}`,
|
|
207
|
+
fetch: { type: "file" },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
writeFileSync(
|
|
212
|
+
join(BASE, "registry", "skills.json"),
|
|
213
|
+
JSON.stringify({ skills: skillEntries }, null, 2)
|
|
214
|
+
);
|
|
215
|
+
console.log(`✓ registry/skills.json — ${skillEntries.length} skills total`);
|
|
216
|
+
|
|
217
|
+
// ── 4. Create minimal registry/agents.json ─────────────────────────
|
|
218
|
+
|
|
219
|
+
const agentsRegistry = {
|
|
220
|
+
agents: [
|
|
221
|
+
{
|
|
222
|
+
id: "general-assistant",
|
|
223
|
+
name: "General Assistant",
|
|
224
|
+
provider: "AgenticLedger",
|
|
225
|
+
description: "A general-purpose agent for everyday tasks.",
|
|
226
|
+
category: "general",
|
|
227
|
+
verified: true,
|
|
228
|
+
source: "agenticledger/platform",
|
|
229
|
+
tags: ["general"],
|
|
230
|
+
localPath: "registry/agents/platform/general-assistant",
|
|
231
|
+
fetch: { type: "file" },
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
writeFileSync(
|
|
237
|
+
join(BASE, "registry", "agents.json"),
|
|
238
|
+
JSON.stringify(agentsRegistry, null, 2)
|
|
239
|
+
);
|
|
240
|
+
console.log("✓ registry/agents.json — 1 template");
|
|
241
|
+
|
|
242
|
+
// ── 5. Create empty installed-drafts.json ─────────────────────────
|
|
243
|
+
|
|
244
|
+
const draftsPath = join(BASE, "registry", "installed-drafts.json");
|
|
245
|
+
if (!existsSync(draftsPath)) {
|
|
246
|
+
writeFileSync(draftsPath, JSON.stringify({ drafts: [] }, null, 2));
|
|
247
|
+
console.log("✓ registry/installed-drafts.json — created empty");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log("\n✅ Registry seed complete.");
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- [ ] **Step 3: Run the seed script**
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
cd ~/Desktop/APPs/channelToAgentToClaude && npx ts-node --esm scripts/seed-registry.ts 2>&1
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Expected output:
|
|
260
|
+
```
|
|
261
|
+
✓ registry/mcps.json — 47 MCPs
|
|
262
|
+
✓ registry/skills/platform/ — 67 skills copied
|
|
263
|
+
fetched: tdd.md (or "placeholder" if GitHub unavailable)
|
|
264
|
+
...
|
|
265
|
+
✓ registry/skills.json — 73 skills total
|
|
266
|
+
✓ registry/agents.json — 1 template
|
|
267
|
+
✓ registry/installed-drafts.json — created empty
|
|
268
|
+
✅ Registry seed complete.
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
- [ ] **Step 4: Verify output files exist and are valid JSON**
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
node -e "const f=require('fs');['registry/mcps.json','registry/skills.json','registry/agents.json'].forEach(p=>{const d=JSON.parse(f.readFileSync(p,'utf-8'));console.log(p,JSON.stringify(Object.keys(d)))})"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Expected: `registry/mcps.json ["mcps"]`, `registry/skills.json ["skills"]`, `registry/agents.json ["agents"]`
|
|
278
|
+
|
|
279
|
+
- [ ] **Step 5: Commit registry data**
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
cd ~/Desktop/APPs/channelToAgentToClaude
|
|
283
|
+
git add registry/ scripts/seed-registry.ts
|
|
284
|
+
git commit -m "feat: add registry data layer and seed script"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Task 2: Backend API Endpoints
|
|
290
|
+
|
|
291
|
+
**Files:**
|
|
292
|
+
- Modify: `src/web-ui.ts` (add after the recover endpoint, around line 565)
|
|
293
|
+
|
|
294
|
+
The three endpoints follow the exact same patterns as existing endpoints: read opts.config in memory, write config.json then sync to opts.config, return `{ ok: true }` or error.
|
|
295
|
+
|
|
296
|
+
- [ ] **Step 1: Add the `appendFileSync` import check**
|
|
297
|
+
|
|
298
|
+
`appendFileSync` is already imported from Task 0 (we added it in the recover endpoint earlier). Verify:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
grep "appendFileSync" ~/Desktop/APPs/channelToAgentToClaude/src/web-ui.ts | head -1
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Expected: sees `appendFileSync` in the import line.
|
|
305
|
+
|
|
306
|
+
- [ ] **Step 2: Add GET /api/marketplace/:type endpoint**
|
|
307
|
+
|
|
308
|
+
In `src/web-ui.ts`, find the recover endpoint we added earlier and add the marketplace endpoints immediately after it. Add this block:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// ─── API: Marketplace ──────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
app.get("/api/marketplace/:type", (req, res) => {
|
|
314
|
+
const { type } = req.params;
|
|
315
|
+
if (!["mcps", "skills", "agents"].includes(type)) {
|
|
316
|
+
return res.status(400).json({ error: "type must be mcps, skills, or agents" });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const registryPath = join(opts.baseDir, "registry", `${type}.json`);
|
|
320
|
+
if (!existsSync(registryPath)) {
|
|
321
|
+
return res.json({ items: [] });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let entries: any[] = [];
|
|
325
|
+
try {
|
|
326
|
+
const data = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
327
|
+
entries = data[type] || [];
|
|
328
|
+
} catch {
|
|
329
|
+
return res.status(500).json({ error: "Failed to read registry" });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const home = homedir();
|
|
333
|
+
const resolveTilde = (p: string) => p.startsWith("~") ? p.replace("~", home) : p;
|
|
334
|
+
const personalSkillsDir = join(resolveTilde(getPersonalAgentsDir(opts.config)), "skills");
|
|
335
|
+
const claudeCommandsDir = join(home, ".claude", "commands");
|
|
336
|
+
|
|
337
|
+
const items = entries.map((entry: any) => {
|
|
338
|
+
let installed = false;
|
|
339
|
+
const assignedTo: string[] = [];
|
|
340
|
+
|
|
341
|
+
if (type === "skills") {
|
|
342
|
+
const id = entry.id;
|
|
343
|
+
installed = existsSync(join(personalSkillsDir, `${id}.md`))
|
|
344
|
+
|| existsSync(join(claudeCommandsDir, `${id}.md`));
|
|
345
|
+
for (const [agentId, agent] of Object.entries(opts.config.agents)) {
|
|
346
|
+
if ((agent as any).skills?.includes(id)) assignedTo.push(agentId);
|
|
347
|
+
}
|
|
348
|
+
} else if (type === "mcps") {
|
|
349
|
+
installed = !!opts.config.mcps?.[entry.id];
|
|
350
|
+
for (const [agentId, agent] of Object.entries(opts.config.agents)) {
|
|
351
|
+
if ((agent as any).mcps?.includes(entry.id)) assignedTo.push(agentId);
|
|
352
|
+
}
|
|
353
|
+
} else if (type === "agents") {
|
|
354
|
+
// Check installed-drafts.json + real config agents
|
|
355
|
+
const draftsPath = join(opts.baseDir, "registry", "installed-drafts.json");
|
|
356
|
+
let drafts: string[] = [];
|
|
357
|
+
try {
|
|
358
|
+
drafts = JSON.parse(readFileSync(draftsPath, "utf-8")).drafts.map((d: any) => d.id);
|
|
359
|
+
} catch { /* ignore */ }
|
|
360
|
+
installed = existsSync(join(opts.baseDir, "agents", entry.id))
|
|
361
|
+
|| drafts.includes(entry.id)
|
|
362
|
+
|| !!opts.config.agents[entry.id];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return { ...entry, installed, assignedTo };
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
res.json({ items });
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
- [ ] **Step 3: Add POST /api/marketplace/install endpoint**
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
app.post("/api/marketplace/install", (req, res) => {
|
|
376
|
+
const { type, id } = req.body as { type?: string; id?: string };
|
|
377
|
+
if (!type || !id) return res.status(400).json({ error: "Missing type or id" });
|
|
378
|
+
|
|
379
|
+
const registryPath = join(opts.baseDir, "registry", `${type}s.json`);
|
|
380
|
+
if (!existsSync(registryPath)) return res.status(404).json({ error: "Registry not found" });
|
|
381
|
+
|
|
382
|
+
let entry: any;
|
|
383
|
+
try {
|
|
384
|
+
const data = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
385
|
+
const key = type === "mcp" ? "mcps" : type === "skill" ? "skills" : "agents";
|
|
386
|
+
entry = (data[key] || []).find((e: any) => e.id === id);
|
|
387
|
+
} catch {
|
|
388
|
+
return res.status(500).json({ error: "Failed to read registry" });
|
|
389
|
+
}
|
|
390
|
+
if (!entry) return res.status(404).json({ error: `${type} "${id}" not found in registry` });
|
|
391
|
+
|
|
392
|
+
const home = homedir();
|
|
393
|
+
const resolveTilde = (p: string) => p.startsWith("~") ? p.replace("~", home) : p;
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
if (type === "skill") {
|
|
397
|
+
// Copy skill file to personalAgents/skills/ directory
|
|
398
|
+
const destDir = join(resolveTilde(getPersonalAgentsDir(opts.config)), "skills");
|
|
399
|
+
mkdirSync(destDir, { recursive: true });
|
|
400
|
+
const srcPath = join(opts.baseDir, entry.localPath);
|
|
401
|
+
const destPath = join(destDir, `${id}.md`);
|
|
402
|
+
if (!existsSync(srcPath)) return res.status(500).json({ error: `Source file not found: ${entry.localPath}` });
|
|
403
|
+
const { copyFileSync: cfSync } = require("node:fs");
|
|
404
|
+
cfSync(srcPath, destPath);
|
|
405
|
+
log.info(`[Marketplace] Installed skill ${id} → ${destPath}`);
|
|
406
|
+
|
|
407
|
+
} else if (type === "mcp") {
|
|
408
|
+
if (entry.fetch?.type === "http") {
|
|
409
|
+
// Write http entry to config.mcps
|
|
410
|
+
const configPath = join(opts.baseDir, "config.json");
|
|
411
|
+
const rawConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
412
|
+
if (!rawConfig.mcps) rawConfig.mcps = {};
|
|
413
|
+
rawConfig.mcps[id] = { type: "http", url: entry.fetch.url, headers: {} };
|
|
414
|
+
writeFileSync(configPath, JSON.stringify(rawConfig, null, 2));
|
|
415
|
+
if (!opts.config.mcps) (opts.config as any).mcps = {};
|
|
416
|
+
(opts.config.mcps as any)[id] = { type: "http", url: entry.fetch.url, headers: {} };
|
|
417
|
+
log.info(`[Marketplace] Installed MCP ${id} (http)`);
|
|
418
|
+
|
|
419
|
+
} else if (entry.fetch?.type === "npm") {
|
|
420
|
+
// Install npm package
|
|
421
|
+
const { execSync: es } = require("node:child_process");
|
|
422
|
+
es(`npm install ${entry.fetch.package}`, { cwd: opts.baseDir, timeout: 30_000 });
|
|
423
|
+
const configPath = join(opts.baseDir, "config.json");
|
|
424
|
+
const rawConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
425
|
+
if (!rawConfig.mcps) rawConfig.mcps = {};
|
|
426
|
+
rawConfig.mcps[id] = { type: "stdio", command: "npx", args: entry.fetch.args || ["-y", entry.fetch.package], env: {} };
|
|
427
|
+
writeFileSync(configPath, JSON.stringify(rawConfig, null, 2));
|
|
428
|
+
if (!opts.config.mcps) (opts.config as any).mcps = {};
|
|
429
|
+
(opts.config.mcps as any)[id] = rawConfig.mcps[id];
|
|
430
|
+
log.info(`[Marketplace] Installed MCP ${id} (npm: ${entry.fetch.package})`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
} else if (type === "agent") {
|
|
434
|
+
// Copy template files, write to installed-drafts.json (NOT config.json — see spec)
|
|
435
|
+
const srcDir = join(opts.baseDir, entry.localPath);
|
|
436
|
+
const destDir = join(opts.baseDir, "agents", id);
|
|
437
|
+
if (existsSync(srcDir)) {
|
|
438
|
+
mkdirSync(destDir, { recursive: true });
|
|
439
|
+
for (const file of readdirSync(srcDir)) {
|
|
440
|
+
const { copyFileSync: cfSync } = require("node:fs");
|
|
441
|
+
cfSync(join(srcDir, file), join(destDir, file));
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
// No template files — just create the directory with a default CLAUDE.md
|
|
445
|
+
mkdirSync(join(destDir, "memory"), { recursive: true });
|
|
446
|
+
writeFileSync(join(destDir, "CLAUDE.md"), `# ${entry.name}\n\n${entry.description}\n`);
|
|
447
|
+
writeFileSync(join(destDir, "agent.json"), JSON.stringify({ id, name: entry.name, draft: true, version: "1.0.0", created: new Date().toISOString() }, null, 2));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Append to installed-drafts.json
|
|
451
|
+
const draftsPath = join(opts.baseDir, "registry", "installed-drafts.json");
|
|
452
|
+
let draftsData = { drafts: [] as any[] };
|
|
453
|
+
try { draftsData = JSON.parse(readFileSync(draftsPath, "utf-8")); } catch { /* fresh */ }
|
|
454
|
+
if (!draftsData.drafts.find((d: any) => d.id === id)) {
|
|
455
|
+
draftsData.drafts.push({ id, name: entry.name, installedAt: new Date().toISOString() });
|
|
456
|
+
writeFileSync(draftsPath, JSON.stringify(draftsData, null, 2));
|
|
457
|
+
}
|
|
458
|
+
log.info(`[Marketplace] Installed agent template ${id} → draft`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const requiresKeys = type === "mcp" && (entry.requiredKeys?.length > 0);
|
|
462
|
+
res.json({ ok: true, item: { ...entry, installed: true }, requiresKeys });
|
|
463
|
+
|
|
464
|
+
} catch (err) {
|
|
465
|
+
log.error(`[Marketplace] Install failed for ${type}/${id}: ${err}`);
|
|
466
|
+
res.status(500).json({ error: String(err) });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
- [ ] **Step 4: Add POST /api/marketplace/assign endpoint**
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
app.post("/api/marketplace/assign", (req, res) => {
|
|
475
|
+
const { type, id, agentIds } = req.body as { type?: string; id?: string; agentIds?: string[] };
|
|
476
|
+
if (!type || !id || !Array.isArray(agentIds) || agentIds.length === 0) {
|
|
477
|
+
return res.status(400).json({ error: "Missing type, id, or agentIds" });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const configPath = join(opts.baseDir, "config.json");
|
|
481
|
+
let rawConfig: any;
|
|
482
|
+
try {
|
|
483
|
+
rawConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
484
|
+
} catch {
|
|
485
|
+
return res.status(500).json({ error: "Failed to read config.json" });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const missingKeys: string[] = [];
|
|
489
|
+
|
|
490
|
+
for (const agentId of agentIds) {
|
|
491
|
+
if (!rawConfig.agents[agentId]) continue;
|
|
492
|
+
|
|
493
|
+
if (type === "skill") {
|
|
494
|
+
if (!rawConfig.agents[agentId].skills) rawConfig.agents[agentId].skills = [];
|
|
495
|
+
if (!rawConfig.agents[agentId].skills.includes(id)) {
|
|
496
|
+
rawConfig.agents[agentId].skills.push(id);
|
|
497
|
+
}
|
|
498
|
+
if (!opts.config.agents[agentId].skills) (opts.config.agents[agentId] as any).skills = [];
|
|
499
|
+
if (!(opts.config.agents[agentId] as any).skills.includes(id)) {
|
|
500
|
+
(opts.config.agents[agentId] as any).skills.push(id);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
} else if (type === "mcp") {
|
|
504
|
+
if (!rawConfig.agents[agentId].mcps) rawConfig.agents[agentId].mcps = [];
|
|
505
|
+
if (!rawConfig.agents[agentId].mcps.includes(id)) {
|
|
506
|
+
rawConfig.agents[agentId].mcps.push(id);
|
|
507
|
+
}
|
|
508
|
+
if (!opts.config.agents[agentId].mcps) (opts.config.agents[agentId] as any).mcps = [];
|
|
509
|
+
if (!(opts.config.agents[agentId] as any).mcps.includes(id)) {
|
|
510
|
+
(opts.config.agents[agentId] as any).mcps.push(id);
|
|
511
|
+
}
|
|
512
|
+
// Check if key file exists
|
|
513
|
+
const home = homedir();
|
|
514
|
+
const resolveTilde = (p: string) => p.startsWith("~") ? p.replace("~", home) : p;
|
|
515
|
+
const agentCfg = opts.config.agents[agentId] as any;
|
|
516
|
+
const agentHome = agentCfg.agentHome
|
|
517
|
+
? resolveTilde(agentCfg.agentHome)
|
|
518
|
+
: join(resolveTilde(agentCfg.memoryDir || ""), "..");
|
|
519
|
+
const keyFile = join(agentHome, "mcp-keys", `${id}.env`);
|
|
520
|
+
if (!existsSync(keyFile)) {
|
|
521
|
+
missingKeys.push(agentId);
|
|
522
|
+
// Create empty stub so executor doesn't throw
|
|
523
|
+
mkdirSync(join(agentHome, "mcp-keys"), { recursive: true });
|
|
524
|
+
writeFileSync(keyFile, "");
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
writeFileSync(configPath, JSON.stringify(rawConfig, null, 2));
|
|
531
|
+
} catch (err) {
|
|
532
|
+
return res.status(500).json({ error: `Failed to write config: ${err}` });
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
log.info(`[Marketplace] Assigned ${type}/${id} to agents: ${agentIds.join(", ")}`);
|
|
536
|
+
res.json({ ok: true, assigned: agentIds, missingKeys });
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
- [ ] **Step 5: Add /marketplace route to serve the page**
|
|
541
|
+
|
|
542
|
+
Find the existing page serving routes (around line 60 in web-ui.ts) and add:
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
// ─── Serve the Marketplace page ────────────────────────────────
|
|
546
|
+
app.get("/marketplace", (_req, res) => {
|
|
547
|
+
const htmlPath = join(opts.baseDir, "public", "marketplace.html");
|
|
548
|
+
if (existsSync(htmlPath)) {
|
|
549
|
+
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
550
|
+
res.sendFile(htmlPath);
|
|
551
|
+
} else {
|
|
552
|
+
res.status(404).send("Marketplace page not found.");
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
- [ ] **Step 6: Build and verify no TypeScript errors**
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
cd ~/Desktop/APPs/channelToAgentToClaude && npm run build 2>&1
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Expected: clean build, no errors.
|
|
564
|
+
|
|
565
|
+
- [ ] **Step 7: Commit backend**
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
git add src/web-ui.ts
|
|
569
|
+
git commit -m "feat: add marketplace API endpoints (list, install, assign)"
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Task 3: Frontend — marketplace.html
|
|
575
|
+
|
|
576
|
+
**Files:**
|
|
577
|
+
- Create: `public/marketplace.html`
|
|
578
|
+
|
|
579
|
+
The page must match the existing design system exactly:
|
|
580
|
+
- Same CSS variables as org.html and activity.html
|
|
581
|
+
- Same fonts: DM Sans, IBM Plex Mono, Syne
|
|
582
|
+
- Same topbar with logo-mark, tab-btn navigation, theme toggle
|
|
583
|
+
- Cards using `--bg-card`, `--border-glow`, `--accent`, `--green`, `--amber`
|
|
584
|
+
- Same modal pattern as org.html's existing modals
|
|
585
|
+
|
|
586
|
+
- [ ] **Step 1: Create marketplace.html**
|
|
587
|
+
|
|
588
|
+
Create `public/marketplace.html` with the full implementation below. This is a long file — write it completely:
|
|
589
|
+
|
|
590
|
+
```html
|
|
591
|
+
<!DOCTYPE html>
|
|
592
|
+
<html lang="en">
|
|
593
|
+
<head>
|
|
594
|
+
<meta charset="UTF-8">
|
|
595
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
596
|
+
<title>MyAIforOne — Marketplace</title>
|
|
597
|
+
<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">
|
|
598
|
+
<style>
|
|
599
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
600
|
+
|
|
601
|
+
:root{
|
|
602
|
+
--bg-deep:#060a13;
|
|
603
|
+
--bg-surface:rgba(12,18,33,0.92);
|
|
604
|
+
--bg-card:rgba(16,22,40,0.85);
|
|
605
|
+
--bg-input:rgba(0,0,0,0.35);
|
|
606
|
+
--border-dim:rgba(56,189,248,0.08);
|
|
607
|
+
--border-glow:rgba(56,189,248,0.18);
|
|
608
|
+
--border-active:rgba(56,189,248,0.45);
|
|
609
|
+
--text-primary:rgba(255,255,255,0.92);
|
|
610
|
+
--text-secondary:rgba(255,255,255,0.68);
|
|
611
|
+
--text-muted:rgba(148,163,184,0.55);
|
|
612
|
+
--accent:#22d3ee;
|
|
613
|
+
--accent-soft:#38bdf8;
|
|
614
|
+
--accent-bg:rgba(6,182,212,0.15);
|
|
615
|
+
--accent-glow:rgba(34,211,238,0.12);
|
|
616
|
+
--purple:rgba(139,92,246,0.7);
|
|
617
|
+
--purple-bg:rgba(139,92,246,0.12);
|
|
618
|
+
--green:#4ade80;
|
|
619
|
+
--green-bg:rgba(74,222,128,0.1);
|
|
620
|
+
--amber:#fbbf24;
|
|
621
|
+
--amber-bg:rgba(251,191,36,0.1);
|
|
622
|
+
--red:#f87171;
|
|
623
|
+
--red-bg:rgba(248,113,113,0.1);
|
|
624
|
+
--shadow:0 2px 12px rgba(0,0,0,0.3);
|
|
625
|
+
--shadow-glow:0 0 20px rgba(34,211,238,0.08);
|
|
626
|
+
--radius:12px;
|
|
627
|
+
--font-sans:'DM Sans',system-ui,sans-serif;
|
|
628
|
+
--font-mono:'IBM Plex Mono',monospace;
|
|
629
|
+
--font-display:'Syne',sans-serif;
|
|
630
|
+
}
|
|
631
|
+
[data-theme="light"]{
|
|
632
|
+
--bg-deep:#f4f6f9;--bg-surface:rgba(255,255,255,0.95);--bg-card:rgba(255,255,255,0.9);
|
|
633
|
+
--bg-input:rgba(0,0,0,0.04);--border-dim:rgba(0,0,0,0.08);--border-glow:rgba(14,116,144,0.18);
|
|
634
|
+
--border-active:rgba(14,116,144,0.45);--text-primary:rgba(15,23,42,0.92);
|
|
635
|
+
--text-secondary:rgba(51,65,85,0.8);--text-muted:rgba(100,116,139,0.6);
|
|
636
|
+
--accent:#0891b2;--accent-soft:#0e7490;--accent-bg:rgba(14,116,144,0.08);
|
|
637
|
+
--accent-glow:rgba(14,116,144,0.06);--purple:rgba(109,40,217,0.75);--purple-bg:rgba(139,92,246,0.08);
|
|
638
|
+
--green:#16a34a;--green-bg:rgba(22,163,74,0.08);--amber:#d97706;--amber-bg:rgba(217,119,6,0.08);
|
|
639
|
+
--red:#dc2626;--red-bg:rgba(220,38,38,0.08);--shadow:0 1px 8px rgba(0,0,0,0.06);--shadow-glow:none;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
html,body{width:100%;height:100%;overflow:hidden;background:var(--bg-deep);font-family:var(--font-sans);color:var(--text-primary);transition:background .3s,color .3s}
|
|
643
|
+
|
|
644
|
+
/* ── Topbar ── */
|
|
645
|
+
.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}
|
|
646
|
+
.topbar-logo{display:flex;align-items:center;gap:10px;margin-right:32px}
|
|
647
|
+
.logo-mark{width:32px;height:32px;border-radius:8px;background:var(--accent-bg);border:1px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:var(--font-display);font-size:14px;font-weight:800;color:var(--accent)}
|
|
648
|
+
.logo-text{font-family:var(--font-display);font-size:15px;font-weight:700}
|
|
649
|
+
.tab-group{display:flex;gap:0}
|
|
650
|
+
.tab-btn{font-family:var(--font-sans);font-size:13px;font-weight:600;color:var(--text-muted);background:none;border:none;padding:16px 20px;cursor:pointer;position:relative;transition:color .2s;text-decoration:none}
|
|
651
|
+
.tab-btn:hover{color:var(--text-secondary)}
|
|
652
|
+
.tab-btn.active{color:var(--accent)}
|
|
653
|
+
.tab-btn.active::after{content:'';position:absolute;bottom:0;left:12px;right:12px;height:2px;background:var(--accent);border-radius:1px}
|
|
654
|
+
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:12px}
|
|
655
|
+
.theme-toggle{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 .2s}
|
|
656
|
+
.theme-toggle:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
657
|
+
|
|
658
|
+
/* ── Canvas ── */
|
|
659
|
+
.canvas{position:fixed;top:56px;left:0;right:0;bottom:0;overflow:auto;padding:32px 40px}
|
|
660
|
+
|
|
661
|
+
/* ── Toolbar ── */
|
|
662
|
+
.toolbar{display:flex;align-items:center;gap:12px;margin-bottom:24px;flex-wrap:wrap}
|
|
663
|
+
.type-tabs{display:flex;gap:4px;background:var(--bg-card);border:1px solid var(--border-dim);border-radius:10px;padding:4px}
|
|
664
|
+
.type-tab{font-family:var(--font-sans);font-size:12px;font-weight:600;padding:6px 16px;border-radius:7px;border:none;background:transparent;color:var(--text-muted);cursor:pointer;transition:all .2s}
|
|
665
|
+
.type-tab.active{background:var(--accent-bg);color:var(--accent);border:1px solid var(--border-active)}
|
|
666
|
+
.search-wrap{flex:1;max-width:320px;position:relative}
|
|
667
|
+
.search-input{width:100%;background:var(--bg-input);border:1px solid var(--border-dim);border-radius:8px;padding:8px 12px 8px 32px;font-family:var(--font-sans);font-size:13px;color:var(--text-primary);outline:none;transition:border-color .2s}
|
|
668
|
+
.search-input:focus{border-color:var(--border-active)}
|
|
669
|
+
.search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:13px;color:var(--text-muted);pointer-events:none}
|
|
670
|
+
.filter-pills{display:flex;gap:6px;flex-wrap:wrap}
|
|
671
|
+
.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}
|
|
672
|
+
.pill:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
673
|
+
.pill.active{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
|
|
674
|
+
|
|
675
|
+
/* ── Grid ── */
|
|
676
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
|
677
|
+
|
|
678
|
+
/* ── Card ── */
|
|
679
|
+
.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);position:relative}
|
|
680
|
+
.card:hover{border-color:var(--border-glow);box-shadow:var(--shadow),var(--shadow-glow);transform:translateY(-1px)}
|
|
681
|
+
.card.installed{border-color:rgba(74,222,128,0.2)}
|
|
682
|
+
|
|
683
|
+
.card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
|
|
684
|
+
.card-name{font-family:var(--font-display);font-size:14px;font-weight:700;color:var(--text-primary);line-height:1.2}
|
|
685
|
+
.card-badge-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex-shrink:0}
|
|
686
|
+
.badge{font-family:var(--font-mono);font-size:9px;font-weight:600;padding:3px 7px;border-radius:4px;white-space:nowrap;letter-spacing:.03em}
|
|
687
|
+
.badge-verified{background:var(--accent-bg);color:var(--accent);border:1px solid rgba(34,211,238,0.3)}
|
|
688
|
+
.badge-external{background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim)}
|
|
689
|
+
.badge-installed{background:var(--green-bg);color:var(--green);border:1px solid rgba(74,222,128,0.3)}
|
|
690
|
+
.badge-amber{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(251,191,36,0.3)}
|
|
691
|
+
|
|
692
|
+
.card-provider{font-family:var(--font-mono);font-size:10px;color:var(--text-muted)}
|
|
693
|
+
.card-desc{font-size:12px;color:var(--text-secondary);line-height:1.5;flex:1}
|
|
694
|
+
.card-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
695
|
+
.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)}
|
|
696
|
+
.assigned-to{font-family:var(--font-mono);font-size:9px;color:var(--text-muted)}
|
|
697
|
+
|
|
698
|
+
.card-actions{display:flex;gap:8px;margin-top:4px}
|
|
699
|
+
.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}
|
|
700
|
+
.btn-install{background:var(--accent-bg);color:var(--accent);border:1px solid var(--border-active)}
|
|
701
|
+
.btn-install:hover{background:var(--accent);color:#000}
|
|
702
|
+
.btn-manage{background:transparent;color:var(--text-muted);border:1px solid var(--border-dim)}
|
|
703
|
+
.btn-manage:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
704
|
+
.btn-installing{background:var(--bg-input);color:var(--text-muted);border:1px solid var(--border-dim);cursor:default}
|
|
705
|
+
|
|
706
|
+
/* ── Empty state ── */
|
|
707
|
+
.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:300px;gap:12px;color:var(--text-muted)}
|
|
708
|
+
.empty-icon{font-size:40px;opacity:.4}
|
|
709
|
+
.empty-text{font-size:14px}
|
|
710
|
+
|
|
711
|
+
/* ── Toast ── */
|
|
712
|
+
.toast{position:fixed;bottom:24px;right:24px;background:var(--bg-card);border:1px solid var(--border-glow);border-radius:10px;padding:12px 18px;font-size:13px;color:var(--text-primary);box-shadow:var(--shadow);z-index:300;opacity:0;transform:translateY(8px);transition:all .3s;pointer-events:none}
|
|
713
|
+
.toast.show{opacity:1;transform:translateY(0)}
|
|
714
|
+
.toast.toast-error{border-color:rgba(248,113,113,0.4);color:var(--red)}
|
|
715
|
+
.toast.toast-success{border-color:rgba(74,222,128,0.4);color:var(--green)}
|
|
716
|
+
|
|
717
|
+
/* ── Modal backdrop ── */
|
|
718
|
+
.modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);z-index:200;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s}
|
|
719
|
+
.modal-backdrop.open{opacity:1;pointer-events:all}
|
|
720
|
+
.modal{background:var(--bg-surface);border:1px solid var(--border-glow);border-radius:16px;padding:28px;width:420px;max-width:90vw;max-height:80vh;overflow-y:auto;box-shadow:0 8px 40px rgba(0,0,0,0.5);transform:scale(.96);transition:transform .25s}
|
|
721
|
+
.modal-backdrop.open .modal{transform:scale(1)}
|
|
722
|
+
.modal-title{font-family:var(--font-display);font-size:16px;font-weight:700;margin-bottom:4px}
|
|
723
|
+
.modal-subtitle{font-size:13px;color:var(--text-muted);margin-bottom:20px}
|
|
724
|
+
.modal-section-label{font-family:var(--font-mono);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted);margin-bottom:10px}
|
|
725
|
+
.agent-list{display:flex;flex-direction:column;gap:6px;max-height:260px;overflow-y:auto;margin-bottom:20px}
|
|
726
|
+
.agent-check{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;border:1px solid var(--border-dim);cursor:pointer;transition:all .2s}
|
|
727
|
+
.agent-check:hover{border-color:var(--border-glow);background:var(--accent-bg)}
|
|
728
|
+
.agent-check input[type=checkbox]{accent-color:var(--accent);width:14px;height:14px;cursor:pointer}
|
|
729
|
+
.agent-check-name{font-size:13px;font-weight:500}
|
|
730
|
+
.agent-check-id{font-family:var(--font-mono);font-size:10px;color:var(--text-muted)}
|
|
731
|
+
.modal-actions{display:flex;gap:10px}
|
|
732
|
+
.btn-primary{background:var(--accent);color:#000;font-family:var(--font-sans);font-size:13px;font-weight:700;padding:10px 20px;border-radius:9px;border:none;cursor:pointer;transition:opacity .2s;flex:1}
|
|
733
|
+
.btn-primary:hover{opacity:.85}
|
|
734
|
+
.btn-secondary{background:transparent;color:var(--text-muted);font-family:var(--font-sans);font-size:13px;font-weight:600;padding:10px 20px;border-radius:9px;border:1px solid var(--border-dim);cursor:pointer;transition:all .2s}
|
|
735
|
+
.btn-secondary:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
736
|
+
.modal-warning{font-size:12px;color:var(--amber);background:var(--amber-bg);border:1px solid rgba(251,191,36,0.2);border-radius:8px;padding:10px 12px;margin-top:12px}
|
|
737
|
+
</style>
|
|
738
|
+
</head>
|
|
739
|
+
<body>
|
|
740
|
+
|
|
741
|
+
<!-- Topbar -->
|
|
742
|
+
<header class="topbar">
|
|
743
|
+
<div class="topbar-logo">
|
|
744
|
+
<div class="logo-mark">M</div>
|
|
745
|
+
<span class="logo-text">MyAIforOne</span>
|
|
746
|
+
</div>
|
|
747
|
+
<nav class="tab-group">
|
|
748
|
+
<a href="/" class="tab-btn">Home</a>
|
|
749
|
+
<a href="/org" class="tab-btn">Dashboard</a>
|
|
750
|
+
<a href="/marketplace" class="tab-btn active">Marketplace</a>
|
|
751
|
+
<a href="/activity" class="tab-btn">Activity</a>
|
|
752
|
+
</nav>
|
|
753
|
+
<div class="topbar-right">
|
|
754
|
+
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme">☀</button>
|
|
755
|
+
</div>
|
|
756
|
+
</header>
|
|
757
|
+
|
|
758
|
+
<main class="canvas">
|
|
759
|
+
<!-- Toolbar -->
|
|
760
|
+
<div class="toolbar">
|
|
761
|
+
<div class="type-tabs">
|
|
762
|
+
<button class="type-tab active" onclick="setType('mcps')">MCPs</button>
|
|
763
|
+
<button class="type-tab" onclick="setType('skills')">Skills</button>
|
|
764
|
+
<button class="type-tab" onclick="setType('agents')">Agents</button>
|
|
765
|
+
</div>
|
|
766
|
+
<div class="search-wrap">
|
|
767
|
+
<span class="search-icon">⌕</span>
|
|
768
|
+
<input class="search-input" type="text" placeholder="Search..." oninput="onSearch(this.value)" id="searchInput">
|
|
769
|
+
</div>
|
|
770
|
+
<div class="filter-pills" id="categoryPills"></div>
|
|
771
|
+
</div>
|
|
772
|
+
|
|
773
|
+
<!-- Grid -->
|
|
774
|
+
<div class="grid" id="grid"></div>
|
|
775
|
+
</main>
|
|
776
|
+
|
|
777
|
+
<!-- Assign Modal -->
|
|
778
|
+
<div class="modal-backdrop" id="assignModal">
|
|
779
|
+
<div class="modal">
|
|
780
|
+
<div class="modal-title" id="modalTitle">Installed!</div>
|
|
781
|
+
<div class="modal-subtitle" id="modalSubtitle">Assign to agents now? (optional)</div>
|
|
782
|
+
<div class="modal-section-label">Select agents</div>
|
|
783
|
+
<div class="agent-list" id="agentList"></div>
|
|
784
|
+
<div class="modal-actions">
|
|
785
|
+
<button class="btn-primary" onclick="submitAssign()">Assign selected</button>
|
|
786
|
+
<button class="btn-secondary" onclick="closeModal()">Skip for now</button>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="modal-warning" id="modalWarning" style="display:none"></div>
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
|
|
792
|
+
<!-- Toast -->
|
|
793
|
+
<div class="toast" id="toast"></div>
|
|
794
|
+
|
|
795
|
+
<script>
|
|
796
|
+
// ── State ──────────────────────────────────────────────────────────
|
|
797
|
+
let currentType = 'mcps';
|
|
798
|
+
let allItems = [];
|
|
799
|
+
let agents = [];
|
|
800
|
+
let searchQuery = '';
|
|
801
|
+
let activeCat = 'all';
|
|
802
|
+
let pendingInstall = null; // { type, id, name }
|
|
803
|
+
|
|
804
|
+
// ── Init ───────────────────────────────────────────────────────────
|
|
805
|
+
async function init() {
|
|
806
|
+
// Restore theme
|
|
807
|
+
const saved = localStorage.getItem('theme');
|
|
808
|
+
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
|
809
|
+
|
|
810
|
+
// Load agents for assign modal
|
|
811
|
+
try {
|
|
812
|
+
const res = await fetch('/api/dashboard');
|
|
813
|
+
const data = await res.json();
|
|
814
|
+
agents = data.agents || [];
|
|
815
|
+
} catch { agents = []; }
|
|
816
|
+
|
|
817
|
+
await loadType('mcps');
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ── Load a type ────────────────────────────────────────────────────
|
|
821
|
+
async function loadType(type) {
|
|
822
|
+
currentType = type;
|
|
823
|
+
searchQuery = '';
|
|
824
|
+
activeCat = 'all';
|
|
825
|
+
document.getElementById('searchInput').value = '';
|
|
826
|
+
|
|
827
|
+
// Update tab buttons
|
|
828
|
+
document.querySelectorAll('.type-tab').forEach(b => b.classList.remove('active'));
|
|
829
|
+
document.querySelectorAll('.type-tab')[['mcps','skills','agents'].indexOf(type)].classList.add('active');
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
const res = await fetch(`/api/marketplace/${type}`);
|
|
833
|
+
const data = await res.json();
|
|
834
|
+
allItems = data.items || [];
|
|
835
|
+
} catch {
|
|
836
|
+
allItems = [];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
buildCategoryPills();
|
|
840
|
+
render();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function setType(type) { loadType(type); }
|
|
844
|
+
|
|
845
|
+
// ── Search / filter ────────────────────────────────────────────────
|
|
846
|
+
function onSearch(q) { searchQuery = q.toLowerCase(); render(); }
|
|
847
|
+
|
|
848
|
+
function buildCategoryPills() {
|
|
849
|
+
const cats = ['all', ...new Set(allItems.map(i => i.category).filter(Boolean))];
|
|
850
|
+
const el = document.getElementById('categoryPills');
|
|
851
|
+
el.innerHTML = cats.map(c =>
|
|
852
|
+
`<button class="pill${c === activeCat ? ' active' : ''}" onclick="setCat('${c}')">${c}</button>`
|
|
853
|
+
).join('');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function setCat(cat) {
|
|
857
|
+
activeCat = cat;
|
|
858
|
+
buildCategoryPills();
|
|
859
|
+
render();
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// ── Render ─────────────────────────────────────────────────────────
|
|
863
|
+
function render() {
|
|
864
|
+
const grid = document.getElementById('grid');
|
|
865
|
+
|
|
866
|
+
const filtered = allItems.filter(item => {
|
|
867
|
+
const matchCat = activeCat === 'all' || item.category === activeCat;
|
|
868
|
+
const matchSearch = !searchQuery
|
|
869
|
+
|| item.name?.toLowerCase().includes(searchQuery)
|
|
870
|
+
|| item.description?.toLowerCase().includes(searchQuery)
|
|
871
|
+
|| item.tags?.some(t => t.toLowerCase().includes(searchQuery))
|
|
872
|
+
|| item.provider?.toLowerCase().includes(searchQuery);
|
|
873
|
+
return matchCat && matchSearch;
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
if (filtered.length === 0) {
|
|
877
|
+
grid.innerHTML = `<div class="empty" style="grid-column:1/-1">
|
|
878
|
+
<div class="empty-icon">◇</div>
|
|
879
|
+
<div class="empty-text">No items found</div>
|
|
880
|
+
</div>`;
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
grid.innerHTML = filtered.map(item => renderCard(item)).join('');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function renderCard(item) {
|
|
888
|
+
const typeKey = currentType === 'mcps' ? 'mcp' : currentType === 'skills' ? 'skill' : 'agent';
|
|
889
|
+
const assigned = item.assignedTo?.length > 0
|
|
890
|
+
? `<span class="assigned-to">on ${item.assignedTo.length} agent${item.assignedTo.length > 1 ? 's' : ''}</span>`
|
|
891
|
+
: '';
|
|
892
|
+
const tags = (item.tags || []).slice(0,2).map(t => `<span class="tag">${t}</span>`).join('');
|
|
893
|
+
|
|
894
|
+
const verifiedBadge = item.verified
|
|
895
|
+
? `<span class="badge badge-verified">✓ verified</span>`
|
|
896
|
+
: `<span class="badge badge-external">${escHtml(item.provider || 'external')}</span>`;
|
|
897
|
+
|
|
898
|
+
const installedBadge = item.installed ? `<span class="badge badge-installed">✓ installed</span>` : '';
|
|
899
|
+
|
|
900
|
+
const actionBtn = item.installed
|
|
901
|
+
? `<button class="btn btn-manage" onclick="openManage('${escHtml(item.id)}')">Manage</button>`
|
|
902
|
+
: `<button class="btn btn-install" id="install-${escHtml(item.id)}" onclick="installItem('${typeKey}','${escHtml(item.id)}','${escHtml(item.name)}')">+ Install</button>`;
|
|
903
|
+
|
|
904
|
+
return `
|
|
905
|
+
<div class="card${item.installed ? ' installed' : ''}">
|
|
906
|
+
<div class="card-top">
|
|
907
|
+
<div>
|
|
908
|
+
<div class="card-name">${escHtml(item.name)}</div>
|
|
909
|
+
<div class="card-provider">${escHtml(item.provider || '')}</div>
|
|
910
|
+
</div>
|
|
911
|
+
<div class="card-badge-wrap">${verifiedBadge}${installedBadge}</div>
|
|
912
|
+
</div>
|
|
913
|
+
<div class="card-desc">${escHtml(item.description || '')}</div>
|
|
914
|
+
<div class="card-meta">${tags}${assigned}</div>
|
|
915
|
+
<div class="card-actions">${actionBtn}</div>
|
|
916
|
+
</div>`;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// ── Install ────────────────────────────────────────────────────────
|
|
920
|
+
async function installItem(type, id, name) {
|
|
921
|
+
const btn = document.getElementById(`install-${id}`);
|
|
922
|
+
if (btn) { btn.textContent = 'Installing...'; btn.className = 'btn btn-installing'; btn.disabled = true; }
|
|
923
|
+
|
|
924
|
+
try {
|
|
925
|
+
const res = await fetch('/api/marketplace/install', {
|
|
926
|
+
method: 'POST',
|
|
927
|
+
headers: { 'Content-Type': 'application/json' },
|
|
928
|
+
body: JSON.stringify({ type, id }),
|
|
929
|
+
});
|
|
930
|
+
const data = await res.json();
|
|
931
|
+
|
|
932
|
+
if (!res.ok || !data.ok) {
|
|
933
|
+
toast(`Install failed: ${data.error || 'unknown error'}`, 'error');
|
|
934
|
+
if (btn) { btn.textContent = '+ Install'; btn.className = 'btn btn-install'; btn.disabled = false; }
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Update local state
|
|
939
|
+
const item = allItems.find(i => i.id === id);
|
|
940
|
+
if (item) item.installed = true;
|
|
941
|
+
|
|
942
|
+
toast(`✓ ${name} installed`, 'success');
|
|
943
|
+
render();
|
|
944
|
+
openAssignModal(type, id, name, false);
|
|
945
|
+
|
|
946
|
+
} catch (err) {
|
|
947
|
+
toast(`Install error: ${err.message}`, 'error');
|
|
948
|
+
if (btn) { btn.textContent = '+ Install'; btn.className = 'btn btn-install'; btn.disabled = false; }
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// ── Assign modal ───────────────────────────────────────────────────
|
|
953
|
+
function openAssignModal(type, id, name, isManage) {
|
|
954
|
+
pendingInstall = { type, id, name };
|
|
955
|
+
|
|
956
|
+
document.getElementById('modalTitle').textContent = isManage
|
|
957
|
+
? `Manage — ${name}`
|
|
958
|
+
: `✓ ${name} installed`;
|
|
959
|
+
document.getElementById('modalSubtitle').textContent = isManage
|
|
960
|
+
? 'Update which agents have this assigned.'
|
|
961
|
+
: 'Assign to agents now? (optional)';
|
|
962
|
+
|
|
963
|
+
const item = allItems.find(i => i.id === id);
|
|
964
|
+
const alreadyAssigned = item?.assignedTo || [];
|
|
965
|
+
|
|
966
|
+
// Build agent checklist
|
|
967
|
+
const listEl = document.getElementById('agentList');
|
|
968
|
+
listEl.innerHTML = agents.map(a => `
|
|
969
|
+
<label class="agent-check">
|
|
970
|
+
<input type="checkbox" value="${escHtml(a.id)}" ${alreadyAssigned.includes(a.id) ? 'checked' : ''}>
|
|
971
|
+
<div>
|
|
972
|
+
<div class="agent-check-name">${escHtml(a.name || a.id)}</div>
|
|
973
|
+
<div class="agent-check-id">${escHtml(a.id)}</div>
|
|
974
|
+
</div>
|
|
975
|
+
</label>`).join('');
|
|
976
|
+
|
|
977
|
+
document.getElementById('modalWarning').style.display = 'none';
|
|
978
|
+
document.getElementById('assignModal').classList.add('open');
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function openManage(id) {
|
|
982
|
+
const item = allItems.find(i => i.id === id);
|
|
983
|
+
if (!item) return;
|
|
984
|
+
const typeKey = currentType === 'mcps' ? 'mcp' : currentType === 'skills' ? 'skill' : 'agent';
|
|
985
|
+
openAssignModal(typeKey, id, item.name, true);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function closeModal() {
|
|
989
|
+
document.getElementById('assignModal').classList.remove('open');
|
|
990
|
+
pendingInstall = null;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async function submitAssign() {
|
|
994
|
+
if (!pendingInstall) return;
|
|
995
|
+
const { type, id, name } = pendingInstall;
|
|
996
|
+
|
|
997
|
+
const checked = [...document.querySelectorAll('#agentList input[type=checkbox]:checked')]
|
|
998
|
+
.map(el => el.value);
|
|
999
|
+
|
|
1000
|
+
if (checked.length === 0) { closeModal(); return; }
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
const res = await fetch('/api/marketplace/assign', {
|
|
1004
|
+
method: 'POST',
|
|
1005
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1006
|
+
body: JSON.stringify({ type, id, agentIds: checked }),
|
|
1007
|
+
});
|
|
1008
|
+
const data = await res.json();
|
|
1009
|
+
|
|
1010
|
+
if (!res.ok || !data.ok) {
|
|
1011
|
+
toast(`Assign failed: ${data.error || 'unknown'}`, 'error');
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Update local assigned state
|
|
1016
|
+
const item = allItems.find(i => i.id === id);
|
|
1017
|
+
if (item) item.assignedTo = checked;
|
|
1018
|
+
|
|
1019
|
+
if (data.missingKeys?.length > 0) {
|
|
1020
|
+
document.getElementById('modalWarning').style.display = 'block';
|
|
1021
|
+
document.getElementById('modalWarning').textContent =
|
|
1022
|
+
`⚠ API key needed for: ${data.missingKeys.join(', ')} — add key in agent config to activate.`;
|
|
1023
|
+
render();
|
|
1024
|
+
return; // Keep modal open to show warning
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
toast(`✓ ${name} assigned to ${checked.length} agent${checked.length > 1 ? 's' : ''}`, 'success');
|
|
1028
|
+
render();
|
|
1029
|
+
closeModal();
|
|
1030
|
+
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
toast(`Assign error: ${err.message}`, 'error');
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Close modal on backdrop click
|
|
1037
|
+
document.getElementById('assignModal').addEventListener('click', function(e) {
|
|
1038
|
+
if (e.target === this) closeModal();
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// ── Theme ──────────────────────────────────────────────────────────
|
|
1042
|
+
function toggleTheme() {
|
|
1043
|
+
const current = document.documentElement.getAttribute('data-theme');
|
|
1044
|
+
const next = current === 'light' ? 'dark' : 'light';
|
|
1045
|
+
if (next === 'dark') document.documentElement.removeAttribute('data-theme');
|
|
1046
|
+
else document.documentElement.setAttribute('data-theme', 'light');
|
|
1047
|
+
localStorage.setItem('theme', next === 'dark' ? '' : 'light');
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// ── Toast ──────────────────────────────────────────────────────────
|
|
1051
|
+
let toastTimer;
|
|
1052
|
+
function toast(msg, type = 'success') {
|
|
1053
|
+
const el = document.getElementById('toast');
|
|
1054
|
+
el.textContent = msg;
|
|
1055
|
+
el.className = `toast toast-${type} show`;
|
|
1056
|
+
clearTimeout(toastTimer);
|
|
1057
|
+
toastTimer = setTimeout(() => el.classList.remove('show'), 3500);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// ── Util ───────────────────────────────────────────────────────────
|
|
1061
|
+
function escHtml(s) {
|
|
1062
|
+
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
init();
|
|
1066
|
+
</script>
|
|
1067
|
+
</body>
|
|
1068
|
+
</html>
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
- [ ] **Step 2: Verify the file was written correctly**
|
|
1072
|
+
|
|
1073
|
+
```bash
|
|
1074
|
+
wc -l ~/Desktop/APPs/channelToAgentToClaude/public/marketplace.html
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
Expected: 250+ lines
|
|
1078
|
+
|
|
1079
|
+
- [ ] **Step 3: Restart service and open the page**
|
|
1080
|
+
|
|
1081
|
+
```bash
|
|
1082
|
+
launchctl kickstart -k gui/$(id -u)/com.agenticledger.channelToAgentToClaude
|
|
1083
|
+
sleep 3
|
|
1084
|
+
open http://localhost:4888/marketplace
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
Expected: Marketplace page loads with MCPs tab showing cards from registry.
|
|
1088
|
+
|
|
1089
|
+
- [ ] **Step 4: Commit frontend**
|
|
1090
|
+
|
|
1091
|
+
```bash
|
|
1092
|
+
git add public/marketplace.html
|
|
1093
|
+
git commit -m "feat: add marketplace.html — browse, install, assign MCPs/skills/agents"
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
---
|
|
1097
|
+
|
|
1098
|
+
## Task 4: Add Marketplace Nav Link to All Existing Pages
|
|
1099
|
+
|
|
1100
|
+
**Files:**
|
|
1101
|
+
- Modify: `public/org.html`
|
|
1102
|
+
- Modify: `public/activity.html`
|
|
1103
|
+
- Modify: `public/home.html`
|
|
1104
|
+
- Modify: `public/index.html`
|
|
1105
|
+
|
|
1106
|
+
Each page has its own nav pattern. Match the existing style exactly.
|
|
1107
|
+
|
|
1108
|
+
- [ ] **Step 1: org.html — add Marketplace tab**
|
|
1109
|
+
|
|
1110
|
+
In `public/org.html`, find the `tab-group` nav section. It looks like:
|
|
1111
|
+
```html
|
|
1112
|
+
<a href="/" class="tab-btn">Home</a>
|
|
1113
|
+
```
|
|
1114
|
+
Add `<a href="/marketplace" class="tab-btn">Marketplace</a>` after the Dashboard/Org link.
|
|
1115
|
+
|
|
1116
|
+
- [ ] **Step 2: activity.html — add Marketplace nav link**
|
|
1117
|
+
|
|
1118
|
+
In `public/activity.html`, find the nav links section (uses `.nav-link` class). Add:
|
|
1119
|
+
```html
|
|
1120
|
+
<a href="/marketplace" class="nav-link">Marketplace</a>
|
|
1121
|
+
```
|
|
1122
|
+
alongside the other nav links.
|
|
1123
|
+
|
|
1124
|
+
- [ ] **Step 3: home.html — add Marketplace nav link**
|
|
1125
|
+
|
|
1126
|
+
Find the nav links in home.html and add the Marketplace link using the same class/style as other nav links on that page.
|
|
1127
|
+
|
|
1128
|
+
- [ ] **Step 4: index.html — add Marketplace nav link**
|
|
1129
|
+
|
|
1130
|
+
Find the nav links in index.html and add the Marketplace link using the same class/style.
|
|
1131
|
+
|
|
1132
|
+
- [ ] **Step 5: Verify nav links appear on all pages**
|
|
1133
|
+
|
|
1134
|
+
Open each page and confirm "Marketplace" appears in the nav:
|
|
1135
|
+
```bash
|
|
1136
|
+
open http://localhost:4888/org
|
|
1137
|
+
open http://localhost:4888/activity
|
|
1138
|
+
open http://localhost:4888/
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
- [ ] **Step 6: Commit nav updates**
|
|
1142
|
+
|
|
1143
|
+
```bash
|
|
1144
|
+
git add public/org.html public/activity.html public/home.html public/index.html
|
|
1145
|
+
git commit -m "feat: add Marketplace nav link to all pages"
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
---
|
|
1149
|
+
|
|
1150
|
+
## Task 5: Tests
|
|
1151
|
+
|
|
1152
|
+
**Files:**
|
|
1153
|
+
- Modify: `Comprehensive Test Suite/web-ui/api.test.ts`
|
|
1154
|
+
|
|
1155
|
+
- [ ] **Step 1: Add marketplace API tests**
|
|
1156
|
+
|
|
1157
|
+
Append to `Comprehensive Test Suite/web-ui/api.test.ts`:
|
|
1158
|
+
|
|
1159
|
+
```typescript
|
|
1160
|
+
it("GET /api/marketplace/mcps returns items with installed/assignedTo fields", async () => {
|
|
1161
|
+
try {
|
|
1162
|
+
const res = await fetch(`${BASE}/api/marketplace/mcps`);
|
|
1163
|
+
if (!res.ok) return;
|
|
1164
|
+
const data = await res.json() as any;
|
|
1165
|
+
assert.ok(Array.isArray(data.items), "items should be an array");
|
|
1166
|
+
if (data.items.length > 0) {
|
|
1167
|
+
const item = data.items[0];
|
|
1168
|
+
assert.ok("id" in item, "item should have id");
|
|
1169
|
+
assert.ok("name" in item, "item should have name");
|
|
1170
|
+
assert.ok("installed" in item, "item should have installed field");
|
|
1171
|
+
assert.ok(Array.isArray(item.assignedTo), "item.assignedTo should be array");
|
|
1172
|
+
}
|
|
1173
|
+
} catch { /* service not running */ }
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
it("GET /api/marketplace/skills returns items", async () => {
|
|
1177
|
+
try {
|
|
1178
|
+
const res = await fetch(`${BASE}/api/marketplace/skills`);
|
|
1179
|
+
if (!res.ok) return;
|
|
1180
|
+
const data = await res.json() as any;
|
|
1181
|
+
assert.ok(Array.isArray(data.items));
|
|
1182
|
+
} catch { /* service not running */ }
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
it("GET /api/marketplace/agents returns items", async () => {
|
|
1186
|
+
try {
|
|
1187
|
+
const res = await fetch(`${BASE}/api/marketplace/agents`);
|
|
1188
|
+
if (!res.ok) return;
|
|
1189
|
+
const data = await res.json() as any;
|
|
1190
|
+
assert.ok(Array.isArray(data.items));
|
|
1191
|
+
} catch { /* service not running */ }
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it("GET /api/marketplace/invalid returns 400", async () => {
|
|
1195
|
+
try {
|
|
1196
|
+
const res = await fetch(`${BASE}/api/marketplace/invalid`);
|
|
1197
|
+
assert.equal(res.status, 400);
|
|
1198
|
+
} catch { /* service not running */ }
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
it("POST /api/marketplace/install rejects missing fields", async () => {
|
|
1202
|
+
try {
|
|
1203
|
+
const res = await fetch(`${BASE}/api/marketplace/install`, {
|
|
1204
|
+
method: "POST",
|
|
1205
|
+
headers: { "Content-Type": "application/json" },
|
|
1206
|
+
body: JSON.stringify({ type: "skill" }), // missing id
|
|
1207
|
+
});
|
|
1208
|
+
assert.equal(res.status, 400);
|
|
1209
|
+
} catch { /* service not running */ }
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
it("POST /api/marketplace/assign rejects missing fields", async () => {
|
|
1213
|
+
try {
|
|
1214
|
+
const res = await fetch(`${BASE}/api/marketplace/assign`, {
|
|
1215
|
+
method: "POST",
|
|
1216
|
+
headers: { "Content-Type": "application/json" },
|
|
1217
|
+
body: JSON.stringify({ type: "skill", id: "sop_pptx" }), // missing agentIds
|
|
1218
|
+
});
|
|
1219
|
+
assert.equal(res.status, 400);
|
|
1220
|
+
} catch { /* service not running */ }
|
|
1221
|
+
});
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
- [ ] **Step 2: Run the test suite**
|
|
1225
|
+
|
|
1226
|
+
```bash
|
|
1227
|
+
cd ~/Desktop/APPs/channelToAgentToClaude && node "Comprehensive Test Suite/run-all-tests.js" 2>&1
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
Expected: all tests pass (marketplace tests pass or skip if service not running).
|
|
1231
|
+
|
|
1232
|
+
- [ ] **Step 3: Commit tests**
|
|
1233
|
+
|
|
1234
|
+
```bash
|
|
1235
|
+
git add "Comprehensive Test Suite/web-ui/api.test.ts"
|
|
1236
|
+
git commit -m "test: add marketplace API endpoint tests"
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
---
|
|
1240
|
+
|
|
1241
|
+
## Task 6: Final Build, Restart, and Push
|
|
1242
|
+
|
|
1243
|
+
- [ ] **Step 1: Final build**
|
|
1244
|
+
|
|
1245
|
+
```bash
|
|
1246
|
+
cd ~/Desktop/APPs/channelToAgentToClaude && npm run build 2>&1
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
Expected: clean build.
|
|
1250
|
+
|
|
1251
|
+
- [ ] **Step 2: Restart service**
|
|
1252
|
+
|
|
1253
|
+
```bash
|
|
1254
|
+
launchctl kickstart -k gui/$(id -u)/com.agenticledger.channelToAgentToClaude
|
|
1255
|
+
sleep 3 && tail -5 logs/service.log
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
Expected: service running line visible.
|
|
1259
|
+
|
|
1260
|
+
- [ ] **Step 3: Smoke test the full flow**
|
|
1261
|
+
|
|
1262
|
+
```bash
|
|
1263
|
+
# Marketplace page loads
|
|
1264
|
+
curl -s http://localhost:4888/marketplace | grep -c "Marketplace"
|
|
1265
|
+
|
|
1266
|
+
# MCPs endpoint returns items
|
|
1267
|
+
curl -s http://localhost:4888/api/marketplace/mcps | node -e "const d=require('fs').readFileSync('/dev/stdin','utf-8');const j=JSON.parse(d);console.log('items:',j.items?.length)"
|
|
1268
|
+
|
|
1269
|
+
# Skills endpoint returns items
|
|
1270
|
+
curl -s http://localhost:4888/api/marketplace/skills | node -e "const d=require('fs').readFileSync('/dev/stdin','utf-8');const j=JSON.parse(d);console.log('skills:',j.items?.length)"
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
Expected: `items: 47` for MCPs, skills count > 0.
|
|
1274
|
+
|
|
1275
|
+
- [ ] **Step 4: Push to both repos**
|
|
1276
|
+
|
|
1277
|
+
```bash
|
|
1278
|
+
cd ~/Desktop/APPs/channelToAgentToClaude
|
|
1279
|
+
git push origin main
|
|
1280
|
+
git push client main
|
|
1281
|
+
```
|