@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/package.json
CHANGED
|
@@ -1,169 +1,33 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "2.5.8",
|
|
4
2
|
"name": "@toothfairyai/tfcode",
|
|
5
|
-
"
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"files": [
|
|
8
|
-
"bin",
|
|
9
|
-
"app",
|
|
10
|
-
"postinstall.mjs",
|
|
11
|
-
"LICENSE"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"prepare": "effect-language-service patch || true",
|
|
15
|
-
"postinstall": "node scripts/postinstall.cjs",
|
|
16
|
-
"typecheck": "tsgo --noEmit",
|
|
17
|
-
"test": "bun test --timeout 30000",
|
|
18
|
-
"build": "bun run script/build-tfcode.ts",
|
|
19
|
-
"build:single": "bun run script/build-tfcode.ts --single",
|
|
20
|
-
"publish:upload": "bun run script/publish-tfcode.ts --upload",
|
|
21
|
-
"publish:npm": "bun run script/publish-tfcode.ts --npm",
|
|
22
|
-
"publish:brew": "bun run script/publish-tfcode.ts --brew",
|
|
23
|
-
"publish:all": "bun run script/publish-tfcode.ts --all",
|
|
24
|
-
"dev": "bun run --conditions=browser ./src/index.ts",
|
|
25
|
-
"db": "bun drizzle-kit"
|
|
26
|
-
},
|
|
3
|
+
"version": "2.5.9",
|
|
27
4
|
"bin": {
|
|
28
|
-
"tfcode": "./bin/tfcode"
|
|
5
|
+
"tfcode": "./bin/tfcode.js"
|
|
29
6
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
},
|
|
33
|
-
"imports": {
|
|
34
|
-
"#db": {
|
|
35
|
-
"bun": "./src/storage/db.bun.ts",
|
|
36
|
-
"node": "./src/storage/db.node.ts",
|
|
37
|
-
"default": "./src/storage/db.bun.ts"
|
|
38
|
-
}
|
|
7
|
+
"scripts": {
|
|
8
|
+
"postinstall": "node ./postinstall.mjs"
|
|
39
9
|
},
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"@
|
|
54
|
-
"@tsconfig/bun": "catalog:",
|
|
55
|
-
"@types/babel__core": "7.20.5",
|
|
56
|
-
"@types/bun": "1.3.11",
|
|
57
|
-
"@types/cross-spawn": "6.0.6",
|
|
58
|
-
"@types/mime-types": "3.0.1",
|
|
59
|
-
"@types/node": "22.13.9",
|
|
60
|
-
"@types/semver": "^7.5.8",
|
|
61
|
-
"@types/turndown": "5.0.5",
|
|
62
|
-
"@types/which": "3.0.4",
|
|
63
|
-
"@types/ws": "8.18.1",
|
|
64
|
-
"@types/yargs": "17.0.33",
|
|
65
|
-
"@typescript/native-preview": "catalog:",
|
|
66
|
-
"drizzle-kit": "catalog:",
|
|
67
|
-
"drizzle-orm": "catalog:",
|
|
68
|
-
"typescript": "catalog:",
|
|
69
|
-
"vscode-languageserver-types": "3.17.5",
|
|
70
|
-
"why-is-node-running": "3.2.2",
|
|
71
|
-
"zod-to-json-schema": "3.24.5"
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"optionalDependencies": {
|
|
12
|
+
"@toothfairyai/tfcode-linux-arm64": "2.5.9",
|
|
13
|
+
"@toothfairyai/tfcode-windows-x64": "2.5.9",
|
|
14
|
+
"@toothfairyai/tfcode-linux-x64-baseline-musl": "2.5.9",
|
|
15
|
+
"@toothfairyai/tfcode-darwin-x64-baseline": "2.5.9",
|
|
16
|
+
"@toothfairyai/tfcode-linux-x64-musl": "2.5.9",
|
|
17
|
+
"@toothfairyai/tfcode-windows-x64-baseline": "2.5.9",
|
|
18
|
+
"@toothfairyai/tfcode-linux-arm64-musl": "2.5.9",
|
|
19
|
+
"@toothfairyai/tfcode-windows-arm64": "2.5.9",
|
|
20
|
+
"@toothfairyai/tfcode-linux-x64": "2.5.9",
|
|
21
|
+
"@toothfairyai/tfcode-darwin-x64": "2.5.9",
|
|
22
|
+
"@toothfairyai/tfcode-linux-x64-baseline": "2.5.9",
|
|
23
|
+
"@toothfairyai/tfcode-darwin-arm64": "2.5.9"
|
|
72
24
|
},
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"@actions/github": "6.0.1",
|
|
76
|
-
"@agentclientprotocol/sdk": "0.14.1",
|
|
77
|
-
"@ai-sdk/amazon-bedrock": "3.0.82",
|
|
78
|
-
"@ai-sdk/anthropic": "2.0.65",
|
|
79
|
-
"@ai-sdk/azure": "2.0.91",
|
|
80
|
-
"@ai-sdk/cerebras": "1.0.36",
|
|
81
|
-
"@ai-sdk/cohere": "2.0.22",
|
|
82
|
-
"@ai-sdk/deepinfra": "1.0.36",
|
|
83
|
-
"@ai-sdk/gateway": "2.0.30",
|
|
84
|
-
"@ai-sdk/google": "2.0.54",
|
|
85
|
-
"@ai-sdk/google-vertex": "3.0.106",
|
|
86
|
-
"@ai-sdk/groq": "2.0.34",
|
|
87
|
-
"@ai-sdk/mistral": "2.0.27",
|
|
88
|
-
"@ai-sdk/openai": "2.0.89",
|
|
89
|
-
"@ai-sdk/openai-compatible": "1.0.32",
|
|
90
|
-
"@ai-sdk/perplexity": "2.0.23",
|
|
91
|
-
"@ai-sdk/provider": "2.0.1",
|
|
92
|
-
"@ai-sdk/provider-utils": "3.0.21",
|
|
93
|
-
"@ai-sdk/togetherai": "1.0.34",
|
|
94
|
-
"@ai-sdk/vercel": "1.0.33",
|
|
95
|
-
"@ai-sdk/xai": "2.0.51",
|
|
96
|
-
"@aws-sdk/credential-providers": "3.993.0",
|
|
97
|
-
"@clack/prompts": "1.0.0-alpha.1",
|
|
98
|
-
"@effect/platform-node": "catalog:",
|
|
99
|
-
"@hono/standard-validator": "0.1.5",
|
|
100
|
-
"@hono/zod-validator": "catalog:",
|
|
101
|
-
"@modelcontextprotocol/sdk": "1.29.0",
|
|
102
|
-
"@octokit/graphql": "9.0.2",
|
|
103
|
-
"@octokit/rest": "catalog:",
|
|
104
|
-
"@openauthjs/openauth": "catalog:",
|
|
105
|
-
"@opencode-ai/gateway": "workspace:*",
|
|
106
|
-
"@opencode-ai/plugin": "workspace:*",
|
|
107
|
-
"@opencode-ai/script": "workspace:*",
|
|
108
|
-
"@opencode-ai/sdk": "workspace:*",
|
|
109
|
-
"@opencode-ai/util": "workspace:*",
|
|
110
|
-
"@openrouter/ai-sdk-provider": "1.5.4",
|
|
111
|
-
"@opentui/core": "0.2.16",
|
|
112
|
-
"@opentui/solid": "0.2.16",
|
|
113
|
-
"@parcel/watcher": "2.5.1",
|
|
114
|
-
"@pierre/diffs": "catalog:",
|
|
115
|
-
"@solid-primitives/event-bus": "1.1.2",
|
|
116
|
-
"@solid-primitives/scheduled": "1.5.2",
|
|
117
|
-
"@standard-schema/spec": "1.0.0",
|
|
118
|
-
"@zip.js/zip.js": "2.7.62",
|
|
119
|
-
"ai": "catalog:",
|
|
120
|
-
"ai-gateway-provider": "2.3.1",
|
|
121
|
-
"bonjour-service": "1.3.0",
|
|
122
|
-
"bun-pty": "0.4.8",
|
|
123
|
-
"chokidar": "4.0.3",
|
|
124
|
-
"clipboardy": "4.0.0",
|
|
125
|
-
"croner": "10.0.1",
|
|
126
|
-
"cross-spawn": "^7.0.6",
|
|
127
|
-
"decimal.js": "10.5.0",
|
|
128
|
-
"diff": "catalog:",
|
|
129
|
-
"drizzle-orm": "catalog:",
|
|
130
|
-
"effect": "catalog:",
|
|
131
|
-
"fuzzysort": "3.1.0",
|
|
132
|
-
"gitlab-ai-provider": "5.2.2",
|
|
133
|
-
"glob": "13.0.5",
|
|
134
|
-
"google-auth-library": "10.5.0",
|
|
135
|
-
"gray-matter": "4.0.3",
|
|
136
|
-
"hono": "catalog:",
|
|
137
|
-
"hono-openapi": "catalog:",
|
|
138
|
-
"ignore": "7.0.5",
|
|
139
|
-
"jsonc-parser": "3.3.1",
|
|
140
|
-
"marked": "catalog:",
|
|
141
|
-
"mime-types": "3.0.2",
|
|
142
|
-
"minimatch": "10.0.3",
|
|
143
|
-
"open": "10.1.2",
|
|
144
|
-
"opencode-gitlab-auth": "2.0.0",
|
|
145
|
-
"opentui-spinner": "0.0.6",
|
|
146
|
-
"partial-json": "0.1.7",
|
|
147
|
-
"react": "19.2.4",
|
|
148
|
-
"react-dom": "19.2.4",
|
|
149
|
-
"remeda": "catalog:",
|
|
150
|
-
"semver": "^7.6.3",
|
|
151
|
-
"solid-js": "catalog:",
|
|
152
|
-
"strip-ansi": "7.1.2",
|
|
153
|
-
"tree-sitter-bash": "0.25.0",
|
|
154
|
-
"tree-sitter-powershell": "0.25.10",
|
|
155
|
-
"turndown": "7.2.0",
|
|
156
|
-
"ulid": "catalog:",
|
|
157
|
-
"vscode-jsonrpc": "8.2.1",
|
|
158
|
-
"web-tree-sitter": "0.25.10",
|
|
159
|
-
"which": "6.0.1",
|
|
160
|
-
"ws": "8.21.0",
|
|
161
|
-
"xdg-basedir": "5.1.0",
|
|
162
|
-
"yargs": "18.0.0",
|
|
163
|
-
"zod": "catalog:",
|
|
164
|
-
"zod-to-json-schema": "3.24.5"
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
165
27
|
},
|
|
166
|
-
"
|
|
167
|
-
|
|
28
|
+
"homepage": "https://toothfairyai.com/developers/tfcode",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git"
|
|
168
32
|
}
|
|
169
|
-
}
|
|
33
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import os from "os"
|
|
6
|
+
import { spawnSync } from "child_process"
|
|
7
|
+
import { fileURLToPath } from "url"
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
10
|
+
const __dirname = path.dirname(__filename)
|
|
11
|
+
|
|
12
|
+
const GITEA_HOST = process.env.TFCODE_GITEA_HOST || "gitea.toothfairyai.com"
|
|
13
|
+
const GITEA_REPO = process.env.TFCODE_GITEA_REPO || "ToothFairyAI/tf_code"
|
|
14
|
+
|
|
15
|
+
function detectPlatform() {
|
|
16
|
+
let platform
|
|
17
|
+
switch (os.platform()) {
|
|
18
|
+
case "darwin":
|
|
19
|
+
platform = "darwin"
|
|
20
|
+
break
|
|
21
|
+
case "linux":
|
|
22
|
+
platform = "linux"
|
|
23
|
+
break
|
|
24
|
+
case "win32":
|
|
25
|
+
platform = "windows"
|
|
26
|
+
break
|
|
27
|
+
default:
|
|
28
|
+
platform = os.platform()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let arch
|
|
32
|
+
switch (os.arch()) {
|
|
33
|
+
case "x64":
|
|
34
|
+
arch = "x64"
|
|
35
|
+
break
|
|
36
|
+
case "arm64":
|
|
37
|
+
arch = "arm64"
|
|
38
|
+
break
|
|
39
|
+
default:
|
|
40
|
+
arch = os.arch()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check for AVX2 on x64
|
|
44
|
+
let needsBaseline = false
|
|
45
|
+
if (arch === "x64" && (platform === "linux" || platform === "darwin")) {
|
|
46
|
+
try {
|
|
47
|
+
if (platform === "linux") {
|
|
48
|
+
const cpuinfo = fs.readFileSync("/proc/cpuinfo", "utf8")
|
|
49
|
+
needsBaseline = !cpuinfo.toLowerCase().includes("avx2")
|
|
50
|
+
} else if (platform === "darwin") {
|
|
51
|
+
const result = spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], { encoding: "utf8" })
|
|
52
|
+
needsBaseline = result.stdout.trim() !== "1"
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check for musl on Linux
|
|
58
|
+
let abi = ""
|
|
59
|
+
if (platform === "linux") {
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync("/etc/alpine-release")) {
|
|
62
|
+
abi = "musl"
|
|
63
|
+
} else {
|
|
64
|
+
const result = spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
65
|
+
if ((result.stdout + result.stderr).toLowerCase().includes("musl")) {
|
|
66
|
+
abi = "musl"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { platform, arch, needsBaseline, abi }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getVersion() {
|
|
76
|
+
try {
|
|
77
|
+
const res = await fetch(`https://${GITEA_HOST}/api/v1/repos/${GITEA_REPO}/releases/latest`)
|
|
78
|
+
const data = await res.json()
|
|
79
|
+
return data.tag_name?.replace(/^v/, "") || "1.0.0"
|
|
80
|
+
} catch {
|
|
81
|
+
return "1.0.0"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Detect the machine architecture of an existing binary. Returns "arm64",
|
|
86
|
+
// "x64", or null when it can't be determined (null => keep existing binary).
|
|
87
|
+
function detectBinaryMachineArch(binaryPath, platform) {
|
|
88
|
+
try {
|
|
89
|
+
if (platform === "win32") {
|
|
90
|
+
const buf = fs.readFileSync(binaryPath)
|
|
91
|
+
if (buf.length < 0x40) return null
|
|
92
|
+
const pe = buf.readUInt32LE(0x3c)
|
|
93
|
+
if (pe + 6 > buf.length) return null
|
|
94
|
+
const machine = buf.readUInt16LE(pe + 4)
|
|
95
|
+
if (machine === 0x8664) return "x64"
|
|
96
|
+
if (machine === 0xaa64) return "arm64"
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
const out = spawnSync("file", [binaryPath], { encoding: "utf8" }).stdout || ""
|
|
100
|
+
if (/arm64|aarch64/i.test(out)) return "arm64"
|
|
101
|
+
if (/x86-64|x86_64/i.test(out)) return "x64"
|
|
102
|
+
return null
|
|
103
|
+
} catch {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// A missing/unparseable binary is treated as a match so installs never break
|
|
109
|
+
// when detection isn't possible; a real mismatch forces a re-download.
|
|
110
|
+
function binaryArchMatches(binaryPath, expectedArch, platform) {
|
|
111
|
+
const actual = detectBinaryMachineArch(binaryPath, platform)
|
|
112
|
+
if (!actual) return true
|
|
113
|
+
return actual === expectedArch
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function downloadBinary() {
|
|
117
|
+
const { platform, arch, needsBaseline, abi } = detectPlatform()
|
|
118
|
+
const version = await getVersion()
|
|
119
|
+
|
|
120
|
+
// Check if binary already exists (included in npm package)
|
|
121
|
+
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
|
|
122
|
+
const binDir = path.join(__dirname, "bin")
|
|
123
|
+
const existingBinary = path.join(binDir, binaryName)
|
|
124
|
+
|
|
125
|
+
if (fs.existsSync(existingBinary) && binaryArchMatches(existingBinary, arch, platform)) {
|
|
126
|
+
console.log(`✓ Binary already exists at ${existingBinary}`)
|
|
127
|
+
// Still need to copy app dir from platform package if missing
|
|
128
|
+
const targetAppDir = path.join(binDir, "app")
|
|
129
|
+
if (!fs.existsSync(path.join(targetAppDir, "dist", "index.html"))) {
|
|
130
|
+
let target = `tfcode-${platform}-${arch}`
|
|
131
|
+
if (needsBaseline) target += "-baseline"
|
|
132
|
+
if (abi) target += `-${abi}`
|
|
133
|
+
const pkgName = `@toothfairyai/${target}`
|
|
134
|
+
try {
|
|
135
|
+
const pkgUrl = import.meta.resolve(`${pkgName}/package.json`)
|
|
136
|
+
const pkgDir = path.dirname(fileURLToPath(pkgUrl))
|
|
137
|
+
const srcAppDir = path.join(pkgDir, "bin", "app")
|
|
138
|
+
if (fs.existsSync(path.join(srcAppDir, "dist", "index.html"))) {
|
|
139
|
+
fs.cpSync(srcAppDir, targetAppDir, { recursive: true })
|
|
140
|
+
console.log("✓ Web app copied from platform package")
|
|
141
|
+
}
|
|
142
|
+
copyComputerAssets(path.join(pkgDir, "bin"), binDir)
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.log(`! Could not copy web app: ${e.message}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build filename for download
|
|
151
|
+
let target = `tfcode-${platform}-${arch}`
|
|
152
|
+
if (needsBaseline) target += "-baseline"
|
|
153
|
+
if (abi) target += `-${abi}`
|
|
154
|
+
|
|
155
|
+
const ext = platform === "linux" ? ".tar.gz" : ".zip"
|
|
156
|
+
const filename = `${target}${ext}`
|
|
157
|
+
const url = `https://${GITEA_HOST}/${GITEA_REPO}/releases/download/v${version}/${filename}`
|
|
158
|
+
|
|
159
|
+
console.log(`Downloading tfcode v${version} for ${target}...`)
|
|
160
|
+
|
|
161
|
+
// Download
|
|
162
|
+
if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true })
|
|
163
|
+
|
|
164
|
+
const tmpDir = path.join(os.tmpdir(), `tfcode-install-${process.pid}`)
|
|
165
|
+
fs.mkdirSync(tmpDir, { recursive: true })
|
|
166
|
+
const archivePath = path.join(tmpDir, filename)
|
|
167
|
+
|
|
168
|
+
// Use curl to download
|
|
169
|
+
const curlResult = spawnSync("curl", ["-fsSL", "-o", archivePath, url], { stdio: "inherit" })
|
|
170
|
+
if (curlResult.status !== 0) {
|
|
171
|
+
console.error(`Failed to download from ${url}`)
|
|
172
|
+
console.error("Trying npm package fallback...")
|
|
173
|
+
|
|
174
|
+
// Fallback to npm optionalDependencies
|
|
175
|
+
try {
|
|
176
|
+
const pkgName = `@toothfairyai/${target}`
|
|
177
|
+
|
|
178
|
+
// ESM-compatible module resolution
|
|
179
|
+
let pkgDir
|
|
180
|
+
try {
|
|
181
|
+
const pkgUrl = import.meta.resolve(`${pkgName}/package.json`)
|
|
182
|
+
const pkgPath = fileURLToPath(pkgUrl)
|
|
183
|
+
pkgDir = path.dirname(pkgPath)
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error(`Could not resolve ${pkgName}:`, e.message)
|
|
186
|
+
throw e
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
|
|
190
|
+
const binaryPath = path.join(pkgDir, "bin", binaryName)
|
|
191
|
+
|
|
192
|
+
console.log(`Looking for binary at: ${binaryPath}`)
|
|
193
|
+
|
|
194
|
+
if (fs.existsSync(binaryPath)) {
|
|
195
|
+
console.log(`Found binary at npm package location`)
|
|
196
|
+
setupBinary(binaryPath, platform)
|
|
197
|
+
return
|
|
198
|
+
} else {
|
|
199
|
+
console.error(`Binary not found at ${binaryPath}`)
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.error("npm package fallback failed:", e.message)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.error("")
|
|
206
|
+
console.error("Installation failed. The binary could not be downloaded or found.")
|
|
207
|
+
console.error("")
|
|
208
|
+
console.error("Possible solutions:")
|
|
209
|
+
console.error(" 1. If this is a private installation, set TFCODE_GITEA_HOST to an accessible host")
|
|
210
|
+
console.error(" 2. Manually download the binary and place it in the bin/ directory")
|
|
211
|
+
console.error(" 3. Contact ToothFairyAI support for assistance")
|
|
212
|
+
console.error("")
|
|
213
|
+
process.exit(1)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Extract
|
|
217
|
+
console.log("Extracting...")
|
|
218
|
+
if (platform === "linux") {
|
|
219
|
+
spawnSync("tar", ["-xzf", archivePath, "-C", tmpDir], { stdio: "inherit" })
|
|
220
|
+
} else {
|
|
221
|
+
spawnSync("unzip", ["-q", archivePath, "-d", tmpDir], { stdio: "inherit" })
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Move binary
|
|
225
|
+
const extractedBinary = path.join(tmpDir, "tfcode")
|
|
226
|
+
const targetBinary = path.join(binDir, binaryName)
|
|
227
|
+
|
|
228
|
+
if (fs.existsSync(extractedBinary)) {
|
|
229
|
+
if (fs.existsSync(targetBinary)) fs.unlinkSync(targetBinary)
|
|
230
|
+
fs.copyFileSync(extractedBinary, targetBinary)
|
|
231
|
+
fs.chmodSync(targetBinary, 0o755)
|
|
232
|
+
console.log(`Installed tfcode to ${targetBinary}`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Move app directory
|
|
236
|
+
const extractedAppDir = path.join(tmpDir, "app")
|
|
237
|
+
const targetAppDir = path.join(binDir, "app")
|
|
238
|
+
if (fs.existsSync(path.join(extractedAppDir, "dist", "index.html"))) {
|
|
239
|
+
if (fs.existsSync(targetAppDir)) fs.rmSync(targetAppDir, { recursive: true, force: true })
|
|
240
|
+
fs.cpSync(extractedAppDir, targetAppDir, { recursive: true })
|
|
241
|
+
console.log("Installed web app")
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Move bundled computer-use helpers (cliclick on macOS, if archived)
|
|
245
|
+
copyComputerAssets(tmpDir, binDir)
|
|
246
|
+
|
|
247
|
+
// Cleanup
|
|
248
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Copy bundled computer-use helper binaries (e.g. cliclick on macOS) from a
|
|
252
|
+
// source bin dir into the installed bin dir, so `tfcode computer setup` finds
|
|
253
|
+
// them next to the binary without Homebrew/apt. No-op when absent. npm may
|
|
254
|
+
// strip the exec bit, so chmod the helpers back to 0755.
|
|
255
|
+
function copyComputerAssets(srcBinDir, dstBinDir) {
|
|
256
|
+
const srcComputer = path.join(srcBinDir, "computer")
|
|
257
|
+
if (!fs.existsSync(srcComputer)) return
|
|
258
|
+
const dstComputer = path.join(dstBinDir, "computer")
|
|
259
|
+
if (fs.existsSync(dstComputer)) fs.rmSync(dstComputer, { recursive: true, force: true })
|
|
260
|
+
fs.cpSync(srcComputer, dstComputer, { recursive: true })
|
|
261
|
+
for (const name of fs.readdirSync(dstComputer)) {
|
|
262
|
+
try {
|
|
263
|
+
fs.chmodSync(path.join(dstComputer, name), 0o755)
|
|
264
|
+
} catch {}
|
|
265
|
+
}
|
|
266
|
+
console.log("✓ Computer-use helpers copied from platform package")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function setupBinary(sourcePath, platform) {
|
|
270
|
+
const binDir = path.join(__dirname, "bin")
|
|
271
|
+
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
|
|
272
|
+
const targetBinary = path.join(binDir, binaryName)
|
|
273
|
+
|
|
274
|
+
if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true })
|
|
275
|
+
if (fs.existsSync(targetBinary)) fs.unlinkSync(targetBinary)
|
|
276
|
+
|
|
277
|
+
// Try hardlink, fall back to copy
|
|
278
|
+
try {
|
|
279
|
+
fs.linkSync(sourcePath, targetBinary)
|
|
280
|
+
} catch {
|
|
281
|
+
fs.copyFileSync(sourcePath, targetBinary)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fs.chmodSync(targetBinary, 0o755)
|
|
285
|
+
console.log(`tfcode installed to ${targetBinary}`)
|
|
286
|
+
|
|
287
|
+
// Also copy app dir from platform package
|
|
288
|
+
const sourceDir = path.dirname(sourcePath)
|
|
289
|
+
const srcAppDir = path.join(sourceDir, "app")
|
|
290
|
+
const targetAppDir = path.join(binDir, "app")
|
|
291
|
+
if (fs.existsSync(path.join(srcAppDir, "dist", "index.html"))) {
|
|
292
|
+
if (fs.existsSync(targetAppDir)) fs.rmSync(targetAppDir, { recursive: true, force: true })
|
|
293
|
+
fs.cpSync(srcAppDir, targetAppDir, { recursive: true })
|
|
294
|
+
console.log("Web app copied from platform package")
|
|
295
|
+
}
|
|
296
|
+
// Also copy bundled computer-use helpers (cliclick on macOS) if present.
|
|
297
|
+
copyComputerAssets(sourceDir, binDir)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function main() {
|
|
301
|
+
console.log("")
|
|
302
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
303
|
+
console.log(" tfcode - ToothFairyAI's official coding agent")
|
|
304
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
305
|
+
console.log("")
|
|
306
|
+
|
|
307
|
+
// Download and setup binary
|
|
308
|
+
await downloadBinary()
|
|
309
|
+
|
|
310
|
+
// Check for Python (needed for TF integration)
|
|
311
|
+
try {
|
|
312
|
+
const result = spawnSync("python3", ["--version"], { encoding: "utf8" })
|
|
313
|
+
if (result.status === 0) {
|
|
314
|
+
console.log(`✓ Found ${result.stdout.trim()}`)
|
|
315
|
+
|
|
316
|
+
// Install Python SDK
|
|
317
|
+
console.log("Installing ToothFairyAI Python SDK...")
|
|
318
|
+
const pipResult = spawnSync(
|
|
319
|
+
"python3",
|
|
320
|
+
["-m", "pip", "install", "--user", "--break-system-packages", "toothfairyai", "pydantic", "httpx", "rich"],
|
|
321
|
+
{
|
|
322
|
+
stdio: "inherit",
|
|
323
|
+
},
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if (pipResult.status === 0) {
|
|
327
|
+
console.log("✓ Python SDK installed")
|
|
328
|
+
} else {
|
|
329
|
+
console.log("! Python SDK install failed, run manually:")
|
|
330
|
+
console.log(" pip install toothfairyai pydantic httpx rich")
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
console.log("! Python 3.10+ not found. Install with:")
|
|
335
|
+
console.log(" macOS: brew install python@3.12")
|
|
336
|
+
console.log(" Ubuntu: sudo apt install python3.12")
|
|
337
|
+
console.log(" Windows: Download from python.org/downloads")
|
|
338
|
+
console.log("")
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log("")
|
|
342
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
343
|
+
console.log("✓ tfcode installed successfully!")
|
|
344
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
345
|
+
console.log("")
|
|
346
|
+
console.log("Quick Start:")
|
|
347
|
+
console.log("")
|
|
348
|
+
console.log(" 1. Set credentials:")
|
|
349
|
+
console.log(' export TF_WORKSPACE_ID="your-workspace-id"')
|
|
350
|
+
console.log(' export TF_API_KEY="your-api-key"')
|
|
351
|
+
console.log("")
|
|
352
|
+
console.log(" 2. Validate:")
|
|
353
|
+
console.log(" tfcode validate")
|
|
354
|
+
console.log("")
|
|
355
|
+
console.log(" 3. Sync tools:")
|
|
356
|
+
console.log(" tfcode sync")
|
|
357
|
+
console.log("")
|
|
358
|
+
console.log(" 4. Start coding:")
|
|
359
|
+
console.log(" tfcode")
|
|
360
|
+
console.log("")
|
|
361
|
+
// Optional feature — keep it short and clearly labeled so the coding-first
|
|
362
|
+
// Quick Start isn't noised up. Point npm installers at the one-command setup.
|
|
363
|
+
const cuPlatform = os.platform()
|
|
364
|
+
const cuHint =
|
|
365
|
+
cuPlatform === "darwin"
|
|
366
|
+
? "a permissions dialog will appear (Accessibility & Screen Recording); cliclick is bundled."
|
|
367
|
+
: cuPlatform === "win32"
|
|
368
|
+
? "no extra installs needed — pre-compiles the input helper."
|
|
369
|
+
: "installs xdotool + a screenshot tool via your package manager."
|
|
370
|
+
console.log(" Computer use (optional — control the OS GUI: mouse, keyboard, screen):")
|
|
371
|
+
console.log(" tfcode computer setup " + cuHint)
|
|
372
|
+
console.log(" tfcode computer doctor verify it works")
|
|
373
|
+
console.log("")
|
|
374
|
+
console.log("Documentation: https://toothfairyai.com/developers/tfcode")
|
|
375
|
+
console.log("")
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
main().catch(console.error)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tf-sync: ToothFairyAI workspace sync layer for tfcode
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from tf_sync.agents import sync_agents
|
|
6
|
+
from tf_sync.mcp import sync_mcp_servers
|
|
7
|
+
from tf_sync.tools import sync_tools, ToolType
|
|
8
|
+
from tf_sync.config import TFConfig, load_config, validate_credentials, get_region_urls
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"sync_agents",
|
|
12
|
+
"sync_mcp_servers",
|
|
13
|
+
"sync_tools",
|
|
14
|
+
"ToolType",
|
|
15
|
+
"TFConfig",
|
|
16
|
+
"load_config",
|
|
17
|
+
"validate_credentials",
|
|
18
|
+
"get_region_urls",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent sync module for tfcode.
|
|
3
|
+
|
|
4
|
+
NOTE: This module is reserved for future implementation.
|
|
5
|
+
Currently, tfcode only syncs tools (MCP, Skills, Database, Functions).
|
|
6
|
+
Agent sync will be added in a later phase.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from tf_sync.config import TFConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentSyncResult(BaseModel):
|
|
17
|
+
"""Result of agent sync operation."""
|
|
18
|
+
|
|
19
|
+
success: bool
|
|
20
|
+
agents: list[dict[str, Any]] = []
|
|
21
|
+
error: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def sync_agents(config: TFConfig) -> AgentSyncResult:
|
|
25
|
+
"""
|
|
26
|
+
Sync agents from ToothFairyAI workspace.
|
|
27
|
+
|
|
28
|
+
NOTE: Currently not implemented. Reserved for future use.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config: TFConfig instance
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
AgentSyncResult (currently always returns not implemented)
|
|
35
|
+
"""
|
|
36
|
+
return AgentSyncResult(
|
|
37
|
+
success=False,
|
|
38
|
+
error="Agent sync not yet implemented. Use tools sync for now.",
|
|
39
|
+
)
|