zubo 0.1.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.
Files changed (222) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/README.md +149 -0
  3. package/bun.lock +216 -0
  4. package/desktop/README.md +57 -0
  5. package/desktop/package.json +12 -0
  6. package/desktop/src-tauri/Cargo.toml +25 -0
  7. package/desktop/src-tauri/build.rs +3 -0
  8. package/desktop/src-tauri/icons/README.md +17 -0
  9. package/desktop/src-tauri/icons/icon.png +0 -0
  10. package/desktop/src-tauri/src/main.rs +189 -0
  11. package/desktop/src-tauri/tauri.conf.json +68 -0
  12. package/docs/ROADMAP.md +490 -0
  13. package/migrations/001_init.sql +9 -0
  14. package/migrations/002_memory.sql +33 -0
  15. package/migrations/003_cron.sql +24 -0
  16. package/migrations/004_usage.sql +12 -0
  17. package/migrations/005_secrets.sql +8 -0
  18. package/migrations/006_agents.sql +1 -0
  19. package/migrations/007_workflows.sql +22 -0
  20. package/migrations/008_proactive.sql +24 -0
  21. package/migrations/009_uploads.sql +9 -0
  22. package/migrations/010_observability.sql +22 -0
  23. package/migrations/011_api_keys.sql +7 -0
  24. package/migrations/012_indexes.sql +5 -0
  25. package/migrations/013_budget.sql +11 -0
  26. package/migrations/014_usage_session_idx.sql +2 -0
  27. package/package.json +39 -0
  28. package/site/404.html +156 -0
  29. package/site/CNAME +1 -0
  30. package/site/docs/agents.html +294 -0
  31. package/site/docs/api.html +446 -0
  32. package/site/docs/channels.html +345 -0
  33. package/site/docs/cli.html +238 -0
  34. package/site/docs/config.html +1034 -0
  35. package/site/docs/index.html +433 -0
  36. package/site/docs/integrations.html +381 -0
  37. package/site/docs/memory.html +254 -0
  38. package/site/docs/security.html +375 -0
  39. package/site/docs/skills.html +322 -0
  40. package/site/docs.css +412 -0
  41. package/site/index.html +638 -0
  42. package/site/install.sh +98 -0
  43. package/site/logo.svg +1 -0
  44. package/site/og-image.png +0 -0
  45. package/site/robots.txt +4 -0
  46. package/site/script.js +361 -0
  47. package/site/sitemap.xml +63 -0
  48. package/site/skills.html +532 -0
  49. package/site/style.css +1686 -0
  50. package/src/agent/agents.ts +159 -0
  51. package/src/agent/compaction.ts +53 -0
  52. package/src/agent/context.ts +18 -0
  53. package/src/agent/delegate.ts +118 -0
  54. package/src/agent/loop.ts +318 -0
  55. package/src/agent/prompts.ts +111 -0
  56. package/src/agent/session.ts +87 -0
  57. package/src/agent/teams.ts +116 -0
  58. package/src/agent/workflow-executor.ts +192 -0
  59. package/src/agent/workflow.ts +175 -0
  60. package/src/channels/adapter.ts +21 -0
  61. package/src/channels/dashboard.html.ts +2969 -0
  62. package/src/channels/discord.ts +137 -0
  63. package/src/channels/optional-deps.d.ts +17 -0
  64. package/src/channels/router.ts +199 -0
  65. package/src/channels/signal.ts +133 -0
  66. package/src/channels/slack.ts +101 -0
  67. package/src/channels/telegram.ts +102 -0
  68. package/src/channels/utils.ts +18 -0
  69. package/src/channels/webchat.ts +1797 -0
  70. package/src/channels/whatsapp.ts +119 -0
  71. package/src/config/loader.ts +22 -0
  72. package/src/config/paths.ts +43 -0
  73. package/src/config/schema.ts +121 -0
  74. package/src/db/connection.ts +20 -0
  75. package/src/db/export.ts +148 -0
  76. package/src/db/migrations.ts +42 -0
  77. package/src/index.ts +261 -0
  78. package/src/llm/claude.ts +193 -0
  79. package/src/llm/factory.ts +115 -0
  80. package/src/llm/failover.ts +101 -0
  81. package/src/llm/openai-compat.ts +409 -0
  82. package/src/llm/provider.ts +83 -0
  83. package/src/llm/smart-router.ts +241 -0
  84. package/src/logs.ts +53 -0
  85. package/src/memory/chunker.ts +58 -0
  86. package/src/memory/document-parser.ts +115 -0
  87. package/src/memory/embedder.ts +235 -0
  88. package/src/memory/engine.ts +170 -0
  89. package/src/memory/fts-index.ts +55 -0
  90. package/src/memory/hybrid-search.ts +72 -0
  91. package/src/memory/store.ts +56 -0
  92. package/src/memory/vector-index.ts +72 -0
  93. package/src/model.ts +118 -0
  94. package/src/registry/cli.ts +43 -0
  95. package/src/registry/client.ts +54 -0
  96. package/src/registry/installer.ts +67 -0
  97. package/src/scheduler/briefing.ts +71 -0
  98. package/src/scheduler/cron.ts +258 -0
  99. package/src/scheduler/heartbeat.ts +58 -0
  100. package/src/scheduler/memory-triggers.ts +100 -0
  101. package/src/scheduler/natural-cron.ts +163 -0
  102. package/src/scheduler/proactive.ts +25 -0
  103. package/src/scheduler/recipes.ts +110 -0
  104. package/src/secrets/store.ts +64 -0
  105. package/src/setup.ts +413 -0
  106. package/src/skills.ts +293 -0
  107. package/src/start.ts +373 -0
  108. package/src/status.ts +165 -0
  109. package/src/tools/builtin/connect-service.ts +205 -0
  110. package/src/tools/builtin/cron.ts +126 -0
  111. package/src/tools/builtin/datetime.ts +36 -0
  112. package/src/tools/builtin/delegate-task.ts +81 -0
  113. package/src/tools/builtin/delegate.ts +42 -0
  114. package/src/tools/builtin/diagnose.ts +41 -0
  115. package/src/tools/builtin/google-oauth.ts +379 -0
  116. package/src/tools/builtin/manage-agents.ts +149 -0
  117. package/src/tools/builtin/manage-skills.ts +294 -0
  118. package/src/tools/builtin/manage-teams.ts +89 -0
  119. package/src/tools/builtin/manage-triggers.ts +94 -0
  120. package/src/tools/builtin/manage-workflows.ts +119 -0
  121. package/src/tools/builtin/memory-search.ts +38 -0
  122. package/src/tools/builtin/memory-write.ts +30 -0
  123. package/src/tools/builtin/run-workflow.ts +36 -0
  124. package/src/tools/builtin/secrets.ts +122 -0
  125. package/src/tools/builtin/skill-registry.ts +75 -0
  126. package/src/tools/builtin-integrations/api-helpers.ts +26 -0
  127. package/src/tools/builtin-integrations/github/github_issues/SKILL.md +56 -0
  128. package/src/tools/builtin-integrations/github/github_issues/handler.ts +108 -0
  129. package/src/tools/builtin-integrations/github/github_prs/SKILL.md +57 -0
  130. package/src/tools/builtin-integrations/github/github_prs/handler.ts +113 -0
  131. package/src/tools/builtin-integrations/github/github_repos/SKILL.md +37 -0
  132. package/src/tools/builtin-integrations/github/github_repos/handler.ts +88 -0
  133. package/src/tools/builtin-integrations/google/gmail/SKILL.md +51 -0
  134. package/src/tools/builtin-integrations/google/gmail/handler.ts +125 -0
  135. package/src/tools/builtin-integrations/google/google_calendar/SKILL.md +35 -0
  136. package/src/tools/builtin-integrations/google/google_calendar/handler.ts +105 -0
  137. package/src/tools/builtin-integrations/google/google_docs/SKILL.md +35 -0
  138. package/src/tools/builtin-integrations/google/google_docs/handler.ts +108 -0
  139. package/src/tools/builtin-integrations/google/google_drive/SKILL.md +39 -0
  140. package/src/tools/builtin-integrations/google/google_drive/handler.ts +106 -0
  141. package/src/tools/builtin-integrations/google/google_sheets/SKILL.md +36 -0
  142. package/src/tools/builtin-integrations/google/google_sheets/handler.ts +116 -0
  143. package/src/tools/builtin-integrations/jira/jira_boards/SKILL.md +21 -0
  144. package/src/tools/builtin-integrations/jira/jira_boards/handler.ts +74 -0
  145. package/src/tools/builtin-integrations/jira/jira_issues/SKILL.md +28 -0
  146. package/src/tools/builtin-integrations/jira/jira_issues/handler.ts +140 -0
  147. package/src/tools/builtin-integrations/linear/linear_issues/SKILL.md +30 -0
  148. package/src/tools/builtin-integrations/linear/linear_issues/handler.ts +75 -0
  149. package/src/tools/builtin-integrations/linear/linear_projects/SKILL.md +21 -0
  150. package/src/tools/builtin-integrations/linear/linear_projects/handler.ts +43 -0
  151. package/src/tools/builtin-integrations/notion/notion_databases/SKILL.md +39 -0
  152. package/src/tools/builtin-integrations/notion/notion_databases/handler.ts +83 -0
  153. package/src/tools/builtin-integrations/notion/notion_pages/SKILL.md +43 -0
  154. package/src/tools/builtin-integrations/notion/notion_pages/handler.ts +130 -0
  155. package/src/tools/builtin-integrations/notion/notion_search/SKILL.md +27 -0
  156. package/src/tools/builtin-integrations/notion/notion_search/handler.ts +69 -0
  157. package/src/tools/builtin-integrations/slack/slack_messages/SKILL.md +42 -0
  158. package/src/tools/builtin-integrations/slack/slack_messages/handler.ts +72 -0
  159. package/src/tools/builtin-integrations/twitter/twitter_posts/SKILL.md +24 -0
  160. package/src/tools/builtin-integrations/twitter/twitter_posts/handler.ts +133 -0
  161. package/src/tools/builtin-skills/file-read/SKILL.md +26 -0
  162. package/src/tools/builtin-skills/file-read/handler.ts +66 -0
  163. package/src/tools/builtin-skills/file-write/SKILL.md +30 -0
  164. package/src/tools/builtin-skills/file-write/handler.ts +64 -0
  165. package/src/tools/builtin-skills/http-request/SKILL.md +34 -0
  166. package/src/tools/builtin-skills/http-request/handler.ts +87 -0
  167. package/src/tools/builtin-skills/shell/SKILL.md +26 -0
  168. package/src/tools/builtin-skills/shell/handler.ts +96 -0
  169. package/src/tools/builtin-skills/url-fetch/SKILL.md +26 -0
  170. package/src/tools/builtin-skills/url-fetch/handler.ts +37 -0
  171. package/src/tools/builtin-skills/web-search/SKILL.md +26 -0
  172. package/src/tools/builtin-skills/web-search/handler.ts +50 -0
  173. package/src/tools/executor.ts +205 -0
  174. package/src/tools/integration-installer.ts +106 -0
  175. package/src/tools/permissions.ts +45 -0
  176. package/src/tools/registry.ts +39 -0
  177. package/src/tools/sandbox-runner.ts +56 -0
  178. package/src/tools/sandbox.ts +82 -0
  179. package/src/tools/skill-installer.ts +52 -0
  180. package/src/tools/skill-loader.ts +259 -0
  181. package/src/types/optional-deps.d.ts +23 -0
  182. package/src/util/auth.ts +121 -0
  183. package/src/util/costs.ts +59 -0
  184. package/src/util/error-buffer.ts +32 -0
  185. package/src/util/google-tokens.ts +180 -0
  186. package/src/util/logger.ts +73 -0
  187. package/src/util/perf-collector.ts +35 -0
  188. package/src/util/rate-limiter.ts +70 -0
  189. package/src/util/tokens.ts +17 -0
  190. package/src/voice/stt.ts +57 -0
  191. package/src/voice/tts.ts +103 -0
  192. package/tests/agent/session.test.ts +109 -0
  193. package/tests/agent-loop.test.ts +54 -0
  194. package/tests/auth.test.ts +89 -0
  195. package/tests/channels.test.ts +67 -0
  196. package/tests/compaction.test.ts +44 -0
  197. package/tests/config.test.ts +51 -0
  198. package/tests/costs.test.ts +19 -0
  199. package/tests/cron.test.ts +55 -0
  200. package/tests/db/export.test.ts +219 -0
  201. package/tests/executor.test.ts +144 -0
  202. package/tests/export.test.ts +137 -0
  203. package/tests/helpers/mock-llm.ts +34 -0
  204. package/tests/helpers/test-db.ts +74 -0
  205. package/tests/integration/chat-flow.test.ts +48 -0
  206. package/tests/integrations.test.ts +97 -0
  207. package/tests/memory/engine.test.ts +114 -0
  208. package/tests/memory-engine.test.ts +57 -0
  209. package/tests/permissions.test.ts +21 -0
  210. package/tests/rate-limiter.test.ts +70 -0
  211. package/tests/registry.test.ts +67 -0
  212. package/tests/router.test.ts +36 -0
  213. package/tests/session.test.ts +58 -0
  214. package/tests/skill-loader.test.ts +44 -0
  215. package/tests/tokens.test.ts +30 -0
  216. package/tests/tools/executor.test.ts +130 -0
  217. package/tests/util/auth.test.ts +75 -0
  218. package/tests/util/rate-limiter.test.ts +73 -0
  219. package/tests/voice.test.ts +60 -0
  220. package/tests/webchat.test.ts +88 -0
  221. package/tests/workflow.test.ts +38 -0
  222. package/tsconfig.json +16 -0
