@toothfairyai/tfcode 2.5.8 → 2.5.9
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/LICENSE +21 -0
- package/README.md +0 -0
- package/bin/tfcode.js +42 -504
- package/package.json +25 -161
- package/postinstall.mjs +378 -0
- package/python/tf_sync/__init__.py +21 -0
- package/python/tf_sync/agents.py +39 -0
- package/python/tf_sync/config.py +222 -0
- package/python/tf_sync/mcp.py +41 -0
- package/python/tf_sync/tools.py +282 -0
- package/bin/tfcode +0 -17
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 opencode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
File without changes
|
package/bin/tfcode.js
CHANGED
|
@@ -1,521 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
const { spawn, spawnSync } = require('child_process')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const os = require('os')
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { homedir } from "os"
|
|
7
|
-
import * as readline from "readline"
|
|
8
|
-
import { fileURLToPath } from "url"
|
|
7
|
+
const platform = os.platform() === 'darwin' ? 'darwin' : os.platform() === 'win32' ? 'windows' : 'linux'
|
|
8
|
+
const arch = os.arch() === 'arm64' ? 'arm64' : 'x64'
|
|
9
|
+
const binName = platform === 'windows' ? 'tfcode.exe' : 'tfcode'
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const TFCODE_DIR = join(homedir(), ".tfcode")
|
|
14
|
-
const TOOLS_FILE = join(TFCODE_DIR, "tools.json")
|
|
15
|
-
const CREDENTIALS_FILE = join(TFCODE_DIR, "credentials.json")
|
|
16
|
-
const CONFIG_FILE = join(TFCODE_DIR, "config.json")
|
|
17
|
-
|
|
18
|
-
const COLORS = {
|
|
19
|
-
reset: "\x1b[0m",
|
|
20
|
-
bold: "\x1b[1m",
|
|
21
|
-
green: "\x1b[32m",
|
|
22
|
-
red: "\x1b[31m",
|
|
23
|
-
cyan: "\x1b[36m",
|
|
24
|
-
dim: "\x1b[90m",
|
|
25
|
-
yellow: "\x1b[33m",
|
|
26
|
-
magenta: "\x1b[35m",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function log(msg) {
|
|
30
|
-
console.log(msg)
|
|
31
|
-
}
|
|
32
|
-
function success(msg) {
|
|
33
|
-
console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`)
|
|
34
|
-
}
|
|
35
|
-
function error(msg) {
|
|
36
|
-
console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`)
|
|
37
|
-
}
|
|
38
|
-
function info(msg) {
|
|
39
|
-
console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function ensureConfigDir() {
|
|
43
|
-
if (!existsSync(TFCODE_DIR)) mkdirSync(TFCODE_DIR, { recursive: true })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function loadConfig() {
|
|
47
|
-
const envConfig = {
|
|
48
|
-
workspace_id: process.env.TF_WORKSPACE_ID,
|
|
49
|
-
api_key: process.env.TF_API_KEY,
|
|
50
|
-
region: process.env.TF_REGION,
|
|
51
|
-
}
|
|
52
|
-
if (envConfig.workspace_id && envConfig.api_key) return envConfig
|
|
53
|
-
if (existsSync(CONFIG_FILE)) {
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"))
|
|
56
|
-
} catch {}
|
|
57
|
-
}
|
|
58
|
-
return null
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function saveConfig(config) {
|
|
62
|
-
ensureConfigDir()
|
|
63
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function runPythonSync(method, config = null) {
|
|
67
|
-
const wsId = config?.workspace_id || process.env.TF_WORKSPACE_ID || ""
|
|
68
|
-
const apiKey = config?.api_key || process.env.TF_API_KEY || ""
|
|
69
|
-
const region = config?.region || process.env.TF_REGION || "au"
|
|
70
|
-
|
|
71
|
-
// Add embedded python path to sys.path
|
|
72
|
-
const embeddedPythonPath = join(__dirname, "..", "python")
|
|
73
|
-
|
|
74
|
-
const pythonCode = `
|
|
75
|
-
import json, sys, os
|
|
76
|
-
try:
|
|
77
|
-
# Add embedded tf_sync module path
|
|
78
|
-
sys.path.insert(0, "${embeddedPythonPath}")
|
|
79
|
-
os.environ["TF_WORKSPACE_ID"] = "${wsId}"
|
|
80
|
-
os.environ["TF_API_KEY"] = "${apiKey}"
|
|
81
|
-
os.environ["TF_REGION"] = "${region}"
|
|
82
|
-
from tf_sync.config import load_config, validate_credentials, Region
|
|
83
|
-
from tf_sync.tools import sync_tools
|
|
84
|
-
from tf_sync.config import get_region_urls
|
|
85
|
-
|
|
86
|
-
method = "${method}"
|
|
87
|
-
if method == "validate":
|
|
88
|
-
config = load_config()
|
|
89
|
-
result = validate_credentials(config)
|
|
90
|
-
urls = get_region_urls(config.region)
|
|
91
|
-
print(json.dumps({"success": result.success, "workspace_id": result.workspace_id, "workspace_name": result.workspace_name, "error": result.error, "base_url": urls["base_url"]}))
|
|
92
|
-
elif method == "sync":
|
|
93
|
-
config = load_config()
|
|
94
|
-
result = sync_tools(config)
|
|
95
|
-
tools_data = [{"id": t.id, "name": t.name, "description": t.description, "tool_type": t.tool_type.value, "request_type": t.request_type.value if t.request_type else None, "url": t.url, "auth_via": t.auth_via, "interpolation_string": t.interpolation_string, "goals": t.goals, "temperature": t.temperature, "max_tokens": t.max_tokens, "llm_base_model": t.llm_base_model, "llm_provider": t.llm_provider} for t in result.tools]
|
|
96
|
-
print(json.dumps({"success": result.success, "tools": tools_data, "by_type": result.by_type, "error": result.error}))
|
|
97
|
-
except Exception as e:
|
|
98
|
-
print(json.dumps({"success": False, "error": str(e)}))
|
|
99
|
-
`
|
|
100
|
-
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
const proc = spawn(process.env.TFCODE_PYTHON_PATH || "python3", ["-c", pythonCode], { env: { ...process.env } })
|
|
103
|
-
let stdout = "",
|
|
104
|
-
stderr = ""
|
|
105
|
-
proc.stdout.on("data", (d) => (stdout += d))
|
|
106
|
-
proc.stderr.on("data", (d) => (stderr += d))
|
|
107
|
-
proc.on("close", (code) => {
|
|
108
|
-
if (code !== 0 && !stdout) reject(new Error(`Python failed: ${stderr}`))
|
|
109
|
-
else
|
|
110
|
-
try {
|
|
111
|
-
resolve(JSON.parse(stdout.trim()))
|
|
112
|
-
} catch (e) {
|
|
113
|
-
reject(new Error(`Parse error: ${stdout}`))
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
proc.on("error", reject)
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function loadCachedTools() {
|
|
121
|
-
if (!existsSync(TOOLS_FILE)) return null
|
|
11
|
+
function needBaseline() {
|
|
12
|
+
if (arch !== 'x64' || platform === 'windows') return false
|
|
122
13
|
try {
|
|
123
|
-
|
|
14
|
+
if (platform === 'linux') {
|
|
15
|
+
return !fs.readFileSync('/proc/cpuinfo', 'utf8').toLowerCase().includes('avx2')
|
|
16
|
+
}
|
|
17
|
+
const r = spawnSync('sysctl', ['-n', 'hw.optional.avx2_0'], { encoding: 'utf8' })
|
|
18
|
+
return r.stdout.trim() !== '1'
|
|
124
19
|
} catch {
|
|
125
|
-
return
|
|
20
|
+
return false
|
|
126
21
|
}
|
|
127
22
|
}
|
|
128
23
|
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2))
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function question(prompt) {
|
|
135
|
-
return new Promise((resolve) => {
|
|
136
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
137
|
-
rl.question(prompt, (answer) => {
|
|
138
|
-
rl.close()
|
|
139
|
-
resolve(answer.trim())
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function select(prompt, options) {
|
|
145
|
-
log("")
|
|
146
|
-
log(prompt)
|
|
147
|
-
log("")
|
|
148
|
-
options.forEach((opt, i) => log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${opt}`))
|
|
149
|
-
log("")
|
|
150
|
-
const answer = await question("Select (1-" + options.length + "): ")
|
|
151
|
-
const idx = parseInt(answer) - 1
|
|
152
|
-
return idx >= 0 && idx < options.length ? idx : 0
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function interactiveSetup() {
|
|
156
|
-
log("")
|
|
157
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
158
|
-
log(`${COLORS.bold}${COLORS.magenta} tfcode Setup${COLORS.reset}`)
|
|
159
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
160
|
-
log("")
|
|
161
|
-
log("This will guide you through setting up your ToothFairyAI credentials.")
|
|
162
|
-
log("")
|
|
163
|
-
log(`${COLORS.dim}You can find your credentials at:${COLORS.reset}`)
|
|
164
|
-
log(`${COLORS.dim} https://app.toothfairyai.com → Settings → API Keys${COLORS.reset}`)
|
|
165
|
-
log("")
|
|
166
|
-
|
|
167
|
-
log(`${COLORS.bold}Step 1: Workspace ID${COLORS.reset}`)
|
|
168
|
-
log(`${COLORS.dim}This is your workspace UUID${COLORS.reset}`)
|
|
169
|
-
log("")
|
|
170
|
-
const workspaceId = await question("Enter your Workspace ID: ")
|
|
171
|
-
if (!workspaceId) {
|
|
172
|
-
error("Workspace ID is required")
|
|
173
|
-
process.exit(1)
|
|
174
|
-
}
|
|
175
|
-
log("")
|
|
176
|
-
|
|
177
|
-
log(`${COLORS.bold}Step 2: API Key${COLORS.reset}`)
|
|
178
|
-
log(`${COLORS.dim}Paste or type your API key${COLORS.reset}`)
|
|
179
|
-
log("")
|
|
180
|
-
const apiKey = await question("Enter your API Key: ")
|
|
181
|
-
if (!apiKey) {
|
|
182
|
-
error("API Key is required")
|
|
183
|
-
process.exit(1)
|
|
184
|
-
}
|
|
185
|
-
log("")
|
|
186
|
-
|
|
187
|
-
log(`${COLORS.bold}Step 3: Region${COLORS.reset}`)
|
|
188
|
-
const regions = ["dev (Development)", "au (Australia)", "eu (Europe)", "us (United States)"]
|
|
189
|
-
const regionIdx = await select("Select your region:", regions)
|
|
190
|
-
const region = ["dev", "au", "eu", "us"][regionIdx]
|
|
191
|
-
|
|
192
|
-
log("")
|
|
193
|
-
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
194
|
-
log("")
|
|
195
|
-
log(`${COLORS.bold}Summary:${COLORS.reset}`)
|
|
196
|
-
log(` Workspace ID: ${workspaceId}`)
|
|
197
|
-
log(` API Key: ***${apiKey.slice(-4)}`)
|
|
198
|
-
log(` Region: ${region}`)
|
|
199
|
-
log("")
|
|
200
|
-
|
|
201
|
-
const confirm = await question("Save these credentials? (Y/n): ")
|
|
202
|
-
if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
|
|
203
|
-
log("Setup cancelled.")
|
|
204
|
-
return
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const config = { workspace_id: workspaceId, api_key: apiKey, region }
|
|
208
|
-
saveConfig(config)
|
|
209
|
-
success("Credentials saved to ~/.tfcode/config.json")
|
|
210
|
-
log("")
|
|
211
|
-
|
|
212
|
-
const testNow = await question("Validate credentials now? (Y/n): ")
|
|
213
|
-
if (testNow.toLowerCase() === "n" || testNow.toLowerCase() === "no") return
|
|
214
|
-
|
|
215
|
-
log("")
|
|
216
|
-
info("Validating credentials...")
|
|
217
|
-
log("")
|
|
218
|
-
|
|
24
|
+
function libcAbi() {
|
|
25
|
+
if (platform !== 'linux') return ''
|
|
219
26
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
log("")
|
|
226
|
-
|
|
227
|
-
const syncNow = await question("Sync tools now? (Y/n): ")
|
|
228
|
-
if (syncNow.toLowerCase() === "n" || syncNow.toLowerCase() === "no") return
|
|
229
|
-
|
|
230
|
-
log("")
|
|
231
|
-
info("Syncing tools...")
|
|
232
|
-
log("")
|
|
233
|
-
|
|
234
|
-
const syncResult = await runPythonSync("sync", config)
|
|
235
|
-
if (syncResult.success) {
|
|
236
|
-
saveToolsCache(syncResult)
|
|
237
|
-
success(`Synced ${syncResult.tools.length} tools`)
|
|
238
|
-
if (syncResult.by_type && Object.keys(syncResult.by_type).length > 0) {
|
|
239
|
-
log("")
|
|
240
|
-
log("By type:")
|
|
241
|
-
for (const [type, count] of Object.entries(syncResult.by_type)) log(` ${type}: ${count}`)
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
error(`Sync failed: ${syncResult.error}`)
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
error(`Validation failed: ${result.error}`)
|
|
248
|
-
}
|
|
249
|
-
} catch (e) {
|
|
250
|
-
error(`Failed: ${e.message}`)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
log("")
|
|
254
|
-
log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
255
|
-
log(`${COLORS.bold}${COLORS.green} Setup Complete!${COLORS.reset}`)
|
|
256
|
-
log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
257
|
-
log("")
|
|
258
|
-
log("Commands:")
|
|
259
|
-
log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Check credentials`)
|
|
260
|
-
log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools`)
|
|
261
|
-
log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List tools`)
|
|
262
|
-
log("")
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function showHelp() {
|
|
266
|
-
log("")
|
|
267
|
-
log(`${COLORS.bold}tfcode${COLORS.reset} - ToothFairyAI's AI coding agent`)
|
|
268
|
-
log("")
|
|
269
|
-
log("Commands:")
|
|
270
|
-
log(` ${COLORS.cyan}tfcode setup${COLORS.reset} Interactive credential setup`)
|
|
271
|
-
log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Test credentials`)
|
|
272
|
-
log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools from workspace`)
|
|
273
|
-
log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List synced tools`)
|
|
274
|
-
log(` ${COLORS.cyan}tfcode test-agent <id>${COLORS.reset} Test agent prompt injection`)
|
|
275
|
-
log(` ${COLORS.cyan}tfcode debug${COLORS.reset} Show debug info`)
|
|
276
|
-
log(` ${COLORS.cyan}tfcode --help${COLORS.reset} Show this help`)
|
|
277
|
-
log(` ${COLORS.cyan}tfcode --version${COLORS.reset} Show version`)
|
|
278
|
-
log("")
|
|
279
|
-
log(`${COLORS.dim}For full TUI, run from source:${COLORS.reset}`)
|
|
280
|
-
log(`${COLORS.dim} bun run packages/tfcode/src/index.ts${COLORS.reset}`)
|
|
281
|
-
log("")
|
|
27
|
+
if (fs.existsSync('/etc/alpine-release')) return 'musl'
|
|
28
|
+
const r = spawnSync('ldd', ['--version'], { encoding: 'utf8' })
|
|
29
|
+
if ((r.stdout + r.stderr).toLowerCase().includes('musl')) return 'musl'
|
|
30
|
+
} catch {}
|
|
31
|
+
return ''
|
|
282
32
|
}
|
|
283
33
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
log(` ${COLORS.bold}TOOLS_FILE:${COLORS.reset} ${TOOLS_FILE}`)
|
|
290
|
-
log(` ${COLORS.bold}CONFIG_FILE:${COLORS.reset} ${CONFIG_FILE}`)
|
|
291
|
-
log(` ${COLORS.bold}CREDENTIALS_FILE:${COLORS.reset} ${CREDENTIALS_FILE}`)
|
|
292
|
-
log("")
|
|
293
|
-
log(` ${COLORS.bold}tools.json exists:${COLORS.reset} ${existsSync(TOOLS_FILE)}`)
|
|
294
|
-
if (existsSync(TOOLS_FILE)) {
|
|
295
|
-
const tools = loadCachedTools()
|
|
296
|
-
log(` ${COLORS.bold}tools.json valid:${COLORS.reset} ${tools?.success ?? false}`)
|
|
297
|
-
log(` ${COLORS.bold}tools count:${COLORS.reset} ${tools?.tools?.length ?? 0}`)
|
|
298
|
-
const coderAgents = tools?.tools?.filter((t) => t.tool_type === "coder_agent") ?? []
|
|
299
|
-
log(` ${COLORS.bold}coder_agent count:${COLORS.reset} ${coderAgents.length}`)
|
|
300
|
-
if (coderAgents.length > 0) {
|
|
301
|
-
log("")
|
|
302
|
-
log(` ${COLORS.bold}Coder Agents:${COLORS.reset}`)
|
|
303
|
-
coderAgents.forEach((a) => {
|
|
304
|
-
log(` - ${a.name} (id: ${a.id})`)
|
|
305
|
-
log(` interpolation_string: ${a.interpolation_string ? "YES" : "NO"}`)
|
|
306
|
-
log(` goals: ${a.goals ? "YES" : "NO"}`)
|
|
307
|
-
log(` llm_provider: ${a.llm_provider ?? "(null)"}`)
|
|
308
|
-
log(` llm_base_model: ${a.llm_base_model ?? "(null)"}`)
|
|
309
|
-
})
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
log("")
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function testAgentPrompt(agentId) {
|
|
316
|
-
const tools = loadCachedTools()
|
|
317
|
-
if (!tools?.success) {
|
|
318
|
-
error("No tools. Run: tfcode sync")
|
|
319
|
-
process.exit(1)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const agent = tools.tools.find((t) => t.id === agentId || t.name === agentId)
|
|
323
|
-
if (!agent) {
|
|
324
|
-
error(`Agent not found: ${agentId}`)
|
|
325
|
-
log("")
|
|
326
|
-
log("Available coder agents:")
|
|
327
|
-
tools.tools
|
|
328
|
-
.filter((t) => t.tool_type === "coder_agent")
|
|
329
|
-
.forEach((t) => {
|
|
330
|
-
log(` ${COLORS.cyan}${t.id}${COLORS.reset} - ${t.name}`)
|
|
331
|
-
})
|
|
332
|
-
process.exit(1)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
log("")
|
|
336
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
337
|
-
log(`${COLORS.bold}${COLORS.magenta} Agent Data from tools.json${COLORS.reset}`)
|
|
338
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
339
|
-
log("")
|
|
340
|
-
log(` ${COLORS.bold}id:${COLORS.reset} ${agent.id}`)
|
|
341
|
-
log(` ${COLORS.bold}name:${COLORS.reset} ${agent.name}`)
|
|
342
|
-
log(` ${COLORS.bold}description:${COLORS.reset} ${agent.description || "(none)"}`)
|
|
343
|
-
log(` ${COLORS.bold}tool_type:${COLORS.reset} ${agent.tool_type}`)
|
|
344
|
-
log(` ${COLORS.bold}auth_via:${COLORS.reset} ${agent.auth_via}`)
|
|
345
|
-
log("")
|
|
346
|
-
log(` ${COLORS.bold}interpolation_string:${COLORS.reset}`)
|
|
347
|
-
if (agent.interpolation_string) {
|
|
348
|
-
log(` ${agent.interpolation_string.substring(0, 200)}${agent.interpolation_string.length > 200 ? "..." : ""}`)
|
|
349
|
-
} else {
|
|
350
|
-
log(` ${COLORS.dim}(none)${COLORS.reset}`)
|
|
351
|
-
}
|
|
352
|
-
log("")
|
|
353
|
-
log(` ${COLORS.bold}goals:${COLORS.reset}`)
|
|
354
|
-
if (agent.goals) {
|
|
355
|
-
log(` ${agent.goals.substring(0, 200)}${agent.goals.length > 200 ? "..." : ""}`)
|
|
356
|
-
} else {
|
|
357
|
-
log(` ${COLORS.dim}(none)${COLORS.reset}`)
|
|
358
|
-
}
|
|
359
|
-
log("")
|
|
360
|
-
log(` ${COLORS.bold}temperature:${COLORS.reset} ${agent.temperature ?? "(none)"}`)
|
|
361
|
-
log(` ${COLORS.bold}max_tokens:${COLORS.reset} ${agent.max_tokens ?? "(none)"}`)
|
|
362
|
-
log(` ${COLORS.bold}llm_base_model:${COLORS.reset} ${agent.llm_base_model ?? "(none)"}`)
|
|
363
|
-
log(` ${COLORS.bold}llm_provider:${COLORS.reset} ${agent.llm_provider ?? "(none)"}`)
|
|
364
|
-
log("")
|
|
34
|
+
const baseline = needBaseline()
|
|
35
|
+
const abi = libcAbi()
|
|
36
|
+
let target = 'tfcode-' + platform + '-' + arch
|
|
37
|
+
if (baseline) target += '-baseline'
|
|
38
|
+
if (abi) target += '-' + abi
|
|
365
39
|
|
|
366
|
-
|
|
367
|
-
|
|
40
|
+
let binaryPath
|
|
41
|
+
try {
|
|
42
|
+
const dir = path.dirname(require.resolve('@toothfairyai/' + target + '/package.json'))
|
|
43
|
+
const candidate = path.join(dir, 'bin', binName)
|
|
44
|
+
if (fs.existsSync(candidate)) binaryPath = candidate
|
|
45
|
+
} catch {}
|
|
368
46
|
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
373
|
-
log(`${COLORS.bold}${COLORS.magenta} Model Mapping${COLORS.reset}`)
|
|
374
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
375
|
-
log("")
|
|
376
|
-
log(` ${COLORS.bold}isTFProvider:${COLORS.reset} ${isTFProvider}`)
|
|
377
|
-
log(
|
|
378
|
-
` ${COLORS.bold}mapped model:${COLORS.reset} ${isTFProvider && agent.llm_base_model ? `toothfairyai/${agent.llm_base_model}` : "(no mapping)"}`,
|
|
379
|
-
)
|
|
380
|
-
log("")
|
|
381
|
-
|
|
382
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
383
|
-
log(`${COLORS.bold}${COLORS.magenta} Highlighted Instructions Preview${COLORS.reset}`)
|
|
384
|
-
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
|
385
|
-
log("")
|
|
386
|
-
|
|
387
|
-
if (!hasPrompt && !hasGoals) {
|
|
388
|
-
log(
|
|
389
|
-
` ${COLORS.dim}(No interpolation_string or goals - no highlighted instructions will be generated)${COLORS.reset}`,
|
|
390
|
-
)
|
|
391
|
-
} else {
|
|
392
|
-
log("")
|
|
393
|
-
log("═══════════════════════════════════════════════════════════════════════════════")
|
|
394
|
-
log("⚠️ ULTRA IMPORTANT - AGENT CONFIGURATION ⚠️")
|
|
395
|
-
log("═══════════════════════════════════════════════════════════════════════════════")
|
|
396
|
-
log("")
|
|
397
|
-
log(`You are acting as the agent: "${agent.name}"`)
|
|
398
|
-
if (agent.description) {
|
|
399
|
-
log(`Description: ${agent.description}`)
|
|
400
|
-
}
|
|
401
|
-
log("")
|
|
402
|
-
log("The following instructions and goals are MANDATORY and MUST be followed")
|
|
403
|
-
log("with the HIGHEST PRIORITY. These override any conflicting default behaviors.")
|
|
404
|
-
log("═══════════════════════════════════════════════════════════════════════════════")
|
|
405
|
-
|
|
406
|
-
if (hasPrompt) {
|
|
407
|
-
log("")
|
|
408
|
-
log("┌─────────────────────────────────────────────────────────────────────────────┐")
|
|
409
|
-
log(`│ 🎯 AGENT "${agent.name}" INSTRUCTIONS (CRITICAL - MUST FOLLOW) │`)
|
|
410
|
-
log("└─────────────────────────────────────────────────────────────────────────────┘")
|
|
411
|
-
log("")
|
|
412
|
-
log(agent.interpolation_string)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (hasGoals) {
|
|
416
|
-
log("")
|
|
417
|
-
log("┌─────────────────────────────────────────────────────────────────────────────┐")
|
|
418
|
-
log(`│ 🎯 AGENT "${agent.name}" GOALS (CRITICAL - MUST ACHIEVE) │`)
|
|
419
|
-
log("└─────────────────────────────────────────────────────────────────────────────┘")
|
|
420
|
-
log("")
|
|
421
|
-
log(agent.goals)
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
log("")
|
|
425
|
-
log("═══════════════════════════════════════════════════════════════════════════════")
|
|
426
|
-
log(`⚠️ END OF ULTRA IMPORTANT AGENT "${agent.name}" CONFIGURATION ⚠️`)
|
|
427
|
-
log("═══════════════════════════════════════════════════════════════════════════════")
|
|
428
|
-
}
|
|
429
|
-
log("")
|
|
47
|
+
if (!binaryPath) {
|
|
48
|
+
const bundled = path.join(__dirname, binName)
|
|
49
|
+
if (fs.existsSync(bundled)) binaryPath = bundled
|
|
430
50
|
}
|
|
431
51
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
436
|
-
showHelp()
|
|
437
|
-
} else if (args.includes("--version") || args.includes("-v")) {
|
|
438
|
-
log("tfcode v1.0.0-beta.9")
|
|
439
|
-
} else if (command === "setup") {
|
|
440
|
-
interactiveSetup()
|
|
441
|
-
} else if (command === "validate") {
|
|
442
|
-
;(async () => {
|
|
443
|
-
const config = loadConfig()
|
|
444
|
-
if (!config) {
|
|
445
|
-
error("No credentials. Run: tfcode setup")
|
|
446
|
-
process.exit(1)
|
|
447
|
-
}
|
|
448
|
-
info("Validating...")
|
|
449
|
-
try {
|
|
450
|
-
const result = await runPythonSync("validate", config)
|
|
451
|
-
if (result.success) {
|
|
452
|
-
success("Credentials valid")
|
|
453
|
-
log(` API URL: ${result.base_url}`)
|
|
454
|
-
} else {
|
|
455
|
-
error(`Failed: ${result.error}`)
|
|
456
|
-
process.exit(1)
|
|
457
|
-
}
|
|
458
|
-
} catch (e) {
|
|
459
|
-
error(`Failed: ${e.message}`)
|
|
460
|
-
process.exit(1)
|
|
461
|
-
}
|
|
462
|
-
})()
|
|
463
|
-
} else if (command === "sync") {
|
|
464
|
-
;(async () => {
|
|
465
|
-
const config = loadConfig()
|
|
466
|
-
if (!config) {
|
|
467
|
-
error("No credentials. Run: tfcode setup")
|
|
468
|
-
process.exit(1)
|
|
469
|
-
}
|
|
470
|
-
info("Syncing tools...")
|
|
471
|
-
try {
|
|
472
|
-
const result = await runPythonSync("sync", config)
|
|
473
|
-
if (result.success) {
|
|
474
|
-
saveToolsCache(result)
|
|
475
|
-
success(`Synced ${result.tools.length} tools`)
|
|
476
|
-
if (result.by_type) {
|
|
477
|
-
log("")
|
|
478
|
-
log("By type:")
|
|
479
|
-
for (const [t, c] of Object.entries(result.by_type)) log(` ${t}: ${c}`)
|
|
480
|
-
}
|
|
481
|
-
} else {
|
|
482
|
-
error(`Failed: ${result.error}`)
|
|
483
|
-
process.exit(1)
|
|
484
|
-
}
|
|
485
|
-
} catch (e) {
|
|
486
|
-
error(`Failed: ${e.message}`)
|
|
487
|
-
process.exit(1)
|
|
488
|
-
}
|
|
489
|
-
})()
|
|
490
|
-
} else if (command === "tools" && args[1] === "list") {
|
|
491
|
-
const cached = loadCachedTools()
|
|
492
|
-
if (!cached?.success) {
|
|
493
|
-
error("No tools. Run: tfcode sync")
|
|
494
|
-
process.exit(1)
|
|
495
|
-
}
|
|
496
|
-
let tools = cached.tools
|
|
497
|
-
if (args[2] === "--type" && args[3]) tools = tools.filter((t) => t.tool_type === args[3])
|
|
498
|
-
log(`\n${tools.length} tool(s):\n`)
|
|
499
|
-
for (const t of tools) {
|
|
500
|
-
log(` ${COLORS.cyan}${t.name}${COLORS.reset}`)
|
|
501
|
-
log(` Type: ${t.tool_type}`)
|
|
502
|
-
if (t.description) log(` ${COLORS.dim}${t.description.slice(0, 60)}${COLORS.reset}`)
|
|
503
|
-
log(` Auth: ${t.auth_via}\n`)
|
|
504
|
-
}
|
|
505
|
-
} else if (command === "test-agent") {
|
|
506
|
-
const agentId = args[1]
|
|
507
|
-
if (!agentId) {
|
|
508
|
-
error("Usage: tfcode test-agent <agent-id-or-name>")
|
|
509
|
-
process.exit(1)
|
|
510
|
-
}
|
|
511
|
-
testAgentPrompt(agentId)
|
|
512
|
-
} else if (command === "debug") {
|
|
513
|
-
showDebugInfo()
|
|
514
|
-
} else if (!command) {
|
|
515
|
-
// Show help instead of trying TUI (TUI requires full build)
|
|
516
|
-
showHelp()
|
|
517
|
-
} else {
|
|
518
|
-
error(`Unknown command: ${command}`)
|
|
519
|
-
showHelp()
|
|
52
|
+
if (!binaryPath) {
|
|
53
|
+
console.error('tfcode binary not found for ' + target + '. Try: npm install -g --allow-scripts=@toothfairyai/tfcode')
|
|
520
54
|
process.exit(1)
|
|
521
55
|
}
|
|
56
|
+
|
|
57
|
+
const child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit', env: process.env })
|
|
58
|
+
child.on('exit', (code) => process.exit(code || 0))
|
|
59
|
+
child.on('error', (err) => { console.error('Failed to start tfcode:', err.message); process.exit(1) })
|