@toothfairyai/tfcode 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/bin/tfcode +17 -0
- package/bin/tfcode.js +333 -0
- package/package.json +30 -0
- package/scripts/postinstall.cjs +171 -0
- package/scripts/quickstart.cjs +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# tfcode
|
|
2
|
+
|
|
3
|
+
**ToothFairyAI's official AI coding agent** - A terminal-based coding assistant with seamless integration to your ToothFairyAI workspace.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🤖 **AI-Powered Coding** - Intelligent code generation, refactoring, and debugging
|
|
8
|
+
- 🔌 **Tool Integration** - Sync and use tools from your ToothFairyAI workspace
|
|
9
|
+
- 💻 **Terminal-Based** - Fast, lightweight, runs in your terminal
|
|
10
|
+
- 🔐 **Secure** - Credentials stay in ToothFairyAI, route via TF Proxy
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- **Python 3.10+** - Required for ToothFairyAI integration
|
|
15
|
+
- **Node.js 18+** - Required to run tfcode
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g tfcode
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The installer will:
|
|
24
|
+
- Check Python is installed
|
|
25
|
+
- Install the ToothFairyAI Python SDK automatically
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 1. Set your ToothFairyAI credentials
|
|
31
|
+
export TF_WORKSPACE_ID="your-workspace-id"
|
|
32
|
+
export TF_API_KEY="your-api-key"
|
|
33
|
+
export TF_REGION="au"
|
|
34
|
+
|
|
35
|
+
# 2. Validate your credentials
|
|
36
|
+
tfcode validate
|
|
37
|
+
|
|
38
|
+
# 3. Sync tools from your workspace
|
|
39
|
+
tfcode sync
|
|
40
|
+
|
|
41
|
+
# 4. Start coding!
|
|
42
|
+
tfcode
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
| Command | Description |
|
|
48
|
+
|---------|-------------|
|
|
49
|
+
| `tfcode` | Start the AI coding assistant |
|
|
50
|
+
| `tfcode quickstart` | Show quick start guide |
|
|
51
|
+
| `tfcode validate` | Test your credentials |
|
|
52
|
+
| `tfcode sync` | Sync tools from workspace |
|
|
53
|
+
| `tfcode tools list` | List synced tools |
|
|
54
|
+
| `tfcode tools list --type mcp` | List MCP servers |
|
|
55
|
+
| `tfcode tools credentials <name> --set` | Set API key for a tool |
|
|
56
|
+
|
|
57
|
+
## Regions
|
|
58
|
+
|
|
59
|
+
| Region | URL | Use Case |
|
|
60
|
+
|--------|-----|----------|
|
|
61
|
+
| `dev` | api.toothfairylab.link | Development/Testing |
|
|
62
|
+
| `au` | api.toothfairyai.com | Australia (Default) |
|
|
63
|
+
| `eu` | api.eu.toothfairyai.com | Europe |
|
|
64
|
+
| `us` | api.us.toothfairyai.com | United States |
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
Credentials can be set via environment variables:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export TF_WORKSPACE_ID="your-workspace-id"
|
|
72
|
+
export TF_API_KEY="your-api-key"
|
|
73
|
+
export TF_REGION="au"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or in `~/.tfcode/tfcode.json`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"toothfairy": {
|
|
81
|
+
"workspace_id": "your-workspace-id",
|
|
82
|
+
"api_key": "your-api-key",
|
|
83
|
+
"region": "au"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Getting Your Credentials
|
|
89
|
+
|
|
90
|
+
1. Log in to [ToothFairyAI](https://app.toothfairyai.com)
|
|
91
|
+
2. Go to **Settings** → **API Keys**
|
|
92
|
+
3. Copy your **Workspace ID** and **API Key**
|
|
93
|
+
|
|
94
|
+
## Troubleshooting
|
|
95
|
+
|
|
96
|
+
### "Python 3.10+ is required but not found"
|
|
97
|
+
|
|
98
|
+
Install Python:
|
|
99
|
+
- **macOS**: `brew install python@3.12`
|
|
100
|
+
- **Windows**: Download from https://python.org/downloads
|
|
101
|
+
- **Linux**: `sudo apt install python3.12`
|
|
102
|
+
|
|
103
|
+
### "Failed to validate: Invalid API key"
|
|
104
|
+
|
|
105
|
+
- Check your API key is correct
|
|
106
|
+
- Generate a new key in ToothFairyAI Settings → API Keys
|
|
107
|
+
|
|
108
|
+
### "Failed to validate: Connection test failed"
|
|
109
|
+
|
|
110
|
+
Try a different region:
|
|
111
|
+
```bash
|
|
112
|
+
export TF_REGION="eu" # or us, au
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
- **Full Guide**: https://toothfairyai.com/developers/tfcode
|
|
118
|
+
- **API Docs**: https://apidocs.toothfairyai.com
|
|
119
|
+
- **Issues**: https://github.com/ToothFairyAI/tfcode/issues
|
|
120
|
+
|
|
121
|
+
## Credits
|
|
122
|
+
|
|
123
|
+
tfcode is based on [opencode](https://github.com/anomalyco/opencode) by [anomalyco](https://github.com/anomalyco).
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/bin/tfcode
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("child_process")
|
|
4
|
+
const path = require("path")
|
|
5
|
+
|
|
6
|
+
const scriptDir = __dirname
|
|
7
|
+
const srcPath = path.join(scriptDir, "..", "src", "index.ts")
|
|
8
|
+
|
|
9
|
+
const child = spawn("npx", ["tsx", srcPath, ...process.argv.slice(2)], {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
shell: process.platform === "win32",
|
|
12
|
+
cwd: scriptDir
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
child.on("exit", (code) => {
|
|
16
|
+
process.exit(code || 0)
|
|
17
|
+
})
|
package/bin/tfcode.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
|
|
10
|
+
const TFCODE_DIR = join(homedir(), '.tfcode');
|
|
11
|
+
const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
|
|
12
|
+
const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json');
|
|
13
|
+
|
|
14
|
+
const COLORS = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
green: '\x1b[32m',
|
|
18
|
+
red: '\x1b[31m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
dim: '\x1b[90m',
|
|
21
|
+
yellow: '\x1b[33m'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function log(msg) {
|
|
25
|
+
console.log(msg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function success(msg) {
|
|
29
|
+
console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function error(msg) {
|
|
33
|
+
console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function info(msg) {
|
|
37
|
+
console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function runPythonSync(method) {
|
|
41
|
+
const pythonCode = `
|
|
42
|
+
import json
|
|
43
|
+
import sys
|
|
44
|
+
import os
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from tf_sync.config import load_config, validate_credentials, Region
|
|
48
|
+
from tf_sync.tools import sync_tools
|
|
49
|
+
from tf_sync.config import get_region_urls
|
|
50
|
+
|
|
51
|
+
method = "${method}"
|
|
52
|
+
|
|
53
|
+
if method == "validate":
|
|
54
|
+
config = load_config()
|
|
55
|
+
result = validate_credentials(config)
|
|
56
|
+
urls = get_region_urls(config.region)
|
|
57
|
+
print(json.dumps({
|
|
58
|
+
"success": result.success,
|
|
59
|
+
"workspace_id": result.workspace_id,
|
|
60
|
+
"workspace_name": result.workspace_name,
|
|
61
|
+
"error": result.error,
|
|
62
|
+
"base_url": urls["base_url"]
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
elif method == "sync":
|
|
66
|
+
config = load_config()
|
|
67
|
+
result = sync_tools(config)
|
|
68
|
+
|
|
69
|
+
tools_data = []
|
|
70
|
+
for tool in result.tools:
|
|
71
|
+
tools_data.append({
|
|
72
|
+
"id": tool.id,
|
|
73
|
+
"name": tool.name,
|
|
74
|
+
"description": tool.description,
|
|
75
|
+
"tool_type": tool.tool_type.value,
|
|
76
|
+
"request_type": tool.request_type.value if tool.request_type else None,
|
|
77
|
+
"url": tool.url,
|
|
78
|
+
"auth_via": tool.auth_via
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
print(json.dumps({
|
|
82
|
+
"success": result.success,
|
|
83
|
+
"tools": tools_data,
|
|
84
|
+
"by_type": result.by_type,
|
|
85
|
+
"error": result.error
|
|
86
|
+
}))
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(json.dumps({"success": False, "error": str(e)}))
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const pythonPath = process.env.TFCODE_PYTHON_PATH || 'python3';
|
|
95
|
+
const proc = spawn(pythonPath, ['-c', pythonCode], {
|
|
96
|
+
env: {
|
|
97
|
+
...process.env,
|
|
98
|
+
PYTHONPATH: process.env.TFCODE_PYTHONPATH || ''
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let stdout = '';
|
|
103
|
+
let stderr = '';
|
|
104
|
+
|
|
105
|
+
proc.stdout.on('data', (data) => {
|
|
106
|
+
stdout += data.toString();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
proc.stderr.on('data', (data) => {
|
|
110
|
+
stderr += data.toString();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
proc.on('close', (code) => {
|
|
114
|
+
if (code !== 0 && !stdout) {
|
|
115
|
+
reject(new Error(`Python sync failed: ${stderr}`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const result = JSON.parse(stdout.trim());
|
|
121
|
+
resolve(result);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
reject(new Error(`Failed to parse Python output: ${stdout}\nstderr: ${stderr}`));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
proc.on('error', (err) => {
|
|
128
|
+
reject(err);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function ensureConfigDir() {
|
|
134
|
+
if (!existsSync(TFCODE_DIR)) {
|
|
135
|
+
mkdirSync(TFCODE_DIR, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function loadCachedTools() {
|
|
140
|
+
if (!existsSync(TOOLS_FILE)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8'));
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function saveToolsCache(tools) {
|
|
151
|
+
ensureConfigDir();
|
|
152
|
+
writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const cli = yargs(hideBin(process.argv))
|
|
156
|
+
.scriptName('tfcode')
|
|
157
|
+
.wrap(100)
|
|
158
|
+
.help()
|
|
159
|
+
.alias('help', 'h')
|
|
160
|
+
.command({
|
|
161
|
+
command: 'quickstart',
|
|
162
|
+
describe: 'show quick start guide',
|
|
163
|
+
handler: () => {
|
|
164
|
+
log('');
|
|
165
|
+
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
166
|
+
log(`${COLORS.bold} tfcode - Quick Start Guide${COLORS.reset}`);
|
|
167
|
+
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
168
|
+
log('');
|
|
169
|
+
log('Welcome to tfcode! Follow these steps to get started:');
|
|
170
|
+
log('');
|
|
171
|
+
log(`${COLORS.cyan}STEP 1: Set Your Credentials${COLORS.reset}`);
|
|
172
|
+
log(`${COLORS.dim} export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`);
|
|
173
|
+
log(`${COLORS.dim} export TF_API_KEY="your-api-key"${COLORS.reset}`);
|
|
174
|
+
log(`${COLORS.dim} export TF_REGION="au"${COLORS.reset}`);
|
|
175
|
+
log(`${COLORS.dim} Regions: dev, au, eu, us${COLORS.reset}`);
|
|
176
|
+
log('');
|
|
177
|
+
log(`${COLORS.cyan}STEP 2: Validate Connection${COLORS.reset}`);
|
|
178
|
+
log(`${COLORS.dim} tfcode validate${COLORS.reset}`);
|
|
179
|
+
log('');
|
|
180
|
+
log(`${COLORS.cyan}STEP 3: Sync Your Tools${COLORS.reset}`);
|
|
181
|
+
log(`${COLORS.dim} tfcode sync${COLORS.reset}`);
|
|
182
|
+
log('');
|
|
183
|
+
log(`${COLORS.cyan}STEP 4: Start Coding!${COLORS.reset}`);
|
|
184
|
+
log(`${COLORS.dim} tfcode${COLORS.reset}`);
|
|
185
|
+
log('');
|
|
186
|
+
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
187
|
+
log('');
|
|
188
|
+
log(' Useful Commands:');
|
|
189
|
+
log('');
|
|
190
|
+
log(' tfcode validate Test your credentials');
|
|
191
|
+
log(' tfcode sync Sync tools from workspace');
|
|
192
|
+
log(' tfcode tools list Show all your tools');
|
|
193
|
+
log('');
|
|
194
|
+
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
195
|
+
log('');
|
|
196
|
+
log(' Need help? https://toothfairyai.com/developers/tfcode');
|
|
197
|
+
log('');
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.command({
|
|
201
|
+
command: 'validate',
|
|
202
|
+
describe: 'validate ToothFairyAI credentials',
|
|
203
|
+
handler: async () => {
|
|
204
|
+
info('Validating ToothFairyAI credentials...');
|
|
205
|
+
log('');
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const result = await runPythonSync('validate');
|
|
209
|
+
|
|
210
|
+
if (result.success) {
|
|
211
|
+
success('Credentials valid');
|
|
212
|
+
if (result.base_url) {
|
|
213
|
+
log(`${COLORS.dim} API URL: ${result.base_url}${COLORS.reset}`);
|
|
214
|
+
}
|
|
215
|
+
if (result.workspace_id) {
|
|
216
|
+
log(`${COLORS.dim} Workspace ID: ${result.workspace_id}${COLORS.reset}`);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
error(`Validation failed: ${result.error || 'Unknown error'}`);
|
|
220
|
+
log('');
|
|
221
|
+
log(`${COLORS.dim}Check your credentials:${COLORS.reset}`);
|
|
222
|
+
log(`${COLORS.dim} TF_WORKSPACE_ID: ${process.env.TF_WORKSPACE_ID || 'not set'}${COLORS.reset}`);
|
|
223
|
+
log(`${COLORS.dim} TF_API_KEY: ${process.env.TF_API_KEY ? '***' + process.env.TF_API_KEY.slice(-4) : 'not set'}${COLORS.reset}`);
|
|
224
|
+
log(`${COLORS.dim} TF_REGION: ${process.env.TF_REGION || 'au (default)'}${COLORS.reset}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
error(`Failed to validate: ${e.message}`);
|
|
229
|
+
log('');
|
|
230
|
+
log(`${COLORS.dim}Make sure Python 3.10+ and the ToothFairyAI SDK are installed:${COLORS.reset}`);
|
|
231
|
+
log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
.command({
|
|
237
|
+
command: 'sync',
|
|
238
|
+
describe: 'sync tools from ToothFairyAI workspace',
|
|
239
|
+
handler: async () => {
|
|
240
|
+
info('Syncing tools from ToothFairyAI workspace...');
|
|
241
|
+
log('');
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const result = await runPythonSync('sync');
|
|
245
|
+
|
|
246
|
+
if (result.success) {
|
|
247
|
+
saveToolsCache(result);
|
|
248
|
+
success(`Synced ${result.tools.length} tools`);
|
|
249
|
+
log('');
|
|
250
|
+
|
|
251
|
+
if (result.by_type && Object.keys(result.by_type).length > 0) {
|
|
252
|
+
log('By type:');
|
|
253
|
+
for (const [type, count] of Object.entries(result.by_type)) {
|
|
254
|
+
log(` ${type}: ${count}`);
|
|
255
|
+
}
|
|
256
|
+
log('');
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
error(`Sync failed: ${result.error || 'Unknown error'}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
} catch (e) {
|
|
263
|
+
error(`Failed to sync: ${e.message}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
.command({
|
|
269
|
+
command: 'tools',
|
|
270
|
+
describe: 'manage tools',
|
|
271
|
+
builder: (yargs) => {
|
|
272
|
+
return yargs
|
|
273
|
+
.command({
|
|
274
|
+
command: 'list',
|
|
275
|
+
describe: 'list synced tools',
|
|
276
|
+
builder: (yargs) => {
|
|
277
|
+
return yargs.option('type', {
|
|
278
|
+
type: 'string',
|
|
279
|
+
describe: 'filter by type (api_function)'
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
handler: (args) => {
|
|
283
|
+
const cached = loadCachedTools();
|
|
284
|
+
|
|
285
|
+
if (!cached || !cached.success) {
|
|
286
|
+
error('No tools synced. Run \'tfcode sync\' first.');
|
|
287
|
+
process.exit(1);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let tools = cached.tools;
|
|
292
|
+
|
|
293
|
+
if (args.type) {
|
|
294
|
+
tools = tools.filter(t => t.tool_type === args.type);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (tools.length === 0) {
|
|
298
|
+
log('No tools found.');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
log('');
|
|
303
|
+
log(`${tools.length} tool(s):`);
|
|
304
|
+
log('');
|
|
305
|
+
|
|
306
|
+
for (const tool of tools) {
|
|
307
|
+
log(` ${COLORS.cyan}${tool.name}${COLORS.reset}`);
|
|
308
|
+
log(` Type: ${tool.tool_type}`);
|
|
309
|
+
if (tool.description) {
|
|
310
|
+
log(` ${COLORS.dim}${tool.description.slice(0, 60)}${tool.description.length > 60 ? '...' : ''}${COLORS.reset}`);
|
|
311
|
+
}
|
|
312
|
+
log(` Auth: ${tool.auth_via}`);
|
|
313
|
+
log('');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
.demandCommand();
|
|
318
|
+
},
|
|
319
|
+
handler: () => {}
|
|
320
|
+
})
|
|
321
|
+
.demandCommand()
|
|
322
|
+
.strict()
|
|
323
|
+
.fail((msg, err) => {
|
|
324
|
+
if (msg) {
|
|
325
|
+
error(msg);
|
|
326
|
+
}
|
|
327
|
+
if (err) {
|
|
328
|
+
error(err.message);
|
|
329
|
+
}
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
cli.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toothfairyai/tfcode",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "ToothFairyAI's official AI coding agent",
|
|
5
|
+
"keywords": ["toothfairyai", "ai", "coding", "cli"],
|
|
6
|
+
"author": "ToothFairyAI",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"tfcode": "./bin/tfcode.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"scripts/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"yargs": "^17.7.2"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://toothfairyai.com/developers/tfcode",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/ToothFairyAI/tfcode.git"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const RESET = '\x1b[0m';
|
|
8
|
+
const BOLD = '\x1b[1m';
|
|
9
|
+
const GREEN = '\x1b[32m';
|
|
10
|
+
const YELLOW = '\x1b[33m';
|
|
11
|
+
const RED = '\x1b[31m';
|
|
12
|
+
const CYAN = '\x1b[36m';
|
|
13
|
+
const DIM = '\x1b[90m';
|
|
14
|
+
|
|
15
|
+
function log(msg) {
|
|
16
|
+
console.log(msg);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function logSuccess(msg) {
|
|
20
|
+
console.log(`${GREEN}✓${RESET} ${msg}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function logError(msg) {
|
|
24
|
+
console.error(`${RED}✗${RESET} ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function logInfo(msg) {
|
|
28
|
+
console.log(`${CYAN}ℹ${RESET} ${msg}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function logWarning(msg) {
|
|
32
|
+
console.log(`${YELLOW}!${RESET} ${msg}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function checkPython() {
|
|
36
|
+
const commands = ['python3', 'python'];
|
|
37
|
+
|
|
38
|
+
for (const cmd of commands) {
|
|
39
|
+
try {
|
|
40
|
+
const result = execSync(`${cmd} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
41
|
+
const match = result.match(/Python (\d+)\.(\d+)/);
|
|
42
|
+
if (match) {
|
|
43
|
+
const major = parseInt(match[1]);
|
|
44
|
+
const minor = parseInt(match[2]);
|
|
45
|
+
if (major >= 3 && minor >= 10) {
|
|
46
|
+
return { cmd, version: result.trim() };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function installPythonDeps(pythonCmd) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const packages = ['toothfairyai', 'pydantic', 'httpx', 'rich'];
|
|
58
|
+
|
|
59
|
+
log(`${DIM}Installing Python packages: ${packages.join(', ')}...${RESET}`);
|
|
60
|
+
|
|
61
|
+
// Try with --user first, then --break-system-packages if needed
|
|
62
|
+
const args = ['-m', 'pip', 'install', '--user', ...packages];
|
|
63
|
+
|
|
64
|
+
const proc = spawn(pythonCmd, args, {
|
|
65
|
+
stdio: 'inherit',
|
|
66
|
+
shell: process.platform === 'win32'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
proc.on('close', (code) => {
|
|
70
|
+
if (code === 0) {
|
|
71
|
+
resolve();
|
|
72
|
+
} else {
|
|
73
|
+
// Try with --break-system-packages flag
|
|
74
|
+
log(`${DIM}Retrying with --break-system-packages...${RESET}`);
|
|
75
|
+
const retryArgs = ['-m', 'pip', 'install', '--user', '--break-system-packages', ...packages];
|
|
76
|
+
const retry = spawn(pythonCmd, retryArgs, {
|
|
77
|
+
stdio: 'inherit',
|
|
78
|
+
shell: process.platform === 'win32'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
retry.on('close', (retryCode) => {
|
|
82
|
+
if (retryCode === 0) {
|
|
83
|
+
resolve();
|
|
84
|
+
} else {
|
|
85
|
+
reject(new Error(`pip install exited with code ${retryCode}`));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
retry.on('error', (err) => {
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
proc.on('error', (err) => {
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function main() {
|
|
102
|
+
log('');
|
|
103
|
+
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
104
|
+
log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`);
|
|
105
|
+
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
106
|
+
log('');
|
|
107
|
+
|
|
108
|
+
// Check for Python
|
|
109
|
+
logInfo('Checking Python installation...');
|
|
110
|
+
const python = checkPython();
|
|
111
|
+
|
|
112
|
+
if (!python) {
|
|
113
|
+
log('');
|
|
114
|
+
logError('Python 3.10+ is required but not found.');
|
|
115
|
+
log('');
|
|
116
|
+
log(`${BOLD}Please install Python 3.10 or later:${RESET}`);
|
|
117
|
+
log('');
|
|
118
|
+
log(` ${CYAN}macOS:${RESET} brew install python@3.12`);
|
|
119
|
+
log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`);
|
|
120
|
+
log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`);
|
|
121
|
+
log('');
|
|
122
|
+
log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`);
|
|
123
|
+
log('');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
logSuccess(`Found ${python.version} (${python.cmd})`);
|
|
128
|
+
log('');
|
|
129
|
+
|
|
130
|
+
// Install Python dependencies
|
|
131
|
+
logInfo('Installing ToothFairyAI Python SDK...');
|
|
132
|
+
try {
|
|
133
|
+
await installPythonDeps(python.cmd);
|
|
134
|
+
logSuccess('Python dependencies installed');
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logWarning(`Failed to install Python dependencies: ${err.message}`);
|
|
137
|
+
log('');
|
|
138
|
+
log(`${DIM}You can install them manually with:${RESET}`);
|
|
139
|
+
log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`);
|
|
140
|
+
log('');
|
|
141
|
+
// Don't exit - user might install manually later
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
log('');
|
|
145
|
+
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
146
|
+
log(`${GREEN}✓ tfcode installed successfully!${RESET}`);
|
|
147
|
+
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
148
|
+
log('');
|
|
149
|
+
log(`${BOLD}Quick Start:${RESET}`);
|
|
150
|
+
log('');
|
|
151
|
+
log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`);
|
|
152
|
+
log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`);
|
|
153
|
+
log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`);
|
|
154
|
+
log('');
|
|
155
|
+
log(` ${CYAN}2.${RESET} Validate your credentials:`);
|
|
156
|
+
log(` ${DIM}tfcode validate${RESET}`);
|
|
157
|
+
log('');
|
|
158
|
+
log(` ${CYAN}3.${RESET} Sync tools from your workspace:`);
|
|
159
|
+
log(` ${DIM}tfcode sync${RESET}`);
|
|
160
|
+
log('');
|
|
161
|
+
log(` ${CYAN}4.${RESET} Start coding:`);
|
|
162
|
+
log(` ${DIM}tfcode${RESET}`);
|
|
163
|
+
log('');
|
|
164
|
+
log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`);
|
|
165
|
+
log('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
main().catch((err) => {
|
|
169
|
+
logError(`Installation failed: ${err.message}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
console.log(`
|
|
4
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5
|
+
tfcode - Quick Start Guide
|
|
6
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
7
|
+
|
|
8
|
+
Welcome to tfcode! Follow these steps to get started:
|
|
9
|
+
|
|
10
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ STEP 1: Set Your Credentials │
|
|
12
|
+
├─────────────────────────────────────────────────────────────┤
|
|
13
|
+
│ │
|
|
14
|
+
│ export TF_WORKSPACE_ID="your-workspace-id" │
|
|
15
|
+
│ export TF_API_KEY="your-api-key" │
|
|
16
|
+
│ export TF_REGION="au" │
|
|
17
|
+
│ │
|
|
18
|
+
│ Regions: au (Australia), eu (Europe), us (United States) │
|
|
19
|
+
│ │
|
|
20
|
+
└─────────────────────────────────────────────────────────────┘
|
|
21
|
+
|
|
22
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ STEP 2: Validate Connection │
|
|
24
|
+
├─────────────────────────────────────────────────────────────┤
|
|
25
|
+
│ │
|
|
26
|
+
│ tfcode validate │
|
|
27
|
+
│ │
|
|
28
|
+
│ This checks your credentials are correct. │
|
|
29
|
+
│ │
|
|
30
|
+
└─────────────────────────────────────────────────────────────┘
|
|
31
|
+
|
|
32
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
33
|
+
│ STEP 3: Sync Your Tools │
|
|
34
|
+
├─────────────────────────────────────────────────────────────┤
|
|
35
|
+
│ │
|
|
36
|
+
│ tfcode sync │
|
|
37
|
+
│ │
|
|
38
|
+
│ This downloads your tools from ToothFairyAI. │
|
|
39
|
+
│ │
|
|
40
|
+
└─────────────────────────────────────────────────────────────┘
|
|
41
|
+
|
|
42
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
43
|
+
│ STEP 4: Start Coding! │
|
|
44
|
+
├─────────────────────────────────────────────────────────────┤
|
|
45
|
+
│ │
|
|
46
|
+
│ tfcode │
|
|
47
|
+
│ │
|
|
48
|
+
│ Start the AI coding assistant. │
|
|
49
|
+
│ │
|
|
50
|
+
└─────────────────────────────────────────────────────────────┘
|
|
51
|
+
|
|
52
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
53
|
+
|
|
54
|
+
Useful Commands:
|
|
55
|
+
|
|
56
|
+
tfcode validate Test your credentials
|
|
57
|
+
tfcode sync Sync tools from workspace
|
|
58
|
+
tfcode tools list Show all your tools
|
|
59
|
+
tfcode Start coding assistant
|
|
60
|
+
|
|
61
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
62
|
+
|
|
63
|
+
Need help? https://toothfairyai.com/developers/tfcode
|
|
64
|
+
|
|
65
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
66
|
+
`);
|