@@ -0,0 +1,532 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Community Skills — Zubo</title>
7
+ <meta name="description" content="Browse and install community skills for Zubo. Extend your AI agent with custom tools, integrations, and automations.">
8
+ <meta name="theme-color" content="#060608">
9
+ <link rel="canonical" href="https://zubo.bot/skills.html">
10
+ <meta property="og:title" content="Community Skills — Zubo">
11
+ <meta property="og:description" content="Browse and install community skills for Zubo. Extend your AI agent with custom tools, integrations, and automations.">
12
+ <meta property="og:type" content="website">
13
+ <meta property="og:url" content="https://zubo.bot/skills.html">
14
+ <meta property="og:image" content="https://zubo.bot/og-image.png">
15
+ <meta property="og:site_name" content="Zubo">
16
+ <meta name="twitter:card" content="summary_large_image">
17
+ <meta name="twitter:title" content="Community Skills — Zubo">
18
+ <meta name="twitter:description" content="Browse and install community skills for Zubo. Extend your AI agent with custom tools, integrations, and automations.">
19
+ <meta name="twitter:image" content="https://zubo.bot/og-image.png">
20
+ <meta name="twitter:creator" content="@thomaskanze">
21
+ <link rel="preconnect" href="https://fonts.googleapis.com">
22
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
23
+ <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
24
+ <link rel="stylesheet" href="style.css">
25
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='20' fill='%237c3aed'/><path d='M50 15C52 37 63 48 85 50C63 52 52 63 50 85C48 63 37 52 15 50C37 48 48 37 50 15Z' fill='white'/></svg>">
26
+ </head>
27
+ <body>
28
+
29
+ <div class="scroll-progress" id="scroll-progress"></div>
30
+ <div class="noise" aria-hidden="true"></div>
31
+
32
+ <!-- Navigation -->
33
+ <header class="nav" id="nav">
34
+ <div class="nav-inner">
35
+ <a href="index.html" class="nav-logo">
36
+ <span class="logo-wordmark">zubo</span>
37
+ </a>
38
+ <nav class="nav-links" id="nav-links">
39
+ <a href="index.html#features">Features</a>
40
+ <a href="docs/index.html">Docs</a>
41
+ <a href="skills.html" style="color:#fff;">Skills</a>
42
+ <a href="index.html#get-started">Get Started</a>
43
+ </nav>
44
+ <div class="nav-right">
45
+ <a href="https://github.com/apwn/zubo" class="nav-github" aria-label="GitHub">
46
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
47
+ </a>
48
+ <a href="index.html#get-started" class="btn btn-primary btn-nav">Get Started</a>
49
+ <button class="nav-toggle" id="nav-toggle" aria-label="Toggle menu">
50
+ <span></span><span></span><span></span>
51
+ </button>
52
+ </div>
53
+ </div>
54
+ </header>
55
+
56
+ <!-- Hero -->
57
+ <section class="skills-hero">
58
+ <div class="container">
59
+ <div class="section-header reveal">
60
+ <span class="section-badge">Community</span>
61
+ <h1 class="section-title" style="font-size:clamp(2.2rem,5vw,3.5rem);">Extend Zubo with <span class="gradient-text">community skills</span></h1>
62
+ <p class="section-subtitle">Browse, search, and install skills built by the community. One command to add new capabilities to your agent.</p>
63
+ </div>
64
+ <div class="skills-search-wrap">
65
+ <input type="text" id="skills-search" class="skills-search" placeholder="Search skills by name, tag, or description..." autocomplete="off">
66
+ </div>
67
+ </div>
68
+ </section>
69
+
70
+ <!-- Tag Filter -->
71
+ <section class="skills-filter">
72
+ <div class="container">
73
+ <div class="skills-tags" id="skills-tags">
74
+ <button class="skill-tag-btn active" data-tag="all">All</button>
75
+ </div>
76
+ </div>
77
+ </section>
78
+
79
+ <!-- Skills Grid -->
80
+ <section class="skills-listing">
81
+ <div class="container">
82
+ <div id="skills-loading" class="skills-state">Loading skills...</div>
83
+ <div id="skills-error" class="skills-state" style="display:none;"></div>
84
+ <div id="skills-empty" class="skills-state" style="display:none;">No skills match your search.</div>
85
+ <div class="skills-grid" id="skills-grid"></div>
86
+ </div>
87
+ </section>
88
+
89
+ <!-- Create a Skill Guide -->
90
+ <section class="skill-guide" id="create">
91
+ <div class="container">
92
+ <div class="section-header reveal">
93
+ <span class="section-badge">Build</span>
94
+ <h2 class="section-title">Create your own <span class="gradient-text">skill</span></h2>
95
+ <p class="section-subtitle">Skills are self-contained TypeScript modules. Build one in four steps.</p>
96
+ </div>
97
+
98
+ <div class="skill-guide-steps reveal-stagger">
99
+ <div class="skill-guide-step">
100
+ <div class="step-number">1</div>
101
+ <h3>Scaffold</h3>
102
+ <p>Run <code>zubo skills new</code> to generate a skill directory with a <code>handler.ts</code> template.</p>
103
+ </div>
104
+ <div class="skill-guide-step">
105
+ <div class="step-number">2</div>
106
+ <h3>Define</h3>
107
+ <p>Export a <code>skill</code> config object with your tool's name, description, and parameters.</p>
108
+ </div>
109
+ <div class="skill-guide-step">
110
+ <div class="step-number">3</div>
111
+ <h3>Implement</h3>
112
+ <p>Write your logic as the default export function. Access inputs and return a JSON string.</p>
113
+ </div>
114
+ <div class="skill-guide-step">
115
+ <div class="step-number">4</div>
116
+ <h3>Publish</h3>
117
+ <p>Run <code>zubo publish</code> to submit to the skill registry. Others can install with one command.</p>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Single-file format example -->
122
+ <div class="skill-format-block reveal">
123
+ <div class="terminal" style="border-radius:var(--radius-lg);overflow:hidden;">
124
+ <div class="terminal-chrome">
125
+ <div class="terminal-dots">
126
+ <span class="td td-r"></span>
127
+ <span class="td td-y"></span>
128
+ <span class="td td-g"></span>
129
+ </div>
130
+ <div class="terminal-title">handler.ts</div>
131
+ </div>
132
+ <div class="terminal-body" style="padding:20px 24px;font-size:13px;line-height:1.8;">
133
+ <span style="color:#c4b5fd;">export const</span> <span style="color:#fff;">skill</span> <span style="color:#5f5f73;">=</span> <span style="color:#9595a8;">{</span>
134
+ <span style="color:#fbbf24;">name:</span> <span style="color:#34d399;">"my_skill"</span>,
135
+ <span style="color:#fbbf24;">description:</span> <span style="color:#34d399;">"A brief description of what this skill does"</span>,
136
+ <span style="color:#fbbf24;">params:</span> <span style="color:#9595a8;">{</span>
137
+ <span style="color:#fbbf24;">query:</span> <span style="color:#9595a8;">{ type: </span><span style="color:#34d399;">"string"</span><span style="color:#9595a8;">, required: true },</span>
138
+ <span style="color:#fbbf24;">limit:</span> <span style="color:#9595a8;">{ type: </span><span style="color:#34d399;">"number"</span><span style="color:#9595a8;"> }</span>
139
+ <span style="color:#9595a8;">}</span>
140
+ <span style="color:#9595a8;">};</span>
141
+
142
+ <span style="color:#c4b5fd;">export default async function</span> <span style="color:#9595a8;">(input): Promise&lt;string&gt; {</span>
143
+ <span style="color:#5f5f73;">// your logic here</span>
144
+ <span style="color:#c4b5fd;">return</span> <span style="color:#9595a8;">JSON.stringify({ result: </span><span style="color:#34d399;">"..."</span><span style="color:#9595a8;"> });</span>
145
+ <span style="color:#9595a8;">}</span>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </section>
151
+
152
+ <!-- Build with AI -->
153
+ <section class="ai-prompt-section" id="ai-prompt">
154
+ <div class="container">
155
+ <div class="section-header reveal">
156
+ <span class="section-badge">AI-Powered</span>
157
+ <h2 class="section-title">Build with <span class="gradient-text">AI</span></h2>
158
+ <p class="section-subtitle">Copy this prompt and send it to your Zubo agent to build a new skill on the fly.</p>
159
+ </div>
160
+
161
+ <div class="ai-prompt-block reveal">
162
+ <div class="ai-prompt-header">
163
+ <span class="ai-prompt-label">Skill Builder Prompt</span>
164
+ <button class="ai-prompt-copy-btn" id="ai-prompt-copy" aria-label="Copy prompt to clipboard">
165
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
166
+ <span>Copy</span>
167
+ </button>
168
+ </div>
169
+ <pre class="ai-prompt-code" id="ai-prompt-text">I want to build a Zubo skill. A skill is a single TypeScript file (handler.ts)
170
+ that lives in its own directory under ~/.zubo/skills/.
171
+
172
+ The file must:
173
+ 1. Export a `skill` config object with name, description, and params
174
+ 2. Export a default async function that takes input and returns a string
175
+
176
+ Format:
177
+
178
+ export const skill = {
179
+ name: "tool_name", // lowercase + underscores only, [a-z0-9_]+
180
+ description: "What it does", // shown to the AI to decide when to use it
181
+ params: {
182
+ param_name: { type: "string", description: "...", required: true },
183
+ optional_param: { type: "number", description: "..." }
184
+ }
185
+ };
186
+
187
+ export default async function (input: Record&lt;string, unknown&gt;): Promise&lt;string&gt; {
188
+ const param_name = input.param_name as string;
189
+ // your logic here
190
+ return JSON.stringify({ result: "..." });
191
+ }
192
+
193
+ Example — a complete working skill:
194
+
195
+ export const skill = {
196
+ name: "word_count",
197
+ description: "Count words, characters, and sentences in text",
198
+ params: {
199
+ text: { type: "string", description: "The text to analyze", required: true }
200
+ }
201
+ };
202
+
203
+ export default async function (input: Record&lt;string, unknown&gt;): Promise&lt;string&gt; {
204
+ const text = input.text as string;
205
+ const words = text.split(/\s+/).filter(Boolean).length;
206
+ const chars = text.length;
207
+ const sentences = text.split(/[.!?]+/).filter(Boolean).length;
208
+ return JSON.stringify({ words, characters: chars, sentences });
209
+ }
210
+
211
+ Rules:
212
+ - name must match [a-z0-9_]+ (no hyphens, no uppercase)
213
+ - The function must return a JSON string
214
+ - You can use fetch(), Bun APIs, and any built-in Node/Bun modules
215
+ - For API keys, read from process.env (e.g., process.env.MY_API_KEY)
216
+ - Keep it focused — one tool, one purpose
217
+
218
+ Please build me a skill that: [DESCRIBE WHAT YOU WANT]</pre>
219
+ </div>
220
+ </div>
221
+ </section>
222
+
223
+ <!-- CTA -->
224
+ <section class="final-cta" style="padding:100px 0;">
225
+ <div class="container">
226
+ <div class="cta-content reveal">
227
+ <h2>Ready to build your <span class="gradient-text">perfect AI agent</span>?</h2>
228
+ <p>Install skills with one command. Build your own in minutes.</p>
229
+ <div class="cta-actions">
230
+ <a href="index.html#get-started" class="btn btn-primary btn-xl">
231
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
232
+ Get Started
233
+ </a>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </section>
238
+
239
+ <!-- Footer -->
240
+ <footer class="footer">
241
+ <div class="container">
242
+ <div class="footer-inner">
243
+ <div class="footer-brand">
244
+ <a href="index.html" class="nav-logo">
245
+ <span class="logo-wordmark">zubo</span>
246
+ </a>
247
+ <p>Your AI agent that never forgets.</p>
248
+ </div>
249
+ <div class="footer-links">
250
+ <div class="footer-col">
251
+ <h5>Product</h5>
252
+ <a href="index.html#features">Features</a>
253
+ <a href="index.html#integrations">Integrations</a>
254
+ <a href="index.html#channels">Channels</a>
255
+ <a href="index.html#get-started">Get Started</a>
256
+ </div>
257
+ <div class="footer-col">
258
+ <h5>Resources</h5>
259
+ <a href="docs/index.html">Documentation</a>
260
+ <a href="skills.html">Community Skills</a>
261
+ <a href="https://github.com/apwn/zubo">GitHub</a>
262
+ <a href="https://github.com/apwn/zubo/issues">Issues</a>
263
+ </div>
264
+ <div class="footer-col">
265
+ <h5>Community</h5>
266
+ <a href="https://github.com/apwn/zubo/discussions">Discussions</a>
267
+ <a href="https://github.com/apwn/zubo/blob/main/CONTRIBUTING.md">Contributing</a>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ <div class="footer-bottom">
272
+ <p>&copy; 2026 Zubo. MIT License. Built with care.</p>
273
+ </div>
274
+ </div>
275
+ </footer>
276
+
277
+ <script src="script.js"></script>
278
+ <script>
279
+ (function() {
280
+ 'use strict';
281
+
282
+ var REGISTRY_URL = 'https://raw.githubusercontent.com/zubo-skills/registry/main/registry.json';
283
+ var allSkills = [];
284
+ var activeTag = 'all';
285
+
286
+ function escapeHtml(str) {
287
+ var d = document.createElement('div');
288
+ d.textContent = str;
289
+ return d.innerHTML;
290
+ }
291
+
292
+ function createSkillCard(skill) {
293
+ var card = document.createElement('div');
294
+ card.className = 'skill-card';
295
+
296
+ var header = document.createElement('div');
297
+ header.className = 'skill-card-header';
298
+
299
+ var nameEl = document.createElement('div');
300
+ nameEl.className = 'skill-card-name';
301
+ nameEl.textContent = skill.name;
302
+ header.appendChild(nameEl);
303
+
304
+ var metaEl = document.createElement('div');
305
+ metaEl.className = 'skill-card-meta';
306
+ if (skill.author) {
307
+ var authorSpan = document.createElement('span');
308
+ authorSpan.textContent = 'by ' + skill.author;
309
+ metaEl.appendChild(authorSpan);
310
+ }
311
+ if (skill.version) {
312
+ var versionSpan = document.createElement('span');
313
+ versionSpan.textContent = 'v' + skill.version;
314
+ metaEl.appendChild(versionSpan);
315
+ }
316
+ header.appendChild(metaEl);
317
+ card.appendChild(header);
318
+
319
+ var descEl = document.createElement('div');
320
+ descEl.className = 'skill-card-desc';
321
+ descEl.textContent = skill.description || '';
322
+ card.appendChild(descEl);
323
+
324
+ if (skill.tags && skill.tags.length) {
325
+ var tagsEl = document.createElement('div');
326
+ tagsEl.className = 'skill-card-tags';
327
+ skill.tags.forEach(function(t) {
328
+ var tagSpan = document.createElement('span');
329
+ tagSpan.className = 'skill-card-tag';
330
+ tagSpan.textContent = t;
331
+ tagsEl.appendChild(tagSpan);
332
+ });
333
+ card.appendChild(tagsEl);
334
+ }
335
+
336
+ if (skill.secrets && skill.secrets.length) {
337
+ var secretsEl = document.createElement('div');
338
+ secretsEl.className = 'skill-card-secrets';
339
+ secretsEl.appendChild(document.createTextNode('Requires: '));
340
+ skill.secrets.forEach(function(s, i) {
341
+ if (i > 0) secretsEl.appendChild(document.createTextNode(', '));
342
+ var code = document.createElement('code');
343
+ code.textContent = s;
344
+ secretsEl.appendChild(code);
345
+ });
346
+ card.appendChild(secretsEl);
347
+ }
348
+
349
+ var installEl = document.createElement('div');
350
+ installEl.className = 'skill-card-install';
351
+ var installCode = document.createElement('code');
352
+ var safeName = (skill.name || '').replace(/[^a-zA-Z0-9_\-\.]/g, '');
353
+ var installCmd = 'zubo install ' + safeName;
354
+ installCode.textContent = installCmd;
355
+ installEl.appendChild(installCode);
356
+
357
+ var copyBtn = document.createElement('button');
358
+ copyBtn.className = 'skill-copy-btn';
359
+ copyBtn.setAttribute('aria-label', 'Copy install command');
360
+ copyBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
361
+ copyBtn.addEventListener('click', function() {
362
+ navigator.clipboard.writeText(installCmd).then(function() {
363
+ copyBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
364
+ copyBtn.style.color = '#10b981';
365
+ setTimeout(function() {
366
+ copyBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
367
+ copyBtn.style.color = '';
368
+ }, 2000);
369
+ }).catch(function() {});
370
+ });
371
+ installEl.appendChild(copyBtn);
372
+ card.appendChild(installEl);
373
+
374
+ return card;
375
+ }
376
+
377
+ function renderSkills(skills) {
378
+ var grid = document.getElementById('skills-grid');
379
+ var empty = document.getElementById('skills-empty');
380
+ var loading = document.getElementById('skills-loading');
381
+ loading.style.display = 'none';
382
+
383
+ grid.textContent = '';
384
+
385
+ if (!skills.length) {
386
+ empty.style.display = 'block';
387
+ return;
388
+ }
389
+ empty.style.display = 'none';
390
+ skills.forEach(function(skill) {
391
+ grid.appendChild(createSkillCard(skill));
392
+ });
393
+ }
394
+
395
+ function buildTagButtons(skills) {
396
+ var tagSet = {};
397
+ skills.forEach(function(s) {
398
+ (s.tags || []).forEach(function(t) { tagSet[t] = (tagSet[t] || 0) + 1; });
399
+ });
400
+ var container = document.getElementById('skills-tags');
401
+ Object.keys(tagSet).sort().forEach(function(tag) {
402
+ var btn = document.createElement('button');
403
+ btn.className = 'skill-tag-btn';
404
+ btn.setAttribute('data-tag', tag);
405
+ btn.textContent = tag + ' (' + tagSet[tag] + ')';
406
+ container.appendChild(btn);
407
+ });
408
+
409
+ container.addEventListener('click', function(e) {
410
+ var btn = e.target.closest('.skill-tag-btn');
411
+ if (!btn) return;
412
+ activeTag = btn.getAttribute('data-tag');
413
+ container.querySelectorAll('.skill-tag-btn').forEach(function(b) { b.classList.remove('active'); });
414
+ btn.classList.add('active');
415
+ filterAndRender();
416
+ });
417
+ }
418
+
419
+ function filterAndRender() {
420
+ var query = (document.getElementById('skills-search').value || '').toLowerCase().trim();
421
+ var filtered = allSkills.filter(function(s) {
422
+ if (activeTag !== 'all' && (s.tags || []).indexOf(activeTag) === -1) return false;
423
+ if (!query) return true;
424
+ var haystack = ((s.name || '') + ' ' + (s.description || '') + ' ' + (s.tags || []).join(' ')).toLowerCase();
425
+ return haystack.indexOf(query) !== -1;
426
+ });
427
+ renderSkills(filtered);
428
+ }
429
+
430
+ // Fetch registry
431
+ fetch(REGISTRY_URL)
432
+ .then(function(r) {
433
+ if (!r.ok) throw new Error('Failed to load registry');
434
+ return r.json();
435
+ })
436
+ .then(function(data) {
437
+ allSkills = data.skills || data || [];
438
+ if (!Array.isArray(allSkills)) allSkills = [];
439
+ buildTagButtons(allSkills);
440
+ renderSkills(allSkills);
441
+ })
442
+ .catch(function() {
443
+ document.getElementById('skills-loading').style.display = 'none';
444
+ var errEl = document.getElementById('skills-error');
445
+ errEl.style.display = 'block';
446
+ errEl.textContent = 'Could not load the skill registry. The registry may not be published yet.';
447
+ // Show placeholder skills for demo
448
+ allSkills = [
449
+ { name: 'weather', description: 'Get current weather and forecasts for any location', author: 'zubo', version: '1.0.0', tags: ['api', 'weather', 'utility'], secrets: ['OPENWEATHER_API_KEY'] },
450
+ { name: 'email-summary', description: 'Summarize unread emails and draft replies', author: 'community', version: '0.2.0', tags: ['email', 'productivity'], secrets: ['GMAIL_TOKEN'] },
451
+ { name: 'stock-price', description: 'Fetch real-time stock quotes and market data', author: 'zubo', version: '1.1.0', tags: ['finance', 'api'], secrets: ['ALPHA_VANTAGE_KEY'] },
452
+ { name: 'pomodoro', description: 'Pomodoro timer with session tracking and break reminders', author: 'community', version: '0.1.0', tags: ['productivity', 'timer'], secrets: [] },
453
+ { name: 'translate', description: 'Translate text between 100+ languages using DeepL', author: 'zubo', version: '1.0.0', tags: ['translation', 'api', 'utility'], secrets: ['DEEPL_API_KEY'] },
454
+ { name: 'screenshot', description: 'Take screenshots of any URL and return the image', author: 'community', version: '0.3.0', tags: ['web', 'utility'], secrets: [] },
455
+ ];
456
+ buildTagButtons(allSkills);
457
+ renderSkills(allSkills);
458
+ });
459
+
460
+ // Search
461
+ var searchInput = document.getElementById('skills-search');
462
+ var searchTimeout;
463
+ searchInput.addEventListener('input', function() {
464
+ clearTimeout(searchTimeout);
465
+ searchTimeout = setTimeout(filterAndRender, 200);
466
+ });
467
+
468
+ // AI Prompt copy button
469
+ var aiCopyBtn = document.getElementById('ai-prompt-copy');
470
+ var aiPromptText = document.getElementById('ai-prompt-text');
471
+ if (aiCopyBtn && aiPromptText) {
472
+ aiCopyBtn.addEventListener('click', function() {
473
+ navigator.clipboard.writeText(aiPromptText.textContent).then(function() {
474
+ var svgCheck = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
475
+ svgCheck.setAttribute('width', '16');
476
+ svgCheck.setAttribute('height', '16');
477
+ svgCheck.setAttribute('viewBox', '0 0 24 24');
478
+ svgCheck.setAttribute('fill', 'none');
479
+ svgCheck.setAttribute('stroke', 'currentColor');
480
+ svgCheck.setAttribute('stroke-width', '2');
481
+ svgCheck.setAttribute('stroke-linecap', 'round');
482
+ svgCheck.setAttribute('stroke-linejoin', 'round');
483
+ var polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
484
+ polyline.setAttribute('points', '20 6 9 17 4 12');
485
+ svgCheck.appendChild(polyline);
486
+
487
+ var label = document.createElement('span');
488
+ label.textContent = 'Copied!';
489
+
490
+ aiCopyBtn.textContent = '';
491
+ aiCopyBtn.appendChild(svgCheck);
492
+ aiCopyBtn.appendChild(label);
493
+ aiCopyBtn.style.borderColor = 'rgba(16, 185, 129, 0.4)';
494
+ aiCopyBtn.style.color = '#10b981';
495
+
496
+ setTimeout(function() {
497
+ var svgCopy = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
498
+ svgCopy.setAttribute('width', '16');
499
+ svgCopy.setAttribute('height', '16');
500
+ svgCopy.setAttribute('viewBox', '0 0 24 24');
501
+ svgCopy.setAttribute('fill', 'none');
502
+ svgCopy.setAttribute('stroke', 'currentColor');
503
+ svgCopy.setAttribute('stroke-width', '2');
504
+ svgCopy.setAttribute('stroke-linecap', 'round');
505
+ svgCopy.setAttribute('stroke-linejoin', 'round');
506
+ var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
507
+ rect.setAttribute('x', '9');
508
+ rect.setAttribute('y', '9');
509
+ rect.setAttribute('width', '13');
510
+ rect.setAttribute('height', '13');
511
+ rect.setAttribute('rx', '2');
512
+ svgCopy.appendChild(rect);
513
+ var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
514
+ path.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
515
+ svgCopy.appendChild(path);
516
+
517
+ var resetLabel = document.createElement('span');
518
+ resetLabel.textContent = 'Copy';
519
+
520
+ aiCopyBtn.textContent = '';
521
+ aiCopyBtn.appendChild(svgCopy);
522
+ aiCopyBtn.appendChild(resetLabel);
523
+ aiCopyBtn.style.borderColor = '';
524
+ aiCopyBtn.style.color = '';
525
+ }, 2500);
526
+ }).catch(function() {});
527
+ });
528
+ }
529
+ })();
530
+ </script>
531
+ </body>
532
+ </html>