zubo 0.1.0 → 0.1.2
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/.github/workflows/pages.yml +31 -0
- package/CONTRIBUTING.md +52 -0
- package/README.md +1 -1
- package/package.json +19 -1
- package/site/install.sh +98 -98
- package/src/channels/webchat.ts +5 -5
- package/src/model.ts +3 -4
- package/src/tools/builtin-integrations/google/gmail/handler.ts +3 -3
- package/src/tools/builtin-integrations/google/google_calendar/handler.ts +3 -3
- package/src/tools/builtin-integrations/google/google_docs/handler.ts +3 -3
- package/src/tools/builtin-integrations/google/google_drive/handler.ts +3 -3
- package/src/tools/builtin-integrations/google/google_sheets/handler.ts +3 -3
- package/src/tools/builtin-integrations/jira/jira_issues/handler.ts +1 -1
- package/src/tools/builtin-integrations/linear/linear_issues/handler.ts +1 -1
- package/src/tools/builtin-integrations/slack/slack_messages/handler.ts +1 -1
- package/src/tools/builtin-integrations/twitter/twitter_posts/handler.ts +2 -2
- package/src/tools/builtin-skills/http-request/handler.ts +1 -1
- package/src/tools/builtin-skills/shell/handler.ts +1 -1
- package/src/tools/builtin-skills/url-fetch/handler.ts +1 -1
- package/src/tools/builtin-skills/web-search/handler.ts +1 -1
- package/src/voice/stt.ts +1 -1
- package/src/voice/tts.ts +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Deploy to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths: [site/**]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pages: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: pages
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
environment:
|
|
22
|
+
name: github-pages
|
|
23
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/configure-pages@v5
|
|
27
|
+
- uses: actions/upload-pages-artifact@v3
|
|
28
|
+
with:
|
|
29
|
+
path: site
|
|
30
|
+
- id: deployment
|
|
31
|
+
uses: actions/deploy-pages@v4
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Contributing to Zubo
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing to Zubo! Whether you are fixing a bug, adding a feature, or writing a custom skill, we appreciate the help.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/your-org/zubo.git
|
|
9
|
+
cd zubo
|
|
10
|
+
bun install
|
|
11
|
+
zubo setup
|
|
12
|
+
bun run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Making Changes
|
|
16
|
+
|
|
17
|
+
1. Create a branch off `main` for your work.
|
|
18
|
+
2. Write your code. Zubo is built with Bun and TypeScript, so keep things typed.
|
|
19
|
+
3. Run tests before opening a PR:
|
|
20
|
+
```bash
|
|
21
|
+
bun test
|
|
22
|
+
```
|
|
23
|
+
4. Make sure the project type-checks cleanly:
|
|
24
|
+
```bash
|
|
25
|
+
npx tsc --noEmit
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Skills
|
|
29
|
+
|
|
30
|
+
Zubo supports custom skills -- self-contained TypeScript handlers that extend what the agent can do. You can contribute new skills by adding them to `~/.zubo/workspace/skills/`. Each skill is a TypeScript file that exports a handler. If you have an idea for a skill, open an issue first so we can discuss the design.
|
|
31
|
+
|
|
32
|
+
## Pull Requests
|
|
33
|
+
|
|
34
|
+
- Give your PR a clear, descriptive title.
|
|
35
|
+
- Explain what you changed and why.
|
|
36
|
+
- Make sure all tests pass and there are no type errors.
|
|
37
|
+
- Keep PRs focused. One concern per PR is easier to review.
|
|
38
|
+
|
|
39
|
+
## Reporting Bugs
|
|
40
|
+
|
|
41
|
+
If you run into a problem, please open an issue and include:
|
|
42
|
+
|
|
43
|
+
- A short description of the bug.
|
|
44
|
+
- Steps to reproduce it.
|
|
45
|
+
- What you expected to happen vs. what actually happened.
|
|
46
|
+
- Your OS and Bun version, if relevant.
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
|
|
50
|
+
For more details on how Zubo works, visit the docs: https://zubo.bot/docs
|
|
51
|
+
|
|
52
|
+
Thanks again for contributing.
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zubo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Your AI agent that never forgets. Persistent memory, 20+ tools, 6 channels, 11+ LLM providers — runs entirely on your machine.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "thomaskanze",
|
|
7
|
+
"homepage": "https://zubo.bot",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/apwn/zubo.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"llm",
|
|
16
|
+
"chatbot",
|
|
17
|
+
"memory",
|
|
18
|
+
"tools",
|
|
19
|
+
"bun",
|
|
20
|
+
"typescript"
|
|
21
|
+
],
|
|
4
22
|
"type": "module",
|
|
5
23
|
"bin": {
|
|
6
24
|
"zubo": "src/index.ts"
|
package/site/install.sh
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# Zubo Installer
|
|
5
|
-
# Usage: curl -fsSL https://zubo.bot/install.sh | bash
|
|
6
|
-
|
|
7
|
-
BOLD='\033[1m'
|
|
8
|
-
DIM='\033[2m'
|
|
9
|
-
GREEN='\033[32m'
|
|
10
|
-
YELLOW='\033[33m'
|
|
11
|
-
RED='\033[31m'
|
|
12
|
-
CYAN='\033[36m'
|
|
13
|
-
RESET='\033[0m'
|
|
14
|
-
|
|
15
|
-
info() { echo -e "${CYAN}${BOLD}→${RESET} $1"; }
|
|
16
|
-
ok() { echo -e "${GREEN}${BOLD}✓${RESET} $1"; }
|
|
17
|
-
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $1"; }
|
|
18
|
-
fail() { echo -e "${RED}${BOLD}✗${RESET} $1"; exit 1; }
|
|
19
|
-
|
|
20
|
-
echo ""
|
|
21
|
-
echo -e "${BOLD} zubo installer${RESET}"
|
|
22
|
-
echo -e "${DIM} The AI agent that never forgets${RESET}"
|
|
23
|
-
echo ""
|
|
24
|
-
|
|
25
|
-
# --- OS detection ---
|
|
26
|
-
OS="$(uname -s)"
|
|
27
|
-
ARCH="$(uname -m)"
|
|
28
|
-
|
|
29
|
-
case "$OS" in
|
|
30
|
-
Linux*) PLATFORM="linux" ;;
|
|
31
|
-
Darwin*) PLATFORM="darwin" ;;
|
|
32
|
-
*) fail "Unsupported OS: $OS. Zubo supports macOS and Linux." ;;
|
|
33
|
-
esac
|
|
34
|
-
|
|
35
|
-
case "$ARCH" in
|
|
36
|
-
x86_64|amd64) ARCH="x64" ;;
|
|
37
|
-
arm64|aarch64) ARCH="aarch64" ;;
|
|
38
|
-
*) fail "Unsupported architecture: $ARCH" ;;
|
|
39
|
-
esac
|
|
40
|
-
|
|
41
|
-
info "Detected ${PLATFORM}/${ARCH}"
|
|
42
|
-
|
|
43
|
-
# --- Install Bun if missing ---
|
|
44
|
-
if command -v bun &>/dev/null; then
|
|
45
|
-
BUN_VERSION=$(bun --version 2>/dev/null || echo "unknown")
|
|
46
|
-
ok "Bun already installed (v${BUN_VERSION})"
|
|
47
|
-
else
|
|
48
|
-
info "Installing Bun runtime..."
|
|
49
|
-
curl -fsSL https://bun.sh/install | bash
|
|
50
|
-
|
|
51
|
-
# Source the updated profile so bun is on PATH
|
|
52
|
-
export BUN_INSTALL="${HOME}/.bun"
|
|
53
|
-
export PATH="${BUN_INSTALL}/bin:${PATH}"
|
|
54
|
-
|
|
55
|
-
if command -v bun &>/dev/null; then
|
|
56
|
-
ok "Bun installed (v$(bun --version))"
|
|
57
|
-
else
|
|
58
|
-
fail "Bun installation failed. Install manually: https://bun.sh"
|
|
59
|
-
fi
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
# --- Install Zubo ---
|
|
63
|
-
info "Installing Zubo..."
|
|
64
|
-
|
|
65
|
-
if bun add -g zubo 2>/dev/null; then
|
|
66
|
-
ok "Zubo installed globally"
|
|
67
|
-
else
|
|
68
|
-
warn "Global install failed, trying with sudo..."
|
|
69
|
-
sudo bun add -g zubo || fail "Installation failed. Try: bun add -g zubo"
|
|
70
|
-
ok "Zubo installed globally (with sudo)"
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
# --- Verify ---
|
|
74
|
-
if command -v zubo &>/dev/null; then
|
|
75
|
-
ZUBO_VERSION=$(zubo --version 2>/dev/null || echo "installed")
|
|
76
|
-
ok "Zubo is ready (${ZUBO_VERSION})"
|
|
77
|
-
else
|
|
78
|
-
# Bun global bin might not be on PATH yet
|
|
79
|
-
ZUBO_BIN="${HOME}/.bun/bin/zubo"
|
|
80
|
-
if [ -f "$ZUBO_BIN" ]; then
|
|
81
|
-
warn "Zubo installed but not on PATH. Add this to your shell profile:"
|
|
82
|
-
echo ""
|
|
83
|
-
echo -e " ${DIM}export PATH=\"\$HOME/.bun/bin:\$PATH\"${RESET}"
|
|
84
|
-
echo ""
|
|
85
|
-
else
|
|
86
|
-
fail "Zubo binary not found after install"
|
|
87
|
-
fi
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
echo ""
|
|
91
|
-
echo -e "${BOLD} Next steps:${RESET}"
|
|
92
|
-
echo ""
|
|
93
|
-
echo -e " ${CYAN}1.${RESET} Run the setup wizard: ${BOLD}zubo setup${RESET}"
|
|
94
|
-
echo -e " ${CYAN}2.${RESET} Start your agent: ${BOLD}zubo start${RESET}"
|
|
95
|
-
echo -e " ${CYAN}3.${RESET} Open the dashboard: ${BOLD}zubo dashboard${RESET}"
|
|
96
|
-
echo ""
|
|
97
|
-
echo -e " ${DIM}Docs: https://zubo.bot/docs${RESET}"
|
|
98
|
-
echo ""
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Zubo Installer
|
|
5
|
+
# Usage: curl -fsSL https://zubo.bot/install.sh | bash
|
|
6
|
+
|
|
7
|
+
BOLD='\033[1m'
|
|
8
|
+
DIM='\033[2m'
|
|
9
|
+
GREEN='\033[32m'
|
|
10
|
+
YELLOW='\033[33m'
|
|
11
|
+
RED='\033[31m'
|
|
12
|
+
CYAN='\033[36m'
|
|
13
|
+
RESET='\033[0m'
|
|
14
|
+
|
|
15
|
+
info() { echo -e "${CYAN}${BOLD}→${RESET} $1"; }
|
|
16
|
+
ok() { echo -e "${GREEN}${BOLD}✓${RESET} $1"; }
|
|
17
|
+
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $1"; }
|
|
18
|
+
fail() { echo -e "${RED}${BOLD}✗${RESET} $1"; exit 1; }
|
|
19
|
+
|
|
20
|
+
echo ""
|
|
21
|
+
echo -e "${BOLD} zubo installer${RESET}"
|
|
22
|
+
echo -e "${DIM} The AI agent that never forgets${RESET}"
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
# --- OS detection ---
|
|
26
|
+
OS="$(uname -s)"
|
|
27
|
+
ARCH="$(uname -m)"
|
|
28
|
+
|
|
29
|
+
case "$OS" in
|
|
30
|
+
Linux*) PLATFORM="linux" ;;
|
|
31
|
+
Darwin*) PLATFORM="darwin" ;;
|
|
32
|
+
*) fail "Unsupported OS: $OS. Zubo supports macOS and Linux." ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
case "$ARCH" in
|
|
36
|
+
x86_64|amd64) ARCH="x64" ;;
|
|
37
|
+
arm64|aarch64) ARCH="aarch64" ;;
|
|
38
|
+
*) fail "Unsupported architecture: $ARCH" ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
info "Detected ${PLATFORM}/${ARCH}"
|
|
42
|
+
|
|
43
|
+
# --- Install Bun if missing ---
|
|
44
|
+
if command -v bun &>/dev/null; then
|
|
45
|
+
BUN_VERSION=$(bun --version 2>/dev/null || echo "unknown")
|
|
46
|
+
ok "Bun already installed (v${BUN_VERSION})"
|
|
47
|
+
else
|
|
48
|
+
info "Installing Bun runtime..."
|
|
49
|
+
curl -fsSL https://bun.sh/install | bash
|
|
50
|
+
|
|
51
|
+
# Source the updated profile so bun is on PATH
|
|
52
|
+
export BUN_INSTALL="${HOME}/.bun"
|
|
53
|
+
export PATH="${BUN_INSTALL}/bin:${PATH}"
|
|
54
|
+
|
|
55
|
+
if command -v bun &>/dev/null; then
|
|
56
|
+
ok "Bun installed (v$(bun --version))"
|
|
57
|
+
else
|
|
58
|
+
fail "Bun installation failed. Install manually: https://bun.sh"
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# --- Install Zubo ---
|
|
63
|
+
info "Installing Zubo..."
|
|
64
|
+
|
|
65
|
+
if bun add -g zubo 2>/dev/null; then
|
|
66
|
+
ok "Zubo installed globally"
|
|
67
|
+
else
|
|
68
|
+
warn "Global install failed, trying with sudo..."
|
|
69
|
+
sudo bun add -g zubo || fail "Installation failed. Try: bun add -g zubo"
|
|
70
|
+
ok "Zubo installed globally (with sudo)"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# --- Verify ---
|
|
74
|
+
if command -v zubo &>/dev/null; then
|
|
75
|
+
ZUBO_VERSION=$(zubo --version 2>/dev/null || echo "installed")
|
|
76
|
+
ok "Zubo is ready (${ZUBO_VERSION})"
|
|
77
|
+
else
|
|
78
|
+
# Bun global bin might not be on PATH yet
|
|
79
|
+
ZUBO_BIN="${HOME}/.bun/bin/zubo"
|
|
80
|
+
if [ -f "$ZUBO_BIN" ]; then
|
|
81
|
+
warn "Zubo installed but not on PATH. Add this to your shell profile:"
|
|
82
|
+
echo ""
|
|
83
|
+
echo -e " ${DIM}export PATH=\"\$HOME/.bun/bin:\$PATH\"${RESET}"
|
|
84
|
+
echo ""
|
|
85
|
+
else
|
|
86
|
+
fail "Zubo binary not found after install"
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
echo -e "${BOLD} Next steps:${RESET}"
|
|
92
|
+
echo ""
|
|
93
|
+
echo -e " ${CYAN}1.${RESET} Run the setup wizard: ${BOLD}zubo setup${RESET}"
|
|
94
|
+
echo -e " ${CYAN}2.${RESET} Start your agent: ${BOLD}zubo start${RESET}"
|
|
95
|
+
echo -e " ${CYAN}3.${RESET} Open the dashboard: ${BOLD}zubo dashboard${RESET}"
|
|
96
|
+
echo ""
|
|
97
|
+
echo -e " ${DIM}Docs: https://zubo.bot/docs${RESET}"
|
|
98
|
+
echo ""
|
package/src/channels/webchat.ts
CHANGED
|
@@ -196,7 +196,7 @@ function getConfigInfo(): {
|
|
|
196
196
|
function switchModelConfig(provider: string, model: string): { ok: boolean; error?: string } {
|
|
197
197
|
try {
|
|
198
198
|
if (!model) {
|
|
199
|
-
return { ok: false, error: "Model is required" };
|
|
199
|
+
return { ok: false, error: "Model name is required. Use the format: provider/model (e.g. anthropic/claude-sonnet-4-5-20250929)" };
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
const config = JSON.parse(readFileSync(paths.config, "utf-8"));
|
|
@@ -297,7 +297,7 @@ function handleDashboardApi(url: URL, req: Request): Response | null {
|
|
|
297
297
|
return (async () => {
|
|
298
298
|
const body = (await req.json()) as { provider?: string; model?: string };
|
|
299
299
|
if (!body.provider) {
|
|
300
|
-
return Response.json({ ok: false, error: "
|
|
300
|
+
return Response.json({ ok: false, error: "Provider name is required." }, { status: 400 });
|
|
301
301
|
}
|
|
302
302
|
const result = switchModelConfig(body.provider, body.model ?? "");
|
|
303
303
|
return Response.json(result, { status: result.ok ? 200 : 400 });
|
|
@@ -1443,7 +1443,7 @@ export function createWebChatAdapter(
|
|
|
1443
1443
|
const check = chatLimiter.check(ip);
|
|
1444
1444
|
if (!check.allowed) {
|
|
1445
1445
|
return Response.json(
|
|
1446
|
-
{ error: "
|
|
1446
|
+
{ error: "Too many requests. Please wait a moment and try again." },
|
|
1447
1447
|
{ status: 429, headers: { "Retry-After": String(Math.ceil((check.retryAfterMs ?? 1000) / 1000)) } }
|
|
1448
1448
|
);
|
|
1449
1449
|
}
|
|
@@ -1455,7 +1455,7 @@ export function createWebChatAdapter(
|
|
|
1455
1455
|
const check = uploadLimiter.check(ip);
|
|
1456
1456
|
if (!check.allowed) {
|
|
1457
1457
|
return Response.json(
|
|
1458
|
-
{ error: "
|
|
1458
|
+
{ error: "Too many requests. Please wait a moment and try again." },
|
|
1459
1459
|
{ status: 429, headers: { "Retry-After": String(Math.ceil((check.retryAfterMs ?? 1000) / 1000)) } }
|
|
1460
1460
|
);
|
|
1461
1461
|
}
|
|
@@ -1695,7 +1695,7 @@ export function createWebChatAdapter(
|
|
|
1695
1695
|
|
|
1696
1696
|
const stt = getSttProvider();
|
|
1697
1697
|
if (!stt) {
|
|
1698
|
-
return Response.json({ error: "
|
|
1698
|
+
return Response.json({ error: "Voice not configured. Add voice settings to your config:\n\nzubo config set voice.stt.provider whisper\nzubo config set voice.stt.apiKey YOUR_OPENAI_API_KEY\n\nThen restart Zubo." }, { status: 400 });
|
|
1699
1699
|
}
|
|
1700
1700
|
|
|
1701
1701
|
// Transcribe
|
package/src/model.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { configExists } from "./config/loader";
|
|
|
4
4
|
|
|
5
5
|
function readConfig(): any {
|
|
6
6
|
if (!configExists()) {
|
|
7
|
-
console.log("
|
|
7
|
+
console.log("No config found. Run 'zubo setup' to get started.");
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
10
|
return JSON.parse(readFileSync(paths.config, "utf-8"));
|
|
@@ -33,7 +33,7 @@ function showCurrent() {
|
|
|
33
33
|
} else if (config.anthropicApiKey) {
|
|
34
34
|
console.log(`\n Active: anthropic/${config.model ?? "claude-sonnet-4-5-20250929"} (legacy config)`);
|
|
35
35
|
} else {
|
|
36
|
-
console.log("\n No provider configured.");
|
|
36
|
+
console.log("\n No provider configured. Run 'zubo setup' to add one.");
|
|
37
37
|
}
|
|
38
38
|
console.log("");
|
|
39
39
|
}
|
|
@@ -95,8 +95,7 @@ function switchModel(target: string) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Provider not configured — offer quick setup for known providers
|
|
98
|
-
console.log(`Provider "${providerName}" is not configured.`);
|
|
99
|
-
console.log(`Run 'zubo setup' to add it, or edit ~/.zubo/config.json directly.`);
|
|
98
|
+
console.log(`Provider "${providerName}" is not configured. Run 'zubo setup' to add it.`);
|
|
100
99
|
process.exit(1);
|
|
101
100
|
}
|
|
102
101
|
|
|
@@ -3,7 +3,7 @@ const API = "https://gmail.googleapis.com/gmail/v1/users/me";
|
|
|
3
3
|
async function getToken(): Promise<string> {
|
|
4
4
|
const Zubo = (globalThis as any).Zubo;
|
|
5
5
|
if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
|
|
6
|
-
throw new Error("Google is
|
|
6
|
+
throw new Error("Google is not connected. Ask me to set up Google and I'll walk you through it.");
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
async function safeApiErr(res: Response, service: string): Promise<string> {
|
|
@@ -28,8 +28,8 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
28
28
|
} catch (err: any) {
|
|
29
29
|
return JSON.stringify({
|
|
30
30
|
error: err.message,
|
|
31
|
-
action_required: "Google is
|
|
32
|
-
"Ask
|
|
31
|
+
action_required: "Google is not connected. The user needs to set up Google OAuth. " +
|
|
32
|
+
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
33
33
|
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
34
34
|
});
|
|
35
35
|
}
|
|
@@ -3,7 +3,7 @@ const API = "https://www.googleapis.com/calendar/v3";
|
|
|
3
3
|
async function getToken(): Promise<string> {
|
|
4
4
|
const Zubo = (globalThis as any).Zubo;
|
|
5
5
|
if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
|
|
6
|
-
throw new Error("Google is
|
|
6
|
+
throw new Error("Google is not connected. Ask me to set up Google and I'll walk you through it.");
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function safeApiErr(status: number, statusText: string, service: string): string {
|
|
@@ -17,8 +17,8 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
17
17
|
} catch (err: any) {
|
|
18
18
|
return JSON.stringify({
|
|
19
19
|
error: err.message,
|
|
20
|
-
action_required: "Google is
|
|
21
|
-
"Ask
|
|
20
|
+
action_required: "Google is not connected. The user needs to set up Google OAuth. " +
|
|
21
|
+
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
22
22
|
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -3,7 +3,7 @@ const API = "https://docs.googleapis.com/v1/documents";
|
|
|
3
3
|
async function getToken(): Promise<string> {
|
|
4
4
|
const Zubo = (globalThis as any).Zubo;
|
|
5
5
|
if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
|
|
6
|
-
throw new Error("Google is
|
|
6
|
+
throw new Error("Google is not connected. Ask me to set up Google and I'll walk you through it.");
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function safeApiErr(status: number, statusText: string, service: string): string {
|
|
@@ -17,8 +17,8 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
17
17
|
} catch (err: any) {
|
|
18
18
|
return JSON.stringify({
|
|
19
19
|
error: err.message,
|
|
20
|
-
action_required: "Google is
|
|
21
|
-
"Ask
|
|
20
|
+
action_required: "Google is not connected. The user needs to set up Google OAuth. " +
|
|
21
|
+
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
22
22
|
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -3,7 +3,7 @@ const API = "https://www.googleapis.com/drive/v3";
|
|
|
3
3
|
async function getToken(): Promise<string> {
|
|
4
4
|
const Zubo = (globalThis as any).Zubo;
|
|
5
5
|
if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
|
|
6
|
-
throw new Error("Google is
|
|
6
|
+
throw new Error("Google is not connected. Ask me to set up Google and I'll walk you through it.");
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function safeApiErr(status: number, statusText: string, service: string): string {
|
|
@@ -17,8 +17,8 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
17
17
|
} catch (err: any) {
|
|
18
18
|
return JSON.stringify({
|
|
19
19
|
error: err.message,
|
|
20
|
-
action_required: "Google is
|
|
21
|
-
"Ask
|
|
20
|
+
action_required: "Google is not connected. The user needs to set up Google OAuth. " +
|
|
21
|
+
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
22
22
|
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -3,7 +3,7 @@ const API = "https://sheets.googleapis.com/v4/spreadsheets";
|
|
|
3
3
|
async function getToken(): Promise<string> {
|
|
4
4
|
const Zubo = (globalThis as any).Zubo;
|
|
5
5
|
if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
|
|
6
|
-
throw new Error("Google is
|
|
6
|
+
throw new Error("Google is not connected. Ask me to set up Google and I'll walk you through it.");
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function safeApiErr(status: number, statusText: string, service: string): string {
|
|
@@ -17,8 +17,8 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
17
17
|
} catch (err: any) {
|
|
18
18
|
return JSON.stringify({
|
|
19
19
|
error: err.message,
|
|
20
|
-
action_required: "Google is
|
|
21
|
-
"Ask
|
|
20
|
+
action_required: "Google is not connected. The user needs to set up Google OAuth. " +
|
|
21
|
+
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
22
22
|
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -14,7 +14,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
14
14
|
const email = (globalThis as any).Zubo?.getSecret?.("jira_email");
|
|
15
15
|
const baseUrl = (globalThis as any).Zubo?.getSecret?.("jira_url");
|
|
16
16
|
if (!token || !email || !baseUrl) {
|
|
17
|
-
return JSON.stringify({ error: "Jira not
|
|
17
|
+
return JSON.stringify({ error: "Jira is not connected. Tell me your Jira details and I'll set it up. I need: your Jira URL (e.g. yourteam.atlassian.net), email, and an API token from id.atlassian.net/manage-profile/security/api-tokens." });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Validate jira_url to prevent SSRF — must be HTTPS and a valid Jira host
|
|
@@ -22,7 +22,7 @@ async function gql(token: string, query: string, variables?: any): Promise<any>
|
|
|
22
22
|
|
|
23
23
|
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
24
24
|
const token = (globalThis as any).Zubo?.getSecret?.("linear_token");
|
|
25
|
-
if (!token) return JSON.stringify({ error: "Linear
|
|
25
|
+
if (!token) return JSON.stringify({ error: "Linear is not connected. Tell me your Linear API key and I'll set it up. You can create one at linear.app/settings/api." });
|
|
26
26
|
|
|
27
27
|
const { action, issue_id, title, description, team_id, query, assignee_id, priority } = input as {
|
|
28
28
|
action: string; issue_id?: string; title?: string; description?: string;
|
|
@@ -14,7 +14,7 @@ const API = "https://slack.com/api";
|
|
|
14
14
|
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
15
15
|
const token = (globalThis as any).Zubo?.getSecret?.("slack_token");
|
|
16
16
|
if (!token) {
|
|
17
|
-
return JSON.stringify({ error: "Slack
|
|
17
|
+
return JSON.stringify({ error: "Slack is not connected. Tell me your Slack Bot Token (starts with xoxb-) and I'll set it up." });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const { action, channel, text, query, limit } = input as {
|
|
@@ -14,7 +14,7 @@ const API = "https://api.twitter.com/2";
|
|
|
14
14
|
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
15
15
|
const bearer = (globalThis as any).Zubo?.getSecret?.("twitter_bearer_token");
|
|
16
16
|
if (!bearer) {
|
|
17
|
-
return JSON.stringify({ error: "Twitter
|
|
17
|
+
return JSON.stringify({ error: "Twitter is not connected. Tell me your Twitter Bearer Token and I'll set it up. You can get one from developer.x.com." });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const { action, text, query, tweet_id, max_results } = input as {
|
|
@@ -51,7 +51,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
51
51
|
const accessToken = (globalThis as any).Zubo?.getSecret?.("twitter_access_token");
|
|
52
52
|
const accessSecret = (globalThis as any).Zubo?.getSecret?.("twitter_access_secret");
|
|
53
53
|
if (!apiKey || !apiSecret || !accessToken || !accessSecret) {
|
|
54
|
-
return JSON.stringify({ error: "Twitter OAuth credentials
|
|
54
|
+
return JSON.stringify({ error: "Twitter posting requires OAuth credentials. Tell me your API Key, API Secret, Access Token, and Access Secret from developer.x.com and I'll set them up." });
|
|
55
55
|
}
|
|
56
56
|
// Use OAuth 1.0a -- simplified via fetch with pre-computed header
|
|
57
57
|
const oauthHeader = computeOAuth1Header("POST", `${API}/tweets`, {}, apiKey, apiSecret, accessToken, accessSecret);
|
|
@@ -26,7 +26,7 @@ function validateUrl(raw: string): void {
|
|
|
26
26
|
try {
|
|
27
27
|
parsed = new URL(raw);
|
|
28
28
|
} catch {
|
|
29
|
-
throw new Error(`Invalid URL: ${raw}
|
|
29
|
+
throw new Error(`Invalid URL: "${raw}". Make sure it starts with http:// or https://`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (isBlockedHost(parsed.hostname)) {
|
|
@@ -57,7 +57,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
57
57
|
// Block obviously destructive commands
|
|
58
58
|
for (const pattern of BLOCKED_PATTERNS) {
|
|
59
59
|
if (pattern.test(command)) {
|
|
60
|
-
throw new Error(`Blocked: command
|
|
60
|
+
throw new Error(`Blocked: this command is not allowed for safety reasons. Destructive system commands (rm -rf /, mkfs, shutdown, etc.) are restricted.`);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -12,7 +12,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
if (!res.ok) {
|
|
15
|
-
throw new Error(`
|
|
15
|
+
throw new Error(`Could not fetch URL (${res.status}). The page may be down or blocking requests.`);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const contentType = res.headers.get("content-type") || "";
|
|
@@ -12,7 +12,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
if (!res.ok) {
|
|
15
|
-
throw new Error(`Search failed
|
|
15
|
+
throw new Error(`Search failed (${res.status}). Try again in a moment.`);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const html = await res.text();
|
package/src/voice/stt.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function initStt(config: { provider: string; apiKey: string; model?: stri
|
|
|
48
48
|
logger.info("STT initialized: Whisper API");
|
|
49
49
|
break;
|
|
50
50
|
default:
|
|
51
|
-
logger.warn(`Unknown STT provider: ${config.provider}`);
|
|
51
|
+
logger.warn(`Unknown STT provider: "${config.provider}". Supported: whisper, openai`);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
package/src/voice/tts.ts
CHANGED
|
@@ -94,7 +94,7 @@ export function initTts(config: { provider: string; apiKey: string; voice?: stri
|
|
|
94
94
|
logger.info("TTS initialized: ElevenLabs");
|
|
95
95
|
break;
|
|
96
96
|
default:
|
|
97
|
-
logger.warn(`Unknown TTS provider: ${config.provider}`);
|
|
97
|
+
logger.warn(`Unknown TTS provider: "${config.provider}". Supported: openai, elevenlabs`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|