claude-b 0.3.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/LICENSE +229 -0
- package/README.md +877 -0
- package/assets/Claude-B.png +0 -0
- package/bin/cb +3 -0
- package/bin/cb-notify.sh +156 -0
- package/dist/chunk-6D3ICY25.js +303 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1643 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +5730 -0
- package/eslint.config.js +44 -0
- package/package.json +68 -0
- package/scripts/install.sh +124 -0
- package/start-daemon.sh +15 -0
- package/website/deploy.sh +112 -0
- package/website/index.html +73 -0
- package/website/install.sh +124 -0
- package/website/nginx.conf +30 -0
package/eslint.config.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from '@typescript-eslint/eslint-plugin';
|
|
3
|
+
import tsparser from '@typescript-eslint/parser';
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: ['dist/**', 'node_modules/**', '*.config.js']
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
files: ['src/**/*.ts'],
|
|
11
|
+
languageOptions: {
|
|
12
|
+
parser: tsparser,
|
|
13
|
+
parserOptions: {
|
|
14
|
+
ecmaVersion: 2022,
|
|
15
|
+
sourceType: 'module'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
plugins: {
|
|
19
|
+
'@typescript-eslint': tseslint
|
|
20
|
+
},
|
|
21
|
+
rules: {
|
|
22
|
+
// TypeScript specific rules
|
|
23
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
24
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
25
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
26
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
27
|
+
|
|
28
|
+
// General rules
|
|
29
|
+
'no-console': 'off',
|
|
30
|
+
'prefer-const': 'error',
|
|
31
|
+
'no-var': 'error',
|
|
32
|
+
'eqeqeq': ['error', 'always'],
|
|
33
|
+
'curly': ['error', 'multi-line'],
|
|
34
|
+
'no-throw-literal': 'error'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
files: ['src/**/*.test.ts'],
|
|
39
|
+
rules: {
|
|
40
|
+
// Relax rules for test files
|
|
41
|
+
'@typescript-eslint/no-explicit-any': 'off'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-b",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Background-capable Claude Code with async workflows and REST API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/cli/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cb": "./bin/cb"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsup src/cli/index.ts src/daemon/index.ts --watch --format esm",
|
|
12
|
+
"build": "tsup src/cli/index.ts src/daemon/index.ts --format esm --dts --clean",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"lint": "eslint src/",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"start:daemon": "node dist/daemon/index.js"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@anthropic-ai/sdk": "^0.74.0",
|
|
20
|
+
"@fastify/cors": "^10.0.0",
|
|
21
|
+
"@fastify/jwt": "^9.0.0",
|
|
22
|
+
"@fastify/rate-limit": "^10.0.0",
|
|
23
|
+
"@fastify/websocket": "^11.0.0",
|
|
24
|
+
"@speechmatics/batch-client": "^5.1.0",
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"commander": "^12.1.0",
|
|
27
|
+
"fastify": "^5.0.0",
|
|
28
|
+
"nanoid": "^5.0.7",
|
|
29
|
+
"node-pty": "^1.0.0",
|
|
30
|
+
"node-telegram-bot-api": "^0.67.0",
|
|
31
|
+
"ora": "^8.1.0",
|
|
32
|
+
"strip-ansi": "^7.1.0",
|
|
33
|
+
"ws": "^8.18.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.10.0",
|
|
37
|
+
"@types/node-telegram-bot-api": "^0.64.13",
|
|
38
|
+
"@types/ws": "^8.18.1",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
40
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
41
|
+
"eslint": "^9.39.2",
|
|
42
|
+
"tsup": "^8.3.0",
|
|
43
|
+
"typescript": "^5.7.0",
|
|
44
|
+
"vitest": "^2.1.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.0.0"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"claude",
|
|
51
|
+
"ai",
|
|
52
|
+
"cli",
|
|
53
|
+
"background",
|
|
54
|
+
"async",
|
|
55
|
+
"anthropic",
|
|
56
|
+
"claude-code"
|
|
57
|
+
],
|
|
58
|
+
"author": "danimoya",
|
|
59
|
+
"license": "AGPL-3.0",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/danimoya/Claude-B.git"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/danimoya/Claude-B#readme",
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/danimoya/Claude-B/issues"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude-B installer
|
|
3
|
+
# Usage:
|
|
4
|
+
# curl -fsSL https://cb.danielmoya.cv | bash
|
|
5
|
+
# curl -fsSL https://cb.danielmoya.cv | bash -s -- --method npm
|
|
6
|
+
#
|
|
7
|
+
# Methods (auto-detected, override with --method):
|
|
8
|
+
# npm — requires node >= 20
|
|
9
|
+
# docker — pulls ghcr.io/danimoya/claude-b:latest
|
|
10
|
+
# auto — prefers npm, falls back to docker (default)
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
REPO="${CLAUDE_B_REPO:-danimoya/Claude-B}"
|
|
14
|
+
GHCR_IMAGE="ghcr.io/${REPO,,}"
|
|
15
|
+
METHOD="auto"
|
|
16
|
+
SKIP_INIT=0
|
|
17
|
+
|
|
18
|
+
for arg in "$@"; do
|
|
19
|
+
case "$arg" in
|
|
20
|
+
--method=*) METHOD="${arg#*=}" ;;
|
|
21
|
+
--method) shift; METHOD="${1:-auto}" ;;
|
|
22
|
+
--skip-init) SKIP_INIT=1 ;;
|
|
23
|
+
-h|--help)
|
|
24
|
+
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
|
|
25
|
+
exit 0
|
|
26
|
+
;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
RESET='\033[0m'; BOLD='\033[1m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; GRAY='\033[90m'
|
|
31
|
+
|
|
32
|
+
say() { printf '%b\n' "${BOLD}==>${RESET} $*"; }
|
|
33
|
+
warn() { printf '%b\n' "${YELLOW}warning:${RESET} $*" >&2; }
|
|
34
|
+
fail() { printf '%b\n' "${RED}error:${RESET} $*" >&2; exit 1; }
|
|
35
|
+
|
|
36
|
+
have() { command -v "$1" >/dev/null 2>&1; }
|
|
37
|
+
|
|
38
|
+
check_node() {
|
|
39
|
+
have node || return 1
|
|
40
|
+
local major; major=$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)
|
|
41
|
+
[ "$major" -ge 20 ] || return 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pick_method() {
|
|
45
|
+
case "$METHOD" in
|
|
46
|
+
npm|docker) return 0 ;;
|
|
47
|
+
auto)
|
|
48
|
+
if check_node && have npm; then METHOD=npm
|
|
49
|
+
elif have docker; then METHOD=docker
|
|
50
|
+
else fail "neither node>=20 nor docker found. install one and re-run, or pick --method"
|
|
51
|
+
fi
|
|
52
|
+
;;
|
|
53
|
+
*) fail "unknown --method '$METHOD' (use npm|docker|auto)" ;;
|
|
54
|
+
esac
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
install_npm() {
|
|
58
|
+
say "Installing Claude-B via npm"
|
|
59
|
+
if [ "$(id -u)" -ne 0 ] && [ ! -w "$(npm prefix -g 2>/dev/null || echo /usr/local)" ]; then
|
|
60
|
+
warn "global npm install may need sudo"
|
|
61
|
+
if have sudo; then
|
|
62
|
+
sudo npm install -g claude-b
|
|
63
|
+
else
|
|
64
|
+
npm install -g claude-b
|
|
65
|
+
fi
|
|
66
|
+
else
|
|
67
|
+
npm install -g claude-b
|
|
68
|
+
fi
|
|
69
|
+
say "${GREEN}✓ installed${RESET}: $(command -v cb)"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
install_docker() {
|
|
73
|
+
say "Pulling Docker image ${GHCR_IMAGE}:latest"
|
|
74
|
+
docker pull "${GHCR_IMAGE}:latest"
|
|
75
|
+
|
|
76
|
+
local shim="/usr/local/bin/cb"
|
|
77
|
+
local data_dir="${HOME}/.claude-b"
|
|
78
|
+
mkdir -p "$data_dir"
|
|
79
|
+
|
|
80
|
+
say "Writing shim to ${shim}"
|
|
81
|
+
local shim_content
|
|
82
|
+
shim_content=$(cat <<EOF
|
|
83
|
+
#!/usr/bin/env bash
|
|
84
|
+
# Claude-B docker shim
|
|
85
|
+
exec docker run --rm -it \\
|
|
86
|
+
-v "\${HOME}/.claude-b:/root/.claude-b" \\
|
|
87
|
+
--env-file "\${HOME}/.claude-b/.env" 2>/dev/null || \\
|
|
88
|
+
exec docker run --rm -it \\
|
|
89
|
+
-v "\${HOME}/.claude-b:/root/.claude-b" \\
|
|
90
|
+
${GHCR_IMAGE}:latest "\$@"
|
|
91
|
+
EOF
|
|
92
|
+
)
|
|
93
|
+
if [ -w "$(dirname "$shim")" ]; then
|
|
94
|
+
printf '%s\n' "$shim_content" > "$shim"
|
|
95
|
+
chmod +x "$shim"
|
|
96
|
+
elif have sudo; then
|
|
97
|
+
printf '%s\n' "$shim_content" | sudo tee "$shim" >/dev/null
|
|
98
|
+
sudo chmod +x "$shim"
|
|
99
|
+
else
|
|
100
|
+
fail "cannot write to $shim (no sudo). try --method npm or run as root."
|
|
101
|
+
fi
|
|
102
|
+
say "${GREEN}✓ installed${RESET}: $shim"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main() {
|
|
106
|
+
say "${BOLD}Claude-B installer${RESET}"
|
|
107
|
+
pick_method
|
|
108
|
+
say "Method: ${BOLD}${METHOD}${RESET}"
|
|
109
|
+
|
|
110
|
+
case "$METHOD" in
|
|
111
|
+
npm) install_npm ;;
|
|
112
|
+
docker) install_docker ;;
|
|
113
|
+
esac
|
|
114
|
+
|
|
115
|
+
if [ "$SKIP_INIT" -eq 0 ]; then
|
|
116
|
+
say "Running ${BOLD}cb init${RESET} to configure your environment"
|
|
117
|
+
echo
|
|
118
|
+
cb init || warn "init skipped — rerun with: cb init"
|
|
119
|
+
else
|
|
120
|
+
say "Install complete. Configure with: ${BOLD}cb init${RESET}"
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main "$@"
|
package/start-daemon.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude-B daemon startup script for systemd
|
|
3
|
+
# Cleans stale files, starts the daemon, then enables the REST API.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SOCK="$HOME/.claude-b/daemon.sock"
|
|
8
|
+
PID_FILE="$HOME/.claude-b/daemon.pid"
|
|
9
|
+
REST_PORT="${CB_REST_PORT:-3847}"
|
|
10
|
+
|
|
11
|
+
# Clean stale files from previous crash
|
|
12
|
+
rm -f "$SOCK" "$PID_FILE"
|
|
13
|
+
|
|
14
|
+
# Start daemon (foreground — systemd manages the process)
|
|
15
|
+
exec /usr/bin/node /home/app/Claude-B/dist/daemon/index.js
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Deploy the claude-b.danielmoya.cv / cb.danielmoya.cv landing container.
|
|
3
|
+
#
|
|
4
|
+
# Prerequisites:
|
|
5
|
+
# - Docker daemon reachable
|
|
6
|
+
# - NPM (Nginx Proxy Manager) running at http://localhost:81
|
|
7
|
+
# - NPM credentials exported:
|
|
8
|
+
# NPM_USER=daniel.moya@...
|
|
9
|
+
# NPM_PASSWORD=...
|
|
10
|
+
# - A record for claude-b.danielmoya.cv + cb.danielmoya.cv pointing at the server IP
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# cd website && ./deploy.sh
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
cd "$(dirname "$0")"
|
|
17
|
+
|
|
18
|
+
# Keep install.sh in sync with scripts/install.sh (single source of truth)
|
|
19
|
+
cp ../scripts/install.sh install.sh
|
|
20
|
+
|
|
21
|
+
IMAGE="claude-b-landing:latest"
|
|
22
|
+
CONTAINER="claude-b-landing"
|
|
23
|
+
NETWORK="management-network"
|
|
24
|
+
DOMAINS='["claude-b.danielmoya.cv","cb.danielmoya.cv"]'
|
|
25
|
+
|
|
26
|
+
echo "==> Building $IMAGE"
|
|
27
|
+
docker build -t "$IMAGE" .
|
|
28
|
+
|
|
29
|
+
echo "==> Replacing container $CONTAINER"
|
|
30
|
+
docker rm -f "$CONTAINER" 2>/dev/null || true
|
|
31
|
+
docker run -d \
|
|
32
|
+
--name "$CONTAINER" \
|
|
33
|
+
--network "$NETWORK" \
|
|
34
|
+
--restart unless-stopped \
|
|
35
|
+
"$IMAGE"
|
|
36
|
+
|
|
37
|
+
# Create/update NPM proxy host
|
|
38
|
+
: "${NPM_USER:?export NPM_USER (see ~/.npm-credentials)}"
|
|
39
|
+
: "${NPM_PASSWORD:?export NPM_PASSWORD (see ~/.npm-credentials)}"
|
|
40
|
+
|
|
41
|
+
echo "==> Authenticating with NPM"
|
|
42
|
+
TOKEN=$(curl -fsS -X POST "http://localhost:81/api/tokens" \
|
|
43
|
+
-H "Content-Type: application/json" \
|
|
44
|
+
-d "{\"identity\":\"$NPM_USER\",\"secret\":\"$NPM_PASSWORD\"}" \
|
|
45
|
+
| python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
|
|
46
|
+
|
|
47
|
+
echo "==> Creating/updating proxy host"
|
|
48
|
+
HOST_ID=$(curl -fsS "http://localhost:81/api/nginx/proxy-hosts" \
|
|
49
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
50
|
+
| python3 -c "
|
|
51
|
+
import json, sys
|
|
52
|
+
hosts = json.load(sys.stdin)
|
|
53
|
+
for h in hosts:
|
|
54
|
+
if 'cb.danielmoya.cv' in h['domain_names'] or 'claude-b.danielmoya.cv' in h['domain_names']:
|
|
55
|
+
print(h['id']); break
|
|
56
|
+
")
|
|
57
|
+
|
|
58
|
+
PAYLOAD=$(cat <<EOF
|
|
59
|
+
{
|
|
60
|
+
"domain_names": $DOMAINS,
|
|
61
|
+
"forward_scheme": "http",
|
|
62
|
+
"forward_host": "$CONTAINER",
|
|
63
|
+
"forward_port": 80,
|
|
64
|
+
"allow_websocket_upgrade": false,
|
|
65
|
+
"block_exploits": true,
|
|
66
|
+
"caching_enabled": true,
|
|
67
|
+
"http2_support": true,
|
|
68
|
+
"advanced_config": "add_header X-Content-Type-Options nosniff;"
|
|
69
|
+
}
|
|
70
|
+
EOF
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if [ -n "$HOST_ID" ]; then
|
|
74
|
+
echo " → Updating existing proxy host $HOST_ID"
|
|
75
|
+
curl -fsS -X PUT "http://localhost:81/api/nginx/proxy-hosts/$HOST_ID" \
|
|
76
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
77
|
+
-H "Content-Type: application/json" \
|
|
78
|
+
-d "$PAYLOAD" >/dev/null
|
|
79
|
+
else
|
|
80
|
+
echo " → Creating new proxy host"
|
|
81
|
+
NEW=$(curl -fsS -X POST "http://localhost:81/api/nginx/proxy-hosts" \
|
|
82
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-d "$PAYLOAD")
|
|
85
|
+
HOST_ID=$(printf '%s' "$NEW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
echo "==> Requesting Let's Encrypt cert"
|
|
89
|
+
# NPM's create-cert schema only accepts a minimal meta — letsencrypt_email / agree
|
|
90
|
+
# are pulled from settings, not the request body.
|
|
91
|
+
CERT_ID=$(curl -fsS -X POST "http://localhost:81/api/nginx/certificates" \
|
|
92
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
93
|
+
-H "Content-Type: application/json" \
|
|
94
|
+
-d "{\"provider\":\"letsencrypt\",\"nice_name\":\"cb.danielmoya.cv\",\"domain_names\":$DOMAINS,\"meta\":{\"dns_challenge\":false}}" \
|
|
95
|
+
| python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' 2>/dev/null || echo "")
|
|
96
|
+
|
|
97
|
+
if [ -n "$CERT_ID" ]; then
|
|
98
|
+
echo " → Attaching cert $CERT_ID to proxy host $HOST_ID"
|
|
99
|
+
curl -fsS -X PUT "http://localhost:81/api/nginx/proxy-hosts/$HOST_ID" \
|
|
100
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
101
|
+
-H "Content-Type: application/json" \
|
|
102
|
+
-d "$(printf '%s' "$PAYLOAD" | python3 -c "
|
|
103
|
+
import json, sys
|
|
104
|
+
d = json.load(sys.stdin)
|
|
105
|
+
d.update({'certificate_id': $CERT_ID, 'ssl_forced': True, 'hsts_enabled': True})
|
|
106
|
+
print(json.dumps(d))
|
|
107
|
+
")" >/dev/null
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
echo
|
|
111
|
+
echo "✓ Deployed. Test with:"
|
|
112
|
+
echo " curl -fsSL https://cb.danielmoya.cv | head"
|
|
@@ -0,0 +1,73 @@
|
|
|
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" />
|
|
6
|
+
<title>Claude-B — background Claude Code</title>
|
|
7
|
+
<meta name="description" content="Claude-B: background-capable Claude Code with async workflows, Telegram bot, and multi-host orchestration." />
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0b0d10; --fg: #e6edf3; --mute: #8b949e; --accent: #79c0ff; --border: #30363d;
|
|
11
|
+
--code-bg: #161b22;
|
|
12
|
+
}
|
|
13
|
+
* { box-sizing: border-box; }
|
|
14
|
+
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; line-height: 1.55; }
|
|
15
|
+
main { max-width: 720px; margin: 0 auto; padding: 4rem 1.5rem 6rem; }
|
|
16
|
+
h1 { font-size: 2rem; margin: 0 0 0.25rem; }
|
|
17
|
+
h2 { font-size: 1.15rem; margin: 2.5rem 0 0.75rem; color: var(--accent); }
|
|
18
|
+
.tag { color: var(--mute); font-size: 0.95rem; margin-bottom: 2.5rem; }
|
|
19
|
+
pre {
|
|
20
|
+
background: var(--code-bg);
|
|
21
|
+
border: 1px solid var(--border);
|
|
22
|
+
border-radius: 6px;
|
|
23
|
+
padding: 1rem 1.1rem;
|
|
24
|
+
overflow-x: auto;
|
|
25
|
+
font-size: 0.92rem;
|
|
26
|
+
}
|
|
27
|
+
code { color: var(--accent); }
|
|
28
|
+
pre code { color: var(--fg); }
|
|
29
|
+
a { color: var(--accent); }
|
|
30
|
+
ul { padding-left: 1.25rem; }
|
|
31
|
+
li { margin: 0.3rem 0; }
|
|
32
|
+
.cta { display: inline-block; margin-top: 0.5rem; font-size: 0.9rem; color: var(--mute); }
|
|
33
|
+
.footer { margin-top: 4rem; color: var(--mute); font-size: 0.85rem; }
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body>
|
|
37
|
+
<main>
|
|
38
|
+
<h1>Claude-B</h1>
|
|
39
|
+
<p class="tag">Background-capable Claude Code — async workflows, Telegram bot, REST API.</p>
|
|
40
|
+
|
|
41
|
+
<h2>Install</h2>
|
|
42
|
+
<pre><code>curl -fsSL https://cb.danielmoya.cv | bash</code></pre>
|
|
43
|
+
<p class="cta">Auto-detects <code>npm</code> or <code>docker</code>. Use <code>--method docker</code> to force.</p>
|
|
44
|
+
|
|
45
|
+
<h2>Configure</h2>
|
|
46
|
+
<pre><code>cb init</code></pre>
|
|
47
|
+
<p class="cta">Interactive wizard — connects the Telegram bot and writes <code>~/.claude-b/.env</code>.</p>
|
|
48
|
+
|
|
49
|
+
<h2>What you get</h2>
|
|
50
|
+
<ul>
|
|
51
|
+
<li>Background Claude Code sessions with async prompts</li>
|
|
52
|
+
<li>Telegram bot for remote control + voice notes (Whisper + TTS)</li>
|
|
53
|
+
<li>REST API + WebSocket for programmatic access</li>
|
|
54
|
+
<li>Multi-host orchestration across machines</li>
|
|
55
|
+
</ul>
|
|
56
|
+
|
|
57
|
+
<h2>Manual install</h2>
|
|
58
|
+
<pre><code># npm
|
|
59
|
+
npm i -g claude-b
|
|
60
|
+
|
|
61
|
+
# Docker
|
|
62
|
+
docker run -d \
|
|
63
|
+
-v ~/.claude-b:/root/.claude-b \
|
|
64
|
+
--env-file ~/.claude-b/.env \
|
|
65
|
+
ghcr.io/danimoya/claude-b:latest</code></pre>
|
|
66
|
+
|
|
67
|
+
<p class="footer">
|
|
68
|
+
Source: <a href="https://github.com/danimoya/Claude-B">github.com/danimoya/Claude-B</a> ·
|
|
69
|
+
License: AGPL-3.0
|
|
70
|
+
</p>
|
|
71
|
+
</main>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude-B installer
|
|
3
|
+
# Usage:
|
|
4
|
+
# curl -fsSL https://cb.danielmoya.cv | bash
|
|
5
|
+
# curl -fsSL https://cb.danielmoya.cv | bash -s -- --method npm
|
|
6
|
+
#
|
|
7
|
+
# Methods (auto-detected, override with --method):
|
|
8
|
+
# npm — requires node >= 20
|
|
9
|
+
# docker — pulls ghcr.io/danimoya/claude-b:latest
|
|
10
|
+
# auto — prefers npm, falls back to docker (default)
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
REPO="${CLAUDE_B_REPO:-danimoya/Claude-B}"
|
|
14
|
+
GHCR_IMAGE="ghcr.io/${REPO,,}"
|
|
15
|
+
METHOD="auto"
|
|
16
|
+
SKIP_INIT=0
|
|
17
|
+
|
|
18
|
+
for arg in "$@"; do
|
|
19
|
+
case "$arg" in
|
|
20
|
+
--method=*) METHOD="${arg#*=}" ;;
|
|
21
|
+
--method) shift; METHOD="${1:-auto}" ;;
|
|
22
|
+
--skip-init) SKIP_INIT=1 ;;
|
|
23
|
+
-h|--help)
|
|
24
|
+
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
|
|
25
|
+
exit 0
|
|
26
|
+
;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
RESET='\033[0m'; BOLD='\033[1m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; GRAY='\033[90m'
|
|
31
|
+
|
|
32
|
+
say() { printf '%b\n' "${BOLD}==>${RESET} $*"; }
|
|
33
|
+
warn() { printf '%b\n' "${YELLOW}warning:${RESET} $*" >&2; }
|
|
34
|
+
fail() { printf '%b\n' "${RED}error:${RESET} $*" >&2; exit 1; }
|
|
35
|
+
|
|
36
|
+
have() { command -v "$1" >/dev/null 2>&1; }
|
|
37
|
+
|
|
38
|
+
check_node() {
|
|
39
|
+
have node || return 1
|
|
40
|
+
local major; major=$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)
|
|
41
|
+
[ "$major" -ge 20 ] || return 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pick_method() {
|
|
45
|
+
case "$METHOD" in
|
|
46
|
+
npm|docker) return 0 ;;
|
|
47
|
+
auto)
|
|
48
|
+
if check_node && have npm; then METHOD=npm
|
|
49
|
+
elif have docker; then METHOD=docker
|
|
50
|
+
else fail "neither node>=20 nor docker found. install one and re-run, or pick --method"
|
|
51
|
+
fi
|
|
52
|
+
;;
|
|
53
|
+
*) fail "unknown --method '$METHOD' (use npm|docker|auto)" ;;
|
|
54
|
+
esac
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
install_npm() {
|
|
58
|
+
say "Installing Claude-B via npm"
|
|
59
|
+
if [ "$(id -u)" -ne 0 ] && [ ! -w "$(npm prefix -g 2>/dev/null || echo /usr/local)" ]; then
|
|
60
|
+
warn "global npm install may need sudo"
|
|
61
|
+
if have sudo; then
|
|
62
|
+
sudo npm install -g claude-b
|
|
63
|
+
else
|
|
64
|
+
npm install -g claude-b
|
|
65
|
+
fi
|
|
66
|
+
else
|
|
67
|
+
npm install -g claude-b
|
|
68
|
+
fi
|
|
69
|
+
say "${GREEN}✓ installed${RESET}: $(command -v cb)"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
install_docker() {
|
|
73
|
+
say "Pulling Docker image ${GHCR_IMAGE}:latest"
|
|
74
|
+
docker pull "${GHCR_IMAGE}:latest"
|
|
75
|
+
|
|
76
|
+
local shim="/usr/local/bin/cb"
|
|
77
|
+
local data_dir="${HOME}/.claude-b"
|
|
78
|
+
mkdir -p "$data_dir"
|
|
79
|
+
|
|
80
|
+
say "Writing shim to ${shim}"
|
|
81
|
+
local shim_content
|
|
82
|
+
shim_content=$(cat <<EOF
|
|
83
|
+
#!/usr/bin/env bash
|
|
84
|
+
# Claude-B docker shim
|
|
85
|
+
exec docker run --rm -it \\
|
|
86
|
+
-v "\${HOME}/.claude-b:/root/.claude-b" \\
|
|
87
|
+
--env-file "\${HOME}/.claude-b/.env" 2>/dev/null || \\
|
|
88
|
+
exec docker run --rm -it \\
|
|
89
|
+
-v "\${HOME}/.claude-b:/root/.claude-b" \\
|
|
90
|
+
${GHCR_IMAGE}:latest "\$@"
|
|
91
|
+
EOF
|
|
92
|
+
)
|
|
93
|
+
if [ -w "$(dirname "$shim")" ]; then
|
|
94
|
+
printf '%s\n' "$shim_content" > "$shim"
|
|
95
|
+
chmod +x "$shim"
|
|
96
|
+
elif have sudo; then
|
|
97
|
+
printf '%s\n' "$shim_content" | sudo tee "$shim" >/dev/null
|
|
98
|
+
sudo chmod +x "$shim"
|
|
99
|
+
else
|
|
100
|
+
fail "cannot write to $shim (no sudo). try --method npm or run as root."
|
|
101
|
+
fi
|
|
102
|
+
say "${GREEN}✓ installed${RESET}: $shim"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main() {
|
|
106
|
+
say "${BOLD}Claude-B installer${RESET}"
|
|
107
|
+
pick_method
|
|
108
|
+
say "Method: ${BOLD}${METHOD}${RESET}"
|
|
109
|
+
|
|
110
|
+
case "$METHOD" in
|
|
111
|
+
npm) install_npm ;;
|
|
112
|
+
docker) install_docker ;;
|
|
113
|
+
esac
|
|
114
|
+
|
|
115
|
+
if [ "$SKIP_INIT" -eq 0 ]; then
|
|
116
|
+
say "Running ${BOLD}cb init${RESET} to configure your environment"
|
|
117
|
+
echo
|
|
118
|
+
cb init || warn "init skipped — rerun with: cb init"
|
|
119
|
+
else
|
|
120
|
+
say "Install complete. Configure with: ${BOLD}cb init${RESET}"
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main "$@"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 80 default_server;
|
|
3
|
+
server_name _;
|
|
4
|
+
|
|
5
|
+
root /usr/share/nginx/html;
|
|
6
|
+
|
|
7
|
+
# Default: serve install.sh when hit by curl/wget/other scripts,
|
|
8
|
+
# serve landing page when hit by browsers.
|
|
9
|
+
location = / {
|
|
10
|
+
default_type application/octet-stream;
|
|
11
|
+
if ($http_user_agent ~* "(curl|wget|fetch|HTTPie|Go-http-client|PowerShell)") {
|
|
12
|
+
rewrite ^ /install.sh last;
|
|
13
|
+
}
|
|
14
|
+
rewrite ^ /index.html last;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
location = /install.sh {
|
|
18
|
+
add_header Content-Type text/x-shellscript;
|
|
19
|
+
add_header Cache-Control "public, max-age=300";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
location = /install {
|
|
23
|
+
rewrite ^ /install.sh last;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Static landing page + assets
|
|
27
|
+
location / {
|
|
28
|
+
try_files $uri $uri/ =404;
|
|
29
|
+
}
|
|
30
|
+
}
|