@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.
@@ -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