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.
@@ -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 "$@"
@@ -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
+ }