zubo 0.1.4 → 0.1.5
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/.dockerignore +12 -0
- package/Dockerfile +24 -0
- package/docker-compose.yml +20 -0
- package/package.json +1 -1
- package/site/docs/index.html +59 -11
- package/site/index.html +137 -44
- package/site/script.js +14 -5
- package/site/style.css +38 -0
- package/src/tools/builtin-integrations/google/gmail/handler.ts +8 -2
- package/src/tools/builtin-skills/http-request/handler.ts +7 -1
package/.dockerignore
ADDED
package/Dockerfile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Zubo — Dockerfile
|
|
2
|
+
# Runs the Zubo AI agent using the official Bun runtime image.
|
|
3
|
+
# Persistent data (config, database, memory, skills) lives in /root/.zubo
|
|
4
|
+
# and should be mounted as a volume.
|
|
5
|
+
|
|
6
|
+
FROM oven/bun:1
|
|
7
|
+
|
|
8
|
+
WORKDIR /app
|
|
9
|
+
|
|
10
|
+
# Install dependencies first (layer cache optimisation)
|
|
11
|
+
COPY package.json bun.lock ./
|
|
12
|
+
RUN bun install --frozen-lockfile
|
|
13
|
+
|
|
14
|
+
# Copy the rest of the project
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
# Webchat dashboard port
|
|
18
|
+
EXPOSE 8787
|
|
19
|
+
|
|
20
|
+
# Persistent storage mount point — config.json, zubo.db, workspace, logs, etc.
|
|
21
|
+
VOLUME /root/.zubo
|
|
22
|
+
|
|
23
|
+
# Start the agent
|
|
24
|
+
CMD ["bun", "run", "src/index.ts", "start"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Zubo — Docker Compose
|
|
2
|
+
# Usage:
|
|
3
|
+
# 1. Copy .env.example to .env and fill in your API keys
|
|
4
|
+
# 2. docker compose up -d
|
|
5
|
+
# 3. Open http://localhost:8787 for the webchat dashboard
|
|
6
|
+
|
|
7
|
+
services:
|
|
8
|
+
zubo:
|
|
9
|
+
build: .
|
|
10
|
+
container_name: zubo
|
|
11
|
+
restart: unless-stopped
|
|
12
|
+
|
|
13
|
+
ports:
|
|
14
|
+
- "8787:8787" # Webchat dashboard
|
|
15
|
+
|
|
16
|
+
volumes:
|
|
17
|
+
- ~/.zubo:/root/.zubo # Persistent data (config, DB, memory, skills, logs)
|
|
18
|
+
|
|
19
|
+
env_file:
|
|
20
|
+
- .env # API keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, TELEGRAM_BOT_TOKEN, etc.
|
package/package.json
CHANGED
package/site/docs/index.html
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Getting Started — Zubo Docs</title>
|
|
7
|
-
<meta name="description" content="Get started with Zubo, a self-hosted AI agent with persistent semantic memory, multi-channel presence,
|
|
7
|
+
<meta name="description" content="Get started with Zubo, a self-hosted AI agent with persistent semantic memory, multi-channel presence, 20+ tools, sub-agent delegation, and workflow automation.">
|
|
8
8
|
<meta name="theme-color" content="#060608">
|
|
9
9
|
<link rel="canonical" href="https://zubo.bot/docs/">
|
|
10
10
|
<meta property="og:title" content="Getting Started — Zubo Docs">
|
|
11
|
-
<meta property="og:description" content="Get started with Zubo, a self-hosted AI agent with persistent semantic memory, multi-channel presence,
|
|
11
|
+
<meta property="og:description" content="Get started with Zubo, a self-hosted AI agent with persistent semantic memory, multi-channel presence, 20+ tools, sub-agent delegation, and workflow automation.">
|
|
12
12
|
<meta property="og:type" content="article">
|
|
13
13
|
<meta property="og:url" content="https://zubo.bot/docs/">
|
|
14
14
|
<meta property="og:image" content="https://zubo.bot/og-image.png">
|
|
15
15
|
<meta property="og:site_name" content="Zubo">
|
|
16
16
|
<meta name="twitter:card" content="summary_large_image">
|
|
17
17
|
<meta name="twitter:title" content="Getting Started — Zubo Docs">
|
|
18
|
-
<meta name="twitter:description" content="Get started with Zubo, a self-hosted AI agent with persistent semantic memory,
|
|
18
|
+
<meta name="twitter:description" content="Get started with Zubo, a self-hosted AI agent with persistent semantic memory, 20+ tools, and workflow automation.">
|
|
19
19
|
<meta name="twitter:image" content="https://zubo.bot/og-image.png">
|
|
20
20
|
<meta name="twitter:creator" content="@thomaskanze">
|
|
21
21
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
<h1>Getting Started with Zubo</h1>
|
|
88
88
|
|
|
89
89
|
<p>
|
|
90
|
-
Zubo is a self-hosted AI agent with persistent semantic memory, multi-channel presence across Telegram, Discord, Slack, WhatsApp, Signal, and Web Chat,
|
|
90
|
+
Zubo is a self-hosted AI agent with persistent semantic memory, multi-channel presence across Telegram, Discord, Slack, WhatsApp, Signal, and Web Chat, 20+ smart built-in tools, sub-agent delegation, and workflow automation. It runs entirely on your machine — your data never leaves your infrastructure. Whether you need a personal assistant that remembers everything, an automation hub that connects your services, or an AI copilot embedded in every chat platform you use, Zubo handles it from a single process with a single configuration file.
|
|
91
91
|
</p>
|
|
92
92
|
|
|
93
93
|
<!-- ================================================================ -->
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
|
|
126
126
|
<h3>Tools</h3>
|
|
127
127
|
<ul>
|
|
128
|
-
<li><strong>
|
|
128
|
+
<li><strong>20+ built-in tools</strong> including shell command execution, file I/O (read, write, list, search), web search (DuckDuckGo, Brave, Google), HTTP requests, memory read/write/search, secrets management, and cron job scheduling.</li>
|
|
129
129
|
<li>Tools are permission-gated with three levels: <code>safe</code>, <code>sensitive</code>, and <code>dangerous</code>. Dangerous operations require explicit confirmation tokens.</li>
|
|
130
130
|
</ul>
|
|
131
131
|
|
|
@@ -200,21 +200,69 @@
|
|
|
200
200
|
<!-- ================================================================ -->
|
|
201
201
|
<h2 id="installation">Installation</h2>
|
|
202
202
|
|
|
203
|
+
<p>Choose the installation method that fits your platform.</p>
|
|
204
|
+
|
|
205
|
+
<h3 id="install-mac-linux">macOS & Linux</h3>
|
|
206
|
+
|
|
203
207
|
<p>Zubo requires <strong>Bun v1.0+</strong> as its runtime. If you do not have Bun installed, visit <a href="https://bun.sh" target="_blank" rel="noopener">bun.sh</a> and run the one-line installer.</p>
|
|
204
208
|
|
|
205
|
-
<
|
|
209
|
+
<p><strong>One-liner (recommended):</strong></p>
|
|
210
|
+
|
|
211
|
+
<pre><code>curl -fsSL https://zubo.bot/install.sh | bash</code></pre>
|
|
206
212
|
|
|
207
|
-
<p>
|
|
213
|
+
<p>This installs Bun (if needed), installs Zubo globally, and runs the setup wizard automatically.</p>
|
|
214
|
+
|
|
215
|
+
<p><strong>Manual install:</strong></p>
|
|
208
216
|
|
|
209
217
|
<pre><code># Using Bun (recommended)
|
|
210
218
|
bun add -g zubo
|
|
211
219
|
|
|
212
220
|
# Or using npm
|
|
213
|
-
npm i -g zubo
|
|
221
|
+
npm i -g zubo
|
|
222
|
+
|
|
223
|
+
# Then run the setup wizard
|
|
224
|
+
zubo setup</code></pre>
|
|
225
|
+
|
|
226
|
+
<h3 id="install-windows">Windows (via WSL)</h3>
|
|
227
|
+
|
|
228
|
+
<p>Zubo runs on Windows through the Windows Subsystem for Linux (WSL). This gives you full compatibility with zero workarounds.</p>
|
|
229
|
+
|
|
230
|
+
<p><strong>Step 1:</strong> Install WSL (requires Windows 10 version 2004+ or Windows 11):</p>
|
|
231
|
+
|
|
232
|
+
<pre><code>wsl --install</code></pre>
|
|
233
|
+
|
|
234
|
+
<p>Restart your computer when prompted. This installs Ubuntu by default.</p>
|
|
235
|
+
|
|
236
|
+
<p><strong>Step 2:</strong> Open the Ubuntu terminal from the Start Menu and install Zubo:</p>
|
|
237
|
+
|
|
238
|
+
<pre><code>curl -fsSL https://zubo.bot/install.sh | bash</code></pre>
|
|
239
|
+
|
|
240
|
+
<p>That's it. Zubo runs inside WSL with full access to all features. The web dashboard is accessible from your Windows browser at <code>http://localhost:3000</code>.</p>
|
|
241
|
+
|
|
242
|
+
<h3 id="install-docker">Docker</h3>
|
|
243
|
+
|
|
244
|
+
<p>Run Zubo as a container with no local dependencies.</p>
|
|
245
|
+
|
|
246
|
+
<pre><code># Clone the repo
|
|
247
|
+
git clone https://github.com/apwn/zubo.git && cd zubo
|
|
248
|
+
|
|
249
|
+
# Copy the example env and add your API key
|
|
250
|
+
cp .env.example .env
|
|
251
|
+
|
|
252
|
+
# Start Zubo
|
|
253
|
+
docker compose up -d</code></pre>
|
|
254
|
+
|
|
255
|
+
<p>The container exposes port <code>8787</code> for the web dashboard and mounts <code>~/.zubo</code> for persistent data. Stop with <code>docker compose down</code>.</p>
|
|
256
|
+
|
|
257
|
+
<p>To configure, edit <code>.env</code> or mount your own <code>config.json</code>:</p>
|
|
258
|
+
|
|
259
|
+
<pre><code>volumes:
|
|
260
|
+
- ~/.zubo:/root/.zubo
|
|
261
|
+
- ./my-config.json:/root/.zubo/config.json</code></pre>
|
|
214
262
|
|
|
215
|
-
<
|
|
263
|
+
<h3 id="setup-wizard">The Setup Wizard</h3>
|
|
216
264
|
|
|
217
|
-
<
|
|
265
|
+
<p>After installing (on any platform), run the setup wizard:</p>
|
|
218
266
|
|
|
219
267
|
<pre><code>zubo setup</code></pre>
|
|
220
268
|
|
|
@@ -228,7 +276,7 @@ npm i -g zubo</code></pre>
|
|
|
228
276
|
<li>Create a default <code>SYSTEM.md</code> system prompt that you can customize later.</li>
|
|
229
277
|
</ul>
|
|
230
278
|
|
|
231
|
-
<h3>
|
|
279
|
+
<h3 id="starting">Starting Zubo</h3>
|
|
232
280
|
|
|
233
281
|
<pre><code># Start in foreground (logs to stdout)
|
|
234
282
|
zubo start
|
package/site/index.html
CHANGED
|
@@ -440,86 +440,179 @@
|
|
|
440
440
|
<h2 class="section-title">Running in <span class="gradient-text">60 seconds</span>.</h2>
|
|
441
441
|
</div>
|
|
442
442
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
</
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
443
|
+
<!-- OS toggle -->
|
|
444
|
+
<div class="os-toggle reveal">
|
|
445
|
+
<button class="os-btn active" data-os="mac" onclick="switchOS('mac')">
|
|
446
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
|
|
447
|
+
macOS / Linux
|
|
448
|
+
</button>
|
|
449
|
+
<button class="os-btn" data-os="windows" onclick="switchOS('windows')">
|
|
450
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801"/></svg>
|
|
451
|
+
Windows
|
|
452
|
+
</button>
|
|
453
|
+
<button class="os-btn" data-os="docker" onclick="switchOS('docker')">
|
|
454
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.186.186 0 00-.185.186v1.887c0 .102.083.185.185.185zm-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.186.186 0 00-.185.185v1.888c0 .102.082.185.185.186zm0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.186.186 0 00-.185.185v1.887c0 .102.082.186.185.186zm-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.186.186 0 00-.185.185v1.887c0 .102.083.186.185.186zm-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.186.186 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.186.186.186zm5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.186v1.887c0 .102.082.185.185.185zm-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.186v1.887c0 .102.083.185.185.185zm-2.964 0h2.119a.186.186 0 00.185-.185V9.006a.186.186 0 00-.185-.186H5.136a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185zm-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185zM23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.687 11.687 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.33 12.33 0 003.544-1.372 10.078 10.078 0 002.506-2.053c1.076-1.223 1.718-2.58 2.198-3.794h.19c1.18 0 1.907-.482 2.31-.882.265-.26.478-.57.625-.916l.087-.252z"/></svg>
|
|
455
|
+
Docker
|
|
456
|
+
</button>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<!-- macOS / Linux terminal -->
|
|
460
|
+
<div class="os-content active" data-os-content="mac">
|
|
461
|
+
<div class="qs-terminal glow-border reveal">
|
|
462
|
+
<div class="qs-chrome">
|
|
463
|
+
<div class="terminal-dots">
|
|
464
|
+
<span class="td td-r"></span>
|
|
465
|
+
<span class="td td-y"></span>
|
|
466
|
+
<span class="td td-g"></span>
|
|
462
467
|
</div>
|
|
463
|
-
<div class="qs-
|
|
464
|
-
<
|
|
465
|
-
<
|
|
466
|
-
<button class="
|
|
467
|
-
|
|
468
|
-
|
|
468
|
+
<div class="qs-tabs">
|
|
469
|
+
<button class="qs-tab active" data-tab="one-liner">One-liner</button>
|
|
470
|
+
<button class="qs-tab" data-tab="bun">bun</button>
|
|
471
|
+
<button class="qs-tab" data-tab="npm">npm</button>
|
|
472
|
+
</div>
|
|
473
|
+
<div class="qs-chrome-spacer"></div>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="qs-body">
|
|
476
|
+
<div class="qs-pane active" id="qs-one-liner">
|
|
477
|
+
<div class="qs-line">
|
|
478
|
+
<span class="qs-comment"># Install and launch in one shot</span>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="qs-line qs-cmd">
|
|
481
|
+
<span class="qs-prompt">$</span>
|
|
482
|
+
<span class="qs-text">curl -fsSL https://zubo.bot/install.sh | bash</span>
|
|
483
|
+
<button class="copy-btn" data-copy="curl -fsSL https://zubo.bot/install.sh | bash" aria-label="Copy command">
|
|
484
|
+
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
485
|
+
</button>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
<div class="qs-pane" id="qs-bun">
|
|
489
|
+
<div class="qs-line">
|
|
490
|
+
<span class="qs-comment"># Install Zubo</span>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="qs-line qs-cmd">
|
|
493
|
+
<span class="qs-prompt">$</span>
|
|
494
|
+
<span class="qs-text">bun add -g zubo</span>
|
|
495
|
+
<button class="copy-btn" data-copy="bun add -g zubo" aria-label="Copy command">
|
|
496
|
+
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
497
|
+
</button>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="qs-line qs-spacer"></div>
|
|
500
|
+
<div class="qs-line">
|
|
501
|
+
<span class="qs-comment"># Meet your new agent</span>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="qs-line qs-cmd">
|
|
504
|
+
<span class="qs-prompt">$</span>
|
|
505
|
+
<span class="qs-text">zubo setup</span>
|
|
506
|
+
<button class="copy-btn" data-copy="zubo setup" aria-label="Copy command">
|
|
507
|
+
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
508
|
+
</button>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="qs-pane" id="qs-npm">
|
|
512
|
+
<div class="qs-line">
|
|
513
|
+
<span class="qs-comment"># Install Zubo</span>
|
|
514
|
+
</div>
|
|
515
|
+
<div class="qs-line qs-cmd">
|
|
516
|
+
<span class="qs-prompt">$</span>
|
|
517
|
+
<span class="qs-text">npm i -g zubo</span>
|
|
518
|
+
<button class="copy-btn" data-copy="npm i -g zubo" aria-label="Copy command">
|
|
519
|
+
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
520
|
+
</button>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="qs-line qs-spacer"></div>
|
|
523
|
+
<div class="qs-line">
|
|
524
|
+
<span class="qs-comment"># Meet your new agent</span>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="qs-line qs-cmd">
|
|
527
|
+
<span class="qs-prompt">$</span>
|
|
528
|
+
<span class="qs-text">zubo setup</span>
|
|
529
|
+
<button class="copy-btn" data-copy="zubo setup" aria-label="Copy command">
|
|
530
|
+
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
531
|
+
</button>
|
|
532
|
+
</div>
|
|
469
533
|
</div>
|
|
470
534
|
</div>
|
|
471
|
-
|
|
472
|
-
|
|
535
|
+
</div>
|
|
536
|
+
<p class="qs-note reveal">Works on macOS & Linux. The installer sets up Bun and everything else for you.</p>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<!-- Windows terminal -->
|
|
540
|
+
<div class="os-content" data-os-content="windows">
|
|
541
|
+
<div class="qs-terminal glow-border reveal">
|
|
542
|
+
<div class="qs-chrome">
|
|
543
|
+
<div class="terminal-dots">
|
|
544
|
+
<span class="td td-r"></span>
|
|
545
|
+
<span class="td td-y"></span>
|
|
546
|
+
<span class="td td-g"></span>
|
|
547
|
+
</div>
|
|
548
|
+
<div class="terminal-title">PowerShell</div>
|
|
549
|
+
<div class="qs-chrome-spacer"></div>
|
|
550
|
+
</div>
|
|
551
|
+
<div class="qs-body">
|
|
473
552
|
<div class="qs-line">
|
|
474
|
-
<span class="qs-comment"># Install
|
|
553
|
+
<span class="qs-comment"># Install WSL (one-time, requires restart)</span>
|
|
475
554
|
</div>
|
|
476
555
|
<div class="qs-line qs-cmd">
|
|
477
|
-
<span class="qs-prompt"
|
|
478
|
-
<span class="qs-text">
|
|
479
|
-
<button class="copy-btn" data-copy="
|
|
556
|
+
<span class="qs-prompt">></span>
|
|
557
|
+
<span class="qs-text">wsl --install</span>
|
|
558
|
+
<button class="copy-btn" data-copy="wsl --install" aria-label="Copy command">
|
|
480
559
|
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
481
560
|
</button>
|
|
482
561
|
</div>
|
|
483
562
|
<div class="qs-line qs-spacer"></div>
|
|
484
563
|
<div class="qs-line">
|
|
485
|
-
<span class="qs-comment">#
|
|
564
|
+
<span class="qs-comment"># Then open WSL and install Zubo</span>
|
|
486
565
|
</div>
|
|
487
566
|
<div class="qs-line qs-cmd">
|
|
488
567
|
<span class="qs-prompt">$</span>
|
|
489
|
-
<span class="qs-text">zubo
|
|
490
|
-
<button class="copy-btn" data-copy="zubo
|
|
568
|
+
<span class="qs-text">curl -fsSL https://zubo.bot/install.sh | bash</span>
|
|
569
|
+
<button class="copy-btn" data-copy="curl -fsSL https://zubo.bot/install.sh | bash" aria-label="Copy command">
|
|
491
570
|
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
492
571
|
</button>
|
|
493
572
|
</div>
|
|
494
573
|
</div>
|
|
495
|
-
|
|
496
|
-
|
|
574
|
+
</div>
|
|
575
|
+
<p class="qs-note reveal">Runs inside WSL (Windows Subsystem for Linux). Full performance, native experience.</p>
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
<!-- Docker terminal -->
|
|
579
|
+
<div class="os-content" data-os-content="docker">
|
|
580
|
+
<div class="qs-terminal glow-border reveal">
|
|
581
|
+
<div class="qs-chrome">
|
|
582
|
+
<div class="terminal-dots">
|
|
583
|
+
<span class="td td-r"></span>
|
|
584
|
+
<span class="td td-y"></span>
|
|
585
|
+
<span class="td td-g"></span>
|
|
586
|
+
</div>
|
|
587
|
+
<div class="terminal-title">Terminal</div>
|
|
588
|
+
<div class="qs-chrome-spacer"></div>
|
|
589
|
+
</div>
|
|
590
|
+
<div class="qs-body">
|
|
497
591
|
<div class="qs-line">
|
|
498
|
-
<span class="qs-comment">#
|
|
592
|
+
<span class="qs-comment"># Clone and start with Docker</span>
|
|
499
593
|
</div>
|
|
500
594
|
<div class="qs-line qs-cmd">
|
|
501
595
|
<span class="qs-prompt">$</span>
|
|
502
|
-
<span class="qs-text">
|
|
503
|
-
<button class="copy-btn" data-copy="
|
|
596
|
+
<span class="qs-text">git clone https://github.com/apwn/zubo.git && cd zubo</span>
|
|
597
|
+
<button class="copy-btn" data-copy="git clone https://github.com/apwn/zubo.git && cd zubo" aria-label="Copy command">
|
|
504
598
|
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
505
599
|
</button>
|
|
506
600
|
</div>
|
|
507
601
|
<div class="qs-line qs-spacer"></div>
|
|
508
602
|
<div class="qs-line">
|
|
509
|
-
<span class="qs-comment">#
|
|
603
|
+
<span class="qs-comment"># Launch Zubo in a container</span>
|
|
510
604
|
</div>
|
|
511
605
|
<div class="qs-line qs-cmd">
|
|
512
606
|
<span class="qs-prompt">$</span>
|
|
513
|
-
<span class="qs-text">
|
|
514
|
-
<button class="copy-btn" data-copy="
|
|
607
|
+
<span class="qs-text">docker compose up -d</span>
|
|
608
|
+
<button class="copy-btn" data-copy="docker compose up -d" aria-label="Copy command">
|
|
515
609
|
<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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
516
610
|
</button>
|
|
517
611
|
</div>
|
|
518
612
|
</div>
|
|
519
613
|
</div>
|
|
614
|
+
<p class="qs-note reveal">Works on any OS with Docker. Dashboard at <strong>localhost:8787</strong>.</p>
|
|
520
615
|
</div>
|
|
521
|
-
|
|
522
|
-
<p class="qs-note reveal">Works on macOS & Linux. The installer sets up Bun and everything else for you.</p>
|
|
523
616
|
</div>
|
|
524
617
|
</section>
|
|
525
618
|
|
package/site/script.js
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
Vanilla JS, zero dependencies
|
|
4
4
|
============================================ */
|
|
5
5
|
|
|
6
|
+
/* OS toggle — must be global for inline onclick handlers */
|
|
7
|
+
function switchOS(os) {
|
|
8
|
+
document.querySelectorAll('.os-btn').forEach(function (b) { b.classList.remove('active'); });
|
|
9
|
+
document.querySelectorAll('.os-content').forEach(function (c) { c.classList.remove('active'); });
|
|
10
|
+
var btn = document.querySelector('.os-btn[data-os="' + os + '"]');
|
|
11
|
+
if (btn) btn.classList.add('active');
|
|
12
|
+
var content = document.querySelector('[data-os-content="' + os + '"]');
|
|
13
|
+
if (content) content.classList.add('active');
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
(function () {
|
|
7
17
|
'use strict';
|
|
8
18
|
|
|
@@ -231,14 +241,13 @@
|
|
|
231
241
|
});
|
|
232
242
|
|
|
233
243
|
/* ------------------------------------------
|
|
234
|
-
6. Quick Start tab switching
|
|
244
|
+
6. Quick Start tab switching (within macOS/Linux)
|
|
235
245
|
------------------------------------------ */
|
|
236
246
|
document.querySelectorAll('.qs-tab').forEach(function (tab) {
|
|
237
247
|
tab.addEventListener('click', function () {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
// Activate clicked tab and matching pane
|
|
248
|
+
var parent = tab.closest('.os-content') || document;
|
|
249
|
+
parent.querySelectorAll('.qs-tab').forEach(function (t) { t.classList.remove('active'); });
|
|
250
|
+
parent.querySelectorAll('.qs-pane').forEach(function (p) { p.classList.remove('active'); });
|
|
242
251
|
tab.classList.add('active');
|
|
243
252
|
var paneId = 'qs-' + tab.getAttribute('data-tab');
|
|
244
253
|
var pane = document.getElementById(paneId);
|
package/site/style.css
CHANGED
|
@@ -986,6 +986,44 @@ h1, h2, h3, h4,
|
|
|
986
986
|
color: #c4b5fd;
|
|
987
987
|
}
|
|
988
988
|
.qs-chrome-spacer { flex: 1; }
|
|
989
|
+
|
|
990
|
+
/* OS toggle */
|
|
991
|
+
.os-toggle {
|
|
992
|
+
display: flex;
|
|
993
|
+
justify-content: center;
|
|
994
|
+
gap: 6px;
|
|
995
|
+
margin-bottom: 24px;
|
|
996
|
+
}
|
|
997
|
+
.os-btn {
|
|
998
|
+
display: inline-flex;
|
|
999
|
+
align-items: center;
|
|
1000
|
+
gap: 8px;
|
|
1001
|
+
background: rgba(255,255,255,0.04);
|
|
1002
|
+
border: 1px solid var(--border);
|
|
1003
|
+
border-radius: 10px;
|
|
1004
|
+
padding: 10px 20px;
|
|
1005
|
+
font-family: var(--font);
|
|
1006
|
+
font-size: 14px;
|
|
1007
|
+
font-weight: 500;
|
|
1008
|
+
color: var(--text-muted);
|
|
1009
|
+
cursor: pointer;
|
|
1010
|
+
transition: all 0.25s var(--ease);
|
|
1011
|
+
}
|
|
1012
|
+
.os-btn:hover {
|
|
1013
|
+
color: var(--text-secondary);
|
|
1014
|
+
border-color: rgba(124, 58, 237, 0.2);
|
|
1015
|
+
background: rgba(255,255,255,0.06);
|
|
1016
|
+
}
|
|
1017
|
+
.os-btn.active {
|
|
1018
|
+
background: rgba(124, 58, 237, 0.12);
|
|
1019
|
+
border-color: rgba(124, 58, 237, 0.35);
|
|
1020
|
+
color: #c4b5fd;
|
|
1021
|
+
}
|
|
1022
|
+
.os-btn svg { opacity: 0.7; }
|
|
1023
|
+
.os-btn.active svg { opacity: 1; }
|
|
1024
|
+
.os-content { display: none; }
|
|
1025
|
+
.os-content.active { display: block; }
|
|
1026
|
+
|
|
989
1027
|
.qs-body {
|
|
990
1028
|
padding: 28px 28px 32px;
|
|
991
1029
|
font-family: var(--mono);
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
const API = "https://gmail.googleapis.com/gmail/v1/users/me";
|
|
2
2
|
|
|
3
|
+
/** RFC 2047 encode a header value if it contains non-ASCII characters (e.g. emojis) */
|
|
4
|
+
function mimeEncode(value: string): string {
|
|
5
|
+
if (/^[\x20-\x7E]*$/.test(value)) return value;
|
|
6
|
+
return `=?UTF-8?B?${Buffer.from(value, "utf-8").toString("base64")}?=`;
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
async function getToken(): Promise<string> {
|
|
4
10
|
const Zubo = (globalThis as any).Zubo;
|
|
5
11
|
if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
|
|
@@ -80,7 +86,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
80
86
|
case "send": {
|
|
81
87
|
if (!to) return JSON.stringify({ error: "to is required" });
|
|
82
88
|
if (!subject) return JSON.stringify({ error: "subject is required" });
|
|
83
|
-
const raw = Buffer.from(`To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body || ""}`).toString("base64url");
|
|
89
|
+
const raw = Buffer.from(`To: ${to}\r\nSubject: ${mimeEncode(subject)}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body || ""}`).toString("base64url");
|
|
84
90
|
const res = await fetch(`${API}/messages/send`, {
|
|
85
91
|
method: "POST", headers,
|
|
86
92
|
body: JSON.stringify({ raw }),
|
|
@@ -106,7 +112,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
106
112
|
const replyTo = getHeader("From");
|
|
107
113
|
const subj = getHeader("Subject").startsWith("Re:") ? getHeader("Subject") : `Re: ${getHeader("Subject")}`;
|
|
108
114
|
const msgId = getHeader("Message-ID");
|
|
109
|
-
const raw = Buffer.from(`To: ${replyTo}\r\nSubject: ${subj}\r\nIn-Reply-To: ${msgId}\r\nReferences: ${msgId}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body}`).toString("base64url");
|
|
115
|
+
const raw = Buffer.from(`To: ${replyTo}\r\nSubject: ${mimeEncode(subj)}\r\nIn-Reply-To: ${msgId}\r\nReferences: ${msgId}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body}`).toString("base64url");
|
|
110
116
|
const res = await fetch(`${API}/messages/send`, {
|
|
111
117
|
method: "POST", headers,
|
|
112
118
|
body: JSON.stringify({ raw, threadId: origData.threadId }),
|
|
@@ -37,11 +37,17 @@ function validateUrl(raw: string): void {
|
|
|
37
37
|
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
38
38
|
const url = input.url as string;
|
|
39
39
|
const method = ((input.method as string) || "GET").toUpperCase();
|
|
40
|
-
const
|
|
40
|
+
const rawHeaders = (input.headers as Record<string, string>) || {};
|
|
41
41
|
const body = input.body as string | undefined;
|
|
42
42
|
|
|
43
43
|
validateUrl(url);
|
|
44
44
|
|
|
45
|
+
// Strip non-ASCII characters from header values (HTTP/1.1 requires ASCII)
|
|
46
|
+
const headers: Record<string, string> = {};
|
|
47
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
48
|
+
headers[key] = String(value).replace(/[^\x20-\x7E]/g, "");
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
const opts: RequestInit = {
|
|
46
52
|
method,
|
|
47
53
|
headers: {
|