Spaces:
Running
Running
Add AWS deployment setup with GitHub Actions auto-deploy
Browse files- Add GitHub Actions workflow for automatic EC2 deployment
- Add disposable mode for OpenCode (auto-reset after 20 messages)
- Add anonymous mode (no credentials stored)
- Add AWS deployment scripts
- Add disposable mode status bar to admin portal
- Add API endpoints for manual reset and status checking
- Update documentation for AWS deployment
This enables:
- Automatic deployment from GitHub to AWS EC2 on every push
- OpenCode runs anonymously without login
- Auto-resets after 20 messages with fresh device identity
- Complete cleanup of all traces between sessions
- .github/workflows/deploy.yml +82 -0
- AWS_DEPLOYMENT.md +299 -0
- admin_router.py +43 -0
- aws-setup.sh +87 -0
- deploy-to-aws.sh +260 -0
- opencode_terminal.py +247 -104
- run-daemon.sh +112 -0
- start-production.sh +60 -0
- static/qaz.html +122 -2
- test_prompt.py +39 -0
.github/workflows/deploy.yml
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Deploy to AWS EC2
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master ]
|
| 6 |
+
|
| 7 |
+
jobs:
|
| 8 |
+
deploy:
|
| 9 |
+
runs-on: ubuntu-latest
|
| 10 |
+
|
| 11 |
+
steps:
|
| 12 |
+
- name: Checkout code
|
| 13 |
+
uses: actions/checkout@v3
|
| 14 |
+
|
| 15 |
+
- name: Deploy to EC2
|
| 16 |
+
env:
|
| 17 |
+
PRIVATE_KEY: ${{ secrets.EC2_SSH_KEY }}
|
| 18 |
+
HOST: ${{ secrets.EC2_HOST }}
|
| 19 |
+
USER: ${{ secrets.EC2_USER }}
|
| 20 |
+
run: |
|
| 21 |
+
echo "$PRIVATE_KEY" > private_key.pem
|
| 22 |
+
chmod 600 private_key.pem
|
| 23 |
+
|
| 24 |
+
# Create deployment script
|
| 25 |
+
cat > deploy.sh << 'EOF'
|
| 26 |
+
#!/bin/bash
|
| 27 |
+
set -e
|
| 28 |
+
|
| 29 |
+
cd ~/kai-api
|
| 30 |
+
|
| 31 |
+
# Backup current state
|
| 32 |
+
echo "Creating backup..."
|
| 33 |
+
cp -r . ../kai-api-backup-$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
|
| 34 |
+
|
| 35 |
+
# Pull latest code
|
| 36 |
+
echo "Pulling latest code..."
|
| 37 |
+
git fetch origin
|
| 38 |
+
git reset --hard origin/main
|
| 39 |
+
|
| 40 |
+
# Activate virtual environment
|
| 41 |
+
source venv/bin/activate
|
| 42 |
+
|
| 43 |
+
# Install any new dependencies
|
| 44 |
+
echo "Installing dependencies..."
|
| 45 |
+
pip install -r requirements.txt
|
| 46 |
+
|
| 47 |
+
# Restart the server
|
| 48 |
+
echo "Restarting server..."
|
| 49 |
+
pkill -f uvicorn 2>/dev/null || true
|
| 50 |
+
sleep 2
|
| 51 |
+
|
| 52 |
+
# Start server in background
|
| 53 |
+
nohup ./venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1 > /tmp/kai-api.log 2>&1 &
|
| 54 |
+
|
| 55 |
+
echo "โ
Deployment complete!"
|
| 56 |
+
echo "๐ API: http://$(curl -s ifconfig.me):8000"
|
| 57 |
+
EOF
|
| 58 |
+
|
| 59 |
+
chmod +x deploy.sh
|
| 60 |
+
|
| 61 |
+
# Copy and execute deployment script on EC2
|
| 62 |
+
scp -i private_key.pem -o StrictHostKeyChecking=no deploy.sh $USER@$HOST:/tmp/
|
| 63 |
+
ssh -i private_key.pem -o StrictHostKeyChecking=no $USER@$HOST 'bash /tmp/deploy.sh'
|
| 64 |
+
|
| 65 |
+
rm -f private_key.pem deploy.sh
|
| 66 |
+
|
| 67 |
+
- name: Verify deployment
|
| 68 |
+
env:
|
| 69 |
+
HOST: ${{ secrets.EC2_HOST }}
|
| 70 |
+
run: |
|
| 71 |
+
echo "Waiting for server to start..."
|
| 72 |
+
sleep 5
|
| 73 |
+
|
| 74 |
+
# Check if server is responding
|
| 75 |
+
if curl -s http://$HOST:8000/health > /dev/null 2>&1; then
|
| 76 |
+
echo "โ
Deployment successful! Server is running."
|
| 77 |
+
echo "๐ API: http://$HOST:8000"
|
| 78 |
+
echo "๐ง Admin: http://$HOST:8000/qazmlp"
|
| 79 |
+
else
|
| 80 |
+
echo "โ ๏ธ Server might still be starting. Check manually in a minute."
|
| 81 |
+
echo "๐ API: http://$HOST:8000"
|
| 82 |
+
fi
|
AWS_DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AWS Deployment Guide for K-AI API
|
| 2 |
+
|
| 3 |
+
This guide will help you deploy the K-AI API to AWS EC2 with OpenCode in **Anonymous + Disposable Mode**.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
- **Platform**: AWS EC2 (Ubuntu 22.04 LTS)
|
| 8 |
+
- **Instance Type**: t3.medium minimum (2 vCPU, 4GB RAM)
|
| 9 |
+
- **Port**: 8000 (HTTP)
|
| 10 |
+
- **Mode**: Anonymous + Disposable (auto-reset after 20 messages)
|
| 11 |
+
|
| 12 |
+
## Key Features
|
| 13 |
+
|
| 14 |
+
### ๐ Anonymous Mode
|
| 15 |
+
- No login required
|
| 16 |
+
- No credentials stored anywhere
|
| 17 |
+
- Fresh device identity for each session
|
| 18 |
+
- Random device/session IDs generated on startup
|
| 19 |
+
|
| 20 |
+
### ๐๏ธ Disposable Mode
|
| 21 |
+
- **Auto-reset after 20 messages** - Complete wipe and fresh start
|
| 22 |
+
- **New chat between each message** - No context carryover
|
| 23 |
+
- **Aggressive cleanup** - Removes ALL traces (config files, cache, temp files)
|
| 24 |
+
- **Appears as new device** - OpenCode sees a completely different device
|
| 25 |
+
|
| 26 |
+
### ๐งน What Gets Cleaned
|
| 27 |
+
- All config directories
|
| 28 |
+
- All cache and temp files
|
| 29 |
+
- NPM/npx cache
|
| 30 |
+
- Session directories
|
| 31 |
+
- Any stored identifiers
|
| 32 |
+
|
| 33 |
+
## Prerequisites
|
| 34 |
+
|
| 35 |
+
1. **AWS Account** with EC2 access
|
| 36 |
+
2. **SSH Key Pair** created in AWS
|
| 37 |
+
3. **Local terminal** with SSH access
|
| 38 |
+
|
| 39 |
+
## Step 1: Create EC2 Instance
|
| 40 |
+
|
| 41 |
+
1. Go to AWS Console โ EC2 โ Launch Instance
|
| 42 |
+
2. **Name**: `kai-api-server`
|
| 43 |
+
3. **AMI**: Ubuntu 22.04 LTS (64-bit)
|
| 44 |
+
4. **Instance Type**: t3.medium or larger
|
| 45 |
+
5. **Key Pair**: Select your existing key pair or create new
|
| 46 |
+
6. **Network Settings**:
|
| 47 |
+
- Create security group
|
| 48 |
+
- Allow SSH (port 22) from your IP
|
| 49 |
+
- Allow HTTP (port 80) from anywhere
|
| 50 |
+
- Allow Custom TCP (port 8000) from anywhere
|
| 51 |
+
|
| 52 |
+
7. **Storage**: 20 GB gp3 (minimum)
|
| 53 |
+
8. Click **Launch Instance**
|
| 54 |
+
|
| 55 |
+
## Step 2: Get Your EC2 Details
|
| 56 |
+
|
| 57 |
+
Once the instance is running, note down:
|
| 58 |
+
- **Public IPv4 address** (e.g., `54.123.456.789`)
|
| 59 |
+
- **Key pair file path** on your local machine (e.g., `~/Downloads/kai-api-key.pem`)
|
| 60 |
+
|
| 61 |
+
## Step 3: Deploy to AWS (Easy Method)
|
| 62 |
+
|
| 63 |
+
We provide a simple deployment script. Run this from your local machine:
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
# Make scripts executable
|
| 67 |
+
chmod +x deploy-to-aws.sh aws-setup.sh start-production.sh run-daemon.sh
|
| 68 |
+
|
| 69 |
+
# Run deployment
|
| 70 |
+
./deploy-to-aws.sh -i ~/Downloads/kai-api-key.pem -h 54.123.456.789 -u ubuntu
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
This will:
|
| 74 |
+
1. Upload all files to your EC2 instance
|
| 75 |
+
2. Run the setup automatically
|
| 76 |
+
3. Install all dependencies
|
| 77 |
+
4. Start the server
|
| 78 |
+
|
| 79 |
+
## Step 4: Access Your API
|
| 80 |
+
|
| 81 |
+
Once deployed:
|
| 82 |
+
- **API**: http://YOUR_EC2_IP:8000
|
| 83 |
+
- **Admin Portal**: http://YOUR_EC2_IP:8000/qazmlp
|
| 84 |
+
- **API Docs**: http://YOUR_EC2_IP:8000/docs
|
| 85 |
+
|
| 86 |
+
## Managing the Server
|
| 87 |
+
|
| 88 |
+
SSH into your EC2 instance:
|
| 89 |
+
```bash
|
| 90 |
+
ssh -i ~/Downloads/kai-api-key.pem ubuntu@54.123.456.789
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Server Commands
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
cd ~/kai-api
|
| 97 |
+
|
| 98 |
+
# Start in foreground (good for testing)
|
| 99 |
+
./start-production.sh
|
| 100 |
+
|
| 101 |
+
# Run as background daemon
|
| 102 |
+
./run-daemon.sh start
|
| 103 |
+
|
| 104 |
+
# Check status
|
| 105 |
+
./run-daemon.sh status
|
| 106 |
+
|
| 107 |
+
# View logs
|
| 108 |
+
./run-daemon.sh logs
|
| 109 |
+
|
| 110 |
+
# Stop server
|
| 111 |
+
./run-daemon.sh stop
|
| 112 |
+
|
| 113 |
+
# Restart server
|
| 114 |
+
./run-daemon.sh restart
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
## Using OpenCode in Disposable Mode
|
| 118 |
+
|
| 119 |
+
### From the Admin Portal (/qazmlp):
|
| 120 |
+
|
| 121 |
+
1. Go to **Browser Portal** section
|
| 122 |
+
2. Select **"๐ฅ๏ธ OpenCode (Kimi K2.5 Free)"** from dropdown
|
| 123 |
+
3. Click **Connect**
|
| 124 |
+
4. You'll see the **Disposable Mode Status Bar** showing:
|
| 125 |
+
- Message count (0/20)
|
| 126 |
+
- Progress bar
|
| 127 |
+
- Reset timer
|
| 128 |
+
- Manual reset button
|
| 129 |
+
|
| 130 |
+
### Disposable Mode Features:
|
| 131 |
+
|
| 132 |
+
โ
**Automatic**:
|
| 133 |
+
- New chat starts before EVERY message (no context carryover)
|
| 134 |
+
- After 20 messages: Complete reset automatically
|
| 135 |
+
- Fresh device identity generated
|
| 136 |
+
- All traces wiped clean
|
| 137 |
+
|
| 138 |
+
โ
**Manual Control**:
|
| 139 |
+
- Click **"๐ Reset Now"** anytime to force a reset
|
| 140 |
+
- Status bar shows messages remaining
|
| 141 |
+
- Visual warning when approaching reset (last 5 messages)
|
| 142 |
+
|
| 143 |
+
### What Happens During Reset:
|
| 144 |
+
|
| 145 |
+
1. Current session closed
|
| 146 |
+
2. ALL files cleaned up:
|
| 147 |
+
- Config files
|
| 148 |
+
- Cache files
|
| 149 |
+
- Temp files
|
| 150 |
+
- Session directories
|
| 151 |
+
- NPM cache
|
| 152 |
+
3. Fresh identity generated
|
| 153 |
+
4. New session started
|
| 154 |
+
5. OpenCode sees a **completely new device**
|
| 155 |
+
|
| 156 |
+
### API Endpoints for Disposable Mode:
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
# Check disposable status
|
| 160 |
+
curl "http://YOUR_EC2_IP:8000/qaz/terminal/status?model=kimi-k2.5-free"
|
| 161 |
+
|
| 162 |
+
# Response:
|
| 163 |
+
# {
|
| 164 |
+
# "status": "success",
|
| 165 |
+
# "data": {
|
| 166 |
+
# "message_count": 5,
|
| 167 |
+
# "max_messages": 20,
|
| 168 |
+
# "messages_remaining": 15,
|
| 169 |
+
# "auto_reset_enabled": true,
|
| 170 |
+
# "new_chat_between_messages": true,
|
| 171 |
+
# "is_running": true,
|
| 172 |
+
# "anonymous_mode": true
|
| 173 |
+
# }
|
| 174 |
+
# }
|
| 175 |
+
|
| 176 |
+
# Manual reset (wipe everything and start fresh)
|
| 177 |
+
curl -X POST http://YOUR_EC2_IP:8000/qaz/terminal/reset \
|
| 178 |
+
-H "Content-Type: application/json" \
|
| 179 |
+
-d '{"model": "kimi-k2.5-free"}'
|
| 180 |
+
|
| 181 |
+
# Response:
|
| 182 |
+
# {
|
| 183 |
+
# "status": "success",
|
| 184 |
+
# "message": "Full disposable reset completed - OpenCode sees a brand new device!",
|
| 185 |
+
# "model": "kimi-k2.5-free"
|
| 186 |
+
# }
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
## How It Works
|
| 190 |
+
|
| 191 |
+
### Message Flow:
|
| 192 |
+
1. User sends message
|
| 193 |
+
2. System starts **new chat** (clears context)
|
| 194 |
+
3. Message sent to OpenCode
|
| 195 |
+
4. Response received
|
| 196 |
+
5. Message counter incremented
|
| 197 |
+
6. If counter >= 20 โ **Full reset triggered**
|
| 198 |
+
|
| 199 |
+
### Reset Process:
|
| 200 |
+
```
|
| 201 |
+
[20 messages reached]
|
| 202 |
+
โ
|
| 203 |
+
[Close session]
|
| 204 |
+
โ
|
| 205 |
+
[Delete ALL files]
|
| 206 |
+
โ
|
| 207 |
+
[Clear ALL caches]
|
| 208 |
+
โ
|
| 209 |
+
[Generate new identity]
|
| 210 |
+
โ
|
| 211 |
+
[Start fresh session]
|
| 212 |
+
โ
|
| 213 |
+
[OpenCode sees new device]
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
## Security & Privacy
|
| 217 |
+
|
| 218 |
+
๐ **Anonymous Mode Guarantees**:
|
| 219 |
+
- No credentials stored
|
| 220 |
+
- No login required
|
| 221 |
+
- No persistent identifiers
|
| 222 |
+
- Fresh identity each session
|
| 223 |
+
|
| 224 |
+
๐๏ธ **Disposable Mode Guarantees**:
|
| 225 |
+
- All traces removed after reset
|
| 226 |
+
- No tracking possible between sessions
|
| 227 |
+
- Context never carries over
|
| 228 |
+
- Appears as completely different device
|
| 229 |
+
|
| 230 |
+
## Available Free Models
|
| 231 |
+
|
| 232 |
+
All models work in disposable mode:
|
| 233 |
+
|
| 234 |
+
- **kimi-k2.5-free** (Kimi K2.5 Free)
|
| 235 |
+
- **minimax-m2.5-free** (MiniMax M2.5 Free)
|
| 236 |
+
- **big-pickle** (Big Pickle)
|
| 237 |
+
- **glm-4.7** (GLM 4.7)
|
| 238 |
+
|
| 239 |
+
## Troubleshooting
|
| 240 |
+
|
| 241 |
+
### Can't connect to EC2:
|
| 242 |
+
```bash
|
| 243 |
+
chmod 600 ~/Downloads/kai-api-key.pem
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
### Server not starting:
|
| 247 |
+
```bash
|
| 248 |
+
./run-daemon.sh logs
|
| 249 |
+
# Or run in foreground
|
| 250 |
+
./start-production.sh
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
### OpenCode not resetting:
|
| 254 |
+
- Check status: `curl "http://IP:8000/qaz/terminal/status"`
|
| 255 |
+
- Manual reset: Use the "๐ Reset Now" button
|
| 256 |
+
- Check logs: `./run-daemon.sh logs`
|
| 257 |
+
|
| 258 |
+
### Too many resets happening?
|
| 259 |
+
- Default is 20 messages per reset
|
| 260 |
+
- You can change this in `opencode_terminal.py`:
|
| 261 |
+
```python
|
| 262 |
+
MAX_MESSAGES_BEFORE_RESET = 20 # Change this number
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
## Cost Estimation
|
| 266 |
+
|
| 267 |
+
- **t3.medium**: ~$30/month (on-demand)
|
| 268 |
+
- **t3.large**: ~$60/month (on-demand)
|
| 269 |
+
- **Data transfer**: Variable
|
| 270 |
+
|
| 271 |
+
Use Reserved Instances for 30-60% savings.
|
| 272 |
+
|
| 273 |
+
## Files You Can Modify
|
| 274 |
+
|
| 275 |
+
- `opencode_terminal.py` - Change disposable mode settings
|
| 276 |
+
- `aws-setup.sh` - Change EC2 setup
|
| 277 |
+
- `start-production.sh` - Change how server starts
|
| 278 |
+
- `run-daemon.sh` - Change daemon behavior
|
| 279 |
+
|
| 280 |
+
## Need Help?
|
| 281 |
+
|
| 282 |
+
1. Check logs: `./run-daemon.sh logs`
|
| 283 |
+
2. Verify EC2 security group allows port 8000
|
| 284 |
+
3. Ensure Node.js 18+ is installed
|
| 285 |
+
4. Check disposable status endpoint
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
## Summary
|
| 290 |
+
|
| 291 |
+
Your OpenCode integration now:
|
| 292 |
+
โ
Requires NO login
|
| 293 |
+
โ
Stores NO credentials
|
| 294 |
+
โ
Auto-resets every 20 messages
|
| 295 |
+
โ
Starts new chat between messages
|
| 296 |
+
โ
Wipes ALL traces on reset
|
| 297 |
+
โ
Appears as new device to OpenCode
|
| 298 |
+
|
| 299 |
+
**You get fresh free tokens automatically with each reset!**
|
admin_router.py
CHANGED
|
@@ -1383,3 +1383,46 @@ async def close_terminal(req: dict):
|
|
| 1383 |
except Exception as e:
|
| 1384 |
raise HTTPException(status_code=500, detail=str(e))
|
| 1385 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1383 |
except Exception as e:
|
| 1384 |
raise HTTPException(status_code=500, detail=str(e))
|
| 1385 |
|
| 1386 |
+
|
| 1387 |
+
@router.post("/terminal/reset")
|
| 1388 |
+
async def reset_terminal(req: dict):
|
| 1389 |
+
"""Manually trigger a full disposable reset (wipes all traces and starts fresh)."""
|
| 1390 |
+
try:
|
| 1391 |
+
from opencode_terminal import get_terminal_manager
|
| 1392 |
+
|
| 1393 |
+
model = req.get("model", "kimi-k2.5-free")
|
| 1394 |
+
manager = get_terminal_manager()
|
| 1395 |
+
portal = manager.get_portal(model)
|
| 1396 |
+
|
| 1397 |
+
if not portal.is_running():
|
| 1398 |
+
raise HTTPException(status_code=400, detail="Terminal not running")
|
| 1399 |
+
|
| 1400 |
+
success = await portal.manual_reset()
|
| 1401 |
+
|
| 1402 |
+
return {
|
| 1403 |
+
"status": "success" if success else "error",
|
| 1404 |
+
"message": "Full disposable reset completed - OpenCode sees a brand new device!",
|
| 1405 |
+
"model": model
|
| 1406 |
+
}
|
| 1407 |
+
except Exception as e:
|
| 1408 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 1409 |
+
|
| 1410 |
+
|
| 1411 |
+
@router.get("/terminal/status")
|
| 1412 |
+
async def get_terminal_status(model: str = "kimi-k2.5-free"):
|
| 1413 |
+
"""Get disposable mode status and message count."""
|
| 1414 |
+
try:
|
| 1415 |
+
from opencode_terminal import get_terminal_manager
|
| 1416 |
+
|
| 1417 |
+
manager = get_terminal_manager()
|
| 1418 |
+
portal = manager.get_portal(model)
|
| 1419 |
+
|
| 1420 |
+
status = portal.get_disposable_status()
|
| 1421 |
+
|
| 1422 |
+
return {
|
| 1423 |
+
"status": "success",
|
| 1424 |
+
"data": status
|
| 1425 |
+
}
|
| 1426 |
+
except Exception as e:
|
| 1427 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 1428 |
+
|
aws-setup.sh
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# AWS EC2 Deployment Script for K-AI API
|
| 3 |
+
# ======================================
|
| 4 |
+
# This script sets up the K-AI API on an AWS EC2 instance
|
| 5 |
+
# Run this on your EC2 instance after creating it
|
| 6 |
+
|
| 7 |
+
set -e # Exit on error
|
| 8 |
+
|
| 9 |
+
echo "๐ Starting K-AI API deployment on AWS EC2..."
|
| 10 |
+
|
| 11 |
+
# Update system
|
| 12 |
+
echo "๐ฆ Updating system packages..."
|
| 13 |
+
sudo apt-get update && sudo apt-get upgrade -y
|
| 14 |
+
|
| 15 |
+
# Install essential packages
|
| 16 |
+
echo "๐ง Installing dependencies..."
|
| 17 |
+
sudo apt-get install -y \
|
| 18 |
+
git \
|
| 19 |
+
curl \
|
| 20 |
+
wget \
|
| 21 |
+
build-essential \
|
| 22 |
+
python3 \
|
| 23 |
+
python3-pip \
|
| 24 |
+
python3-venv \
|
| 25 |
+
nginx \
|
| 26 |
+
certbot \
|
| 27 |
+
python3-certbot-nginx \
|
| 28 |
+
software-properties-common \
|
| 29 |
+
apt-transport-https \
|
| 30 |
+
ca-certificates \
|
| 31 |
+
gnupg \
|
| 32 |
+
lsb-release
|
| 33 |
+
|
| 34 |
+
# Install Node.js 18.x (required for OpenCode)
|
| 35 |
+
echo "โฌ๏ธ Installing Node.js..."
|
| 36 |
+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
| 37 |
+
sudo apt-get install -y nodejs
|
| 38 |
+
|
| 39 |
+
# Verify installations
|
| 40 |
+
echo "โ
Checking installations..."
|
| 41 |
+
python3 --version
|
| 42 |
+
node --version
|
| 43 |
+
npm --version
|
| 44 |
+
|
| 45 |
+
# Install Docker
|
| 46 |
+
echo "๐ณ Installing Docker..."
|
| 47 |
+
sudo apt-get install -y docker.io docker-compose
|
| 48 |
+
sudo systemctl start docker
|
| 49 |
+
sudo systemctl enable docker
|
| 50 |
+
sudo usermod -aG docker $USER
|
| 51 |
+
|
| 52 |
+
# Install Playwright dependencies (for browser portals)
|
| 53 |
+
echo "๐ญ Installing Playwright dependencies..."
|
| 54 |
+
sudo apt-get install -y \
|
| 55 |
+
libnss3 \
|
| 56 |
+
libatk-bridge2.0-0 \
|
| 57 |
+
libxss1 \
|
| 58 |
+
libgtk-3-0 \
|
| 59 |
+
libgbm1 \
|
| 60 |
+
libasound2
|
| 61 |
+
|
| 62 |
+
# Create app directory
|
| 63 |
+
echo "๐ Creating app directory..."
|
| 64 |
+
mkdir -p ~/kai-api
|
| 65 |
+
cd ~/kai-api
|
| 66 |
+
|
| 67 |
+
# Clone your repository (you'll need to replace this URL)
|
| 68 |
+
echo "๐ฅ Cloning repository..."
|
| 69 |
+
# git clone https://github.com/YOUR_USERNAME/KAI_API.git .
|
| 70 |
+
# OR if using SCP/SFTP to upload files:
|
| 71 |
+
echo "โณ Waiting for application files..."
|
| 72 |
+
echo "Please upload your K-AI API files to ~/kai-api/"
|
| 73 |
+
echo "You can use: scp -r /local/path/to/KAI_API/* ubuntu@YOUR_EC2_IP:~/kai-api/"
|
| 74 |
+
|
| 75 |
+
# Create a flag file to indicate setup is complete
|
| 76 |
+
touch ~/kai-api/.setup-complete
|
| 77 |
+
|
| 78 |
+
echo ""
|
| 79 |
+
echo "=========================================="
|
| 80 |
+
echo "โ
EC2 Setup Complete!"
|
| 81 |
+
echo "=========================================="
|
| 82 |
+
echo ""
|
| 83 |
+
echo "Next steps:"
|
| 84 |
+
echo "1. Upload your K-AI API files to ~/kai-api/"
|
| 85 |
+
echo "2. Run: cd ~/kai-api && ./start-production.sh"
|
| 86 |
+
echo ""
|
| 87 |
+
echo "Your EC2 instance is ready!"
|
deploy-to-aws.sh
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# AWS Deployment Script - Automatic Upload and Setup
|
| 3 |
+
# ==================================================
|
| 4 |
+
# This script uploads files to EC2 and sets everything up automatically
|
| 5 |
+
|
| 6 |
+
set -e # Exit on error
|
| 7 |
+
|
| 8 |
+
# Configuration
|
| 9 |
+
EC2_IP="44.201.146.74"
|
| 10 |
+
EC2_USER="ubuntu"
|
| 11 |
+
KEY_PATH="" # Will be set interactively
|
| 12 |
+
REMOTE_DIR="/home/ubuntu/kai-api"
|
| 13 |
+
|
| 14 |
+
# Colors for output
|
| 15 |
+
RED='\033[0;31m'
|
| 16 |
+
GREEN='\033[0;32m'
|
| 17 |
+
YELLOW='\033[1;33m'
|
| 18 |
+
BLUE='\033[0;34m'
|
| 19 |
+
NC='\033[0m' # No Color
|
| 20 |
+
|
| 21 |
+
print_status() {
|
| 22 |
+
echo -e "${BLUE}[INFO]${NC} $1"
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
print_success() {
|
| 26 |
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
print_error() {
|
| 30 |
+
echo -e "${RED}[ERROR]${NC} $1"
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
print_warning() {
|
| 34 |
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
# Check if running from KAI_API directory
|
| 38 |
+
if [ ! -f "main.py" ] || [ ! -d "providers" ]; then
|
| 39 |
+
print_error "Please run this script from the KAI_API directory"
|
| 40 |
+
exit 1
|
| 41 |
+
fi
|
| 42 |
+
|
| 43 |
+
# Get SSH key path
|
| 44 |
+
read -p "Enter path to your .pem key file (e.g., ~/Downloads/kai-api-key.pem): " KEY_PATH
|
| 45 |
+
KEY_PATH="${KEY_PATH/#\~/$HOME}" # Expand ~ to home directory
|
| 46 |
+
|
| 47 |
+
if [ ! -f "$KEY_PATH" ]; then
|
| 48 |
+
print_error "Key file not found: $KEY_PATH"
|
| 49 |
+
exit 1
|
| 50 |
+
fi
|
| 51 |
+
|
| 52 |
+
# Fix key permissions
|
| 53 |
+
chmod 600 "$KEY_PATH"
|
| 54 |
+
print_success "Key permissions set to 600"
|
| 55 |
+
|
| 56 |
+
print_status "Starting deployment to EC2 instance: $EC2_IP"
|
| 57 |
+
print_status "This will upload files and set up the server automatically..."
|
| 58 |
+
|
| 59 |
+
echo ""
|
| 60 |
+
read -p "Press Enter to continue..."
|
| 61 |
+
|
| 62 |
+
# Step 1: Test SSH connection
|
| 63 |
+
print_status "Testing SSH connection..."
|
| 64 |
+
if ssh -i "$KEY_PATH" -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$EC2_USER@$EC2_IP" "echo 'SSH connection successful'" 2>/dev/null; then
|
| 65 |
+
print_success "SSH connection successful"
|
| 66 |
+
else
|
| 67 |
+
print_error "Cannot connect to EC2 instance. Please check:"
|
| 68 |
+
print_error " 1. Instance is running"
|
| 69 |
+
print_error " 2. Security group allows port 22 from your IP"
|
| 70 |
+
print_error " 3. Key file is correct"
|
| 71 |
+
exit 1
|
| 72 |
+
fi
|
| 73 |
+
|
| 74 |
+
# Step 2: Create remote directory
|
| 75 |
+
print_status "Creating remote directory..."
|
| 76 |
+
ssh -i "$KEY_PATH" "$EC2_USER@$EC2_IP" "mkdir -p $REMOTE_DIR"
|
| 77 |
+
|
| 78 |
+
# Step 3: Upload files
|
| 79 |
+
print_status "Uploading files to EC2 (this may take a few minutes)..."
|
| 80 |
+
print_status "Uploading application files..."
|
| 81 |
+
|
| 82 |
+
# Create a list of files to upload (excluding unnecessary files)
|
| 83 |
+
cat > /tmp/upload_list.txt << 'EOF'
|
| 84 |
+
main.py
|
| 85 |
+
config.py
|
| 86 |
+
models.py
|
| 87 |
+
engine.py
|
| 88 |
+
services.py
|
| 89 |
+
db.py
|
| 90 |
+
auth.py
|
| 91 |
+
v1_router.py
|
| 92 |
+
admin_router.py
|
| 93 |
+
copilot_portal.py
|
| 94 |
+
copilot_session.py
|
| 95 |
+
browser_portal.py
|
| 96 |
+
opencode_terminal.py
|
| 97 |
+
proxy_manager.py
|
| 98 |
+
provider_state.py
|
| 99 |
+
provider_sessions.py
|
| 100 |
+
search_engine.py
|
| 101 |
+
error_handling.py
|
| 102 |
+
utils.py
|
| 103 |
+
sanitizer.py
|
| 104 |
+
useragent.py
|
| 105 |
+
requirements.txt
|
| 106 |
+
aws-setup.sh
|
| 107 |
+
start-production.sh
|
| 108 |
+
run-daemon.sh
|
| 109 |
+
Dockerfile
|
| 110 |
+
EOF
|
| 111 |
+
|
| 112 |
+
# Upload the list and files
|
| 113 |
+
rsync -avz --progress \
|
| 114 |
+
-e "ssh -i $KEY_PATH" \
|
| 115 |
+
--files-from=/tmp/upload_list.txt \
|
| 116 |
+
. \
|
| 117 |
+
"$EC2_USER@$EC2_IP:$REMOTE_DIR/"
|
| 118 |
+
|
| 119 |
+
print_status "Uploading providers directory..."
|
| 120 |
+
rsync -avz --progress \
|
| 121 |
+
-e "ssh -i $KEY_PATH" \
|
| 122 |
+
providers/ \
|
| 123 |
+
"$EC2_USER@$EC2_IP:$REMOTE_DIR/providers/"
|
| 124 |
+
|
| 125 |
+
print_status "Uploading static directory..."
|
| 126 |
+
rsync -avz --progress \
|
| 127 |
+
-e "ssh -i $KEY_PATH" \
|
| 128 |
+
static/ \
|
| 129 |
+
"$EC2_USER@$EC2_IP:$REMOTE_DIR/static/"
|
| 130 |
+
|
| 131 |
+
print_success "All files uploaded successfully!"
|
| 132 |
+
|
| 133 |
+
# Step 4: Run setup on EC2
|
| 134 |
+
print_status "Running setup on EC2 instance..."
|
| 135 |
+
ssh -i "$KEY_PATH" "$EC2_USER@$EC2_IP" << 'REMOTE_SCRIPT'
|
| 136 |
+
|
| 137 |
+
cd ~/kai-api
|
| 138 |
+
|
| 139 |
+
echo "=========================================="
|
| 140 |
+
echo "๐ Starting K-AI API Setup"
|
| 141 |
+
echo "=========================================="
|
| 142 |
+
|
| 143 |
+
# Update system
|
| 144 |
+
echo "๐ฆ Updating system packages..."
|
| 145 |
+
sudo apt-get update -y
|
| 146 |
+
|
| 147 |
+
# Install dependencies
|
| 148 |
+
echo "๐ง Installing dependencies..."
|
| 149 |
+
sudo apt-get install -y \
|
| 150 |
+
git curl wget build-essential \
|
| 151 |
+
python3 python3-pip python3-venv \
|
| 152 |
+
nginx certbot python3-certbot-nginx \
|
| 153 |
+
software-properties-common \
|
| 154 |
+
apt-transport-https ca-certificates \
|
| 155 |
+
gnupg lsb-release
|
| 156 |
+
|
| 157 |
+
# Install Node.js 18.x
|
| 158 |
+
echo "โฌ๏ธ Installing Node.js..."
|
| 159 |
+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
| 160 |
+
sudo apt-get install -y nodejs
|
| 161 |
+
|
| 162 |
+
echo "โ
Node.js version: $(node --version)"
|
| 163 |
+
echo "โ
npm version: $(npm --version)"
|
| 164 |
+
|
| 165 |
+
# Install Docker
|
| 166 |
+
echo "๐ณ Installing Docker..."
|
| 167 |
+
sudo apt-get install -y docker.io docker-compose
|
| 168 |
+
sudo systemctl start docker
|
| 169 |
+
sudo systemctl enable docker
|
| 170 |
+
sudo usermod -aG docker $USER
|
| 171 |
+
|
| 172 |
+
# Install Playwright dependencies
|
| 173 |
+
echo "๐ญ Installing Playwright dependencies..."
|
| 174 |
+
sudo apt-get install -y \
|
| 175 |
+
libnss3 libatk-bridge2.0-0 libxss1 \
|
| 176 |
+
libgtk-3-0 libgbm1 libasound2
|
| 177 |
+
|
| 178 |
+
# Create virtual environment
|
| 179 |
+
echo "๐ Setting up Python virtual environment..."
|
| 180 |
+
python3 -m venv venv
|
| 181 |
+
source venv/bin/activate
|
| 182 |
+
|
| 183 |
+
# Install Python dependencies
|
| 184 |
+
echo "๐ฅ Installing Python dependencies..."
|
| 185 |
+
pip install --upgrade pip
|
| 186 |
+
pip install -r requirements.txt
|
| 187 |
+
|
| 188 |
+
# Install Playwright browsers
|
| 189 |
+
echo "๐ญ Installing Playwright browsers..."
|
| 190 |
+
playwright install chromium
|
| 191 |
+
|
| 192 |
+
echo "=========================================="
|
| 193 |
+
echo "โ
Setup Complete!"
|
| 194 |
+
echo "=========================================="
|
| 195 |
+
echo ""
|
| 196 |
+
echo "To start the server, run:"
|
| 197 |
+
echo " cd ~/kai-api && ./start-production.sh"
|
| 198 |
+
echo ""
|
| 199 |
+
echo "Or run as background daemon:"
|
| 200 |
+
echo " cd ~/kai-api && ./run-daemon.sh start"
|
| 201 |
+
echo ""
|
| 202 |
+
|
| 203 |
+
REMOTE_SCRIPT
|
| 204 |
+
|
| 205 |
+
print_success "Setup completed on EC2!"
|
| 206 |
+
|
| 207 |
+
# Step 5: Start the server
|
| 208 |
+
print_status "Starting the server..."
|
| 209 |
+
ssh -i "$KEY_PATH" "$EC2_USER@$EC2_IP" << 'REMOTE_SCRIPT'
|
| 210 |
+
|
| 211 |
+
cd ~/kai-api
|
| 212 |
+
|
| 213 |
+
# Kill any existing processes
|
| 214 |
+
pkill -f uvicorn 2>/dev/null || true
|
| 215 |
+
|
| 216 |
+
# Start the server in background
|
| 217 |
+
nohup ./venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1 > /tmp/kai-api.log 2>&1 &
|
| 218 |
+
|
| 219 |
+
# Wait a moment for startup
|
| 220 |
+
sleep 5
|
| 221 |
+
|
| 222 |
+
# Check if it's running
|
| 223 |
+
if pgrep -f uvicorn > /dev/null; then
|
| 224 |
+
echo "โ
Server is running!"
|
| 225 |
+
echo ""
|
| 226 |
+
echo "๐ Your API is available at:"
|
| 227 |
+
echo " http://$(curl -s ifconfig.me):8000"
|
| 228 |
+
echo ""
|
| 229 |
+
echo "๐ง Admin Portal:"
|
| 230 |
+
echo " http://$(curl -s ifconfig.me):8000/qazmlp"
|
| 231 |
+
echo ""
|
| 232 |
+
echo "๐ API Docs:"
|
| 233 |
+
echo " http://$(curl -s ifconfig.me):8000/docs"
|
| 234 |
+
else
|
| 235 |
+
echo "โ Server failed to start. Check logs:"
|
| 236 |
+
echo " cat /tmp/kai-api.log"
|
| 237 |
+
fi
|
| 238 |
+
|
| 239 |
+
REMOTE_SCRIPT
|
| 240 |
+
|
| 241 |
+
echo ""
|
| 242 |
+
echo "=========================================="
|
| 243 |
+
print_success "DEPLOYMENT COMPLETE!"
|
| 244 |
+
echo "=========================================="
|
| 245 |
+
echo ""
|
| 246 |
+
echo "๐ Your K-AI API is now running at:"
|
| 247 |
+
echo " http://$EC2_IP:8000"
|
| 248 |
+
echo ""
|
| 249 |
+
echo "๐ง Admin Portal (OpenCode Terminal):"
|
| 250 |
+
echo " http://$EC2_IP:8000/qazmlp"
|
| 251 |
+
echo ""
|
| 252 |
+
echo "๐ API Documentation:"
|
| 253 |
+
echo " http://$EC2_IP:8000/docs"
|
| 254 |
+
echo ""
|
| 255 |
+
echo "๐ To check server status:"
|
| 256 |
+
echo " ssh -i $KEY_PATH $EC2_USER@$EC2_IP 'cd ~/kai-api && ./run-daemon.sh status'"
|
| 257 |
+
echo ""
|
| 258 |
+
echo "๐ To view logs:"
|
| 259 |
+
echo " ssh -i $KEY_PATH $EC2_USER@$EC2_IP 'tail -f /tmp/kai-api.log'"
|
| 260 |
+
echo ""
|
opencode_terminal.py
CHANGED
|
@@ -3,6 +3,8 @@ OpenCode Terminal Portal
|
|
| 3 |
-------------------------
|
| 4 |
Manages OpenCode terminal TUI as a provider.
|
| 5 |
Supports free models: Kimi K2.5 Free, MiniMax M2.5 Free, Big Pickle, GLM 4.7
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import asyncio
|
|
@@ -10,11 +12,14 @@ import logging
|
|
| 10 |
import subprocess
|
| 11 |
import os
|
| 12 |
import json
|
|
|
|
|
|
|
| 13 |
from typing import Optional, Dict, Any, Callable
|
| 14 |
from dataclasses import dataclass
|
| 15 |
from datetime import datetime
|
| 16 |
import threading
|
| 17 |
import queue
|
|
|
|
| 18 |
|
| 19 |
logger = logging.getLogger("kai_api.terminal_portal")
|
| 20 |
|
|
@@ -28,6 +33,15 @@ class TerminalConfig:
|
|
| 28 |
config_path: str = ".opencode/config.json"
|
| 29 |
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# Free models configuration
|
| 32 |
OPENCODE_MODELS = {
|
| 33 |
"kimi-k2.5-free": {
|
|
@@ -54,7 +68,11 @@ OPENCODE_MODELS = {
|
|
| 54 |
|
| 55 |
|
| 56 |
class OpenCodeTerminalPortal:
|
| 57 |
-
"""Manages OpenCode terminal TUI session."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
def __init__(self, config: TerminalConfig):
|
| 60 |
self.config = config
|
|
@@ -67,22 +85,49 @@ class OpenCodeTerminalPortal:
|
|
| 67 |
self.screenshot_path = f"/tmp/opencode_{config.model}.png"
|
| 68 |
self.last_activity = None
|
| 69 |
self._keyboard_active = False
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
async def initialize(self):
|
| 72 |
-
"""Initialize OpenCode terminal session."""
|
| 73 |
if self.is_initialized:
|
| 74 |
return
|
| 75 |
|
| 76 |
try:
|
| 77 |
-
# Restore auth from Supabase if available
|
| 78 |
-
await self.restore_auth()
|
| 79 |
-
|
| 80 |
logger.info(f"๐ Starting OpenCode terminal with model: {self.config.model}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
#
|
| 83 |
-
|
|
|
|
|
|
|
| 84 |
|
| 85 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
config_data = {
|
| 87 |
"$schema": "https://opencode.ai/config.json",
|
| 88 |
"theme": "opencode",
|
|
@@ -100,40 +145,25 @@ class OpenCodeTerminalPortal:
|
|
| 100 |
},
|
| 101 |
"model": f"opencode-zen/{self.config.model}",
|
| 102 |
"autoshare": False,
|
| 103 |
-
"autoupdate": True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
}
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
"$schema": "https://opencode.ai/config.json",
|
| 111 |
-
"theme": "opencode",
|
| 112 |
-
"provider": {
|
| 113 |
-
"opencode-zen": {
|
| 114 |
-
"npm": "@ai-sdk/openai-compatible",
|
| 115 |
-
"options": {
|
| 116 |
-
"baseURL": "https://opencode.ai/zen/v1"
|
| 117 |
-
},
|
| 118 |
-
"models": {
|
| 119 |
-
model: {"name": info["name"]}
|
| 120 |
-
for model, info in OPENCODE_MODELS.items()
|
| 121 |
-
}
|
| 122 |
-
}
|
| 123 |
-
},
|
| 124 |
-
"model": f"opencode-zen/{self.config.model}",
|
| 125 |
-
"autoshare": False,
|
| 126 |
-
"autoupdate": True
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
with open(self.config.config_path, 'w') as f:
|
| 130 |
-
json.dump(config_data, f, indent=2)
|
| 131 |
-
else:
|
| 132 |
-
logger.info(f"Using existing config at {self.config.config_path}")
|
| 133 |
|
| 134 |
-
# Start OpenCode process
|
| 135 |
env = os.environ.copy()
|
| 136 |
-
env['OPENCODE_CONFIG'] = os.path.abspath(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
self.process = subprocess.Popen(
|
| 139 |
['npx', '-y', 'opencode-ai'],
|
|
@@ -141,7 +171,7 @@ class OpenCodeTerminalPortal:
|
|
| 141 |
env=env,
|
| 142 |
stdin=subprocess.PIPE,
|
| 143 |
stdout=subprocess.PIPE,
|
| 144 |
-
stderr=subprocess.
|
| 145 |
text=True,
|
| 146 |
bufsize=1
|
| 147 |
)
|
|
@@ -152,9 +182,31 @@ class OpenCodeTerminalPortal:
|
|
| 152 |
self.output_thread.start()
|
| 153 |
|
| 154 |
self.is_initialized = True
|
| 155 |
-
logger.info(f"โ
OpenCode terminal ready
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
-
await asyncio.sleep(2) # Give it time to start
|
| 158 |
await self.take_screenshot()
|
| 159 |
|
| 160 |
except Exception as e:
|
|
@@ -181,20 +233,138 @@ class OpenCodeTerminalPortal:
|
|
| 181 |
except Exception as e:
|
| 182 |
logger.error(f"Error reading output: {e}")
|
| 183 |
|
| 184 |
-
async def send_input(self, text: str):
|
| 185 |
-
"""Send text input to OpenCode."""
|
| 186 |
if not self.process or not self.is_initialized:
|
| 187 |
return False
|
| 188 |
|
| 189 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
self.process.stdin.write(text + '\n')
|
| 191 |
self.process.stdin.flush()
|
| 192 |
self.last_activity = datetime.now()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
return True
|
| 194 |
except Exception as e:
|
| 195 |
logger.error(f"Error sending input: {e}")
|
| 196 |
return False
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
async def send_key(self, key: str):
|
| 199 |
"""Send a special key to OpenCode (e.g., 'ctrl+c', 'enter', 'tab')."""
|
| 200 |
if not self.process or not self.is_initialized:
|
|
@@ -288,7 +458,7 @@ class OpenCodeTerminalPortal:
|
|
| 288 |
return self.process.poll() is None
|
| 289 |
|
| 290 |
async def close(self):
|
| 291 |
-
"""Close the OpenCode terminal."""
|
| 292 |
try:
|
| 293 |
if self.process:
|
| 294 |
# Send exit command
|
|
@@ -309,74 +479,47 @@ class OpenCodeTerminalPortal:
|
|
| 309 |
self.process = None
|
| 310 |
|
| 311 |
self.is_initialized = False
|
| 312 |
-
logger.info("OpenCode terminal closed")
|
| 313 |
-
|
| 314 |
-
except Exception as e:
|
| 315 |
-
logger.error(f"Error closing OpenCode: {e}")
|
| 316 |
-
|
| 317 |
-
async def restore_auth(self):
|
| 318 |
-
"""Restore auth token from Supabase."""
|
| 319 |
-
try:
|
| 320 |
-
from db import get_supabase
|
| 321 |
-
supabase = get_supabase()
|
| 322 |
-
if not supabase:
|
| 323 |
-
return
|
| 324 |
-
|
| 325 |
-
# Check if we have auth data in Supabase
|
| 326 |
-
res = supabase.table("kaiapi_settings").select("value").eq("key", "opencode_auth").execute()
|
| 327 |
-
if not res.data:
|
| 328 |
-
return
|
| 329 |
-
|
| 330 |
-
auth_data = res.data[0]["value"]
|
| 331 |
|
| 332 |
-
#
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
| 337 |
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
with open(path, 'w') as f:
|
| 341 |
-
json.dump(auth_data, f, indent=2)
|
| 342 |
|
| 343 |
-
logger.info("
|
| 344 |
|
| 345 |
except Exception as e:
|
| 346 |
-
logger.
|
| 347 |
|
| 348 |
async def sync_auth(self):
|
| 349 |
-
"""
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
supabase = get_supabase()
|
| 353 |
-
if not supabase:
|
| 354 |
-
logger.error("Supabase not configured, cannot sync auth")
|
| 355 |
-
return False
|
| 356 |
-
|
| 357 |
-
# Find auth file
|
| 358 |
-
auth_path = os.path.expanduser("~/.local/share/opencode/auth.json")
|
| 359 |
-
if not os.path.exists(auth_path):
|
| 360 |
-
logger.warning(f"Auth file not found at {auth_path}")
|
| 361 |
-
return False
|
| 362 |
-
|
| 363 |
-
with open(auth_path, 'r') as f:
|
| 364 |
-
auth_data = json.load(f)
|
| 365 |
-
|
| 366 |
-
# Save to Supabase
|
| 367 |
-
supabase.table("kaiapi_settings").upsert({
|
| 368 |
-
"key": "opencode_auth",
|
| 369 |
-
"value": auth_data,
|
| 370 |
-
"updated_at": datetime.now().isoformat()
|
| 371 |
-
}).execute()
|
| 372 |
-
|
| 373 |
-
logger.info("โ
Synced OpenCode auth to Supabase")
|
| 374 |
-
return True
|
| 375 |
-
|
| 376 |
-
except Exception as e:
|
| 377 |
-
logger.error(f"Failed to sync auth to Supabase: {e}")
|
| 378 |
-
return False
|
| 379 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
|
| 382 |
class TerminalPortalManager:
|
|
|
|
| 3 |
-------------------------
|
| 4 |
Manages OpenCode terminal TUI as a provider.
|
| 5 |
Supports free models: Kimi K2.5 Free, MiniMax M2.5 Free, Big Pickle, GLM 4.7
|
| 6 |
+
|
| 7 |
+
ANONYMOUS MODE: No credentials stored, fresh device identity each session
|
| 8 |
"""
|
| 9 |
|
| 10 |
import asyncio
|
|
|
|
| 12 |
import subprocess
|
| 13 |
import os
|
| 14 |
import json
|
| 15 |
+
import random
|
| 16 |
+
import string
|
| 17 |
from typing import Optional, Dict, Any, Callable
|
| 18 |
from dataclasses import dataclass
|
| 19 |
from datetime import datetime
|
| 20 |
import threading
|
| 21 |
import queue
|
| 22 |
+
import shutil
|
| 23 |
|
| 24 |
logger = logging.getLogger("kai_api.terminal_portal")
|
| 25 |
|
|
|
|
| 33 |
config_path: str = ".opencode/config.json"
|
| 34 |
|
| 35 |
|
| 36 |
+
def generate_anonymous_identity():
|
| 37 |
+
"""Generate random device identifiers to appear as different device each time."""
|
| 38 |
+
return {
|
| 39 |
+
"device_id": ''.join(random.choices(string.hexdigits.lower(), k=32)),
|
| 40 |
+
"session_id": ''.join(random.choices(string.hexdigits.lower(), k=16)),
|
| 41 |
+
"fingerprint": ''.join(random.choices(string.hexdigits.lower(), k=24)),
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
# Free models configuration
|
| 46 |
OPENCODE_MODELS = {
|
| 47 |
"kimi-k2.5-free": {
|
|
|
|
| 68 |
|
| 69 |
|
| 70 |
class OpenCodeTerminalPortal:
|
| 71 |
+
"""Manages OpenCode terminal TUI session with DISPOSABLE mode."""
|
| 72 |
+
|
| 73 |
+
# Disposable mode settings
|
| 74 |
+
MAX_MESSAGES_BEFORE_RESET = 20 # Auto-reset after 20 messages
|
| 75 |
+
AUTO_NEW_CHAT_BETWEEN_MESSAGES = True # Start new chat between each message
|
| 76 |
|
| 77 |
def __init__(self, config: TerminalConfig):
|
| 78 |
self.config = config
|
|
|
|
| 85 |
self.screenshot_path = f"/tmp/opencode_{config.model}.png"
|
| 86 |
self.last_activity = None
|
| 87 |
self._keyboard_active = False
|
| 88 |
+
self.message_count = 0 # Track messages for auto-reset
|
| 89 |
+
self.current_identity = None # Track current session identity
|
| 90 |
+
self.session_dir = None # Track current session directory
|
| 91 |
|
| 92 |
async def initialize(self):
|
| 93 |
+
"""Initialize OpenCode terminal session in ANONYMOUS/DISPOSABLE mode."""
|
| 94 |
if self.is_initialized:
|
| 95 |
return
|
| 96 |
|
| 97 |
try:
|
|
|
|
|
|
|
|
|
|
| 98 |
logger.info(f"๐ Starting OpenCode terminal with model: {self.config.model}")
|
| 99 |
+
logger.info("๐ Anonymous mode: No credentials stored, fresh device identity")
|
| 100 |
+
logger.info("๐๏ธ Disposable mode: Auto-reset after 20 messages, new chat between messages")
|
| 101 |
+
|
| 102 |
+
# Generate fresh anonymous identity
|
| 103 |
+
self.current_identity = generate_anonymous_identity()
|
| 104 |
+
identity = self.current_identity
|
| 105 |
|
| 106 |
+
# Create isolated config directory for this session
|
| 107 |
+
self.session_dir = f"/tmp/opencode_session_{identity['session_id'][:8]}"
|
| 108 |
+
config_path = f"{self.session_dir}/config.json"
|
| 109 |
+
os.makedirs(self.session_dir, exist_ok=True)
|
| 110 |
|
| 111 |
+
# Remove any existing auth files to ensure anonymous mode
|
| 112 |
+
auth_paths = [
|
| 113 |
+
os.path.expanduser("~/.local/share/opencode/auth.json"),
|
| 114 |
+
os.path.expanduser("~/.local/share/opencode"),
|
| 115 |
+
".opencode/auth.json",
|
| 116 |
+
"/tmp/opencode_auth.json"
|
| 117 |
+
]
|
| 118 |
+
for path in auth_paths:
|
| 119 |
+
if os.path.exists(path):
|
| 120 |
+
try:
|
| 121 |
+
if os.path.isfile(path):
|
| 122 |
+
os.remove(path)
|
| 123 |
+
logger.info(f"๐๏ธ Removed auth file: {path}")
|
| 124 |
+
elif os.path.isdir(path):
|
| 125 |
+
shutil.rmtree(path)
|
| 126 |
+
logger.info(f"๐๏ธ Removed auth directory: {path}")
|
| 127 |
+
except Exception as e:
|
| 128 |
+
logger.warning(f"Could not remove {path}: {e}")
|
| 129 |
+
|
| 130 |
+
# Create anonymous config - NO login required
|
| 131 |
config_data = {
|
| 132 |
"$schema": "https://opencode.ai/config.json",
|
| 133 |
"theme": "opencode",
|
|
|
|
| 145 |
},
|
| 146 |
"model": f"opencode-zen/{self.config.model}",
|
| 147 |
"autoshare": False,
|
| 148 |
+
"autoupdate": True,
|
| 149 |
+
# Anonymous mode settings
|
| 150 |
+
"anonymous": True,
|
| 151 |
+
"deviceId": identity["device_id"],
|
| 152 |
+
"sessionId": identity["session_id"]
|
| 153 |
}
|
| 154 |
|
| 155 |
+
with open(config_path, 'w') as f:
|
| 156 |
+
json.dump(config_data, f, indent=2)
|
| 157 |
+
|
| 158 |
+
logger.info(f"โ
Config created at: {config_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
| 160 |
+
# Start OpenCode process with custom environment
|
| 161 |
env = os.environ.copy()
|
| 162 |
+
env['OPENCODE_CONFIG'] = os.path.abspath(config_path)
|
| 163 |
+
# Set random terminal identifier
|
| 164 |
+
env['TERM_SESSION_ID'] = identity["session_id"]
|
| 165 |
+
# Prevent any auth persistence
|
| 166 |
+
env['OPENCODE_NO_AUTH'] = '1'
|
| 167 |
|
| 168 |
self.process = subprocess.Popen(
|
| 169 |
['npx', '-y', 'opencode-ai'],
|
|
|
|
| 171 |
env=env,
|
| 172 |
stdin=subprocess.PIPE,
|
| 173 |
stdout=subprocess.PIPE,
|
| 174 |
+
stderr=subprocess.STDOUT, # Merge stderr into stdout for easier reading
|
| 175 |
text=True,
|
| 176 |
bufsize=1
|
| 177 |
)
|
|
|
|
| 182 |
self.output_thread.start()
|
| 183 |
|
| 184 |
self.is_initialized = True
|
| 185 |
+
logger.info(f"โ
OpenCode terminal ready (Anonymous mode)")
|
| 186 |
+
logger.info(f"๐ Session ID: {identity['session_id'][:16]}...")
|
| 187 |
+
|
| 188 |
+
# Wait for startup and send initial commands to select free model
|
| 189 |
+
await asyncio.sleep(3)
|
| 190 |
+
|
| 191 |
+
# Auto-select the free model (Ctrl+X then M)
|
| 192 |
+
await self.send_key('ctrl+x')
|
| 193 |
+
await asyncio.sleep(0.5)
|
| 194 |
+
await self.send_input('m')
|
| 195 |
+
await asyncio.sleep(1)
|
| 196 |
+
|
| 197 |
+
# Select model based on config
|
| 198 |
+
model_map = {
|
| 199 |
+
"kimi-k2.5-free": "kimi",
|
| 200 |
+
"minimax-m2.5-free": "minimax",
|
| 201 |
+
"big-pickle": "big pickle",
|
| 202 |
+
"glm-4.7": "glm"
|
| 203 |
+
}
|
| 204 |
+
model_keyword = model_map.get(self.config.model, "kimi")
|
| 205 |
+
|
| 206 |
+
# Send enter to select default (usually Kimi)
|
| 207 |
+
await self.send_key('Enter')
|
| 208 |
+
await asyncio.sleep(0.5)
|
| 209 |
|
|
|
|
| 210 |
await self.take_screenshot()
|
| 211 |
|
| 212 |
except Exception as e:
|
|
|
|
| 233 |
except Exception as e:
|
| 234 |
logger.error(f"Error reading output: {e}")
|
| 235 |
|
| 236 |
+
async def send_input(self, text: str, is_message: bool = True):
|
| 237 |
+
"""Send text input to OpenCode with disposable mode handling."""
|
| 238 |
if not self.process or not self.is_initialized:
|
| 239 |
return False
|
| 240 |
|
| 241 |
try:
|
| 242 |
+
# If this is a user message (not a command), handle disposable mode
|
| 243 |
+
if is_message and self.AUTO_NEW_CHAT_BETWEEN_MESSAGES:
|
| 244 |
+
# Start a new chat before sending the message
|
| 245 |
+
await self._start_new_chat()
|
| 246 |
+
await asyncio.sleep(0.5)
|
| 247 |
+
|
| 248 |
+
# Send the actual message
|
| 249 |
self.process.stdin.write(text + '\n')
|
| 250 |
self.process.stdin.flush()
|
| 251 |
self.last_activity = datetime.now()
|
| 252 |
+
|
| 253 |
+
# Track message count for auto-reset
|
| 254 |
+
if is_message:
|
| 255 |
+
self.message_count += 1
|
| 256 |
+
logger.info(f"๐จ Message {self.message_count}/{self.MAX_MESSAGES_BEFORE_RESET}")
|
| 257 |
+
|
| 258 |
+
# Check if we need to auto-reset
|
| 259 |
+
if self.message_count >= self.MAX_MESSAGES_BEFORE_RESET:
|
| 260 |
+
logger.info("๐ Auto-reset triggered after 20 messages!")
|
| 261 |
+
await self._full_reset()
|
| 262 |
+
|
| 263 |
return True
|
| 264 |
except Exception as e:
|
| 265 |
logger.error(f"Error sending input: {e}")
|
| 266 |
return False
|
| 267 |
|
| 268 |
+
async def _start_new_chat(self):
|
| 269 |
+
"""Start a new chat to avoid context carryover."""
|
| 270 |
+
try:
|
| 271 |
+
# Send Ctrl+N for new chat (or equivalent command)
|
| 272 |
+
logger.info("๐ Starting new chat...")
|
| 273 |
+
self.process.stdin.write('\x0e') # Ctrl+N
|
| 274 |
+
self.process.stdin.flush()
|
| 275 |
+
await asyncio.sleep(0.3)
|
| 276 |
+
except Exception as e:
|
| 277 |
+
logger.warning(f"Could not start new chat: {e}")
|
| 278 |
+
|
| 279 |
+
async def _full_reset(self):
|
| 280 |
+
"""Complete reset: wipe everything and start fresh."""
|
| 281 |
+
logger.info("๐งน PERFORMING FULL DISPOSABLE RESET...")
|
| 282 |
+
|
| 283 |
+
# 1. Close current session
|
| 284 |
+
await self.close()
|
| 285 |
+
|
| 286 |
+
# 2. Aggressive cleanup of ALL traces
|
| 287 |
+
await self._complete_cleanup()
|
| 288 |
+
|
| 289 |
+
# 3. Reset counters
|
| 290 |
+
self.message_count = 0
|
| 291 |
+
self.current_identity = None
|
| 292 |
+
self.session_dir = None
|
| 293 |
+
|
| 294 |
+
# 4. Wait a moment to ensure cleanup
|
| 295 |
+
await asyncio.sleep(2)
|
| 296 |
+
|
| 297 |
+
# 5. Reinitialize with fresh identity
|
| 298 |
+
logger.info("๐ Starting fresh session...")
|
| 299 |
+
await self.initialize()
|
| 300 |
+
|
| 301 |
+
logger.info("โ
Full reset complete - OpenCode sees a completely new device!")
|
| 302 |
+
|
| 303 |
+
async def _complete_cleanup(self):
|
| 304 |
+
"""Complete cleanup of ALL OpenCode traces."""
|
| 305 |
+
cleanup_paths = [
|
| 306 |
+
# Config directories
|
| 307 |
+
"/tmp/opencode_session_*",
|
| 308 |
+
os.path.expanduser("~/.local/share/opencode"),
|
| 309 |
+
os.path.expanduser("~/.config/opencode"),
|
| 310 |
+
os.path.expanduser("~/.opencode"),
|
| 311 |
+
".opencode",
|
| 312 |
+
|
| 313 |
+
# Cache and temp files
|
| 314 |
+
os.path.expanduser("~/.cache/opencode"),
|
| 315 |
+
"/tmp/opencode*",
|
| 316 |
+
"/tmp/.opencode*",
|
| 317 |
+
|
| 318 |
+
# Node/npm cache that might have identifiers
|
| 319 |
+
os.path.expanduser("~/.npm/_npx/*opencode*"),
|
| 320 |
+
os.path.expanduser("~/.npm/_logs/*opencode*"),
|
| 321 |
+
|
| 322 |
+
# Any auth files
|
| 323 |
+
os.path.expanduser("~/.local/share/opencode/auth.json"),
|
| 324 |
+
"/tmp/opencode_auth.json",
|
| 325 |
+
"/tmp/kai-opencode-*",
|
| 326 |
+
]
|
| 327 |
+
|
| 328 |
+
for path_pattern in cleanup_paths:
|
| 329 |
+
try:
|
| 330 |
+
import glob
|
| 331 |
+
matching_paths = glob.glob(path_pattern)
|
| 332 |
+
for path in matching_paths:
|
| 333 |
+
if os.path.exists(path):
|
| 334 |
+
if os.path.isfile(path):
|
| 335 |
+
os.remove(path)
|
| 336 |
+
logger.info(f"๐๏ธ Deleted file: {path}")
|
| 337 |
+
elif os.path.isdir(path):
|
| 338 |
+
shutil.rmtree(path, ignore_errors=True)
|
| 339 |
+
logger.info(f"๐๏ธ Deleted directory: {path}")
|
| 340 |
+
except Exception as e:
|
| 341 |
+
logger.warning(f"Cleanup warning for {path_pattern}: {e}")
|
| 342 |
+
|
| 343 |
+
# Clear npm/npx cache
|
| 344 |
+
try:
|
| 345 |
+
subprocess.run(["npm", "cache", "clean", "--force"],
|
| 346 |
+
capture_output=True, timeout=10)
|
| 347 |
+
logger.info("๐งน NPM cache cleared")
|
| 348 |
+
except Exception as e:
|
| 349 |
+
logger.warning(f"Could not clear NPM cache: {e}")
|
| 350 |
+
|
| 351 |
+
# Clear any system-level temporary identifiers
|
| 352 |
+
try:
|
| 353 |
+
# Clear /tmp of any opencode related files
|
| 354 |
+
import glob
|
| 355 |
+
for f in glob.glob("/tmp/*opencode*"):
|
| 356 |
+
try:
|
| 357 |
+
if os.path.isfile(f):
|
| 358 |
+
os.remove(f)
|
| 359 |
+
elif os.path.isdir(f):
|
| 360 |
+
shutil.rmtree(f, ignore_errors=True)
|
| 361 |
+
except:
|
| 362 |
+
pass
|
| 363 |
+
except:
|
| 364 |
+
pass
|
| 365 |
+
|
| 366 |
+
logger.info("๐งน Complete cleanup finished - No traces left!")
|
| 367 |
+
|
| 368 |
async def send_key(self, key: str):
|
| 369 |
"""Send a special key to OpenCode (e.g., 'ctrl+c', 'enter', 'tab')."""
|
| 370 |
if not self.process or not self.is_initialized:
|
|
|
|
| 458 |
return self.process.poll() is None
|
| 459 |
|
| 460 |
async def close(self):
|
| 461 |
+
"""Close the OpenCode terminal and cleanup anonymous session."""
|
| 462 |
try:
|
| 463 |
if self.process:
|
| 464 |
# Send exit command
|
|
|
|
| 479 |
self.process = None
|
| 480 |
|
| 481 |
self.is_initialized = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
|
| 483 |
+
# Cleanup session directory to remove any traces
|
| 484 |
+
if self.session_dir and os.path.exists(self.session_dir):
|
| 485 |
+
try:
|
| 486 |
+
shutil.rmtree(self.session_dir, ignore_errors=True)
|
| 487 |
+
logger.info(f"๐งน Cleaned up session directory: {self.session_dir}")
|
| 488 |
+
except Exception as e:
|
| 489 |
+
logger.warning(f"Session cleanup warning: {e}")
|
| 490 |
|
| 491 |
+
# Reset message counter
|
| 492 |
+
self.message_count = 0
|
|
|
|
|
|
|
| 493 |
|
| 494 |
+
logger.info("OpenCode terminal closed (Anonymous session cleaned)")
|
| 495 |
|
| 496 |
except Exception as e:
|
| 497 |
+
logger.error(f"Error closing OpenCode: {e}")
|
| 498 |
|
| 499 |
async def sync_auth(self):
|
| 500 |
+
"""DEPRECATED: Anonymous mode - no auth to sync."""
|
| 501 |
+
logger.warning("sync_auth() called but anonymous mode is active - no credentials stored")
|
| 502 |
+
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
|
| 504 |
+
async def manual_reset(self):
|
| 505 |
+
"""Manually trigger a full disposable reset."""
|
| 506 |
+
logger.info("๐ Manual reset requested")
|
| 507 |
+
await self._full_reset()
|
| 508 |
+
return True
|
| 509 |
+
|
| 510 |
+
def get_disposable_status(self) -> dict:
|
| 511 |
+
"""Get current disposable mode status."""
|
| 512 |
+
return {
|
| 513 |
+
"message_count": self.message_count,
|
| 514 |
+
"max_messages": self.MAX_MESSAGES_BEFORE_RESET,
|
| 515 |
+
"messages_remaining": max(0, self.MAX_MESSAGES_BEFORE_RESET - self.message_count),
|
| 516 |
+
"auto_reset_enabled": True,
|
| 517 |
+
"new_chat_between_messages": self.AUTO_NEW_CHAT_BETWEEN_MESSAGES,
|
| 518 |
+
"is_running": self.is_running(),
|
| 519 |
+
"anonymous_mode": True,
|
| 520 |
+
"session_dir": self.session_dir,
|
| 521 |
+
"device_id": self.current_identity["device_id"][:16] + "..." if self.current_identity else None,
|
| 522 |
+
}
|
| 523 |
|
| 524 |
|
| 525 |
class TerminalPortalManager:
|
run-daemon.sh
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Run K-AI API as a Background Daemon
|
| 3 |
+
# ===================================
|
| 4 |
+
# This script runs the API in the background with auto-restart
|
| 5 |
+
|
| 6 |
+
cd ~/kai-api
|
| 7 |
+
|
| 8 |
+
APP_NAME="kai-api"
|
| 9 |
+
PID_FILE="/tmp/kai-api.pid"
|
| 10 |
+
LOG_FILE="/tmp/kai-api.log"
|
| 11 |
+
|
| 12 |
+
start() {
|
| 13 |
+
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
|
| 14 |
+
echo "โ
$APP_NAME is already running (PID: $(cat $PID_FILE))"
|
| 15 |
+
return
|
| 16 |
+
fi
|
| 17 |
+
|
| 18 |
+
echo "๐ Starting $APP_NAME..."
|
| 19 |
+
|
| 20 |
+
# Activate virtual environment
|
| 21 |
+
source venv/bin/activate
|
| 22 |
+
|
| 23 |
+
# Set environment
|
| 24 |
+
export PYTHONUNBUFFERED=1
|
| 25 |
+
export ENVIRONMENT=production
|
| 26 |
+
|
| 27 |
+
# Load .env if exists
|
| 28 |
+
if [ -f ".env" ]; then
|
| 29 |
+
export $(cat .env | grep -v '^#' | xargs)
|
| 30 |
+
fi
|
| 31 |
+
|
| 32 |
+
# Start with nohup
|
| 33 |
+
nohup uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1 > "$LOG_FILE" 2>&1 &
|
| 34 |
+
|
| 35 |
+
# Save PID
|
| 36 |
+
echo $! > "$PID_FILE"
|
| 37 |
+
|
| 38 |
+
echo "โ
$APP_NAME started (PID: $(cat $PID_FILE))"
|
| 39 |
+
echo "๐ Logs: $LOG_FILE"
|
| 40 |
+
echo "๐ API: http://$(curl -s ifconfig.me):8000"
|
| 41 |
+
echo "๐ง Admin: http://$(curl -s ifconfig.me):8000/qazmlp"
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
stop() {
|
| 45 |
+
if [ ! -f "$PID_FILE" ]; then
|
| 46 |
+
echo "โ $APP_NAME is not running"
|
| 47 |
+
return
|
| 48 |
+
fi
|
| 49 |
+
|
| 50 |
+
PID=$(cat "$PID_FILE")
|
| 51 |
+
echo "๐ Stopping $APP_NAME (PID: $PID)..."
|
| 52 |
+
|
| 53 |
+
kill $PID 2>/dev/null
|
| 54 |
+
sleep 2
|
| 55 |
+
|
| 56 |
+
if kill -0 $PID 2>/dev/null; then
|
| 57 |
+
echo "๐ฅ Force killing..."
|
| 58 |
+
kill -9 $PID 2>/dev/null
|
| 59 |
+
fi
|
| 60 |
+
|
| 61 |
+
rm -f "$PID_FILE"
|
| 62 |
+
echo "โ
$APP_NAME stopped"
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
restart() {
|
| 66 |
+
stop
|
| 67 |
+
sleep 2
|
| 68 |
+
start
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
status() {
|
| 72 |
+
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
|
| 73 |
+
echo "โ
$APP_NAME is running (PID: $(cat $PID_FILE))"
|
| 74 |
+
echo "๐ API: http://$(curl -s ifconfig.me):8000"
|
| 75 |
+
echo ""
|
| 76 |
+
echo "๐ Recent logs:"
|
| 77 |
+
tail -20 "$LOG_FILE"
|
| 78 |
+
else
|
| 79 |
+
echo "โ $APP_NAME is not running"
|
| 80 |
+
rm -f "$PID_FILE"
|
| 81 |
+
fi
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
logs() {
|
| 85 |
+
if [ -f "$LOG_FILE" ]; then
|
| 86 |
+
tail -f "$LOG_FILE"
|
| 87 |
+
else
|
| 88 |
+
echo "โ No log file found"
|
| 89 |
+
fi
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
case "${1:-start}" in
|
| 93 |
+
start)
|
| 94 |
+
start
|
| 95 |
+
;;
|
| 96 |
+
stop)
|
| 97 |
+
stop
|
| 98 |
+
;;
|
| 99 |
+
restart)
|
| 100 |
+
restart
|
| 101 |
+
;;
|
| 102 |
+
status)
|
| 103 |
+
status
|
| 104 |
+
;;
|
| 105 |
+
logs)
|
| 106 |
+
logs
|
| 107 |
+
;;
|
| 108 |
+
*)
|
| 109 |
+
echo "Usage: $0 {start|stop|restart|status|logs}"
|
| 110 |
+
exit 1
|
| 111 |
+
;;
|
| 112 |
+
esac
|
start-production.sh
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Production Start Script for K-AI API
|
| 3 |
+
# ====================================
|
| 4 |
+
# This script starts the K-AI API in production mode
|
| 5 |
+
|
| 6 |
+
cd ~/kai-api
|
| 7 |
+
|
| 8 |
+
echo "๐ Starting K-AI API in production mode..."
|
| 9 |
+
|
| 10 |
+
# Check if virtual environment exists
|
| 11 |
+
if [ ! -d "venv" ]; then
|
| 12 |
+
echo "๐ฆ Creating virtual environment..."
|
| 13 |
+
python3 -m venv venv
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
# Activate virtual environment
|
| 17 |
+
source venv/bin/activate
|
| 18 |
+
|
| 19 |
+
# Install/update dependencies
|
| 20 |
+
echo "๐ฅ Installing dependencies..."
|
| 21 |
+
pip install --upgrade pip
|
| 22 |
+
pip install -r requirements.txt
|
| 23 |
+
|
| 24 |
+
# Install Playwright browsers
|
| 25 |
+
echo "๐ญ Installing Playwright browsers..."
|
| 26 |
+
playwright install chromium
|
| 27 |
+
|
| 28 |
+
# Create necessary directories
|
| 29 |
+
mkdir -p static
|
| 30 |
+
mkdir -p /tmp/opencode_sessions
|
| 31 |
+
|
| 32 |
+
# Set environment variables for production
|
| 33 |
+
export PYTHONUNBUFFERED=1
|
| 34 |
+
export ENVIRONMENT=production
|
| 35 |
+
export PORT=8000
|
| 36 |
+
export HOST=0.0.0.0
|
| 37 |
+
|
| 38 |
+
# Check if .env file exists and source it
|
| 39 |
+
if [ -f ".env" ]; then
|
| 40 |
+
echo "๐ Loading environment variables from .env..."
|
| 41 |
+
export $(cat .env | grep -v '^#' | xargs)
|
| 42 |
+
fi
|
| 43 |
+
|
| 44 |
+
echo ""
|
| 45 |
+
echo "========================================"
|
| 46 |
+
echo "๐ K-AI API is starting!"
|
| 47 |
+
echo "========================================"
|
| 48 |
+
echo ""
|
| 49 |
+
echo "The API will be available at:"
|
| 50 |
+
echo " โข Local: http://localhost:8000"
|
| 51 |
+
echo " โข Admin: http://localhost:8000/qazmlp"
|
| 52 |
+
echo ""
|
| 53 |
+
echo "To run in background with auto-restart, use:"
|
| 54 |
+
echo " ./run-daemon.sh"
|
| 55 |
+
echo ""
|
| 56 |
+
echo "Starting server..."
|
| 57 |
+
echo ""
|
| 58 |
+
|
| 59 |
+
# Start the server
|
| 60 |
+
exec uvicorn main:app --host $HOST --port $PORT --workers 1
|
static/qaz.html
CHANGED
|
@@ -805,6 +805,28 @@
|
|
| 805 |
</div>
|
| 806 |
</div>
|
| 807 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
<!-- Browser Interface -->
|
| 809 |
<div id="browser-interface" style="display:none;">
|
| 810 |
<!-- Toolbar -->
|
|
@@ -821,7 +843,7 @@
|
|
| 821 |
|
| 822 |
<div class="quality-selector">
|
| 823 |
<span style="font-size:0.75rem;color:var(--text-secondary);">Quality:</span>
|
| 824 |
-
<button class="quality-btn active" onclick="setStreamQuality('low')
|
| 825 |
id="quality-low">Low</button>
|
| 826 |
<button class="quality-btn" onclick="setStreamQuality('medium')"
|
| 827 |
id="quality-medium">Med</button>
|
|
@@ -1206,9 +1228,15 @@
|
|
| 1206 |
// Update controls for terminal
|
| 1207 |
document.querySelector('.browser-address-bar').style.visibility = 'hidden';
|
| 1208 |
document.querySelector('.quality-selector').style.visibility = 'hidden';
|
| 1209 |
-
document.getElementById('terminal-sync-btn').style.display = '
|
| 1210 |
document.getElementById('browser-page-title').textContent = 'OpenCode Terminal';
|
| 1211 |
document.getElementById('browser-coords').textContent = 'Type commands below';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1212 |
}
|
| 1213 |
|
| 1214 |
async function syncTerminalAuth() {
|
|
@@ -1267,6 +1295,98 @@
|
|
| 1267 |
}, 1000);
|
| 1268 |
}
|
| 1269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1270 |
function showBrowserInterface() {
|
| 1271 |
document.getElementById('browser-interface').style.display = 'block';
|
| 1272 |
|
|
|
|
| 805 |
</div>
|
| 806 |
</div>
|
| 807 |
|
| 808 |
+
<!-- Disposable Mode Status Bar (shown only for terminal) -->
|
| 809 |
+
<div id="disposable-status-bar" style="display:none;padding:10px 16px;background:var(--surface-hover);border-bottom:1px solid var(--border);">
|
| 810 |
+
<div style="display:flex;justify-content:space-between;align-items:center;">
|
| 811 |
+
<div style="display:flex;align-items:center;gap:12px;">
|
| 812 |
+
<span style="font-size:0.875rem;font-weight:600;">๐๏ธ Disposable Mode</span>
|
| 813 |
+
<span style="font-size:0.75rem;color:var(--text-secondary);">
|
| 814 |
+
Messages: <span id="msg-count" style="color:var(--accent);font-weight:600;">0</span>/<span id="msg-max">20</span>
|
| 815 |
+
</span>
|
| 816 |
+
<div class="progress-bar" style="width:100px;height:6px;">
|
| 817 |
+
<div id="msg-progress" class="progress-fill" style="width:0%;background:var(--success);"></div>
|
| 818 |
+
</div>
|
| 819 |
+
</div>
|
| 820 |
+
<div style="display:flex;gap:8px;">
|
| 821 |
+
<span id="next-reset-timer" style="font-size:0.75rem;color:var(--text-secondary);"></span>
|
| 822 |
+
<button class="btn btn-secondary" style="padding:4px 12px;font-size:0.75rem;" onclick="manualReset()">๐ Reset Now</button>
|
| 823 |
+
</div>
|
| 824 |
+
</div>
|
| 825 |
+
<div style="margin-top:6px;font-size:0.75rem;color:var(--text-secondary);">
|
| 826 |
+
๐ Anonymous โข ๐ New chat between messages โข ๐งน Auto-reset after 20 messages
|
| 827 |
+
</div>
|
| 828 |
+
</div>
|
| 829 |
+
|
| 830 |
<!-- Browser Interface -->
|
| 831 |
<div id="browser-interface" style="display:none;">
|
| 832 |
<!-- Toolbar -->
|
|
|
|
| 843 |
|
| 844 |
<div class="quality-selector">
|
| 845 |
<span style="font-size:0.75rem;color:var(--text-secondary);">Quality:</span>
|
| 846 |
+
<button class="quality-btn active" onclick="setStreamQuality('low')
|
| 847 |
id="quality-low">Low</button>
|
| 848 |
<button class="quality-btn" onclick="setStreamQuality('medium')"
|
| 849 |
id="quality-medium">Med</button>
|
|
|
|
| 1228 |
// Update controls for terminal
|
| 1229 |
document.querySelector('.browser-address-bar').style.visibility = 'hidden';
|
| 1230 |
document.querySelector('.quality-selector').style.visibility = 'hidden';
|
| 1231 |
+
document.getElementById('terminal-sync-btn').style.display = 'none'; // Hide sync button (anonymous mode)
|
| 1232 |
document.getElementById('browser-page-title').textContent = 'OpenCode Terminal';
|
| 1233 |
document.getElementById('browser-coords').textContent = 'Type commands below';
|
| 1234 |
+
|
| 1235 |
+
// Show disposable mode status bar
|
| 1236 |
+
document.getElementById('disposable-status-bar').style.display = 'block';
|
| 1237 |
+
|
| 1238 |
+
// Start polling disposable status
|
| 1239 |
+
startDisposableStatusPolling();
|
| 1240 |
}
|
| 1241 |
|
| 1242 |
async function syncTerminalAuth() {
|
|
|
|
| 1295 |
}, 1000);
|
| 1296 |
}
|
| 1297 |
|
| 1298 |
+
// Disposable Mode Functions
|
| 1299 |
+
let disposableStatusInterval = null;
|
| 1300 |
+
|
| 1301 |
+
function startDisposableStatusPolling() {
|
| 1302 |
+
if (disposableStatusInterval) clearInterval(disposableStatusInterval);
|
| 1303 |
+
|
| 1304 |
+
// Poll status every 3 seconds
|
| 1305 |
+
disposableStatusInterval = setInterval(async () => {
|
| 1306 |
+
if (currentProvider !== 'opencode') return;
|
| 1307 |
+
|
| 1308 |
+
try {
|
| 1309 |
+
const res = await fetch('/qaz/terminal/status?model=kimi-k2.5-free');
|
| 1310 |
+
const data = await res.json();
|
| 1311 |
+
|
| 1312 |
+
if (data.status === 'success' && data.data) {
|
| 1313 |
+
updateDisposableStatusUI(data.data);
|
| 1314 |
+
}
|
| 1315 |
+
} catch (e) {
|
| 1316 |
+
console.error('Disposable status poll error:', e);
|
| 1317 |
+
}
|
| 1318 |
+
}, 3000);
|
| 1319 |
+
|
| 1320 |
+
// Initial poll
|
| 1321 |
+
fetch('/qaz/terminal/status?model=kimi-k2.5-free')
|
| 1322 |
+
.then(r => r.json())
|
| 1323 |
+
.then(data => {
|
| 1324 |
+
if (data.status === 'success') updateDisposableStatusUI(data.data);
|
| 1325 |
+
})
|
| 1326 |
+
.catch(e => console.error(e));
|
| 1327 |
+
}
|
| 1328 |
+
|
| 1329 |
+
function updateDisposableStatusUI(status) {
|
| 1330 |
+
document.getElementById('msg-count').textContent = status.message_count;
|
| 1331 |
+
document.getElementById('msg-max').textContent = status.max_messages;
|
| 1332 |
+
|
| 1333 |
+
const progress = (status.message_count / status.max_messages) * 100;
|
| 1334 |
+
const progressBar = document.getElementById('msg-progress');
|
| 1335 |
+
progressBar.style.width = progress + '%';
|
| 1336 |
+
|
| 1337 |
+
// Change color based on progress
|
| 1338 |
+
if (progress >= 90) {
|
| 1339 |
+
progressBar.style.background = 'var(--error)';
|
| 1340 |
+
} else if (progress >= 70) {
|
| 1341 |
+
progressBar.style.background = 'var(--warning)';
|
| 1342 |
+
} else {
|
| 1343 |
+
progressBar.style.background = 'var(--success)';
|
| 1344 |
+
}
|
| 1345 |
+
|
| 1346 |
+
const remaining = status.max_messages - status.message_count;
|
| 1347 |
+
document.getElementById('next-reset-timer').textContent =
|
| 1348 |
+
remaining <= 5 ? `โ ๏ธ Reset in ${remaining} messages!` : `${remaining} messages until reset`;
|
| 1349 |
+
}
|
| 1350 |
+
|
| 1351 |
+
async function manualReset() {
|
| 1352 |
+
if (!confirm('๐๏ธ This will completely wipe all traces and start fresh. OpenCode will see a brand new device. Continue?')) {
|
| 1353 |
+
return;
|
| 1354 |
+
}
|
| 1355 |
+
|
| 1356 |
+
const btn = event.target;
|
| 1357 |
+
const originalText = btn.textContent;
|
| 1358 |
+
btn.textContent = 'Resetting...';
|
| 1359 |
+
btn.disabled = true;
|
| 1360 |
+
|
| 1361 |
+
try {
|
| 1362 |
+
const res = await fetch('/qaz/terminal/reset', {
|
| 1363 |
+
method: 'POST',
|
| 1364 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1365 |
+
body: JSON.stringify({ model: 'kimi-k2.5-free' })
|
| 1366 |
+
});
|
| 1367 |
+
|
| 1368 |
+
const data = await res.json();
|
| 1369 |
+
|
| 1370 |
+
if (data.status === 'success') {
|
| 1371 |
+
btn.textContent = 'โ
Reset Complete!';
|
| 1372 |
+
alert('๐๏ธ Full disposable reset completed!\n\nOpenCode now sees a completely new device with fresh tokens.');
|
| 1373 |
+
// Clear terminal view
|
| 1374 |
+
document.getElementById('terminal-view').innerHTML = '๐ Session reset - Starting fresh...';
|
| 1375 |
+
} else {
|
| 1376 |
+
alert('Reset failed: ' + data.message);
|
| 1377 |
+
btn.textContent = 'โ Error';
|
| 1378 |
+
}
|
| 1379 |
+
} catch (e) {
|
| 1380 |
+
alert('Reset error: ' + e);
|
| 1381 |
+
btn.textContent = 'โ Error';
|
| 1382 |
+
} finally {
|
| 1383 |
+
setTimeout(() => {
|
| 1384 |
+
btn.textContent = originalText;
|
| 1385 |
+
btn.disabled = false;
|
| 1386 |
+
}, 2000);
|
| 1387 |
+
}
|
| 1388 |
+
}
|
| 1389 |
+
|
| 1390 |
function showBrowserInterface() {
|
| 1391 |
document.getElementById('browser-interface').style.display = 'block';
|
| 1392 |
|
test_prompt.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import asyncio
|
| 3 |
+
import os
|
| 4 |
+
import logging
|
| 5 |
+
from engine import AIEngine
|
| 6 |
+
|
| 7 |
+
# Configure logging
|
| 8 |
+
logging.basicConfig(level=logging.INFO)
|
| 9 |
+
|
| 10 |
+
async def main():
|
| 11 |
+
print("Initializing Engine...")
|
| 12 |
+
engine = AIEngine()
|
| 13 |
+
|
| 14 |
+
# Mocking system prompt file if it doesn't exist (it should)
|
| 15 |
+
|
| 16 |
+
print("\n--- Testing Chat ---")
|
| 17 |
+
try:
|
| 18 |
+
# We try 'opencode' because we know we settled auth for it,
|
| 19 |
+
# OR we try 'g4f'/pollinations if they are easier.
|
| 20 |
+
# User reported issue on 'gemini-3-flash'.
|
| 21 |
+
# Let's try to mock the provider response handling to see what PROMPT it actually gets?
|
| 22 |
+
# Actually, we can just print the prompt in engine.py by adding a log.
|
| 23 |
+
|
| 24 |
+
# But let's run a real request if possible.
|
| 25 |
+
# If not, at least we see if it crashes.
|
| 26 |
+
|
| 27 |
+
response = await engine.chat(
|
| 28 |
+
prompt="Hello! Are you there?",
|
| 29 |
+
model="gemini", # Friendly name for gemini-pro or similar, mapped in config
|
| 30 |
+
provider="auto"
|
| 31 |
+
)
|
| 32 |
+
print(f"\nResponse: {response['response']}")
|
| 33 |
+
print(f"Model used: {response['model']}")
|
| 34 |
+
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"Error: {e}")
|
| 37 |
+
|
| 38 |
+
if __name__ == "__main__":
|
| 39 |
+
asyncio.run(main())
|