# Tianji Documentation Export (Full) This file aggregates documentation for LLM consumption. It preserves Markdown headings, links, and code fences; frontmatter and MDX ESM are removed. ================================================================================ ===== FILE: docs/ai-router/intro.md ===== # AI Router AI Router gives one stable AI endpoint for a group of AI Gateways. It routes each request through configured gateway routes, spreads traffic by weight inside the same tier, and falls back to the next tier when retryable failures happen. Use it when you want: - One endpoint for your application instead of hard-coding one AI provider. - Weighted traffic split across multiple gateways. - Fallback from a primary provider to backup providers during outages or rate limits. - A migration path where you can move traffic gradually by changing weights. ## How it relates to AI Gateway AI Gateway is still the unit that stores provider credentials, custom base URLs, model pricing, quota alerts, and gateway logs. AI Router does not replace that. AI Router only decides which gateway route should receive the request. The runtime flow is: 1. Your application calls an AI Router endpoint. 2. AI Router finds the router by workspace ID and router ID. 3. AI Router chooses an eligible gateway route from the first tier. 4. The selected AI Gateway sends the request to the upstream AI provider. 5. If the attempt succeeds, AI Router returns that response. 6. If the attempt fails with a retryable error, AI Router tries another route in the same tier, then the next tier. ## Prerequisites Before adding routes, create at least one AI Gateway with a stored model API key. Gateways without a stored key are not shown in the AI Router route picker. Runtime requests still need a Tianji API key: - For OpenAI-compatible endpoints, send `Authorization: Bearer `. - For Anthropic Messages endpoints, send `x-api-key: `. Tianji verifies the caller API key, then uses the stored AI Gateway provider key for the upstream request. ## Create a router 1. Open **AI Router** in the Tianji sidebar. 2. Click **Add AI Router**. 3. Enter a router name. 4. Keep **Enabled** on if the router should accept runtime traffic. 5. Save the router. After the router is created, open the **Routes** tab to configure tiers and gateway routes. ## Tiers A tier is a fallback level. Requests always start at the first tier. If a retryable failure happens, AI Router keeps trying eligible routes in that tier. If every eligible route in the tier fails, AI Router moves to the next tier. Use multiple tiers when you want strict fallback order. Example: | Tier | Routes | Meaning | | --- | --- | --- | | Tier 1 | OpenAI primary, OpenRouter primary | Normal production traffic | | Tier 2 | DeepSeek backup | Backup after primary providers fail | | Tier 3 | Custom internal model | Last-resort fallback | Drag tiers to reorder them. The top tier is tried first. ## Weights inside a tier Routes inside the same tier do not have a fixed order. They share traffic by weight. Example: | Route | Weight | Approximate first-attempt share | | --- | ---: | ---: | | Gateway A | 80 | 80% | | Gateway B | 20 | 20% | This is useful for: - Random traffic split across providers. - Gradual migration from one provider to another. - Canarying a new gateway with a small percentage of traffic. If you need strict order, put the routes in different tiers instead of the same tier. ## Add a gateway route On the **Routes** tab: 1. Click **Add Gateway** inside a tier. 2. Select an existing AI Gateway. 3. Select the provider mode for this route. 4. Set the route options. 5. Save. You can edit or delete a route later from the route card. ### Provider Provider controls how AI Router calls the selected AI Gateway for this route. The same AI Gateway can be used in different routes with different provider modes if that matches your setup. Supported provider values: - `openai` - `deepseek` - `anthropic` - `openrouter` - `custom` For `custom`, AI Router uses the custom model settings stored on the selected AI Gateway, such as custom base URL and custom model name. ### Weight Weight controls how traffic is distributed between routes in the same tier. Higher weight means the route is more likely to be tried first. Default: `100`. ### Model Override Model Override is optional. When set, AI Router replaces the request `model` with this value before sending the request to the selected gateway route. Leave it empty if the application request should decide the model. ### Timeout Timeout is the maximum time for one gateway attempt. Default: `30000ms`. If the attempt times out, AI Router treats it as retryable and can try the next eligible route. ### Retryable Status Codes AI Router always treats network errors, timeouts, and these status codes as retryable: - `429` - `500` - `502` - `503` - `504` Use **Retryable Status Codes** to add more status codes for a route. For example, you can add `408` if a provider often reports request timeout as an HTTP response. Be careful with validation errors such as `400` or `401`. Those usually mean the request or key is wrong, and retrying another provider can hide the real problem. ### Fail on Empty Content Fail on Empty Content is optional and defaults to off. When enabled for a gateway route, AI Router treats a successful upstream response with no assistant text content as a failed attempt. It then tries the next eligible route in the same tier, followed by lower tiers if needed. Tool-only responses are still treated as valid responses. This prevents function calling or tool-use requests from failing only because they did not produce assistant text. ## Logs The **Logs** tab shows runtime attempts for a router: - Status: `Success`, `Failed`, or `Partial`. - Protocol: the matched request protocol. - Attempts: how many gateway routes were tried. - Final Gateway: the gateway that produced the final result. - Final Gateway Log: the linked AI Gateway log ID. - Duration. Use router logs to understand failover behavior. Use the linked AI Gateway logs to inspect token usage, upstream model details, cost, and provider response data. ===== FILE: docs/ai-router/usage.md ===== # AI Router usage AI Router exposes OpenAI-compatible and Anthropic-compatible endpoints under your Tianji server: ```bash https://your-tianji-domain.com/api/ai-router////v1/... ``` The `` segment must match the provider mode you configured on the route. ## Supported endpoints | Provider segment | Chat Completions | Responses | Anthropic Messages | | --- | --- | --- | --- | | `openai` | `/openai/v1/chat/completions` | `/openai/v1/responses` | - | | `deepseek` | `/deepseek/v1/chat/completions` | - | - | | `anthropic` | `/anthropic/v1/chat/completions` | - | `/anthropic/v1/messages` | | `openrouter` | `/openrouter/v1/chat/completions` | - | `/openrouter/v1/messages` | | `custom` | `/custom/v1/chat/completions` | `/custom/v1/responses` | `/custom/v1/messages` | ## OpenAI Chat Completions Base URL for OpenAI SDK: ```bash https://your-tianji-domain.com/api/ai-router///openai/v1 ``` Node.js: ```js const client = new OpenAI({ apiKey: process.env.TIANJI_API_KEY, baseURL: 'https://your-tianji-domain.com/api/ai-router///openai/v1', }); const response = await client.chat.completions.create({ model: 'gpt-5.5', messages: [{ role: 'user', content: 'Hello from Tianji AI Router' }], }); console.log(response.choices[0].message); ``` cURL: ```bash curl -X POST 'https://your-tianji-domain.com/api/ai-router///openai/v1/chat/completions' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "model": "gpt-5.5", "messages": [ { "role": "user", "content": "Hello from Tianji AI Router" } ] }' ``` ## OpenAI Responses Base URL for OpenAI SDK: ```bash https://your-tianji-domain.com/api/ai-router///openai/v1 ``` Node.js: ```js const client = new OpenAI({ apiKey: process.env.TIANJI_API_KEY, baseURL: 'https://your-tianji-domain.com/api/ai-router///openai/v1', }); const response = await client.responses.create({ model: 'gpt-5.5', input: 'Write a short deployment checklist.', }); console.log(response.output_text); ``` cURL: ```bash curl -X POST 'https://your-tianji-domain.com/api/ai-router///openai/v1/responses' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "model": "gpt-5.5", "input": "Write a short deployment checklist." }' ``` ## Anthropic Messages Base URL for Anthropic SDK: ```bash https://your-tianji-domain.com/api/ai-router///anthropic ``` Node.js: ```js const client = new Anthropic({ apiKey: process.env.TIANJI_API_KEY, baseURL: 'https://your-tianji-domain.com/api/ai-router///anthropic', }); const message = await client.messages.create({ model: 'claude-opus-4-8', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello from Tianji AI Router' }], }); console.log(message.content); ``` cURL: ```bash curl -X POST 'https://your-tianji-domain.com/api/ai-router///anthropic/v1/messages' \ -H 'Content-Type: application/json' \ -H 'x-api-key: ' \ -H 'anthropic-version: 2023-06-01' \ -d '{ "model": "claude-opus-4-8", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Hello from Tianji AI Router" } ] }' ``` ## Custom provider routes Use the `custom` provider segment when the selected AI Gateway stores a custom base URL or custom model name. Examples: ```bash /api/ai-router///custom/v1/chat/completions /api/ai-router///custom/v1/responses /api/ai-router///custom/v1/messages ``` The custom upstream details stay on AI Gateway. AI Router only selects the route and forwards the normalized request. ## Recommended routing patterns ### Active backup Use this when uptime matters more than traffic distribution. | Tier | Route | Weight | | --- | --- | ---: | | 1 | Primary OpenAI gateway | 100 | | 2 | OpenRouter gateway | 100 | | 3 | Custom fallback gateway | 100 | AI Router tries Tier 2 only after Tier 1 returns retryable failures. ### Weighted split Use this when you want to share traffic across providers in normal operation. | Tier | Route | Weight | | --- | --- | ---: | | 1 | Gateway A | 80 | | 1 | Gateway B | 20 | Both routes are in the same tier, so there is no primary/secondary order. The weights decide which route is likely to be tried first. ### Canary migration Use this when testing a new provider. | Tier | Route | Weight | | --- | --- | ---: | | 1 | Current provider | 95 | | 1 | New provider | 5 | | 2 | Stable fallback | 100 | Increase the new provider weight after you confirm quality and reliability in the logs. ## Troubleshooting ### No eligible AI Router nodes are available Check that: - The router is enabled. - At least one tier has enabled routes. - The selected AI Gateway still has a stored model API key. - The route provider supports the endpoint you are calling. ### The router stops after one failed attempt AI Router only continues after retryable failures. Network errors, timeouts, `429`, `500`, `502`, `503`, and `504` are retryable by default. Add route-specific retryable status codes if a provider uses other temporary failure codes. ### The wrong model is being used Check both places: - Route **Model Override**. If set, it replaces the request `model`. - AI Gateway custom model name. For `custom` routes, the gateway can replace the model with its custom model name. ### The request returns 401 or 403 Use a Tianji API key in the runtime request. Do not send the upstream provider key to AI Router when the gateway stores its own provider credential. For OpenAI-compatible endpoints, use: ```bash Authorization: Bearer ``` For Anthropic Messages endpoints, use: ```bash x-api-key: ``` ===== FILE: docs/api/authentication.md ===== # Authentication This document provides detailed instructions on how to authenticate with the Tianji API, including obtaining, using, and managing API keys. ## Authentication Method The Tianji API uses **Bearer Token** authentication. You need to include your API key in the HTTP header of each API request. ### HTTP Header Format ```http Authorization: Bearer ``` ## Obtaining API Keys 1. Log in to your Tianji instance 2. Click on your avatar in the top right corner 4. Find the **API Keys** section 5. Click the + button to create a new API key 6. Name your API key and save it ## API Key Management ### View Existing Keys In the **API Keys** section, you can see: - API key name/description - Creation date - Last used time - Usage count statistics ### Delete API Keys If you need to revoke an API key: 1. Find the API key you want to delete 2. Click the **Delete** button 3. Confirm the deletion operation :::warning Note After deleting an API key, all applications using that key will no longer be able to access the API. ::: ## Using API Keys ### cURL Example ```bash curl -X GET "https://your-tianji-domain.com/open/global/config" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ### JavaScript/Node.js Example ```javascript const apiKey = ''; const baseUrl = 'https://your-tianji-domain.com/open'; const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; // Using fetch const response = await fetch(`${baseUrl}/global/config`, { method: 'GET', headers: headers }); // Using axios const axios = require('axios'); const response = await axios.get(`${baseUrl}/global/config`, { headers: headers }); ``` ### Python Example ```python $baseUrl = 'https://your-tianji-domain.com/open'; $headers = [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json' ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $baseUrl . '/global/config'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); ?> ``` ## Permissions and Scope ### API Key Permissions API keys inherit all permissions of their creator, including: - Access to all data in the user's workspaces - Execute all operations the user has permission for - Manage resources created by that user ### Workspace Access API keys can only access workspaces that the user belongs to. If you need to access multiple workspaces, ensure your user account has appropriate permissions for those workspaces. ## Error Handling ### Common Authentication Errors #### 401 Unauthorized ```json { "error": { "code": "UNAUTHORIZED", "message": "Authorization not provided" } } ``` **Causes**: - No Authorization header provided - Incorrect API key format #### 403 Forbidden ```json { "error": { "code": "FORBIDDEN", "message": "Insufficient access" } } ``` **Causes**: - Invalid or deleted API key - User doesn't have permission to access the requested resource ### Debugging Authentication Issues 1. **Check API key format**: Ensure you're using the `Bearer token_here` format 2. **Verify key validity**: Confirm the key still exists in the Tianji interface 3. **Check permissions**: Ensure the user account has permission to access the target resource 4. **Test simple endpoints**: Start by testing public endpoints like `/global/config` ===== FILE: docs/api/getting-started.md ===== # API Getting Started Tianji provides a complete REST API that allows you to programmatically access and operate all Tianji features. This guide will help you quickly get started with the Tianji API. ## Overview The Tianji API is based on REST architecture and uses JSON format for data exchange. All API requests must be made over HTTPS and require proper authentication. ### API Base URL ```bash https://your-tianji-domain.com/open ``` ### Supported Features Through the Tianji API, you can: - Manage website analytics data - Create and manage monitoring projects - Get server status information - Manage surveys - Operate telemetry data - Create and manage workspaces ## Quick Start ### 1. Get API Key Before using the API, you need to obtain an API key: 1. Log in to your Tianji instance 2. Click on your avatar in the top right corner 4. Find the **API Keys** section 5. Click the + button to create a new API key 6. Name your API key and save it ### 2. Enable OpenAPI Make sure your Tianji instance has OpenAPI access enabled: Set in your environment variables: ```bash ALLOW_OPENAPI=true ``` ### 3. First API Call Test your API connection using curl: ```bash curl -X GET "https://your-tianji-domain.com/open/global/config" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ## Next Steps - Check the [Authentication Documentation](./authentication.md) for detailed authentication methods - Browse the [API Reference Documentation](/api) for all available endpoints - Use the [OpenAPI SDK](./openapi-sdk.md) for type-safe API calls ===== FILE: docs/api/openapi-sdk.md ===== # OpenAPI SDK Usage Guide This document provides detailed instructions on how to use the Tianji SDK to call OpenAPI interfaces and achieve complete programmatic access to Tianji services. ## Overview The Tianji OpenAPI SDK is based on an auto-generated TypeScript client, providing type-safe API calling methods. Through the SDK, you can: - Manage workspaces and websites - Retrieve analytics data and statistics - Operate monitoring projects - Manage surveys - Handle Feed channels and events - ... [Complete API Documentation](/api) ## Installation and Initialization ### Install SDK ```bash npm install tianji-client-sdk # or yarn add tianji-client-sdk # or pnpm add tianji-client-sdk ``` ### Initialize OpenAPI Client ```javascript initOpenapiSDK('https://your-tianji-domain.com', { apiKey: 'your-api-key' }); ``` ## Global Configuration API ### Get System Configuration ```javascript async function getSystemConfig() { try { const config = await openApiClient.GlobalService.globalConfig(); console.log('Allow registration:', config.allowRegister); console.log('AI features enabled:', config.ai.enable); console.log('Billing enabled:', config.enableBilling); return config; } catch (error) { console.error('Failed to get system configuration:', error); } } ``` ===== FILE: docs/application/tracking.md ===== # Application Tracking Tianji provides a powerful SDK for tracking events and user behavior in your applications. This guide explains how to integrate and use the Application Tracking SDK in your projects. ## Installation Install the Tianji react native SDK in your project: ```bash npm install tianji-react-native # or yarn add tianji-react-native # or pnpm add tianji-react-native ``` ## Initialization Before using any tracking features, you need to initialize the Application SDK with your Tianji server URL and application ID: ```ts initApplication({ serverUrl: 'https://tianji.example.com', // Your Tianji server URL applicationId: 'your-application-id' // Your application identifier }); ``` ## Tracking Events You can track custom events in your application to monitor user actions and behaviors: ```ts // Track a simple event reportApplicationEvent('Button Clicked'); // Track an event with additional data reportApplicationEvent('Purchase Completed', { productId: 'product-123', price: 29.99, currency: 'USD' }); ``` ## Screen Tracking Track screen views in your application to understand user navigation patterns: ### Setting Current Screen You can set the current screen information which will be included in subsequent events: ```ts // Update current screen when user navigates updateCurrentApplicationScreen('ProductDetails', { productId: 'product-123' }); ``` ### Reporting Screen Views Explicitly report screen view events: ```ts // Report current screen view reportApplicationScreenView(); // Or report a specific screen view reportApplicationScreenView('Checkout', { cartItems: 3 }); ``` #### Intergate with expo-router ```tsx // Identify a user with their information identifyApplicationUser({ id: 'user-123', // Unique user identifier email: 'user@example.com', name: 'John Doe', // Add any other user properties plan: 'premium', signupDate: '2023-01-15' }); ``` ## API Reference ### `initApplication(options)` Initializes the application tracking SDK. **Parameters:** - `options`: ApplicationTrackingOptions - `serverUrl`: Your Tianji server URL (e.g., 'https://tianji.example.com') - `applicationId`: Your application identifier ### `reportApplicationEvent(eventName, eventData?, screenName?, screenParams?)` Sends an application event to the Tianji server. **Parameters:** - `eventName`: Name of the event (max 50 chars) - `eventData`: (Optional) Event data object - `screenName`: (Optional) Screen name to override current screen - `screenParams`: (Optional) Screen parameters to override current screen params ### `updateCurrentApplicationScreen(name, params)` Updates the current application screen information. **Parameters:** - `name`: Screen name - `params`: Screen parameters object ### `reportApplicationScreenView(screenName?, screenParams?)` Sends a screen view event to the Tianji server. **Parameters:** - `screenName`: (Optional) Screen name to override current screen - `screenParams`: (Optional) Screen parameters to override current screen params ### `identifyApplicationUser(userInfo)` Identifies a user in the application. **Parameters:** - `userInfo`: User identification data object ## Payload Limitations - Language information: max 35 characters - Operating system information: max 20 characters - URL information: max 500 characters - Event name: max 50 characters ===== FILE: docs/dev/prepare.md ===== # Prepare To Develop ## Prepare Database which using docker ```bash docker run --name tianji-pg -e POSTGRES_DB=tianji -e POSTGRES_USER=tianji -e POSTGRES_PASSWORD=tianji -d postgres:15.4-alpine ``` ## Prepare .env file ```bash cp .env.example src/server/.env ``` config `DATABASE_URL` with `postgresql://tianji:tianji@localhost:5432/tianji?schema=public` Like this: ```ini DATABASE_URL="postgresql://tianji:tianji@localhost:5432/tianji?schema=public" ALLOW_OPENAPI=true ``` ## Prepare Database struct Now you can visit database, and you can visit ```bash cd src/server && pnpm db:migrate:apply ``` ## Default Account Default Account is `admin`/`admin` ===== FILE: docs/events/track.md ===== # Event Tracking You can track user actions on your website. Tianji provides a simple way to do that. ## Using Script Tag if you are using script tag, you just need to call `track` function in any where after you inject script ```ts tianji.track(eventName, data); ``` ## Using SDK if you are using sdk, you just need to call `reportEvent()` function after you `initTianjiTracker()` ```ts initTianjiTracker({ url: backendUrl, websiteId, }); reportEvent('Demo Event', { foo: 'bar', }); ``` ===== FILE: docs/feed/channels.md ===== # Channels Channels are containers of events. You can create multiple channels for different products/teams/environments. ## Create a channel Console → Feed → Add Channel. Fields - name: Display name - notifyFrequency: Control how often notifications are sent for this channel - notificationIds: Select existing notification targets to receive fan-out ## Edit a channel You can update name, notificationIds, notifyFrequency, and set an optional webhookSignature. Once a signature is set, any public webhook to this channel must include header `x-webhook-signature` with the same value. ## API - List channels: GET `/open/workspace/{workspaceId}/feed/channels` - Channel info: GET `/open/workspace/{workspaceId}/feed/{channelId}/info` - Update: POST `/open/workspace/{workspaceId}/feed/{channelId}/update` - Create: POST `/open/workspace/{workspaceId}/feed/createChannel` - Delete: DELETE `/open/workspace/{workspaceId}/feed/{channelId}/del` Example (update notification targets) ```bash curl -X POST \ "$BASE_URL/workspace/$WORKSPACE_ID/feed/$CHANNEL_ID/update" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Ops", "notifyFrequency": 60, "notificationIds": ["notif_123", "notif_456"] }' ``` ===== FILE: docs/feed/integrations.md ===== # Integrations Tianji provides built-in webhook adapters to convert third-party payloads into Feed events. ## GitHub Endpoint POST `/open/feed/{channelId}/github` Notes - Use "application/json" content type. - Header `X-GitHub-Event` is required by GitHub and consumed by the adapter. - Supported types: `push`, `star`, `issues` (opened/closed). Others will be logged as unknown. ## Stripe Endpoint POST `/open/feed/{channelId}/stripe` Notes - Configure a Stripe webhook endpoint pointing to the URL above. - Supported types: `payment_intent.succeeded`, `payment_intent.canceled`, `customer.subscription.created`, `customer.subscription.deleted`. ## Sentry Endpoint POST `/open/feed/{channelId}/sentry` Notes - Header `Sentry-Hook-Resource: event_alert` and action `triggered` are mapped to Feed events. - See step-by-step screenshots in "Integration with Sentry". ## Tencent Cloud Alarm Endpoint POST `/open/feed/{channelId}/tencent-cloud/alarm` Notes - Supports alarm types `event` and `metric`. Payload is validated; invalid requests are rejected. ## Webhook Playground Endpoint POST `/open/feed/playground/{workspaceId}` Notes - Echo headers/body/method/url to the workspace real-time playground for debugging integrations. ===== FILE: docs/feed/intro.md ===== # Feed overview Feed is a lightweight event stream for your workspace. It helps teams aggregate important events from different systems into channels, collaborate around incidents, and keep stakeholders informed. ## Concepts - Channel: A logical stream to collect and organize events. Each channel can be connected to one or more notification targets and can optionally require a webhook signature. - Event: A single record with name, content, tags, source, sender identity, importance and optional payload. Events can be archived/unarchived. - State: A special kind of ongoing event that can be upserted (created or updated) repeatedly by a stable eventId, and resolved when finished. - Integration: Built-in webhook adapters that convert 3rd-party payloads (e.g. GitHub, Stripe, Sentry, Tencent Cloud Alarm) into Feed events. - Notification: Channels can fan-out events to configured notifiers; delivery frequency can be tuned by channel settings. ## Typical use cases - Product and infra incident stream across multiple services - CI/CD deployment and release notices - Billing and subscription signals - Security, monitoring and error alerts ===== FILE: docs/feed/sentry.md ===== # Integration with Sentry :::info Learn more about sentry in [sentry.io](https://sentry.io/) ::: Click `Settings` => `Integrations` => `Create New Integration` ![](/img/docs/sentry/sentry1.png) Create a `Internal Integration` application ![](/img/docs/sentry/sentry2.png) Input name `Tianji` and put in webhook url into form. ![](/img/docs/sentry/sentry3.png) Don't forget enable `Alert Rule Action` ![](/img/docs/sentry/sentry4.png) Then, add issue read `permission`, and webhook add `issue` and `error` ![](/img/docs/sentry/sentry5.png) Finally, you can create a alert rule, and you can see `Tianji` in notification section dropdown list ![](/img/docs/sentry/sentry6.png) ===== FILE: docs/feed/state.md ===== # State (ongoing incidents) Feed State models ongoing incidents with an immutable `eventId`. You can repeatedly upsert updates to the same `eventId` until it is resolved. ## Endpoints - Upsert state (public): `POST /open/feed/{channelId}/state/upsert` - Resolve state (auth): `POST /open/workspace/{workspaceId}/feed/{channelId}/state/resolve` - List ongoing states (auth): `GET /open/workspace/{workspaceId}/feed/state/all?channelId=...&limit=...` ## Upsert body ```json { "eventId": "deploy#2025-08-12", "eventName": "deploy_progress", "eventContent": "Rollout 60%", "tags": ["prod"], "source": "ci", "senderId": "runner-42", "senderName": "GitHub Actions", "important": true, "payload": {"stage": "canary"} } ``` If the channel is configured with `webhookSignature`, you must include header `x-webhook-signature`. ## Resolve example ```bash curl -X POST \ "$BASE_URL/workspace/$WORKSPACE_ID/feed/$CHANNEL_ID/state/resolve" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"stateId": "STATE_ID"}' ``` ===== FILE: docs/install/docker-monitoring.md ===== # Docker Container Monitoring Configuration ## Default Monitoring Behavior When you install Tianji using Docker or Docker Compose, the system automatically enables built-in server monitoring functionality. By default: - **Tianji automatically monitors its own container's** system resource usage - Monitoring data includes: CPU usage, memory usage, disk usage, network traffic, etc. - This data is automatically reported to the default workspace without additional configuration - The container will appear as `tianji-container` in the monitoring dashboard ## Monitoring All Docker Services on the Host Machine If you want Tianji to monitor all Docker containers and services running on the host machine, not just Tianji itself, you need to map the Docker Socket into the container. ### Configuration Method Add the following volumes configuration to the `tianji` service section in your `docker-compose.yml` file: ```yaml services: tianji: image: moonrailgun/tianji # ... other configurations ... volumes: - /var/run/docker.sock:/var/run/docker.sock # ... other configurations ... ``` ### Complete docker-compose.yml Example ```yaml version: '3' services: tianji: image: moonrailgun/tianji build: context: ./ dockerfile: ./Dockerfile ports: - "12345:12345" environment: DATABASE_URL: postgresql://tianji:tianji@postgres:5432/tianji JWT_SECRET: replace-me-with-a-random-string ALLOW_REGISTER: "false" ALLOW_OPENAPI: "true" volumes: - /var/run/docker.sock:/var/run/docker.sock # Add this line depends_on: - postgres restart: always postgres: # ... postgres configuration ... ``` ### Using Docker Run Command If you start Tianji using the `docker run` command, you can add the following parameter: ```bash docker run -d \ --name tianji \ -p 12345:12345 \ -v /var/run/docker.sock:/var/run/docker.sock \ moonrailgun/tianji ``` ## Effects After Configuration After adding the Docker Socket mapping, Tianji will be able to: - Monitor all Docker containers running on the host machine - Obtain container resource usage information - Display container status information - Provide a more comprehensive system monitoring view ===== FILE: docs/install/environment.md ===== # Environment Variables Tianji supports various environment variables to customize its behavior. You can configure these variables in your docker compose `env` field or through your deployment environment. ## Basic Configuration | Variable | Description | Default | Example | | --- | --- | --- | --- | | `PORT` | Server port | `12345` | `3000` | | `JWT_SECRET` | Secret for JWT tokens | Random Text | `your-secret-key` | | `ALLOW_REGISTER` | Enable user registration | `false` | `true` | | `ALLOW_OPENAPI` | Enable OpenAPI access | `true` | `false` | | `WEBSITE_ID` | Website identifier | - | `your-website-id` | | `DISABLE_AUTO_CLEAR` | Disable automatic data cleanup | `false` | `true` | | `DISABLE_ACCESS_LOGS` | Disable access logs | `false` | `true` | | `DB_DEBUG` | Enable database debugging | `false` | `true` | | `ALPHA_MODE` | Enable alpha features | `false` | `true` | | `ENABLE_FUNCTION_WORKER` | Enable function worker | `false` | `true` | | `WORKER_EXECUTION_REQUEST_PAYLOAD_DISABLED_WORKER_IDS` | Comma-separated worker IDs whose execution request payloads are not persisted. Payloads are still passed to worker code. | - | `worker-id-1,worker-id-2` | | `REGISTER_AUTO_JOIN_WORKSPACE_ID` | Auto-join workspace ID for new users | - | `workspace-id-123` | ## Cache Configuration | Variable | Description | Default | Example | | --- | --- | --- | --- | | `CACHE_MEMORY_ONLY` | Use memory-only caching | `false` | `true` | | `REDIS_URL` | Redis connection URL | - | `redis://localhost:6379` | ## Authentication | Variable | Description | Default | Example | | --- | --- | --- | --- | | `DISABLE_ACCOUNT` | Disable account-based authentication | `false` | `true` | | `AUTH_SECRET` | Authentication secret | MD5 of JWT secret | `your-auth-secret` | | `AUTH_RESTRICT_EMAIL` | Restrict registration to specific email domains | - | `@example.com` | | `AUTH_USE_SECURE_COOKIES` | Use secure cookies for authentication | `false` | `true` | ### Email Authentication and Email Invitation | Variable | Description | Default | Example | | --- | --- | --- | --- | | `EMAIL_SERVER` | SMTP server for email | - | `smtp://user:pass@smtp.example.com:587` | | `EMAIL_FROM` | Email sender address | - | `noreply@example.com` | ### GitHub Authentication | Variable | Description | Default | Example | | --- | --- | --- | --- | | `AUTH_GITHUB_ID` | GitHub OAuth client ID | - | `your-github-client-id` | | `AUTH_GITHUB_SECRET` | GitHub OAuth client secret | - | `your-github-client-secret` | ### Google Authentication | Variable | Description | Default | Example | | --- | --- | --- | --- | | `AUTH_GOOGLE_ID` | Google OAuth client ID | - | `your-google-client-id` | | `AUTH_GOOGLE_SECRET` | Google OAuth client secret | - | `your-google-client-secret` | ### Custom OAuth/OIDC Authentication | Variable | Description | Default | Example | | --- | --- | --- | --- | | `AUTH_CUSTOM_ID` | Custom OAuth/OIDC client ID | - | `your-custom-client-id` | | `AUTH_CUSTOM_SECRET` | Custom OAuth/OIDC client secret | - | `your-custom-client-secret` | | `AUTH_CUSTOM_NAME` | Custom provider name | `Custom` | `Enterprise SSO` | | `AUTH_CUSTOM_TYPE` | Authentication type | `oidc` | `oauth` | | `AUTH_CUSTOM_ISSUER` | OIDC issuer URL | - | `https://auth.example.com` | ## AI Features | Variable | Description | Default | Example | | --- | --- | --- | --- | | `SHARED_OPENAI_API_KEY` | OpenAI API key | - | `your-openai-api-key` | | `SHARED_OPENAI_BASE_URL` | Custom OpenAI API URL | - | `https://api.openai.com/v1` | | `SHARED_OPENAI_MODEL_NAME` | OpenAI model to use | `gpt-4o` | `gpt-3.5-turbo` | | `SHARED_OPENAI_TOKEN_CALC_CONCURRENCY` | Token calculation concurrency | `5` | `10` | | `DEBUG_AI_FEATURE` | Debug AI features | `false` | `true` | ## ClickHouse Configuration | Variable | Description | Default | Example | | --- | --- | --- | --- | | `CLICKHOUSE_URL` | ClickHouse database URL | - | `http://localhost:8123` | | `CLICKHOUSE_USER` | ClickHouse username | - | `default` | | `CLICKHOUSE_PASSWORD` | ClickHouse password | - | `your-password` | | `CLICKHOUSE_DATABASE` | ClickHouse database name | - | `tianji` | | `CLICKHOUSE_DEBUG` | Enable ClickHouse debugging | `false` | `true` | | `CLICKHOUSE_DISABLE_SYNC` | Disable ClickHouse synchronization | `false` | `true` | | `CLICKHOUSE_SYNC_BATCH_SIZE` | Synchronization batch size | `10000` | `5000` | | `CLICKHOUSE_ENABLE_FALLBACK` | Enable ClickHouse fallback | `true` | `false` | | `CLICKHOUSE_HEALTH_CHECK_INTERVAL` | Health check interval (ms) | `30000` | `60000` | | `CLICKHOUSE_MAX_CONSECUTIVE_FAILURES` | Maximum consecutive failures | `3` | `5` | | `CLICKHOUSE_RETRY_INTERVAL` | Retry interval (ms) | `5000` | `10000` | ## Billing System (LemonSqueezy) | Variable | Description | Default | Example | | --- | --- | --- | --- | | `ENABLE_BILLING` | Enable billing functionality | `false` | `true` | | `LEMON_SQUEEZY_SIGNATURE_SECRET` | LemonSqueezy webhook signature secret | - | `your-signature-secret` | | `LEMON_SQUEEZY_API_KEY` | LemonSqueezy API key | - | `your-api-key` | | `LEMON_SQUEEZY_STORE_ID` | LemonSqueezy store ID | - | `your-store-id` | | `LEMON_SQUEEZY_SUBSCRIPTION_FREE_ID` | Free tier subscription variant ID | - | `free-variant-id` | | `LEMON_SQUEEZY_SUBSCRIPTION_PRO_ID` | Pro tier subscription variant ID | - | `pro-variant-id` | | `LEMON_SQUEEZY_SUBSCRIPTION_TEAM_ID` | Team tier subscription variant ID | - | `team-variant-id` | ## Sandbox Configuration | Variable | Description | Default | Example | | --- | --- | --- | --- | | `USE_VM2` | Use VM2 for sandbox execution | `false` | `true` | | `SANDBOX_MEMORY_LIMIT` | Memory limit for sandbox (MB) | `16` | `32` | | `PUPPETEER_EXECUTABLE_PATH` | Custom path to Puppeteer executable | - | `/usr/bin/chromium` | ## Maps Integration | Variable | Description | Default | Example | | --- | --- | --- | --- | | `AMAP_TOKEN` | AMap (Gaode) API token | - | `your-amap-token` | | `MAPBOX_TOKEN` | Mapbox API token | - | `your-mapbox-token` | ## Telemetry | Variable | Description | Default | Example | | --- | --- | --- | --- | | `DISABLE_ANONYMOUS_TELEMETRY` | Disable anonymous telemetry | `false` | `true` | | `CUSTOM_TRACKER_SCRIPT_NAME` | Custom tracker script name | - | `custom-tracker.js` | ## Setting Environment Variables You can set these environment variables in different ways: 1. Set them directly in your deployment environment (Docker, Kubernetes, etc.) 2. For Docker deployments, you can use environment variables in your docker-compose.yml: ```yaml services: tianji: image: moonrailgun/tianji:latest environment: - PORT=3000 - ALLOW_REGISTER=true ``` ## Boolean Values For boolean environment variables, you can use either `"1"` or `"true"` to enable the feature, and either omit the variable or set it to any other value to disable it. ===== FILE: docs/install/kubernetes/helm.md ===== # Install in Helm Helm is a tool that streamlines installing and managing Kubernetes applications. Although helm you can easy and quickly enjoy tianji in kubernetes. ## Add repo At first, you should add msgbyte charts registry into helm repo list. ```bash helm repo add msgbyte https://msgbyte.github.io/charts ``` Now you can search tianji with `helm search` command. ```bash helm search repo tianji ``` ## Install Then, feel free to install with one command: ```bash helm install tianji msgbyte/tianji ``` Its will bring you a pg database and tianji. ===== FILE: docs/install/manual.md ===== # Install without docker Use docker to install `Tianji` is best way which you dont need consider about enviroment problem. But if your server not support dockerize, you can try to install by manual. ## Requirements You need: - [Node.js](https://nodejs.org/en/download/) 18.12+ / 20.4+ - [pnpm](https://pnpm.io/) 9.x(9.7.1 better) - [Git](https://git-scm.com/downloads) - [postgresql](https://www.postgresql.org/) - [pm2](https://pm2.keymetrics.io/) - For running Tianji in the background - [apprise](https://github.com/caronc/apprise) - optional, if you need it to notify ## Clone Code and Build ```bash git clone https://github.com/msgbyte/tianji.git cd tianji pnpm install pnpm build ``` ## Prepare Environment File Create a `.env` file in `src/server` ```ini DATABASE_URL="postgresql://user:pass@127.0.0.1:5432/tianji?schema=public" JWT_SECRET="replace-me-with-a-random-string" ``` Make sure your database url is correct. and dont remember create database before. For more environment can check this document [environment](./environment.md) > if you can, better to make sure your encoding is en_US.utf8, for example: `createdb -E UTF8 -l en_US.utf8 tianji` ## Run server ```bash npm install pm2 -g && pm2 install pm2-logrotate # Init db migrate cd src/server pnpm db:migrate:apply # Start Server pm2 start ./dist/src/server/main.js --name tianji ``` Default, `Tianji` will run on `http://localhost:12345` ## Update Code to new Version ```bash # Checkout new release/tags cd tianji git fetch --tags git checkout -q # Update dependencies pnpm install # Build project pnpm build # Run db migrations cd src/server pnpm db:migrate:apply # Restart Server pm2 restart tianji ``` # Frequently Asked Questions ## Install `isolated-vm` failed If you are using python 3.12, its will report error like this: ``` ModuleNotFoundError: No module named 'distutils' ``` Its because of python 3.12 remove `distutils` from builtin module. now we have good resolution about it. You can switch your python version from 3.12 to 3.9 can resolve it. ### How to resolve it in brew controlled python ```bash brew install python@3.9 rm /opt/homebrew/bin/python3 ln -sf /opt/homebrew/bin/python3 /opt/homebrew/bin/python3.9 ``` then you can check version with `python3 --version` ===== FILE: docs/install/other/install-in-aapanel.md ===== # Install in aaPanel ## Intro `aaPanel` is a powerful and easy-to-use web hosting control panel for linux server. one-click install LNMP/LAMP/OpenLiteSpeed developing environment and software. [Click here to deploy aaPanel in your server](https://www.aapanel.com/new/download.html?r=dk_tianji) > Applicable only for aaPanel version 7.0.11 and above. ## Update List and Search Navbar `docker` -> `one click install` -> `search tianji` ![](/img/docs/aapanel/1.png) > If you can't find it, please click `Update app list` button. ## Fill form to install ![](/img/docs/aapanel/2.png) you can fill the form quickly to install. and only just need to wait for a moment. then you can open your own Tianji in your browser. > Url: `http://:12345` > Default username: `admin` > Default password: `admin` ===== FILE: docs/install/telemetry.md ===== # Telemetry ## About Anonymous Telemetry To enhance the user experience, we have **enabled anonymous telemetry by default** in your self-hosted Tianji service. We understand that data privacy is important to you. Rest assured, this telemetry has the following characteristics: - **Completely Anonymous**: We do not collect any personally identifiable information or service content. - **Extremely Limited Scope**: It only includes basic usage data, helping us minimally understand Tianji's usage to continuously improve the product. - **Fully Transparent**: All telemetry data transmissions can be observed in your service’s network panel, ensuring complete transparency. Additionally, we fully respect your choice. If you prefer to **disable anonymous telemetry entirely**, simply set the following environment variable: ```bash DISABLE_ANONYMOUS_TELEMETRY=1 ``` For detailed configuration instructions, please refer to the Environment Variables Documentation. With this approach, we aim to improve Tianji while ensuring privacy and security. If you have any questions or feedback, please feel free to reach out to us! ===== FILE: docs/install/troubleshooting.md ===== # Troubleshooting This document collects common issues and their solutions that you may encounter when using Tianji. ## WebSocket Connection Issues ### Problem Description When using HTTPS services, other functions work normally, but WebSocket service cannot connect properly, which manifests as: - The connection status indicator in the bottom left corner shows gray - Server page list shows counts but no actual content ### Root Cause This issue is usually caused by improper WebSocket forwarding policies in reverse proxy software. In HTTPS environments, WebSocket connections require correct Cookie security policies. ### Solution You can resolve this issue by setting the following environment variable: ```bash AUTH_USE_SECURE_COOKIES=true ``` This setting forces the application to treat cookies passed by the browser as encrypted cookies, thereby resolving WebSocket connection issues. #### Configuration Methods **Docker Environment:** ```yaml # docker-compose.yml services: tianji: environment: - AUTH_USE_SECURE_COOKIES=true ``` **Direct Deployment:** ```bash export AUTH_USE_SECURE_COOKIES=true ``` ### Verification Steps After configuration, restart the service and check: 1. The bottom left connection status indicator should show green 2. Server pages should display real-time data normally 3. WebSocket connections should be established properly in browser developer tools --- *If you encounter other issues, feel free to submit an [Issue](https://github.com/msgbyte/tianji/issues) or contribute solutions to this documentation.* ===== FILE: docs/intro.md ===== # Introduction ## What is Tianji One sentence to summarize: **Tianji** = **Website Analytics** + **Uptime Monitor** + **Server Status** ### Why is it called Tianji? Tianji(天机, pronunciation Tiān Jī) in chinese which means **Heavenly Opportunity** or **Strategy** The characters 天 (Tiān) and 机 (Jī) can be translated as "heaven" and "machine" or "mechanism" respectively. When combined, it might refer to a strategic or opportunistic plan or opportunity that seems to be orchestrated by a higher power or a celestial force. ## Motivation During our observations of the website. We often need to use multiple applications together. For example, we need analysis tools such as `GA`/`umami` to check pv/uv and the number of visits to each page, we need an uptime monitor to check the network quality and connectivity of the server, and we need to use prometheus to obtain the status reported by the server to check the quality of the server. In addition, if we develop an application that allows open source deployment, we often need a telemetry system to help us collect the simplest information about other people's deployment situations. I think these tools should serve the same purpose, so is there an application that can integrate these common needs in a lightweight way? After all, most of the time we don't need very professional and in-depth functions. But in order to achieve comprehensive monitoring, I need to install so many services. It's good to specialize in one thing, if we are experts in related abilities we need such specialized tools. But for most users who only have lightweight needs, an **All-in-One** application will be more convenient and easier to use. ## Installation Install Tianji with Docker is very simple. just make sure you have been install docker and docker-compose plugin and then, run those command in anywhere: ```bash wget https://raw.githubusercontent.com/msgbyte/tianji/master/docker-compose.yml docker compose up -d ``` > Default account is **admin**/**admin**, please change password ASAP. ## Community Join our thriving community to connect with fellow users, share experiences, and stay updated on the latest features and developments. Collaborate, ask questions, and contribute to the growth of the Tianji community. - [GitHub](https://github.com/msgbyte/tianji) - [Discord](https://discord.gg/8Vv47wAEej) - [Stack Overflow](https://stackoverflow.com/questions/tagged/tianji) ===== FILE: docs/mcp/mcp.md ===== # Integration with MCP Add tianji MCP server to Cursor
[![Add to Kiro](https://kiro.dev/images/add-to-kiro.svg)](https://kiro.dev/launch/mcp/add?name=tianji&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22tianji-mcp-server%22%5D%2C%22env%22%3A%7B%22TIANJI_BASE_URL%22%3A%22https%3A%2F%2Ftianji.example.com%22%2C%22TIANJI_API_KEY%22%3A%22%3Cyour-api-key%3E%22%2C%22TIANJI_WORKSPACE_ID%22%3A%22%3Cyour-workspace-id%3E%22%7D%7D) ## Introduction Tianji MCP Server is a server based on Model Context Protocol (MCP) that serves as a bridge between AI assistants and the Tianji platform. It exposes Tianji platform's survey functionality to AI assistants through the MCP protocol. This server provides the following core features: - Query survey results - Get detailed survey information - Get all surveys in a workspace - Get website list ## Installation Methods ### NPX Installation You can use Tianji MCP Server by adding the following configuration to your AI assistant's configuration file: ```json { "mcpServers": { "tianji": { "type": "stdio", "command": "npx", "args": [ "-y", "tianji-mcp-server" ], "env": { "TIANJI_BASE_URL": "https://tianji.example.com", "TIANJI_API_KEY": "", "TIANJI_WORKSPACE_ID": "" } } } } ``` ### Environment Variable Configuration Before using Tianji MCP Server, you need to set the following environment variables: ```bash # Tianji platform API base URL TIANJI_BASE_URL=https://tianji.example.com # Tianji platform API key TIANJI_API_KEY=your_api_key_here # Tianji platform workspace ID TIANJI_WORKSPACE_ID=your_workspace_id_here ``` ### Getting an API Key You can obtain a Tianji platform API key by following these steps: 1. After logging into the Tianji platform, click on your **profile picture** in the top right corner 2. Select **Profile** from the dropdown menu 3. On the profile page, find the **API Keys** option 4. Click on create new key, and follow the prompts to complete the key creation ## Usage Instructions Tianji MCP Server provides a series of tools that can interact with AI assistants through the MCP protocol. Below are detailed descriptions of each tool: ### Query Survey Results Use the `tianji_get_survey_results` tool to query result data for a specific survey. **Parameters:** - `workspaceId`: Tianji workspace ID (defaults to the value configured in environment variables) - `surveyId`: Survey ID - `limit`: Limit on the number of records returned (default 20) - `cursor`: Pagination cursor (optional) - `startAt`: Start time, ISO format, example: 2023-10-01T00:00:00Z - `endAt`: End time, ISO format, example: 2023-10-31T23:59:59Z - `filter`: Filter conditions (optional) ### Get Survey Information Use the `tianji_get_survey_info` tool to get basic information about a specific survey. **Parameters:** - `workspaceId`: Tianji workspace ID (defaults to the value configured in environment variables) - `surveyId`: Survey ID ### Get All Survey List Use the `tianji_get_all_survey_list` tool to get a list of all surveys in the workspace. **Parameters:** - `workspaceId`: Tianji workspace ID (defaults to the value configured in environment variables) ### Get Website List Use the `tianji_get_website_list` tool to get a list of websites in the workspace. **Parameters:** - `workspaceId`: Tianji workspace ID (defaults to the value configured in environment variables) ## Usage Examples Below are examples of natural language interactions between users and AI assistants integrated with Tianji MCP Server: ### Example 1: Get All Survey List ``` User: Help me view all my survey questionnaire lists AI Assistant: I'll get all your survey questionnaire lists for you. [AI Assistant calls tianji_get_all_survey_list through MCP protocol to get data] Based on the data in your workspace, you currently have the following surveys: 1. User Experience Satisfaction Survey (created on 2023-09-15) 2. Product Feature Feedback Survey (created on 2023-10-01) 3. Website Usability Assessment (created on 2023-10-20) ``` ### Example 2: Get Specific Survey Details ``` User: Please tell me the details of the "User Experience Satisfaction Survey" AI Assistant: I'll query the details of that survey for you. [AI Assistant calls tianji_get_survey_info through MCP protocol to get data] Details of the "User Experience Satisfaction Survey": - Creation time: 2023-09-15 - Status: Active - Number of questions: 10 - Responses collected: 128 - Most recent response: 2023-10-25 ``` ### Example 3: Query Survey Results ``` User: Help me analyze the October results of the "Product Feature Feedback Survey" AI Assistant: I'll analyze the October results of the "Product Feature Feedback Survey". [AI Assistant calls tianji_get_survey_results through MCP protocol to get data for the specific time range] Analysis of the October "Product Feature Feedback Survey" results: - Total responses: 42 - Most popular feature: Automated Reports (85% positive feedback) - Feature most in need of improvement: Mobile adaptation (mentioned by 62%) - Average satisfaction rating: 4.2/5 - Main user suggestions: Add data export functionality, optimize page loading speed ``` ===== FILE: docs/monitor/custom-script.md ===== # Custom Script Compared with traditional monitoring services, **Tianji** supports custom scripts to support more customized scenarios. Essentially, you can understand it as a restricted, memory safe JavaScript runtime that accepts a number to display on your chart. The most common scenario is the time required for network requests to access an url. Of course, it can also be other things, such as your OpenAI balance, your github star number, and all the information that can be expressed in numbers. if this script return -1, its means this work is failed, and try to send notification to you, just like normal monitor. If you want to view the trend of a number's changes, opening the trending mode can help you better discover subtle changes in the number Here is some example: ## Examples ### get tailchat available service number from health endpoint ```js const res = await request({ url: 'https:///health' }) if(!res || !res.data || !res.data.services) { return -1 } return res.data.services.length ``` ### get github star count ```js const res = await request({ url: 'https://api.github.com/repos/msgbyte/tianji' }) return res.data.stargazers_count ?? -1 ``` replace `msgbyte/tianji` to your own repo name ### get docker pull count ```js const res = await request({ url: "https://hub.docker.com/v2/repositories/moonrailgun/tianji/" }); return res.data.pull_count; ``` replace `moonrailgun/tianji` to your own image name ### example for match text ```js const start = Date.now(); const res = await request({ url: "https://example.com/" }); const usage = Date.now() - start; const matched = /maintain/.test(String(res.data)); if(matched) { return -1; } return usage; ``` return `-1` means somthing wrong. in this case, its means in html body include `maintain` text. ### or more Very very welcome to submit your script in this page. Tianji is driven by open source community. ===== FILE: docs/monitor/push-monitor.md ===== # Push Monitor Push Monitor is a monitoring method where your application actively sends heartbeat signals to **Tianji** instead of Tianji checking your service. This is particularly useful for monitoring background tasks, cron jobs, or services behind firewalls that cannot be accessed directly. ## How It Works 1. **Tianji** provides you with a unique push endpoint URL 2. Your application sends HTTP POST requests to this endpoint at regular intervals 3. If no heartbeat is received within the configured timeout period, Tianji triggers an alert ## Configuration When creating a Push Monitor, you need to configure: - **Monitor Name**: A descriptive name for your monitor - **Timeout**: The maximum time (in seconds) to wait between heartbeats before considering the service down - **Recommended Interval**: How often your application should send heartbeats (typically the same as timeout) ## Push Endpoint Format ``` POST https://tianji.example.com/api/push/{pushToken} ``` ### Status Parameters - **Normal Status**: Send without parameters or with `?status=up` - **Down Status**: Send with `?status=down` to manually trigger an alert - **Custom Message**: Add `&msg=your-message` to include additional information - **Custom Value**: Add `&value=123` to track numeric values ## Examples ### Basic Heartbeat (cURL) ```bash # Send heartbeat every 60 seconds while true; do curl -X POST "https://tianji.example.com/api/push/" sleep 60 done ``` ### JavaScript/Node.js ```javascript // Send heartbeat every 60 seconds setInterval(async () => { try { await fetch('https://tianji.example.com/api/push/', { method: 'POST' }); console.log('Heartbeat sent successfully'); } catch (error) { console.error('Failed to send heartbeat:', error); } }, 60000); ``` ### Python ```python import requests import time def send_heartbeat(): try: response = requests.post('https://tianji.example.com/api/push/') print('Heartbeat sent successfully') except Exception as e: print(f'Failed to send heartbeat: {e}') # Send heartbeat every 60 seconds while True: send_heartbeat() time.sleep(60) ``` ## Use Cases ### 1. Cron Jobs Monitor the execution of scheduled tasks: ```bash #!/bin/bash # your-cron-job.sh # Your actual job logic here ./run-backup.sh # Send success signal if [ $? -eq 0 ]; then curl -X POST "https://tianji.example.com/api/push/?status=up&msg=backup-completed" else curl -X POST "https://tianji.example.com/api/push/?status=down&msg=backup-failed" fi ``` ### 2. Background Services Monitor long-running background processes: ```python import requests import time import threading class ServiceMonitor: def __init__(self, push_url): self.push_url = push_url self.running = True def start_heartbeat(self): def heartbeat_loop(): while self.running: try: requests.post(self.push_url) time.sleep(30) # Send every 30 seconds except Exception as e: print(f"Heartbeat failed: {e}") thread = threading.Thread(target=heartbeat_loop) thread.daemon = True thread.start() # Usage monitor = ServiceMonitor('https://tianji.example.com/api/push/') monitor.start_heartbeat() # Your main service logic here while True: # Do your work time.sleep(1) ``` ### 3. Database Sync Jobs Monitor data synchronization tasks: ```python import requests import schedule import time def sync_data(): try: # Your data sync logic here result = perform_data_sync() if result.success: requests.post( 'https://tianji.example.com/api/push/', params={'status': 'up', 'msg': f'synced-{result.records}-records'} ) else: requests.post( 'https://tianji.example.com/api/push/', params={'status': 'down', 'msg': 'sync-failed'} ) except Exception as e: requests.post( 'https://tianji.example.com/api/push/', params={'status': 'down', 'msg': f'error-{str(e)}'} ) # Schedule to run every hour schedule.every().hour.do(sync_data) while True: schedule.run_pending() time.sleep(1) ``` ## Best Practices 1. **Set Appropriate Timeouts**: Configure timeout values based on your application's needs. For frequent tasks, use shorter timeouts. For periodic jobs, use longer timeouts. 2. **Handle Network Failures**: Implement retry logic in your heartbeat code to handle temporary network issues. 3. **Use Meaningful Messages**: Include descriptive messages with your heartbeats to provide context when reviewing logs. 4. **Monitor Critical Paths**: Place heartbeat calls at critical points in your application flow, not just at the beginning. 5. **Exception Handling**: Send a "down" status when an exception occurs in your application. ## Troubleshooting ### Common Issues **No heartbeats received**: - Verify the push token is correct - Check network connectivity from your application to Tianji - Ensure your application is actually running the heartbeat code **Frequent false alarms**: - Increase the timeout value - Check if your application is experiencing performance issues - Review network stability between your app and Tianji **Missing heartbeats**: - Add error handling and logging to your heartbeat code - Consider implementing retry logic for failed requests - Monitor your application's resource usage ## Security Considerations - Keep your push tokens secure and don't expose them in public repositories - Use HTTPS endpoints to encrypt data in transit - Consider rotating push tokens periodically - Limit the frequency of heartbeats to avoid overwhelming your Tianji instance Push monitoring provides a reliable way to monitor services that traditional ping-based monitoring cannot reach, making it an essential tool for comprehensive infrastructure monitoring. ===== FILE: docs/notification/webhook.md ===== # Webhook If you need a more flexible notification method, you can try using custom webhooks to notify your messages. This way, you can integrate Tianji's notifications into any system. ### Example Result Tianji system will send a POST request with below example content. ```json { "notification": { "workspaceId": "xxxxxxxxxxx", "name": "New Notification", "type": "webhook", "payload": { "webhookUrl": "example.com" } }, "title": "New Notification Notification Testing", "content": "Tianji: Insight into everything\\nThis is Notification Testing from New Notification\\n[image]", "raw": [ { "type": "title", "level": 2, "content": "Tianji: Insight into everything" }, { "type": "text", "content": "This is Notification Testing from New Notification" }, { "type": "newline" }, { "type": "image", "url": "https://tianji.dev/img/social-card.png" } ], "time": "2024-06-19T15:41:09.390Z" } ``` ===== FILE: docs/server-status/kubernetes/reporter-daemonset.md ===== # Deploy Reporter as DaemonSet If you are running Tianji inside Kubernetes, you may want to collect each node's system metrics. The easiest way is to run `tianji-reporter` as a DaemonSet so it runs on every node. 1. Edit `docker/k8s/reporter-daemonset.yaml` and replace the `TIANJI_SERVER_URL` and `TIANJI_WORKSPACE_ID` values with your actual server address and workspace ID. 2. Apply the manifest: ```bash kubectl apply -f docker/k8s/reporter-daemonset.yaml ``` Every node will start a `tianji-reporter` container which reports system statistics to your Tianji instance. You can check the logs of a specific pod to ensure it works: ```bash kubectl logs -l app=tianji-reporter -f ``` Once the pods are running, the **Servers** page in Tianji will list your Kubernetes nodes just like regular machines. ===== FILE: docs/server-status/server-status-page.md ===== # Server Status Page You can create a server status page for user to show your server status to public which wanna make others know. ## Configure custom domain You can config your status page in your own domain, for example: `status.example.com` set it in page config, and create a `CNAME` record in your DNS dashboard. ``` CNAME status.example.com tianji.example.com ``` then you can visit custom `status.example.com` to your page. ### Troubleshooting If you will throw 500 error, it looks like your Reverse Proxy is not configured correctly. Please make sure your reverse proxy include your new status route. for example: ``` server { listen 80; server_name tianji.example.com status.example.com; listen 443 ssl; } ``` ===== FILE: docs/server-status/server-status-reporter.md ===== # Server Status Reporter you can report your server status easily with tianji reporter you can download from [https://github.com/msgbyte/tianji/releases](https://github.com/msgbyte/tianji/releases) ## Usage ``` Usage of tianji-reporter: --interval int Input the INTERVAL, seconed (default 5) --mode http The send mode of report data, you can select: `http` or `udp`, default is `http` (default "http") --name string The identification name for this machine --url string The http url of tianji, for example: https://tianji.dev --vnstat Use vnstat for traffic statistics, linux only --workspace string The workspace id for tianji, this should be a uuid ``` The **url** and **workspace** is required, its means you will report your service to which host and which workspace. Default a server node name will be same with hostname, so you can custom your name with `--name` which can help you identify server. ## Auto install script You can get your auto install script in `Tianji` -> `Servers` -> `Add` -> `Auto` tab its will auto download reporter and create linux service in your machine. so its need root permission. ### Uninstall if you wanna uninstall reporter service, you can use this command like: ```bash curl -o- https://tianji.example.com/serverStatus/xxxxxxxxxxxxxxxxxxx/install.sh?url=https://tianji.example.com | sudo bash -s uninstall ``` The main change is to append `-s uninstall` to your install command. ## Kubernetes If your servers are running in a Kubernetes cluster, you can deploy the reporter as a DaemonSet so each node reports metrics automatically. See [Deploy Reporter as DaemonSet](./kubernetes/reporter-daemonset.md) for details. ## Q&A ### How to check tianji reporter service log? If you install with auto install script, tianji will help you install a service which named `tianji-reporter` in your linux machine. You can use this command to check tianji reporter log: ```bash journalctl -fu tianji-reporter.service ``` ### Not found your machine in server tab even report show success Maybe your tianji is behind a reverse proxy for example `nginx`. Please make sure your reverse proxy add websocket support ## Why my machine is always offline? Please check your server datetime. ===== FILE: docs/skill/installation.md ===== # Installation The skill is just three files. Any modern AI agent (Cursor, Claude Code, Codex, Copilot CLI...) already knows where its own skills directory is — so installation can be as simple as pasting one prompt. ## One-Click Installation (via AI Agent) Paste the prompt below into your AI agent. It will download the files into the correct skills directory for its platform, then ask you for any missing configuration. ``` Please install the Tianji Data Query Skill into your skills directory: https://github.com/msgbyte/tianji/tree/master/skills/tianji-data-query After downloading, check whether these environment variables are set: - TIANJI_BASE_URL - TIANJI_API_KEY - TIANJI_WORKSPACE_ID If any are missing, ask me for the values. ``` That's it. The agent picks its own skills directory, fetches the files, and prompts you for credentials when needed. ## Manual Installation If you'd rather install it by hand, pick the target directory for your agent and run: ```bash DEST="$HOME/.cursor/skills/tianji-data-query" # or whatever your agent uses mkdir -p "$DEST/references" BASE="https://raw.githubusercontent.com/msgbyte/tianji/master/skills/tianji-data-query" curl -fSL "$BASE/SKILL.md" -o "$DEST/SKILL.md" curl -fSL "$BASE/references/api-endpoints.md" -o "$DEST/references/api-endpoints.md" curl -fSL "$BASE/references/openapi-readonly.json" -o "$DEST/references/openapi-readonly.json" ``` ### Skills directory by agent | Agent | Directory | |-------|-----------| | Cursor (personal) | `~/.cursor/skills/tianji-data-query/` | | Cursor (project) | `/.cursor/skills/tianji-data-query/` | | Claude Code | `~/.claude/skills/tianji-data-query/` | | Codex | `~/.codex/skills/tianji-data-query/` | | Codex (alt) | `~/.agents/skills/tianji-data-query/` | ## Required Environment Variables The skill expects three values. Export them in your shell rc, or set them in your agent's skill config: ```bash # Tianji instance base URL TIANJI_BASE_URL=https://tianji.example.com # API key for authentication TIANJI_API_KEY=your_api_key_here # Default workspace ID TIANJI_WORKSPACE_ID=your_workspace_id_here ``` ### Getting an API Key 1. Log in to your Tianji instance and click your **profile picture** in the top right corner. 2. Select **Profile** from the dropdown menu. 3. Find the **API Keys** section. 4. Click **Create new key** and follow the prompts. ## Next Steps After installation, head back to [Integration with Agent Skill](./skill.md) to see usage examples, the comparison with the MCP Server, and how the skill handles sensitive data. ===== FILE: docs/skill/skill.md ===== # Integration with Agent Skill ## Introduction The **Tianji Data Query Skill** is a lightweight, agent-agnostic skill bundle that lets AI agents (Cursor, Claude Code, Codex, Copilot CLI, etc.) query the Tianji platform directly through its read-only OpenAPI. It follows the [agentskills.io](https://agentskills.io/specification) specification — a single `SKILL.md` plus reference files. No long-running process, no extra runtime. :::tip Get started See the [Installation Guide](./installation.md) for one-click and manual setup. ::: **What it covers:** 69 GET endpoints across 14 service domains: - **Website** — traffic stats, pageviews, geo distribution, Lighthouse reports - **Monitor** — uptime status, recent check data, monitor events - **Survey** — survey responses, result stats, AI categories - **Telemetry** — custom event counts, telemetry pageviews, metrics - **Feed** — channels, event streams, feed states - **Application** — app store reviews, app info, event stats - **Billing / AI Gateway / Worker / Page / Workspace / Global / AuditLog** ## Skill vs MCP Server Tianji offers two ways to integrate with AI agents. Pick the one that fits your workflow: | | Agent Skill | [MCP Server](/docs/mcp) | |--|--|--| | **Form** | Plain documentation files (`SKILL.md` + references) | A long-running Node.js process | | **Runtime** | None — agent uses `curl` or built-in HTTP tools | `npx tianji-mcp-server` | | **Coverage** | 69 GET endpoints (read-only, full surface) | A curated subset of tools (read + some write) | | **Setup** | Drop files into the agent's skills directory | Add MCP config to the agent's config file | | **Best for** | Cursor / Claude Code / Codex / any agent following the agentskills.io spec | Agents with first-class MCP support | You can use both at the same time without conflict. ## Usage Examples Once installed, you can ask your AI agent natural-language questions and it will pick the right endpoint: ### Example 1: Website traffic overview ``` User: Show me the pageviews of my main website over the past 7 days. Agent: [Calls GET /open/workspace/{workspaceId}/website/all to find the site] [Then GET /open/workspace/{workspaceId}/website/{websiteId}/pageviews with startAt/endAt covering 7 days] Your main website received 12,438 pageviews over the past 7 days, with a peak of 2,103 on Tuesday. Top referrer: google.com (43%). ``` ### Example 2: Monitor health check ``` User: Are any of my monitors down right now? Agent: [Calls GET /open/workspace/{workspaceId}/monitor/all] [Then GET /open/workspace/{workspaceId}/monitor/{monitorId}/status for each monitor] 2 monitors are currently DOWN: - api.example.com (HTTP 503, last UP 12 min ago) - db-replica (TCP timeout, last UP 1h ago) The other 8 monitors are healthy. ``` ### Example 3: Survey result analysis ``` User: Summarise the responses to the "Q4 Customer Feedback" survey. Agent: [Calls GET /open/workspace/{workspaceId}/survey/all to resolve the ID] [Then GET /open/workspace/{workspaceId}/survey/{surveyId}/stats] [Then GET /open/workspace/{workspaceId}/survey/{surveyId}/result/list with a pagination cursor] "Q4 Customer Feedback" received 218 responses. Average satisfaction 4.3/5. Top theme: dashboard performance (mentioned 47 times). Most requested feature: dark mode (31 mentions). ``` ## Sensitive Data Handling Some endpoints may return platform-stored secrets (e.g. `modelApiKey`, `customModelBaseUrl` in AI Gateway responses) or PII (workspace members, audit logs, billing). The skill instructs agents to: - **Never display** `apiKey`, `modelApiKey`, `secret`, `token`, `password`, or `credential` fields. - **Redact or omit** these fields when summarising responses. - For workspace members / audit logs, only surface non-sensitive metadata (names, roles, timestamps) unless the user explicitly requests full detail. The bundled `openapi-readonly.json` also pre-redacts these fields at the schema level, so agents cannot accidentally rely on their structure. ## Source The skill source lives in the Tianji repository under [`skills/tianji-data-query/`](https://github.com/msgbyte/tianji/tree/master/skills/tianji-data-query). Pull requests welcome. ===== FILE: docs/special-thanks.md ===== # Special thanks ## Open source project Tianji is very inspired by those project, thanks for your contributions to the open source community. I also loves those project. Salute to these excellent projects! - [umami](https://github.com/umami-software/umami) - [uptime kuma](https://github.com/louislam/uptime-kuma) ===== FILE: docs/telemetry/intro.md ===== # Introduce ## Background As content creators, we often publish our articles on various third-party platforms. However, for those of us who are serious about our content, publishing is just the beginning. We need to continuously monitor our articles' readership over time. Unfortunately, our data collection capabilities are limited to what each platform offers, which heavily depends on the platform's own capabilities. Moreover, when we distribute the same content across different platforms, the readership and visitation data are completely isolated. As a developer, I create many software applications. However, once I release these applications, I often lose control over them. For example, after releasing a command-line program, I have no way of knowing how users are interacting with it or even how many users are using my application. Similarly, when developing an open-source application, in the past, I could only gauge interest through GitHub stars, leaving me in the dark about actual usage. Therefore, we need a simple solution that collects minimal information, respecting personal privacy and other restrictions. This solution is telemetry. ## Telemetry In the field of computing, telemetry is a common technology that involves the minimal and anonymous reporting of information to accommodate privacy concerns while still meeting the basic analytical needs of content creators. For example, React's Next.js framework collects information using telemetry: [API Reference: Next.js CLI | Next.js (nextjs.org)](https://nextjs.org/docs/app/api-reference/next-cli#telemetry) Alternatively, by embedding a 1px-sized blank transparent pixel image in an article, it's possible to collect visitor data on websites over which we have no control. Modern browsers and most websites block the insertion of custom scripts due to potential security risks. However, an image appears much more harmless by comparison. Almost all websites allow the loading of third-party images, making telemetry feasible. ## What Information Can We Collect Through an Image? Surprisingly, receiving a single image request allows us to collect more information than one might expect. By analyzing network requests, we can obtain the user's IP address, visit time, referrer, and device type. This enables us to analyze traffic patterns, such as peak readership times and trends, demographic distribution, and traffic granularity across different platforms. This information is particularly valuable for marketing and promotional activities. ![](/img/telemetry/1.png) ## How Can We Implement Telemetry? Telemetry is a straightforward technology that essentially requires an endpoint to receive internet requests. Due to its simplicity, there are few dedicated tools for this purpose. Many may not consider analytics important, or they might be deterred by the initial barriers. However, the demand for such functionality is clear. Developing a telemetry solution is simple. You just need to create a project, set up a route, collect information from the request body, and return a blank image. Here's an example using Node.js: ```jsx router.get( '/telemetry.gif', async (req, res) => { const ip = req.ip; const referer = req.header['referer']; const userAgent = req.headers['user-agent']; // Store it in your database const blankGifBuffer = Buffer.from( 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64' ); res.header('Content-Type', 'image/gif').send(blankGifBuffer); } ); ``` If you prefer not to develop your own solution, I recommend Tianji. As an open-source project offering **Website Analytics**, **Uptime Monitoring**, and **Server Status**, Tianji has recently introduced a telemetry feature to assist content creators in reporting telemetry, thereby facilitating better data collection. Most importantly, being open-source means you have control over your data and can aggregate traffic from multiple platforms in one place, avoiding the fragmentation of viewing the same information in different locations. ![](/img/telemetry/2.png) GitHub: [https://github.com/msgbyte/tianji](https://github.com/msgbyte/tianji) Official Website: [https://tianji.dev/](https://tianji.dev/) ===== FILE: docs/telemetry/params.md ===== # Parameters Here is some example for how to use and config with telemetry image. All is optional. its will improve your usage in different place. | name | description | | -------- | --------- | | url | default will get referrer url which auto generate by browser, but in some website will dont allow carry this header, so you have to bring it by your self. if Tianji cannot get url in any where, system will ignore and not record this visit | | name | define telemetry event name, its can be use for distinguish different event but in same telemetry record. | | title | **[Badge ONLY]**, define badge title | | start | **[Badge ONLY]**, define badge start count number | | fullNum | **[Badge ONLY]**, define badge will show full number, default is abbreviated digits(for example: `12345` and `12.3k`) | ## How to use Its easy to carry params on url for example: ``` https://tianji.example.com/telemetry///badge.svg?name=myEvent&url=https://google.com&title=My+Counter&start=100000&fullNum=true ``` If you dont familiar with this, you can check wiki page about this: [https://en.wikipedia.org/wiki/Query_string](https://en.wikipedia.org/wiki/Query_string) ===== FILE: docs/website/framework/docusaurus.md ===== # Use in Docusaurus In `docusaurus.config.js`: ```js /** @type {import('@docusaurus/types').Config} */ const config = { // ... scripts: [ { src: 'https:///tracker.js', async: true, defer: true, 'data-website-id': '', }, ], }; module.exports = config; ``` ===== FILE: docs/website/framework/install-in-traefik-with-plugin.md ===== # Install in Traefik with plugin Tianji provides a Traefik plugin that allows you to easily integrate Tianji website analytics functionality into your Traefik proxy. ## Plugin Overview [traefik-tianji-plugin](https://github.com/msgbyte/traefik-tianji-plugin) is a Traefik middleware plugin specifically developed for Tianji that can automatically inject the Tianji tracking script into your website without modifying your website code to start collecting visitor data. ## Installing the Plugin ### 1. Add Plugin in Static Configuration First, you need to add the plugin reference in Traefik's static configuration. The plugin version number references the git tag. #### YAML Configuration Add the following to your `traefik.yml` or static configuration file: ```yaml experimental: plugins: traefik-tianji-plugin: moduleName: "github.com/msgbyte/traefik-tianji-plugin" version: "v0.2.1" ``` #### TOML Configuration ```toml [experimental.plugins.traefik-tianji-plugin] moduleName = "github.com/msgbyte/traefik-tianji-plugin" version = "v0.2.1" ``` #### Command Line ```bash --experimental.plugins.traefik-tianji-plugin.modulename=github.com/msgbyte/traefik-tianji-plugin --experimental.plugins.traefik-tianji-plugin.version=v0.2.1 ``` ### 2. Configure Middleware After installing the plugin, you need to configure the middleware in the dynamic configuration. #### YAML Dynamic Configuration In your `config.yml` or dynamic configuration file: ```yaml http: middlewares: my-tianji-middleware: plugin: traefik-tianji-plugin: tianjiHost: "https://tianji.your-domain.com" websiteId: "your-website-id" ``` #### TOML Dynamic Configuration ```toml [http.middlewares.my-tianji-middleware.plugin.traefik-tianji-plugin] tianjiHost = "https://tianji.your-domain.com" websiteId = "your-website-id" ``` #### Docker Compose Labels ```yaml version: '3.7' services: my-app: image: nginx:latest labels: - "traefik.enable=true" - "traefik.http.routers.my-app.rule=Host(`my-app.local`)" - "traefik.http.routers.my-app.middlewares=my-tianji-middleware" - "traefik.http.middlewares.my-tianji-middleware.plugin.traefik-tianji-plugin.tianjiHost=https://tianji.your-domain.com" - "traefik.http.middlewares.my-tianji-middleware.plugin.traefik-tianji-plugin.websiteId=your-website-id" ``` ## Configuration Parameters ### Required Parameters - **tianjiHost**: The complete URL of your Tianji server - Example: `https://tianji.your-domain.com` - If using the official hosted service: `https://app.tianji.dev` - **websiteId**: The website ID created in Tianji - Can be found in the website settings of your Tianji admin panel ### Optional Parameters The plugin also supports other configuration parameters to customize behavior. For specific parameters, please refer to the [GitHub repository documentation](https://github.com/msgbyte/traefik-tianji-plugin). ## Using the Middleware After configuration, you need to use this middleware in your router: ### YAML Configuration ```yaml http: routers: my-app: rule: "Host(`my-app.local`)" middlewares: - "my-tianji-middleware" service: "my-app-service" ``` ### Docker Compose Labels ```yaml labels: - "traefik.http.routers.my-app.middlewares=my-tianji-middleware" ``` ## How It Works 1. When requests pass through the Traefik proxy, the plugin checks the response content 2. If the response is HTML content, the plugin automatically injects the Tianji tracking script 3. The script starts collecting visitor data and sends it to the Tianji server when the page loads ## Important Notes - Ensure that the Tianji server address is accessible from client browsers - The website ID must be valid, otherwise data cannot be collected properly - The plugin only takes effect when the response content type is HTML - It's recommended to use the latest version of the plugin for optimal performance and features ## Reference - [Plugin Source Code](https://github.com/msgbyte/traefik-tianji-plugin) - [Traefik Plugin Documentation](https://doc.traefik.io/traefik/plugins/) ===== FILE: docs/website/track-script.md ===== # Tracker Script ## Installation To track website events, you just need inject a simple script(< 2 KB) into your website. the script look like below: ```html ``` you can get this script code from your **Tianji** website list ## Script Attributes The tracker script supports the following `data-*` attributes on the ` ``` ### Disable Tracking via localStorage You can also disable tracking at runtime by setting a localStorage flag: ```javascript localStorage.setItem('tianji.disabled', '1'); ``` ## Report Event **Tianji** provide a simple way to report user click event, its easy to help you track which action user like and often to use. This is a very common method in website analysis. You can use it quickly get it by using **Tianji**. ### Basic Usage After you inject script code into your website, you just need add a `data-tianji-event` in dom attribute. for example: ```html ``` Now, when user click this button, your dashboard will receive new event ### Attach Event Data You can attach additional data to your events by using `data-tianji-event-{key}` attributes. Any attribute matching this pattern will be collected and sent with the event. ```html ``` This will send an event named `purchase` with the following data: ```json { "product": "Premium Plan", "price": "99", "currency": "USD" } ``` ### Track Link Clicks When using `data-tianji-event` on anchor (``) tags, **Tianji** handles them specially to ensure the event is tracked before navigation: ```html Check Pricing ``` For internal links (not opening in new tab), the tracker will: 1. Prevent the default navigation 2. Send the tracking event 3. Navigate to the target URL after tracking completes For external links or links with `target="_blank"`, the event is tracked without blocking the navigation. ### JavaScript API After the tracker script is loaded, you can also track events programmatically using the `window.tianji` object. #### Track Custom Events ```javascript // Simple event tracking window.tianji.track('button-clicked'); // Event with custom data window.tianji.track('purchase', { product: 'Premium Plan', price: 99, currency: 'USD' }); // Track with custom payload object window.tianji.track({ website: 'your-website-id', name: 'custom-event', data: { key: 'value' } }); // Track using a function (receives current page info) window.tianji.track((payload) => ({ ...payload, name: 'dynamic-event', data: { timestamp: Date.now() } })); ``` #### Identify Users You can attach user information to tracking sessions: ```javascript window.tianji.identify({ userId: 'user-123', email: 'user@example.com', plan: 'premium' }); ``` This information will be associated with subsequent events from this user. ## Modify default script name > This feature available on v1.7.4+ You can use environment `CUSTOM_TRACKER_SCRIPT_NAME` when you start it for example: ``` CUSTOM_TRACKER_SCRIPT_NAME="my-tracker.js" ``` then you can visit your tracker script with `"https:///my-tracker.js"` This is to help you avoid some ad-blockers. You do not need the `.js` suffix. It can be any path you choose, even you can use as `CUSTOM_TRACKER_SCRIPT_NAME="this/is/very/long/path"` ## Tracking Specified Domains Only Generally the tracker will report all events wherever your site is running. But sometimes we need to ignore events like `localhost`. Tianji provides an attribute of the tracker script to do that. You can add `data-domains` into your script. The value should be your root domains to track. Use `,` to separate multiple domains. ```html ``` Then you can just see the events from these domains. ===== FILE: docs/worker/agent-reference.md ===== # Worker Agent Reference This page is a machine-oriented reference for coding agents that generate Tianji Worker source code. For the user-facing setup and operation guide, read [Worker Getting Started](./getting-started.md). Read this page completely before generating code. Do not infer Node.js, browser, or Cloudflare Worker APIs that are not declared here. ## Give this page to an Agent If you are a Tianji user, copy this page's URL and send it to an Agent that can access web pages: ```text Read this reference completely before writing code: https://tianji.dev/docs/worker/agent-reference ``` If the Agent cannot access the URL, copy and paste the entire page into the conversation instead. Then describe the Worker's purpose, input, environment-variable names, external APIs, temporary data, and expected output. Never include real Secret values. The [Worker Getting Started guide](./getting-started.md#ask-an-agent-to-write-your-worker) provides a ready-to-copy request template and the complete user workflow. ## Choose the target There are two valid generation targets: | Target | Output | | --- | --- | | Tianji dashboard | One self-contained source file using plain JavaScript syntax | | Tianji CLI project | `src/index.ts`; TypeScript and bundled npm dependencies are allowed | Dashboard code should use plain JavaScript unless the Tianji instance explicitly enables `ENABLE_FUNCTION_WORKER_TYPESCRIPT_SUPPORT`. A CLI project uses Vite to compile TypeScript before deployment. Generate a Module Worker. Do not generate the legacy global `function fetch(...) {}` format. ```js }, }; ``` ## Runtime types Use this contract for reasoning and TypeScript generation: ```ts type JsonValue = | null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue }; interface WorkerRequestContext { method: string; url: string; headers: Record; } interface WorkerContext { type: 'http' | 'cron' | 'manual' | 'test'; env: Record; request?: WorkerRequestContext; } interface TianjiWorker { fetch( payload: Record, context: WorkerContext ): unknown | Promise; } interface RequestConfig { url: string; method?: string; headers?: Record; params?: Record; data?: unknown; timeout?: number; } interface RequestResult { status: number; headers: Record; data: unknown; } declare function request(config: RequestConfig): Promise; interface KVScope { get(key: string): Promise; set(key: string, value: JsonValue, ttlMilliseconds?: number): Promise; delete(key: string): Promise; } declare const kv: KVScope & { workspace: KVScope }; ``` The types above document runtime globals. Do not include the `declare` statements in dashboard JavaScript output. A CLI TypeScript project may include declarations when type checking needs them. ## Input contract - `payload` is always an object. - For HTTP execution, Tianji merges query parameters and the request body. Body fields overwrite query fields with the same name. - A cron execution without input receives `{}`. - Treat every payload field as untrusted. Check its type, required status, format, length, and allowed range before using it. - Do not assume query parameters have already been converted to numbers or booleans. - Reject or return a clear error for invalid input rather than silently constructing unsafe requests. `context.request` exists only when `context.type === 'http'`. Guard access to it: ```js if (context.type !== 'http') { return { error: 'HTTP trigger required' }; } const method = context.request.method; ``` ## Output contract - Return JSON-compatible objects and arrays, a string, or a number. - For an HTTP trigger, Tianji serializes object results as JSON. - Do not return secrets or internal error objects. - Normalize upstream API responses into a stable result owned by the worker. - Use `console.log`, `console.warn`, and `console.error` for execution logs. ## Sandbox contract The Worker sandbox provides standard JavaScript built-ins, captured `console` methods, `request`, and `kv`. The sandbox is not Node.js and not a browser. Do not generate code that uses: - `process`, `Buffer`, filesystem, child processes, sockets, or other Node.js APIs. - DOM APIs, browser storage, or browser `fetch`. - Cloudflare Worker bindings or event handlers. - Undocumented globals. For outbound HTTP, use `request` with an Axios-style configuration: ```js const response = await request({ method: 'POST', url: context.env.API_URL, headers: { Authorization: `Bearer ${context.env.API_TOKEN}` }, data: { value: payload.value }, timeout: 10_000, }); ``` Check required URLs and credentials before calling `request`. Handle remote failures and avoid placing credentials in query strings. ## Environment and Secret contract Environment values are available only through `context.env`: ```js const apiUrl = context.env.API_URL; const apiToken = context.env.API_TOKEN; ``` Variable names match `^[A-Za-z_][A-Za-z0-9_]*$` and are unique within a worker. Text and Secret values are both strings at runtime. Secret values are replaced with `[secret]` in captured log strings, arrays, nested object values, and object keys. Empty Secret values are not redacted. Log redaction does not prevent exfiltration. Worker code can still return a secret or send it to an external service. Use a Secret only for its intended outbound request, and never intentionally log or return it. ## KV contract `kv` stores data private to the current worker. `kv.workspace` shares data across workers in the same workspace and must be used only when cross-worker sharing is intentional. ```js const cached = await kv.get('result'); await kv.set('result', { ok: true }, 60_000); const deleted = await kv.delete('result'); ``` KV limits apply to each execution: | Limit | Value | | --- | ---: | | Default TTL | 10 minutes | | Minimum TTL | 1 second | | Maximum TTL | 24 hours | | Key length | 1-256 characters | | Value size | 256 KiB of JSON | | Calls across both scopes | 50 | | Total data written across both scopes | 1 MiB | | Timeout for one KV operation | 2 seconds | Values must be JSON-compatible. Functions, symbols, `undefined`, `BigInt`, non-finite numbers, class instances, custom `toJSON` methods, symbol properties, accessors, non-enumerable properties, and cyclic data are invalid. KV failures expose one stable error code: ```text WORKER_KV_INVALID_KEY WORKER_KV_INVALID_VALUE WORKER_KV_INVALID_TTL WORKER_KV_LIMIT_EXCEEDED WORKER_KV_TIMEOUT WORKER_KV_UNAVAILABLE ``` KV is cache storage, not durable storage. Always handle `undefined` from `get`. Test executions use isolated KV namespaces and cannot read or modify the deployed worker's KV data. ## Trigger contract Generate code that supports only the requested triggers and handles unexpected triggers explicitly. - HTTP: `context.type === 'http'`; `context.request` is present. - Cron: `context.type === 'cron'`; payload is normally `{}`. - Manual: `context.type === 'manual'`. - Editor test: `context.type === 'test'`; test KV is isolated. Cron expressions have a minimum interval of one minute. Execution uses the workspace timezone or UTC if none is configured. The public HTTP route is: ```text /api/worker// ``` Only active, public workers can execute through this route. It does not require a Tianji API key. Treat the endpoint as public and the payload as attacker-controlled. Private workers are rejected by the public route and can be tested through authenticated Tianji operations. ## Generation checklist Before returning generated Worker code, verify all of the following: 1. The output uses a default-exported object with exactly one `async fetch(payload, context)` method. 2. It matches the requested dashboard JavaScript or CLI TypeScript target. 3. Every payload field is validated before use. 4. Configuration and credentials come from named `context.env` keys. 5. No real credential or placeholder that resembles a real credential is embedded in source. 6. Outbound HTTP uses `request`, validates required configuration, sets a timeout, and handles failures. 7. Temporary private data uses `kv`; `kv.workspace` appears only when cross-worker sharing was explicitly requested. 8. KV keys, values, TTLs, and operation counts stay within the documented limits. 9. The result is JSON-compatible and does not expose secrets or unnecessary upstream response data. 10. The code does not use Node.js, browser, Cloudflare, or undocumented runtime APIs. 11. At least one valid and one invalid test payload are described separately from the source code. 12. The agent does not deploy, overwrite remote code, change visibility, or add Secret values without explicit user authorization. ## Prompt template ```text Read the Tianji Worker Agent Reference completely. Generate Worker source code using only its documented runtime contract. Target: Triggers: Input: Environment: External APIs: KV: Output: Failures: Return the source code first, followed by valid and invalid test payloads. Do not deploy, overwrite remote code, or include real Secret values. ``` ## Agent-readable documentation Tianji publishes a documentation index at [`/llms.txt`](https://tianji.dev/llms.txt) and the complete documentation corpus at [`/llms-full.txt`](https://tianji.dev/llms-full.txt). This reference and the user-facing guide are included in the full corpus. ===== FILE: docs/worker/getting-started.md ===== # Worker Getting Started Tianji Worker lets you run small functions without maintaining a separate service. A worker can receive HTTP requests, run on a schedule, call external APIs, read environment variables, and store short-lived data. This guide walks you through creating and publishing your first worker in the Tianji dashboard. If you want an AI coding agent to generate the source code, see the [Worker Agent Reference](./agent-reference.md). ## Before you start Worker is disabled by default on self-hosted Tianji instances. Add this environment variable to the Tianji server and restart it: ```bash ENABLE_FUNCTION_WORKER=true ``` After Worker is enabled, open **Worker** from the Tianji sidebar. ## Ask an Agent to write your Worker You do not need to write the Worker code by hand. Give the [Worker Agent Reference](./agent-reference.md) to your coding agent so it can learn Tianji's runtime and generate compatible code. ### If your Agent can open web pages Copy the following prompt, replace the placeholders, and send it to your Agent: ```text First, read the Tianji Worker Agent Reference completely: https://tianji.dev/docs/worker/agent-reference Then create a Tianji Worker for me. Target: dashboard JavaScript Purpose: Input: Environment variables: External APIs: Temporary data: Output: Return the Worker source code and example test payloads. Do not deploy it. ``` For example, you can replace `Purpose` with “receive a GitHub webhook and forward a short message to my team webhook,” then list the required payload fields and environment-variable names. ### If your Agent cannot open web pages 1. Open the [Worker Agent Reference](./agent-reference.md). 2. Copy the entire page. 3. Paste it into your conversation with the Agent. 4. Add your Worker requirements using the prompt above. Do not paste real API keys or passwords into the conversation. Give the Agent environment-variable names such as `API_TOKEN`; add the actual values later as **Secret** variables in Tianji. After the Agent returns code: 1. Review the environment-variable names it requires. 2. Paste the code into the Tianji Worker editor. 3. Add Text and Secret variables in Tianji. 4. Run the provided valid and invalid test payloads. 5. Save and activate the worker only after the results are correct. ## Create your first worker 1. Open **Worker** in the dashboard. 2. Click **Add Worker**. 3. Enter a name and an optional description. 4. Paste the following code into the editor. ```js return { message: `Hello, ${name}!`, trigger: context.type, timestamp: new Date().toISOString(), }; }, }; ``` Every worker exports a `fetch` method. Tianji calls it with: - `payload`: input values sent to the worker. - `context`: information about the trigger, request, and configured environment variables. The value returned by `fetch` becomes the worker result. ## Test before saving Use the editor's **Test** action with this payload: ```json { "name": "Tianji" } ``` The result should look similar to: ```json { "message": "Hello, Tianji!", "trigger": "test", "timestamp": "2026-08-16T00:00:00.000Z" } ``` Use `console.log`, `console.warn`, or `console.error` while debugging. Tianji displays these messages in the execution details. ## Call a worker over HTTP Save the worker, keep it **Active**, and make sure it is **Public**. Its endpoint is: ```text https:///api/worker// ``` You can pass values as query parameters: ```bash curl 'https:///api/worker//?name=Tianji' ``` Or send a JSON request body: ```bash curl --request POST \ --header 'Content-Type: application/json' \ --data '{"name":"Tianji"}' \ 'https:///api/worker//' ``` The endpoint accepts any HTTP method. Query parameters and request-body fields are merged into `payload`; body fields take precedence when the same key appears in both places. A public worker can be called by anyone who has its URL and does not require a Tianji API key. Validate all input and never return credentials. Private workers cannot be called through the public endpoint, but you can test them from the authenticated dashboard. ## Handle different triggers `context.type` tells you why the worker is running: | Type | When it is used | | --- | --- | | `http` | The public HTTP endpoint called the worker | | `cron` | A configured schedule triggered the worker | | `manual` | A user ran the saved worker from Tianji | | `test` | A user tested code in the editor | For HTTP calls, `context.request` contains the request method, URL, and headers: ```js } return { method: context.request.method, payload, }; }, }; ``` ## Add environment variables and secrets Open the worker's environment-variable section and add: - **Text** variables for non-sensitive settings such as a base URL. - **Secret** variables for API keys, tokens, and passwords. Read both types from `context.env`: ```js const apiBaseUrl = context.env.API_BASE_URL; const apiToken = context.env.API_TOKEN; ``` Variable names can contain letters, numbers, and underscores, but must start with a letter or underscore. Names must be unique within a worker. Tianji replaces configured Secret values with `[secret]` in captured logs. A worker can still send or return a secret, so do not log or return credentials intentionally. ## Call an external API Use the global `request` function for outbound HTTP requests: ```js const apiToken = context.env.API_TOKEN; if (!apiBaseUrl || !apiToken) { throw new Error('API configuration is missing'); } const response = await request({ method: 'GET', url: `${apiBaseUrl}/items`, params: { query: String(payload.query ?? '') }, headers: { Authorization: `Bearer ${apiToken}` }, timeout: 10_000, }); return { status: response.status, data: response.data, }; }, }; ``` `request` accepts an Axios-style configuration and returns `status`, `headers`, and `data`. The browser `fetch` API is not available in the Worker sandbox. ## Store short-lived data Use the global `kv` object when a worker needs temporary state: ```js const current = (await kv.get(key)) ?? 0; const next = Number(current) + 1; await kv.set(key, next, 60 * 60 * 1000); return { count: next }; }, }; ``` The available operations are: - `kv.get(key)` - `kv.set(key, value, ttlMilliseconds?)` - `kv.delete(key)` Data stored with `kv` is private to the current worker. Use `kv.workspace` only when multiple workers in the same workspace intentionally share temporary data. KV is a cache rather than a durable database. Entries expire and may be evicted, so your worker must handle missing values. The default TTL is 10 minutes and the maximum TTL is 24 hours. See the [Worker Agent Reference](./agent-reference.md#kv-contract) for the complete limits and error codes. ## Run on a schedule Enable **Cron Schedule** in the worker editor and enter a cron expression. The minimum interval is one minute. Schedules use the workspace timezone, or UTC when no workspace timezone is configured. Common expressions: | Expression | Meaning | | --- | --- | | `*/5 * * * *` | Every 5 minutes | | `0 * * * *` | At the start of every hour | | `0 9 * * 1-5` | At 09:00, Monday through Friday | You can restrict a worker to scheduled runs: ```js } const webhookUrl = context.env.WEBHOOK_URL; if (!webhookUrl) { throw new Error('WEBHOOK_URL is not configured'); } const response = await request({ method: 'POST', url: webhookUrl, data: { executedAt: new Date().toISOString() }, timeout: 10_000, }); return { delivered: response.status >= 200 && response.status < 300 }; }, }; ``` ## Develop with the CLI The dashboard is the quickest way to create a worker. Use the CLI when you want a local TypeScript project, third-party dependencies, version control, and a repeatable build. Build and link the current CLI from the Tianji repository: ```bash git clone https://github.com/msgbyte/tianji.git cd tianji pnpm install pnpm --dir packages/cli build cd packages/cli pnpm link --global ``` Then create and deploy a project from your projects directory: ```bash tianji login tianji worker init my-worker cd my-worker npm install npm run build tianji worker deploy ``` `tianji login` asks for your Tianji server URL, workspace ID, and API key. The deploy command builds `src/index.ts`, uploads `dist/index.js`, and records the remote worker ID in `.tianjirc`. To download an existing worker into an empty directory: ```bash tianji worker pull npm install ``` Pulling into an existing project does not replace `src/index.ts` unless you explicitly pass `--overwrite`. Configure environment variables, secrets, visibility, and cron in the dashboard. Do not put credentials in source code or `.tianjirc`. ## Troubleshooting ### `Function worker is not enabled` Set `ENABLE_FUNCTION_WORKER=true` on the Tianji server and restart it. ### `fetch is not defined` Make sure the worker has a default export containing a `fetch` method, as shown in the first example. ### `Worker is private` Private workers cannot run through the public HTTP endpoint. Test the worker from the authenticated dashboard, or make it public only when unauthenticated link access is intended. ### TypeScript causes a syntax error in the dashboard Use plain JavaScript in the dashboard. TypeScript syntax requires the server option `ENABLE_FUNCTION_WORKER_TYPESCRIPT_SUPPORT`. CLI projects compile TypeScript before uploading it. ### A KV value disappears KV data expires and may be evicted. Use an external durable database when the data must not be lost. ## Generate a worker with an Agent Follow [Ask an Agent to write your Worker](#ask-an-agent-to-write-your-worker) to give the reference to your Agent. The [Worker Agent Reference](./agent-reference.md) provides the precise runtime contract, type definitions, sandbox restrictions, KV limits, generation checklist, and a more detailed prompt template. ===== FILE: src/pages/markdown-page.md ===== # Markdown page example You don't need React to write simple standalone pages. ===== FILE: src/pages/private-policy.md ===== # Tianji Privacy Policy > Last updated August 26, 2024 This privacy notice for `tianji.msgbye.com` ("we," "us," or "our") explains how and why we collect, store, use, and/or share ("process") your personal information when you use our services ("Services"). This includes actions like: - Visiting our website at `msgbye.com`, or any other website of ours that links to this privacy notice. - Interacting with us in other related ways, such as during sales, marketing activities, or events. If you have any questions or concerns about our privacy practices, reading this privacy notice will help you understand your rights and choices. If you disagree with our policies and practices, please refrain from using our services. For any further questions or concerns, contact us at `moonrailgun@gmail.com`. ## Personal Data We Collect ### Information You Provide We collect the personal data you voluntarily provide when you register for our Services, express interest in obtaining information about us or our products and Services, participate in activities on the Services, or contact us directly. The personal data we collect may include, but is not limited to: - Email addresses - Usernames - Passwords We encrypt sensitive information, such as passwords, to protect your data even if our database is compromised. ## Personal Data We Obtain From Third Parties ### Single Sign-On If you sign up for our website `tianji.msgbye.com` through Google, you authorize us to collect authentication information, including your username, encrypted access credentials, and any other personal data available through your third-party application account. ## How We Use Your Personal Data We use your personal data to: - **Provide, Improve, and Develop the Platform**: We process your data primarily to ensure the proper functioning of our website. We may also use your data to improve our website and offer you the best possible experience. Specifically, we: - Operate and maintain your `tianji.msgbye.com` account, allowing you to access and use the Platform. Your email address and password are used to identify you when you log in. - **Maintain a Safe and Secure Environment**: We process your data to enhance the security of our Platform, ensuring a safe experience for all users. ## How We Share Your Personal Data We do not sell your personal data to external parties. - **Through Your Profile**: Any personal data you choose to disclose in your public profile on `tianji.msgbye.com` will be visible to other users. They may view your profile information and interact with any public prompts you share. - **Compliance with Laws**: We may disclose your personal data to courts, law enforcement agencies, or government authorities if required to: - Comply with legal obligations or processes. - Respond to claims against `tianji.msgbye.com`. - Address verified requests related to criminal investigations or alleged illegal activities. - Enforce our Terms of Use and other agreements. - Protect the rights, property, or personal safety of `tianji.msgbye.com`, its employees, and the public. ## Information Security `tianji.msgbye.com` implements technical, administrative, and physical safeguards designed to prevent unauthorized access, use, or disclosure of the personal data we collect or store. These measures are intended to ensure a level of security appropriate to the risk involved. We regularly monitor our systems for vulnerabilities and potential attacks. However, no internet transmission is completely secure, so you provide personal data at your own discretion and risk. ## Contact Information For questions or complaints about this Privacy Policy or our practices, please contact us by email at `moonrailgun@gmail.com`.