@vima_tech/telos 1.4.0
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 +179 -0
- package/README.zh.md +180 -0
- package/bin/telos.js +477 -0
- package/package.json +37 -0
- package/scripts/auto-distill.sh +154 -0
- package/scripts/available.json +11 -0
- package/scripts/bridge-to-coder.sh +139 -0
- package/scripts/feedback-hook.sh +157 -0
- package/scripts/telos-install.sh +329 -0
- package/skills/_kernel/distillation.md +311 -0
- package/skills/_kernel/skill-extraction.md +227 -0
- package/skills/_template/domain.md +93 -0
- package/skills/_template/feedback-questions.sh +9 -0
- package/skills/_template/skill.yaml +22 -0
- package/skills/req-mining/artifacts.md +185 -0
- package/skills/req-mining/domain.md +243 -0
- package/skills/req-mining/feedback-questions.sh +9 -0
- package/skills/req-mining/industry/erp.md +108 -0
- package/skills/req-mining/industry/retail.md +24 -0
- package/skills/req-mining/memory/failure-patterns.md +84 -0
- package/skills/req-mining/skill.yaml +45 -0
- package/templates/state.json +91 -0
package/bin/telos.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn, execSync } = require('child_process');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
const NPM_PKG = '@vima_tech/telos';
|
|
9
|
+
|
|
10
|
+
function resolveScript(name) {
|
|
11
|
+
return path.join(__dirname, '../scripts/' + name + '.sh');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveKernelFile(name) {
|
|
15
|
+
return path.join(__dirname, '../' + name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runScript(scriptPath, args, extraEnv = {}) {
|
|
19
|
+
const env = { ...process.env, TELOS_DIR: process.cwd(), ...extraEnv };
|
|
20
|
+
if (proxy) env.TELOS_PROXY = proxy;
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn('bash', [scriptPath, ...args], {
|
|
24
|
+
cwd: process.cwd(),
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
env,
|
|
27
|
+
});
|
|
28
|
+
child.on('exit', (code) => {
|
|
29
|
+
if (code === 0) resolve(0);
|
|
30
|
+
else reject(new Error('exit code ' + code));
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function downloadFile(url, destPath) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const args = ['-fsSL', url, '-o', destPath];
|
|
38
|
+
if (proxy) args.splice(1, 0, '--proxy', proxy);
|
|
39
|
+
spawn('curl', args, { stdio: 'ignore' })
|
|
40
|
+
.on('exit', (code) => {
|
|
41
|
+
if (code === 0) resolve(0);
|
|
42
|
+
else reject(new Error('curl failed'));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function initKernel(dir, skill) {
|
|
48
|
+
const baseUrl = 'https://raw.githubusercontent.com/renmengkai/telos/main';
|
|
49
|
+
|
|
50
|
+
const kernelFiles = [
|
|
51
|
+
'CLAUDE.md', '.gitignore',
|
|
52
|
+
'.claude/commands/telos.md', '.claude/commands/skill.md', '.claude/commands/add-skill.md',
|
|
53
|
+
'scripts/auto-distill.sh', 'scripts/feedback-hook.sh',
|
|
54
|
+
'scripts/bridge-to-coder.sh', 'scripts/telos-install.sh',
|
|
55
|
+
'templates/state.json',
|
|
56
|
+
'skills/_kernel/distillation.md', 'skills/_kernel/skill-extraction.md',
|
|
57
|
+
'skills/_template/skill.yaml', 'skills/_template/domain.md',
|
|
58
|
+
'skills/_template/feedback-questions.sh',
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const f of kernelFiles) {
|
|
62
|
+
const dest = path.join(dir, f);
|
|
63
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
64
|
+
const srcPath = resolveKernelFile(f);
|
|
65
|
+
if (fs.existsSync(srcPath)) {
|
|
66
|
+
fs.copyFileSync(srcPath, dest);
|
|
67
|
+
} else {
|
|
68
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (skill) {
|
|
73
|
+
const skillRequired = [
|
|
74
|
+
'skills/' + skill + '/skill.yaml',
|
|
75
|
+
'skills/' + skill + '/domain.md',
|
|
76
|
+
'skills/' + skill + '/feedback-questions.sh',
|
|
77
|
+
];
|
|
78
|
+
const skillOptional = [
|
|
79
|
+
'skills/' + skill + '/artifacts.md',
|
|
80
|
+
'skills/' + skill + '/memory/failure-patterns.md',
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
for (const f of skillRequired) {
|
|
84
|
+
const dest = path.join(dir, f);
|
|
85
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
86
|
+
const srcPath = resolveKernelFile(f);
|
|
87
|
+
if (fs.existsSync(srcPath)) {
|
|
88
|
+
fs.copyFileSync(srcPath, dest);
|
|
89
|
+
} else {
|
|
90
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const f of skillOptional) {
|
|
95
|
+
const dest = path.join(dir, f);
|
|
96
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
97
|
+
const srcPath = resolveKernelFile(f);
|
|
98
|
+
if (fs.existsSync(srcPath)) {
|
|
99
|
+
fs.copyFileSync(srcPath, dest);
|
|
100
|
+
} else {
|
|
101
|
+
try { await downloadFile(baseUrl + '/' + f, dest); } catch (e) {}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 解析 skill.yaml 中声明的行业包,逐一下载(找不到则跳过)
|
|
106
|
+
const skillYamlPath = path.join(dir, 'skills/' + skill + '/skill.yaml');
|
|
107
|
+
if (fs.existsSync(skillYamlPath)) {
|
|
108
|
+
const yamlContent = fs.readFileSync(skillYamlPath, 'utf8');
|
|
109
|
+
for (const match of yamlContent.matchAll(/^\s+file:\s+(.+)$/gm)) {
|
|
110
|
+
const f = match[1].trim();
|
|
111
|
+
const dest = path.join(dir, f);
|
|
112
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
113
|
+
const srcPath = resolveKernelFile(f);
|
|
114
|
+
if (fs.existsSync(srcPath)) {
|
|
115
|
+
fs.copyFileSync(srcPath, dest);
|
|
116
|
+
} else {
|
|
117
|
+
try { await downloadFile(baseUrl + '/' + f, dest); } catch (e) {}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fs.mkdirSync(path.join(dir, '.distill-needed'), { recursive: true });
|
|
124
|
+
fs.mkdirSync(path.join(dir, 'episodic-logs'), { recursive: true });
|
|
125
|
+
fs.mkdirSync(path.join(dir, 'projects'), { recursive: true });
|
|
126
|
+
fs.writeFileSync(path.join(dir, '.distill-needed/.gitkeep'), '');
|
|
127
|
+
fs.writeFileSync(path.join(dir, 'episodic-logs/.gitkeep'), '');
|
|
128
|
+
fs.writeFileSync(path.join(dir, 'projects/.gitkeep'), '');
|
|
129
|
+
|
|
130
|
+
if (!fs.existsSync(path.join(dir, '.gitignore'))) {
|
|
131
|
+
fs.writeFileSync(path.join(dir, '.gitignore'), `# telos-managed\nprojects/*/\nepisodic-logs/\n.distill-needed/*\n!.distill-needed/.gitkeep\n*.log\n`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(path.join(dir, '.telos'), JSON.stringify({
|
|
135
|
+
version: '1.4.0',
|
|
136
|
+
created: new Date().toISOString(),
|
|
137
|
+
skill: skill || 'none'
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
execSync('chmod +x ' + path.join(dir, 'scripts/*.sh'), { stdio: 'ignore', shell: true });
|
|
142
|
+
} catch (e) {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function cmdNew(args) {
|
|
146
|
+
if (args.length < 1) {
|
|
147
|
+
console.error('用法: telos new <project-name> [--skill req-mining]');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const projectName = args[0];
|
|
152
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
153
|
+
|
|
154
|
+
if (fs.existsSync(targetDir)) {
|
|
155
|
+
console.error('❌ 目录已存在: ' + targetDir);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let skill = '';
|
|
160
|
+
|
|
161
|
+
for (let i = 1; i < args.length; i++) {
|
|
162
|
+
if (args[i] === '--skill' && args[i + 1]) {
|
|
163
|
+
skill = args[i + 1];
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
170
|
+
console.log(' Telos 初始化');
|
|
171
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
172
|
+
console.log(' 项目: ' + projectName);
|
|
173
|
+
console.log(' 目录: ' + targetDir);
|
|
174
|
+
console.log(' Skill: ' + (skill || '无(后续手动添加)'));
|
|
175
|
+
console.log('');
|
|
176
|
+
|
|
177
|
+
console.log('📦 初始化框架文件...');
|
|
178
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
179
|
+
await initKernel(targetDir, skill);
|
|
180
|
+
console.log(' ✓ 安装完成');
|
|
181
|
+
|
|
182
|
+
if (!skill) {
|
|
183
|
+
console.log('');
|
|
184
|
+
console.log(' ℹ 未指定 Skill,可后续安装:');
|
|
185
|
+
console.log(' telos install req-mining');
|
|
186
|
+
console.log(' telos available # 查看可用 Skill');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
191
|
+
console.log(' ✅ 项目创建完成!');
|
|
192
|
+
console.log('');
|
|
193
|
+
console.log(' cd ' + projectName + ' && claude');
|
|
194
|
+
console.log(' 然后输入 tos 启动 Telos');
|
|
195
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function cmdStart() {
|
|
199
|
+
const detected = detectCodingAgent();
|
|
200
|
+
if (!detected) {
|
|
201
|
+
console.error('❌ 未检测到 Claude Code 或 OpenCode');
|
|
202
|
+
console.error('请先安装 Claude Code 或 OpenCode');
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log('🚀 启动 ' + detected + '...');
|
|
207
|
+
spawn(detected, [], {
|
|
208
|
+
cwd: process.cwd(),
|
|
209
|
+
stdio: 'inherit',
|
|
210
|
+
shell: true,
|
|
211
|
+
env: { ...process.env, TELOS_DIR: process.cwd() },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function detectCodingAgent() {
|
|
216
|
+
const agents = ['claude', 'claude-code', 'opencode', 'codex'];
|
|
217
|
+
for (const a of agents) {
|
|
218
|
+
try {
|
|
219
|
+
execSync('which ' + a, { stdio: 'ignore' });
|
|
220
|
+
return a;
|
|
221
|
+
} catch (e) {}
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function findTelosProjects(startPath, maxDepth = 3) {
|
|
227
|
+
const projects = [];
|
|
228
|
+
const homeDir = os.homedir();
|
|
229
|
+
|
|
230
|
+
function scan(dir, depth) {
|
|
231
|
+
if (depth > maxDepth) return;
|
|
232
|
+
try {
|
|
233
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
if (!entry.isDirectory()) continue;
|
|
236
|
+
if (entry.name === '.' || entry.name === '..') continue;
|
|
237
|
+
if (entry.name === 'node_modules') continue;
|
|
238
|
+
|
|
239
|
+
const fullPath = path.join(dir, entry.name);
|
|
240
|
+
if (fs.existsSync(path.join(fullPath, '.telos'))) {
|
|
241
|
+
projects.push(fullPath);
|
|
242
|
+
} else if (fullPath.startsWith(homeDir)) {
|
|
243
|
+
scan(fullPath, depth + 1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
scan(startPath, 0);
|
|
250
|
+
return projects;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function updateProjectFramework(projectPath) {
|
|
254
|
+
const baseUrl = 'https://raw.githubusercontent.com/renmengkai/telos/main';
|
|
255
|
+
|
|
256
|
+
const kernelFiles = [
|
|
257
|
+
'CLAUDE.md',
|
|
258
|
+
'.claude/commands/telos.md', '.claude/commands/skill.md', '.claude/commands/add-skill.md',
|
|
259
|
+
'scripts/auto-distill.sh', 'scripts/feedback-hook.sh',
|
|
260
|
+
'scripts/bridge-to-coder.sh', 'scripts/telos-install.sh',
|
|
261
|
+
'skills/_kernel/distillation.md', 'skills/_kernel/skill-extraction.md',
|
|
262
|
+
'skills/_template/skill.yaml', 'skills/_template/domain.md',
|
|
263
|
+
'skills/_template/feedback-questions.sh',
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
console.log(' 更新框架文件...');
|
|
267
|
+
for (const f of kernelFiles) {
|
|
268
|
+
const dest = path.join(projectPath, f);
|
|
269
|
+
try {
|
|
270
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
271
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
272
|
+
console.log(' ✓ ' + f);
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.log(' ✗ ' + f + ' (失败)');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 更新已安装的 Skill 文件
|
|
279
|
+
const telosMarker = path.join(projectPath, '.telos');
|
|
280
|
+
if (fs.existsSync(telosMarker)) {
|
|
281
|
+
let skill = '';
|
|
282
|
+
try { skill = JSON.parse(fs.readFileSync(telosMarker, 'utf8')).skill || ''; } catch (e) {}
|
|
283
|
+
if (skill && skill !== 'none') {
|
|
284
|
+
console.log(' 更新 Skill: ' + skill);
|
|
285
|
+
const skillFiles = [
|
|
286
|
+
'skills/' + skill + '/skill.yaml',
|
|
287
|
+
'skills/' + skill + '/domain.md',
|
|
288
|
+
'skills/' + skill + '/feedback-questions.sh',
|
|
289
|
+
'skills/' + skill + '/artifacts.md',
|
|
290
|
+
'skills/' + skill + '/memory/failure-patterns.md',
|
|
291
|
+
];
|
|
292
|
+
for (const f of skillFiles) {
|
|
293
|
+
const dest = path.join(projectPath, f);
|
|
294
|
+
try {
|
|
295
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
296
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
297
|
+
console.log(' ✓ ' + f);
|
|
298
|
+
} catch (e) {}
|
|
299
|
+
}
|
|
300
|
+
// 更新行业包(从已下载的 skill.yaml 中读取)
|
|
301
|
+
const skillYamlPath = path.join(projectPath, 'skills/' + skill + '/skill.yaml');
|
|
302
|
+
if (fs.existsSync(skillYamlPath)) {
|
|
303
|
+
const yamlContent = fs.readFileSync(skillYamlPath, 'utf8');
|
|
304
|
+
for (const match of yamlContent.matchAll(/^\s+file:\s+(.+)$/gm)) {
|
|
305
|
+
const f = match[1].trim();
|
|
306
|
+
const dest = path.join(projectPath, f);
|
|
307
|
+
try {
|
|
308
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
309
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
310
|
+
console.log(' ✓ ' + f);
|
|
311
|
+
} catch (e) {}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function cmdUpgrade(args) {
|
|
319
|
+
console.log('');
|
|
320
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
321
|
+
console.log(' Telos 升级');
|
|
322
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
323
|
+
|
|
324
|
+
console.log('');
|
|
325
|
+
console.log('📦 升级 telos CLI...');
|
|
326
|
+
const npmArgs = ['install', '-g', NPM_PKG];
|
|
327
|
+
if (proxy) npmArgs.push('--proxy', proxy);
|
|
328
|
+
const npmSpawn = spawn('npm', npmArgs, { stdio: 'inherit' });
|
|
329
|
+
await new Promise((resolve) => npmSpawn.on('exit', resolve));
|
|
330
|
+
|
|
331
|
+
if (args.length > 0) {
|
|
332
|
+
const targetPath = path.resolve(args[0]);
|
|
333
|
+
if (fs.existsSync(path.join(targetPath, '.telos'))) {
|
|
334
|
+
console.log('');
|
|
335
|
+
console.log('📦 更新项目框架: ' + targetPath);
|
|
336
|
+
await updateProjectFramework(targetPath);
|
|
337
|
+
} else {
|
|
338
|
+
console.error('❌ 不是有效的 telos 项目: ' + targetPath);
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
console.log('');
|
|
342
|
+
console.log('🔍 扫描 telos 项目...');
|
|
343
|
+
const projects = findTelosProjects(os.homedir());
|
|
344
|
+
|
|
345
|
+
if (projects.length === 0) {
|
|
346
|
+
console.log(' 未找到 telos 项目');
|
|
347
|
+
} else {
|
|
348
|
+
console.log(' 找到 ' + projects.length + ' 个项目:');
|
|
349
|
+
projects.forEach((p, i) => console.log(' ' + (i + 1) + '. ' + p));
|
|
350
|
+
|
|
351
|
+
console.log('');
|
|
352
|
+
console.log(' 是否更新所有项目框架?(y/N)');
|
|
353
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
354
|
+
const answer = await new Promise((resolve) => rl.question('', resolve));
|
|
355
|
+
rl.close();
|
|
356
|
+
|
|
357
|
+
if (answer.toLowerCase() === 'y') {
|
|
358
|
+
for (const proj of projects) {
|
|
359
|
+
console.log('');
|
|
360
|
+
console.log('📦 更新: ' + proj);
|
|
361
|
+
await updateProjectFramework(proj);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
console.log('');
|
|
368
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
369
|
+
console.log(' ✅ 升级完成');
|
|
370
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let proxy = '';
|
|
374
|
+
const rawArgs = process.argv.slice(2);
|
|
375
|
+
const filteredArgs = [];
|
|
376
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
377
|
+
if (rawArgs[i] === '-x' || rawArgs[i] === '--proxy') {
|
|
378
|
+
proxy = rawArgs[i + 1];
|
|
379
|
+
i++;
|
|
380
|
+
} else {
|
|
381
|
+
filteredArgs.push(rawArgs[i]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const [command, ...restArgs] = filteredArgs;
|
|
386
|
+
|
|
387
|
+
const HELP = `
|
|
388
|
+
Telos CLI — 对话驱动的个人能力操作系统
|
|
389
|
+
|
|
390
|
+
用法: telos <command> [args]
|
|
391
|
+
|
|
392
|
+
可用命令:
|
|
393
|
+
new|create|init 创建新项目
|
|
394
|
+
start|run|launch 启动 Claude Code 或 OpenCode
|
|
395
|
+
install|add|i 安装 Skill 包
|
|
396
|
+
list|ls 列出已安装的 Skill
|
|
397
|
+
update [skill] 更新 Skill 包
|
|
398
|
+
publish <skill> 发布自建 Skill 到注册表
|
|
399
|
+
available 查看可安装的 Skill
|
|
400
|
+
distill|dist 手动触发蒸馏
|
|
401
|
+
upgrade [path] 升级 telos 工具,并更新项目框架
|
|
402
|
+
-v|--version 显示版本号
|
|
403
|
+
-h|--help 显示帮助
|
|
404
|
+
|
|
405
|
+
选项:
|
|
406
|
+
-x, --proxy <url> 使用代理
|
|
407
|
+
|
|
408
|
+
示例:
|
|
409
|
+
telos new my-project
|
|
410
|
+
telos new my-project --skill req-mining
|
|
411
|
+
telos install req-mining
|
|
412
|
+
telos update
|
|
413
|
+
telos upgrade
|
|
414
|
+
telos upgrade ./my-project
|
|
415
|
+
`;
|
|
416
|
+
|
|
417
|
+
if (!command) {
|
|
418
|
+
console.log(HELP);
|
|
419
|
+
process.exit(0);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
switch (command) {
|
|
423
|
+
case '-v':
|
|
424
|
+
case '--version':
|
|
425
|
+
case '-V':
|
|
426
|
+
case 'version':
|
|
427
|
+
console.log('@vima_tech/telos v1.4.0');
|
|
428
|
+
break;
|
|
429
|
+
case 'new':
|
|
430
|
+
case 'create':
|
|
431
|
+
case 'init':
|
|
432
|
+
cmdNew(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
433
|
+
break;
|
|
434
|
+
case 'start':
|
|
435
|
+
case 'run':
|
|
436
|
+
case 'launch':
|
|
437
|
+
cmdStart();
|
|
438
|
+
break;
|
|
439
|
+
case '-h':
|
|
440
|
+
case '--help':
|
|
441
|
+
case '-?':
|
|
442
|
+
case 'help':
|
|
443
|
+
case 'h':
|
|
444
|
+
console.log(HELP);
|
|
445
|
+
break;
|
|
446
|
+
case 'install':
|
|
447
|
+
case 'add':
|
|
448
|
+
case 'i':
|
|
449
|
+
runScript(resolveScript('telos-install'), ['add', ...restArgs]).catch((e) => process.exit(1));
|
|
450
|
+
break;
|
|
451
|
+
case 'available':
|
|
452
|
+
case 'avail':
|
|
453
|
+
case 'search':
|
|
454
|
+
runScript(resolveScript('telos-install'), ['available']).catch((e) => process.exit(1));
|
|
455
|
+
break;
|
|
456
|
+
case 'list':
|
|
457
|
+
case 'ls':
|
|
458
|
+
runScript(resolveScript('telos-install'), ['list']).catch((e) => process.exit(1));
|
|
459
|
+
break;
|
|
460
|
+
case 'update':
|
|
461
|
+
runScript(resolveScript('telos-install'), ['update', ...restArgs]).catch((e) => process.exit(1));
|
|
462
|
+
break;
|
|
463
|
+
case 'publish':
|
|
464
|
+
runScript(resolveScript('telos-install'), ['publish', ...restArgs]).catch((e) => process.exit(1));
|
|
465
|
+
break;
|
|
466
|
+
case 'distill':
|
|
467
|
+
case 'dist':
|
|
468
|
+
runScript(resolveScript('auto-distill'), restArgs).catch((e) => process.exit(1));
|
|
469
|
+
break;
|
|
470
|
+
case 'upgrade':
|
|
471
|
+
cmdUpgrade(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
472
|
+
break;
|
|
473
|
+
default:
|
|
474
|
+
console.error('未知命令: ' + command);
|
|
475
|
+
console.error('运行 telos help 查看用法');
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vima_tech/telos",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "对话驱动的个人能力操作系统 — 每次对话都让你变得更懂行,技能跨会话永久积累",
|
|
5
|
+
"main": "bin/telos.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"telos": "./bin/telos.js",
|
|
8
|
+
"tos": "./bin/telos.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"scripts/",
|
|
13
|
+
"templates/",
|
|
14
|
+
"skills/_kernel/",
|
|
15
|
+
"skills/_template/",
|
|
16
|
+
"skills/req-mining/"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"postinstall": "chmod +x bin/telos.js"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"ai",
|
|
23
|
+
"skill",
|
|
24
|
+
"claude",
|
|
25
|
+
"telos",
|
|
26
|
+
"procedural-memory",
|
|
27
|
+
"self-evolution"
|
|
28
|
+
],
|
|
29
|
+
"author": "renmengkai",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=16.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@playwright/test": "^1.60.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/auto-distill.sh
|
|
3
|
+
# 自动检查 episodic-logs 和 .distill-needed/ 标记,触发蒸馏
|
|
4
|
+
#
|
|
5
|
+
# 用法:
|
|
6
|
+
# ./scripts/auto-distill.sh # 检查所有项目
|
|
7
|
+
# ./scripts/auto-distill.sh <project_id> # 检查指定项目
|
|
8
|
+
# ./scripts/auto-distill.sh --cron # Cron 模式(静默,仅在需要时输出)
|
|
9
|
+
#
|
|
10
|
+
# 设置为 Cron 自动运行(每天早上 9 点检查):
|
|
11
|
+
# 0 9 * * * /path/to/telos/scripts/auto-distill.sh --cron
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
|
+
LOGS_DIR="$REPO_DIR/episodic-logs"
|
|
18
|
+
DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
|
|
19
|
+
THRESHOLD=${DISTILL_THRESHOLD:-3}
|
|
20
|
+
CRON_MODE=false
|
|
21
|
+
TARGET_PROJECT=""
|
|
22
|
+
|
|
23
|
+
for arg in "$@"; do
|
|
24
|
+
case "$arg" in
|
|
25
|
+
--cron) CRON_MODE=true ;;
|
|
26
|
+
*) TARGET_PROJECT="$arg" ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
check_project() {
|
|
31
|
+
local project_id="$1"
|
|
32
|
+
|
|
33
|
+
# 优先检查标记文件(由 feedback-hook.sh --auto 写入)
|
|
34
|
+
if [ -f "$DISTILL_NEEDED_DIR/$project_id" ]; then
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# 回退:扫描日志文件
|
|
39
|
+
local log_file="$LOGS_DIR/${project_id}.jsonl"
|
|
40
|
+
[ -f "$log_file" ] || return 1
|
|
41
|
+
|
|
42
|
+
local pending
|
|
43
|
+
pending=$(python3 - << PYEOF
|
|
44
|
+
import json
|
|
45
|
+
try:
|
|
46
|
+
with open("$log_file") as f:
|
|
47
|
+
events = [json.loads(l) for l in f if l.strip()]
|
|
48
|
+
pending = sum(1 for e in events
|
|
49
|
+
if e.get("distillation_candidate") and not e.get("distilled")
|
|
50
|
+
and e.get("quality_score", 0) >= 0.6)
|
|
51
|
+
print(pending)
|
|
52
|
+
except:
|
|
53
|
+
print(0)
|
|
54
|
+
PYEOF
|
|
55
|
+
)
|
|
56
|
+
[ "$pending" -ge "$THRESHOLD" ]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
report_status() {
|
|
60
|
+
local project_id="$1"
|
|
61
|
+
local log_file="$LOGS_DIR/${project_id}.jsonl"
|
|
62
|
+
local has_marker=""
|
|
63
|
+
[ -f "$DISTILL_NEEDED_DIR/$project_id" ] && has_marker=" [标记文件]"
|
|
64
|
+
|
|
65
|
+
python3 - << PYEOF
|
|
66
|
+
import json
|
|
67
|
+
try:
|
|
68
|
+
with open("$log_file") as f:
|
|
69
|
+
events = [json.loads(l) for l in f if l.strip()]
|
|
70
|
+
total = len(events)
|
|
71
|
+
pending = sum(1 for e in events
|
|
72
|
+
if e.get("distillation_candidate") and not e.get("distilled")
|
|
73
|
+
and e.get("quality_score", 0) >= 0.6)
|
|
74
|
+
distilled = sum(1 for e in events if e.get("distilled"))
|
|
75
|
+
print(f" 项目 $project_id$has_marker: 总事件={total}, 待蒸馏={pending}, 已蒸馏={distilled}")
|
|
76
|
+
except:
|
|
77
|
+
print(" 项目 $project_id$has_marker: 无日志文件(由标记文件触发)")
|
|
78
|
+
PYEOF
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# ── 收集待检查项目 ─────────────────────────────────────────────
|
|
82
|
+
declare -A seen_projects
|
|
83
|
+
|
|
84
|
+
if [ -n "$TARGET_PROJECT" ]; then
|
|
85
|
+
seen_projects["$TARGET_PROJECT"]=1
|
|
86
|
+
else
|
|
87
|
+
# 从标记文件收集
|
|
88
|
+
if [ -d "$DISTILL_NEEDED_DIR" ]; then
|
|
89
|
+
for marker in "$DISTILL_NEEDED_DIR"/*; do
|
|
90
|
+
[ -f "$marker" ] || continue
|
|
91
|
+
project_id=$(basename "$marker")
|
|
92
|
+
[ "$project_id" = ".gitkeep" ] && continue
|
|
93
|
+
seen_projects["$project_id"]=1
|
|
94
|
+
done
|
|
95
|
+
fi
|
|
96
|
+
# 从日志文件收集
|
|
97
|
+
for log_file in "$LOGS_DIR"/*.jsonl; do
|
|
98
|
+
[ -f "$log_file" ] || continue
|
|
99
|
+
project_id=$(basename "$log_file" .jsonl)
|
|
100
|
+
[ "$project_id" = ".gitkeep" ] && continue
|
|
101
|
+
seen_projects["$project_id"]=1
|
|
102
|
+
done
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
projects=("${!seen_projects[@]}")
|
|
106
|
+
|
|
107
|
+
if [ ${#projects[@]} -eq 0 ]; then
|
|
108
|
+
$CRON_MODE || echo "📭 没有找到任何项目。"
|
|
109
|
+
exit 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
$CRON_MODE || echo "━━━ 蒸馏检查器 ━━━"
|
|
113
|
+
$CRON_MODE || echo "检查阈值:$THRESHOLD 条待蒸馏事件"
|
|
114
|
+
$CRON_MODE || echo ""
|
|
115
|
+
|
|
116
|
+
needs_distill=()
|
|
117
|
+
for project_id in "${projects[@]}"; do
|
|
118
|
+
if check_project "$project_id"; then
|
|
119
|
+
needs_distill+=("$project_id")
|
|
120
|
+
$CRON_MODE || report_status "$project_id"
|
|
121
|
+
$CRON_MODE || echo " ⚡ 需要蒸馏"
|
|
122
|
+
else
|
|
123
|
+
$CRON_MODE || report_status "$project_id"
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
if [ ${#needs_distill[@]} -eq 0 ]; then
|
|
128
|
+
$CRON_MODE || echo "✅ 所有项目暂无需要蒸馏(待蒸馏事件均 < $THRESHOLD 条)"
|
|
129
|
+
exit 0
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── 有需要蒸馏的项目 ──────────────────────────────────────────
|
|
133
|
+
echo ""
|
|
134
|
+
echo "━━━ 蒸馏建议 ━━━"
|
|
135
|
+
echo "以下项目已积累足够反馈,建议进行蒸馏:"
|
|
136
|
+
for p in "${needs_distill[@]}"; do
|
|
137
|
+
echo " • $p"
|
|
138
|
+
done
|
|
139
|
+
echo ""
|
|
140
|
+
echo "操作方式:"
|
|
141
|
+
echo " 1. 打开 Telos 会话(cd $REPO_DIR && claude)"
|
|
142
|
+
echo " 2. 系统将自动检测待蒸馏标记并启动蒸馏"
|
|
143
|
+
echo ""
|
|
144
|
+
|
|
145
|
+
# Cron 模式下发送桌面通知
|
|
146
|
+
if $CRON_MODE; then
|
|
147
|
+
if command -v notify-send &>/dev/null; then
|
|
148
|
+
notify-send "Telos 蒸馏提醒" "项目 ${needs_distill[*]} 已积累足够反馈,建议蒸馏"
|
|
149
|
+
elif command -v osascript &>/dev/null; then
|
|
150
|
+
osascript -e "display notification \"项目 ${needs_distill[*]} 已积累足够反馈\" with title \"req-miner 蒸馏提醒\""
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
exit 0
|