From 994db0267e8482c29d8fe1e388983f1e7998b895 Mon Sep 17 00:00:00 2001 From: ajianaz Date: Wed, 2 Sep 2026 13:49:12 +0700 Subject: [PATCH] docs: fix accuracy findings in deployment, usage, auth-flow (v0.9.0 audit) deployment.md: .env examples use literal values (Compose does not evaluate $(...)), keys standardized on hex 32; chmod 777 -> 755 + chown guidance for the container user; Caddyfile header_up moved inside reverse_proxy block; systemd example uses EnvironmentFile for secrets and includes TITEN_ENCRYPTION_KEY; health checks use /api/health (compose deployments go through the web proxy on :3000); container refs match docker-compose.yml (titen-api); backup/restore aligned with the ./data bind mount; local media storage clarified (S3 optional, not required). usage.md: stray curl VIDEO example removed from the CLI section (CLI has no video flags; API section already covers it); TITEN_URL annotated (CLI API base URL, distinct from APP_URL); MCP tools table rebuilt from crates/titen-mcp/src/main.rs: 29 tools verified, stale names fixed. auth-flow.md: authorize URL documents all 8 requested scopes (matches settings.rs authorize_url construction). architecture.md: MCP tool count 14 -> 29 (verified against tools_list()). --- docs/architecture.md | 2 +- docs/auth-flow.md | 5 +- docs/deployment.md | 118 ++++++++++++++++++++++++------------------- docs/usage.md | 55 +++++++++++--------- 4 files changed, 102 insertions(+), 78 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index fa8b93a..34bfc9a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -108,7 +108,7 @@ The command-line entry point, built with **Clap**. An **MCP (Model Context Protocol) stdio server** for AI agent integration. -- Exposes **14 tools** covering accounts, posts, schedules, analytics, and media +- Exposes **29 tools** covering accounts, posts, schedules, analytics, and media - Communicates over stdin/stdout using the MCP protocol - Delegates all logic to `titen-core` diff --git a/docs/auth-flow.md b/docs/auth-flow.md index cd90c3a..1c891f9 100644 --- a/docs/auth-flow.md +++ b/docs/auth-flow.md @@ -117,7 +117,10 @@ Redirect to Threads authorize URL │ https://threads.net/oauth/authorize │ ?client_id=... │ &redirect_uri=.../auth/callback - │ &scope=threads_basic,threads_content_publish + │ &scope=threads_basic,threads_content_publish, + │ threads_manage_replies,threads_manage_mentions, + │ threads_keyword_search,threads_profile_discovery, + │ threads_share_to_instagram,threads_location_tagging │ &response_type=code │ &state= ← CSRF protection (#237) │ diff --git a/docs/deployment.md b/docs/deployment.md index 56ceb8f..9eb83f4 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -131,11 +131,14 @@ API_IMAGE=ghcr.io/codecoradev/titen:latest-api APP_URL=https://titen.yourdomain.com TITEN_HOST=titen.yourdomain.com -# Generate API key -TITEN_API_KEY=$(openssl rand -hex 24) - -# Generate encryption key for tokens at rest -TITEN_ENCRYPTION_KEY=$(openssl rand -hex 32) +# Generate API key (64 hex chars). Compose does NOT evaluate $(...) in .env +# files — generate the value in your shell first and paste the literal: +# openssl rand -hex 32 +TITEN_API_KEY=PASTE_GENERATED_64_HEX_CHAR_VALUE_HERE + +# Generate encryption key for tokens at rest (same rule: paste the literal): +# openssl rand -hex 32 +TITEN_ENCRYPTION_KEY=PASTE_GENERATED_64_HEX_CHAR_VALUE_HERE TITEN_REQUIRE_ENCRYPTION=true # HTTPS settings @@ -147,10 +150,16 @@ TITEN_CORS_ORIGINS=https://titen.yourdomain.com ```bash mkdir -p data -chmod 777 data +chmod 755 data ``` -SQLite needs write permission to this folder. The `chmod 777` ensures the container user can create and write the database file. +SQLite needs write permission to this folder. The API container runs as the non-root `titen` user — if it cannot write, grant that user ownership instead of loosening permissions: + +```bash +# Find the container user's UID (typically 1000): +docker compose exec api id -u titen +sudo chown -R 1000:1000 data +``` ### Step 5: Start the Containers @@ -165,8 +174,8 @@ docker compose up -d # Check container status docker compose ps -# Check health -curl http://localhost:3000/health +# Check health (the web container proxies /health and /api/health to the API) +curl -f http://localhost:3000/api/health # View logs docker compose logs -f --tail=50 @@ -179,7 +188,8 @@ You should see both containers running. The API container should log `titen-api The `./data/` directory is bind-mounted to `/data` inside the API container and stores: - **SQLite database** (`/data/titen.db`): all posts, schedules, accounts, analytics -- **Uploaded media** (if stored locally rather than S3) +- **Uploaded media** (media works out of the box on local storage under `/data/media`; + S3-compatible storage is optional — see [section 7](#7-s3-media-storage-optional)) This directory **survives container restarts and updates**. Without it, all data would be lost when containers are recreated. @@ -254,9 +264,15 @@ Group=titen # Binary location ExecStart=/usr/local/bin/titen serve -# Environment configuration +# Secrets live in a root-only environment file, NOT inline in the unit +# (unit files are world-readable via /etc/systemd/system): +# sudo install -m 600 /dev/null /etc/titen/titen.env then add: +# TITEN_API_KEY= +# TITEN_ENCRYPTION_KEY= +EnvironmentFile=/etc/titen/titen.env + +# Non-secret environment configuration Environment="TITEN_DB_PATH=/var/lib/titen/titen.db" -Environment="TITEN_API_KEY=your-secure-api-key-here" Environment="TITEN_HOST=0.0.0.0" Environment="TITEN_PORT=7845" Environment="TITEN_URL=https://titen.yourdomain.com" @@ -309,17 +325,17 @@ Create `/etc/caddy/Caddyfile`: ```caddyfile titen.yourdomain.com { - reverse_proxy localhost:7845 - # Allow large media uploads (images/video) request_body { max_size 100MB } - # Pass real client IP - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} + reverse_proxy localhost:7845 { + # Pass real client IP + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + } # Security headers header { @@ -491,11 +507,11 @@ The built-in scheduler checks token validity every 60 seconds (configurable via **Manual Refresh (CLI):** ```bash -# Inside the container -docker exec -it titen titen account list +# Inside the API container (name per docker-compose.yml) +docker exec -it titen-api titen account list # Refresh token manually -docker exec -it titen titen account refresh +docker exec -it titen-api titen account refresh ``` If a token expires, the Threads API will return `401 Unauthorized`. See [Troubleshooting](#11-troubleshooting). @@ -512,7 +528,7 @@ S3-compatible storage is needed when you: - **Upload videos** (VIDEO post type) - **Store media locally** is not sufficient for your scale -Without S3 configured, media uploads will fail. TEXT-only posts work without S3. +Media uploads work without any S3 configuration — files are stored locally under `/data/media` (served at `/media`). Configure S3-compatible storage only if you want object storage (e.g. for multi-host deployments or external CDN access). ### Supported Providers @@ -631,7 +647,7 @@ This command creates a safe backup without locking the running database: ```bash # Docker -docker exec titen sqlite3 /data/titen.db ".backup '/data/titen-backup-$(date +%Y%m%d).db'" +docker exec titen-api sqlite3 /data/titen.db ".backup '/data/titen-backup-$(date +%Y%m%d).db'" # Native sqlite3 /var/lib/titen/titen.db ".backup '/var/lib/titen/titen-backup-$(date +%Y%m%d).db'" @@ -647,17 +663,16 @@ Create a daily cron job: BACKUP_DIR="/opt/backups/titen" mkdir -p "$BACKUP_DIR" -# Backup SQLite -docker exec titen sqlite3 /data/titen.db ".backup '/data/backup.db'" -docker cp titen:/data/backup.db "$BACKUP_DIR/titen-$(date +%Y%m%d-%H%M%S).db" +# Backup SQLite (transactionally consistent copy) +docker exec titen-api sqlite3 /data/titen.db ".backup '/data/backup.db'" +docker cp titen-api:/data/backup.db "$BACKUP_DIR/titen-$(date +%Y%m%d-%H%M%S).db" -# Backup full volume -docker run --rm -v titen_titen-data:/data -v "$BACKUP_DIR":/backup \ - alpine tar czf "/backup/titen-volume-$(date +%Y%m%d-%H%M%S).tar.gz" /data +# Backup the full data directory (bind-mounted ./data per docker-compose.yml) +tar czf "$BACKUP_DIR/titen-data-$(date +%Y%m%d-%H%M%S).tar.gz" -C . data # Retain last 30 days find "$BACKUP_DIR" -name "titen-*.db" -mtime +30 -delete -find "$BACKUP_DIR" -name "titen-volume-*.tar.gz" -mtime +30 -delete +find "$BACKUP_DIR" -name "titen-data-*.tar.gz" -mtime +30 -delete echo "Backup complete: $(date)" ``` @@ -676,8 +691,7 @@ Back up the entire data volume (SQLite + media + config): ```bash # Create backup -docker run --rm -v titen_titen-data:/data -v $(pwd):/backup \ - alpine tar czf /backup/titen-volume-$(date +%Y%m%d).tar.gz /data +tar czf "/backup/titen-data-$(date +%Y%m%d).tar.gz" -C . data # List backups ls -lh titen-volume-*.tar.gz @@ -691,15 +705,14 @@ ls -lh titen-volume-*.tar.gz # 1. Stop Titen docker compose down -# 2. Copy backup into the volume -docker run --rm -v titen_titen-data:/data -v $(pwd):/backup \ - alpine cp /backup/titen-backup-20260101.db /data/titen.db +# 2. Copy the backup into ./data (bind-mounted to /data per docker-compose.yml) +cp /backup/titen-backup-20260101.db data/titen.db # 3. Start Titen docker compose up -d -# 4. Verify -curl http://localhost:7845/api/health +# 4. Verify (web proxies /api/health to the API) +curl -f http://localhost:3000/api/health ``` #### Restore Full Volume @@ -708,16 +721,15 @@ curl http://localhost:7845/api/health # 1. Stop Titen docker compose down -# 2. Extract volume backup -docker run --rm -v titen_titen-data:/data -v $(pwd):/backup \ - alpine tar xzf /backup/titen-volume-20260101.tar.gz -C / +# 2. Extract the data-directory backup +tar xzf /backup/titen-data-20260101.tar.gz -C . # 3. Start Titen docker compose up -d # 4. Verify -docker logs titen -curl http://localhost:7845/api/health +docker compose logs -f --tail=50 +curl -f http://localhost:3000/api/health ``` --- @@ -736,8 +748,8 @@ docker compose pull docker compose up -d # Verify the new version is running -docker logs titen -curl http://localhost:7845/api/health +docker compose logs --tail=50 +curl -f http://localhost:3000/api/health ``` This preserves your data volume. No data loss. @@ -845,7 +857,7 @@ sudo kill -9 4. **WAL file corruption**: if persistent, run a checkpoint: ```bash - docker exec titen sqlite3 /data/titen.db "PRAGMA wal_checkpoint(TRUNCATE);" + docker exec titen-api sqlite3 /data/titen.db "PRAGMA wal_checkpoint(TRUNCATE);" ``` --- @@ -858,10 +870,10 @@ sudo kill -9 ```bash # Check account status -docker exec titen titen account list +docker exec titen-api titen account list # View recent errors in logs -docker logs titen 2>&1 | grep -i "401\|unauthorized\|token" +docker logs titen-api 2>&1 | grep -i "401\|unauthorized\|token" ``` **Fix:** @@ -869,7 +881,7 @@ docker logs titen 2>&1 | grep -i "401\|unauthorized\|token" 1. **Manual refresh:** ```bash - docker exec -it titen titen account refresh + docker exec -it titen-api titen account refresh ``` 2. **Re-authenticate if refresh fails** (token fully expired): @@ -885,7 +897,7 @@ docker logs titen 2>&1 | grep -i "401\|unauthorized\|token" 3. **Verify the scheduler is running** (handles automatic refresh): ```bash - docker logs titen 2>&1 | grep -i "scheduler\|refresh\|token" + docker logs titen-api 2>&1 | grep -i "scheduler\|refresh\|token" ``` --- @@ -898,7 +910,7 @@ docker logs titen 2>&1 | grep -i "401\|unauthorized\|token" ```bash # Check logs for S3 errors -docker logs titen 2>&1 | grep -i "s3\|media\|upload" +docker logs titen-api 2>&1 | grep -i "s3\|media\|upload" ``` **Fix:** @@ -967,7 +979,7 @@ docker compose up -d ```bash # View container logs -docker logs titen +docker logs titen-api # Check container status docker compose ps -a @@ -988,7 +1000,7 @@ docker inspect titen --format='{{.State.ExitCode}}' ```bash # 1. Read the full logs -docker logs titen --tail 100 +docker logs titen-api --tail 100 # 2. Check if port is in use docker compose down @@ -1012,4 +1024,4 @@ If you're still stuck: - **GitHub Issues:** [github.com/codecoradev/titen/issues](https://github.com/codecoradev/titen/issues) - **Documentation:** [github.com/codecoradev/titen/docs](https://github.com/codecoradev/titen/tree/main/docs) -- **Logs:** Always include `docker logs titen` output when reporting issues +- **Logs:** Always include `docker logs titen-api` output when reporting issues diff --git a/docs/usage.md b/docs/usage.md index 421e216..a5cfc80 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -61,7 +61,7 @@ Titen uses the Threads Graph API. You need a Threads account and a Meta for Deve ```bash # Set your API key (if auth is enabled) export TITEN_API_KEY=your-key -export TITEN_URL=http://localhost:7845 +export TITEN_URL=http://localhost:7845 # API base URL used by the CLI (APP_URL is the separate public site URL) # Add the account titen account add mybrand \ @@ -133,17 +133,10 @@ titen post create mybrand \ --image-url "https://example.com/photo.jpg" \ --text "Check this out" -# Video post -curl -X POST http://localhost:7845/api/posts \ - -H "X-API-Key: your-key" \ - -H "Content-Type: application/json" \ - -d '{ - "account_id": 1, - "media_type": "VIDEO", - "video_url": "https://example.com/video.mp4" - }' ``` +(The API supports VIDEO posts via `POST /api/posts` — see the next section.) + ### Via API ```bash @@ -449,23 +442,39 @@ Add to your MCP settings (`Settings → MCP`): } ``` -### Available MCP Tools (14) +### Available MCP Tools (29) | Tool | Description | |------|-------------| -| `list_accounts` | List all Threads accounts | -| `get_account` | Get account by ID | -| `create_post` | Create and publish a post | -| `schedule_post` | Schedule a post | +| `list_accounts` | List all Threads accounts managed by titen | +| `get_user_profile` | Fetch a Threads user's profile from the Threads API | +| `get_publishing_limit` | Fetch an account's Threads publishing quota (daily post limit, etc.) | +| `create_post` | Create and publish a Threads post | +| `schedule_post` | Schedule a post for future publishing | | `list_schedules` | List scheduled posts | -| `cancel_schedule` | Cancel a schedule | -| `fetch_comments` | Fetch comments from Threads | -| `get_post_sentiment` | Analyze comment sentiment | -| `get_post_analytics` | Analytics for a post | -| `get_account_analytics` | Analytics summary per account | -| `upload_media` | Upload media to S3 | -| `refresh_token` | Refresh an account token | -| `check_tokens` | Batch token expiry check | +| `cancel_schedule` | Cancel a scheduled post | +| `refresh_token` | Refresh an account's Threads access token | +| `check_tokens` | Check all accounts' token expiry status and auto-refresh expiring tokens | +| `fetch_comments` | Fetch and store comments from a Threads post via the Threads API | +| `get_post_sentiment` | Get sentiment analysis for a post's comments | +| `get_post_insights` | Fetch post insights (likes, replies, reposts, views, quotes) from the Threads API | +| `get_account_analytics` | Get analytics summary for an account's posts | +| `delete_post` | Delete a post from Threads and the local database | +| `create_container` | Create a Threads container (first step for media posts, carousel, etc.) | +| `publish_container` | Publish a previously created Threads container by container ID | +| `list_posts` | List published/draft posts with optional filtering | +| `get_post` | Get a single post by ID | +| `get_schedule` | Get a single scheduled post by ID | +| `approve_schedule` | Approve a pending scheduled post for publishing (HITL approval) | +| `reject_schedule` | Reject a pending scheduled post with optional reason (HITL rejection) | +| `upload_media` | Upload a media asset (image) to titen storage for use in posts/carousels | +| `list_media` | List media assets stored in titen | +| `fetch_mentions` | Fetch mentions of a managed account from the Threads API and store them | +| `list_mentions` | List stored mentions for an account | +| `search_keyword` | Search Threads for a keyword or trending topic | +| `get_post_trend` | Get time-series engagement trend data for a post (stored analytics snapshots) | +| `reply_to_comment` | Reply to a comment on a Threads post directly from the AI agent | +| `exchange_oauth_code` | Exchange an OAuth authorization code for a long-lived token and add account to titen | ### Example: Ask Claude to Post