@wwkit/freetoken 1.0.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/cli/helpers/args.js +48 -0
  4. package/cli/index.js +374 -0
  5. package/cli/pid-manager.js +87 -0
  6. package/client/index.html +25 -0
  7. package/client/public/favicon.svg +1 -0
  8. package/client/public/icons.svg +24 -0
  9. package/client/src/App.vue +136 -0
  10. package/client/src/assets/vite.svg +1 -0
  11. package/client/src/assets/vue.svg +1 -0
  12. package/client/src/components/ChatBox.vue +320 -0
  13. package/client/src/components/CheckList.vue +52 -0
  14. package/client/src/components/CodeBlock.vue +99 -0
  15. package/client/src/components/DetailBlock.vue +47 -0
  16. package/client/src/components/HelloWorld.vue +95 -0
  17. package/client/src/components/LangSwitch.vue +32 -0
  18. package/client/src/components/LinkList.vue +32 -0
  19. package/client/src/components/OsSwitch.vue +42 -0
  20. package/client/src/components/OsToggle.vue +15 -0
  21. package/client/src/components/PageHeader.vue +31 -0
  22. package/client/src/components/StepList.vue +92 -0
  23. package/client/src/composables/useClipboard.js +26 -0
  24. package/client/src/composables/useOs.js +22 -0
  25. package/client/src/data/docs/agent.js +1002 -0
  26. package/client/src/data/docs/auth.js +631 -0
  27. package/client/src/data/docs/builtin-tools.js +322 -0
  28. package/client/src/data/docs/chat.js +181 -0
  29. package/client/src/data/docs/index.js +9 -0
  30. package/client/src/data/docs/skills.js +396 -0
  31. package/client/src/data/docs/spec-conversion.js +1042 -0
  32. package/client/src/data/freeModels/bluesliu.js +97 -0
  33. package/client/src/data/freeModels/index.js +11 -0
  34. package/client/src/data/freeModels/nvidia.js +126 -0
  35. package/client/src/data/freeModels/openrouter.js +116 -0
  36. package/client/src/data/harness/claude.js +89 -0
  37. package/client/src/data/harness/codex.js +66 -0
  38. package/client/src/data/harness/dsh.js +86 -0
  39. package/client/src/data/harness/hermes.js +56 -0
  40. package/client/src/data/harness/index.js +22 -0
  41. package/client/src/data/harness/opencode.js +77 -0
  42. package/client/src/data/proxy/api-relay.js +53 -0
  43. package/client/src/data/proxy/builtin-proxy.js +54 -0
  44. package/client/src/data/proxy/cf-workers.js +67 -0
  45. package/client/src/data/proxy/ecs-forward.js +75 -0
  46. package/client/src/data/proxy/ecs-reverse.js +89 -0
  47. package/client/src/data/proxy/index.js +15 -0
  48. package/client/src/docs/claudecode.md +44 -0
  49. package/client/src/docs/codex.md +90 -0
  50. package/client/src/docs/hermesagent.md +42 -0
  51. package/client/src/docs/opencode.md +103 -0
  52. package/client/src/locales/en.js +436 -0
  53. package/client/src/locales/index.js +31 -0
  54. package/client/src/locales/zh-CN.js +461 -0
  55. package/client/src/main.js +16 -0
  56. package/client/src/router/index.js +76 -0
  57. package/client/src/style.css +15 -0
  58. package/client/src/views/AdminModelsView.vue +214 -0
  59. package/client/src/views/DocsView.vue +1604 -0
  60. package/client/src/views/FreeModelsView.vue +518 -0
  61. package/client/src/views/HarnessView.vue +314 -0
  62. package/client/src/views/HomeView.vue +147 -0
  63. package/client/src/views/ProxyView.vue +112 -0
  64. package/client/src/views/TokenMarketView.vue +26 -0
  65. package/client/vite.config.js +41 -0
  66. package/package.json +85 -0
  67. package/scripts/build-zip.sh +61 -0
  68. package/scripts/postinstall.js +7 -0
  69. package/server/src/config/targets.json +1 -0
  70. package/server/src/index.js +52 -0
  71. package/server/src/lib/coding-test.js +215 -0
  72. package/server/src/lib/database.js +353 -0
  73. package/server/src/lib/run-test.js +15 -0
  74. package/server/src/lib/scheduler.js +21 -0
  75. package/server/src/lib/tester.js +310 -0
  76. package/server/src/routes/admin.js +48 -0
  77. package/server/src/routes/proxy.js +64 -0
  78. package/server/src/routes/speed.js +87 -0
  79. package/src/config.js +45 -0
  80. package/src/config.json5 +27 -0
  81. package/src/index.js +10 -0
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@wwkit/freetoken",
3
+ "version": "1.0.1",
4
+ "author": "bluesliu <langcai163@163.com>",
5
+ "description": "Free LLM API token manager — speed test, proxy, and web dashboard (Vue 3 + Express)",
6
+ "type": "module",
7
+ "main": "src/index.js",
8
+ "bin": {
9
+ "freetoken": "cli/index.js"
10
+ },
11
+ "files": [
12
+ "cli",
13
+ "src",
14
+ "server/src",
15
+ "client/src",
16
+ "client/public",
17
+ "client/index.html",
18
+ "client/vite.config.js",
19
+ "scripts",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "engines": {
24
+ "node": ">=20.0.0"
25
+ },
26
+ "jest": {
27
+ "transform": {},
28
+ "testMatch": [
29
+ "**/tests/**/*.test.js"
30
+ ]
31
+ },
32
+ "keywords": [
33
+ "freetoken",
34
+ "llm",
35
+ "api",
36
+ "proxy",
37
+ "speed-test",
38
+ "vue",
39
+ "express"
40
+ ],
41
+ "license": "MIT",
42
+ "publishConfig": {
43
+ "registry": "https://registry.npmjs.org/",
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "axios": "^1.19.0",
48
+ "dotenv": "^16.4.0",
49
+ "express": "^4.21.0",
50
+ "http-proxy-middleware": "^3.0.0",
51
+ "node-cron": "^3.0.3",
52
+ "sql.js": "1.14.1",
53
+ "@wwkit/shared": "1.0.25"
54
+ },
55
+ "devDependencies": {
56
+ "@element-plus/icons-vue": "^2.3.2",
57
+ "@vitejs/plugin-vue": "^6.0.8",
58
+ "element-plus": "^2.14.4",
59
+ "highlight.js": "^11.12.0",
60
+ "jest": "^29.7.0",
61
+ "json5": "^2.2.3",
62
+ "marked": "^18.0.9",
63
+ "pinia": "^4.0.3",
64
+ "unplugin-auto-import": "^21.1.0",
65
+ "unplugin-vue-components": "^32.1.0",
66
+ "vite": "^8.2.0",
67
+ "vue": "^3.5.40",
68
+ "vue-i18n": "^9.14.5",
69
+ "vue-router": "^4.6.4"
70
+ },
71
+ "scripts": {
72
+ "postinstall": "node scripts/postinstall.js",
73
+ "start": "node cli/index.js web start",
74
+ "stop": "node cli/index.js web stop",
75
+ "status": "node cli/index.js status",
76
+ "restart": "node cli/index.js web restart",
77
+ "build": "vite build --config client/vite.config.js --root client",
78
+ "speed-test": "node cli/index.js speed-test",
79
+ "build:zip": "bash scripts/build-zip.sh",
80
+ "check": "node --check cli/index.js",
81
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache tests",
82
+ "test:all": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache tests",
83
+ "release": "node ../../packages/shared/bin/release-pkg.js"
84
+ }
85
+ }
@@ -0,0 +1,61 @@
1
+ #!/bin/bash
2
+
3
+ # 打包 monorepo 工程源码为 zip
4
+ # 排除: node_modules, client/dist, server/db, .git
5
+ # 默认输出目录: /usr1/mytodos/binary/public/blues
6
+
7
+ set -e
8
+
9
+ # 配置
10
+ OUTPUT_DIR="${OUTPUT_DIR:-/usr1/mytodos/binary/public/blues}"
11
+
12
+ # 读取 package.json(ESM 兼容,使用 import 而非 require)
13
+ read_pkg_field() {
14
+ node --input-type=module -e "
15
+ import { readFileSync } from 'node:fs';
16
+ const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
17
+ const value = $1;
18
+ console.log(value);
19
+ "
20
+ }
21
+
22
+ PROJECT_NAME=$(read_pkg_field "pkg.name.split('/').pop()")
23
+
24
+ # 自动增加版本号(patch + 1)
25
+ echo "增加版本号..."
26
+ node --input-type=module -e "
27
+ import { readFileSync, writeFileSync } from 'node:fs';
28
+ const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
29
+ const parts = pkg.version.split('.').map(Number);
30
+ parts[2] += 1;
31
+ pkg.version = parts.join('.');
32
+ writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
33
+ console.log('版本: ' + pkg.version);
34
+ "
35
+
36
+ VERSION=$(read_pkg_field "pkg.version")
37
+ ZIP_NAME="${PROJECT_NAME}_${VERSION}.zip"
38
+
39
+ echo "=========================================="
40
+ echo "打包源码 ${PROJECT_NAME} v${VERSION}"
41
+ echo "=========================================="
42
+
43
+ # 创建 zip 压缩包(排除指定目录)
44
+ echo "创建 zip 压缩包..."
45
+ zip -r "${ZIP_NAME}" . \
46
+ -x "node_modules/*" \
47
+ -x "client/dist/*" \
48
+ -x "server/db/*" \
49
+ -x ".git/*" \
50
+ -x "*.zip"
51
+
52
+ # 移动到目标目录
53
+ echo "移动到目标目录: ${OUTPUT_DIR}"
54
+ mkdir -p "${OUTPUT_DIR}"
55
+ mv "${ZIP_NAME}" "${OUTPUT_DIR}/"
56
+
57
+ echo "=========================================="
58
+ echo "✅ 打包完成!"
59
+ echo "输出文件: ${OUTPUT_DIR}/${ZIP_NAME}"
60
+ echo "文件大小: $(du -h "${OUTPUT_DIR}/${ZIP_NAME}" | cut -f1)"
61
+ echo "=========================================="
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { mergeBuiltinConfig, getConfigPath } from '../src/config.js'
4
+
5
+ if (mergeBuiltinConfig()) {
6
+ console.log(`freetoken: config initialized at ${getConfigPath()}`)
7
+ }
@@ -0,0 +1 @@
1
+ [{"provider": "bluesliu", "baseUrl": "http://7.237.94.74:32527/v1", "models": [{"name": "MiniMax-M2.7", "apiKeyEnv": "BLUESLIU_API_KEY"}, {"name": "maas-glm-5-aliyun-codeagent", "apiKeyEnv": "BLUESLIU_API_KEY"}, {"name": "maas-glm-5.1-zhipu", "apiKeyEnv": "BLUESLIU_API_KEY"}]}, {"provider": "openrouter", "baseUrl": "https://openrouter.ai/api/v1", "models": [{"name": "stealth/ox-alpha", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "z-ai/glm-5.2:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "minimax/minimax-m2.7:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "minimax/minimax-m3:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "nvidia/nemotron-3-super-120b-a12b:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "nvidia/nemotron-3-ultra-550b-a55b:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "poolside/laguna-s-2.1:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "cohere/north-mini-code:free", "apiKeyEnv": "OPENROUTER_API_KEY"}]}, {"provider": "nvidia", "baseUrl": "https://integrate.api.nvidia.com/v1", "models": [{"name": "meta/llama-3.3-70b-instruct", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "meta/llama-3.1-405b-instruct", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "mistralai/mistral-nemo-12b", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "qwen/qwen2.5-7b-instruct", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "google/gemma-2-9b", "apiKeyEnv": "NVIDIA_API_KEY"}]}]
@@ -0,0 +1,52 @@
1
+ import express from 'express'
2
+ import path from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import dotenv from 'dotenv'
5
+ import proxyRoutes from './routes/proxy.js'
6
+ import speedRoutes from './routes/speed.js'
7
+ import adminRoutes from './routes/admin.js'
8
+ import { initScheduler } from './lib/scheduler.js'
9
+ import { initDatabase, initModelsFromConfig } from './lib/database.js'
10
+ import { getServerPort } from '../../src/config.js'
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = path.dirname(__filename)
14
+
15
+ dotenv.config({ path: path.join(__dirname, '../.env') })
16
+
17
+ const app = express()
18
+ const PORT = getServerPort()
19
+
20
+ app.use(express.json())
21
+
22
+ // API routes
23
+ app.use('/api/proxy', proxyRoutes)
24
+ app.use('/api/speed', speedRoutes)
25
+ app.use('/api/admin', adminRoutes)
26
+
27
+ // Static files - serve client/dist in production
28
+ const clientDistPath = path.join(__dirname, '../../client/dist')
29
+ app.use(express.static(clientDistPath))
30
+
31
+ // SPA fallback - serve index.html for non-API routes
32
+ app.get('*', (req, res) => {
33
+ res.sendFile(path.join(clientDistPath, 'index.html'))
34
+ })
35
+
36
+ async function start() {
37
+ await initDatabase()
38
+ console.log('Database initialized')
39
+
40
+ initModelsFromConfig()
41
+ console.log('Models initialized from config')
42
+
43
+ app.listen(PORT, () => {
44
+ console.log(`Server running on port ${PORT}`)
45
+ initScheduler()
46
+ })
47
+ }
48
+
49
+ start().catch(err => {
50
+ console.error('Failed to start server:', err)
51
+ process.exit(1)
52
+ })
@@ -0,0 +1,215 @@
1
+ import axios from 'axios'
2
+
3
+ const TIMEOUT_MS = 60000
4
+
5
+ // 编程测试题
6
+ const codingTasks = [
7
+ {
8
+ id: 'bubble_sort',
9
+ difficulty: 'basic',
10
+ weight: 20,
11
+ prompt: `请用 JavaScript 实现冒泡排序函数。
12
+
13
+ 要求:
14
+ 1. 函数名: bubbleSort
15
+ 2. 参数: 一个数字数组
16
+ 3. 返回值: 排序后的数组(升序)
17
+ 4. 只返回函数代码,不要解释
18
+
19
+ 示例:
20
+ 输入: [5, 3, 1, 4, 2]
21
+ 输出: [1, 2, 3, 4, 5]`,
22
+ testFn: (code) => {
23
+ try {
24
+ const fn = new Function('return ' + code)()
25
+ const result = fn([5, 3, 1, 4, 2])
26
+ const expected = [1, 2, 3, 4, 5]
27
+ return JSON.stringify(result) === JSON.stringify(expected)
28
+ } catch {
29
+ return false
30
+ }
31
+ }
32
+ },
33
+ {
34
+ id: 'reverse_words',
35
+ difficulty: 'medium',
36
+ weight: 30,
37
+ prompt: `请用 JavaScript 实现一个函数,反转字符串中的单词顺序。
38
+
39
+ 要求:
40
+ 1. 函数名: reverseWords
41
+ 2. 参数: 一个字符串
42
+ 3. 返回值: 单词顺序反转后的字符串
43
+ 4. 只返回函数代码,不要解释
44
+
45
+ 示例:
46
+ 输入: "hello world"
47
+ 输出: "world hello"
48
+
49
+ 输入: "the quick brown fox"
50
+ 输出: "fox brown quick the"`,
51
+ testFn: (code) => {
52
+ try {
53
+ const fn = new Function('return ' + code)()
54
+ return fn('hello world') === 'world hello' &&
55
+ fn('the quick brown fox') === 'fox brown quick the'
56
+ } catch {
57
+ return false
58
+ }
59
+ }
60
+ },
61
+ {
62
+ id: 'fetch_with_retry',
63
+ difficulty: 'advanced',
64
+ weight: 50,
65
+ prompt: `请用 JavaScript 实现一个带超时和重试的 fetch 函数。
66
+
67
+ 要求:
68
+ 1. 函数名: fetchWithRetry
69
+ 2. 参数: url (字符串), options (对象, 可选), timeout (毫秒), retries (重试次数)
70
+ 3. 返回值: Promise,resolve 为 fetch 响应,reject 为错误
71
+ 4. 超时后应该中止请求并重试
72
+ 5. 只返回函数代码,不要解释
73
+
74
+ 注意: 使用 AbortController 实现超时`,
75
+ testFn: (code) => {
76
+ try {
77
+ // 语法检查
78
+ new Function('return ' + code)()
79
+ // 检查关键代码
80
+ const hasAbortController = code.includes('AbortController')
81
+ const hasTimeout = code.includes('timeout') || code.includes('setTimeout')
82
+ const hasRetry = code.includes('retries') || code.includes('retry')
83
+ const hasFetch = code.includes('fetch')
84
+
85
+ return hasAbortController && hasTimeout && hasRetry && hasFetch
86
+ } catch {
87
+ return false
88
+ }
89
+ }
90
+ }
91
+ ]
92
+
93
+ // 提取代码块
94
+ function extractCode(text) {
95
+ // 尝试提取 ```javascript ... ``` 或 ``` ... ``` 中的代码
96
+ const codeBlockMatch = text.match(/```(?:javascript|js)?\s*([\s\S]*?)```/)
97
+ if (codeBlockMatch) {
98
+ return codeBlockMatch[1].trim()
99
+ }
100
+
101
+ // 尝试提取函数定义
102
+ const funcMatch = text.match(/(function\s+\w+[\s\S]*|const\s+\w+\s*=\s*(?:async\s*)?\(?.*?\)?\s*=>[\s\S]*|async\s+function[\s\S]*)/)
103
+ if (funcMatch) {
104
+ return funcMatch[1].trim()
105
+ }
106
+
107
+ // 直接返回文本(假设整个响应就是代码)
108
+ return text.trim()
109
+ }
110
+
111
+ // 评估代码质量 (0-100)
112
+ function evaluateCodeQuality(code) {
113
+ let score = 50 // 基础分
114
+
115
+ // 有注释 +10
116
+ if (code.includes('//') || code.includes('/*')) {
117
+ score += 10
118
+ }
119
+
120
+ // 使用 const/let +10
121
+ if (code.includes('const ') || code.includes('let ')) {
122
+ score += 10
123
+ }
124
+
125
+ // 有错误处理 +15
126
+ if (code.includes('try') && code.includes('catch')) {
127
+ score += 15
128
+ }
129
+
130
+ // 有参数验证 +10
131
+ if (code.includes('if') && (code.includes('null') || code.includes('undefined'))) {
132
+ score += 10
133
+ }
134
+
135
+ // 代码简洁 (少于 500 字符) +5
136
+ if (code.length < 500) {
137
+ score += 5
138
+ }
139
+
140
+ return Math.min(100, score)
141
+ }
142
+
143
+ // 测试单个编程题
144
+ async function testCodingTask(baseUrl, apiKey, model, task) {
145
+ try {
146
+ const response = await axios({
147
+ method: 'POST',
148
+ url: `${baseUrl}/chat/completions`,
149
+ headers: {
150
+ 'Authorization': `Bearer ${apiKey}`,
151
+ 'Content-Type': 'application/json'
152
+ },
153
+ data: {
154
+ model,
155
+ messages: [{ role: 'user', content: task.prompt }],
156
+ max_tokens: 1000,
157
+ stream: false
158
+ },
159
+ timeout: TIMEOUT_MS
160
+ })
161
+
162
+ const content = response.data.choices?.[0]?.message?.content
163
+ || response.data.choices?.[0]?.message?.reasoning_content
164
+ || ''
165
+ const code = extractCode(content)
166
+
167
+ // 运行测试
168
+ const passed = task.testFn(code)
169
+ const qualityScore = evaluateCodeQuality(code)
170
+
171
+ // 综合得分 = 通过得分 * 0.7 + 代码质量 * 0.3
172
+ const score = passed ? (task.weight * 0.7 + qualityScore * task.weight / 100 * 0.3) : 0
173
+
174
+ return {
175
+ id: task.id,
176
+ difficulty: task.difficulty,
177
+ passed,
178
+ score: Math.round(score),
179
+ qualityScore,
180
+ code: code.substring(0, 200)
181
+ }
182
+ } catch (error) {
183
+ return {
184
+ id: task.id,
185
+ difficulty: task.difficulty,
186
+ passed: false,
187
+ score: 0,
188
+ error: error.message
189
+ }
190
+ }
191
+ }
192
+
193
+ // 执行编码能力测试
194
+ export async function testCodingAbility(baseUrl, apiKey, model) {
195
+ let totalScore = 0
196
+ let passedCount = 0
197
+ const results = []
198
+
199
+ for (const task of codingTasks) {
200
+ const result = await testCodingTask(baseUrl, apiKey, model, task)
201
+ results.push(result)
202
+
203
+ if (result.passed) {
204
+ passedCount++
205
+ }
206
+ totalScore += result.score
207
+ }
208
+
209
+ return {
210
+ score: totalScore,
211
+ passed: passedCount,
212
+ total: codingTasks.length,
213
+ details: results
214
+ }
215
+ }