From 4380efd65898877e2550d6e1879beba2bff60d4a Mon Sep 17 00:00:00 2001 From: Gab Date: Tue, 24 Mar 2026 14:01:32 +1100 Subject: [PATCH] feat: tfcode beta --- README.md | 27 +- docs/setup-guide.md | 311 ++++++++++++++++++++ packages/tfcode/README.md | 124 +++++++- packages/tfcode/bin/tfcode | 184 +----------- packages/tfcode/bin/tfcode.js | 333 ++++++++++++++++++++++ packages/tfcode/package-beta.json | 34 +++ packages/tfcode/package.json | 169 ++--------- packages/tfcode/scripts/quickstart.cjs | 66 +++++ packages/tfcode/src/cli/cmd/quickstart.ts | 53 ++++ packages/tfcode/src/index.ts | 2 + 10 files changed, 971 insertions(+), 332 deletions(-) create mode 100644 docs/setup-guide.md create mode 100644 packages/tfcode/bin/tfcode.js create mode 100644 packages/tfcode/package-beta.json create mode 100644 packages/tfcode/scripts/quickstart.cjs create mode 100644 packages/tfcode/src/cli/cmd/quickstart.ts diff --git a/README.md b/README.md index 3f5851cd8..14d712158 100644 --- a/README.md +++ b/README.md @@ -297,13 +297,10 @@ tf_code/ ```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 ``` +**Don't have Python?** See our [Setup Guide](./docs/setup-guide.md) for detailed instructions. + ### Install tfcode ```bash @@ -322,6 +319,26 @@ brew install toothfairyai/tap/tfcode The postinstall script will automatically install the ToothFairyAI Python SDK. +### First-Time Setup + +```bash +# Show quick start guide +tfcode quickstart + +# Set your credentials +export TF_WORKSPACE_ID="your-workspace-id" +export TF_API_KEY="your-api-key" +export TF_REGION="au" + +# Validate connection +tfcode validate + +# Sync tools +tfcode sync +``` + +See [Setup Guide](./docs/setup-guide.md) for detailed step-by-step instructions. + --- ## Quick Start diff --git a/docs/setup-guide.md b/docs/setup-guide.md new file mode 100644 index 000000000..3e5030ece --- /dev/null +++ b/docs/setup-guide.md @@ -0,0 +1,311 @@ +# tfcode Setup Guide for New Users + +**Time required**: ~10 minutes + +This guide will walk you through installing tfcode step by step. No technical experience needed. + +--- + +## What You'll Need + +1. **Python 3.10 or newer** (required for ToothFairyAI integration) +2. **Node.js** (required to run tfcode) +3. **Your ToothFairyAI credentials** (workspace ID and API key) + +--- + +## Step 1: Install Python + +### macOS + +1. Open **Terminal** (press `Cmd + Space`, type "Terminal", press Enter) +2. Copy and paste this command, then press Enter: + +```bash +brew install python@3.12 +``` + +If you don't have Homebrew, install it first: +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +### Windows + +1. Go to **https://python.org/downloads** +2. Click "Download Python 3.12" (or latest version) +3. Run the installer +4. **Important**: Check the box that says "Add Python to PATH" +5. Click "Install Now" + +### Linux (Ubuntu/Debian) + +Open Terminal and run: +```bash +sudo apt update +sudo apt install python3.12 python3-pip +``` + +### Verify Python Installation + +Open Terminal (macOS/Linux) or Command Prompt (Windows) and type: + +```bash +python3 --version +``` + +You should see something like: `Python 3.12.x` + +✅ If you see this, Python is installed correctly! + +--- + +## Step 2: Install Node.js + +### macOS + +```bash +brew install node +``` + +### Windows + +1. Go to **https://nodejs.org** +2. Download the "LTS" (Long Term Support) version +3. Run the installer with default settings + +### Linux (Ubuntu/Debian) + +```bash +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs +``` + +### Verify Node.js Installation + +```bash +node --version +``` + +You should see something like: `v20.x.x` + +✅ If you see this, Node.js is installed correctly! + +--- + +## Step 3: Install tfcode + +Open Terminal (macOS/Linux) or Command Prompt (Windows) and run: + +```bash +npm install -g tfcode +``` + +You'll see output like: +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + tfcode - ToothFairyAI's official coding agent +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +✓ Found Python 3.12.x (python3) +✓ Python dependencies installed + +✓ tfcode installed successfully! +``` + +✅ tfcode is now installed! + +--- + +## Step 4: Get Your ToothFairyAI Credentials + +1. Log in to your ToothFairyAI workspace at **https://app.toothfairyai.com** +2. Go to **Settings** → **API Keys** +3. Copy your **Workspace ID** and **API Key** + +You'll need these for the next step. + +--- + +## Step 5: Configure tfcode + +### macOS / Linux + +Open Terminal and run these commands (replace with your actual credentials): + +```bash +# Set your credentials +export TF_WORKSPACE_ID="your-workspace-id-here" +export TF_API_KEY="your-api-key-here" +export TF_REGION="au" + +# Make them permanent (so you don't have to set them every time) +echo 'export TF_WORKSPACE_ID="your-workspace-id-here"' >> ~/.zshrc +echo 'export TF_API_KEY="your-api-key-here"' >> ~/.zshrc +echo 'export TF_REGION="au"' >> ~/.zshrc +``` + +### Windows (Command Prompt) + +```cmd +setx TF_WORKSPACE_ID "your-workspace-id-here" +setx TF_API_KEY "your-api-key-here" +setx TF_REGION "au" +``` + +**Note**: Close and reopen Command Prompt after running these commands. + +### Region Options + +- `au` - Australia (default) +- `eu` - Europe +- `us` - United States +- `dev` - Development (for internal testing) + +--- + +## Step 6: Validate Your Setup + +Run this command to check everything is working: + +```bash +tfcode validate +``` + +You should see: +``` +Validating ToothFairyAI credentials... +✓ Credentials valid + Workspace: Your Workspace Name + ID: your-workspace-id +``` + +✅ If you see this, your credentials are correct! + +❌ If you see an error: +- Double-check your Workspace ID and API Key +- Make sure you copied them completely (no extra spaces) +- Try a different region (`au`, `eu`, or `us`) + +--- + +## Step 7: Sync Your Tools + +Run this command to sync tools from your ToothFairyAI workspace: + +```bash +tfcode sync +``` + +You should see: +``` +Syncing tools from ToothFairyAI workspace... +✓ Synced X tools + +By type: + api_function: X +``` + +--- + +## Step 8: View Your Tools + +List all synced tools: + +```bash +tfcode tools list +``` + +You'll see all the tools available from your workspace. + +--- + +## Step 9: Start Coding! + +Now you're ready to use tfcode: + +```bash +tfcode +``` + +This opens the tfcode terminal interface where you can: +- Chat with the AI +- Ask it to write code +- Use your synced tools + +--- + +## Quick Reference + +| Command | What it does | +|---------|--------------| +| `tfcode validate` | Check your credentials work | +| `tfcode sync` | Sync tools from your workspace | +| `tfcode tools list` | Show all your synced tools | +| `tfcode tools list --type mcp` | Show only MCP servers | +| `tfcode tools credentials --set` | Add an API key for a tool | +| `tfcode` | Start the coding assistant | + +--- + +## Troubleshooting + +### "Python 3.10+ is required but not found" + +**Solution**: Install Python (see Step 1) + +### "Failed to validate: Invalid API key" + +**Solution**: +1. Go to ToothFairyAI Settings → API Keys +2. Generate a new API key +3. Update your credentials: + - macOS/Linux: Edit `~/.zshrc` + - Windows: Run the `setx` commands again + +### "Failed to validate: Workspace not found" + +**Solution**: Check your Workspace ID is correct + +### "Failed to validate: Connection test failed" + +**Solution**: Try a different region: +- `au` - Australia +- `eu` - Europe +- `us` - United States + +Update with: +```bash +export TF_REGION="eu" # or us, au +``` + +### "command not found: tfcode" + +**Solution**: +1. Make sure you installed tfcode: `npm install -g tfcode` +2. Restart your terminal + +### "npm: command not found" + +**Solution**: Install Node.js (see Step 2) + +--- + +## Need Help? + +- **Documentation**: https://toothfairyai.com/developers/tfcode +- **Support**: Contact ToothFairyAI support +- **Issues**: Report bugs at https://github.com/ToothFairyAI/tfcode/issues + +--- + +## What's Next? + +1. **Read the docs**: https://toothfairyai.com/developers/tfcode +2. **Try a simple task**: `tfcode` then ask "Create a simple Node.js HTTP server" +3. **Configure MCP servers**: Add MCP servers to `~/.tfcode/tfcode.json` +4. **Set up API keys**: `tfcode tools credentials --set` + +--- + +*Last updated: 2026-03-24* \ No newline at end of file diff --git a/packages/tfcode/README.md b/packages/tfcode/README.md index 75890119c..bcadd8257 100644 --- a/packages/tfcode/README.md +++ b/packages/tfcode/README.md @@ -1,15 +1,127 @@ -# js +# tfcode -To install dependencies: +**ToothFairyAI's official AI coding agent** - A terminal-based coding assistant with seamless integration to your ToothFairyAI workspace. + +## Features + +- 🤖 **AI-Powered Coding** - Intelligent code generation, refactoring, and debugging +- 🔌 **Tool Integration** - Sync and use tools from your ToothFairyAI workspace +- 💻 **Terminal-Based** - Fast, lightweight, runs in your terminal +- 🔐 **Secure** - Credentials stay in ToothFairyAI, route via TF Proxy + +## Requirements + +- **Python 3.10+** - Required for ToothFairyAI integration +- **Node.js 18+** - Required to run tfcode + +## Installation ```bash -bun install +npm install -g tfcode ``` -To run: +The installer will: +- Check Python is installed +- Install the ToothFairyAI Python SDK automatically + +## Quick Start ```bash -bun run index.ts +# 1. Set your ToothFairyAI credentials +export TF_WORKSPACE_ID="your-workspace-id" +export TF_API_KEY="your-api-key" +export TF_REGION="au" + +# 2. Validate your credentials +tfcode validate + +# 3. Sync tools from your workspace +tfcode sync + +# 4. Start coding! +tfcode ``` -This project was created using `bun init` in bun v1.2.12. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. +## Commands + +| Command | Description | +|---------|-------------| +| `tfcode` | Start the AI coding assistant | +| `tfcode quickstart` | Show quick start guide | +| `tfcode validate` | Test your credentials | +| `tfcode sync` | Sync tools from workspace | +| `tfcode tools list` | List synced tools | +| `tfcode tools list --type mcp` | List MCP servers | +| `tfcode tools credentials --set` | Set API key for a tool | + +## Regions + +| Region | 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 | + +## Configuration + +Credentials can be set via environment variables: + +```bash +export TF_WORKSPACE_ID="your-workspace-id" +export TF_API_KEY="your-api-key" +export TF_REGION="au" +``` + +Or in `~/.tfcode/tfcode.json`: + +```json +{ + "toothfairy": { + "workspace_id": "your-workspace-id", + "api_key": "your-api-key", + "region": "au" + } +} +``` + +## Getting Your Credentials + +1. Log in to [ToothFairyAI](https://app.toothfairyai.com) +2. Go to **Settings** → **API Keys** +3. Copy your **Workspace ID** and **API Key** + +## Troubleshooting + +### "Python 3.10+ is required but not found" + +Install Python: +- **macOS**: `brew install python@3.12` +- **Windows**: Download from https://python.org/downloads +- **Linux**: `sudo apt install python3.12` + +### "Failed to validate: Invalid API key" + +- Check your API key is correct +- Generate a new key in ToothFairyAI Settings → API Keys + +### "Failed to validate: Connection test failed" + +Try a different region: +```bash +export TF_REGION="eu" # or us, au +``` + +## Documentation + +- **Full Guide**: https://toothfairyai.com/developers/tfcode +- **API Docs**: https://apidocs.toothfairyai.com +- **Issues**: https://github.com/ToothFairyAI/tfcode/issues + +## Credits + +tfcode is based on [opencode](https://github.com/anomalyco/opencode) by [anomalyco](https://github.com/anomalyco). + +## License + +MIT \ No newline at end of file diff --git a/packages/tfcode/bin/tfcode b/packages/tfcode/bin/tfcode index 561c8595d..ff76f1284 100755 --- a/packages/tfcode/bin/tfcode +++ b/packages/tfcode/bin/tfcode @@ -1,179 +1,17 @@ #!/usr/bin/env node -const childProcess = require("child_process") -const fs = require("fs") +const { spawn } = require("child_process") const path = require("path") -const os = require("os") -function run(target) { - const result = childProcess.spawnSync(target, process.argv.slice(2), { - stdio: "inherit", - }) - if (result.error) { - console.error(result.error.message) - process.exit(1) - } - const code = typeof result.status === "number" ? result.status : 0 - process.exit(code) -} +const scriptDir = __dirname +const srcPath = path.join(scriptDir, "..", "src", "index.ts") -const envPath = process.env.OPENCODE_BIN_PATH -if (envPath) { - run(envPath) -} +const child = spawn("npx", ["tsx", srcPath, ...process.argv.slice(2)], { + stdio: "inherit", + shell: process.platform === "win32", + cwd: scriptDir +}) -const scriptPath = fs.realpathSync(__filename) -const scriptDir = path.dirname(scriptPath) - -// -const cached = path.join(scriptDir, ".tfcode") -if (fs.existsSync(cached)) { - run(cached) -} - -const platformMap = { - darwin: "darwin", - linux: "linux", - win32: "windows", -} -const archMap = { - x64: "x64", - arm64: "arm64", - arm: "arm", -} - -let platform = platformMap[os.platform()] -if (!platform) { - platform = os.platform() -} -let arch = archMap[os.arch()] -if (!arch) { - arch = os.arch() -} -const base = "opencode-" + platform + "-" + arch -const binary = platform === "windows" ? "opencode.exe" : "opencode" - -function supportsAvx2() { - if (arch !== "x64") return false - - if (platform === "linux") { - try { - return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8")) - } catch { - return false - } - } - - if (platform === "darwin") { - try { - const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], { - encoding: "utf8", - timeout: 1500, - }) - if (result.status !== 0) return false - return (result.stdout || "").trim() === "1" - } catch { - return false - } - } - - if (platform === "windows") { - const cmd = - '(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)' - - for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) { - try { - const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], { - encoding: "utf8", - timeout: 3000, - windowsHide: true, - }) - if (result.status !== 0) continue - const out = (result.stdout || "").trim().toLowerCase() - if (out === "true" || out === "1") return true - if (out === "false" || out === "0") return false - } catch { - continue - } - } - - return false - } - - return false -} - -const names = (() => { - const avx2 = supportsAvx2() - const baseline = arch === "x64" && !avx2 - - if (platform === "linux") { - const musl = (() => { - try { - if (fs.existsSync("/etc/alpine-release")) return true - } catch { - // ignore - } - - try { - const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" }) - const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase() - if (text.includes("musl")) return true - } catch { - // ignore - } - - return false - })() - - if (musl) { - if (arch === "x64") { - if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base] - return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`] - } - return [`${base}-musl`, base] - } - - if (arch === "x64") { - if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`] - return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`] - } - return [base, `${base}-musl`] - } - - if (arch === "x64") { - if (baseline) return [`${base}-baseline`, base] - return [base, `${base}-baseline`] - } - return [base] -})() - -function findBinary(startDir) { - let current = startDir - for (;;) { - const modules = path.join(current, "node_modules") - if (fs.existsSync(modules)) { - for (const name of names) { - const candidate = path.join(modules, name, "bin", binary) - if (fs.existsSync(candidate)) return candidate - } - } - const parent = path.dirname(current) - if (parent === current) { - return - } - current = parent - } -} - -const resolved = findBinary(scriptDir) -if (!resolved) { - console.error( - "It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing " + - names.map((n) => `\"${n}\"`).join(" or ") + - " package", - ) - process.exit(1) -} - -run(resolved) +child.on("exit", (code) => { + process.exit(code || 0) +}) diff --git a/packages/tfcode/bin/tfcode.js b/packages/tfcode/bin/tfcode.js new file mode 100644 index 000000000..09bdcba1d --- /dev/null +++ b/packages/tfcode/bin/tfcode.js @@ -0,0 +1,333 @@ +#!/usr/bin/env node + +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import { spawn } from 'child_process'; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; +import { join } from 'path'; +import { homedir } from 'os'; + +const TFCODE_DIR = join(homedir(), '.tfcode'); +const TOOLS_FILE = join(TFCODE_DIR, 'tools.json'); +const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json'); + +const COLORS = { + reset: '\x1b[0m', + bold: '\x1b[1m', + green: '\x1b[32m', + red: '\x1b[31m', + cyan: '\x1b[36m', + dim: '\x1b[90m', + yellow: '\x1b[33m' +}; + +function log(msg) { + console.log(msg); +} + +function success(msg) { + console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`); +} + +function error(msg) { + console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`); +} + +function info(msg) { + console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`); +} + +function runPythonSync(method) { + const pythonCode = ` +import json +import sys +import os + +try: + from tf_sync.config import load_config, validate_credentials, Region + from tf_sync.tools import sync_tools + from tf_sync.config import get_region_urls + + method = "${method}" + + if method == "validate": + config = load_config() + result = validate_credentials(config) + urls = get_region_urls(config.region) + print(json.dumps({ + "success": result.success, + "workspace_id": result.workspace_id, + "workspace_name": result.workspace_name, + "error": result.error, + "base_url": urls["base_url"] + })) + + 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, + "request_type": tool.request_type.value if tool.request_type else None, + "url": tool.url, + "auth_via": tool.auth_via + }) + + print(json.dumps({ + "success": result.success, + "tools": tools_data, + "by_type": result.by_type, + "error": result.error + })) + +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: process.env.TFCODE_PYTHONPATH || '' + } + }); + + 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 ensureConfigDir() { + if (!existsSync(TFCODE_DIR)) { + mkdirSync(TFCODE_DIR, { recursive: true }); + } +} + +function loadCachedTools() { + if (!existsSync(TOOLS_FILE)) { + return null; + } + try { + return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8')); + } catch { + return null; + } +} + +function saveToolsCache(tools) { + ensureConfigDir(); + writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2)); +} + +const cli = yargs(hideBin(process.argv)) + .scriptName('tfcode') + .wrap(100) + .help() + .alias('help', 'h') + .command({ + command: 'quickstart', + describe: 'show quick start guide', + handler: () => { + log(''); + log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); + log(`${COLORS.bold} tfcode - Quick Start Guide${COLORS.reset}`); + log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); + log(''); + log('Welcome to tfcode! Follow these steps to get started:'); + log(''); + log(`${COLORS.cyan}STEP 1: Set Your Credentials${COLORS.reset}`); + log(`${COLORS.dim} export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`); + log(`${COLORS.dim} export TF_API_KEY="your-api-key"${COLORS.reset}`); + log(`${COLORS.dim} export TF_REGION="au"${COLORS.reset}`); + log(`${COLORS.dim} Regions: dev, au, eu, us${COLORS.reset}`); + log(''); + log(`${COLORS.cyan}STEP 2: Validate Connection${COLORS.reset}`); + log(`${COLORS.dim} tfcode validate${COLORS.reset}`); + log(''); + log(`${COLORS.cyan}STEP 3: Sync Your Tools${COLORS.reset}`); + log(`${COLORS.dim} tfcode sync${COLORS.reset}`); + log(''); + log(`${COLORS.cyan}STEP 4: Start Coding!${COLORS.reset}`); + log(`${COLORS.dim} tfcode${COLORS.reset}`); + log(''); + log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); + log(''); + log(' Useful Commands:'); + log(''); + log(' tfcode validate Test your credentials'); + log(' tfcode sync Sync tools from workspace'); + log(' tfcode tools list Show all your tools'); + log(''); + log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); + log(''); + log(' Need help? https://toothfairyai.com/developers/tfcode'); + log(''); + } + }) + .command({ + command: 'validate', + describe: 'validate ToothFairyAI credentials', + handler: async () => { + info('Validating ToothFairyAI credentials...'); + log(''); + + try { + const result = await runPythonSync('validate'); + + if (result.success) { + success('Credentials valid'); + if (result.base_url) { + log(`${COLORS.dim} API URL: ${result.base_url}${COLORS.reset}`); + } + if (result.workspace_id) { + log(`${COLORS.dim} Workspace ID: ${result.workspace_id}${COLORS.reset}`); + } + } else { + error(`Validation failed: ${result.error || 'Unknown error'}`); + log(''); + log(`${COLORS.dim}Check your credentials:${COLORS.reset}`); + log(`${COLORS.dim} TF_WORKSPACE_ID: ${process.env.TF_WORKSPACE_ID || 'not set'}${COLORS.reset}`); + log(`${COLORS.dim} TF_API_KEY: ${process.env.TF_API_KEY ? '***' + process.env.TF_API_KEY.slice(-4) : 'not set'}${COLORS.reset}`); + log(`${COLORS.dim} TF_REGION: ${process.env.TF_REGION || 'au (default)'}${COLORS.reset}`); + process.exit(1); + } + } catch (e) { + error(`Failed to validate: ${e.message}`); + log(''); + log(`${COLORS.dim}Make sure Python 3.10+ and the ToothFairyAI SDK are installed:${COLORS.reset}`); + log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`); + process.exit(1); + } + } + }) + .command({ + command: 'sync', + describe: 'sync tools from ToothFairyAI workspace', + handler: async () => { + info('Syncing tools from ToothFairyAI workspace...'); + log(''); + + try { + const result = await runPythonSync('sync'); + + if (result.success) { + saveToolsCache(result); + success(`Synced ${result.tools.length} tools`); + log(''); + + if (result.by_type && Object.keys(result.by_type).length > 0) { + log('By type:'); + for (const [type, count] of Object.entries(result.by_type)) { + log(` ${type}: ${count}`); + } + log(''); + } + } else { + error(`Sync failed: ${result.error || 'Unknown error'}`); + process.exit(1); + } + } catch (e) { + error(`Failed to sync: ${e.message}`); + process.exit(1); + } + } + }) + .command({ + command: 'tools', + describe: 'manage tools', + builder: (yargs) => { + return yargs + .command({ + command: 'list', + describe: 'list synced tools', + builder: (yargs) => { + return yargs.option('type', { + type: 'string', + describe: 'filter by type (api_function)' + }); + }, + handler: (args) => { + const cached = loadCachedTools(); + + if (!cached || !cached.success) { + error('No tools synced. Run \'tfcode sync\' first.'); + process.exit(1); + return; + } + + let tools = cached.tools; + + if (args.type) { + tools = tools.filter(t => t.tool_type === args.type); + } + + if (tools.length === 0) { + log('No tools found.'); + return; + } + + log(''); + log(`${tools.length} tool(s):`); + log(''); + + for (const tool of tools) { + log(` ${COLORS.cyan}${tool.name}${COLORS.reset}`); + log(` Type: ${tool.tool_type}`); + if (tool.description) { + log(` ${COLORS.dim}${tool.description.slice(0, 60)}${tool.description.length > 60 ? '...' : ''}${COLORS.reset}`); + } + log(` Auth: ${tool.auth_via}`); + log(''); + } + } + }) + .demandCommand(); + }, + handler: () => {} + }) + .demandCommand() + .strict() + .fail((msg, err) => { + if (msg) { + error(msg); + } + if (err) { + error(err.message); + } + process.exit(1); + }); + +cli.parse(); \ No newline at end of file diff --git a/packages/tfcode/package-beta.json b/packages/tfcode/package-beta.json new file mode 100644 index 000000000..5366c220d --- /dev/null +++ b/packages/tfcode/package-beta.json @@ -0,0 +1,34 @@ +{ + "name": "tfcode", + "version": "1.0.0-beta.1", + "description": "ToothFairyAI's official AI coding agent", + "keywords": ["toothfairyai", "ai", "coding", "cli"], + "author": "ToothFairyAI", + "license": "MIT", + "type": "module", + "bin": { + "tfcode": "./bin/tfcode.js" + }, + "files": [ + "bin/", + "src/", + "scripts/" + ], + "scripts": { + "postinstall": "node scripts/postinstall.cjs" + }, + "dependencies": { + "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/yargs": "^17.0.33" + }, + "engines": { + "node": ">=18" + }, + "homepage": "https://toothfairyai.com/developers/tfcode", + "repository": { + "type": "git", + "url": "https://github.com/ToothFairyAI/tfcode.git" + } +} \ No newline at end of file diff --git a/packages/tfcode/package.json b/packages/tfcode/package.json index daf18bdc4..fc5480ebc 100644 --- a/packages/tfcode/package.json +++ b/packages/tfcode/package.json @@ -1,157 +1,30 @@ { - "$schema": "https://json.schemastore.org/package.json", - "version": "1.3.0", - "name": "tfcode", - "type": "module", + "name": "@toothfairyai/tfcode", + "version": "1.0.0-beta.1", + "description": "ToothFairyAI's official AI coding agent", + "keywords": ["toothfairyai", "ai", "coding", "cli"], + "author": "ToothFairyAI", "license": "MIT", - "private": true, - "scripts": { - "prepare": "effect-language-service patch || true", - "postinstall": "node scripts/postinstall.cjs", - "typecheck": "tsgo --noEmit", - "test": "bun test --timeout 30000", - "build": "bun run script/build.ts", - "dev": "bun run --conditions=browser ./src/index.ts", - "random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'", - "clean": "echo 'Cleaning up...' && rm -rf node_modules dist", - "lint": "echo 'Running lint checks...' && bun test --coverage", - "format": "echo 'Formatting code...' && bun run --prettier --write src/**/*.ts", - "docs": "echo 'Generating documentation...' && find src -name '*.ts' -exec echo 'Processing: {}' \\;", - "deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'", - "db": "bun drizzle-kit" - }, + "type": "module", "bin": { - "tfcode": "./bin/tfcode" + "tfcode": "./bin/tfcode.js" }, - "randomField": "this-is-a-random-value-12345", - "exports": { - "./*": "./src/*.ts" - }, - "imports": { - "#db": { - "bun": "./src/storage/db.bun.ts", - "node": "./src/storage/db.node.ts", - "default": "./src/storage/db.bun.ts" - } - }, - "devDependencies": { - "@babel/core": "7.28.4", - "@effect/language-service": "0.79.0", - "@octokit/webhooks-types": "7.6.1", - "@opencode-ai/script": "workspace:*", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1", - "@standard-schema/spec": "1.0.0", - "@tsconfig/bun": "catalog:", - "@types/babel__core": "7.20.5", - "@types/bun": "catalog:", - "@types/cross-spawn": "6.0.6", - "@types/mime-types": "3.0.1", - "@types/semver": "^7.5.8", - "@types/turndown": "5.0.5", - "@types/which": "3.0.4", - "@types/yargs": "17.0.33", - "@typescript/native-preview": "catalog:", - "drizzle-kit": "catalog:", - "drizzle-orm": "catalog:", - "typescript": "catalog:", - "vscode-languageserver-types": "3.17.5", - "why-is-node-running": "3.2.2", - "zod-to-json-schema": "3.24.5" + "files": [ + "bin/", + "scripts/" + ], + "scripts": { + "postinstall": "node scripts/postinstall.cjs" }, "dependencies": { - "@actions/core": "1.11.1", - "@actions/github": "6.0.1", - "@agentclientprotocol/sdk": "0.14.1", - "@ai-sdk/amazon-bedrock": "3.0.82", - "@ai-sdk/anthropic": "2.0.65", - "@ai-sdk/azure": "2.0.91", - "@ai-sdk/cerebras": "1.0.36", - "@ai-sdk/cohere": "2.0.22", - "@ai-sdk/deepinfra": "1.0.36", - "@ai-sdk/gateway": "2.0.30", - "@ai-sdk/google": "2.0.54", - "@ai-sdk/google-vertex": "3.0.106", - "@ai-sdk/groq": "2.0.34", - "@ai-sdk/mistral": "2.0.27", - "@ai-sdk/openai": "2.0.89", - "@ai-sdk/openai-compatible": "1.0.32", - "@ai-sdk/perplexity": "2.0.23", - "@ai-sdk/provider": "2.0.1", - "@ai-sdk/provider-utils": "3.0.21", - "@ai-sdk/togetherai": "1.0.34", - "@ai-sdk/vercel": "1.0.33", - "@ai-sdk/xai": "2.0.51", - "@aws-sdk/credential-providers": "3.993.0", - "@clack/prompts": "1.0.0-alpha.1", - "@effect/platform-node": "catalog:", - "@hono/standard-validator": "0.1.5", - "@hono/zod-validator": "catalog:", - "@modelcontextprotocol/sdk": "1.25.2", - "@octokit/graphql": "9.0.2", - "@octokit/rest": "catalog:", - "@openauthjs/openauth": "catalog:", - "@opencode-ai/plugin": "workspace:*", - "@opencode-ai/script": "workspace:*", - "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/util": "workspace:*", - "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.90", - "@opentui/solid": "0.1.90", - "@parcel/watcher": "2.5.1", - "@pierre/diffs": "catalog:", - "@solid-primitives/event-bus": "1.1.2", - "@solid-primitives/scheduled": "1.5.2", - "@standard-schema/spec": "1.0.0", - "@zip.js/zip.js": "2.7.62", - "ai": "catalog:", - "ai-gateway-provider": "2.3.1", - "bonjour-service": "1.3.0", - "bun-pty": "0.4.8", - "chokidar": "4.0.3", - "clipboardy": "4.0.0", - "cross-spawn": "^7.0.6", - "decimal.js": "10.5.0", - "diff": "catalog:", - "drizzle-orm": "catalog:", - "effect": "catalog:", - "fuzzysort": "3.1.0", - "gitlab-ai-provider": "5.2.2", - "glob": "13.0.5", - "google-auth-library": "10.5.0", - "gray-matter": "4.0.3", - "hono": "catalog:", - "hono-openapi": "catalog:", - "ignore": "7.0.5", - "jsonc-parser": "3.3.1", - "mime-types": "3.0.2", - "minimatch": "10.0.3", - "open": "10.1.2", - "opencode-gitlab-auth": "2.0.0", - "opentui-spinner": "0.0.6", - "partial-json": "0.1.7", - "remeda": "catalog:", - "semver": "^7.6.3", - "solid-js": "catalog:", - "strip-ansi": "7.1.2", - "tree-sitter-bash": "0.25.0", - "turndown": "7.2.0", - "ulid": "catalog:", - "vscode-jsonrpc": "8.2.1", - "web-tree-sitter": "0.25.10", - "which": "6.0.1", - "xdg-basedir": "5.1.0", - "yargs": "18.0.0", - "zod": "catalog:", - "zod-to-json-schema": "3.24.5" + "yargs": "^17.7.2" }, - "overrides": { - "drizzle-orm": "catalog:" + "engines": { + "node": ">=18" + }, + "homepage": "https://toothfairyai.com/developers/tfcode", + "repository": { + "type": "git", + "url": "https://github.com/ToothFairyAI/tfcode.git" } } diff --git a/packages/tfcode/scripts/quickstart.cjs b/packages/tfcode/scripts/quickstart.cjs new file mode 100644 index 000000000..8c0b2e153 --- /dev/null +++ b/packages/tfcode/scripts/quickstart.cjs @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +console.log(` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + tfcode - Quick Start Guide +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Welcome to tfcode! Follow these steps to get started: + +┌─────────────────────────────────────────────────────────────┐ +│ STEP 1: Set Your Credentials │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ export TF_WORKSPACE_ID="your-workspace-id" │ +│ export TF_API_KEY="your-api-key" │ +│ export TF_REGION="au" │ +│ │ +│ Regions: au (Australia), eu (Europe), us (United States) │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ STEP 2: Validate Connection │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ tfcode validate │ +│ │ +│ This checks your credentials are correct. │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ STEP 3: Sync Your Tools │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ tfcode sync │ +│ │ +│ This downloads your tools from ToothFairyAI. │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ STEP 4: Start Coding! │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ tfcode │ +│ │ +│ Start the AI coding assistant. │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + Useful Commands: + + tfcode validate Test your credentials + tfcode sync Sync tools from workspace + tfcode tools list Show all your tools + tfcode Start coding assistant + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + Need help? https://toothfairyai.com/developers/tfcode + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +`); \ No newline at end of file diff --git a/packages/tfcode/src/cli/cmd/quickstart.ts b/packages/tfcode/src/cli/cmd/quickstart.ts new file mode 100644 index 000000000..a52d941b8 --- /dev/null +++ b/packages/tfcode/src/cli/cmd/quickstart.ts @@ -0,0 +1,53 @@ +import { cmd } from "@/cli/cmd/cmd" +import { UI } from "@/cli/ui" + +const QuickstartCommand = cmd({ + command: "quickstart", + describe: "show quick start guide", + handler: () => { + UI.println("") + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + " tfcode - Quick Start Guide" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + UI.Style.TEXT_NORMAL) + UI.println("") + UI.println("Welcome to tfcode! Follow these steps to get started:") + UI.println("") + + UI.println(UI.Style.TEXT_INFO_BOLD + "STEP 1: Set Your Credentials" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " export TF_WORKSPACE_ID=\"your-workspace-id\"" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " export TF_API_KEY=\"your-api-key\"" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " export TF_REGION=\"au\"" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " Regions: au, eu, us" + UI.Style.TEXT_NORMAL) + UI.println("") + + UI.println(UI.Style.TEXT_INFO_BOLD + "STEP 2: Validate Connection" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " tfcode validate" + UI.Style.TEXT_NORMAL) + UI.println("") + + UI.println(UI.Style.TEXT_INFO_BOLD + "STEP 3: Sync Your Tools" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " tfcode sync" + UI.Style.TEXT_NORMAL) + UI.println("") + + UI.println(UI.Style.TEXT_INFO_BOLD + "STEP 4: Start Coding!" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + " tfcode" + UI.Style.TEXT_NORMAL) + UI.println("") + + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + UI.Style.TEXT_NORMAL) + UI.println("") + UI.println(" Useful Commands:") + UI.println("") + UI.println(" tfcode validate Test your credentials") + UI.println(" tfcode sync Sync tools from workspace") + UI.println(" tfcode tools list Show all your tools") + UI.println(" tfcode Start coding assistant") + UI.println("") + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + UI.Style.TEXT_NORMAL) + UI.println("") + UI.println(" Need help? https://toothfairyai.com/developers/tfcode") + UI.println("") + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + UI.Style.TEXT_NORMAL) + UI.println("") + }, +}) + +export { QuickstartCommand } \ No newline at end of file diff --git a/packages/tfcode/src/index.ts b/packages/tfcode/src/index.ts index 10a09f827..8ef3160d5 100644 --- a/packages/tfcode/src/index.ts +++ b/packages/tfcode/src/index.ts @@ -31,6 +31,7 @@ import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" import { DbCommand } from "./cli/cmd/db" import { ValidateCommand, SyncCommand, ToolsMainCommand } from "./cli/cmd/tools" +import { QuickstartCommand } from "./cli/cmd/quickstart" import path from "path" import { Global } from "./global" import { JsonMigration } from "./storage/json-migration" @@ -149,6 +150,7 @@ let cli = yargs(hideBin(process.argv)) .command(ValidateCommand) .command(SyncCommand) .command(ToolsMainCommand) + .command(QuickstartCommand) if (Installation.isLocal()) { cli = cli.command(WorkspaceServeCommand)