{"id":"2c801444-b683-4219-a006-aacead383e39","shortId":"rqxMtR","kind":"skill","title":"sap-extension-creator","tagline":"Create Super Agent Party (SAP) extensions. This skill should be used when users want to create, build, or scaffold a new extension for Super Agent Party - including static HTML extensions (pure frontend) and Node.js backend extensions. Triggers on requests like \"create a new SAP ","description":"# SAP Extension Creator\n\n## Overview\n\nCreate Super Agent Party extensions—self-contained packages that extend the platform with custom chat UI and tools. Two modes are supported:\n\n- **Static extension**: Pure HTML/CSS/JS frontend, served directly by SAP from the extension folder\n- **Node.js extension**: Full-stack with Express backend, auto-managed by SAP (`npm install` + `node index.js <port>`)\n\nBoth modes support MCP tool registration (the `register_node_extension_mcp` protocol message works for ANY extension via WebSocket, despite the \"node\" in its name).\n\n## Quick Decision Tree\n\n```\nUser wants to create an extension?\n├─ Only needs UI (chat, display, simple interactions)? → Static Extension\n└─ Needs backend logic (API calls, DB, file processing)? → Node.js Extension\n```\n\n## Core Files Every Extension Needs\n\n| File | Required | Purpose |\n|------|----------|---------|\n| `package.json` | ✅ | Metadata, dependencies, window config |\n| `index.html` | ✅ | Main UI (full HTML page, single-file app) |\n| `index.js` | Node only | Node.js entry point |\n| `node_modules/` | Node only | Auto-installed by SAP via `npm install` |\n\n## Workflow\n\n### Step 1: Gather Requirements\n\nAsk the user:\n\n1. **Extension name?** (hyphen-case, e.g., `my-weather-widget`)\n2. **Description?** (one sentence)\n3. **Static or Node.js?** (Node.js only if backend logic/server-side code is needed)\n4. **For Node.js: what npm dependencies?**\n5. **Should it register custom tools for the AI?** (works in both static and Node.js modes via WebSocket MCP)\n6. **GitHub repository URL?** (optional, for updates)\n7. **Transparent window?** (frameless, always-on-top — for mini widgets like music controllers)\n8. **Default window size?** (width/height in pixels)\n\n### Step 2: Scaffold the Extension\n\nUse the templates in `assets/` as starting points:\n\n- **Static**: Copy `assets/static-template/`\n- **Node.js**: Copy `assets/node-template/`\n\nCreate the extension directory under the workspace (user will later install it into SAP's `extensions/` folder).\n\n### Step 3: Write package.json\n\nSee `references/package-json-spec.md` for the complete field reference. Minimum:\n\n```json\n{\n  \"name\": \"my-extension\",\n  \"version\": \"1.0.0\",\n  \"description\": \"What it does\",\n  \"author\": \"your-name\",\n  \"repository\": \"https://github.com/user/repo\",\n  \"backupRepository\": \"https://gitee.com/user/repo\",\n  \"category\": \"Tools\"\n}\n```\n\nFor Node.js extensions, also include:\n```json\n{\n  \"main\": \"index.js\",\n  \"nodePort\": 0,\n  \"dependencies\": { \"express\": \"^5.1.0\" }\n}\n```\n\nFor transparent/frameless widgets (e.g., mini music controllers, floating panels):\n```json\n{\n  \"transparent\": true,\n  \"width\": 280,\n  \"height\": 80\n}\n```\n\nWhen `transparent: true`, SAP creates a frameless, transparent, always-on-top window (see main.js `open-extension-window` handler). Use this for compact overlay widgets.\n\n### Step 4: Write index.html\n\nThe HTML page is rendered inside an Electron BrowserWindow (either directly or via an iframe). Key patterns:\n\n- **Self-contained**: The extension is a single HTML file with all CSS/JS inlined or loaded from CDN. For Node.js extensions, static assets are served from the extension directory.\n- **Font Awesome**: Use CDN to ensure reliable loading in both static and Node.js modes:\n  ```html\n  <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n  ```\n  Avoid relative paths like `../../fontawesome/` — these may work for static extensions but break for Node.js extensions (different serving paths).\n- **Dark/Light mode**: Always support both (see \"Theme & i18n\" section below).\n- **i18n (Chinese/English)**: Always support bilingual UI (see \"Theme & i18n\" section below).\n- **WebSocket connection**: Connect to `ws://host/ws` for messaging and MCP.\n- **Extension ID**: Parse `window.location.pathname` for `/extensions/{ext_id}/`.\n- **Message rendering**: Listen for `messages_update` and `broadcast_messages` events.\n- **Send user input**: Send `set_user_input` then `trigger_send_message`.\n\n### Step 5: Write index.js (Node.js only)\n\nSee `references/node-entry-spec.md` for the full protocol. The entry point:\n\n1. Receives a port number via `process.argv[2]`\n2. Starts an Express server on that port at `127.0.0.1`\n3. Serves static files from its own directory\n4. Exposes a `/health` endpoint for readiness checks\n5. SAP reverse-proxies requests to the extension\n\n### Step 6: Implement Tool Registration (optional, works in both modes)\n\nExtensions can register tools that the AI agent can call — via WebSocket in the frontend (both static and Node.js). The MCP lifecycle has three mandatory stages:\n\n```\nSTARTUP  → ws.onopen         → registerMcpTools()\nRUNTIME  → ws.onmessage      → handleMcpCall() when AI calls a tool\nSHUTDOWN → window.beforeunload → unregisterMcpTools()\n```\n\n**① Register on startup** — always in `ws.onopen`, using a dedicated function:\n\n```js\nfunction registerMcpTools() {\n  getExtId();\n  ws.send(JSON.stringify({\n    type: 'register_node_extension_mcp',\n    data: {\n      ext_id: MY_EXT_ID,\n      tools: [{\n        name: `${MY_EXT_ID}_my_tool`,\n        description: 'What this tool does (use the user\\'s language)',\n        parameters: {\n          type: 'object',\n          properties: {\n            param1: { type: 'string', description: '...' }\n          },\n          required: ['param1']\n        }\n      }]\n    }\n  }));\n}\n```\n\n**② Handle tool calls** — the AI agent calls your tool:\n\n```js\nasync function handleMcpCall(data) {\n  const { ext_id, tool_name, tool_params, call_id } = data;\n  if (ext_id !== MY_EXT_ID && !tool_name.includes(MY_EXT_ID)) return;\n  // ... execute logic, then:\n  ws.send(JSON.stringify({\n    type: 'mcp_tool_result',\n    data: { call_id, result: 'output' }\n  }));\n}\n```\n\n**③ Unregister on shutdown** — MUST send `unregister_node_extension_mcp` before the window closes:\n\n```js\nfunction unregisterMcpTools() {\n  if (ws && ws.readyState === WebSocket.OPEN) {\n    ws.send(JSON.stringify({ type: 'unregister_node_extension_mcp', data: { ext_id: MY_EXT_ID } }));\n  }\n}\nwindow.addEventListener('beforeunload', () => { unregisterMcpTools(); });\n```\n\n**Key rule**: Registration and unregistration MUST be in separate named functions (`registerMcpTools` / `unregisterMcpTools`), NOT inline code. This makes the lifecycle explicit and easy for AI to understand.\n\nIf an extension has no MCP tools, all three functions can be deleted.\n\nSee `sap-lx-music/index.html` for a complete real-world MCP implementation example (static extension with 12+ registered tools).\n\n---\n\n## Theme & i18n (Dark/Light Mode + Bilingual)\n\nEvery extension should support **dark/light mode** and **Chinese/English bilingual** UI. Do NOT hardcode a single theme color scheme — use CSS variables so each extension can have its own identity.\n\n### CSS Variable Pattern\n\nDefine light theme in `:root` and override in `body.dark`:\n\n```css\n:root {\n  --bg: #ffffff;\n  --bg-secondary: #f5f5f5;\n  --text: #333333;\n  --text-sub: #888888;\n  --accent: #ec4141;        /* extension's own brand color */\n  --accent-hover: #d73a3a;\n  --border: rgba(0,0,0,0.08);\n  --transition: 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);\n  --font: -apple-system, BlinkMacSystemFont, \"SF Pro Display\", \"Helvetica Neue\", sans-serif;\n}\n\nbody.dark {\n  --bg: #2b2b2b;\n  --bg-secondary: #222222;\n  --text: #e0e0e0;\n  --text-sub: #888888;\n  --border: rgba(255,255,255,0.06);\n}\n\n* { box-sizing: border-box; margin: 0; padding: 0; }\nhtml, body {\n  height: 100%; font-family: var(--font);\n  background: var(--bg); color: var(--text);\n  transition: background var(--transition);\n}\n```\n\n### Dark Mode Toggle\n\n```js\nfunction initTheme() {\n  const saved = localStorage.getItem('myext_dark');\n  if (saved === 'dark' || (!saved && matchMedia('(prefers-color-scheme:dark)').matches)) {\n    document.body.classList.add('dark');\n  }\n}\n\nfunction toggleDarkMode() {\n  const isDark = document.body.classList.toggle('dark');\n  localStorage.setItem('myext_dark', isDark ? 'dark' : 'light');\n}\n```\n\n### i18n Pattern\n\n```js\nconst i18n = {\n  zh: {\n    welcome: '欢迎使用我的扩展',\n    send: '发送',\n    // ... all UI strings\n  },\n  en: {\n    welcome: 'Welcome to My Extension',\n    send: 'Send',\n    // ...\n  }\n};\n\nlet lang = localStorage.getItem('myext_lang') || 'zh';\nfunction t(k) { return i18n[lang]?.[k] || i18n.zh[k] || k; }\n\nfunction toggleLanguage() {\n  lang = lang === 'zh' ? 'en' : 'zh';\n  localStorage.setItem('myext_lang', lang);\n  updateAllTexts();  // re-render all i18n-dependent UI\n}\n```\n\nWhen registering MCP tools, set `description` and `parameters` in the current user's language for better AI interaction.\n\n---\n\n## Responsive Design\n\nEvery extension should work well across different window sizes. Critical patterns:\n\n### Viewport Meta (REQUIRED)\n\n```html\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n```\n\n### CSS Media Queries\n\nUse breakpoints to adapt layout at small sizes:\n\n```css\n@media (max-width: 900px) {\n  /* stack layouts vertically, reduce padding */\n}\n\n@media (max-width: 600px) {\n  /* hide secondary elements, compact controls */\n}\n```\n\nKey responsive practices:\n- Use `vw` units for widths as fallback (e.g., `width: 65vw; max-width: 360px`)\n- Use `flex` layouts with `flex-wrap` that naturally adapt\n- Hide non-essential elements on small screens (`display: none`)\n- Reduce font sizes and padding at breakpoints\n\n---\n\n## iframe Compatibility\n\nExtensions may be rendered inside an iframe (depending on SAP's configuration). Ensure:\n\n- **Extension ID detection**: Use `window.location.pathname` (works in both direct and iframe contexts):\n  ```js\n  function getExtId() {\n    try {\n      const match = window.location.pathname.match(/\\/extensions\\/([^\\/]+)/);\n      return match ? match[1] : 'unknown';\n    } catch(e) { return 'unknown'; }\n  }\n  ```\n- **WebSocket connection**: Use `location.host` (not hardcoded):\n  ```js\n  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';\n  ws = new WebSocket(`${proto}//${location.host}/ws`);\n  ```\n- **Window close**: `window.close()` works in both direct and iframe contexts\n- **Avoid `window.top` / `window.parent` assumptions** — your extension may be the top-level window\n- **Font Awesome via CDN** ensures icons load regardless of serving path\n\n---\n\n## Transparent Window / Compact Mode\n\nWhen `transparent: true` is set in package.json, SAP creates a frameless transparent window. The extension must implement **compact mode** to work correctly.\n\n### How SAP Creates Transparent Windows\n\nFrom `main.js`, when `extension.transparent` is true:\n\n```js\n{\n  frame: false,\n  transparent: true,\n  alwaysOnTop: true,\n  skipTaskbar: false,\n  hasShadow: false,\n  backgroundColor: 'rgba(0, 0, 0, 0)',\n}\n```\n\n### Compact Mode CSS (REQUIRED for transparent extensions)\n\n```css\n/* Transparent backgrounds */\nbody.compact { background: transparent !important; }\nhtml.compact { background: transparent !important; }\n\n/* Drag regions — make structural elements draggable for frameless windows */\nbody.compact header,\nbody.compact footer,\nbody.compact #inputBar {\n  -webkit-app-region: drag;\n}\n\n/* Interactive elements MUST opt-out of drag */\nbody.compact button,\nbody.compact input,\nbody.compact textarea,\nbody.compact select,\nbody.compact a,\nbody.compact .compact-close-btn {\n  -webkit-app-region: no-drag;\n}\n\n/* Compact close button (red circle, top-right) */\n.compact-close-btn { display: none; }\nbody.compact .compact-close-btn {\n  display: flex;\n  position: absolute;\n  top: 5px; right: 5px;\n  width: 20px; height: 20px;\n  background: rgb(255, 57, 57);\n  border: none; border-radius: 50%;\n  color: #fff;\n  align-items: center; justify-content: center;\n  font-size: 10px; cursor: pointer;\n  transition: 0.2s;\n  z-index: 100;\n  -webkit-app-region: no-drag;\n}\nbody.compact .compact-close-btn:hover { background: #ec4141; }\n```\n\n### Compact Mode Detection (REQUIRED)\n\n```js\nfunction checkCompactMode() {\n  if (window.innerHeight < 200) {\n    document.documentElement.classList.add('compact');\n    document.body.classList.add('compact');\n  } else {\n    document.documentElement.classList.remove('compact');\n    document.body.classList.remove('compact');\n  }\n}\n\nfunction closeWindow() { window.close(); }\n\ncheckCompactMode();\nwindow.addEventListener('resize', checkCompactMode);\n```\n\n### Placing the Close Button\n\nThe close button HTML must be placed at the body level (not nested inside containers), typically right after `<body>`:\n\n```html\n<body>\n  <button class=\"compact-close-btn\" onclick=\"closeWindow()\" title=\"关闭窗口\">\n    <i class=\"fa-solid fa-xmark\"></i>\n  </button>\n  <!-- rest of content -->\n</body>\n```\n\nFor transparent mini-widgets, you can also place the close button inside a content container and make it visible on hover — see `sap-lx-music` for this pattern.\n\n---\n\n## Using iframes for Custom URL Schemes\n\nIf your extension needs to invoke custom protocol URLs (e.g., `lxmusic://`, `myapp://`), use a hidden iframe technique:\n\n```js\nfunction invokeScheme(url) {\n  let iframe = document.getElementById('scheme-invoker');\n  if (!iframe) {\n    iframe = document.createElement('iframe');\n    iframe.id = 'scheme-invoker';\n    iframe.style.display = 'none';\n    document.body.appendChild(iframe);\n  }\n  iframe.src = url;\n}\n```\n\nThis avoids `window.open()` popup blockers and works reliably inside Electron.\n\n---\n\n## WebSocket Protocol Reference\n\n| Message Type | Direction | Purpose |\n|---|---|---|\n| `get_messages` | → SAP | Request current message history |\n| `messages_update` | ← SAP | Message list updated |\n| `broadcast_messages` | ← SAP | Broadcast message update |\n| `set_user_input` | → SAP | Update user input text |\n| `trigger_send_message` | → SAP | Send current input as user message |\n| `trigger_clear_message` | → SAP | Clear all messages |\n| `register_node_extension_mcp` | → SAP | Register MCP tools (works for static AND Node.js) |\n| `unregister_node_extension_mcp` | → SAP | Unregister on page close |\n| `mcp_registered` | ← SAP | Confirmation of registration |\n| `call_mcp_tool` | ← SAP | AI agent calls a registered tool |\n| `mcp_tool_result` | → SAP | Return tool execution result |\n| `trigger_close_extension` | → SAP | Request extension window close |\n\n---\n\n## Simple Chat HTTP API (`/simple_chat`)\n\nSAP exposes a **stateless HTTP endpoint** `POST /simple_chat` that extensions can call for one-off AI tasks — translation, summarization, quick Q&A, code generation — **without** going through the WebSocket chat flow and **without** adding messages to the conversation history.\n\nThis is ideal when your extension needs a quick, single-turn AI call: translate text, summarize content, extract keywords, classify input, etc.\n\n### When to Use `/simple_chat` vs WebSocket\n\n| Feature | `/simple_chat` HTTP API | WebSocket (`trigger_send_message`) |\n|---|---|---|\n| Conversation history | ❌ Stateless — no history | ✅ Full chat history |\n| Messages shown in UI | ❌ Not added to chat | ✅ Rendered in message list |\n| Use case | One-off: translate, summarize, classify | Multi-turn chat, agent tasks |\n| Response format | OpenAI-compatible JSON / NDJSON stream | `messages_update` / `broadcast_messages` events |\n| Speed | Uses SAP's `fast` client config | Uses current active model provider |\n\n### Endpoint\n\n```\nPOST /simple_chat\nContent-Type: application/json\n```\n\nThe endpoint is on the same origin as the extension, so use a relative URL:\n\n```js\nconst res = await fetch('/simple_chat', { ... });\n```\n\n### Request Format\n\n```json\n{\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You are a professional translator.\" },\n    { \"role\": \"user\", \"content\": \"Translate 'Hello world' to Chinese.\" }\n  ],\n  \"stream\": false,\n  \"temperature\": 0.7\n}\n```\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `messages` | array | ✅ | Array of `{role, content}` objects (system/user/assistant) |\n| `stream` | boolean | ❌ (default `false`) | `true` for streaming, `false` for one-shot JSON response |\n| `temperature` | number | ❌ (default from settings) | 0–2, lower = more deterministic |\n\n### Non-Streaming Response (`stream: false`)\n\nReturns a standard **OpenAI-compatible ChatCompletion JSON object**:\n\n```json\n{\n  \"id\": \"chatcmpl-xxx\",\n  \"object\": \"chat.completion\",\n  \"created\": 1234567890,\n  \"model\": \"gpt-4o\",\n  \"choices\": [\n    {\n      \"index\": 0,\n      \"message\": {\n        \"role\": \"assistant\",\n        \"content\": \"你好世界\"\n      },\n      \"finish_reason\": \"stop\"\n    }\n  ],\n  \"usage\": {\n    \"prompt_tokens\": 20,\n    \"completion_tokens\": 5,\n    \"total_tokens\": 25\n  }\n}\n```\n\nAccess the result: `data.choices[0].message.content`\n\n### Streaming Response (`stream: true`)\n\nReturns **NDJSON** (one JSON object per line), matching OpenAI's streaming format. Each line contains a delta chunk:\n\n```\n{\"id\":\"chatcmpl-xxx\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n{\"id\":\"chatcmpl-xxx\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"你好\"},\"finish_reason\":null}]}\n{\"id\":\"chatcmpl-xxx\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"世界\"},\"finish_reason\":null}]}\n{\"id\":\"chatcmpl-xxx\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n```\n\n**Note**: The stream does NOT send a `[DONE]` marker. Detect completion by checking `choices[0].finish_reason`.\n\n### JavaScript Usage Examples\n\n#### Non-Streaming (Simple One-Shot Call)\n\n```js\n/**\n * Call SAP's /simple_chat for a one-off AI task.\n * @param {Array} messages - [{role, content}, ...]\n * @param {number} [temperature=0.7]\n * @returns {Promise<object>} OpenAI-compatible ChatCompletion\n */\nasync function simpleChat(messages, temperature = 0.7) {\n  const res = await fetch('/simple_chat', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ messages, stream: false, temperature })\n  });\n  if (!res.ok) {\n    const err = await res.json();\n    throw new Error(err.error?.message || `HTTP ${res.status}`);\n  }\n  return await res.json();\n}\n\n// ---------- Practical Examples ----------\n\n// Translation\nasync function translate(text, targetLang = 'Chinese') {\n  const res = await simpleChat([\n    { role: 'system', content: `You are a translator. Translate to ${targetLang}. Reply ONLY with the translation, no explanations.` },\n    { role: 'user', content: text }\n  ]);\n  return res.choices[0].message.content;\n}\n\n// Summarization\nasync function summarize(text, maxWords = 50) {\n  const res = await simpleChat([\n    { role: 'system', content: `Summarize in ≤${maxWords} words. Reply ONLY with the summary.` },\n    { role: 'user', content: text }\n  ]);\n  return res.choices[0].message.content;\n}\n\n// Quick classification\nasync function classify(text, labels) {\n  const res = await simpleChat([\n    { role: 'system', content: `Classify into one of: ${labels.join(', ')}. Reply ONLY with the label.` },\n    { role: 'user', content: text }\n  ]);\n  return res.choices[0].message.content.trim();\n}\n```\n\n#### Streaming (Real-Time Display)\n\n```js\n/**\n * Call /simple_chat with streaming. Yields delta content strings.\n * @param {Array} messages\n * @param {number} [temperature=0.7]\n * @returns {AsyncGenerator<string>} Yields delta content chunks\n */\nasync function* simpleChatStream(messages, temperature = 0.7) {\n  const res = await fetch('/simple_chat', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ messages, stream: true, temperature })\n  });\n  if (!res.ok) {\n    const err = await res.json();\n    throw new Error(err.error?.message || `HTTP ${res.status}`);\n  }\n\n  const reader = res.body.getReader();\n  const decoder = new TextDecoder();\n  let buf = '';\n\n  while (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n    buf += decoder.decode(value, { stream: true });\n    const lines = buf.split('\\n');\n    buf = lines.pop();  // keep incomplete line in buffer\n    for (const line of lines) {\n      if (!line.trim()) continue;\n      try {\n        const chunk = JSON.parse(line);\n        const content = chunk.choices?.[0]?.delta?.content;\n        if (content) yield content;\n        if (chunk.choices?.[0]?.finish_reason === 'stop') return;\n      } catch(e) { /* ignore parse errors for partial lines */ }\n    }\n  }\n}\n\n// Usage: render streaming response into an element\nconst el = document.getElementById('output');\nel.textContent = '';\nfor await (const chunk of simpleChatStream([\n  { role: 'user', content: 'Write a haiku about coding.' }\n])) {\n  el.textContent += chunk;\n}\n```\n\n### Error Handling\n\nOn error, the endpoint returns a JSON object with an `error` field:\n\n```json\n{\n  \"error\": {\n    \"message\": \"No model providers configured\",\n    \"type\": \"server_error\",\n    \"code\": 500\n  }\n}\n```\n\nAlways check `res.ok` and parse the error body.\n\n### Important Notes for `/simple_chat`\n\n- **Stateless**: Each call is independent. No conversation context is preserved between calls.\n- **No UI impact**: Results are NOT displayed in the main chat window. Your extension owns the rendering.\n- **Uses fast client**: The endpoint uses SAP's \"fast\" model provider configuration. This may be a different model than the main chat.\n- **Same origin only**: Extensions are served from the same origin, so no CORS issues. Use a relative URL (`/simple_chat`).\n- **Not a replacement for MCP tools**: If you need the AI agent to call your extension, register MCP tools via WebSocket. `/simple_chat` is for your extension to call the AI, not the other way around.\n\n---\n\n## Important Notes\n\n- **Extension ID format**: `{owner}_{repo}` (e.g., `heshengtao_sap-example`)\n- **nodePort: 0** means auto-assign a free port (3100-13999 range)\n- **Always register `beforeunload` handler** to send `unregister_node_extension_mcp`\n- **MCP works in both static and Node.js extensions** — the `register_node_extension_mcp` message type name is historical; it works over WebSocket from any extension. Always follow the three-stage lifecycle: `registerMcpTools()` on WS open, `handleMcpCall()` on tool call, `unregisterMcpTools()` on beforeunload\n- **Font Awesome**: Always use CDN (`cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css`). Relative paths like `../../fontawesome/` do NOT work for Node.js extensions (they're served from Express, not from SAP's static directory)\n- **Theme colors**: Each extension defines its own identity via CSS variables on `:root` and `body.dark`. Do NOT force SAP's theme colors\n- **Always implement dark/light mode** and **Chinese/English i18n** as basic functionality\n- **Transparent windows**: Always implement compact mode. Without `-webkit-app-region: drag`, frameless windows cannot be moved. Without `-webkit-app-region: no-drag` on interactive elements, buttons become unclickable\n- **Close button**: For transparent/frameless windows, the extension MUST provide its own close button since there's no native title bar\n\n---\n\n## Reference Implementations\n\nStudy these real extensions for patterns:\n\n- **sap-lx-music** — Static extension with MCP, transparent compact mode, dark/light theme, i18n, custom scheme invocation\n- **sap-example** (heshengtao_sap-example) — Basic static chat UI extension\n- **sap-example-with-node** (heshengtao_sap-example-with-node) — Node.js extension with Express backend\n\n## Resources\n\n### assets/\n- `assets/static-template/` — Complete starter template for static extensions\n- `assets/node-template/` — Complete starter template for Node.js extensions\n\n### references/\n- `references/package-json-spec.md` — Complete package.json field reference\n- `references/node-entry-spec.md` — Node.js entry point and lifecycle specification","tags":["sap","extension","creator","super","agent","party","heshengtao","agent-skills","ai-companion","ai-vtuber","claude-code","comfyui"],"capabilities":["skill","source-heshengtao","skill-sap-extension-creator","topic-agent-skills","topic-ai-companion","topic-ai-vtuber","topic-claude-code","topic-comfyui","topic-cowork","topic-discord-bot","topic-home-assistant","topic-im-bot","topic-livestream","topic-mcp","topic-neuro-sama"],"categories":["super-agent-party"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/heshengtao/super-agent-party/sap-extension-creator","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add heshengtao/super-agent-party","source_repo":"https://github.com/heshengtao/super-agent-party","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 2278 github stars · SKILL.md body (23,672 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:52:51.554Z","embedding":null,"createdAt":"2026-05-03T12:52:25.485Z","updatedAt":"2026-05-18T18:52:51.554Z","lastSeenAt":"2026-05-18T18:52:51.554Z","tsv":"'-13999':2649 '/../fontawesome':485,2715 '/ajax/libs/font-awesome/6.4.0/css/all.min.css':2711 '/extensions':535,1253 '/health':603 '/index.html':851 '/simple_chat':1762,1770,1829,1833,1901,1926,2155,2188,2326,2356,2521,2591,2613 '/user/repo':354,358 '/ws':1281 '0':370,940,941,942,993,995,1366,1367,1368,1369,1982,2017,2040,2072,2088,2103,2118,2137,2254,2285,2317,2434,2443,2640 '0.06':985 '0.08':943 '0.1':951 '0.2':1497 '0.25':950,952 '0.3':945 '0.7':1950,2171,2183,2339,2351 '1':202,208,574,953,1257 '1.0.0':342 '100':999,1502 '10px':1493 '12':864 '1234567890':2010 '127.0.0.1':591 '2':219,289,581,582,1983 '20':2029 '200':1527 '20px':1466,1468 '222222':973 '25':2035 '255':982,983,984,1471 '280':387 '2b2b2b':969 '3':223,325,592 '3100':2648 '333333':922 '360px':1191 '4':235,417,600 '4o':2014 '5':241,560,608,2032 '5.1.0':373 '50':1479,2262 '500':2509 '57':1472,1473 '5px':1462,1464 '6':260,618 '600px':1169 '65vw':1187 '7':267 '8':281 '80':389 '888888':926,979 '900px':1159 'absolut':1460 'accent':927,935 'accent-hov':934 'access':2036 'across':1133 'activ':1896 'ad':1797,1853 'adapt':1149,1201 'agent':7,29,55,634,726,1737,1872,2603 'ai':249,633,660,725,830,1124,1736,1779,1815,2161,2602,2621 'align':1483 'align-item':1482 'also':364,1574 'alway':272,399,502,512,670,2510,2651,2686,2706,2755,2767 'always-on-top':271,398 'alwaysontop':1358 'api':152,1761,1835 'app':181,1405,1433,1505,2774,2785 'appl':956 'apple-system':955 'application/json':1905,2195,2363 'around':2626 'array':1956,1957,2164,2334 'ask':205 'asset':297,459,2870 'assets/node-template':306,2878 'assets/static-template':303,2871 'assign':2644 'assist':2020,2075 'assumpt':1295 'async':731,2178,2221,2257,2289,2346 'asyncgener':2341 'author':347 'auto':98,193,2643 'auto-assign':2642 'auto-instal':192 'auto-manag':97 'avoid':481,1292,1644 'await':1924,2186,2206,2216,2229,2265,2296,2354,2374,2397,2469 'awesom':467,1306,2705 'backend':39,96,150,230,2868 'background':1005,1012,1379,1381,1385,1469,1516 'backgroundcolor':1364 'backuprepositori':355 'bar':2815 'basic':2763,2848 'becom':2794 'beforeunload':804,2653,2703 'better':1123 'bezier':949 'bg':915,918,968,971,1007 'bg-secondari':917,970 'bilingu':514,871,880 'blinkmacsystemfont':958 'blocker':1647 'bodi':997,1557,2196,2364,2517 'body.compact':1380,1397,1399,1401,1416,1418,1420,1422,1424,1426,1452,1510 'body.dark':912,967,2747 'boolean':1964 'border':938,980,990,1474,1477 'border-box':989 'border-radius':1476 'box':987,991 'box-siz':986 'brand':932 'break':493,2401 'breakpoint':1147,1218 'broadcast':545,1673,1676,1884 'browserwindow':428 'btn':1430,1449,1456,1514 'buf':2391,2402,2411 'buf.split':2409 'buffer':2417 'build':21 'button':1417,1440,1547,1550,1578,2793,2797,2808 'call':153,636,661,723,727,742,766,1732,1738,1774,1816,2150,2152,2325,2524,2533,2605,2619,2700 'cannot':2779 'case':213,1861 'catch':1259,2448 'categori':359 'cdn':454,469,1308,2708 'cdnjs.cloudflare.com':2710 'cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css':2709 'center':1485,1489 'chat':68,143,1759,1793,1846,1855,1871,2544,2572,2850 'chat.completion':2008 'chat.completion.chunk':2069,2085,2100,2115 'chatcmpl':2005,2066,2082,2097,2112 'chatcmpl-xxx':2004,2065,2081,2096,2111 'chatcomplet':1999,2177 'check':607,2135,2511 'checkcompactmod':1524,1540,1543 'chines':1946,2226 'chinese/english':511,879,2760 'choic':2015,2070,2086,2101,2116,2136 'chunk':2063,2345,2428,2471,2483 'chunk.choices':2433,2442 'circl':1442 'classif':2288 'classifi':1823,1867,2291,2301 'clear':1698,1701 'client':1892,2553 'close':782,1283,1429,1439,1448,1455,1513,1546,1549,1577,1725,1751,1757,2796,2807 'closewindow':1538 'code':232,821,1786,2481,2508 'color':888,933,1008,1033,1480,2734,2754 'compact':413,1173,1318,1337,1370,1428,1438,1447,1454,1512,1518,1529,1531,1534,1536,2769,2833 'compact-close-btn':1427,1446,1453,1511 'compat':1220,1878,1998,2176 'complet':332,854,2030,2133,2872,2879,2887 'config':171,1893 'configur':1232,2504,2562 'confirm':1729 'connect':522,523,1264 'const':735,1021,1041,1054,1250,1270,1922,2184,2204,2227,2263,2294,2352,2372,2383,2386,2394,2407,2419,2427,2431,2463,2470 'contain':60,439,1562,1582,2060 'content':1488,1581,1820,1903,1933,1941,1960,2021,2076,2090,2105,2167,2193,2233,2250,2269,2281,2300,2313,2331,2344,2361,2432,2436,2438,2440,2476 'content-typ':1902,2192,2360 'context':1245,1291,2529 'continu':2425 'control':280,380,1174 'convers':1801,1840,2528 'copi':302,305 'cor':2585 'core':159 'correct':1341 'creat':5,20,45,53,137,307,394,1328,1344,2009 'creator':4,51 'critic':1137 'css':891,901,913,1143,1154,1372,1377,2742 'css/js':449 'cubic':948 'cubic-bezi':947 'current':1118,1664,1692,1895 'cursor':1494 'custom':67,245,1600,1609,2838 'd73a3a':937 'dark':1015,1025,1028,1035,1038,1044,1047,1049 'dark/light':500,869,876,2757,2835 'data':688,734,744,765,797 'data.choices':2039 'db':154 'decis':132 'decod':2387 'decoder.decode':2403 'dedic':675 'default':282,1965,1979 'defin':904,2737 'delet':845 'delta':2062,2073,2089,2104,2119,2330,2343,2435 'depend':169,240,371,1106,1228 'descript':220,343,701,718,1113,1954 'design':1127 'despit':125 'detect':1236,1520,2132 'determinist':1986 'differ':497,1134,2567 'direct':82,430,1242,1288,1658 'directori':310,465,599,2732 'display':144,961,1210,1450,1457,2323,2540 'document.body.appendchild':1639 'document.body.classlist.add':1037,1530 'document.body.classlist.remove':1535 'document.body.classlist.toggle':1043 'document.createelement':1631 'document.documentelement.classlist.add':1528 'document.documentelement.classlist.remove':1533 'document.getelementbyid':1624,2465 'done':2130,2395,2400 'drag':1388,1407,1415,1437,1509,2776,2789 'draggabl':1393 'e':1260,2449 'e.g':214,377,1185,1612,2634 'e0e0e0':975 'easi':828 'ec4141':928,1517 'either':429 'el':2464 'el.textcontent':2467,2482 'electron':427,1652 'element':1172,1206,1392,1409,2462,2792 'els':1532 'en':1064,1093 'endpoint':604,1768,1899,1907,2489,2555 'ensur':471,1233,1309 'entri':186,572,2893 'err':2205,2373 'err.error':2211,2379 'error':2210,2378,2452,2484,2487,2496,2499,2507,2516 'essenti':1205 'etc':1825 'event':547,1886 'everi':161,872,1128 'exampl':860,2142,2219,2638,2843,2847,2855,2861 'execut':756,1748 'explan':2247 'explicit':826 'expos':601,1764 'express':95,372,585,2726,2867 'ext':536,689,692,697,736,746,749,753,798,801 'extend':63 'extens':3,10,26,34,40,50,57,77,87,90,115,122,139,148,158,162,209,292,309,322,340,363,407,441,457,464,491,496,530,616,627,686,777,795,835,862,873,895,929,1069,1129,1221,1234,1297,1334,1376,1605,1706,1719,1752,1755,1772,1808,1915,2547,2576,2607,2617,2629,2659,2668,2672,2685,2721,2736,2802,2821,2829,2852,2865,2877,2884 'extension.transparent':1350 'extract':1821 'f5f5f5':920 'fallback':1184 'fals':1355,1361,1363,1948,1966,1970,1992,2200 'famili':1002 'fast':1891,2552,2559 'featur':1832 'fetch':1925,2187,2355 'fff':1481 'ffffff':916 'field':333,1951,2497,2889 'file':155,160,164,180,446,595 'finish':2023,2077,2092,2107,2120,2138,2444 'flex':1193,1197,1458 'flex-wrap':1196 'float':381 'flow':1794 'folder':88,323 'follow':2687 'font':466,954,1001,1004,1213,1305,1491,2704 'font-famili':1000 'font-siz':1490 'footer':1400 'forc':2750 'format':1875,1928,2057,2631 'frame':1354 'frameless':270,396,1330,1395,2777 'free':2646 'frontend':36,80,641 'full':92,175,569,1845 'full-stack':91 'function':676,678,732,784,816,842,1019,1039,1078,1088,1247,1523,1537,1619,2179,2222,2258,2290,2347,2764 'gather':203 'generat':1787 'get':1660 'getextid':680,1248 'gitee.com':357 'gitee.com/user/repo':356 'github':261 'github.com':353 'github.com/user/repo':352 'go':1789 'gpt':2013 'gpt-4o':2012 'haiku':2479 'handl':721,2485 'handlemcpcal':658,733,2697 'handler':409,2654 'hardcod':884,1268 'hasshadow':1362 'header':1398,2191,2359 'height':388,998,1467 'hello':1943 'helvetica':962 'heshengtao':2635,2844,2858 'hidden':1615 'hide':1170,1202 'histor':2678 'histori':1666,1802,1841,1844,1847 'host/ws':525 'hover':936,1515,1588 'html':33,176,421,445,480,996,1142,1551,1566 'html.compact':1384 'html/css/js':79 'http':1760,1767,1834,2213,2381 'https':1273 'hyphen':212 'hyphen-cas':211 'i18n':507,510,518,868,1051,1055,1082,1105,2761,2837 'i18n-dependent':1104 'i18n.zh':1085 'icon':1310 'id':531,537,690,693,698,737,743,747,750,754,767,799,802,1235,2003,2064,2080,2095,2110,2630 'ideal':1805 'ident':900,2740 'ifram':434,1219,1227,1244,1290,1598,1616,1623,1629,1630,1632,1640 'iframe.id':1633 'iframe.src':1641 'iframe.style.display':1637 'ignor':2450 'impact':2536 'implement':619,859,1336,2756,2768,2817 'import':1383,1387,2518,2627 'includ':31,365 'incomplet':2414 'independ':2526 'index':1501,2016,2071,2087,2102,2117 'index.html':172,419 'index.js':105,182,368,562 'initthem':1020 'inlin':450,820 'input':550,554,1419,1681,1685,1693,1824 'inputbar':1402 'insid':425,1225,1561,1579,1651 'instal':103,194,199,317 'interact':146,1125,1408,2791 'invoc':2840 'invok':1608,1627,1636 'invokeschem':1620 'isdark':1042,1048 'issu':2586 'item':1484 'javascript':2140 'js':677,730,783,1018,1053,1246,1269,1353,1522,1618,1921,2151,2324 'json':336,366,383,1879,1929,1975,2000,2002,2049,2492,2498 'json.parse':2429 'json.stringify':682,760,791,2197,2365 'justifi':1487 'justify-cont':1486 'k':1080,1084,1086,1087 'keep':2413 'key':435,806,1175 'keyword':1822 'label':2293,2310 'labels.join':2305 'lang':1073,1076,1083,1090,1091,1097,1098 'languag':710,1121 'later':316 'layout':1150,1161,1194 'let':1072,1622,2390 'level':1303,1558 'lifecycl':648,825,2692,2896 'light':905,1050 'like':44,278,484,2714 'line':2052,2059,2408,2415,2420,2422,2430,2455 'line.trim':2424 'lines.pop':2412 'list':1671,1859 'listen':540 'load':452,473,1311 'localstorage.getitem':1023,1074 'localstorage.setitem':1045,1095 'location.host':1266,1280 'location.protocol':1272 'logic':151,757 'logic/server-side':231 'lower':1984 'lx':849,1592,2826 'main':173,367,2543,2571 'main.js':404,1348 'make':823,1390,1584 'manag':99 'mandatori':651 'margin':992 'marker':2131 'match':1036,1251,1255,1256,2053 'matchmedia':1030 'max':1157,1167,1189 'max-width':1156,1166,1188 'maxword':2261,2272 'may':487,1222,1298,2564 'mcp':109,116,259,529,647,687,762,778,796,838,858,1110,1707,1710,1720,1726,1733,1742,2596,2609,2660,2661,2673,2831 'mean':2641 'media':1144,1155,1165 'messag':118,527,538,542,546,558,1656,1661,1665,1667,1670,1674,1677,1689,1696,1699,1703,1798,1839,1848,1858,1882,1885,1930,1955,2018,2165,2181,2198,2212,2335,2349,2366,2380,2500,2674 'message.content':2041,2255,2286 'message.content.trim':2318 'meta':1140 'metadata':168 'method':2189,2357 'mini':276,378,1570 'mini-widget':1569 'minimum':335 'mode':73,107,256,479,501,626,870,877,1016,1319,1338,1371,1519,2758,2770,2834 'model':1897,2011,2502,2560,2568 'modul':189 'move':2781 'multi':1869 'multi-turn':1868 'music':279,379,850,1593,2827 'must':773,811,1335,1410,1552,2803 'my-extens':338 'my-weather-widget':215 'myext':1024,1046,1075,1096 'n':2410 'name':130,210,337,350,695,739,815,2676 'nativ':2813 'natur':1200 'ndjson':1880,2047 'need':141,149,163,234,1606,1809,2600 'nest':1560 'neue':963 'new':25,47,1277,2209,2377,2388 'no-drag':1435,1507,2787 'node':104,114,127,183,188,190,685,776,794,1705,1718,2658,2671,2857,2863 'node.js':38,89,157,185,226,227,237,255,304,362,456,478,495,563,645,1716,2667,2720,2864,2883,2892 'nodeport':369,2639 'non':1204,1988,2144 'non-essenti':1203 'non-stream':1987,2143 'none':1211,1451,1475,1638 'note':2123,2519,2628 'npm':102,198,239 'null':2079,2094,2109 'number':578,1978,2169,2337 'object':713,1961,2001,2007,2050,2068,2084,2099,2114,2493 'one':221,1777,1863,1973,2048,2148,2159,2303 'one-off':1776,1862,2158 'one-shot':1972,2147 'open':406,2696 'open-extension-window':405 'openai':1877,1997,2054,2175 'openai-compat':1876,1996,2174 'opt':1412 'opt-out':1411 'option':264,622 'origin':1912,2574,2582 'output':769,2466 'overlay':414 'overrid':910 'overview':52 'own':2548 'owner':2632 'packag':61 'package.json':167,327,1326,2888 'pad':994,1164,1216 'page':177,422,1724 'panel':382 'param':741,2163,2168,2333,2336 'param1':715,720 'paramet':711,1115 'pars':532,2451,2514 'parti':8,30,56 'partial':2454 'path':483,499,1315,2713 'pattern':436,903,1052,1138,1596,2823 'per':2051 'pixel':287 'place':1544,1554,1575 'platform':65 'point':187,300,573,2894 'pointer':1495 'popup':1646 'port':577,589,2647 'posit':1459 'post':1769,1900,2190,2358 'practic':1177,2218 'prefer':1032 'prefers-color-schem':1031 'preserv':2531 'pro':960 'process':156 'process.argv':580 'profession':1937 'promis':2173 'prompt':2027 'properti':714 'proto':1271,1279 'protocol':117,570,1610,1654 'provid':1898,2503,2561,2804 'proxi':612 'pure':35,78 'purpos':166,1659 'q':1784 'queri':1145 'quick':131,1783,1811,2287 'radius':1478 'rang':2650 're':1101,2723 're-rend':1100 'reader':2384 'reader.read':2398 'readi':606 'real':856,2321,2820 'real-tim':2320 'real-world':855 'reason':2024,2078,2093,2108,2121,2139,2445 'receiv':575 'red':1441 'reduc':1163,1212 'refer':334,1655,2816,2885,2890 'references/node-entry-spec.md':566,2891 'references/package-json-spec.md':329,2886 'regardless':1312 'region':1389,1406,1434,1506,2775,2786 'regist':113,244,629,667,684,865,1109,1704,1709,1727,1740,2608,2652,2670 'registermcptool':655,679,817,2693 'registr':111,621,808,1731 'relat':482,1919,2589,2712 'reliabl':472,1650 'render':424,539,1102,1224,1856,2457,2550 'replac':2594 'repli':2241,2274,2306 'repo':2633 'repositori':262,351 'request':43,613,1663,1754,1927 'requir':165,204,719,1141,1373,1521,1953 'res':1923,2185,2228,2264,2295,2353 'res.body.getreader':2385 'res.choices':2253,2284,2316 'res.json':2207,2217,2375 'res.ok':2203,2371,2512 'res.status':2214,2382 'resiz':1542 'resourc':2869 'respons':1126,1176,1874,1976,1990,2043,2459 'result':764,768,1744,1749,2038,2537 'return':755,1081,1254,1261,1746,1993,2046,2172,2215,2252,2283,2315,2340,2447,2490 'revers':611 'reverse-proxi':610 'rgb':1470 'rgba':939,981,1365 'right':1445,1463,1564 'role':1931,1939,1959,2019,2074,2166,2231,2248,2267,2279,2298,2311,2474 'root':908,914,2745 'rule':807 'runtim':656 'san':965 'sans-serif':964 'sap':2,9,48,49,84,101,196,320,393,609,848,1230,1327,1343,1591,1662,1669,1675,1682,1690,1700,1708,1721,1728,1735,1745,1753,1763,1889,2153,2557,2637,2729,2751,2825,2842,2846,2854,2860 'sap-exampl':2636,2841,2845 'sap-example-with-nod':2853,2859 'sap-extension-cr':1 'sap-lx-mus':847,1590,2824 'save':1022,1027,1029 'scaffold':23,290 'scheme':889,1034,1602,1626,1635,2839 'scheme-invok':1625,1634 'screen':1209 'secondari':919,972,1171 'section':508,519 'see':328,403,505,516,565,846,1589 'select':1423 'self':59,438 'self-contain':58,437 'send':548,551,557,774,1059,1070,1071,1688,1691,1838,2128,2656 'sentenc':222 'separ':814 'serif':966 'serv':81,461,498,593,1314,2578,2724 'server':586,2506 'set':552,1112,1324,1679,1981 'sf':959 'shot':1974,2149 'shown':1849 'shutdown':664,772 'simpl':145,1758,2146 'simplechat':2180,2230,2266,2297 'simplechatstream':2348,2473 'sinc':2809 'singl':179,444,886,1813 'single-fil':178 'single-turn':1812 'size':284,988,1136,1153,1214,1492 'skill':12 'skill-sap-extension-creator' 'skiptaskbar':1360 'small':1152,1208 'source-heshengtao' 'specif':2897 'speed':1887 'stack':93,1160 'stage':652,2691 'standard':1995 'start':299,583 'starter':2873,2880 'startup':653,669 'stateless':1766,1842,2522 'static':32,76,147,224,253,301,458,476,490,594,643,861,1714,2665,2731,2828,2849,2876 'step':201,288,324,416,559,617 'stop':2025,2122,2446 'stream':1881,1947,1963,1969,1989,1991,2042,2044,2056,2125,2145,2199,2319,2328,2367,2405,2458 'string':717,1063,2332 'structur':1391 'studi':2818 'sub':925,978 'summar':1782,1819,1866,2256,2259,2270 'summari':2278 'super':6,28,54 'support':75,108,503,513,875 'system':957,1932,2232,2268,2299 'system/user/assistant':1962 'targetlang':2225,2240 'task':1780,1873,2162 'techniqu':1617 'temperatur':1949,1977,2170,2182,2201,2338,2350,2369 'templat':295,2874,2881 'text':921,924,974,977,1010,1686,1818,2224,2251,2260,2282,2292,2314 'text-sub':923,976 'textarea':1421 'textdecod':2389 'theme':506,517,867,887,906,2733,2753,2836 'three':650,841,2690 'three-stag':2689 'throw':2208,2376 'time':2322 'titl':2814 'toggl':1017 'toggledarkmod':1040 'togglelanguag':1089 'token':2028,2031,2034 'tool':71,110,246,360,620,630,663,694,700,704,722,729,738,740,763,839,866,1111,1711,1734,1741,1743,1747,2597,2610,2699 'tool_name.includes':751 'top':274,401,1302,1444,1461 'top-level':1301 'top-right':1443 'topic-agent-skills' 'topic-ai-companion' 'topic-ai-vtuber' 'topic-claude-code' 'topic-comfyui' 'topic-cowork' 'topic-discord-bot' 'topic-home-assistant' 'topic-im-bot' 'topic-livestream' 'topic-mcp' 'topic-neuro-sama' 'total':2033 'transit':944,1011,1014,1496 'translat':1781,1817,1865,1938,1942,2220,2223,2237,2238,2245 'transpar':268,384,391,397,1316,1321,1331,1345,1356,1375,1378,1382,1386,1568,2765,2832 'transparent/frameless':375,2799 'tree':133 'tri':1249,2426 'trigger':41,556,1687,1697,1750,1837 'true':385,392,1322,1352,1357,1359,1967,2045,2368,2393,2406 'turn':1814,1870 'two':72 'type':683,712,716,761,792,1657,1904,1952,2194,2362,2505,2675 'typic':1563 'ui':69,142,174,515,881,1062,1107,1851,2535,2851 'unclick':2795 'understand':832 'unit':1180 'unknown':1258,1262 'unregist':770,775,793,1717,1722,2657 'unregistermcptool':666,785,805,818,2701 'unregistr':810 'updat':266,543,1668,1672,1678,1683,1883 'updatealltext':1099 'url':263,1601,1611,1621,1642,1920,2590 'usag':2026,2141,2456 'use':15,293,410,468,673,706,890,1146,1178,1192,1237,1265,1597,1613,1828,1860,1888,1894,1917,2551,2556,2587,2707 'user':17,134,207,314,549,553,708,1119,1680,1684,1695,1940,2249,2280,2312,2475 'valu':2396,2404 'var':1003,1006,1009,1013 'variabl':892,902,2743 'version':341 'vertic':1162 'via':123,197,257,432,579,637,1307,2611,2741 'viewport':1139 'visibl':1586 'vs':1830 'vw':1179 'want':18,135 'way':2625 'weather':217 'webkit':1404,1432,1504,2773,2784 'webkit-app-region':1403,1431,1503,2772,2783 'websocket':124,258,521,638,1263,1278,1653,1792,1831,1836,2612,2682 'websocket.open':789 'welcom':1057,1065,1066 'well':1132 'widget':218,277,376,415,1571 'width':386,1158,1168,1182,1186,1190,1465 'width/height':285 'window':170,269,283,402,408,781,1135,1282,1304,1317,1332,1346,1396,1756,2545,2766,2778,2800 'window.addeventlistener':803,1541 'window.beforeunload':665 'window.close':1284,1539 'window.innerheight':1526 'window.location.pathname':533,1238 'window.location.pathname.match':1252 'window.open':1645 'window.parent':1294 'window.top':1293 'without':1788,1796,2771,2782 'word':2273 'work':119,250,488,623,1131,1239,1285,1340,1649,1712,2662,2680,2718 'workflow':200 'workspac':313 'world':857,1944 'wrap':1198 'write':326,418,561,2477 'ws':787,1275,1276,2695 'ws.onmessage':657 'ws.onopen':654,672 'ws.readystate':788 'ws.send':681,759,790 'wss':1274 'xxx':2006,2067,2083,2098,2113 'yield':2329,2342,2439 'your-nam':348 'z':1500 'z-index':1499 'zh':1056,1077,1092,1094 '世界':2106 '你好':2091 '你好世界':2022 '发送':1060 '欢迎使用我的扩展':1058","prices":[{"id":"f38166f3-da06-4f7c-97f5-3108f7c76c41","listingId":"2c801444-b683-4219-a006-aacead383e39","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"heshengtao","category":"super-agent-party","install_from":"skills.sh"},"createdAt":"2026-05-03T12:52:25.485Z"}],"sources":[{"listingId":"2c801444-b683-4219-a006-aacead383e39","source":"github","sourceId":"heshengtao/super-agent-party/sap-extension-creator","sourceUrl":"https://github.com/heshengtao/super-agent-party/tree/main/skills/sap-extension-creator","isPrimary":false,"firstSeenAt":"2026-05-03T12:52:25.485Z","lastSeenAt":"2026-05-18T18:52:51.554Z"}],"details":{"listingId":"2c801444-b683-4219-a006-aacead383e39","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"heshengtao","slug":"sap-extension-creator","github":{"repo":"heshengtao/super-agent-party","stars":2278,"topics":["agent-skills","ai-companion","ai-vtuber","claude-code","comfyui","cowork","discord-bot","home-assistant","im-bot","livestream","mcp","neuro-sama","openclaw","sap","sillytavern","super-agent-party","vrm"],"license":"agpl-3.0","html_url":"https://github.com/heshengtao/super-agent-party","pushed_at":"2026-05-18T09:52:52Z","description":"⭐ All-in-one AI companion! Super Agent Party = Self hosted neuro sama + openclaw! ⭐ 全能AI伴侣！超级智能体派对 = 自托管neuro sama + openclaw!","skill_md_sha":"3804bde3ff4692739ff6d5935496240c8ef6ef76","skill_md_path":"skills/sap-extension-creator/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/heshengtao/super-agent-party/tree/main/skills/sap-extension-creator"},"layout":"multi","source":"github","category":"super-agent-party","frontmatter":{"name":"sap-extension-creator","description":"Create Super Agent Party (SAP) extensions. This skill should be used when users want to create, build, or scaffold a new extension for Super Agent Party - including static HTML extensions (pure frontend) and Node.js backend extensions. Triggers on requests like \"create a new SAP extension\", \"build an extension for Super Agent Party\", \"scaffold a plugin\", \"make a chat UI extension\", or when working with sap extension projects."},"skills_sh_url":"https://skills.sh/heshengtao/super-agent-party/sap-extension-creator"},"updatedAt":"2026-05-18T18:52:51.554Z"}}