Remove outdated daemon lifecycle and Gmail skill documentation

- Deleted `daemon-lifecycle-management.md` to reflect feature deprecation and simplify technical documentation.
- Removed `gmail-skill-integration.md` as it is no longer relevant to the current architecture.
This commit is contained in:
cyrus
2026-03-26 18:26:42 +05:30
parent 1201c905a6
commit 2738d3180f
5 changed files with 0 additions and 1966 deletions
-459
View File
@@ -1,459 +0,0 @@
---
```md
# Cross-Platform Tauri Agent Assistant — MVP Specification
> **Current product docs:** we **ship and support desktop only** (Windows, macOS, Linux). This spec still describes a **broader platform vision** (including mobile) for engineering planning—it is not a promise of availability on Android/iOS or web yet.
## Overview
This MVP defines a **Telegram-based Agent Assistant** built with **Tauri (Rust + Web UI)** targeting:
- Windows
- macOS
- Android
- iOS
The assistant:
- Interacts with users via a **Telegram bot (DM-first)**
- Processes Telegram channel data **locally on device**
- Uses a **minimal backend** only for:
- identity & login
- payments & entitlements
- push notifications (especially for iOS)
- Avoids storing Telegram message content on servers
---
## Core Platform Behavior Summary
| Platform | Listening Model | Trigger to Respond |
| -------- | ------------------------------- | -------------------------- |
| Windows | Continuous (background) | Bot DM or channel activity |
| macOS | Continuous (background) | Bot DM or channel activity |
| Android | Continuous (foreground service) | Bot DM or channel activity |
| iOS | On-demand only | Bot DM → push → tap → sync |
---
## Architectural Pillars
- **UI-first development**
- **Single Rust agent runtime**
- **Telegram bot as the user interface**
- **Privacy-first local processing**
- **Backend as infrastructure, not intelligence**
---
# PHASED MVP PLAN
---
## Phase 0 — Project Skeleton & Tooling
### Goals
- Prepare repo structure
- Establish documentation and contribution rules
- No business logic yet
### Deliverables
- Monorepo structure
- Tauri project scaffold
- Mobile targets enabled (Tauri v2)
- CI hooks (optional)
### Repo Structure
```
/apps
/desktop
/mobile
/core
/agent-runtime (Rust)
/storage
/telegram
/backend
/docs
```
### Documentation Commands (Required)
```bash
/docs/architecture.md # high-level system design
/docs/decisions/ADR-000.md # initial architecture decision record
```
### Exit Criteria
- App builds and runs (blank UI)
- Docs folder initialized
- ADR process agreed upon
---
## Phase 1 — UI-First MVP (No Logic)
### Goals
Build the **entire UI flow** before implementing logic.
### UI Screens
- Login / Signup
- Telegram Connect (bot instructions)
- Channel Selection
- Sync Status Screen
- Settings (background, privacy, storage)
- Plan & Billing (stub)
- Logs / Activity View (local only)
### Platforms
- All platforms (desktop + mobile)
### Deliverables
- Responsive UI
- Navigation between screens
- Mock data only
- No Telegram, no backend, no Rust logic
### Documentation Commands
```bash
/docs/ui/flows.md # user flows
/docs/ui/screens.md # screen definitions
/docs/ui/states.md # loading / error / empty states
```
### Exit Criteria
- Entire app is navigable
- No dead-end screens
- UI approved before logic begins
---
## Phase 2 — Local Agent Runtime (Rust Only)
### Goals
Implement the **local agent engine** without Telegram or backend.
### Components
- Rust agent runtime
- Intent router (question / sync / config)
- Processing pipeline (stubbed)
- Response composer (mock responses)
### Deliverables
- Tauri IPC commands:
- `agent_init`
- `agent_process_query`
- `agent_status`
- In-memory only state
### Documentation Commands
```bash
/docs/core/agent.md # agent architecture
/docs/core/events.md # internal event types
/docs/core/state.md # memory state model
```
### Exit Criteria
- UI can send a question
- Agent returns a mock response
- No persistence yet
---
## Phase 3 — Local Storage & Privacy Layer
### Goals
Add **efficient, privacy-first local storage**.
### Storage Rules
- No Telegram message bodies by default
- Store only:
- channel IDs
- last processed message IDs
- dedupe hashes
- Encrypted at rest
### Deliverables
- Encrypted SQLite
- OS keychain integration
- Storage abstraction in Rust
- “Ephemeral mode” toggle
### Documentation Commands
```bash
/docs/storage/schema.md
/docs/storage/encryption.md
/docs/privacy/model.md
```
### Exit Criteria
- App restarts without losing cursors
- “Delete local data” wipes all state
- No plaintext sensitive data on disk
---
## Phase 4 — Telegram Bot Integration (Agent Assistant)
### Goals
Turn the app into a **real Telegram agent assistant**.
### Telegram Capabilities (MVP)
- Bot DM interaction
- Read user questions
- Fetch channel messages (where bot has access)
- Reply via DM
### Platform Behavior
- Windows/macOS/Android: continuous polling
- iOS: no polling (on-demand only)
### Deliverables
- Telegram Bot Gateway (Rust)
- Update polling / fetching
- Message dedupe + cursoring
- Agent replies sent via bot DM
### Documentation Commands
```bash
/docs/telegram/bot.md
/docs/telegram/flows.md
/docs/telegram/limits.md
```
### Exit Criteria
- User asks a question in Telegram
- App processes it
- Bot replies correctly
- No duplicate replies
---
## Phase 5 — Platform Background Execution
### Goals
Enable **platform-appropriate background behavior**.
### Platform Breakdown
#### Windows
- Tray app
- Autostart
- Background polling
#### macOS
- Menu bar app
- Launch at login
#### Android
- Foreground service (persistent notification)
- Background polling allowed
#### iOS
- ❌ No continuous background
- Only foreground execution
### Documentation Commands
```bash
/docs/platforms/windows.md
/docs/platforms/macos.md
/docs/platforms/android.md
/docs/platforms/ios.md
```
### Exit Criteria
- Desktop apps run without UI open
- Android foreground service stable
- iOS behaves strictly foreground-only
---
## Phase 6 — Minimal Backend Integration
### Goals
Introduce backend **without violating privacy goals**.
### Backend Responsibilities
- Authentication
- Device registration
- Entitlements
- Push notifications
- Payment verification
### Explicit Non-Responsibilities
- No Telegram message storage
- No agent logic
- No summaries
### Deliverables
- Auth flow wired into UI
- Entitlements fetched on startup
- Device registered for push
### Documentation Commands
```bash
/docs/backend/api.md
/docs/backend/data-model.md
/docs/backend/security.md
```
### Exit Criteria
- User login works
- Entitlements enforced locally
- Backend DB contains no message content
---
## Phase 7 — iOS Push → Tap → Sync Flow
### Goals
Implement the **iOS-specific agent interaction model**.
### Flow
1. User sends question to bot
2. Backend triggers visible push
3. User taps notification
4. App opens
5. App syncs Telegram
6. Agent processes
7. Bot replies
### Deliverables
- APNs integration
- Push payload handling
- Sync-on-open logic
### Documentation Commands
```bash
/docs/ios/push-flow.md
/docs/ios/limitations.md
```
### Exit Criteria
- Push reliably opens app
- Sync runs automatically
- Bot replies successfully
---
## Phase 8 — Payments & Plan Gating
### Goals
Monetize safely and correctly.
### Platforms
- Desktop: Stripe / Paddle
- Android: Play Billing
- iOS: StoreKit
### Deliverables
- Purchase flow per platform
- Receipt verification
- Feature gating in Rust
### Documentation Commands
```bash
/docs/billing/plans.md
/docs/billing/verification.md
/docs/billing/entitlements.md
```
### Exit Criteria
- Paid features unlock correctly
- Downgrades enforced
- Offline grace period handled
---
## Phase 9 — Hardening & Release Prep
### Goals
Stability, observability, and trust.
### Deliverables
- Error handling
- Rate limiting
- Abuse prevention
- Crash-safe storage
- UX polish
### Documentation Commands
```bash
/docs/release/checklist.md
/docs/known-issues.md
/docs/security/threat-model.md
```
### Exit Criteria
- No critical crashes
- No duplicate Telegram replies
- Clear user-facing error states
---
## Final Notes
- The **Telegram bot is the product interface**
- The **Tauri app is the execution engine**
- The **backend is infrastructure, not intelligence**
- iOS behavior is intentionally constrained for correctness
---
END OF MVP
-350
View File
@@ -1,350 +0,0 @@
# Daemon Lifecycle Management System
**Status**: ✅ Implemented
**Version**: 1.0.0
**Date**: February 2026
## Overview
The Daemon Lifecycle Management System provides comprehensive frontend integration with the alphahuman Rust daemon, enabling real-time health monitoring, automatic lifecycle management, and user-friendly daemon controls throughout the application.
## Background
### Problem Statement
The alphahuman application runs a sophisticated Rust daemon that manages critical backend services (gateway, channels, heartbeat, scheduler) and emits detailed health information every 5 seconds. However, the frontend previously had:
- **Poor Visibility**: Daemon controls buried in developer console only
- **No Real-Time Monitoring**: Manual status checks with raw JSON output
- **No Automatic Management**: No startup detection or error recovery
- **Disconnected UX**: Health events from Rust were ignored by frontend
- **Manual Recovery**: Users had to manually restart failed services
### Solution Overview
This implementation creates a complete bridge between the Rust daemon's health system and the React frontend, providing:
- Real-time health monitoring with visual indicators
- Automatic daemon startup and error recovery
- User-friendly health displays in main UI
- Enhanced developer tools with live status
- Coordinated daemon and socket connection management
## Architecture
### System Components
```
┌─────────────────────────────────────────────────────────────┐
│ React Frontend │
├─────────────────────────────────────────────────────────────┤
│ UI Layer │
│ ├── DaemonHealthIndicator (Main UI) │
│ ├── DaemonHealthPanel (Detailed View) │
│ └── Enhanced TauriCommandsPanel (Dev Tools) │
├─────────────────────────────────────────────────────────────┤
│ State Management │
│ ├── daemonSlice.ts (Redux) │
│ ├── useDaemonHealth.ts (React Hook) │
│ └── useDaemonLifecycle.ts (Lifecycle Hook) │
├─────────────────────────────────────────────────────────────┤
│ Services │
│ └── daemonHealthService.ts (Event Processing) │
├─────────────────────────────────────────────────────────────┤
│ Integration Layer │
│ ├── tauriSocket.ts (Event Listening) │
│ └── SocketProvider.tsx (Coordinated State) │
└─────────────────────────────────────────────────────────────┘
Tauri Event Bridge
┌─────────────────────────────────────────────────────────────┐
│ Rust Daemon │
├─────────────────────────────────────────────────────────────┤
│ Health System (src-tauri/src/alphahuman/health/mod.rs) │
│ ├── Component Health Tracking │
│ ├── Health Snapshot Generation │
│ └── Event Emission (every 5s) │
├─────────────────────────────────────────────────────────────┤
│ Daemon Supervisor (src-tauri/src/alphahuman/daemon/mod.rs) │
│ ├── Component Management │
│ ├── Automatic Restarts │
│ └── Exponential Backoff │
├─────────────────────────────────────────────────────────────┤
│ Components │
│ ├── Gateway (API Server) │
│ ├── Channels (Communication) │
│ ├── Heartbeat (Health Monitor) │
│ └── Scheduler (Cron Jobs) │
└─────────────────────────────────────────────────────────────┘
```
### Event Flow
1. **Health Generation**: Rust daemon generates health snapshots every 5 seconds
2. **Event Emission**: Health data emitted via `alphahuman:health` Tauri event
3. **Frontend Processing**: `daemonHealthService` receives and parses events
4. **State Updates**: Redux state updated with component health information
5. **UI Reactivity**: React components automatically re-render with new status
6. **User Actions**: Manual controls trigger Tauri commands back to Rust
## Implementation Details
### Redux State Management
**File**: `src/store/daemonSlice.ts`
```typescript
interface DaemonUserState {
status: 'starting' | 'running' | 'error' | 'disconnected';
healthSnapshot: HealthSnapshot | null;
components: {
gateway?: ComponentHealth;
channels?: ComponentHealth;
heartbeat?: ComponentHealth;
scheduler?: ComponentHealth;
};
lastHealthUpdate: string | null;
connectionAttempts: number;
autoStartEnabled: boolean;
isRecovering: boolean;
healthTimeoutId: string | null;
}
```
**Key Features**:
- Per-user daemon state isolation (following existing patterns)
- Component-level health tracking with error details
- Connection attempt management with exponential backoff
- Auto-start preference persistence
- Timeout handling for health event detection
### Health Monitoring Service
**File**: `src/services/daemonHealthService.ts`
```typescript
export class DaemonHealthService {
private healthTimeoutId: NodeJS.Timeout | null = null;
private readonly HEALTH_TIMEOUT_MS = 30000; // 30 seconds
async setupHealthListener(): Promise<UnlistenFn | null>;
private parseHealthSnapshot(payload: unknown): HealthSnapshot | null;
private updateReduxFromHealth(snapshot: HealthSnapshot): void;
startHealthTimeout(): void;
}
```
**Responsibilities**:
- Listen for `alphahuman:health` Tauri events from Rust
- Parse and validate health snapshot data
- Update Redux state with component health information
- Manage 30-second timeout detection for disconnected daemon
- Handle cleanup and error recovery
### UI Components
#### DaemonHealthIndicator
**File**: `src/components/daemon/DaemonHealthIndicator.tsx`
**Purpose**: Compact status indicator for main application UI
**Visual States**:
- 🟢 **Green**: All components running healthy (`status: 'running'`)
- 🟡 **Yellow**: Daemon starting or recovering (`status: 'starting'`)
- 🔴 **Red**: One or more components in error state (`status: 'error'`)
-**Gray**: Daemon disconnected or not running (`status: 'disconnected'`)
**Features**:
- Click to open detailed health panel
- Tooltip showing component health summary
- Responsive sizing (sm/md/lg variants)
- Only visible in Tauri environments
#### DaemonHealthPanel
**File**: `src/components/daemon/DaemonHealthPanel.tsx`
**Purpose**: Detailed health breakdown and manual controls
**Features**:
- Component health table with status, last update, restart counts
- Manual restart buttons for individual components
- Auto-start toggle with persistence
- Connection retry controls
- Real-time health information display
- User-friendly error messages and troubleshooting hints
### Lifecycle Management
**File**: `src/hooks/useDaemonLifecycle.ts`
**Automatic Behaviors**:
1. **App Startup**:
- Detect existing daemon status
- Auto-start daemon if enabled and not running
- Setup health monitoring listeners
2. **Error Recovery**:
- Exponential backoff retry attempts (1s → 2s → 4s → 8s → 30s max)
- Maximum 5 retry attempts before requiring manual intervention
- Clear error states on successful recovery
3. **Background Handling**:
- Maintain health monitoring when app backgrounded
- Reconnection logic without page refresh
- Coordinate with socket connection management
4. **Cleanup**:
- Proper event listener cleanup on unmount
- Timeout cancellation to prevent memory leaks
- Graceful shutdown coordination
### Integration Points
#### Enhanced Service Management
**File**: `src/components/settings/panels/TauriCommandsPanel.tsx`
**Improvements to Existing Developer Console**:
- **Live Status Display**: Real-time daemon status with PID and uptime
- **Component Health Grid**: Visual status for all daemon components
- **Enhanced Controls**: Smart start/stop buttons with proper loading states
- **Auto-Start Toggle**: User preference for automatic daemon startup
- **Connection Tracking**: Display retry attempts and recovery status
- **Better Error Messages**: User-friendly errors with troubleshooting hints
**Before vs After**:
```typescript
// Before: Manual status check
<PrimaryButton onClick={() => run(alphahumanServiceStatus, 'serviceStatus')}>
Status
</PrimaryButton>
// After: Live status display
<div className="flex items-center gap-3">
<DaemonHealthIndicator size="md" />
<div>
<div className="text-white font-medium">Daemon Status: {status}</div>
<div className="text-xs text-gray-400">PID: {pid} | Uptime: {uptime}</div>
</div>
</div>
```
#### Socket Provider Integration
**File**: `src/providers/SocketProvider.tsx`
**Coordinated State Management**:
- Check daemon health before attempting socket connections
- Display daemon-related errors in socket connection status
- Coordinate daemon startup with socket connection flows
- Provide daemon health context to socket consumers
#### Main UI Integration
**File**: `src/components/MiniSidebar.tsx`
**User-Facing Integration**:
- Daemon health indicator in main navigation (Tauri-only)
- Click to open detailed health modal
- Non-intrusive but easily accessible
- Consistent with existing UI patterns
## User Experience
### For End Users
1. **Visible Status**: Daemon health indicator in main UI shows system status at a glance
2. **Automatic Operation**: Daemon starts automatically and recovers from errors without user intervention
3. **Clear Feedback**: User-friendly messages explain daemon state and provide actionable guidance
4. **Quick Access**: Click health indicator to see detailed component status and manual controls
### For Developers
1. **Enhanced Console**: Improved service management in settings with live status updates
2. **Component Monitoring**: Real-time visibility into all daemon components (gateway, channels, etc.)
3. **Debug Information**: Detailed health snapshots, retry attempts, and error history
4. **Manual Override**: Full control over daemon lifecycle with proper state management
### Error Handling
1. **Graceful Degradation**: System works properly in non-Tauri environments
2. **Timeout Management**: 30-second timeout detection with automatic recovery attempts
3. **User Guidance**: Clear error messages with troubleshooting suggestions
4. **Recovery Actions**: Manual restart options when automatic recovery fails
## Technical Benefits
### Performance
- **Efficient Updates**: Debounced Redux updates prevent excessive re-renders
- **Memory Management**: Proper cleanup of timeouts and event listeners
- **Background Optimization**: Minimal resource usage when app backgrounded
### Reliability
- **Timeout Handling**: Robust detection of daemon disconnection
- **Exponential Backoff**: Smart retry logic prevents resource exhaustion
- **State Consistency**: Coordinated daemon and socket connection states
- **Error Recovery**: Automatic recovery from transient failures
### Maintainability
- **TypeScript**: Full type safety throughout the implementation
- **Modular Design**: Clear separation between state, services, and UI components
- **Existing Patterns**: Follows established Redux and component patterns
- **Testing**: Comprehensive error handling and edge case management
## Configuration
### Auto-Start Behavior
Users can control daemon auto-start behavior through:
1. **UI Toggle**: Available in both health panel and settings console
2. **Persistence**: Preference stored in Redux with persistence
3. **Default**: Auto-start enabled by default for better UX
### Health Monitoring
- **Event Frequency**: Rust daemon emits health every 5 seconds
- **Timeout Duration**: 30 seconds without health events = disconnected
- **Retry Logic**: Maximum 5 attempts with exponential backoff
- **Component Tracking**: Gateway, channels, heartbeat, scheduler components
## Future Enhancements
### Potential Improvements
1. **Health History**: Track daemon health over time with charts/graphs
2. **Performance Metrics**: CPU/memory usage from daemon components
3. **Log Integration**: Show daemon logs directly in health panel
4. **Mobile Optimization**: Enhanced mobile-specific daemon management
5. **Notification System**: Push notifications for critical daemon events
### Extensibility
The architecture supports easy extension for:
- Additional daemon components
- Custom health check logic
- Third-party integrations
- Advanced monitoring features
## Conclusion
The Daemon Lifecycle Management System provides a complete bridge between the sophisticated Rust daemon infrastructure and the React frontend, delivering excellent user experience while maintaining the technical robustness required for a production application.
The implementation follows established patterns, provides comprehensive error handling, and creates a foundation for future daemon-related features while ensuring the system remains reliable and user-friendly.
-627
View File
@@ -1,627 +0,0 @@
# Gmail Skill Integration — Design Reference
**Scope:** Native Gmail skill — OAuth2 connect flow, tunnel-based callback, token storage,
initial inbox sync, and `send_email` tool dispatch.
**Status:** Design reference / implementation guide.
---
## 1. Overview
This document describes the full lifecycle of connecting a Gmail skill to ZeroClaw.
It covers four concerns in order:
1. **OAuth2 connect** — how the user authorises Gmail access
2. **Tunnel callback** — how the authorization code reaches the backend through the tunnel
3. **Token storage** — how credentials are encrypted and persisted in the auth profile store
4. **Email sync + send** — how the initial inbox is fetched and how sending works
The skill is declared with the following manifest:
```json
{
"id": "gmail",
"name": "Gmail",
"version": "1.0.0",
"description": "Gmail integration via Google API — comprehensive email management with OAuth2 authentication, send/receive, labels, search, and attachments.",
"auto_start": false,
"platforms": ["windows", "macos", "linux", "android", "ios"],
"setup": {
"required": true,
"label": "Connect Gmail",
"oauth": {
"provider": "gmail",
"scopes": [
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.labels"
]
}
}
}
```
The equivalent `SKILL.toml` that ZeroClaw loads from
`~/.zeroclaw/workspace/skills/gmail/SKILL.toml` is shown in §2.
---
## 2. Skill Manifest (`SKILL.toml`)
Skills in ZeroClaw are loaded by `src/skills/mod.rs`. The loader reads a `SKILL.toml`
(or `SKILL.md`) file from `~/.zeroclaw/workspace/skills/<name>/`. The Rust struct it
populates is `Skill` / `SkillManifest` / `SkillTool`.
`SKILL.toml` for Gmail:
```toml
[skill]
name = "gmail"
version = "1.0.0"
description = "Gmail integration via Google API — send/receive, labels, search, attachments."
author = "zeroclaw"
tags = ["email", "google", "productivity"]
# OAuth setup block — interpreted by the skill connect subsystem (see §3).
[skill.setup]
required = true
label = "Connect Gmail"
[skill.setup.oauth]
provider = "gmail"
scopes = [
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.labels",
]
# Tools exposed to the agent after connection.
[[tools]]
name = "gmail_send_email"
description = "Send an email via Gmail. Requires a connected Gmail account."
kind = "http"
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
[[tools]]
name = "gmail_list_messages"
description = "List messages in the Gmail inbox. Supports query filters."
kind = "http"
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages"
[[tools]]
name = "gmail_get_message"
description = "Fetch the full content of a single Gmail message by ID."
kind = "http"
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}"
[[tools]]
name = "gmail_modify_labels"
description = "Add or remove labels on a Gmail message."
kind = "http"
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}/modify"
```
At runtime, `load_skills_with_config()` (`src/skills/mod.rs:78`) reads these tools into
`Vec<SkillTool>` and injects them into the agent system prompt. The `kind = "http"` tools
are dispatched via `HttpRequestTool` (`src/tools/http_request.rs`) with the stored
Bearer token injected as the `Authorization` header (see §5).
---
## 3. OAuth2 Connect Flow
### 3.1 What the user triggers
When the user asks to connect Gmail (via the frontend or a CLI command), the backend:
1. Generates a **PKCE pair** (code verifier + SHA-256 challenge) — following the same
pattern as `src/auth/openai_oauth.rs:generate_pkce_state()`.
2. Generates a random CSRF `state` token (24 bytes, base64url).
3. Constructs the Google OAuth2 authorisation URL:
```
https://accounts.google.com/o/oauth2/v2/auth
?response_type=code
&client_id=<GOOGLE_CLIENT_ID>
&redirect_uri=<TUNNEL_PUBLIC_URL>/auth/callback/gmail
&scope=https://www.googleapis.com/auth/gmail.send
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/gmail.labels
&code_challenge=<SHA256_CHALLENGE>
&code_challenge_method=S256
&state=<RANDOM_STATE>
&access_type=offline
&prompt=consent
```
`access_type=offline` is required to receive a `refresh_token` from Google.
`prompt=consent` ensures the refresh token is issued on every connect, not just the first.
4. The URL is sent to the frontend. The frontend opens it in a browser or WebView.
### 3.2 Where the redirect URI points
The `redirect_uri` is the **tunnel public URL** with the path `/auth/callback/gmail`.
ZeroClaw's tunnel system (`src/tunnel/mod.rs`) exposes the local gateway port via one of:
| Provider | How public URL is obtained |
| ------------ | ---------------------------------------------------------------- |
| `cloudflare` | `cloudflared tunnel` process stdout — `src/tunnel/cloudflare.rs` |
| `ngrok` | ngrok local API `GET /api/tunnels``src/tunnel/ngrok.rs` |
| `tailscale` | `tailscale funnel` + hostname — `src/tunnel/tailscale.rs` |
| `custom` | user-supplied URL or stdout pattern — `src/tunnel/custom.rs` |
The tunnel's `start(local_host, local_port)` method returns the public URL string.
This URL is what gets used as the `redirect_uri`.
Example with Cloudflare Tunnel:
```
https://your-subdomain.trycloudflare.com/auth/callback/gmail
```
Google's OAuth2 server sends the browser to this URL after the user grants consent.
---
## 4. Receiving the Authorization Code via the Tunnel
### 4.1 The callback endpoint
The gateway (`src/gateway/mod.rs`) runs as an Axum HTTP server. A new route needs to be
registered to receive the OAuth2 callback:
```
GET /auth/callback/gmail?code=<AUTH_CODE>&state=<STATE>
```
This route handler must:
1. **Validate the `state` parameter** against the CSRF token stored in memory when the
flow was initiated. Reject mismatches with `400 Bad Request`.
2. **Extract the `code`** query parameter.
3. **Exchange the code** for tokens by calling Google's token endpoint:
```
POST https://oauth2.googleapis.com/token
grant_type=authorization_code
code=<AUTH_CODE>
client_id=<GOOGLE_CLIENT_ID>
client_secret=<GOOGLE_CLIENT_SECRET>
redirect_uri=<TUNNEL_PUBLIC_URL>/auth/callback/gmail
code_verifier=<PKCE_VERIFIER>
```
The exchange pattern mirrors `src/auth/openai_oauth.rs:exchange_code_for_tokens()`,
which posts a form body and parses the JSON response into `TokenSet`.
Google returns:
```json
{
"access_token": "ya29.a0...",
"expires_in": 3599,
"refresh_token": "1//0g...",
"scope": "https://www.googleapis.com/auth/gmail.send ...",
"token_type": "Bearer"
}
```
4. The handler stores the `TokenSet` (see §4.2) and returns a success response to the
browser (an HTML page or redirect to the frontend app confirming the connection).
### 4.2 The in-flight CSRF / PKCE state
Before redirecting the user to Google, the pending PKCE state and CSRF token must be
stored so the callback handler can look them up. The right storage is the skill's dedicated
in-memory or short-lived file-backed state, keyed by the `state` value:
```
pending_oauth_states: HashMap<state_token, (PkceState, initiated_at)>
```
Entries expire after a fixed window (e.g. 10 minutes) to prevent orphaned state
accumulation. This follows the same pattern ZeroClaw already uses for pairing nonces
in `src/security/pairing.rs`.
---
## 5. Token Storage
After a successful token exchange, the tokens are persisted using the `AuthService`
and `AuthProfilesStore` from `src/auth/`.
### 5.1 Storing the token set
```rust
// Pseudocode — mirrors src/auth/mod.rs AuthService::store_openai_tokens()
let token_set = TokenSet {
access_token: response.access_token,
refresh_token: response.refresh_token,
id_token: None,
expires_at: Some(Utc::now() + Duration::seconds(response.expires_in)),
token_type: Some("Bearer".into()),
scope: Some(scopes.join(" ")),
};
auth_service
.store_oauth_tokens("gmail", "default", token_set, None, true)
.await?;
```
`store_oauth_tokens` calls `AuthProfilesStore::upsert_profile()`, which:
1. Creates or updates an `AuthProfile` with `kind = AuthProfileKind::OAuth`.
2. Serialises the profile data to `~/.zeroclaw/auth-profiles.json` (file-locked via
`auth-profiles.lock` with a 10-second timeout — `src/auth/profiles.rs:1617`).
### 5.2 How tokens are encrypted at rest
Before writing to disk, every secret field passes through `SecretStore::encrypt()`
(`src/security/secrets.rs`). The encryption scheme is:
- **Algorithm**: ChaCha20-Poly1305 AEAD (256-bit key)
- **Key file**: `~/.zeroclaw/.secret_key` (permissions 0600, created on first use)
- **Format on disk**: `enc2:<hex(12-byte-nonce ‖ ciphertext ‖ 16-byte-Poly1305-tag)>`
- **Config**: encryption is enabled by default; disable with `secrets.encrypt = false`
The stored profile entry looks like:
```json
{
"id": "gmail:default",
"provider": "gmail",
"profile_name": "default",
"kind": "oauth",
"token_set": {
"access_token": "enc2:a1b2c3...",
"refresh_token": "enc2:d4e5f6...",
"expires_at": "2026-03-09T12:00:00Z",
"token_type": "Bearer",
"scope": "https://www.googleapis.com/auth/gmail.send ..."
},
"created_at": "2026-03-09T11:00:00Z",
"updated_at": "2026-03-09T11:00:00Z"
}
```
### 5.3 Token refresh
Google access tokens expire after 3600 seconds. Before any Gmail API call, the skill
must check `token_set.is_expiring_within(Duration::from_secs(90))` (the same 90-second
skew used for OpenAI tokens in `src/auth/mod.rs:160`). If expiring:
```
POST https://oauth2.googleapis.com/token
grant_type=refresh_token
refresh_token=<stored_refresh_token>
client_id=<GOOGLE_CLIENT_ID>
client_secret=<GOOGLE_CLIENT_SECRET>
```
The new `access_token` (and new `refresh_token` if Google rotates it) is written back
to the auth profile via `AuthProfilesStore::update_profile()`.
---
## 6. Initial Email Sync (First 100 Emails)
Once connected, the skill performs a one-time initial sync. This is triggered
immediately after a successful token exchange and runs as a background task.
### 6.1 Fetch message IDs
```
GET https://gmail.googleapis.com/gmail/v1/users/me/messages
?maxResults=100
&labelIds=INBOX
Authorization: Bearer <access_token>
```
Returns up to 100 message descriptors: `[{ "id": "...", "threadId": "..." }, ...]`.
### 6.2 Fetch full message details
For each message ID, fetch the full message:
```
GET https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}
?format=full
Authorization: Bearer <access_token>
```
The response contains headers (From, To, Subject, Date), a snippet, and the body parts.
In practice, to avoid 100 sequential requests, use the Gmail batch API:
```
POST https://www.googleapis.com/batch/gmail/v1
Content-Type: multipart/mixed; boundary="batch_boundary"
--batch_boundary
Content-Type: application/http
GET /gmail/v1/users/me/messages/{id1}?format=full
...
```
### 6.3 Storing emails in ZeroClaw memory
Each email is stored via `memory_store` (`src/tools/memory_store.rs`) with:
| Field | Value |
| ---------- | ----------------------------------------------------------------- |
| `key` | `gmail_msg_<message_id>` |
| `content` | Formatted string: `From: ... Subject: ... Date: ... Snippet: ...` |
| `category` | `MemoryCategory::Custom("gmail")` |
This makes every synced email searchable via `memory_recall` with queries like
`"gmail from:zeroclaw@example.com"`.
For larger body content that exceeds a single memory entry, store the full body as a
separate entry keyed `gmail_body_<message_id>` and the summary/metadata as
`gmail_msg_<message_id>`.
Example stored entries after sync:
```
key: gmail_msg_18de7f2a1b3c4e5d
content: From: sender@example.com
To: me@gmail.com
Subject: Project update
Date: 2026-03-08T14:32:00Z
Snippet: "Here is the latest update on the project..."
Labels: INBOX, UNREAD
category: gmail
key: gmail_body_18de7f2a1b3c4e5d
content: (full decoded email body text)
category: gmail
```
### 6.4 Sync state tracking
Store the highest-known history ID after sync so incremental sync can pick up from
where it left off:
```
key: gmail_sync_history_id
content: 12345678
category: core
```
---
## 7. The `gmail_send_email` Tool
### 7.1 Tool definition in `SKILL.toml`
The `[[tools]]` entry from §2:
```toml
[[tools]]
name = "gmail_send_email"
description = "Send an email via Gmail. Requires a connected Gmail account."
kind = "http"
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
```
### 7.2 How the agent calls it
The LLM emits a tool call:
```json
{
"name": "gmail_send_email",
"arguments": {
"to": "recipient@example.com",
"subject": "Hello from ZeroClaw",
"body": "This is a test email sent by the agent."
}
}
```
### 7.3 Tool dispatch path
```
Agent loop (src/agent/loop_.rs)
└── dispatches to registered tool by name
└── HttpRequestTool::execute(args) [src/tools/http_request.rs]
├── Retrieve gmail access_token from AuthService
│ └── auth_service.get_provider_bearer_token("gmail", None)
│ └── decrypts enc2: value via SecretStore::decrypt()
├── Build RFC 2822 message and base64url-encode it
├── POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
│ Headers:
│ Authorization: Bearer <decrypted_access_token>
│ Content-Type: application/json
│ Body:
│ { "raw": "<base64url_encoded_rfc2822_message>" }
└── Return ToolResult { success, output: "Message sent. ID: <id>" }
```
### 7.4 Building the RFC 2822 message
Gmail's send endpoint requires the email encoded as an RFC 2822 message, then base64url
encoded. The structure:
```
From: me@gmail.com
To: recipient@example.com
Subject: Hello from ZeroClaw
Content-Type: text/plain; charset="UTF-8"
MIME-Version: 1.0
This is a test email sent by the agent.
```
Then the entire string is base64url-encoded (no padding) and placed in the `raw` field:
```json
{ "raw": "RnJvbTogbWVAZ21haWwuY29tCi..." }
```
### 7.5 Security enforcement
Before executing any outbound HTTP call, the `SecurityPolicy` is consulted:
```rust
// src/security/policy.rs
security.enforce_tool_operation(ToolOperation::Act, "gmail_send_email")
```
`ToolOperation::Act` is the category for write/side-effect operations. If the agent is
running in `ReadOnly` or `Supervised` mode, this call fails with an explicit error before
any network request is made.
The `HttpRequestTool` additionally enforces the `http.allowed_domains` allowlist. For
Gmail tools, `googleapis.com` must be present in that list:
```toml
# config.toml
[http]
enabled = true
allowed_domains = ["googleapis.com"]
```
### 7.6 Reply-to and threading
To send a reply within an existing thread, add the Gmail `threadId` to the request body:
```json
{ "raw": "<base64url_message_with_In-Reply-To_header>", "threadId": "18de7f2a1b3c4e5d" }
```
The RFC 2822 message must include `In-Reply-To: <original_message_id>` and
`References: <original_message_id>` headers for proper threading.
---
## 8. Full End-to-End Sequence
```
Frontend / User
│ 1. "Connect Gmail"
ZeroClaw Backend
│ 2. Generate PKCE + CSRF state
│ 3. Build Google OAuth2 authorize URL with tunnel redirect_uri
│ 4. Return URL to frontend
Browser (user)
│ 5. User visits URL, grants consent in Google
Google OAuth2
│ 6. Browser redirected to <TUNNEL_PUBLIC_URL>/auth/callback/gmail?code=XXX&state=YYY
Tunnel (cloudflare / ngrok / tailscale)
│ 7. Proxies HTTPS request to local gateway port
Gateway (src/gateway/mod.rs) — GET /auth/callback/gmail
│ 8. Validate CSRF state
│ 9. Exchange code → TokenSet via POST https://oauth2.googleapis.com/token
│ 10. Store TokenSet in AuthProfilesStore (encrypted, "gmail:default")
│ 11. Return success page/redirect to frontend
Background task — initial sync
│ 12. GET /gmail/v1/users/me/messages?maxResults=100&labelIds=INBOX
│ 13. Batch-fetch 100 full messages
│ 14. memory_store each email under key=gmail_msg_<id>, category=gmail
│ 15. memory_store gmail_sync_history_id = <latest_historyId>
Agent (subsequent interactions)
│ 16. LLM generates tool call: gmail_send_email { to, subject, body }
│ 17. Agent loop dispatches to HttpRequestTool
│ 18. HttpRequestTool retrieves + decrypts access_token from AuthService
│ 19. Refreshes token if expiring within 90 seconds
│ 20. POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
│ 21. Returns ToolResult to agent
Done
```
---
## 9. Configuration Reference
All Gmail skill config lives in `config.toml`:
```toml
# Enable HTTP request tool with Google API access
[http]
enabled = true
allowed_domains = ["googleapis.com", "oauth2.googleapis.com"]
timeout_secs = 30
max_response_size = 524288 # 512KB — enough for email batch responses
# Tunnel for OAuth callback (pick one)
[tunnel]
provider = "cloudflare"
[tunnel.cloudflare]
token = "your-cloudflare-tunnel-token"
# Or ngrok:
# [tunnel]
# provider = "ngrok"
# [tunnel.ngrok]
# auth_token = "your-ngrok-token"
# Google OAuth2 app credentials (stored encrypted)
# Set via environment or onboard wizard — never commit raw values
[integrations.gmail]
client_id = "enc2:..."
client_secret = "enc2:..."
```
The Google credentials (`client_id`, `client_secret`) are encrypted by `SecretStore`
before being written to `config.toml` — the same ChaCha20-Poly1305 scheme used for all
secrets (`src/security/secrets.rs`).
---
## 10. Key Source Files
| File | Role |
| ---------------------------- | ----------------------------------------------------------------- |
| `src/skills/mod.rs` | Skill loading, `SkillTool` struct, `load_skills_with_config()` |
| `src/auth/mod.rs` | `AuthService` — store/retrieve/refresh OAuth tokens |
| `src/auth/profiles.rs` | `AuthProfile`, `TokenSet`, `AuthProfilesStore` — JSON persistence |
| `src/auth/openai_oauth.rs` | PKCE generation, code exchange, refresh — reference pattern |
| `src/security/secrets.rs` | `SecretStore` — ChaCha20-Poly1305 encrypt/decrypt |
| `src/tunnel/mod.rs` | `Tunnel` trait + factory — public URL for OAuth redirect |
| `src/tunnel/cloudflare.rs` | Cloudflare Tunnel implementation |
| `src/tunnel/ngrok.rs` | ngrok implementation |
| `src/gateway/mod.rs` | Axum HTTP gateway — where `/auth/callback/gmail` is registered |
| `src/tools/http_request.rs` | `HttpRequestTool` — dispatches Gmail API calls |
| `src/tools/memory_store.rs` | `MemoryStoreTool` — stores synced emails |
| `src/tools/memory_recall.rs` | `MemoryRecallTool` — searches synced emails |
| `src/security/policy.rs` | `SecurityPolicy` — enforces `ToolOperation::Act` guards |
| `docs/config-reference.md` | Full config schema including `[http]`, `[tunnel]` |
---
## 11. Security Notes
- The OAuth2 `redirect_uri` **must be the tunnel URL**. It cannot be `localhost` in a
remote/mobile scenario. Google validates the exact URI registered in the Google Cloud
Console; register it as `https://<your-tunnel-domain>/auth/callback/gmail`.
- The CSRF `state` token must be validated on every callback. Reject mismatched or
missing state with `400 Bad Request` before touching any tokens.
- The PKCE verifier must be destroyed after a successful or failed exchange — never
persist it beyond the in-flight flow.
- `client_secret` must never appear in plaintext in `config.toml`, logs, or agent
tool output. Encrypt it via the secret store and redact it in observability output.
- `googleapis.com` must be explicitly present in `http.allowed_domains` — the
`HttpRequestTool` enforces this allowlist before every request
(`src/tools/http_request.rs`).
- Token refresh runs under the same per-profile Tokio mutex used for OpenAI tokens
(`src/auth/mod.rs:287`) to prevent double-refresh races.
-530
View File
@@ -1,530 +0,0 @@
# AlphaHuman Teams API Reference
Complete reference for all teams-related API endpoints in the AlphaHuman platform.
**Base URL**: `https://api.alphahuman.xyz`
**Authentication**: Bearer JWT token required for all endpoints
**Content-Type**: `application/json`
---
## Core Team Management
### 1. Create Team
**POST** `/teams`
Creates a new team with optional encryption.
**Request Body:**
```json
{ "name": "string (required)", "magicWord": "string (optional)" }
```
**Response Schema:**
```json
{
"id": "string",
"name": "string",
"slug": "string",
"magicWord": "string | null",
"createdBy": "string",
"isPersonal": "boolean",
"subscription": {
"hasActiveSubscription": "boolean",
"plan": "FREE|BASIC|PRO",
"planExpiry": "date-time | null",
"stripeCustomerId": "string | null"
},
"usage": {
"weeklyBudgetUsd": "number",
"spentThisWeekUsd": "number",
"weekStartDate": "date-time"
},
"inviteCode": "string | null",
"maxMembers": "number",
"createdAt": "date-time",
"updatedAt": "date-time"
}
```
**Status Codes:**
- `200` - Team created successfully
- `401` - Unauthorized
---
### 2. List Teams
**GET** `/teams`
Retrieves all teams the authenticated user belongs to.
**Response Schema:**
```json
{
"success": true,
"data": [
{
"team": {
"id": "string",
"name": "string",
"slug": "string",
"isPersonal": "boolean",
"subscription": { "hasActiveSubscription": "boolean", "plan": "FREE|BASIC|PRO" }
},
"role": "admin|billing_manager|member"
}
]
}
```
**Status Codes:**
- `200` - Success
- `401` - Unauthorized
---
### 3. Get Team Details
**GET** `/teams/{teamId}`
Retrieves detailed information for a specific team.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Response Schema:**
```json
{
"id": "string",
"name": "string",
"slug": "string",
"magicWord": "string | null",
"createdBy": "string",
"isPersonal": "boolean",
"subscription": {
"hasActiveSubscription": "boolean",
"plan": "FREE|BASIC|PRO",
"planExpiry": "date-time | null",
"stripeCustomerId": "string | null"
},
"usage": {
"weeklyBudgetUsd": "number",
"spentThisWeekUsd": "number",
"weekStartDate": "date-time"
},
"inviteCode": "string | null",
"maxMembers": "number",
"createdAt": "date-time",
"updatedAt": "date-time"
}
```
**Status Codes:**
- `200` - Success
- `403` - Not a member of the team
- `404` - Team not found
---
### 4. Update Team Settings
**PUT** `/teams/{teamId}`
Updates team settings. **Admin only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Request Body:**
```json
{ "name": "string (optional)", "maxMembers": "number (optional)" }
```
**Status Codes:**
- `200` - Team updated successfully
- `403` - Only admins can update team settings
---
### 5. Delete Team
**DELETE** `/teams/{teamId}`
Deletes a team. **Admin only, non-personal teams only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Status Codes:**
- `200` - Team deleted successfully
- `400` - Cannot delete a personal team
- `403` - Only admins can delete a team
---
### 6. Switch Active Team
**POST** `/teams/{teamId}/switch`
Changes the user's active team.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Status Codes:**
- `200` - Successfully switched active team
- `403` - Not a member of the specified team
---
## Team Member Management
### 7. List Team Members
**GET** `/teams/{teamId}/members`
Lists all members of a team.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Response Schema:**
```json
{
"success": true,
"data": [{ "user": "string", "role": "admin|billing_manager|member", "joinedAt": "date-time" }]
}
```
**Status Codes:**
- `200` - Success
- `403` - Not a member of this team
---
### 8. Remove Team Member
**DELETE** `/teams/{teamId}/members/{userId}`
Removes a member from the team. **Admin only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
- `userId` (path, required) - User identifier to remove
**Status Codes:**
- `200` - Member removed
- `403` - Only admins can remove members
---
### 9. Change Member Role
**PUT** `/teams/{teamId}/members/{userId}/role`
Changes a member's role within the team. **Admin only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
- `userId` (path, required) - User identifier
**Request Body:**
```json
{ "role": "admin|billing_manager|member" }
```
**Status Codes:**
- `200` - Role updated
- `403` - Only admins can change member roles
---
### 10. Leave Team
**POST** `/teams/{teamId}/leave`
Allows a user to leave a team.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Status Codes:**
- `200` - Successfully left the team
- `400` - Cannot leave as the only admin
---
## Team Invite Management
### 11. Create Team Invite
**POST** `/teams/{teamId}/invites`
Creates a new invite code for the team.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Request Body (Optional):**
```json
{ "maxUses": "number (default: 1)", "expiresInDays": "number (default: 7)" }
```
**Response Schema:**
```json
{
"success": true,
"data": { "code": "string (e.g., T-1A2B3C4D5E6F)", "expiresAt": "date-time", "maxUses": "number" }
}
```
**Status Codes:**
- `200` - Invite created successfully
- `403` - Not a team member
---
### 12. List Team Invites
**GET** `/teams/{teamId}/invites`
Lists all invites for a team.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Status Codes:**
- `200` - Success
- `403` - User is not a member of the team
---
### 13. Revoke Team Invite
**DELETE** `/teams/{teamId}/invites/{inviteId}`
Revokes a team invite. **Admin or invite creator only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
- `inviteId` (path, required) - Invite identifier
**Status Codes:**
- `200` - Invite successfully revoked
- `403` - Only admins or invite creator can revoke
- `404` - Invite does not exist
---
### 14. Join Team
**POST** `/teams/join`
Joins a team using an invite code.
**Request Body:**
```json
{ "code": "string (required, e.g., T-1A2B3C4D5E6F)" }
```
**Response Schema:**
```json
{ "success": true, "data": { "team": "string", "membership": "string" } }
```
**Status Codes:**
- `200` - Joined the team successfully
- `400` - Invite expired, max uses reached, or already a team member
- `404` - Invite code not found
---
## Team Billing Management
### 15. Purchase Team Subscription
**POST** `/teams/{teamId}/billing/purchase`
Purchases a subscription plan for the team. **Admin or billing manager only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Request Body:**
```json
{
"plan": "BASIC_MONTHLY|BASIC_YEARLY|PRO_MONTHLY|PRO_YEARLY",
"successUrl": "string (optional)",
"cancelUrl": "string (optional)"
}
```
**Status Codes:**
- `200` - Checkout session created successfully
- `403` - Only admins or billing managers can purchase plans
---
### 16. Get Team Subscription Plan
**GET** `/teams/{teamId}/billing/plan`
Retrieves the team's current subscription information.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Response Schema:**
```json
{
"success": true,
"data": {
"plan": "FREE|BASIC|PRO",
"hasActiveSubscription": "boolean",
"planExpiry": "date-time | null"
}
}
```
**Status Codes:**
- `200` - Success
- `401` - Unauthorized
---
### 17. Create Billing Portal Session
**POST** `/teams/{teamId}/billing/portal`
Creates a Stripe billing portal session for subscription management. **Admin or billing manager only**.
**Parameters:**
- `teamId` (path, required) - Team identifier
**Request Body (Optional):**
```json
{ "returnUrl": "string" }
```
**Response Schema:**
```json
{ "success": true, "data": { "url": "string" } }
```
**Status Codes:**
- `200` - Portal session created successfully
- `403` - Only admins or billing managers can create portal session
---
## Team Roles
### Role Hierarchy
1. **admin** - Full team management permissions
2. **billing_manager** - Billing and subscription management
3. **member** - Basic team member access
### Permission Matrix
| Action | Admin | Billing Manager | Member |
| -------------------- | ----- | --------------- | ------ |
| View team details | ✅ | ✅ | ✅ |
| Update team settings | ✅ | ❌ | ❌ |
| Delete team | ✅ | ❌ | ❌ |
| Add/remove members | ✅ | ❌ | ❌ |
| Change member roles | ✅ | ❌ | ❌ |
| Create invites | ✅ | ✅ | ✅ |
| Manage billing | ✅ | ✅ | ❌ |
| Leave team | ✅\* | ✅ | ✅ |
\*Admin cannot leave if they are the only admin
---
## Team Plans & Limits
### Plan Types
- **FREE** - Basic team functionality
- **BASIC** - Enhanced features and limits
- **PRO** - Full feature set and highest limits
### Usage Tracking
Teams have usage limits tracked through:
- `weeklyBudgetUsd` - Weekly spending budget
- `spentThisWeekUsd` - Current week spending
- `weekStartDate` - When the current week started
- `maxMembers` - Maximum team size
---
## Error Handling
### Common Error Responses
```json
{ "success": false, "error": "Error message description" }
```
### Authentication
All endpoints require a Bearer JWT token:
```
Authorization: Bearer <your-jwt-token>
```
### Rate Limiting
Standard API rate limits apply to all endpoints. Refer to main API documentation for specific limits.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB