@toothfairyai/tfcode 2.5.6 → 2.5.8

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 CHANGED
File without changes
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 CHANGED
@@ -1,59 +1,521 @@
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')
6
2
 
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'
3
+ import { spawn } from "child_process"
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"
5
+ import { join, dirname } from "path"
6
+ import { homedir } from "os"
7
+ import * as readline from "readline"
8
+ import { fileURLToPath } from "url"
10
9
 
11
- function needBaseline() {
12
- if (arch !== 'x64' || platform === 'windows') return false
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = dirname(__filename)
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
13
122
  try {
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'
123
+ return JSON.parse(readFileSync(TOOLS_FILE, "utf-8"))
19
124
  } catch {
20
- return false
125
+ return null
21
126
  }
22
127
  }
23
128
 
24
- function libcAbi() {
25
- if (platform !== 'linux') return ''
129
+ function saveToolsCache(tools) {
130
+ ensureConfigDir()
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
+
26
219
  try {
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 ''
220
+ const result = await runPythonSync("validate", config)
221
+ if (result.success) {
222
+ success("Credentials valid!")
223
+ log(` API URL: ${result.base_url}`)
224
+ log(` Workspace ID: ${result.workspace_id}`)
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("")
32
282
  }
33
283
 
34
- const baseline = needBaseline()
35
- const abi = libcAbi()
36
- let target = 'tfcode-' + platform + '-' + arch
37
- if (baseline) target += '-baseline'
38
- if (abi) target += '-' + abi
284
+ function showDebugInfo() {
285
+ log("")
286
+ log(`${COLORS.bold}Debug Information${COLORS.reset}`)
287
+ log("")
288
+ log(` ${COLORS.bold}TFCODE_DIR:${COLORS.reset} ${TFCODE_DIR}`)
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("")
39
365
 
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 {}
366
+ // Build highlighted instructions
367
+ const isTFProvider = !agent.llm_provider || agent.llm_provider === "toothfairyai" || agent.llm_provider === "tf"
46
368
 
47
- if (!binaryPath) {
48
- const bundled = path.join(__dirname, binName)
49
- if (fs.existsSync(bundled)) binaryPath = bundled
369
+ const hasPrompt = agent.interpolation_string && agent.interpolation_string.trim().length > 0
370
+ const hasGoals = agent.goals && agent.goals.trim().length > 0
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("")
50
430
  }
51
431
 
52
- if (!binaryPath) {
53
- console.error('tfcode binary not found for ' + target + '. Try: npm install -g --allow-scripts=@toothfairyai/tfcode')
432
+ const args = process.argv.slice(2)
433
+ const command = args[0]
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()
54
520
  process.exit(1)
55
521
  }
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) })