tlc-claude-code 2.0.1 → 2.2.0
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/.claude/agents/builder.md +144 -0
- package/.claude/agents/planner.md +143 -0
- package/.claude/agents/reviewer.md +160 -0
- package/.claude/commands/tlc/build.md +4 -0
- package/.claude/commands/tlc/deploy.md +194 -2
- package/.claude/commands/tlc/e2e-verify.md +214 -0
- package/.claude/commands/tlc/guard.md +191 -0
- package/.claude/commands/tlc/help.md +32 -0
- package/.claude/commands/tlc/init.md +73 -37
- package/.claude/commands/tlc/llm.md +19 -4
- package/.claude/commands/tlc/preflight.md +134 -0
- package/.claude/commands/tlc/review-plan.md +363 -0
- package/.claude/commands/tlc/review.md +172 -57
- package/.claude/commands/tlc/watchci.md +159 -0
- package/.claude/hooks/tlc-block-tools.sh +41 -0
- package/.claude/hooks/tlc-capture-exchange.sh +50 -0
- package/.claude/hooks/tlc-post-build.sh +38 -0
- package/.claude/hooks/tlc-post-push.sh +22 -0
- package/.claude/hooks/tlc-prompt-guard.sh +69 -0
- package/.claude/hooks/tlc-session-init.sh +123 -0
- package/CLAUDE.md +13 -0
- package/bin/install.js +268 -2
- package/bin/postinstall.js +102 -24
- package/bin/setup-autoupdate.js +206 -0
- package/bin/setup-autoupdate.test.js +124 -0
- package/bin/tlc.js +0 -0
- package/dashboard-web/dist/assets/index-CdS5CHqu.css +1 -0
- package/dashboard-web/dist/assets/index-CwNPPVpg.js +483 -0
- package/dashboard-web/dist/assets/index-CwNPPVpg.js.map +1 -0
- package/dashboard-web/dist/index.html +2 -2
- package/docker-compose.dev.yml +18 -12
- package/package.json +4 -2
- package/scripts/project-docs.js +1 -1
- package/server/index.js +228 -2
- package/server/lib/capture-bridge.js +242 -0
- package/server/lib/capture-bridge.test.js +363 -0
- package/server/lib/capture-guard.js +140 -0
- package/server/lib/capture-guard.test.js +182 -0
- package/server/lib/command-runner.js +159 -0
- package/server/lib/command-runner.test.js +92 -0
- package/server/lib/cost-tracker.test.js +49 -12
- package/server/lib/deploy/runners/dependency-runner.js +106 -0
- package/server/lib/deploy/runners/dependency-runner.test.js +148 -0
- package/server/lib/deploy/runners/secrets-runner.js +174 -0
- package/server/lib/deploy/runners/secrets-runner.test.js +127 -0
- package/server/lib/deploy/security-gates.js +11 -24
- package/server/lib/deploy/security-gates.test.js +9 -2
- package/server/lib/deploy-engine.js +182 -0
- package/server/lib/deploy-engine.test.js +147 -0
- package/server/lib/docker-api.js +137 -0
- package/server/lib/docker-api.test.js +202 -0
- package/server/lib/docker-client.js +297 -0
- package/server/lib/docker-client.test.js +308 -0
- package/server/lib/input-sanitizer.js +86 -0
- package/server/lib/input-sanitizer.test.js +117 -0
- package/server/lib/launchd-agent.js +225 -0
- package/server/lib/launchd-agent.test.js +185 -0
- package/server/lib/memory-api.js +3 -1
- package/server/lib/memory-api.test.js +3 -5
- package/server/lib/memory-bridge-e2e.test.js +160 -0
- package/server/lib/memory-committer.js +18 -4
- package/server/lib/memory-committer.test.js +21 -0
- package/server/lib/memory-hooks-capture.test.js +69 -4
- package/server/lib/memory-hooks-integration.test.js +98 -0
- package/server/lib/memory-hooks.js +42 -4
- package/server/lib/memory-store-adapter.js +105 -0
- package/server/lib/memory-store-adapter.test.js +141 -0
- package/server/lib/memory-wiring-e2e.test.js +93 -0
- package/server/lib/nginx-config.js +114 -0
- package/server/lib/nginx-config.test.js +82 -0
- package/server/lib/ollama-health.js +91 -0
- package/server/lib/ollama-health.test.js +74 -0
- package/server/lib/orchestration/agent-dispatcher.js +114 -0
- package/server/lib/orchestration/agent-dispatcher.test.js +110 -0
- package/server/lib/orchestration/orchestrator.js +130 -0
- package/server/lib/orchestration/orchestrator.test.js +192 -0
- package/server/lib/orchestration/tmux-manager.js +101 -0
- package/server/lib/orchestration/tmux-manager.test.js +109 -0
- package/server/lib/orchestration/worktree-manager.js +132 -0
- package/server/lib/orchestration/worktree-manager.test.js +129 -0
- package/server/lib/port-guard.js +44 -0
- package/server/lib/port-guard.test.js +65 -0
- package/server/lib/project-scanner.js +37 -2
- package/server/lib/project-scanner.test.js +152 -0
- package/server/lib/remember-command.js +2 -0
- package/server/lib/remember-command.test.js +23 -0
- package/server/lib/review/plan-reviewer.js +260 -0
- package/server/lib/review/plan-reviewer.test.js +269 -0
- package/server/lib/review/review-schemas.js +173 -0
- package/server/lib/review/review-schemas.test.js +152 -0
- package/server/lib/security/crypto-utils.test.js +2 -2
- package/server/lib/semantic-recall.js +1 -1
- package/server/lib/semantic-recall.test.js +17 -0
- package/server/lib/ssh-client.js +184 -0
- package/server/lib/ssh-client.test.js +127 -0
- package/server/lib/vps-api.js +184 -0
- package/server/lib/vps-api.test.js +208 -0
- package/server/lib/vps-bootstrap.js +124 -0
- package/server/lib/vps-bootstrap.test.js +79 -0
- package/server/lib/vps-monitor.js +126 -0
- package/server/lib/vps-monitor.test.js +98 -0
- package/server/lib/workspace-api.js +182 -1
- package/server/lib/workspace-api.test.js +474 -0
- package/server/package-lock.json +737 -0
- package/server/package.json +3 -0
- package/server/setup.sh +271 -271
- package/dashboard-web/dist/assets/index-Uhc49PE-.css +0 -1
- package/dashboard-web/dist/assets/index-W36XHPC5.js +0 -431
- package/dashboard-web/dist/assets/index-W36XHPC5.js.map +0 -1
package/server/package.json
CHANGED
|
@@ -19,16 +19,19 @@
|
|
|
19
19
|
"chokidar": "^3.5.3",
|
|
20
20
|
"cookie-parser": "^1.4.7",
|
|
21
21
|
"cors": "^2.8.6",
|
|
22
|
+
"dockerode": "^4.0.9",
|
|
22
23
|
"express": "^4.18.2",
|
|
23
24
|
"http-proxy-middleware": "^2.0.6",
|
|
24
25
|
"js-yaml": "^4.1.1",
|
|
25
26
|
"pg": "^8.17.2",
|
|
26
27
|
"sqlite-vec": "^0.1.7-alpha.2",
|
|
28
|
+
"ssh2": "^1.17.0",
|
|
27
29
|
"sudo-prompt": "^9.2.1",
|
|
28
30
|
"typescript": "^5.9.3",
|
|
29
31
|
"ws": "^8.14.2"
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
34
|
+
"supertest": "^7.2.2",
|
|
32
35
|
"vitest": "^4.0.18"
|
|
33
36
|
}
|
|
34
37
|
}
|
package/server/setup.sh
CHANGED
|
@@ -1,271 +1,271 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# TLC Server Setup Script
|
|
4
|
-
# Installs Docker and other requirements for TLC dev server
|
|
5
|
-
#
|
|
6
|
-
# Usage: sudo ./setup.sh
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
set -e
|
|
10
|
-
|
|
11
|
-
# Colors for output
|
|
12
|
-
RED='\033[0;31m'
|
|
13
|
-
GREEN='\033[0;32m'
|
|
14
|
-
YELLOW='\033[1;33m'
|
|
15
|
-
NC='\033[0m' # No Color
|
|
16
|
-
|
|
17
|
-
log_info() {
|
|
18
|
-
echo -e "${GREEN}[TLC]${NC} $1"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
log_warn() {
|
|
22
|
-
echo -e "${YELLOW}[TLC]${NC} $1"
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
log_error() {
|
|
26
|
-
echo -e "${RED}[TLC]${NC} $1"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
# Check if running as root
|
|
30
|
-
if [ "$EUID" -ne 0 ]; then
|
|
31
|
-
log_error "Please run with sudo: sudo ./setup.sh"
|
|
32
|
-
exit 1
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
# Get the actual user (not root)
|
|
36
|
-
ACTUAL_USER=${SUDO_USER:-$USER}
|
|
37
|
-
if [ "$ACTUAL_USER" = "root" ]; then
|
|
38
|
-
log_error "Please run as a regular user with sudo, not as root directly"
|
|
39
|
-
exit 1
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
log_info "Setting up TLC server requirements for user: $ACTUAL_USER"
|
|
43
|
-
|
|
44
|
-
# Detect OS
|
|
45
|
-
detect_os() {
|
|
46
|
-
if [ -f /etc/os-release ]; then
|
|
47
|
-
. /etc/os-release
|
|
48
|
-
OS=$ID
|
|
49
|
-
OS_VERSION=$VERSION_ID
|
|
50
|
-
elif [ "$(uname)" = "Darwin" ]; then
|
|
51
|
-
OS="macos"
|
|
52
|
-
OS_VERSION=$(sw_vers -productVersion)
|
|
53
|
-
else
|
|
54
|
-
OS="unknown"
|
|
55
|
-
fi
|
|
56
|
-
echo "$OS"
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
OS=$(detect_os)
|
|
60
|
-
log_info "Detected OS: $OS"
|
|
61
|
-
|
|
62
|
-
# Install Docker based on OS
|
|
63
|
-
install_docker() {
|
|
64
|
-
if command -v docker &> /dev/null; then
|
|
65
|
-
log_info "Docker already installed: $(docker --version)"
|
|
66
|
-
return 0
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
log_info "Installing Docker..."
|
|
70
|
-
|
|
71
|
-
case $OS in
|
|
72
|
-
ubuntu|debian|pop)
|
|
73
|
-
# Remove old versions
|
|
74
|
-
apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
|
|
75
|
-
|
|
76
|
-
# Install prerequisites
|
|
77
|
-
apt-get update
|
|
78
|
-
apt-get install -y \
|
|
79
|
-
ca-certificates \
|
|
80
|
-
curl \
|
|
81
|
-
gnupg \
|
|
82
|
-
lsb-release
|
|
83
|
-
|
|
84
|
-
# Add Docker's official GPG key
|
|
85
|
-
install -m 0755 -d /etc/apt/keyrings
|
|
86
|
-
curl -fsSL https://download.docker.com/linux/$OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
87
|
-
chmod a+r /etc/apt/keyrings/docker.gpg
|
|
88
|
-
|
|
89
|
-
# Set up repository
|
|
90
|
-
echo \
|
|
91
|
-
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS \
|
|
92
|
-
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
93
|
-
|
|
94
|
-
# Install Docker
|
|
95
|
-
apt-get update
|
|
96
|
-
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
97
|
-
|
|
98
|
-
log_info "Docker installed successfully"
|
|
99
|
-
;;
|
|
100
|
-
|
|
101
|
-
fedora|rhel|centos)
|
|
102
|
-
# Install prerequisites
|
|
103
|
-
dnf -y install dnf-plugins-core
|
|
104
|
-
|
|
105
|
-
# Add Docker repo
|
|
106
|
-
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
|
|
107
|
-
|
|
108
|
-
# Install Docker
|
|
109
|
-
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
110
|
-
|
|
111
|
-
log_info "Docker installed successfully"
|
|
112
|
-
;;
|
|
113
|
-
|
|
114
|
-
arch|manjaro)
|
|
115
|
-
pacman -S --noconfirm docker docker-compose
|
|
116
|
-
log_info "Docker installed successfully"
|
|
117
|
-
;;
|
|
118
|
-
|
|
119
|
-
macos)
|
|
120
|
-
log_warn "Please install Docker Desktop from https://www.docker.com/products/docker-desktop"
|
|
121
|
-
log_warn "After installation, enable WSL integration if using WSL"
|
|
122
|
-
return 1
|
|
123
|
-
;;
|
|
124
|
-
|
|
125
|
-
*)
|
|
126
|
-
log_error "Unsupported OS: $OS"
|
|
127
|
-
log_error "Please install Docker manually: https://docs.docker.com/engine/install/"
|
|
128
|
-
return 1
|
|
129
|
-
;;
|
|
130
|
-
esac
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
# Configure Docker for non-root access
|
|
134
|
-
configure_docker_user() {
|
|
135
|
-
log_info "Configuring Docker for user: $ACTUAL_USER"
|
|
136
|
-
|
|
137
|
-
# Add user to docker group
|
|
138
|
-
if ! getent group docker > /dev/null; then
|
|
139
|
-
groupadd docker
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
usermod -aG docker "$ACTUAL_USER"
|
|
143
|
-
log_info "Added $ACTUAL_USER to docker group"
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
# Start Docker service
|
|
147
|
-
start_docker() {
|
|
148
|
-
log_info "Starting Docker service..."
|
|
149
|
-
|
|
150
|
-
# Check if systemd is available (native Linux)
|
|
151
|
-
if command -v systemctl &> /dev/null && systemctl is-system-running &> /dev/null; then
|
|
152
|
-
systemctl enable docker
|
|
153
|
-
systemctl start docker
|
|
154
|
-
# Check if we're in WSL
|
|
155
|
-
elif grep -qi microsoft /proc/version 2>/dev/null; then
|
|
156
|
-
# WSL - use service command
|
|
157
|
-
service docker start || true
|
|
158
|
-
else
|
|
159
|
-
# Try service command as fallback
|
|
160
|
-
service docker start || true
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
# Wait for Docker to be ready
|
|
164
|
-
log_info "Waiting for Docker to be ready..."
|
|
165
|
-
for i in {1..30}; do
|
|
166
|
-
if docker info &> /dev/null; then
|
|
167
|
-
log_info "Docker is ready"
|
|
168
|
-
return 0
|
|
169
|
-
fi
|
|
170
|
-
sleep 1
|
|
171
|
-
done
|
|
172
|
-
|
|
173
|
-
log_warn "Docker may not be fully started. You might need to restart your terminal or run: sudo service docker start"
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
# Pull PostgreSQL image
|
|
177
|
-
pull_postgres_image() {
|
|
178
|
-
log_info "Pulling PostgreSQL image (this may take a moment)..."
|
|
179
|
-
|
|
180
|
-
# Run as the actual user to ensure proper permissions
|
|
181
|
-
su - "$ACTUAL_USER" -c "docker pull postgres:16-alpine" 2>/dev/null || \
|
|
182
|
-
docker pull postgres:16-alpine
|
|
183
|
-
|
|
184
|
-
log_info "PostgreSQL image ready"
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
# Install Node.js if not present
|
|
188
|
-
install_nodejs() {
|
|
189
|
-
if command -v node &> /dev/null; then
|
|
190
|
-
NODE_VERSION=$(node --version)
|
|
191
|
-
log_info "Node.js already installed: $NODE_VERSION"
|
|
192
|
-
return 0
|
|
193
|
-
fi
|
|
194
|
-
|
|
195
|
-
log_info "Installing Node.js..."
|
|
196
|
-
|
|
197
|
-
case $OS in
|
|
198
|
-
ubuntu|debian|pop)
|
|
199
|
-
# Install Node.js 20.x LTS
|
|
200
|
-
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
201
|
-
apt-get install -y nodejs
|
|
202
|
-
;;
|
|
203
|
-
|
|
204
|
-
fedora|rhel|centos)
|
|
205
|
-
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
|
|
206
|
-
dnf install -y nodejs
|
|
207
|
-
;;
|
|
208
|
-
|
|
209
|
-
arch|manjaro)
|
|
210
|
-
pacman -S --noconfirm nodejs npm
|
|
211
|
-
;;
|
|
212
|
-
|
|
213
|
-
macos)
|
|
214
|
-
log_warn "Please install Node.js from https://nodejs.org/"
|
|
215
|
-
return 1
|
|
216
|
-
;;
|
|
217
|
-
|
|
218
|
-
*)
|
|
219
|
-
log_warn "Please install Node.js manually: https://nodejs.org/"
|
|
220
|
-
return 1
|
|
221
|
-
;;
|
|
222
|
-
esac
|
|
223
|
-
|
|
224
|
-
log_info "Node.js installed: $(node --version)"
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
# Main setup
|
|
228
|
-
main() {
|
|
229
|
-
echo ""
|
|
230
|
-
echo " ████████╗██╗ ██████╗"
|
|
231
|
-
echo " ╚══██╔══╝██║ ██╔════╝"
|
|
232
|
-
echo " ██║ ██║ ██║"
|
|
233
|
-
echo " ██║ ██║ ██║"
|
|
234
|
-
echo " ██║ ███████╗╚██████╗"
|
|
235
|
-
echo " ╚═╝ ╚══════╝ ╚═════╝"
|
|
236
|
-
echo ""
|
|
237
|
-
echo " TLC Server Setup"
|
|
238
|
-
echo ""
|
|
239
|
-
|
|
240
|
-
# Install Docker
|
|
241
|
-
install_docker
|
|
242
|
-
|
|
243
|
-
# Configure Docker for user
|
|
244
|
-
configure_docker_user
|
|
245
|
-
|
|
246
|
-
# Start Docker
|
|
247
|
-
start_docker
|
|
248
|
-
|
|
249
|
-
# Pull PostgreSQL image
|
|
250
|
-
pull_postgres_image
|
|
251
|
-
|
|
252
|
-
# Check/install Node.js
|
|
253
|
-
install_nodejs
|
|
254
|
-
|
|
255
|
-
echo ""
|
|
256
|
-
log_info "=========================================="
|
|
257
|
-
log_info "Setup complete!"
|
|
258
|
-
log_info "=========================================="
|
|
259
|
-
echo ""
|
|
260
|
-
log_info "IMPORTANT: Log out and log back in (or restart your terminal)"
|
|
261
|
-
log_info "for Docker group permissions to take effect."
|
|
262
|
-
echo ""
|
|
263
|
-
log_info "Then you can run TLC server with:"
|
|
264
|
-
log_info " cd your-project && npx tlc-claude-code server"
|
|
265
|
-
echo ""
|
|
266
|
-
log_info "Or test Docker now with:"
|
|
267
|
-
log_info " sudo docker run hello-world"
|
|
268
|
-
echo ""
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
main "$@"
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# TLC Server Setup Script
|
|
4
|
+
# Installs Docker and other requirements for TLC dev server
|
|
5
|
+
#
|
|
6
|
+
# Usage: sudo ./setup.sh
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Colors for output
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
NC='\033[0m' # No Color
|
|
16
|
+
|
|
17
|
+
log_info() {
|
|
18
|
+
echo -e "${GREEN}[TLC]${NC} $1"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
log_warn() {
|
|
22
|
+
echo -e "${YELLOW}[TLC]${NC} $1"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
log_error() {
|
|
26
|
+
echo -e "${RED}[TLC]${NC} $1"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Check if running as root
|
|
30
|
+
if [ "$EUID" -ne 0 ]; then
|
|
31
|
+
log_error "Please run with sudo: sudo ./setup.sh"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Get the actual user (not root)
|
|
36
|
+
ACTUAL_USER=${SUDO_USER:-$USER}
|
|
37
|
+
if [ "$ACTUAL_USER" = "root" ]; then
|
|
38
|
+
log_error "Please run as a regular user with sudo, not as root directly"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
log_info "Setting up TLC server requirements for user: $ACTUAL_USER"
|
|
43
|
+
|
|
44
|
+
# Detect OS
|
|
45
|
+
detect_os() {
|
|
46
|
+
if [ -f /etc/os-release ]; then
|
|
47
|
+
. /etc/os-release
|
|
48
|
+
OS=$ID
|
|
49
|
+
OS_VERSION=$VERSION_ID
|
|
50
|
+
elif [ "$(uname)" = "Darwin" ]; then
|
|
51
|
+
OS="macos"
|
|
52
|
+
OS_VERSION=$(sw_vers -productVersion)
|
|
53
|
+
else
|
|
54
|
+
OS="unknown"
|
|
55
|
+
fi
|
|
56
|
+
echo "$OS"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
OS=$(detect_os)
|
|
60
|
+
log_info "Detected OS: $OS"
|
|
61
|
+
|
|
62
|
+
# Install Docker based on OS
|
|
63
|
+
install_docker() {
|
|
64
|
+
if command -v docker &> /dev/null; then
|
|
65
|
+
log_info "Docker already installed: $(docker --version)"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
log_info "Installing Docker..."
|
|
70
|
+
|
|
71
|
+
case $OS in
|
|
72
|
+
ubuntu|debian|pop)
|
|
73
|
+
# Remove old versions
|
|
74
|
+
apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
|
|
75
|
+
|
|
76
|
+
# Install prerequisites
|
|
77
|
+
apt-get update
|
|
78
|
+
apt-get install -y \
|
|
79
|
+
ca-certificates \
|
|
80
|
+
curl \
|
|
81
|
+
gnupg \
|
|
82
|
+
lsb-release
|
|
83
|
+
|
|
84
|
+
# Add Docker's official GPG key
|
|
85
|
+
install -m 0755 -d /etc/apt/keyrings
|
|
86
|
+
curl -fsSL https://download.docker.com/linux/$OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
87
|
+
chmod a+r /etc/apt/keyrings/docker.gpg
|
|
88
|
+
|
|
89
|
+
# Set up repository
|
|
90
|
+
echo \
|
|
91
|
+
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS \
|
|
92
|
+
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
93
|
+
|
|
94
|
+
# Install Docker
|
|
95
|
+
apt-get update
|
|
96
|
+
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
97
|
+
|
|
98
|
+
log_info "Docker installed successfully"
|
|
99
|
+
;;
|
|
100
|
+
|
|
101
|
+
fedora|rhel|centos)
|
|
102
|
+
# Install prerequisites
|
|
103
|
+
dnf -y install dnf-plugins-core
|
|
104
|
+
|
|
105
|
+
# Add Docker repo
|
|
106
|
+
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
|
|
107
|
+
|
|
108
|
+
# Install Docker
|
|
109
|
+
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
110
|
+
|
|
111
|
+
log_info "Docker installed successfully"
|
|
112
|
+
;;
|
|
113
|
+
|
|
114
|
+
arch|manjaro)
|
|
115
|
+
pacman -S --noconfirm docker docker-compose
|
|
116
|
+
log_info "Docker installed successfully"
|
|
117
|
+
;;
|
|
118
|
+
|
|
119
|
+
macos)
|
|
120
|
+
log_warn "Please install Docker Desktop from https://www.docker.com/products/docker-desktop"
|
|
121
|
+
log_warn "After installation, enable WSL integration if using WSL"
|
|
122
|
+
return 1
|
|
123
|
+
;;
|
|
124
|
+
|
|
125
|
+
*)
|
|
126
|
+
log_error "Unsupported OS: $OS"
|
|
127
|
+
log_error "Please install Docker manually: https://docs.docker.com/engine/install/"
|
|
128
|
+
return 1
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Configure Docker for non-root access
|
|
134
|
+
configure_docker_user() {
|
|
135
|
+
log_info "Configuring Docker for user: $ACTUAL_USER"
|
|
136
|
+
|
|
137
|
+
# Add user to docker group
|
|
138
|
+
if ! getent group docker > /dev/null; then
|
|
139
|
+
groupadd docker
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
usermod -aG docker "$ACTUAL_USER"
|
|
143
|
+
log_info "Added $ACTUAL_USER to docker group"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Start Docker service
|
|
147
|
+
start_docker() {
|
|
148
|
+
log_info "Starting Docker service..."
|
|
149
|
+
|
|
150
|
+
# Check if systemd is available (native Linux)
|
|
151
|
+
if command -v systemctl &> /dev/null && systemctl is-system-running &> /dev/null; then
|
|
152
|
+
systemctl enable docker
|
|
153
|
+
systemctl start docker
|
|
154
|
+
# Check if we're in WSL
|
|
155
|
+
elif grep -qi microsoft /proc/version 2>/dev/null; then
|
|
156
|
+
# WSL - use service command
|
|
157
|
+
service docker start || true
|
|
158
|
+
else
|
|
159
|
+
# Try service command as fallback
|
|
160
|
+
service docker start || true
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# Wait for Docker to be ready
|
|
164
|
+
log_info "Waiting for Docker to be ready..."
|
|
165
|
+
for i in {1..30}; do
|
|
166
|
+
if docker info &> /dev/null; then
|
|
167
|
+
log_info "Docker is ready"
|
|
168
|
+
return 0
|
|
169
|
+
fi
|
|
170
|
+
sleep 1
|
|
171
|
+
done
|
|
172
|
+
|
|
173
|
+
log_warn "Docker may not be fully started. You might need to restart your terminal or run: sudo service docker start"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Pull PostgreSQL image
|
|
177
|
+
pull_postgres_image() {
|
|
178
|
+
log_info "Pulling PostgreSQL image (this may take a moment)..."
|
|
179
|
+
|
|
180
|
+
# Run as the actual user to ensure proper permissions
|
|
181
|
+
su - "$ACTUAL_USER" -c "docker pull postgres:16-alpine" 2>/dev/null || \
|
|
182
|
+
docker pull postgres:16-alpine
|
|
183
|
+
|
|
184
|
+
log_info "PostgreSQL image ready"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Install Node.js if not present
|
|
188
|
+
install_nodejs() {
|
|
189
|
+
if command -v node &> /dev/null; then
|
|
190
|
+
NODE_VERSION=$(node --version)
|
|
191
|
+
log_info "Node.js already installed: $NODE_VERSION"
|
|
192
|
+
return 0
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
log_info "Installing Node.js..."
|
|
196
|
+
|
|
197
|
+
case $OS in
|
|
198
|
+
ubuntu|debian|pop)
|
|
199
|
+
# Install Node.js 20.x LTS
|
|
200
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
201
|
+
apt-get install -y nodejs
|
|
202
|
+
;;
|
|
203
|
+
|
|
204
|
+
fedora|rhel|centos)
|
|
205
|
+
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
|
|
206
|
+
dnf install -y nodejs
|
|
207
|
+
;;
|
|
208
|
+
|
|
209
|
+
arch|manjaro)
|
|
210
|
+
pacman -S --noconfirm nodejs npm
|
|
211
|
+
;;
|
|
212
|
+
|
|
213
|
+
macos)
|
|
214
|
+
log_warn "Please install Node.js from https://nodejs.org/"
|
|
215
|
+
return 1
|
|
216
|
+
;;
|
|
217
|
+
|
|
218
|
+
*)
|
|
219
|
+
log_warn "Please install Node.js manually: https://nodejs.org/"
|
|
220
|
+
return 1
|
|
221
|
+
;;
|
|
222
|
+
esac
|
|
223
|
+
|
|
224
|
+
log_info "Node.js installed: $(node --version)"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# Main setup
|
|
228
|
+
main() {
|
|
229
|
+
echo ""
|
|
230
|
+
echo " ████████╗██╗ ██████╗"
|
|
231
|
+
echo " ╚══██╔══╝██║ ██╔════╝"
|
|
232
|
+
echo " ██║ ██║ ██║"
|
|
233
|
+
echo " ██║ ██║ ██║"
|
|
234
|
+
echo " ██║ ███████╗╚██████╗"
|
|
235
|
+
echo " ╚═╝ ╚══════╝ ╚═════╝"
|
|
236
|
+
echo ""
|
|
237
|
+
echo " TLC Server Setup"
|
|
238
|
+
echo ""
|
|
239
|
+
|
|
240
|
+
# Install Docker
|
|
241
|
+
install_docker
|
|
242
|
+
|
|
243
|
+
# Configure Docker for user
|
|
244
|
+
configure_docker_user
|
|
245
|
+
|
|
246
|
+
# Start Docker
|
|
247
|
+
start_docker
|
|
248
|
+
|
|
249
|
+
# Pull PostgreSQL image
|
|
250
|
+
pull_postgres_image
|
|
251
|
+
|
|
252
|
+
# Check/install Node.js
|
|
253
|
+
install_nodejs
|
|
254
|
+
|
|
255
|
+
echo ""
|
|
256
|
+
log_info "=========================================="
|
|
257
|
+
log_info "Setup complete!"
|
|
258
|
+
log_info "=========================================="
|
|
259
|
+
echo ""
|
|
260
|
+
log_info "IMPORTANT: Log out and log back in (or restart your terminal)"
|
|
261
|
+
log_info "for Docker group permissions to take effect."
|
|
262
|
+
echo ""
|
|
263
|
+
log_info "Then you can run TLC server with:"
|
|
264
|
+
log_info " cd your-project && npx tlc-claude-code server"
|
|
265
|
+
echo ""
|
|
266
|
+
log_info "Or test Docker now with:"
|
|
267
|
+
log_info " sudo docker run hello-world"
|
|
268
|
+
echo ""
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
main "$@"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--color-bg-primary: #0a0a0b;--color-bg-secondary: #141416;--color-bg-tertiary: #1e1e21;--color-bg-elevated: #252529;--color-text-primary: #fafafa;--color-text-secondary: #a1a1aa;--color-text-muted: #71717a;--color-border: #27272a;--color-border-hover: #3f3f46;--color-accent: #3b82f6;--color-accent-hover: #2563eb;--color-success: #22c55e;--color-warning: #eab308;--color-error: #ef4444;--color-info: #06b6d4;--space-0: 0;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.25rem;--space-6: 1.5rem;--space-8: 2rem;--space-10: 2.5rem;--space-12: 3rem;--space-16: 4rem;--font-sans: "Inter", system-ui, -apple-system, sans-serif;--font-mono: "JetBrains Mono", ui-monospace, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: 1rem;--text-lg: 1.125rem;--text-xl: 1.25rem;--text-2xl: 1.5rem;--text-3xl: 1.875rem;--font-normal: 400;--font-medium: 500;--font-semibold: 600;--font-bold: 700;--leading-tight: 1.25;--leading-normal: 1.5;--leading-relaxed: 1.625;--radius-sm: .25rem;--radius-md: .375rem;--radius-lg: .5rem;--radius-xl: .75rem;--radius-2xl: 1rem;--radius-full: 9999px;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);--shadow-md: 0 4px 6px rgba(0, 0, 0, .3);--shadow-lg: 0 10px 15px rgba(0, 0, 0, .3);--shadow-xl: 0 20px 25px rgba(0, 0, 0, .3);--transition-fast: .1s ease;--transition-base: .2s ease;--transition-slow: .3s ease;--z-dropdown: 1000;--z-sticky: 1020;--z-modal: 1030;--z-popover: 1040;--z-tooltip: 1050;--z-toast: 1060;--sidebar-width: 240px;--sidebar-collapsed-width: 64px;--header-height: 56px;--mobile-nav-height: 64px}[data-theme=light]{--color-bg-primary: #ffffff;--color-bg-secondary: #f4f4f5;--color-bg-tertiary: #e4e4e7;--color-bg-elevated: #ffffff;--color-text-primary: #09090b;--color-text-secondary: #52525b;--color-text-muted: #a1a1aa;--color-border: #e4e4e7;--color-border-hover: #d4d4d8;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .05);--shadow-md: 0 4px 6px rgba(0, 0, 0, .07);--shadow-lg: 0 10px 15px rgba(0, 0, 0, .1);--shadow-xl: 0 20px 25px rgba(0, 0, 0, .1)}@media(prefers-contrast:high){:root{--color-border: #52525b;--color-text-secondary: #d4d4d8}[data-theme=light]{--color-border: #71717a;--color-text-secondary: #3f3f46}}@media(prefers-reduced-motion:reduce){:root{--transition-fast: 0ms;--transition-base: 0ms;--transition-slow: 0ms}}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:var(--font-sans);font-size:16px;line-height:var(--leading-normal);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;padding:0;background-color:var(--color-bg-primary);color:var(--color-text-primary)}:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}.skip-link{position:absolute;top:-40px;left:0;padding:var(--space-2) var(--space-4);background:var(--color-accent);color:#fff;z-index:var(--z-tooltip);transition:top var(--transition-fast)}.skip-link:focus{top:0}::-moz-selection{background-color:var(--color-accent);color:#fff}::selection{background-color:var(--color-accent);color:#fff}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--color-bg-secondary)}::-webkit-scrollbar-thumb{background:var(--color-border-hover);border-radius:var(--radius-full)}::-webkit-scrollbar-thumb:hover{background:var(--color-text-muted)}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;border-radius:var(--radius-md);padding:.5rem 1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s}.btn:focus-visible{outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: var(--color-accent);--tw-ring-offset-width: 2px}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-primary{background-color:var(--color-accent);--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.btn-primary:hover{background-color:var(--color-accent-hover)}.btn-secondary{border-width:1px;border-color:var(--color-border);background-color:var(--color-bg-tertiary);color:var(--color-text-primary)}.btn-secondary:hover{background-color:var(--color-bg-elevated)}.btn-ghost{background-color:transparent;color:var(--color-text-secondary)}.btn-ghost:hover{background-color:var(--color-bg-tertiary);color:var(--color-text-primary)}.btn-danger{background-color:var(--color-error);--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.btn-danger:hover{opacity:.9}.btn-sm{padding:.375rem .75rem;font-size:.875rem;line-height:1.25rem}.btn-lg{padding:.75rem 1.5rem;font-size:1.125rem;line-height:1.75rem}.card{border-radius:var(--radius-lg);border-width:1px;border-color:var(--color-border);background-color:var(--color-bg-secondary)}.card-hover{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.card-hover:hover{border-color:var(--color-border-hover);background-color:var(--color-bg-tertiary)}.input{width:100%;padding:.5rem .75rem;border-radius:var(--radius-md);border-width:1px;border-color:var(--color-border);background-color:var(--color-bg-tertiary);color:var(--color-text-primary)}.input::-moz-placeholder{color:var(--color-text-muted)}.input::placeholder{color:var(--color-text-muted)}.input:focus{border-color:transparent;outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: var(--color-accent)}.input:disabled{cursor:not-allowed;opacity:.5}.badge{display:inline-flex;align-items:center;gap:.25rem;border-radius:9999px;padding:.125rem .5rem;font-size:.75rem;line-height:1rem;font-weight:500}.badge-success{color:var(--color-success);background-color:color-mix(in srgb,var(--color-success) 20%,transparent)}.badge-warning{color:var(--color-warning);background-color:color-mix(in srgb,var(--color-warning) 20%,transparent)}.badge-error{color:var(--color-error);background-color:color-mix(in srgb,var(--color-error) 20%,transparent)}.badge-info{color:var(--color-info);background-color:color-mix(in srgb,var(--color-info) 20%,transparent)}.badge-neutral{background-color:var(--color-bg-tertiary);color:var(--color-text-secondary)}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.-right-2{right:-.5rem}.-top-1{top:-.25rem}.-top-10{top:-2.5rem}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-1\.5{left:.375rem}.left-2{left:.5rem}.left-3{left:.75rem}.left-4{left:1rem}.left-full{left:100%}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-4{top:1rem}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mb-px{margin-bottom:-1px}.-ml-2{margin-left:-.5rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.ml-2{margin-left:.5rem}.ml-6{margin-left:1.5rem}.ml-9{margin-left:2.25rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-96{height:24rem}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-48{max-height:12rem}.max-h-60{max-height:15rem}.max-h-80{max-height:20rem}.max-h-96{max-height:24rem}.max-h-\[400px\]{max-height:400px}.max-h-\[80vh\]{max-height:80vh}.max-h-\[90vh\]{max-height:90vh}.min-h-\[100px\]{min-height:100px}.min-h-\[200px\]{min-height:200px}.min-h-\[400px\]{min-height:400px}.min-h-\[60vh\]{min-height:60vh}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/3{width:33.333333%}.w-1\/4{width:25%}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-64{width:16rem}.w-8{width:2rem}.w-96{width:24rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[180px\]{min-width:180px}.min-w-\[18px\]{min-width:18px}.min-w-\[300px\]{min-width:300px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-\[400px\]{max-width:400px}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-border>:not([hidden])~:not([hidden]){border-color:var(--color-border)}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-x{border-left-width:1px;border-right-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-none{border-style:none}.border-border{border-color:var(--color-border)}.border-error{border-color:var(--color-error)}.border-info{border-color:var(--color-info)}.border-transparent{border-color:transparent}.border-warning{border-color:var(--color-warning)}.border-l-error{border-left-color:var(--color-error)}.border-l-info{border-left-color:var(--color-info)}.border-l-success{border-left-color:var(--color-success)}.border-l-warning{border-left-color:var(--color-warning)}.bg-accent{background-color:var(--color-accent)}.bg-bg-elevated{background-color:var(--color-bg-elevated)}.bg-bg-primary{background-color:var(--color-bg-primary)}.bg-bg-secondary{background-color:var(--color-bg-secondary)}.bg-bg-tertiary{background-color:var(--color-bg-tertiary)}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-black\/80{background-color:#000c}.bg-border{background-color:var(--color-border)}.bg-error{background-color:var(--color-error)}.bg-info{background-color:var(--color-info)}.bg-success{background-color:var(--color-success)}.bg-text-muted{background-color:var(--color-text-muted)}.bg-transparent{background-color:transparent}.bg-warning{background-color:var(--color-warning)}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-0{padding-bottom:0}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pl-10{padding-left:2.5rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pl-8{padding-left:2rem}.pl-9{padding-left:2.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-\[20vh\]{padding-top:20vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.leading-6{line-height:1.5rem}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-accent{color:var(--color-accent)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-error{color:var(--color-error)}.text-info{color:var(--color-info)}.text-success{color:var(--color-success)}.text-text-muted{color:var(--color-text-muted)}.text-text-primary{color:var(--color-text-primary)}.text-text-secondary{color:var(--color-text-secondary)}.text-warning{color:var(--color-warning)}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.line-through{text-decoration-line:line-through}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: var(--shadow-lg);--tw-shadow-colored: var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.placeholder\:text-text-muted::-moz-placeholder{color:var(--color-text-muted)}.placeholder\:text-text-muted::placeholder{color:var(--color-text-muted)}.hover\:bg-accent-hover:hover{background-color:var(--color-accent-hover)}.hover\:bg-bg-tertiary:hover{background-color:var(--color-bg-tertiary)}.hover\:text-error:hover{color:var(--color-error)}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.hover\:text-text-primary:hover{color:var(--color-text-primary)}.hover\:text-text-secondary:hover{color:var(--color-text-secondary)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:shadow-md:hover{--tw-shadow: var(--shadow-md);--tw-shadow-colored: var(--shadow-md);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-sm:hover{--tw-shadow: var(--shadow-sm);--tw-shadow-colored: var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:border-accent:focus{border-color:var(--color-accent)}.focus\:border-transparent:focus{border-color:transparent}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-accent:focus{--tw-ring-color: var(--color-accent)}.focus\:ring-error:focus{--tw-ring-color: var(--color-error)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:640px){.sm\:block{display:block}.sm\:flex-row{flex-direction:row}}@media(min-width:768px){.md\:block{display:block}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|