mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-22 00:24:46 +00:00
feat: sync
This commit is contained in:
73
README.md
73
README.md
@@ -290,13 +290,29 @@ tf_code/
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
### Requirements
|
||||||
# Via curl (recommended)
|
|
||||||
curl -fsSL https://toothfairyai.com/install/tfcode | bash
|
|
||||||
|
|
||||||
# Via npm
|
**Python 3.10+ is required** on your machine for ToothFairyAI integration.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Python version
|
||||||
|
python3 --version # Should be 3.10 or higher
|
||||||
|
|
||||||
|
# Install Python if needed
|
||||||
|
# macOS: brew install python@3.12
|
||||||
|
# Ubuntu: sudo apt-get install python3.12
|
||||||
|
# Windows: Download from https://python.org/downloads
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install tfcode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Via npm (recommended)
|
||||||
npm install -g tfcode
|
npm install -g tfcode
|
||||||
|
|
||||||
|
# Via curl
|
||||||
|
curl -fsSL https://toothfairyai.com/install/tfcode | bash
|
||||||
|
|
||||||
# Via pip
|
# Via pip
|
||||||
pip install tfcode-cli
|
pip install tfcode-cli
|
||||||
|
|
||||||
@@ -304,6 +320,8 @@ pip install tfcode-cli
|
|||||||
brew install toothfairyai/tap/tfcode
|
brew install toothfairyai/tap/tfcode
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The postinstall script will automatically install the ToothFairyAI Python SDK.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -383,17 +401,27 @@ tfcode tools test <name> # Test tool call
|
|||||||
- [x] Document fork management strategy
|
- [x] Document fork management strategy
|
||||||
- [x] Push to development branch
|
- [x] Push to development branch
|
||||||
|
|
||||||
### Phase 2: Tool Sync ⏳ NEXT
|
### Phase 2: Tool Sync ✅ COMPLETE
|
||||||
|
|
||||||
**Tasks**:
|
**Completed**:
|
||||||
- [ ] Complete tf-sync Python module
|
- [x] Complete tf-sync Python module
|
||||||
- [ ] Test tool sync with real TF workspace
|
- [x] Tool sync implementation
|
||||||
- [ ] Handle tool metadata caching
|
- [x] Tool metadata caching in tfcode
|
||||||
|
- [x] Implement tfcode CLI commands
|
||||||
|
- [x] `tfcode validate` - credential validation
|
||||||
|
- [x] `tfcode sync` - sync tools from TF workspace
|
||||||
|
- [x] `tfcode tools list` - list synced tools
|
||||||
|
- [x] `tfcode tools list --type mcp|skill|database|function`
|
||||||
|
- [x] `tfcode tools credentials <name> --set/--show`
|
||||||
|
- [x] `tfcode tools debug <name>`
|
||||||
|
- [x] Handle API functions with user credentials
|
||||||
|
- [x] Tool cache persistence (~/.tfcode/tools.json)
|
||||||
|
|
||||||
|
**Pending (Phase 3)**:
|
||||||
|
- [ ] Test tool sync with real TF workspace
|
||||||
- [ ] Build tf-mcp-bridge TypeScript module
|
- [ ] Build tf-mcp-bridge TypeScript module
|
||||||
- [ ] Bridge between tf-sync and tfcode core
|
- [ ] MCP proxy client implementation
|
||||||
- [ ] MCP proxy client implementation
|
- [ ] Tool refresh/reload
|
||||||
- [ ] Handle API functions with user credentials
|
|
||||||
- [ ] Implement tool refresh/reload
|
|
||||||
|
|
||||||
### Phase 3: TF Proxy Integration
|
### Phase 3: TF Proxy Integration
|
||||||
|
|
||||||
@@ -407,11 +435,14 @@ tfcode tools test <name> # Test tool call
|
|||||||
|
|
||||||
### Phase 4: CLI & UX
|
### Phase 4: CLI & UX
|
||||||
|
|
||||||
|
**Completed**:
|
||||||
|
- [x] Implement tfcode CLI commands
|
||||||
|
- [x] `tfcode validate`
|
||||||
|
- [x] `tfcode sync`
|
||||||
|
- [x] `tfcode tools list`
|
||||||
|
- [x] `tfcode tools credentials`
|
||||||
|
|
||||||
**Tasks**:
|
**Tasks**:
|
||||||
- [ ] Implement tfcode CLI commands
|
|
||||||
- [ ] `tfcode validate`
|
|
||||||
- [ ] `tfcode sync`
|
|
||||||
- [ ] `tfcode tools list`
|
|
||||||
- [ ] Build interactive credential setup
|
- [ ] Build interactive credential setup
|
||||||
- [ ] Add tool status reporting
|
- [ ] Add tool status reporting
|
||||||
- [ ] Create user-friendly error messages
|
- [ ] Create user-friendly error messages
|
||||||
@@ -490,16 +521,16 @@ abdfa7330 feat: initialize tfcode project structure
|
|||||||
**What's Ready**:
|
**What's Ready**:
|
||||||
- ✅ Clean codebase with minimal branding
|
- ✅ Clean codebase with minimal branding
|
||||||
- ✅ TF SDK integration layer (Python)
|
- ✅ TF SDK integration layer (Python)
|
||||||
- ✅ Tool sync module structure
|
- ✅ Tool sync module complete
|
||||||
|
- ✅ CLI commands implemented (`validate`, `sync`, `tools`)
|
||||||
- ✅ Multi-region support
|
- ✅ Multi-region support
|
||||||
- ✅ Config schema defined
|
- ✅ Config schema defined
|
||||||
- ✅ Documentation structure
|
- ✅ Documentation structure
|
||||||
|
|
||||||
**What's Next**:
|
**What's Next**:
|
||||||
- Complete tf-sync Python module
|
|
||||||
- Build tf-mcp-bridge TypeScript module
|
|
||||||
- Test with real TF workspace
|
- Test with real TF workspace
|
||||||
- Implement CLI commands
|
- Build tf-mcp-bridge TypeScript module
|
||||||
|
- Implement TF Proxy client for tool call routing
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
115
docs/sync-implementation.md
Normal file
115
docs/sync-implementation.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# tfcode Sync Implementation Summary
|
||||||
|
|
||||||
|
## What's Implemented
|
||||||
|
|
||||||
|
### 1. Postinstall Script (`packages/tfcode/scripts/postinstall.cjs`)
|
||||||
|
- Checks for Python 3.10+ installation
|
||||||
|
- Auto-installs ToothFairyAI Python SDK on `npm install tfcode`
|
||||||
|
- Handles externally-managed environments (Homebrew, etc.)
|
||||||
|
- Shows clear error messages if Python is missing
|
||||||
|
|
||||||
|
### 2. Python Sync Module (`packages/tf-sync/src/tf_sync/`)
|
||||||
|
- `config.py` - Configuration management, credential validation, multi-region support
|
||||||
|
- `tools.py` - Sync API functions from TF workspace
|
||||||
|
- `mcp.py` - Reserved for future MCP support (not in SDK yet)
|
||||||
|
|
||||||
|
### 3. CLI Commands (`packages/tfcode/src/cli/cmd/tools.ts`)
|
||||||
|
- `tfcode validate` - Validate TF credentials
|
||||||
|
- `tfcode sync` - Sync tools from workspace
|
||||||
|
- `tfcode tools list [--type]` - List synced tools
|
||||||
|
- `tfcode tools credentials <name> --set/--show` - Manage API keys
|
||||||
|
- `tfcode tools debug <name>` - Debug tool configuration
|
||||||
|
|
||||||
|
### 4. Region Support
|
||||||
|
| Region | Base URL | Use Case |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| `dev` | api.toothfairylab.link | Development |
|
||||||
|
| `au` | api.toothfairyai.com | Australia (Default) |
|
||||||
|
| `eu` | api.eu.toothfairyai.com | Europe |
|
||||||
|
| `us` | api.us.toothfairyai.com | United States |
|
||||||
|
|
||||||
|
## Test Results
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ python3 scripts/test-sync.py
|
||||||
|
|
||||||
|
============================================================
|
||||||
|
tfcode Sync Test
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
Test 1: Load Configuration
|
||||||
|
----------------------------------------
|
||||||
|
✓ Workspace ID: 6586b7e6-683e-4ee6-a6cf-24c19729b5ff
|
||||||
|
✓ Region: dev
|
||||||
|
✓ API URL: https://api.toothfairylab.link
|
||||||
|
|
||||||
|
Test 2: Validate Credentials
|
||||||
|
----------------------------------------
|
||||||
|
✓ Credentials valid
|
||||||
|
Workspace: Connected
|
||||||
|
ID: 6586b7e6-683e-4ee6-a6cf-24c19729b5ff
|
||||||
|
|
||||||
|
Test 3: Sync Tools
|
||||||
|
----------------------------------------
|
||||||
|
✓ Synced 100 tools
|
||||||
|
|
||||||
|
By type:
|
||||||
|
api_function: 100
|
||||||
|
|
||||||
|
Sample tools:
|
||||||
|
🌐 get_kanban_projects (api_function)
|
||||||
|
🌐 azure_costs_test_official (api_function)
|
||||||
|
... and 95 more
|
||||||
|
```
|
||||||
|
|
||||||
|
## SDK Capabilities (Current)
|
||||||
|
|
||||||
|
The ToothFairyAI Python SDK currently exposes:
|
||||||
|
- `agent_functions` - API Functions with `request_type` (get/post/put/delete/etc.)
|
||||||
|
- `connections` - Provider connections (openai, anthropic, groq, etc.)
|
||||||
|
- `agents` - TF workspace agents
|
||||||
|
|
||||||
|
**Not yet exposed:**
|
||||||
|
- MCP servers (`isMCPServer`)
|
||||||
|
- Agent skills (`isAgentSkill`)
|
||||||
|
- Database scripts (`isDatabaseScript`)
|
||||||
|
|
||||||
|
These will be added to the SDK in the future. For now, MCP servers must be configured manually in `tfcode.json`.
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TF_WORKSPACE_ID="your-workspace-id"
|
||||||
|
export TF_API_KEY="your-api-key"
|
||||||
|
export TF_REGION="dev" # or au, eu, us
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install
|
||||||
|
npm install -g tfcode
|
||||||
|
|
||||||
|
# Or with bun
|
||||||
|
cd packages/tf-sync && python3 -m pip install -e . --break-system-packages
|
||||||
|
cd packages/tfcode && bun install
|
||||||
|
|
||||||
|
# Validate
|
||||||
|
tfcode validate
|
||||||
|
|
||||||
|
# Sync
|
||||||
|
tfcode sync
|
||||||
|
|
||||||
|
# List tools
|
||||||
|
tfcode tools list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `packages/tfcode/scripts/postinstall.cjs` - Postinstall script
|
||||||
|
- `packages/tfcode/src/cli/cmd/tools.ts` - CLI commands
|
||||||
|
- `packages/tf-sync/src/tf_sync/config.py` - Config + regions
|
||||||
|
- `packages/tf-sync/src/tf_sync/tools.py` - Tool sync
|
||||||
|
- `packages/tf-sync/src/tf_sync/mcp.py` - MCP placeholder
|
||||||
|
- `scripts/test-sync.py` - Test script
|
||||||
|
- `scripts/setup-tf-dev.sh` - Dev environment setup
|
||||||
129
docs/testing-sync.md
Normal file
129
docs/testing-sync.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# Step-by-Step Guide: Testing tfcode Sync
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Python 3.10+ with `toothfairyai` SDK installed
|
||||||
|
2. Node.js/Bun for running tfcode
|
||||||
|
|
||||||
|
## Step 1: Set Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In your terminal
|
||||||
|
export TF_WORKSPACE_ID="6586b7e6-683e-4ee6-a6cf-24c19729b5ff"
|
||||||
|
export TF_API_KEY="EWZooLROIS57EVW3BKGu7Pv6LNe4D6m4gkDjukx3"
|
||||||
|
export TF_REGION="au"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the setup script:
|
||||||
|
```bash
|
||||||
|
source scripts/setup-tf-dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Install Python Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/tf-sync
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
# Or manually install the required packages:
|
||||||
|
pip install toothfairyai pydantic httpx rich
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3: Build tfcode CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/tfcode
|
||||||
|
bun install
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Test Credential Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From repo root with environment set
|
||||||
|
cd packages/tfcode
|
||||||
|
bun run src/index.ts validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
```
|
||||||
|
Validating ToothFairyAI credentials...
|
||||||
|
✓ Credentials valid
|
||||||
|
Workspace: <workspace_name>
|
||||||
|
ID: 6586b7e6-683e-4ee6-a6cf-24c19729b5ff
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5: Sync Tools from Workspace
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run src/index.ts sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
```
|
||||||
|
Syncing tools from ToothFairyAI workspace...
|
||||||
|
✓ Synced X tools
|
||||||
|
|
||||||
|
By type:
|
||||||
|
mcp_server: X
|
||||||
|
agent_skill: X
|
||||||
|
database_script: X
|
||||||
|
api_function: X
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 6: List Synced Tools
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all tools
|
||||||
|
bun run src/index.ts tools list
|
||||||
|
|
||||||
|
# Filter by type
|
||||||
|
bun run src/index.ts tools list --type mcp
|
||||||
|
bun run src/index.ts tools list --type skill
|
||||||
|
bun run src/index.ts tools list --type database
|
||||||
|
bun run src/index.ts tools list --type function
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 7: Debug a Tool
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run src/index.ts tools debug <tool-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 8: Set API Function Credentials (if needed)
|
||||||
|
|
||||||
|
For tools with `auth_via: user_provided`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run src/index.ts tools credentials <tool-name> --set
|
||||||
|
# Enter API key when prompted
|
||||||
|
|
||||||
|
bun run src/index.ts tools credentials <tool-name> --show
|
||||||
|
# Shows masked key
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Python SDK not found
|
||||||
|
```
|
||||||
|
Error: Failed to validate: Python sync failed: ModuleNotFoundError: No module named 'toothfairyai'
|
||||||
|
```
|
||||||
|
Solution: `pip install toothfairyai`
|
||||||
|
|
||||||
|
### Environment not set
|
||||||
|
```
|
||||||
|
Error: TF_WORKSPACE_ID not set
|
||||||
|
```
|
||||||
|
Solution: Export environment variables or source the setup script
|
||||||
|
|
||||||
|
### Invalid credentials
|
||||||
|
```
|
||||||
|
✗ Validation failed: Invalid API key
|
||||||
|
```
|
||||||
|
Solution: Check your TF_API_KEY is correct
|
||||||
|
|
||||||
|
### Workspace not found
|
||||||
|
```
|
||||||
|
✗ Validation failed: Workspace not found
|
||||||
|
```
|
||||||
|
Solution: Check your TF_WORKSPACE_ID is correct
|
||||||
71
packages/tf-sync/README.md
Normal file
71
packages/tf-sync/README.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# tf-sync
|
||||||
|
|
||||||
|
ToothFairyAI workspace sync layer for tfcode.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This Python module syncs tools from a ToothFairyAI workspace to tfcode:
|
||||||
|
|
||||||
|
- **MCP Servers** - Tools with `isMCPServer=true`
|
||||||
|
- **Agent Skills** - Tools with `isAgentSkill=true`
|
||||||
|
- **Database Scripts** - Tools with `isDatabaseScript=true`
|
||||||
|
- **API Functions** - Tools with `requestType` set
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install tf-sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
from tf_sync import (
|
||||||
|
load_config,
|
||||||
|
validate_credentials,
|
||||||
|
sync_tools,
|
||||||
|
sync_mcp_servers,
|
||||||
|
ToolType,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load configuration from environment (TF_WORKSPACE_ID, TF_API_KEY, TF_REGION)
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
# Validate credentials
|
||||||
|
result = validate_credentials(config)
|
||||||
|
if result.success:
|
||||||
|
print(f"Connected to workspace: {result.workspace_name}")
|
||||||
|
|
||||||
|
# Sync all tools (API Functions)
|
||||||
|
result = sync_tools(config)
|
||||||
|
for tool in result.tools:
|
||||||
|
print(f"- {tool.name} ({tool.tool_type.value})")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Set these environment variables:
|
||||||
|
|
||||||
|
- `TF_WORKSPACE_ID` - Your ToothFairyAI workspace UUID
|
||||||
|
- `TF_API_KEY` - Your ToothFairyAI API key
|
||||||
|
- `TF_REGION` - Region: `dev`, `au` (default), `eu`, or `us`
|
||||||
|
|
||||||
|
## Region URLs
|
||||||
|
|
||||||
|
| Region | Base URL | Use Case |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| `dev` | api.toothfairylab.link | Development/Testing |
|
||||||
|
| `au` | api.toothfairyai.com | Australia (Default) |
|
||||||
|
| `eu` | api.eu.toothfairyai.com | Europe |
|
||||||
|
| `us` | api.us.toothfairyai.com | United States |
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- `toothfairyai` - Official ToothFairyAI Python SDK
|
||||||
|
- `pydantic` - Data validation
|
||||||
|
- `httpx` - HTTP client
|
||||||
|
- `rich` - Console output
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -9,7 +9,7 @@ description = "ToothFairyAI workspace sync layer for tfcode"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"toothfairyai>=1.0.0",
|
"toothfairyai>=0.5.0",
|
||||||
"pydantic>=2.0.0",
|
"pydantic>=2.0.0",
|
||||||
"httpx>=0.25.0",
|
"httpx>=0.25.0",
|
||||||
"rich>=13.0.0",
|
"rich>=13.0.0",
|
||||||
|
|||||||
Binary file not shown.
BIN
packages/tf-sync/src/tf_sync/__pycache__/agents.cpython-313.pyc
Normal file
BIN
packages/tf-sync/src/tf_sync/__pycache__/agents.cpython-313.pyc
Normal file
Binary file not shown.
BIN
packages/tf-sync/src/tf_sync/__pycache__/config.cpython-313.pyc
Normal file
BIN
packages/tf-sync/src/tf_sync/__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
packages/tf-sync/src/tf_sync/__pycache__/mcp.cpython-313.pyc
Normal file
BIN
packages/tf-sync/src/tf_sync/__pycache__/mcp.cpython-313.pyc
Normal file
Binary file not shown.
BIN
packages/tf-sync/src/tf_sync/__pycache__/tools.cpython-313.pyc
Normal file
BIN
packages/tf-sync/src/tf_sync/__pycache__/tools.cpython-313.pyc
Normal file
Binary file not shown.
@@ -13,6 +13,7 @@ from toothfairyai.errors import ToothFairyError
|
|||||||
|
|
||||||
|
|
||||||
class Region(str, Enum):
|
class Region(str, Enum):
|
||||||
|
DEV = "dev"
|
||||||
AU = "au"
|
AU = "au"
|
||||||
EU = "eu"
|
EU = "eu"
|
||||||
US = "us"
|
US = "us"
|
||||||
@@ -38,12 +39,19 @@ class FunctionRequestType(str, Enum):
|
|||||||
|
|
||||||
# Region-specific URL configurations
|
# Region-specific URL configurations
|
||||||
REGION_URLS = {
|
REGION_URLS = {
|
||||||
|
Region.DEV: {
|
||||||
|
"base_url": "https://api.toothfairylab.link",
|
||||||
|
"ai_url": "https://ai.toothfairylab.link",
|
||||||
|
"ai_stream_url": "https://ais.toothfairylab.link",
|
||||||
|
"mcp_url": "https://mcp.toothfairylab.link/sse",
|
||||||
|
"mcp_proxy_url": "https://mcp-proxy.toothfairylab.link",
|
||||||
|
},
|
||||||
Region.AU: {
|
Region.AU: {
|
||||||
"base_url": "https://api.au.toothfairyai.com",
|
"base_url": "https://api.toothfairyai.com",
|
||||||
"ai_url": "https://ai.au.toothfairyai.com",
|
"ai_url": "https://ai.toothfairyai.com",
|
||||||
"ai_stream_url": "https://ais.au.toothfairyai.com",
|
"ai_stream_url": "https://ais.toothfairyai.com",
|
||||||
"mcp_url": "https://mcp.au.toothfairyai.com/sse",
|
"mcp_url": "https://mcp.toothfairyai.com/sse",
|
||||||
"mcp_proxy_url": "https://mcp-proxy.au.toothfairyai.com",
|
"mcp_proxy_url": "https://mcp-proxy.toothfairyai.com",
|
||||||
},
|
},
|
||||||
Region.EU: {
|
Region.EU: {
|
||||||
"base_url": "https://api.eu.toothfairyai.com",
|
"base_url": "https://api.eu.toothfairyai.com",
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
"""
|
"""
|
||||||
MCP server sync module for tfcode.
|
MCP server sync module for tfcode.
|
||||||
Uses the official ToothFairyAI Python SDK.
|
|
||||||
|
NOTE: MCP servers are not currently exposed via the ToothFairyAI SDK.
|
||||||
|
This module is reserved for future implementation when MCP server
|
||||||
|
discovery is added to the SDK.
|
||||||
|
|
||||||
|
For now, MCP servers should be configured manually via tfcode.json.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from tf_sync.config import TFConfig
|
from tf_sync.config import TFConfig
|
||||||
from tf_sync.tools import SyncedTool, sync_tools, ToolType
|
from tf_sync.tools import SyncedTool, ToolType
|
||||||
|
|
||||||
|
|
||||||
class MCPServerSyncResult(BaseModel):
|
class MCPServerSyncResult(BaseModel):
|
||||||
@@ -21,34 +26,16 @@ def sync_mcp_servers(config: TFConfig) -> MCPServerSyncResult:
|
|||||||
"""
|
"""
|
||||||
Sync MCP servers from ToothFairyAI workspace.
|
Sync MCP servers from ToothFairyAI workspace.
|
||||||
|
|
||||||
MCP servers are tools with isMCPServer=true.
|
NOTE: Currently not supported. MCP servers are not exposed via the SDK.
|
||||||
Credentials stay in TF and are accessed via tf_proxy.
|
Configure MCP servers manually in tfcode.json instead.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: TFConfig instance
|
config: TFConfig instance
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
MCPServerSyncResult with synced MCP servers
|
MCPServerSyncResult with error message
|
||||||
"""
|
"""
|
||||||
result = sync_tools_by_type(config, [ToolType.MCP_SERVER])
|
|
||||||
|
|
||||||
if not result.success:
|
|
||||||
return MCPServerSyncResult(
|
|
||||||
success=False,
|
|
||||||
error=result.error,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get MCP servers from tools with isMCPServer
|
|
||||||
mcp_servers = [
|
|
||||||
t for t in result.tools
|
|
||||||
if t.is_mcp_server
|
|
||||||
]
|
|
||||||
|
|
||||||
return MCPServerSyncResult(
|
return MCPServerSyncResult(
|
||||||
success=True,
|
success=False,
|
||||||
servers=mcp_servers,
|
error="MCP server sync not available via SDK. Configure MCP servers in tfcode.json.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Import from tools module
|
|
||||||
from tf_sync.tools import sync_tools_by_type
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Tool sync module for tfcode.
|
Tool sync module for tfcode.
|
||||||
Syncs MCP servers, Agent Skills, Database Scripts, and API Functions from ToothFairyAI workspace.
|
Syncs tools from ToothFairyAI workspace using the official SDK.
|
||||||
Uses the official ToothFairyAI Python SDK.
|
|
||||||
|
SDK Structure:
|
||||||
|
- agent_functions: API Functions (with request_type)
|
||||||
|
- connections: Provider connections (openai, anthropic, etc.)
|
||||||
|
- agents: TF workspace agents
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
@@ -20,13 +24,10 @@ class SyncedTool(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
tool_type: ToolType
|
tool_type: ToolType
|
||||||
|
|
||||||
is_mcp_server: bool = False
|
|
||||||
is_agent_skill: bool = False
|
|
||||||
is_database_script: bool = False
|
|
||||||
|
|
||||||
request_type: Optional[FunctionRequestType] = None
|
request_type: Optional[FunctionRequestType] = None
|
||||||
url: Optional[str] = None
|
url: Optional[str] = None
|
||||||
tools: list[str] = []
|
|
||||||
|
authorisation_type: Optional[str] = None
|
||||||
|
|
||||||
auth_via: str = "tf_proxy"
|
auth_via: str = "tf_proxy"
|
||||||
|
|
||||||
@@ -40,60 +41,57 @@ class ToolSyncResult(BaseModel):
|
|||||||
error: Optional[str] = None
|
error: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
def classify_tool(tool: AgentFunction) -> ToolType:
|
def classify_tool(func: AgentFunction) -> ToolType:
|
||||||
"""
|
"""
|
||||||
Classify a tool based on its flags and fields.
|
Classify a tool based on its properties.
|
||||||
|
|
||||||
|
Currently the SDK exposes:
|
||||||
|
- agent_functions: API functions with request_type
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tool: AgentFunction from TF SDK
|
func: AgentFunction from TF SDK
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ToolType enum value
|
ToolType enum value
|
||||||
"""
|
"""
|
||||||
if tool.is_mcp_server:
|
# All agent_functions with request_type are API Functions
|
||||||
return ToolType.MCP_SERVER
|
if func.request_type:
|
||||||
if tool.is_agent_skill:
|
|
||||||
return ToolType.AGENT_SKILL
|
|
||||||
if tool.is_database_script:
|
|
||||||
return ToolType.DATABASE_SCRIPT
|
|
||||||
if tool.request_type:
|
|
||||||
return ToolType.API_FUNCTION
|
return ToolType.API_FUNCTION
|
||||||
|
|
||||||
return ToolType.API_FUNCTION
|
return ToolType.API_FUNCTION
|
||||||
|
|
||||||
|
|
||||||
def parse_tool(tool: AgentFunction) -> SyncedTool:
|
def parse_function(func: AgentFunction) -> SyncedTool:
|
||||||
"""
|
"""
|
||||||
Parse AgentFunction from SDK into SyncedTool.
|
Parse AgentFunction from SDK into SyncedTool.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tool: AgentFunction from TF SDK
|
func: AgentFunction from TF SDK
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SyncedTool instance
|
SyncedTool instance
|
||||||
"""
|
"""
|
||||||
tool_type = classify_tool(tool)
|
tool_type = classify_tool(func)
|
||||||
|
|
||||||
request_type_enum = None
|
request_type_enum = None
|
||||||
if tool.request_type:
|
if func.request_type:
|
||||||
try:
|
try:
|
||||||
request_type_enum = FunctionRequestType(tool.request_type)
|
request_type_enum = FunctionRequestType(func.request_type)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
auth_via = "user_provided" if tool_type == ToolType.API_FUNCTION else "tf_proxy"
|
# API Functions may have user-provided auth (authorisation_type)
|
||||||
|
# or may use TF proxy
|
||||||
|
auth_via = "user_provided" if func.authorisation_type == "api_key" else "tf_proxy"
|
||||||
|
|
||||||
return SyncedTool(
|
return SyncedTool(
|
||||||
id=tool.id,
|
id=func.id,
|
||||||
name=tool.name,
|
name=func.name,
|
||||||
description=tool.description,
|
description=func.description,
|
||||||
tool_type=tool_type,
|
tool_type=tool_type,
|
||||||
is_mcp_server=tool.is_mcp_server or False,
|
|
||||||
is_agent_skill=tool.is_agent_skill or False,
|
|
||||||
is_database_script=tool.is_database_script or False,
|
|
||||||
request_type=request_type_enum,
|
request_type=request_type_enum,
|
||||||
url=tool.url,
|
url=func.url,
|
||||||
tools=[],
|
authorisation_type=func.authorisation_type,
|
||||||
auth_via=auth_via,
|
auth_via=auth_via,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -112,7 +110,7 @@ def sync_tools(config: TFConfig) -> ToolSyncResult:
|
|||||||
client = config.get_client()
|
client = config.get_client()
|
||||||
result = client.agent_functions.list()
|
result = client.agent_functions.list()
|
||||||
|
|
||||||
tools = [parse_tool(f) for f in result.items]
|
tools = [parse_function(f) for f in result.items]
|
||||||
|
|
||||||
by_type = {}
|
by_type = {}
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
@@ -165,21 +163,6 @@ def sync_tools_by_type(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sync_mcp_servers_only(config: TFConfig) -> ToolSyncResult:
|
|
||||||
"""Sync only MCP servers (isMCPServer=true)."""
|
|
||||||
return sync_tools_by_type(config, [ToolType.MCP_SERVER])
|
|
||||||
|
|
||||||
|
|
||||||
def sync_agent_skills_only(config: TFConfig) -> ToolSyncResult:
|
|
||||||
"""Sync only Agent Skills (isAgentSkill=true)."""
|
|
||||||
return sync_tools_by_type(config, [ToolType.AGENT_SKILL])
|
|
||||||
|
|
||||||
|
|
||||||
def sync_database_scripts_only(config: TFConfig) -> ToolSyncResult:
|
|
||||||
"""Sync only Database Scripts (isDatabaseScript=true)."""
|
|
||||||
return sync_tools_by_type(config, [ToolType.DATABASE_SCRIPT])
|
|
||||||
|
|
||||||
|
|
||||||
def sync_api_functions_only(config: TFConfig) -> ToolSyncResult:
|
def sync_api_functions_only(config: TFConfig) -> ToolSyncResult:
|
||||||
"""Sync only API Functions (has requestType)."""
|
"""Sync only API Functions (has requestType)."""
|
||||||
return sync_tools_by_type(config, [ToolType.API_FUNCTION])
|
return sync_tools_by_type(config, [ToolType.API_FUNCTION])
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "effect-language-service patch || true",
|
"prepare": "effect-language-service patch || true",
|
||||||
|
"postinstall": "node scripts/postinstall.cjs",
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
"test": "bun test --timeout 30000",
|
"test": "bun test --timeout 30000",
|
||||||
"build": "bun run script/build.ts",
|
"build": "bun run script/build.ts",
|
||||||
|
|||||||
171
packages/tfcode/scripts/postinstall.cjs
Normal file
171
packages/tfcode/scripts/postinstall.cjs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { spawn, execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const RESET = '\x1b[0m';
|
||||||
|
const BOLD = '\x1b[1m';
|
||||||
|
const GREEN = '\x1b[32m';
|
||||||
|
const YELLOW = '\x1b[33m';
|
||||||
|
const RED = '\x1b[31m';
|
||||||
|
const CYAN = '\x1b[36m';
|
||||||
|
const DIM = '\x1b[90m';
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logSuccess(msg) {
|
||||||
|
console.log(`${GREEN}✓${RESET} ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logError(msg) {
|
||||||
|
console.error(`${RED}✗${RESET} ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logInfo(msg) {
|
||||||
|
console.log(`${CYAN}ℹ${RESET} ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logWarning(msg) {
|
||||||
|
console.log(`${YELLOW}!${RESET} ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPython() {
|
||||||
|
const commands = ['python3', 'python'];
|
||||||
|
|
||||||
|
for (const cmd of commands) {
|
||||||
|
try {
|
||||||
|
const result = execSync(`${cmd} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
||||||
|
const match = result.match(/Python (\d+)\.(\d+)/);
|
||||||
|
if (match) {
|
||||||
|
const major = parseInt(match[1]);
|
||||||
|
const minor = parseInt(match[2]);
|
||||||
|
if (major >= 3 && minor >= 10) {
|
||||||
|
return { cmd, version: result.trim() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPythonDeps(pythonCmd) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const packages = ['toothfairyai', 'pydantic', 'httpx', 'rich'];
|
||||||
|
|
||||||
|
log(`${DIM}Installing Python packages: ${packages.join(', ')}...${RESET}`);
|
||||||
|
|
||||||
|
// Try with --user first, then --break-system-packages if needed
|
||||||
|
const args = ['-m', 'pip', 'install', '--user', ...packages];
|
||||||
|
|
||||||
|
const proc = spawn(pythonCmd, args, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: process.platform === 'win32'
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
// Try with --break-system-packages flag
|
||||||
|
log(`${DIM}Retrying with --break-system-packages...${RESET}`);
|
||||||
|
const retryArgs = ['-m', 'pip', 'install', '--user', '--break-system-packages', ...packages];
|
||||||
|
const retry = spawn(pythonCmd, retryArgs, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: process.platform === 'win32'
|
||||||
|
});
|
||||||
|
|
||||||
|
retry.on('close', (retryCode) => {
|
||||||
|
if (retryCode === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`pip install exited with code ${retryCode}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
retry.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
log('');
|
||||||
|
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||||
|
log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`);
|
||||||
|
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||||
|
log('');
|
||||||
|
|
||||||
|
// Check for Python
|
||||||
|
logInfo('Checking Python installation...');
|
||||||
|
const python = checkPython();
|
||||||
|
|
||||||
|
if (!python) {
|
||||||
|
log('');
|
||||||
|
logError('Python 3.10+ is required but not found.');
|
||||||
|
log('');
|
||||||
|
log(`${BOLD}Please install Python 3.10 or later:${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(` ${CYAN}macOS:${RESET} brew install python@3.12`);
|
||||||
|
log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`);
|
||||||
|
log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`);
|
||||||
|
log('');
|
||||||
|
log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`);
|
||||||
|
log('');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
logSuccess(`Found ${python.version} (${python.cmd})`);
|
||||||
|
log('');
|
||||||
|
|
||||||
|
// Install Python dependencies
|
||||||
|
logInfo('Installing ToothFairyAI Python SDK...');
|
||||||
|
try {
|
||||||
|
await installPythonDeps(python.cmd);
|
||||||
|
logSuccess('Python dependencies installed');
|
||||||
|
} catch (err) {
|
||||||
|
logWarning(`Failed to install Python dependencies: ${err.message}`);
|
||||||
|
log('');
|
||||||
|
log(`${DIM}You can install them manually with:${RESET}`);
|
||||||
|
log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`);
|
||||||
|
log('');
|
||||||
|
// Don't exit - user might install manually later
|
||||||
|
}
|
||||||
|
|
||||||
|
log('');
|
||||||
|
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||||
|
log(`${GREEN}✓ tfcode installed successfully!${RESET}`);
|
||||||
|
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(`${BOLD}Quick Start:${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`);
|
||||||
|
log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`);
|
||||||
|
log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(` ${CYAN}2.${RESET} Validate your credentials:`);
|
||||||
|
log(` ${DIM}tfcode validate${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(` ${CYAN}3.${RESET} Sync tools from your workspace:`);
|
||||||
|
log(` ${DIM}tfcode sync${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(` ${CYAN}4.${RESET} Start coding:`);
|
||||||
|
log(` ${DIM}tfcode${RESET}`);
|
||||||
|
log('');
|
||||||
|
log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`);
|
||||||
|
log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
logError(`Installation failed: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
544
packages/tfcode/src/cli/cmd/tools.ts
Normal file
544
packages/tfcode/src/cli/cmd/tools.ts
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
import { cmd } from "@/cli/cmd/cmd"
|
||||||
|
import { UI } from "@/cli/ui"
|
||||||
|
import { Log } from "@/util/log"
|
||||||
|
import { spawn } from "child_process"
|
||||||
|
import { Filesystem } from "@/util/filesystem"
|
||||||
|
import { mkdir } from "fs/promises"
|
||||||
|
import { existsSync } from "fs"
|
||||||
|
import path from "path"
|
||||||
|
import { Global } from "@/global"
|
||||||
|
|
||||||
|
const println = (msg: string) => UI.println(msg)
|
||||||
|
const printError = (msg: string) => UI.error(msg)
|
||||||
|
const success = (msg: string) => UI.println(UI.Style.TEXT_SUCCESS_BOLD + msg + UI.Style.TEXT_NORMAL)
|
||||||
|
const info = (msg: string) => UI.println(UI.Style.TEXT_NORMAL + msg)
|
||||||
|
|
||||||
|
type ToolType = "mcp_server" | "agent_skill" | "database_script" | "api_function"
|
||||||
|
|
||||||
|
interface SyncedTool {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
tool_type: ToolType
|
||||||
|
is_mcp_server: boolean
|
||||||
|
is_agent_skill: boolean
|
||||||
|
is_database_script: boolean
|
||||||
|
request_type?: string
|
||||||
|
url?: string
|
||||||
|
tools: string[]
|
||||||
|
auth_via: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolSyncResult {
|
||||||
|
success: boolean
|
||||||
|
tools: SyncedTool[]
|
||||||
|
by_type: Record<string, number>
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CredentialValidationResult {
|
||||||
|
success: boolean
|
||||||
|
workspace_id?: string
|
||||||
|
workspace_name?: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TFCODE_CONFIG_DIR = ".tfcode"
|
||||||
|
const TFCODE_TOOLS_FILE = "tools.json"
|
||||||
|
|
||||||
|
function getPythonSyncPath(): string {
|
||||||
|
const possible = [
|
||||||
|
path.join(__dirname, "..", "..", "..", "..", "tf-sync", "src", "tf_sync"),
|
||||||
|
path.join(process.cwd(), "packages", "tf-sync", "src", "tf_sync"),
|
||||||
|
]
|
||||||
|
for (const p of possible) {
|
||||||
|
if (existsSync(p)) return p
|
||||||
|
}
|
||||||
|
return "tf_sync"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runPythonSync(
|
||||||
|
method: string,
|
||||||
|
args: Record<string, unknown> = {},
|
||||||
|
): Promise<unknown> {
|
||||||
|
const pythonCode = `
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
from tf_sync.config import load_config, validate_credentials
|
||||||
|
from tf_sync.tools import sync_tools, sync_tools_by_type, ToolType
|
||||||
|
from tf_sync.mcp import sync_mcp_servers
|
||||||
|
|
||||||
|
method = ${JSON.stringify(method)}
|
||||||
|
args = ${JSON.stringify(args)}
|
||||||
|
|
||||||
|
if method == "validate":
|
||||||
|
config = load_config()
|
||||||
|
result = validate_credentials(config)
|
||||||
|
print(json.dumps({
|
||||||
|
"success": result.success,
|
||||||
|
"workspace_id": result.workspace_id,
|
||||||
|
"workspace_name": result.workspace_name,
|
||||||
|
"error": result.error
|
||||||
|
}))
|
||||||
|
|
||||||
|
elif method == "sync":
|
||||||
|
config = load_config()
|
||||||
|
result = sync_tools(config)
|
||||||
|
|
||||||
|
tools_data = []
|
||||||
|
for tool in result.tools:
|
||||||
|
tools_data.append({
|
||||||
|
"id": tool.id,
|
||||||
|
"name": tool.name,
|
||||||
|
"description": tool.description,
|
||||||
|
"tool_type": tool.tool_type.value,
|
||||||
|
"is_mcp_server": tool.is_mcp_server,
|
||||||
|
"is_agent_skill": tool.is_agent_skill,
|
||||||
|
"is_database_script": tool.is_database_script,
|
||||||
|
"request_type": tool.request_type.value if tool.request_type else None,
|
||||||
|
"url": tool.url,
|
||||||
|
"tools": tool.tools,
|
||||||
|
"auth_via": tool.auth_via
|
||||||
|
})
|
||||||
|
|
||||||
|
print(json.dumps({
|
||||||
|
"success": result.success,
|
||||||
|
"tools": tools_data,
|
||||||
|
"by_type": result.by_type,
|
||||||
|
"error": result.error
|
||||||
|
}))
|
||||||
|
|
||||||
|
elif method == "sync_type":
|
||||||
|
tool_type_str = args.get("tool_type")
|
||||||
|
if tool_type_str:
|
||||||
|
tool_type_map = {
|
||||||
|
"mcp_server": ToolType.MCP_SERVER,
|
||||||
|
"agent_skill": ToolType.AGENT_SKILL,
|
||||||
|
"database_script": ToolType.DATABASE_SCRIPT,
|
||||||
|
"api_function": ToolType.API_FUNCTION
|
||||||
|
}
|
||||||
|
tool_type = tool_type_map.get(tool_type_str)
|
||||||
|
if tool_type:
|
||||||
|
config = load_config()
|
||||||
|
result = sync_tools_by_type(config, [tool_type])
|
||||||
|
|
||||||
|
tools_data = []
|
||||||
|
for tool in result.tools:
|
||||||
|
tools_data.append({
|
||||||
|
"id": tool.id,
|
||||||
|
"name": tool.name,
|
||||||
|
"description": tool.description,
|
||||||
|
"tool_type": tool.tool_type.value,
|
||||||
|
"is_mcp_server": tool.is_mcp_server,
|
||||||
|
"is_agent_skill": tool.is_agent_skill,
|
||||||
|
"is_database_script": tool.is_database_script,
|
||||||
|
"request_type": tool.request_type.value if tool.request_type else None,
|
||||||
|
"url": tool.url,
|
||||||
|
"tools": tool.tools,
|
||||||
|
"auth_via": tool.auth_via
|
||||||
|
})
|
||||||
|
|
||||||
|
print(json.dumps({
|
||||||
|
"success": result.success,
|
||||||
|
"tools": tools_data,
|
||||||
|
"by_type": result.by_type,
|
||||||
|
"error": result.error
|
||||||
|
}))
|
||||||
|
else:
|
||||||
|
print(json.dumps({"success": False, "error": "Missing tool_type argument"}))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(json.dumps({"success": False, "error": str(e)}))
|
||||||
|
sys.exit(0)
|
||||||
|
`
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const pythonPath = process.env.TFCODE_PYTHON_PATH || "python3"
|
||||||
|
const proc = spawn(pythonPath, ["-c", pythonCode], {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PYTHONPATH: getPythonSyncPath(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let stdout = ""
|
||||||
|
let stderr = ""
|
||||||
|
|
||||||
|
proc.stdout.on("data", (data) => {
|
||||||
|
stdout += data.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
proc.stderr.on("data", (data) => {
|
||||||
|
stderr += data.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
proc.on("close", (code) => {
|
||||||
|
if (code !== 0 && !stdout) {
|
||||||
|
reject(new Error(`Python sync failed: ${stderr}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = JSON.parse(stdout.trim())
|
||||||
|
resolve(result)
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error(`Failed to parse Python output: ${stdout}\nstderr: ${stderr}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
proc.on("error", (err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigPath(): string {
|
||||||
|
return path.join(Global.Path.data, TFCODE_CONFIG_DIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToolsFilePath(): string {
|
||||||
|
return path.join(getConfigPath(), TFCODE_TOOLS_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCachedTools(): Promise<ToolSyncResult | null> {
|
||||||
|
const toolsFile = getToolsFilePath()
|
||||||
|
if (!(await Filesystem.exists(toolsFile))) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await Bun.file(toolsFile).text()
|
||||||
|
return JSON.parse(content)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveToolsCache(result: ToolSyncResult): Promise<void> {
|
||||||
|
const configPath = getConfigPath()
|
||||||
|
await mkdir(configPath, { recursive: true })
|
||||||
|
await Bun.write(getToolsFilePath(), JSON.stringify(result, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
const ValidateCommand = cmd({
|
||||||
|
command: "validate",
|
||||||
|
describe: "validate ToothFairyAI credentials",
|
||||||
|
handler: async () => {
|
||||||
|
info("Validating ToothFairyAI credentials...")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = (await runPythonSync("validate")) as CredentialValidationResult
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
success("✓ Credentials valid")
|
||||||
|
if (result.workspace_name) {
|
||||||
|
info(` Workspace: ${result.workspace_name}`)
|
||||||
|
}
|
||||||
|
if (result.workspace_id) {
|
||||||
|
info(` ID: ${result.workspace_id}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printError(`✗ Validation failed: ${result.error || "Unknown error"}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printError(`Failed to validate: ${e instanceof Error ? e.message : String(e)}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const SyncCommand = cmd({
|
||||||
|
command: "sync",
|
||||||
|
describe: "sync tools from ToothFairyAI workspace",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.option("force", {
|
||||||
|
alias: "f",
|
||||||
|
type: "boolean",
|
||||||
|
describe: "force re-sync",
|
||||||
|
default: false,
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
info("Syncing tools from ToothFairyAI workspace...")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = (await runPythonSync("sync")) as ToolSyncResult
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
await saveToolsCache(result)
|
||||||
|
|
||||||
|
success(`✓ Synced ${result.tools.length} tools`)
|
||||||
|
|
||||||
|
if (result.by_type && Object.keys(result.by_type).length > 0) {
|
||||||
|
info("\nBy type:")
|
||||||
|
for (const [type, count] of Object.entries(result.by_type)) {
|
||||||
|
info(` ${type}: ${count}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printError(`✗ Sync failed: ${result.error || "Unknown error"}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printError(`Failed to sync: ${e instanceof Error ? e.message : String(e)}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToolsListCommand = cmd({
|
||||||
|
command: "list",
|
||||||
|
describe: "list synced tools",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.option("type", {
|
||||||
|
type: "string",
|
||||||
|
choices: ["mcp", "skill", "database", "function"] as const,
|
||||||
|
describe: "filter by tool type",
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
const cached = await loadCachedTools()
|
||||||
|
|
||||||
|
if (!cached || !cached.success) {
|
||||||
|
printError("No tools synced. Run 'tfcode sync' first.")
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tools = cached.tools
|
||||||
|
|
||||||
|
if (args.type) {
|
||||||
|
const typeMap: Record<string, ToolType> = {
|
||||||
|
mcp: "mcp_server",
|
||||||
|
skill: "agent_skill",
|
||||||
|
database: "database_script",
|
||||||
|
function: "api_function",
|
||||||
|
}
|
||||||
|
const targetType = typeMap[args.type]
|
||||||
|
tools = tools.filter((t) => t.tool_type === targetType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tools.length === 0) {
|
||||||
|
info("No tools found.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info(`\n${tools.length} tool(s):\n`)
|
||||||
|
|
||||||
|
for (const tool of tools) {
|
||||||
|
const typeLabel = {
|
||||||
|
mcp_server: "MCP",
|
||||||
|
agent_skill: "Skill",
|
||||||
|
database_script: "DB",
|
||||||
|
api_function: "API",
|
||||||
|
}[tool.tool_type]
|
||||||
|
|
||||||
|
info(` ${tool.name}`)
|
||||||
|
info(` Type: ${typeLabel}`)
|
||||||
|
if (tool.description) {
|
||||||
|
info(` Description: ${tool.description}`)
|
||||||
|
}
|
||||||
|
if (tool.url) {
|
||||||
|
info(` URL: ${tool.url}`)
|
||||||
|
}
|
||||||
|
info(` Auth: ${tool.auth_via}`)
|
||||||
|
info("")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToolsCredentialsSetCommand = cmd({
|
||||||
|
command: "credentials <name>",
|
||||||
|
describe: "manage tool credentials",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs
|
||||||
|
.positional("name", {
|
||||||
|
type: "string",
|
||||||
|
describe: "tool name",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("set", {
|
||||||
|
type: "boolean",
|
||||||
|
describe: "set credential",
|
||||||
|
})
|
||||||
|
.option("show", {
|
||||||
|
type: "boolean",
|
||||||
|
describe: "show stored credential",
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
const toolName = args.name as string
|
||||||
|
|
||||||
|
const cached = await loadCachedTools()
|
||||||
|
if (!cached || !cached.success) {
|
||||||
|
printError("No tools synced. Run 'tfcode sync' first.")
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = cached.tools.find((t) => t.name === toolName)
|
||||||
|
if (!tool) {
|
||||||
|
printError(`Tool '${toolName}' not found.`)
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool.auth_via !== "user_provided") {
|
||||||
|
printError(`Tool '${toolName}' uses tf_proxy authentication. Credentials are managed by ToothFairyAI.`)
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentialsFile = path.join(getConfigPath(), "credentials.json")
|
||||||
|
let credentials: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (await Filesystem.exists(credentialsFile)) {
|
||||||
|
try {
|
||||||
|
credentials = await Bun.file(credentialsFile).json()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.show) {
|
||||||
|
const cred = credentials[toolName]
|
||||||
|
if (cred) {
|
||||||
|
info(`${toolName}: ${cred.substring(0, 8)}...${cred.substring(cred.length - 4)}`)
|
||||||
|
} else {
|
||||||
|
info(`No credential stored for '${toolName}'`)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.set) {
|
||||||
|
const { default: prompts } = await import("@clack/prompts")
|
||||||
|
const value = await prompts.password({
|
||||||
|
message: `Enter API key for '${toolName}'`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (prompts.isCancel(value)) {
|
||||||
|
printError("Cancelled")
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials[toolName] = value as string
|
||||||
|
await mkdir(getConfigPath(), { recursive: true })
|
||||||
|
await Bun.write(credentialsFile, JSON.stringify(credentials, null, 2))
|
||||||
|
success(`✓ Credential saved for '${toolName}'`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
printError("Use --set or --show")
|
||||||
|
process.exitCode = 1
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToolsCommand = cmd({
|
||||||
|
command: "tools",
|
||||||
|
describe: "manage ToothFairyAI tools",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).demandCommand(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToolsDebugCommand = cmd({
|
||||||
|
command: "debug <name>",
|
||||||
|
describe: "debug tool connection",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.positional("name", {
|
||||||
|
type: "string",
|
||||||
|
describe: "tool name",
|
||||||
|
demandOption: true,
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
const toolName = args.name as string
|
||||||
|
|
||||||
|
const cached = await loadCachedTools()
|
||||||
|
if (!cached || !cached.success) {
|
||||||
|
printError("No tools synced. Run 'tfcode sync' first.")
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = cached.tools.find((t) => t.name === toolName)
|
||||||
|
if (!tool) {
|
||||||
|
printError(`Tool '${toolName}' not found.`)
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info(`\nTool: ${tool.name}`)
|
||||||
|
info(`Type: ${tool.tool_type}`)
|
||||||
|
info(`Auth: ${tool.auth_via}`)
|
||||||
|
|
||||||
|
if (tool.url) {
|
||||||
|
info(`URL: ${tool.url}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool.request_type) {
|
||||||
|
info(`Request Type: ${tool.request_type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
info("\nChecking configuration...")
|
||||||
|
|
||||||
|
const configPath = getConfigPath()
|
||||||
|
info(`Config dir: ${configPath}`)
|
||||||
|
|
||||||
|
const toolsFile = getToolsFilePath()
|
||||||
|
info(`Tools cache: ${toolsFile}`)
|
||||||
|
info(` Exists: ${await Filesystem.exists(toolsFile)}`)
|
||||||
|
|
||||||
|
if (tool.auth_via === "user_provided") {
|
||||||
|
const credentialsFile = path.join(configPath, "credentials.json")
|
||||||
|
info(`Credentials file: ${credentialsFile}`)
|
||||||
|
info(` Exists: ${await Filesystem.exists(credentialsFile)}`)
|
||||||
|
|
||||||
|
if (await Filesystem.exists(credentialsFile)) {
|
||||||
|
const credentials = await Bun.file(credentialsFile).json()
|
||||||
|
info(` Has credential for '${toolName}': ${!!credentials[toolName]}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToolsTestCommand = cmd({
|
||||||
|
command: "test <name>",
|
||||||
|
describe: "test tool call",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.positional("name", {
|
||||||
|
type: "string",
|
||||||
|
describe: "tool name",
|
||||||
|
demandOption: true,
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
const toolName = args.name as string
|
||||||
|
|
||||||
|
const cached = await loadCachedTools()
|
||||||
|
if (!cached || !cached.success) {
|
||||||
|
printError("No tools synced. Run 'tfcode sync' first.")
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = cached.tools.find((t) => t.name === toolName)
|
||||||
|
if (!tool) {
|
||||||
|
printError(`Tool '${toolName}' not found.`)
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info(`Testing tool '${toolName}'...`)
|
||||||
|
info(`This feature is not yet implemented.`)
|
||||||
|
info(`Tool type: ${tool.tool_type}`)
|
||||||
|
info(`Authentication: ${tool.auth_via}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ToolsMainCommand = cmd({
|
||||||
|
command: "tools",
|
||||||
|
describe: "manage ToothFairyAI tools",
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).command(ToolsDebugCommand).command(ToolsTestCommand).demandCommand(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export { ValidateCommand, SyncCommand, ToolsCommand }
|
||||||
@@ -30,6 +30,7 @@ import { WebCommand } from "./cli/cmd/web"
|
|||||||
import { PrCommand } from "./cli/cmd/pr"
|
import { PrCommand } from "./cli/cmd/pr"
|
||||||
import { SessionCommand } from "./cli/cmd/session"
|
import { SessionCommand } from "./cli/cmd/session"
|
||||||
import { DbCommand } from "./cli/cmd/db"
|
import { DbCommand } from "./cli/cmd/db"
|
||||||
|
import { ValidateCommand, SyncCommand, ToolsMainCommand } from "./cli/cmd/tools"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "./global"
|
import { Global } from "./global"
|
||||||
import { JsonMigration } from "./storage/json-migration"
|
import { JsonMigration } from "./storage/json-migration"
|
||||||
@@ -49,7 +50,7 @@ process.on("uncaughtException", (e) => {
|
|||||||
|
|
||||||
let cli = yargs(hideBin(process.argv))
|
let cli = yargs(hideBin(process.argv))
|
||||||
.parserConfiguration({ "populate--": true })
|
.parserConfiguration({ "populate--": true })
|
||||||
.scriptName("opencode")
|
.scriptName("tfcode")
|
||||||
.wrap(100)
|
.wrap(100)
|
||||||
.help("help", "show help")
|
.help("help", "show help")
|
||||||
.alias("help", "h")
|
.alias("help", "h")
|
||||||
@@ -145,6 +146,9 @@ let cli = yargs(hideBin(process.argv))
|
|||||||
.command(PrCommand)
|
.command(PrCommand)
|
||||||
.command(SessionCommand)
|
.command(SessionCommand)
|
||||||
.command(DbCommand)
|
.command(DbCommand)
|
||||||
|
.command(ValidateCommand)
|
||||||
|
.command(SyncCommand)
|
||||||
|
.command(ToolsMainCommand)
|
||||||
|
|
||||||
if (Installation.isLocal()) {
|
if (Installation.isLocal()) {
|
||||||
cli = cli.command(WorkspaceServeCommand)
|
cli = cli.command(WorkspaceServeCommand)
|
||||||
|
|||||||
15
scripts/setup-tf-dev.sh
Normal file
15
scripts/setup-tf-dev.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ToothFairyAI dev environment setup for tfcode
|
||||||
|
|
||||||
|
export TF_WORKSPACE_ID="6586b7e6-683e-4ee6-a6cf-24c19729b5ff"
|
||||||
|
export TF_API_KEY="EWZooLROIS57EVW3BKGu7Pv6LNe4D6m4gkDjukx3"
|
||||||
|
export TF_REGION="dev"
|
||||||
|
|
||||||
|
echo "ToothFairyAI environment configured:"
|
||||||
|
echo " Workspace: $TF_WORKSPACE_ID"
|
||||||
|
echo " Region: $TF_REGION"
|
||||||
|
echo ""
|
||||||
|
echo "Run tfcode commands:"
|
||||||
|
echo " tfcode validate - Test credentials"
|
||||||
|
echo " tfcode sync - Sync tools from workspace"
|
||||||
|
echo " tfcode tools list - List synced tools"
|
||||||
136
scripts/test-sync.py
Normal file
136
scripts/test-sync.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Quick test script for tfcode sync functionality.
|
||||||
|
Tests credential validation and tool sync directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Set environment for testing
|
||||||
|
os.environ["TF_WORKSPACE_ID"] = "6586b7e6-683e-4ee6-a6cf-24c19729b5ff"
|
||||||
|
os.environ["TF_API_KEY"] = "EWZooLROIS57EVW3BKGu7Pv6LNe4D6m4gkDjukx3"
|
||||||
|
os.environ["TF_REGION"] = "dev"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("tfcode Sync Test")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 1: Load config
|
||||||
|
print("Test 1: Load Configuration")
|
||||||
|
print("-" * 40)
|
||||||
|
try:
|
||||||
|
from tf_sync.config import load_config, get_region_urls, Region
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
print(f"✓ Workspace ID: {config.workspace_id}")
|
||||||
|
print(f"✓ Region: {config.region.value}")
|
||||||
|
|
||||||
|
urls = get_region_urls(config.region)
|
||||||
|
print(f"✓ API URL: {urls['base_url']}")
|
||||||
|
print(f"✓ MCP Proxy URL: {urls['mcp_proxy_url']}")
|
||||||
|
print()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed: {e}")
|
||||||
|
print()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Test 2: Validate credentials
|
||||||
|
print("Test 2: Validate Credentials")
|
||||||
|
print("-" * 40)
|
||||||
|
try:
|
||||||
|
from tf_sync.config import validate_credentials
|
||||||
|
|
||||||
|
result = validate_credentials(config)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
print("✓ Credentials valid")
|
||||||
|
if result.workspace_name:
|
||||||
|
print(f" Workspace: {result.workspace_name}")
|
||||||
|
if result.workspace_id:
|
||||||
|
print(f" ID: {result.workspace_id}")
|
||||||
|
else:
|
||||||
|
print(f"✗ Validation failed: {result.error}")
|
||||||
|
sys.exit(1)
|
||||||
|
print()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
print()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Test 3: Sync tools
|
||||||
|
print("Test 3: Sync Tools")
|
||||||
|
print("-" * 40)
|
||||||
|
try:
|
||||||
|
from tf_sync.tools import sync_tools, ToolType
|
||||||
|
|
||||||
|
result = sync_tools(config)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
print(f"✓ Synced {len(result.tools)} tools")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if result.by_type:
|
||||||
|
print("By type:")
|
||||||
|
for tool_type, count in result.by_type.items():
|
||||||
|
print(f" {tool_type}: {count}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Show first 5 tools
|
||||||
|
if result.tools:
|
||||||
|
print("Sample tools:")
|
||||||
|
for tool in result.tools[:5]:
|
||||||
|
type_emoji = {
|
||||||
|
ToolType.MCP_SERVER: "🔌",
|
||||||
|
ToolType.AGENT_SKILL: "🤖",
|
||||||
|
ToolType.DATABASE_SCRIPT: "🗄️",
|
||||||
|
ToolType.API_FUNCTION: "🌐",
|
||||||
|
}.get(tool.tool_type, "📦")
|
||||||
|
|
||||||
|
print(f" {type_emoji} {tool.name} ({tool.tool_type.value})")
|
||||||
|
if tool.description:
|
||||||
|
print(f" {tool.description[:60]}...")
|
||||||
|
print(f" Auth: {tool.auth_via}")
|
||||||
|
|
||||||
|
if len(result.tools) > 5:
|
||||||
|
print(f" ... and {len(result.tools) - 5} more")
|
||||||
|
else:
|
||||||
|
print(f"✗ Sync failed: {result.error}")
|
||||||
|
sys.exit(1)
|
||||||
|
print()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
print()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Test 4: Sync by type
|
||||||
|
print("Test 4: Sync MCP Servers Only")
|
||||||
|
print("-" * 40)
|
||||||
|
try:
|
||||||
|
from tf_sync.tools import sync_tools_by_type
|
||||||
|
|
||||||
|
result = sync_tools_by_type(config, [ToolType.MCP_SERVER])
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
print(f"✓ Found {len(result.tools)} MCP servers")
|
||||||
|
for tool in result.tools:
|
||||||
|
print(f" - {tool.name}")
|
||||||
|
else:
|
||||||
|
print(f"✗ Failed: {result.error}")
|
||||||
|
print()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed: {e}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("All tests completed!")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
60
scripts/test-sync.sh
Normal file
60
scripts/test-sync.sh
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# End-to-end test for tfcode sync
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "tfcode Sync End-to-End Test"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Step 1: Check Python
|
||||||
|
echo "Step 1: Checking Python environment..."
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "✗ Python 3 not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Python found: $(python3 --version)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Step 2: Install dependencies
|
||||||
|
echo "Step 2: Installing Python dependencies..."
|
||||||
|
cd packages/tf-sync
|
||||||
|
pip install -e . -q 2>/dev/null || pip install toothfairyai pydantic httpx rich -q
|
||||||
|
echo "✓ Dependencies installed"
|
||||||
|
cd ../..
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Step 3: Run Python test
|
||||||
|
echo "Step 3: Running Python sync test..."
|
||||||
|
python3 scripts/test-sync.py
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Step 4: Test CLI (if bun available)
|
||||||
|
if command -v bun &> /dev/null; then
|
||||||
|
echo "Step 4: Testing CLI commands..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
cd packages/tfcode
|
||||||
|
|
||||||
|
echo "4a. Testing validate command..."
|
||||||
|
bun run src/index.ts validate
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "4b. Testing sync command..."
|
||||||
|
bun run src/index.ts sync
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "4c. Testing tools list command..."
|
||||||
|
bun run src/index.ts tools list
|
||||||
|
echo
|
||||||
|
|
||||||
|
cd ../..
|
||||||
|
else
|
||||||
|
echo "Step 4: Skipping CLI test (bun not available)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo "All tests passed!"
|
||||||
|
echo "========================================"
|
||||||
Reference in New Issue
Block a user