tlc-claude-code 1.2.28 → 1.2.29

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,477 @@
1
+ #!/bin/bash
2
+ #
3
+ # TLC VPS Setup Script
4
+ # Sets up TLC deployment server on Ubuntu VPS
5
+ #
6
+ # Usage:
7
+ # curl -fsSL https://raw.githubusercontent.com/jurgencalleja/TLC/main/scripts/vps-setup.sh | bash
8
+ #
9
+ # Or download and run:
10
+ # wget https://raw.githubusercontent.com/jurgencalleja/TLC/main/scripts/vps-setup.sh
11
+ # chmod +x vps-setup.sh
12
+ # ./vps-setup.sh
13
+ #
14
+
15
+ set -e
16
+
17
+ # Colors
18
+ RED='\033[0;31m'
19
+ GREEN='\033[0;32m'
20
+ YELLOW='\033[1;33m'
21
+ BLUE='\033[0;34m'
22
+ NC='\033[0m' # No Color
23
+
24
+ log() { echo -e "${BLUE}[TLC]${NC} $1"; }
25
+ success() { echo -e "${GREEN}[TLC]${NC} $1"; }
26
+ warn() { echo -e "${YELLOW}[TLC]${NC} $1"; }
27
+ error() { echo -e "${RED}[TLC]${NC} $1"; exit 1; }
28
+
29
+ # Banner
30
+ echo ""
31
+ echo -e "${BLUE} ████████╗██╗ ██████╗${NC}"
32
+ echo -e "${BLUE} ╚══██╔══╝██║ ██╔════╝${NC}"
33
+ echo -e "${BLUE} ██║ ██║ ██║ ${NC}"
34
+ echo -e "${BLUE} ██║ ██║ ██║ ${NC}"
35
+ echo -e "${BLUE} ██║ ███████╗╚██████╗${NC}"
36
+ echo -e "${BLUE} ╚═╝ ╚══════╝ ╚═════╝${NC}"
37
+ echo ""
38
+ echo -e " ${GREEN}TLC VPS Setup${NC}"
39
+ echo -e " ${YELLOW}Deployment Server for Teams${NC}"
40
+ echo ""
41
+
42
+ # Check if running as root
43
+ if [ "$EUID" -eq 0 ]; then
44
+ error "Don't run as root. Script will use sudo when needed."
45
+ fi
46
+
47
+ # Configuration
48
+ TLC_DIR="/opt/tlc"
49
+ TLC_USER="tlc"
50
+ TLC_PORT=3147
51
+ NODE_VERSION="20"
52
+
53
+ # Prompt for configuration
54
+ read -p "Enter your domain (e.g., project.example.com): " DOMAIN
55
+ if [ -z "$DOMAIN" ]; then
56
+ error "Domain is required"
57
+ fi
58
+
59
+ read -p "Enter admin email: " ADMIN_EMAIL
60
+ if [ -z "$ADMIN_EMAIL" ]; then
61
+ error "Admin email is required"
62
+ fi
63
+
64
+ read -p "Enter Slack webhook URL (optional, press Enter to skip): " SLACK_WEBHOOK
65
+
66
+ # Generate secrets
67
+ JWT_SECRET=$(openssl rand -hex 32)
68
+ WEBHOOK_SECRET=$(openssl rand -hex 16)
69
+ ADMIN_PASSWORD=$(openssl rand -base64 12)
70
+
71
+ log "Starting TLC VPS setup..."
72
+ log "Domain: $DOMAIN"
73
+ log "Admin: $ADMIN_EMAIL"
74
+
75
+ # Step 1: System updates
76
+ log "Updating system packages..."
77
+ sudo apt-get update -qq
78
+ sudo apt-get upgrade -y -qq
79
+
80
+ # Step 2: Install dependencies
81
+ log "Installing dependencies..."
82
+ sudo apt-get install -y -qq \
83
+ curl \
84
+ git \
85
+ nginx \
86
+ certbot \
87
+ python3-certbot-nginx \
88
+ postgresql \
89
+ postgresql-contrib
90
+
91
+ # Step 3: Install Node.js
92
+ log "Installing Node.js $NODE_VERSION..."
93
+ if ! command -v node &> /dev/null; then
94
+ curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | sudo -E bash -
95
+ sudo apt-get install -y -qq nodejs
96
+ fi
97
+ success "Node.js $(node -v) installed"
98
+
99
+ # Step 4: Install Docker
100
+ log "Installing Docker..."
101
+ if ! command -v docker &> /dev/null; then
102
+ curl -fsSL https://get.docker.com | sudo sh
103
+ sudo usermod -aG docker $USER
104
+ fi
105
+ success "Docker installed"
106
+
107
+ # Step 5: Create TLC user
108
+ log "Creating TLC user..."
109
+ if ! id "$TLC_USER" &>/dev/null; then
110
+ sudo useradd -r -s /bin/bash -d $TLC_DIR $TLC_USER
111
+ fi
112
+
113
+ # Step 6: Create directories
114
+ log "Creating directories..."
115
+ sudo mkdir -p $TLC_DIR/{deployments,logs,config}
116
+ sudo chown -R $TLC_USER:$TLC_USER $TLC_DIR
117
+
118
+ # Step 7: Setup PostgreSQL
119
+ log "Setting up PostgreSQL..."
120
+ sudo -u postgres psql -c "CREATE USER tlc WITH PASSWORD '$JWT_SECRET';" 2>/dev/null || true
121
+ sudo -u postgres psql -c "CREATE DATABASE tlc OWNER tlc;" 2>/dev/null || true
122
+ sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE tlc TO tlc;" 2>/dev/null || true
123
+
124
+ # Create users table
125
+ sudo -u postgres psql -d tlc -c "
126
+ CREATE TABLE IF NOT EXISTS users (
127
+ id SERIAL PRIMARY KEY,
128
+ email VARCHAR(255) UNIQUE NOT NULL,
129
+ password_hash VARCHAR(255) NOT NULL,
130
+ name VARCHAR(255),
131
+ role VARCHAR(50) DEFAULT 'engineer',
132
+ created_at TIMESTAMP DEFAULT NOW(),
133
+ last_login TIMESTAMP
134
+ );
135
+ " 2>/dev/null || true
136
+
137
+ success "PostgreSQL configured"
138
+
139
+ # Step 8: Install TLC server
140
+ log "Installing TLC server..."
141
+ sudo -u $TLC_USER bash -c "
142
+ cd $TLC_DIR
143
+ npm init -y
144
+ npm install tlc-claude-code express ws http-proxy-middleware jsonwebtoken bcryptjs pg dotenv
145
+ "
146
+
147
+ # Step 9: Create server configuration
148
+ log "Creating server configuration..."
149
+ sudo tee $TLC_DIR/.env > /dev/null <<EOF
150
+ # TLC Server Configuration
151
+ NODE_ENV=production
152
+ PORT=$TLC_PORT
153
+ DOMAIN=$DOMAIN
154
+
155
+ # Security
156
+ JWT_SECRET=$JWT_SECRET
157
+ WEBHOOK_SECRET=$WEBHOOK_SECRET
158
+
159
+ # Database
160
+ DATABASE_URL=postgres://tlc:$JWT_SECRET@localhost:5432/tlc
161
+
162
+ # Slack (optional)
163
+ SLACK_WEBHOOK_URL=$SLACK_WEBHOOK
164
+
165
+ # Admin
166
+ ADMIN_EMAIL=$ADMIN_EMAIL
167
+ ADMIN_PASSWORD_HASH=\$(node -e "console.log(require('bcryptjs').hashSync('$ADMIN_PASSWORD', 10))")
168
+ EOF
169
+
170
+ # Step 10: Create TLC server script
171
+ log "Creating TLC server..."
172
+ sudo tee $TLC_DIR/server.js > /dev/null <<'SERVEREOF'
173
+ const express = require('express');
174
+ const { createServer } = require('http');
175
+ const { WebSocketServer } = require('ws');
176
+ const { createProxyMiddleware } = require('http-proxy-middleware');
177
+ const jwt = require('jsonwebtoken');
178
+ const bcrypt = require('bcryptjs');
179
+ const { Pool } = require('pg');
180
+ const path = require('path');
181
+ const fs = require('fs');
182
+ const { execSync, spawn } = require('child_process');
183
+ require('dotenv').config();
184
+
185
+ const app = express();
186
+ const server = createServer(app);
187
+ const wss = new WebSocketServer({ server });
188
+
189
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
190
+ const PORT = process.env.PORT || 3147;
191
+ const DEPLOYMENTS_DIR = '/opt/tlc/deployments';
192
+
193
+ // Middleware
194
+ app.use(express.json());
195
+ app.use(express.static(path.join(__dirname, 'node_modules/tlc-claude-code/server/dashboard')));
196
+
197
+ // Branch port mapping
198
+ const branchPorts = new Map();
199
+ let nextPort = 10000;
200
+
201
+ // Auth middleware
202
+ const authenticate = async (req, res, next) => {
203
+ const token = req.headers.authorization?.replace('Bearer ', '');
204
+ if (!token) return res.status(401).json({ error: 'No token' });
205
+
206
+ try {
207
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
208
+ req.user = decoded;
209
+ next();
210
+ } catch (e) {
211
+ res.status(401).json({ error: 'Invalid token' });
212
+ }
213
+ };
214
+
215
+ // Login
216
+ app.post('/api/login', async (req, res) => {
217
+ const { email, password } = req.body;
218
+
219
+ try {
220
+ const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
221
+ const user = result.rows[0];
222
+
223
+ if (!user || !bcrypt.compareSync(password, user.password_hash)) {
224
+ return res.status(401).json({ error: 'Invalid credentials' });
225
+ }
226
+
227
+ const token = jwt.sign(
228
+ { id: user.id, email: user.email, role: user.role },
229
+ process.env.JWT_SECRET,
230
+ { expiresIn: '7d' }
231
+ );
232
+
233
+ await pool.query('UPDATE users SET last_login = NOW() WHERE id = $1', [user.id]);
234
+
235
+ res.json({ token, user: { email: user.email, role: user.role } });
236
+ } catch (e) {
237
+ res.status(500).json({ error: e.message });
238
+ }
239
+ });
240
+
241
+ // List deployments
242
+ app.get('/api/deployments', authenticate, async (req, res) => {
243
+ const deployments = [];
244
+
245
+ if (fs.existsSync(DEPLOYMENTS_DIR)) {
246
+ const branches = fs.readdirSync(DEPLOYMENTS_DIR);
247
+ for (const branch of branches) {
248
+ const port = branchPorts.get(branch) || 'stopped';
249
+ deployments.push({
250
+ branch,
251
+ port,
252
+ url: `https://${branch}.${process.env.DOMAIN}`,
253
+ status: port !== 'stopped' ? 'running' : 'stopped'
254
+ });
255
+ }
256
+ }
257
+
258
+ res.json(deployments);
259
+ });
260
+
261
+ // Deploy branch
262
+ app.post('/api/deploy', authenticate, async (req, res) => {
263
+ const { branch, repo } = req.body;
264
+ if (!branch) return res.status(400).json({ error: 'Branch required' });
265
+
266
+ const safeBranch = branch.replace(/[^a-zA-Z0-9-]/g, '-');
267
+ const deployDir = path.join(DEPLOYMENTS_DIR, safeBranch);
268
+
269
+ try {
270
+ // Clone or pull
271
+ if (!fs.existsSync(deployDir)) {
272
+ execSync(`git clone -b ${branch} ${repo} ${deployDir}`);
273
+ } else {
274
+ execSync(`cd ${deployDir} && git pull`);
275
+ }
276
+
277
+ // Install deps
278
+ execSync(`cd ${deployDir} && npm install`);
279
+
280
+ // Assign port
281
+ const port = nextPort++;
282
+ branchPorts.set(safeBranch, port);
283
+
284
+ // Start container or process
285
+ spawn('npm', ['start'], {
286
+ cwd: deployDir,
287
+ env: { ...process.env, PORT: port.toString() },
288
+ detached: true,
289
+ stdio: 'ignore'
290
+ }).unref();
291
+
292
+ // Notify Slack
293
+ if (process.env.SLACK_WEBHOOK_URL) {
294
+ fetch(process.env.SLACK_WEBHOOK_URL, {
295
+ method: 'POST',
296
+ headers: { 'Content-Type': 'application/json' },
297
+ body: JSON.stringify({
298
+ text: `🚀 Deployed ${branch} to https://${safeBranch}.${process.env.DOMAIN}`
299
+ })
300
+ }).catch(() => {});
301
+ }
302
+
303
+ res.json({
304
+ success: true,
305
+ url: `https://${safeBranch}.${process.env.DOMAIN}`,
306
+ port
307
+ });
308
+ } catch (e) {
309
+ res.status(500).json({ error: e.message });
310
+ }
311
+ });
312
+
313
+ // GitHub webhook
314
+ app.post('/api/webhook', (req, res) => {
315
+ const signature = req.headers['x-hub-signature-256'];
316
+ // Verify signature in production
317
+
318
+ const { ref, repository } = req.body;
319
+ const branch = ref?.replace('refs/heads/', '');
320
+
321
+ if (branch) {
322
+ // Trigger deployment
323
+ fetch(`http://localhost:${PORT}/api/deploy`, {
324
+ method: 'POST',
325
+ headers: {
326
+ 'Content-Type': 'application/json',
327
+ 'Authorization': `Bearer ${jwt.sign({ role: 'system' }, process.env.JWT_SECRET)}`
328
+ },
329
+ body: JSON.stringify({ branch, repo: repository.clone_url })
330
+ }).catch(() => {});
331
+ }
332
+
333
+ res.json({ received: true });
334
+ });
335
+
336
+ // Proxy to branch deployments
337
+ app.use('/preview/:branch', (req, res, next) => {
338
+ const branch = req.params.branch;
339
+ const port = branchPorts.get(branch);
340
+
341
+ if (!port) {
342
+ return res.status(404).send('Deployment not found');
343
+ }
344
+
345
+ createProxyMiddleware({
346
+ target: `http://localhost:${port}`,
347
+ changeOrigin: true,
348
+ pathRewrite: { [`^/preview/${branch}`]: '' }
349
+ })(req, res, next);
350
+ });
351
+
352
+ // WebSocket for logs
353
+ wss.on('connection', (ws) => {
354
+ ws.send(JSON.stringify({ type: 'connected' }));
355
+ });
356
+
357
+ // Start server
358
+ server.listen(PORT, () => {
359
+ console.log(`TLC Deploy Server running on port ${PORT}`);
360
+ console.log(`Dashboard: https://dashboard.${process.env.DOMAIN}`);
361
+ });
362
+ SERVEREOF
363
+
364
+ sudo chown $TLC_USER:$TLC_USER $TLC_DIR/.env $TLC_DIR/server.js
365
+
366
+ # Step 11: Create systemd service
367
+ log "Creating systemd service..."
368
+ sudo tee /etc/systemd/system/tlc.service > /dev/null <<EOF
369
+ [Unit]
370
+ Description=TLC Deployment Server
371
+ After=network.target postgresql.service
372
+
373
+ [Service]
374
+ Type=simple
375
+ User=$TLC_USER
376
+ WorkingDirectory=$TLC_DIR
377
+ ExecStart=/usr/bin/node server.js
378
+ Restart=on-failure
379
+ RestartSec=10
380
+ Environment=NODE_ENV=production
381
+
382
+ [Install]
383
+ WantedBy=multi-user.target
384
+ EOF
385
+
386
+ sudo systemctl daemon-reload
387
+ sudo systemctl enable tlc
388
+ sudo systemctl start tlc
389
+
390
+ # Step 12: Configure nginx
391
+ log "Configuring nginx..."
392
+ sudo tee /etc/nginx/sites-available/tlc > /dev/null <<EOF
393
+ # TLC Dashboard
394
+ server {
395
+ listen 80;
396
+ server_name dashboard.$DOMAIN;
397
+
398
+ location / {
399
+ proxy_pass http://localhost:$TLC_PORT;
400
+ proxy_http_version 1.1;
401
+ proxy_set_header Upgrade \$http_upgrade;
402
+ proxy_set_header Connection "upgrade";
403
+ proxy_set_header Host \$host;
404
+ proxy_set_header X-Real-IP \$remote_addr;
405
+ }
406
+ }
407
+
408
+ # Branch deployments (wildcard)
409
+ server {
410
+ listen 80;
411
+ server_name ~^(?<branch>.+)\.$DOMAIN\$;
412
+
413
+ location / {
414
+ proxy_pass http://localhost:$TLC_PORT/preview/\$branch;
415
+ proxy_http_version 1.1;
416
+ proxy_set_header Upgrade \$http_upgrade;
417
+ proxy_set_header Connection "upgrade";
418
+ proxy_set_header Host \$host;
419
+ proxy_set_header X-Real-IP \$remote_addr;
420
+ }
421
+ }
422
+ EOF
423
+
424
+ sudo ln -sf /etc/nginx/sites-available/tlc /etc/nginx/sites-enabled/
425
+ sudo nginx -t && sudo systemctl reload nginx
426
+
427
+ # Step 13: Setup SSL
428
+ log "Setting up SSL certificates..."
429
+ sudo certbot --nginx -d "dashboard.$DOMAIN" -d "*.$DOMAIN" --email "$ADMIN_EMAIL" --agree-tos --non-interactive || {
430
+ warn "SSL setup failed. Run manually: sudo certbot --nginx -d dashboard.$DOMAIN"
431
+ }
432
+
433
+ # Step 14: Create admin user
434
+ log "Creating admin user..."
435
+ ADMIN_HASH=$(node -e "console.log(require('bcryptjs').hashSync('$ADMIN_PASSWORD', 10))")
436
+ sudo -u postgres psql -d tlc -c "
437
+ INSERT INTO users (email, password_hash, name, role)
438
+ VALUES ('$ADMIN_EMAIL', '$ADMIN_HASH', 'Admin', 'admin')
439
+ ON CONFLICT (email) DO UPDATE SET password_hash = '$ADMIN_HASH';
440
+ "
441
+
442
+ # Step 15: Print summary
443
+ echo ""
444
+ echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}"
445
+ echo -e "${GREEN} TLC VPS Setup Complete!${NC}"
446
+ echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}"
447
+ echo ""
448
+ echo -e " ${BLUE}Dashboard:${NC} https://dashboard.$DOMAIN"
449
+ echo -e " ${BLUE}Deployments:${NC} https://{branch}.$DOMAIN"
450
+ echo ""
451
+ echo -e " ${BLUE}Admin Login:${NC}"
452
+ echo -e " Email: $ADMIN_EMAIL"
453
+ echo -e " Password: ${YELLOW}$ADMIN_PASSWORD${NC}"
454
+ echo ""
455
+ echo -e " ${BLUE}Webhook URL:${NC} https://dashboard.$DOMAIN/api/webhook"
456
+ echo -e " ${BLUE}Webhook Secret:${NC} $WEBHOOK_SECRET"
457
+ echo ""
458
+ echo -e " ${BLUE}DNS Configuration:${NC}"
459
+ echo -e " Add these records to your DNS:"
460
+ echo -e " dashboard.$DOMAIN A $(curl -s ifconfig.me)"
461
+ echo -e " *.$DOMAIN A $(curl -s ifconfig.me)"
462
+ echo ""
463
+ echo -e " ${BLUE}GitHub Webhook Setup:${NC}"
464
+ echo -e " 1. Go to your repo Settings > Webhooks"
465
+ echo -e " 2. Add webhook: https://dashboard.$DOMAIN/api/webhook"
466
+ echo -e " 3. Content type: application/json"
467
+ echo -e " 4. Secret: $WEBHOOK_SECRET"
468
+ echo -e " 5. Events: Push events"
469
+ echo ""
470
+ echo -e " ${BLUE}Files:${NC}"
471
+ echo -e " Config: $TLC_DIR/.env"
472
+ echo -e " Logs: journalctl -u tlc -f"
473
+ echo -e " Service: sudo systemctl {start|stop|restart} tlc"
474
+ echo ""
475
+ echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}"
476
+ echo ""
477
+ success "Setup complete! Save the credentials above."
@@ -0,0 +1,91 @@
1
+ # TLC Documentation Sync
2
+ # Auto-generated by /tlc:docs setup
3
+ # Automatically syncs documentation on push
4
+
5
+ name: Documentation Sync
6
+
7
+ on:
8
+ push:
9
+ branches: [main, master]
10
+ paths:
11
+ - 'docs/**'
12
+ - 'README.md'
13
+ - 'src/**'
14
+ - 'package.json'
15
+
16
+ jobs:
17
+ sync-docs:
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ contents: write
21
+
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v4
25
+ with:
26
+ fetch-depth: 0
27
+
28
+ - name: Setup Node.js
29
+ uses: actions/setup-node@v4
30
+ with:
31
+ node-version: '20'
32
+ cache: 'npm'
33
+
34
+ - name: Install dependencies
35
+ run: npm ci
36
+
37
+ - name: Update version in docs
38
+ run: |
39
+ VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "1.0.0")
40
+ echo "Updating docs to version $VERSION"
41
+
42
+ # Update version references
43
+ if [ -d "docs" ]; then
44
+ find docs -name "*.md" -exec sed -i "s/v[0-9]\+\.[0-9]\+\.[0-9]\+/v$VERSION/g" {} \; 2>/dev/null || true
45
+ fi
46
+
47
+ - name: Generate API docs
48
+ run: |
49
+ # Generate API documentation if TypeScript/JSDoc present
50
+ if [ -f "tsconfig.json" ]; then
51
+ npx typedoc --out docs/api src/ 2>/dev/null || echo "Skipping TypeDoc"
52
+ fi
53
+
54
+ - name: Capture screenshots
55
+ run: |
56
+ # If Playwright is installed and app can run, capture screenshots
57
+ if npm ls playwright >/dev/null 2>&1; then
58
+ npm run docs:capture 2>/dev/null || echo "Skipping screenshots"
59
+ fi
60
+
61
+ - name: Sync to GitHub Wiki
62
+ if: github.event.repository.has_wiki
63
+ env:
64
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65
+ run: |
66
+ if [ -d "docs" ]; then
67
+ git clone https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.wiki.git wiki 2>/dev/null || exit 0
68
+
69
+ cp -r docs/* wiki/ 2>/dev/null || true
70
+
71
+ cd wiki
72
+ git config user.name "github-actions[bot]"
73
+ git config user.email "github-actions[bot]@users.noreply.github.com"
74
+
75
+ if [ -n "$(git status --porcelain)" ]; then
76
+ git add -A
77
+ git commit -m "docs: auto-sync from main"
78
+ git push
79
+ fi
80
+ fi
81
+
82
+ - name: Commit doc updates
83
+ run: |
84
+ git config user.name "github-actions[bot]"
85
+ git config user.email "github-actions[bot]@users.noreply.github.com"
86
+
87
+ if [ -n "$(git status --porcelain docs/ README.md)" ]; then
88
+ git add docs/ README.md 2>/dev/null || true
89
+ git commit -m "docs: auto-update" || true
90
+ git push || true
91
+ fi