@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/bridge-to-coder.sh
|
|
3
|
+
# 将 Skill 分析产物桥接到编程执行工具,工具退出后自动收集反馈
|
|
4
|
+
#
|
|
5
|
+
# 用法:./scripts/bridge-to-coder.sh <project_id> [tool] [impl_dir]
|
|
6
|
+
# project_id : Telos 项目 ID(如 proj_20260515_01)
|
|
7
|
+
# tool : claude(默认)| opencode | trae | codex
|
|
8
|
+
# impl_dir : 实现目录(默认 ../{project_id}-impl)
|
|
9
|
+
#
|
|
10
|
+
# 前置条件:projects/{project_id}/artifacts/ai-execution-doc.md 已生成
|
|
11
|
+
|
|
12
|
+
set -e
|
|
13
|
+
|
|
14
|
+
PROJECT_ID="${1:?用法: $0 <project_id> [tool] [impl_dir]}"
|
|
15
|
+
TOOL="${2:-claude}"
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
18
|
+
ARTIFACTS_DIR="$REPO_DIR/projects/$PROJECT_ID/artifacts"
|
|
19
|
+
AI_DOC="$ARTIFACTS_DIR/ai-execution-doc.md"
|
|
20
|
+
STATE_FILE="$REPO_DIR/projects/$PROJECT_ID/state.json"
|
|
21
|
+
IMPL_DIR="${3:-$(dirname "$REPO_DIR")/${PROJECT_ID}-impl}"
|
|
22
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
23
|
+
LOG_FILE="$REPO_DIR/episodic-logs/${PROJECT_ID}.jsonl"
|
|
24
|
+
DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
|
|
25
|
+
|
|
26
|
+
# ── 检查前置条件 ───────────────────────────────────────────────
|
|
27
|
+
if [ ! -f "$AI_DOC" ]; then
|
|
28
|
+
echo "❌ 错误:AI 执行文档不存在:$AI_DOC"
|
|
29
|
+
echo " 请先完成 Skill 分析并生成产物。"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# ── 创建实现工作区 ─────────────────────────────────────────────
|
|
34
|
+
echo "📁 创建实现目录:$IMPL_DIR"
|
|
35
|
+
mkdir -p "$IMPL_DIR"
|
|
36
|
+
|
|
37
|
+
# ── 注入 AI 执行文档为编程工具上下文 ──────────────────────────
|
|
38
|
+
PROJECT_NAME=$(python3 -c "import json,sys; d=json.load(open('$STATE_FILE')); print(d.get('project_name','未命名项目'))" 2>/dev/null || echo "未命名项目")
|
|
39
|
+
|
|
40
|
+
cat > "$IMPL_DIR/CLAUDE.md" << HEREDOC
|
|
41
|
+
# $PROJECT_NAME — AI 执行规格
|
|
42
|
+
|
|
43
|
+
> 本文档由 Telos Skill 分析自动生成,是唯一权威需求来源。
|
|
44
|
+
> **遇到本文档未说明的情况,必须停止并记录,不得自行决定。**
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
$(cat "$AI_DOC")
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 反馈协议(实现完成后必须执行)
|
|
53
|
+
|
|
54
|
+
实现过程中如遇到以下情况,请记录到 \`.telos-feedback.json\`:
|
|
55
|
+
- 需求描述不清晰或有歧义
|
|
56
|
+
- 遇到文档未覆盖的技术决策
|
|
57
|
+
- 发现需求假设与现实不符
|
|
58
|
+
- 验收标准无法被测试
|
|
59
|
+
|
|
60
|
+
记录格式:
|
|
61
|
+
\`\`\`json
|
|
62
|
+
{
|
|
63
|
+
"unclear_specs": ["具体描述哪条规格不清晰"],
|
|
64
|
+
"uncovered_decisions": ["遇到的未覆盖决策"],
|
|
65
|
+
"wrong_assumptions": ["发现的错误假设"],
|
|
66
|
+
"untestable_criteria": ["无法测试的验收标准"],
|
|
67
|
+
"general_feedback": "总体反馈和质量评分(1-10)"
|
|
68
|
+
}
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
**重要:** 实现完成后请写入上述文件。退出编程工具后,飞轮系统会自动读取该文件收集反馈,无需手动运行任何命令。
|
|
72
|
+
HEREDOC
|
|
73
|
+
|
|
74
|
+
echo "✅ CLAUDE.md 已生成"
|
|
75
|
+
|
|
76
|
+
# ── 写入桥接事件日志 ──────────────────────────────────────────
|
|
77
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
78
|
+
EVENT_ID="evt_bridge_$(date +%s)"
|
|
79
|
+
echo "{\"event_id\":\"$EVENT_ID\",\"type\":\"bridge_to_coder\",\"project_id\":\"$PROJECT_ID\",\"tool\":\"$TOOL\",\"impl_dir\":\"$IMPL_DIR\",\"timestamp\":\"$TIMESTAMP\"}" >> "$LOG_FILE"
|
|
80
|
+
|
|
81
|
+
# ── 启动编程工具 ───────────────────────────────────────────────
|
|
82
|
+
echo ""
|
|
83
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
84
|
+
echo " 项目:$PROJECT_NAME ($PROJECT_ID)"
|
|
85
|
+
echo " 工具:$TOOL"
|
|
86
|
+
echo " 目录:$IMPL_DIR"
|
|
87
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
cd "$IMPL_DIR"
|
|
91
|
+
|
|
92
|
+
case "$TOOL" in
|
|
93
|
+
claude)
|
|
94
|
+
echo "🚀 启动 Claude Code..."
|
|
95
|
+
claude
|
|
96
|
+
;;
|
|
97
|
+
opencode)
|
|
98
|
+
echo "🚀 启动 opencode..."
|
|
99
|
+
opencode
|
|
100
|
+
;;
|
|
101
|
+
trae)
|
|
102
|
+
echo "🚀 启动 trae..."
|
|
103
|
+
trae .
|
|
104
|
+
;;
|
|
105
|
+
codex)
|
|
106
|
+
echo "🚀 启动 codex..."
|
|
107
|
+
codex
|
|
108
|
+
;;
|
|
109
|
+
cursor)
|
|
110
|
+
echo "🚀 启动 Cursor..."
|
|
111
|
+
cursor .
|
|
112
|
+
;;
|
|
113
|
+
*)
|
|
114
|
+
echo "⚠️ 未知工具:$TOOL,已准备好实现目录。"
|
|
115
|
+
echo " 手动进入:cd $IMPL_DIR"
|
|
116
|
+
;;
|
|
117
|
+
esac
|
|
118
|
+
|
|
119
|
+
# ── 工具退出后:自动收集反馈 ──────────────────────────────────
|
|
120
|
+
echo ""
|
|
121
|
+
echo "━━━ 编程工具已退出,自动收集反馈... ━━━"
|
|
122
|
+
cd "$REPO_DIR"
|
|
123
|
+
"$SCRIPT_DIR/feedback-hook.sh" "$PROJECT_ID" "$IMPL_DIR" --auto
|
|
124
|
+
|
|
125
|
+
# ── 检查是否需要蒸馏,自动打开蒸馏会话 ───────────────────────
|
|
126
|
+
DISTILL_MARKER="$DISTILL_NEEDED_DIR/$PROJECT_ID"
|
|
127
|
+
if [ -f "$DISTILL_MARKER" ]; then
|
|
128
|
+
echo ""
|
|
129
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
130
|
+
echo " ⚡ 蒸馏条件已满足,自动启动蒸馏会话"
|
|
131
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
132
|
+
echo ""
|
|
133
|
+
echo "即将打开 Telos 蒸馏会话..."
|
|
134
|
+
echo "AI 将展示所有变更预览,你只需确认一次 (y/n)"
|
|
135
|
+
echo ""
|
|
136
|
+
sleep 1
|
|
137
|
+
cd "$REPO_DIR"
|
|
138
|
+
exec claude
|
|
139
|
+
fi
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/feedback-hook.sh
|
|
3
|
+
# 从编程工具实现会话收集反馈,写入 episodic-logs 以驱动蒸馏飞轮
|
|
4
|
+
#
|
|
5
|
+
# 用法:./scripts/feedback-hook.sh <project_id> [impl_dir] [--auto]
|
|
6
|
+
# project_id : 飞轮项目 ID
|
|
7
|
+
# impl_dir : 实现目录(用于读取 .telos-feedback.json)
|
|
8
|
+
# --auto : 非交互模式,仅读取反馈文件,跳过问答
|
|
9
|
+
#
|
|
10
|
+
# 反馈问题可由 Skill 定制:
|
|
11
|
+
# 在 skills/{skill-name}/feedback-questions.sh 中定义
|
|
12
|
+
# QUESTION_1 ~ QUESTION_5 变量
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
PROJECT_ID="${1:?用法: $0 <project_id> [impl_dir] [--auto]}"
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
IMPL_DIR="${2:-$(dirname "$REPO_DIR")/${PROJECT_ID}-impl}"
|
|
20
|
+
LOG_FILE="$REPO_DIR/episodic-logs/${PROJECT_ID}.jsonl"
|
|
21
|
+
FEEDBACK_FILE="$IMPL_DIR/.telos-feedback.json"
|
|
22
|
+
DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
|
|
23
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
24
|
+
EVENT_ID="evt_feedback_$(date +%s)"
|
|
25
|
+
AUTO_MODE=false
|
|
26
|
+
THRESHOLD=${DISTILL_THRESHOLD:-3}
|
|
27
|
+
|
|
28
|
+
# 解析参数(支持 --auto 在任意位置)
|
|
29
|
+
for arg in "$@"; do
|
|
30
|
+
[ "$arg" = "--auto" ] && AUTO_MODE=true
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
$AUTO_MODE || echo "━━━ 需求反馈收集器 ━━━"
|
|
34
|
+
$AUTO_MODE || echo "项目:$PROJECT_ID"
|
|
35
|
+
$AUTO_MODE || echo ""
|
|
36
|
+
|
|
37
|
+
# ── 读取编程工具自动生成的反馈文件 ───────────────────────────
|
|
38
|
+
FEEDBACK_JSON="{}"
|
|
39
|
+
if [ -f "$FEEDBACK_FILE" ]; then
|
|
40
|
+
echo "✅ 读取反馈文件:$FEEDBACK_FILE"
|
|
41
|
+
FEEDBACK_JSON=$(cat "$FEEDBACK_FILE")
|
|
42
|
+
else
|
|
43
|
+
if $AUTO_MODE; then
|
|
44
|
+
echo "ℹ️ 未发现 .telos-feedback.json,写入空反馈事件"
|
|
45
|
+
else
|
|
46
|
+
echo "ℹ️ 未发现自动反馈文件,进入交互收集模式..."
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# ── 加载 Skill 专属反馈问题(如存在则覆盖默认问题)────────────
|
|
51
|
+
QUESTION_1="执行过程中哪些输入信息不清晰?"
|
|
52
|
+
QUESTION_2="遇到了哪些文档未覆盖的决策点?"
|
|
53
|
+
QUESTION_3="发现哪些假设与实际情况不符?"
|
|
54
|
+
QUESTION_4="哪些产出无法验证质量?"
|
|
55
|
+
QUESTION_5="总体反馈(执行难度、输入质量评分 1-10):"
|
|
56
|
+
|
|
57
|
+
# 从 state.json 读取当前 Skill,尝试加载其 feedback-questions.sh
|
|
58
|
+
STATE_FILE="$REPO_DIR/projects/$PROJECT_ID/state.json"
|
|
59
|
+
if [ -f "$STATE_FILE" ]; then
|
|
60
|
+
ACTIVE_SKILL=$(python3 -c "import json; d=json.load(open('$STATE_FILE')); print(d.get('skill',''))" 2>/dev/null || echo "")
|
|
61
|
+
if [ -n "$ACTIVE_SKILL" ] && [ -f "$REPO_DIR/skills/$ACTIVE_SKILL/feedback-questions.sh" ]; then
|
|
62
|
+
source "$REPO_DIR/skills/$ACTIVE_SKILL/feedback-questions.sh"
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ── 交互补充(仅非 auto 模式)────────────────────────────────
|
|
67
|
+
UNCLEAR=""
|
|
68
|
+
UNCOVERED=""
|
|
69
|
+
WRONG_ASSUMPTIONS=""
|
|
70
|
+
UNTESTABLE=""
|
|
71
|
+
GENERAL=""
|
|
72
|
+
|
|
73
|
+
if ! $AUTO_MODE; then
|
|
74
|
+
echo "请回答以下问题(直接按 Enter 跳过):"
|
|
75
|
+
echo ""
|
|
76
|
+
read -p "1. $QUESTION_1 " UNCLEAR
|
|
77
|
+
read -p "2. $QUESTION_2 " UNCOVERED
|
|
78
|
+
read -p "3. $QUESTION_3 " WRONG_ASSUMPTIONS
|
|
79
|
+
read -p "4. $QUESTION_4 " UNTESTABLE
|
|
80
|
+
read -p "5. $QUESTION_5 " GENERAL
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# ── 合并反馈并生成事件 ────────────────────────────────────────
|
|
84
|
+
PENDING=$(python3 - << PYEOF
|
|
85
|
+
import json, os
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
existing = json.loads('''$FEEDBACK_JSON''')
|
|
89
|
+
except:
|
|
90
|
+
existing = {}
|
|
91
|
+
|
|
92
|
+
feedback = {
|
|
93
|
+
"event_id": "$EVENT_ID",
|
|
94
|
+
"type": "implementation_feedback",
|
|
95
|
+
"project_id": "$PROJECT_ID",
|
|
96
|
+
"timestamp": "$TIMESTAMP",
|
|
97
|
+
"distillation_candidate": True,
|
|
98
|
+
"unclear_specs": existing.get("unclear_specs", []) + (["$UNCLEAR"] if "$UNCLEAR" else []),
|
|
99
|
+
"uncovered_decisions": existing.get("uncovered_decisions", []) + (["$UNCOVERED"] if "$UNCOVERED" else []),
|
|
100
|
+
"wrong_assumptions": existing.get("wrong_assumptions", []) + (["$WRONG_ASSUMPTIONS"] if "$WRONG_ASSUMPTIONS" else []),
|
|
101
|
+
"untestable_criteria": existing.get("untestable_criteria", []) + (["$UNTESTABLE"] if "$UNTESTABLE" else []),
|
|
102
|
+
"general_feedback": existing.get("general_feedback", "") or "$GENERAL",
|
|
103
|
+
"impl_dir": "$IMPL_DIR"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# 过滤空字符串
|
|
107
|
+
for key in ["unclear_specs", "uncovered_decisions", "wrong_assumptions", "untestable_criteria"]:
|
|
108
|
+
feedback[key] = [x for x in feedback[key] if x.strip()]
|
|
109
|
+
|
|
110
|
+
# 计算质量分数
|
|
111
|
+
score = 0.0
|
|
112
|
+
if feedback["unclear_specs"]: score += 0.3
|
|
113
|
+
if feedback["uncovered_decisions"]: score += 0.4
|
|
114
|
+
if feedback["wrong_assumptions"]: score += 0.3
|
|
115
|
+
if feedback["general_feedback"]: score += 0.2
|
|
116
|
+
feedback["quality_score"] = min(score, 0.9)
|
|
117
|
+
|
|
118
|
+
log_file = "$LOG_FILE"
|
|
119
|
+
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
|
120
|
+
with open(log_file, "a") as f:
|
|
121
|
+
f.write(json.dumps(feedback, ensure_ascii=False) + "\n")
|
|
122
|
+
|
|
123
|
+
print(f"✅ 反馈已写入:{log_file}")
|
|
124
|
+
print(f" 质量分数:{feedback['quality_score']:.2f}(≥0.6 将被蒸馏提炼)")
|
|
125
|
+
|
|
126
|
+
# 统计待蒸馏事件数
|
|
127
|
+
try:
|
|
128
|
+
with open(log_file) as f:
|
|
129
|
+
events = [json.loads(l) for l in f if l.strip()]
|
|
130
|
+
pending = sum(1 for e in events
|
|
131
|
+
if e.get("distillation_candidate") and not e.get("distilled")
|
|
132
|
+
and e.get("quality_score", 0) >= 0.6)
|
|
133
|
+
print(f" 待蒸馏事件:{pending} 条(阈值:$THRESHOLD)")
|
|
134
|
+
print(str(pending)) # 最后一行供 bash 读取
|
|
135
|
+
except:
|
|
136
|
+
print("0")
|
|
137
|
+
PYEOF
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 读取 python 输出的最后一行(待蒸馏事件数)
|
|
141
|
+
PENDING_COUNT=$(echo "$PENDING" | tail -1)
|
|
142
|
+
echo "$PENDING" | head -n -1 # 打印非数字行
|
|
143
|
+
|
|
144
|
+
# ── 自动触发蒸馏标记 ──────────────────────────────────────────
|
|
145
|
+
if [ "$PENDING_COUNT" -ge "$THRESHOLD" ] 2>/dev/null; then
|
|
146
|
+
mkdir -p "$DISTILL_NEEDED_DIR"
|
|
147
|
+
echo "$PENDING_COUNT" > "$DISTILL_NEEDED_DIR/$PROJECT_ID"
|
|
148
|
+
echo ""
|
|
149
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
150
|
+
echo " ⚡ 蒸馏条件已满足($PENDING_COUNT 条待蒸馏反馈)"
|
|
151
|
+
echo " 已写入标记:$DISTILL_NEEDED_DIR/$PROJECT_ID"
|
|
152
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
153
|
+
else
|
|
154
|
+
$AUTO_MODE || echo ""
|
|
155
|
+
$AUTO_MODE || echo "━━━ 飞轮状态更新完成 ━━━"
|
|
156
|
+
$AUTO_MODE || echo "下一步:积累 ≥$THRESHOLD 条反馈后将自动触发蒸馏"
|
|
157
|
+
fi
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/telos-install.sh
|
|
3
|
+
# 在已安装 telos 的项目中管理 Skill 包
|
|
4
|
+
#
|
|
5
|
+
# Skill 三级分层:
|
|
6
|
+
# bundled — 内置 Skill,随框架发布,不可单独更新(skills/_kernel/ 或 tier: bundled)
|
|
7
|
+
# managed — 从注册表安装,可通过 update 升级(tier: managed)
|
|
8
|
+
# workspace — 本地 /add-skill 创建,不上传注册表(tier: workspace)
|
|
9
|
+
#
|
|
10
|
+
# 用法:
|
|
11
|
+
# ./scripts/telos-install.sh list # 列出所有 Skill(含分层)
|
|
12
|
+
# ./scripts/telos-install.sh add <skill> # 从注册表安装(标记 managed)
|
|
13
|
+
# ./scripts/telos-install.sh update # 更新所有 managed Skill
|
|
14
|
+
# ./scripts/telos-install.sh update <skill> # 更新指定 managed Skill
|
|
15
|
+
# ./scripts/telos-install.sh available # 查看注册表可安装列表
|
|
16
|
+
# ./scripts/telos-install.sh publish <skill> # 发布到本地注册表
|
|
17
|
+
# ./scripts/telos-install.sh tier <skill> <tier> # 修改 Skill 分层
|
|
18
|
+
|
|
19
|
+
set -e
|
|
20
|
+
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
22
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
23
|
+
REPO="renmengkai/telos"
|
|
24
|
+
BRANCH="main"
|
|
25
|
+
BASE_URL="https://raw.githubusercontent.com/$REPO/$BRANCH"
|
|
26
|
+
CMD="${1:-list}"
|
|
27
|
+
TARGET="${2:-}"
|
|
28
|
+
PROXY="${TELOS_PROXY:-}"
|
|
29
|
+
|
|
30
|
+
[ -n "$PROXY" ] && CURL_PROXY="--proxy $PROXY" || CURL_PROXY=""
|
|
31
|
+
|
|
32
|
+
# ── curl helper ─────────────────────────────────────────────────
|
|
33
|
+
do_curl() {
|
|
34
|
+
curl -fsSL $CURL_PROXY "$@"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ── tier helper ─────────────────────────────────────────────────
|
|
38
|
+
# 返回 Skill 的分层:bundled | managed | workspace
|
|
39
|
+
get_tier() {
|
|
40
|
+
local yaml="$1"
|
|
41
|
+
local name="$2"
|
|
42
|
+
[[ "$name" == _* ]] && { echo "bundled"; return; }
|
|
43
|
+
local tier
|
|
44
|
+
tier=$(grep '^tier:' "$yaml" 2>/dev/null | awk '{print $2}')
|
|
45
|
+
echo "${tier:-workspace}"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# tier 显示标签(对齐用)
|
|
49
|
+
tier_label() {
|
|
50
|
+
case "$1" in
|
|
51
|
+
bundled) echo "[bundled ]" ;;
|
|
52
|
+
managed) echo "[managed ]" ;;
|
|
53
|
+
workspace) echo "[workspace]" ;;
|
|
54
|
+
*) echo "[unknown ]" ;;
|
|
55
|
+
esac
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# 写入或更新 skill.yaml 中的 tier 字段
|
|
59
|
+
set_tier_in_yaml() {
|
|
60
|
+
local yaml="$1"
|
|
61
|
+
local tier="$2"
|
|
62
|
+
if grep -q '^tier:' "$yaml" 2>/dev/null; then
|
|
63
|
+
sed -i "s/^tier:.*/tier: $tier/" "$yaml"
|
|
64
|
+
else
|
|
65
|
+
# 插到 version 行之后
|
|
66
|
+
sed -i "/^version:/a tier: $tier" "$yaml"
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ── list ───────────────────────────────────────────────────────
|
|
71
|
+
cmd_list() {
|
|
72
|
+
echo "━━━ 已安装 Skills ━━━"
|
|
73
|
+
echo ""
|
|
74
|
+
|
|
75
|
+
local found=false
|
|
76
|
+
local counts_bundled=0 counts_managed=0 counts_workspace=0
|
|
77
|
+
|
|
78
|
+
for yaml in "$REPO_DIR/skills"/*/skill.yaml; do
|
|
79
|
+
[ -f "$yaml" ] || continue
|
|
80
|
+
local name version desc tier label
|
|
81
|
+
name=$(basename "$(dirname "$yaml")")
|
|
82
|
+
[[ "$name" == _* ]] && continue
|
|
83
|
+
version=$(grep '^version:' "$yaml" 2>/dev/null | awk '{print $2}' || echo "?")
|
|
84
|
+
desc=$(grep '^description:' "$yaml" 2>/dev/null | cut -d: -f2- | xargs || echo "")
|
|
85
|
+
tier=$(get_tier "$yaml" "$name")
|
|
86
|
+
label=$(tier_label "$tier")
|
|
87
|
+
printf " %s %-20s v%-8s %s\n" "$label" "$name" "$version" "$desc"
|
|
88
|
+
found=true
|
|
89
|
+
case "$tier" in
|
|
90
|
+
bundled) counts_bundled=$((counts_bundled + 1)) ;;
|
|
91
|
+
managed) counts_managed=$((counts_managed + 1)) ;;
|
|
92
|
+
workspace) counts_workspace=$((counts_workspace + 1)) ;;
|
|
93
|
+
esac
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
$found || echo " (未安装任何 Skill)"
|
|
97
|
+
|
|
98
|
+
echo ""
|
|
99
|
+
echo " bundled=$counts_bundled managed=$counts_managed workspace=$counts_workspace"
|
|
100
|
+
echo ""
|
|
101
|
+
echo " bundled — 内置,随框架更新,不可单独 update"
|
|
102
|
+
echo " managed — 注册表安装,可 update 升级"
|
|
103
|
+
echo " workspace — 本地创建,不自动更新"
|
|
104
|
+
echo ""
|
|
105
|
+
echo "安装新 Skill:$0 add <name> 创建新 Skill:/add-skill"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# ── available ─────────────────────────────────────────────────
|
|
109
|
+
cmd_available() {
|
|
110
|
+
echo "━━━ 注册表可安装 Skills ━━━"
|
|
111
|
+
local available_file="$SCRIPT_DIR/available.json"
|
|
112
|
+
if [ -f "$available_file" ]; then
|
|
113
|
+
python3 -c "
|
|
114
|
+
import json
|
|
115
|
+
with open('$available_file') as f:
|
|
116
|
+
data = json.load(f)
|
|
117
|
+
for s in data.get('agents', data.get('skills', [])):
|
|
118
|
+
tier = s.get('tier', 'managed')
|
|
119
|
+
print(f\" [{tier:<9}] {s['name']:<20} v{s['version']:<8} {s['description']}\")
|
|
120
|
+
"
|
|
121
|
+
else
|
|
122
|
+
echo " (注册表为空)"
|
|
123
|
+
fi
|
|
124
|
+
echo ""
|
|
125
|
+
echo "安装:$0 add <skill-name>"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ── add ────────────────────────────────────────────────────────
|
|
129
|
+
cmd_add() {
|
|
130
|
+
local agent="$1"
|
|
131
|
+
[ -z "$agent" ] && { echo "❌ 用法:$0 add <skill-name>"; exit 1; }
|
|
132
|
+
|
|
133
|
+
local dest_dir="$REPO_DIR/skills/$agent"
|
|
134
|
+
local dest_yaml="$dest_dir/skill.yaml"
|
|
135
|
+
|
|
136
|
+
echo "安装 Skill(managed):$agent"
|
|
137
|
+
|
|
138
|
+
if ! do_curl "$BASE_URL/skills/$agent/skill.yaml" -o /dev/null 2>/dev/null; then
|
|
139
|
+
echo "❌ 注册表中未找到 Skill '$agent'"
|
|
140
|
+
echo " 运行 '$0 available' 查看可安装列表"
|
|
141
|
+
echo " 本地创建:/add-skill"
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
curl -fsSL $CURL_PROXY "$BASE_URL/install.sh" | bash -s -- "$agent"
|
|
146
|
+
|
|
147
|
+
# 安装后标记为 managed
|
|
148
|
+
if [ -f "$dest_yaml" ]; then
|
|
149
|
+
set_tier_in_yaml "$dest_yaml" "managed"
|
|
150
|
+
echo " ✓ 已标记为 managed"
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# ── publish ────────────────────────────────────────────────────
|
|
155
|
+
cmd_publish() {
|
|
156
|
+
local agent="$1"
|
|
157
|
+
[ -z "$agent" ] && { echo "❌ 用法:$0 publish <skill-name>"; exit 1; }
|
|
158
|
+
|
|
159
|
+
local yaml="$REPO_DIR/skills/$agent/skill.yaml"
|
|
160
|
+
[ -f "$yaml" ] || { echo "❌ skill '$agent' 未找到:$yaml"; exit 1; }
|
|
161
|
+
|
|
162
|
+
local tier
|
|
163
|
+
tier=$(get_tier "$yaml" "$agent")
|
|
164
|
+
if [ "$tier" = "bundled" ]; then
|
|
165
|
+
echo "❌ bundled Skill 不可发布到注册表(随框架维护)"
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
local name version description
|
|
170
|
+
name=$(grep '^name:' "$yaml" | head -1 | awk '{print $2}')
|
|
171
|
+
version=$(grep '^version:' "$yaml" | head -1 | awk '{print $2}')
|
|
172
|
+
description=$(grep '^description:' "$yaml" | head -1 | cut -d: -f2- | xargs)
|
|
173
|
+
|
|
174
|
+
local available_file="$SCRIPT_DIR/available.json"
|
|
175
|
+
|
|
176
|
+
echo "━━━ 发布 Skill: $agent ━━━"
|
|
177
|
+
echo ""
|
|
178
|
+
|
|
179
|
+
if [ -f "$available_file" ] && python3 -c "
|
|
180
|
+
import json, sys
|
|
181
|
+
data = json.load(open('$available_file'))
|
|
182
|
+
agents = data.get('agents', data.get('skills', []))
|
|
183
|
+
sys.exit(0 if any(s['name']=='$name' for s in agents) else 1)
|
|
184
|
+
" 2>/dev/null; then
|
|
185
|
+
echo " ℹ '$name' 已在注册表中,更新版本..."
|
|
186
|
+
python3 - "$available_file" "$name" "$version" "$description" << 'PYEOF'
|
|
187
|
+
import json, sys
|
|
188
|
+
path, name, version, desc = sys.argv[1:]
|
|
189
|
+
data = json.load(open(path))
|
|
190
|
+
key = 'agents' if 'agents' in data else 'skills'
|
|
191
|
+
for s in data[key]:
|
|
192
|
+
if s['name'] == name:
|
|
193
|
+
s['version'] = version
|
|
194
|
+
s['description'] = desc
|
|
195
|
+
s['tier'] = 'managed'
|
|
196
|
+
break
|
|
197
|
+
with open(path, 'w') as f:
|
|
198
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
199
|
+
f.write('\n')
|
|
200
|
+
print(" ✓ available.json 已更新")
|
|
201
|
+
PYEOF
|
|
202
|
+
else
|
|
203
|
+
python3 - "$available_file" "$name" "$version" "$description" << 'PYEOF'
|
|
204
|
+
import json, sys, os
|
|
205
|
+
path, name, version, desc = sys.argv[1:]
|
|
206
|
+
data = json.load(open(path)) if os.path.exists(path) else {"agents": []}
|
|
207
|
+
data.setdefault('agents', []).append({
|
|
208
|
+
"name": name, "version": version,
|
|
209
|
+
"description": desc, "tier": "managed"
|
|
210
|
+
})
|
|
211
|
+
with open(path, 'w') as f:
|
|
212
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
213
|
+
f.write('\n')
|
|
214
|
+
print(f" ✓ '{name}' 已添加到 available.json(tier: managed)")
|
|
215
|
+
PYEOF
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# 更新 skill.yaml 自身的 tier 为 managed
|
|
219
|
+
set_tier_in_yaml "$yaml" "managed"
|
|
220
|
+
|
|
221
|
+
echo ""
|
|
222
|
+
echo "下一步:提交到仓库"
|
|
223
|
+
echo " git add scripts/available.json skills/$agent/"
|
|
224
|
+
echo " git commit -m \"feat(skill): publish $name v$version\""
|
|
225
|
+
echo " git push && 发起 PR 到 https://github.com/renmengkai/telos"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# ── update ─────────────────────────────────────────────────────
|
|
229
|
+
cmd_update() {
|
|
230
|
+
local targets=()
|
|
231
|
+
|
|
232
|
+
if [ -n "$TARGET" ]; then
|
|
233
|
+
# 指定 skill,先检查 tier
|
|
234
|
+
local yaml="$REPO_DIR/skills/$TARGET/skill.yaml"
|
|
235
|
+
[ -f "$yaml" ] || { echo "❌ skill '$TARGET' 未安装"; exit 1; }
|
|
236
|
+
local tier
|
|
237
|
+
tier=$(get_tier "$yaml" "$TARGET")
|
|
238
|
+
case "$tier" in
|
|
239
|
+
bundled)
|
|
240
|
+
echo "⚠️ '$TARGET' 是 bundled Skill,随框架整体更新,不支持单独 update"
|
|
241
|
+
exit 0
|
|
242
|
+
;;
|
|
243
|
+
workspace)
|
|
244
|
+
echo "⚠️ '$TARGET' 是 workspace Skill(本地创建),不从注册表更新"
|
|
245
|
+
echo " 如需发布后更新,先运行:$0 publish $TARGET"
|
|
246
|
+
exit 0
|
|
247
|
+
;;
|
|
248
|
+
esac
|
|
249
|
+
targets=("$TARGET")
|
|
250
|
+
else
|
|
251
|
+
# 只收集 managed Skill
|
|
252
|
+
for yaml in "$REPO_DIR/skills"/*/skill.yaml; do
|
|
253
|
+
[ -f "$yaml" ] || continue
|
|
254
|
+
local name tier
|
|
255
|
+
name=$(basename "$(dirname "$yaml")")
|
|
256
|
+
[[ "$name" == _* ]] && continue
|
|
257
|
+
tier=$(get_tier "$yaml" "$name")
|
|
258
|
+
[ "$tier" = "managed" ] && targets+=("$name")
|
|
259
|
+
done
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
if [ ${#targets[@]} -eq 0 ]; then
|
|
263
|
+
echo "没有 managed Skill 可更新"
|
|
264
|
+
echo "bundled Skill 随框架更新;workspace Skill 通过 publish 后才可 update"
|
|
265
|
+
exit 0
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
echo "━━━ 更新 managed Skills ━━━"
|
|
269
|
+
|
|
270
|
+
local to_update=()
|
|
271
|
+
for agent in "${targets[@]}"; do
|
|
272
|
+
local yaml_url="$BASE_URL/skills/$agent/skill.yaml"
|
|
273
|
+
local local_ver remote_ver
|
|
274
|
+
local_ver=$(grep '^version:' "$REPO_DIR/skills/$agent/skill.yaml" 2>/dev/null | awk '{print $2}' || echo "0")
|
|
275
|
+
remote_ver=$(do_curl "$yaml_url" 2>/dev/null | grep '^version:' | awk '{print $2}' || echo "?")
|
|
276
|
+
|
|
277
|
+
if [ "$remote_ver" = "$local_ver" ]; then
|
|
278
|
+
echo " ✓ $agent 已是最新 v$local_ver"
|
|
279
|
+
else
|
|
280
|
+
echo " → $agent v$local_ver → v$remote_ver"
|
|
281
|
+
to_update+=("$agent")
|
|
282
|
+
fi
|
|
283
|
+
done
|
|
284
|
+
|
|
285
|
+
if [ ${#to_update[@]} -gt 0 ]; then
|
|
286
|
+
echo ""
|
|
287
|
+
echo "更新 ${#to_update[@]} 个 Skill..."
|
|
288
|
+
for agent in "${to_update[@]}"; do
|
|
289
|
+
echo ""
|
|
290
|
+
echo "更新 $agent..."
|
|
291
|
+
cmd_add "$agent"
|
|
292
|
+
done
|
|
293
|
+
fi
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# ── tier ───────────────────────────────────────────────────────
|
|
297
|
+
cmd_tier() {
|
|
298
|
+
local agent="$1"
|
|
299
|
+
local new_tier="$2"
|
|
300
|
+
[ -z "$agent" ] || [ -z "$new_tier" ] && {
|
|
301
|
+
echo "用法:$0 tier <skill-name> <bundled|managed|workspace>"
|
|
302
|
+
exit 1
|
|
303
|
+
}
|
|
304
|
+
case "$new_tier" in
|
|
305
|
+
bundled|managed|workspace) ;;
|
|
306
|
+
*) echo "❌ 无效 tier:$new_tier(可选:bundled managed workspace)"; exit 1 ;;
|
|
307
|
+
esac
|
|
308
|
+
|
|
309
|
+
local yaml="$REPO_DIR/skills/$agent/skill.yaml"
|
|
310
|
+
[ -f "$yaml" ] || { echo "❌ skill '$agent' 未找到"; exit 1; }
|
|
311
|
+
|
|
312
|
+
local old_tier
|
|
313
|
+
old_tier=$(get_tier "$yaml" "$agent")
|
|
314
|
+
set_tier_in_yaml "$yaml" "$new_tier"
|
|
315
|
+
echo "✓ $agent: $old_tier → $new_tier"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case "$CMD" in
|
|
319
|
+
list) cmd_list ;;
|
|
320
|
+
add) cmd_add "$TARGET" ;;
|
|
321
|
+
update) cmd_update ;;
|
|
322
|
+
available) cmd_available ;;
|
|
323
|
+
publish) cmd_publish "$TARGET" ;;
|
|
324
|
+
tier) cmd_tier "$TARGET" "${3:-}" ;;
|
|
325
|
+
*)
|
|
326
|
+
echo "用法:$0 list | add <skill> | update [skill] | available | publish <skill> | tier <skill> <tier>"
|
|
327
|
+
exit 1
|
|
328
|
+
;;
|
|
329
|
+
esac
|