@aiyiran/myclaw 1.0.91 → 1.0.93
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/agent-list/workspace-danci/AGENTS.md +13 -0
- package/agent-list/workspace-danci/BOOTSTRAP.md +5 -0
- package/agent-list/workspace-danci/HEARTBEAT.md +3 -0
- package/agent-list/workspace-danci/IDENTITY.md +5 -0
- package/agent-list/workspace-danci/SOUL.md +6 -0
- package/agent-list/workspace-danci/TOOLS.md +40 -0
- package/agent-list/workspace-danci/USER.md +7 -0
- package/agent-list/workspace-danci/memory/birthday.md +9 -0
- package/agent-list/workspace-danci/memory/english-words-20260401.md +225 -0
- package/build-manifest.js +114 -0
- package/index.js +136 -14
- package/package.json +1 -1
- package/patch-agent.js +214 -0
- package/patch-manifest.json +27 -0
- package/patch-skill.js +10 -35
- package/publish.sh +4 -0
- package/pull.js +197 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# AGENTS.md - danci
|
|
2
|
+
|
|
3
|
+
This workspace belongs to the `danci` agent.
|
|
4
|
+
|
|
5
|
+
## Startup
|
|
6
|
+
- Read `SOUL.md`
|
|
7
|
+
- Read `USER.md`
|
|
8
|
+
- Read recent memory if available
|
|
9
|
+
|
|
10
|
+
## Rules
|
|
11
|
+
- Be helpful, careful, and concise.
|
|
12
|
+
- Prefer using the existing workspace and defaults.
|
|
13
|
+
- Ask before destructive or external actions.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# TOOLS.md - Local Notes
|
|
2
|
+
|
|
3
|
+
Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup.
|
|
4
|
+
|
|
5
|
+
## What Goes Here
|
|
6
|
+
|
|
7
|
+
Things like:
|
|
8
|
+
|
|
9
|
+
- Camera names and locations
|
|
10
|
+
- SSH hosts and aliases
|
|
11
|
+
- Preferred voices for TTS
|
|
12
|
+
- Speaker/room names
|
|
13
|
+
- Device nicknames
|
|
14
|
+
- Anything environment-specific
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
```markdown
|
|
19
|
+
### Cameras
|
|
20
|
+
|
|
21
|
+
- living-room → Main area, 180° wide angle
|
|
22
|
+
- front-door → Entrance, motion-triggered
|
|
23
|
+
|
|
24
|
+
### SSH
|
|
25
|
+
|
|
26
|
+
- home-server → 192.168.1.100, user: admin
|
|
27
|
+
|
|
28
|
+
### TTS
|
|
29
|
+
|
|
30
|
+
- Preferred voice: "Nova" (warm, slightly British)
|
|
31
|
+
- Default speaker: Kitchen HomePod
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Why Separate?
|
|
35
|
+
|
|
36
|
+
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
Add whatever helps you do your job. This is your cheat sheet.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# 英语单词学习记录
|
|
2
|
+
## 日期:2026年4月1日
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 学习目标
|
|
7
|
+
- 20个英语单词
|
|
8
|
+
- 对象:初中生
|
|
9
|
+
- 要求:记得快、记得好
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 单词列表
|
|
14
|
+
|
|
15
|
+
### 1. espionage [ɪˈspaɪənɪdʒ]
|
|
16
|
+
- 词性:名词
|
|
17
|
+
- 含义:间谍活动
|
|
18
|
+
- 例句:The government has several agents involved in espionage.
|
|
19
|
+
(政府有好几个特工参与间谍活动。)
|
|
20
|
+
|
|
21
|
+
### 2. mission [ˈmɪʃn]
|
|
22
|
+
- 词性:名词
|
|
23
|
+
- 含义:任务
|
|
24
|
+
- 例句:The mission brief was sent to your computer.
|
|
25
|
+
(任务简报已经发到你的电脑里了。)
|
|
26
|
+
|
|
27
|
+
### 3. brief [briːf]
|
|
28
|
+
- 词性:名词/形容词
|
|
29
|
+
- 含义:简报(n.)/ 简短的(adj.)
|
|
30
|
+
- 例句:The mission brief was sent to your computer.
|
|
31
|
+
(任务简报已经发到你的电脑里了。)
|
|
32
|
+
|
|
33
|
+
### 4. agent [ˈeɪdʒənt]
|
|
34
|
+
- 词性:名词
|
|
35
|
+
- 含义:特工、代理人
|
|
36
|
+
- 例句:The secret agent went into the building alone.
|
|
37
|
+
(那名秘密特工独自走进了那栋大楼。)
|
|
38
|
+
- 简化例句:He is an undercover cop.(他是一名卧底警察。)
|
|
39
|
+
|
|
40
|
+
### 5. undercover [ˌʌndəˈkʌvə(r)]
|
|
41
|
+
- 词性:形容词
|
|
42
|
+
- 含义:卧底的、秘密执行的
|
|
43
|
+
- 例句:He is an undercover cop.(他是一名卧底警察。)
|
|
44
|
+
- 记忆技巧:under(在…下面)+ cover(覆盖)= 在覆盖之下 = 卧底的
|
|
45
|
+
|
|
46
|
+
### 6. surveillance [səˈveɪləns]
|
|
47
|
+
- 词性:名词
|
|
48
|
+
- 含义:监视、监控
|
|
49
|
+
- 例句:The police kept the building under surveillance.
|
|
50
|
+
(警察对那栋大楼进行了监视。)
|
|
51
|
+
- 记忆技巧:谐音"司马在外"(Sima Fu)= 司马孚在外面监视 = surveillance
|
|
52
|
+
|
|
53
|
+
### 7. gadget [ˈɡædʒɪt]
|
|
54
|
+
- 词性:名词
|
|
55
|
+
- 含义:小玩意儿、小装置
|
|
56
|
+
- 例句:The spy used a cool gadget to open the door.
|
|
57
|
+
(特工用一个酷炫的小装置打开了门。)
|
|
58
|
+
- 词源:19世纪英国水手的俚语,用来称呼各种不知道叫什么的小零件
|
|
59
|
+
|
|
60
|
+
### 8. intel [ˈɪntl]
|
|
61
|
+
- 词性:名词
|
|
62
|
+
- 含义:情报(intelligence的缩写)
|
|
63
|
+
- 例句:The agent got some important intel.
|
|
64
|
+
(特工收到了一条重要情报。)
|
|
65
|
+
- 记忆技巧:in + tel(telephone)= 从电话里得到的情报
|
|
66
|
+
|
|
67
|
+
### 9. extraction [ɪkˈstrækʃn]
|
|
68
|
+
- 词性:名词
|
|
69
|
+
- 含义:撤离、营救
|
|
70
|
+
- 例句:The team made a quick extraction.(小队快速撤离。)
|
|
71
|
+
- 记忆技巧:ex(出来)+ tract(拉)= 拉出来 = 撤离
|
|
72
|
+
- 5个例句:
|
|
73
|
+
1. The rescue team did a fast extraction.
|
|
74
|
+
2. The spy was in trouble, so they ordered an extraction.
|
|
75
|
+
3. The extraction point is near the old building.
|
|
76
|
+
4. He finished the extraction mission successfully.
|
|
77
|
+
5. The helicopter came for the extraction.
|
|
78
|
+
|
|
79
|
+
### 10. code [kəʊd]
|
|
80
|
+
- 词性:名词
|
|
81
|
+
- 含义:密码、代码
|
|
82
|
+
- 例句:The spy used a secret code to send messages.
|
|
83
|
+
(特工用密码发送信息。)
|
|
84
|
+
- 5个例句:
|
|
85
|
+
1. Don't tell anyone the secret code.
|
|
86
|
+
2. The password is a four-digit code.
|
|
87
|
+
3. He broke the code and saved the day.
|
|
88
|
+
4. What is the secret code?
|
|
89
|
+
5. She sent a message in code.
|
|
90
|
+
|
|
91
|
+
### 11. cipher [ˈsaɪfə(r)]
|
|
92
|
+
- 词性:名词
|
|
93
|
+
- 含义:密码、密文(更专业的加密术语)
|
|
94
|
+
- 例句:Can you read this cipher?(你能读懂这个密码吗?)
|
|
95
|
+
- 与code的区别:code是把整个词换成另一个词,cipher是把每个字母换成另一个字母
|
|
96
|
+
- 5个例句:
|
|
97
|
+
1. This text looks like a cipher.
|
|
98
|
+
2. She wrote the message in cipher.
|
|
99
|
+
3. He tried to solve the cipher.
|
|
100
|
+
4. The cipher was very hard to break.
|
|
101
|
+
5. Can you read this cipher?
|
|
102
|
+
|
|
103
|
+
### 12. double agent
|
|
104
|
+
- 词性:名词短语
|
|
105
|
+
- 含义:双面间谍
|
|
106
|
+
- 例句:He is a double agent, working for both sides.
|
|
107
|
+
(他是双面间谍,同时为两边工作。)
|
|
108
|
+
|
|
109
|
+
### 13. coming of age
|
|
110
|
+
- 词性:短语/习语
|
|
111
|
+
- 含义:长大成人、成年
|
|
112
|
+
- 例句:The movie is about a boy's coming of age.
|
|
113
|
+
(这部电影是关于一个男孩的成长故事。)
|
|
114
|
+
- 说明:coming of age = 来到成年,最早是法律术语,后延伸指心理成熟
|
|
115
|
+
- 5个例句:
|
|
116
|
+
1. His coming of age was very difficult.
|
|
117
|
+
2. The book is about coming of age.
|
|
118
|
+
3. She had a coming of age experience.
|
|
119
|
+
4. Coming of age is an important time in life.
|
|
120
|
+
5. This book is a story about a young man's coming of age in a small town.
|
|
121
|
+
|
|
122
|
+
### 14. adolescence [ˌædəˈlesns]
|
|
123
|
+
- 词性:名词
|
|
124
|
+
- 含义:青春期
|
|
125
|
+
- 例句:Many changes happen during adolescence.
|
|
126
|
+
(青春期会发生很多变化。)
|
|
127
|
+
- 5个例句:
|
|
128
|
+
1. He spent his adolescence in Paris.
|
|
129
|
+
2. Adolescence is a time of big changes.
|
|
130
|
+
3. Many teenagers feel confused during adolescence.
|
|
131
|
+
4. She wrote a diary during her adolescence.
|
|
132
|
+
5. Adolescence usually starts around age 12.
|
|
133
|
+
|
|
134
|
+
### 15. identity [aɪˈdentəti]
|
|
135
|
+
- 词性:名词
|
|
136
|
+
- 含义:身份
|
|
137
|
+
- 例句:He hid his true identity.(他隐藏了自己的真实身份。)
|
|
138
|
+
- 注意:identity和Identity是同一个词,只是大小写不同(首字母大写只是因为出现在句首)
|
|
139
|
+
|
|
140
|
+
### 16. independence [ˌɪndɪˈpendəns]
|
|
141
|
+
- 词性:名词
|
|
142
|
+
- 含义:独立、自立
|
|
143
|
+
- 例句:The country got its independence in 1949.
|
|
144
|
+
(这个国家在1949年获得了独立。)
|
|
145
|
+
- 记忆技巧:in(不)+ depend(依赖)+ ence = 不依赖别人 = 独立
|
|
146
|
+
|
|
147
|
+
### 17. peer pressure
|
|
148
|
+
- 词性:名词短语
|
|
149
|
+
- 含义:同伴压力
|
|
150
|
+
- 例句:He started smoking because of peer pressure.
|
|
151
|
+
(他因为同伴压力开始抽烟。)
|
|
152
|
+
- 记忆技巧:peer(同龄人)+ pressure(压力)= 来自同龄人的压力
|
|
153
|
+
- 5个例句:
|
|
154
|
+
1. She didn't want to cheat, but peer pressure was too strong.
|
|
155
|
+
2. Peer pressure can make you do bad things.
|
|
156
|
+
3. He resisted peer pressure and said no.
|
|
157
|
+
4. Teens often face peer pressure at school.
|
|
158
|
+
5. Good friends won't give you bad peer pressure.
|
|
159
|
+
|
|
160
|
+
### 18. belonging [bɪˈlɒŋɪŋ]
|
|
161
|
+
- 词性:名词
|
|
162
|
+
- 含义:归属感、归属
|
|
163
|
+
- 例句:She felt a sense of belonging in her new school.
|
|
164
|
+
(她在新学校找到了归属感。)
|
|
165
|
+
|
|
166
|
+
### 19. self-discovery
|
|
167
|
+
- 词性:名词
|
|
168
|
+
- 含义:自我发现、认识自我
|
|
169
|
+
- 例句:Her trip to Tibet was a journey of self-discovery.
|
|
170
|
+
(她去西藏的旅行是一次自我发现的旅程。)
|
|
171
|
+
- 记忆技巧:self(自己)+ discovery(发现)= 发现你自己
|
|
172
|
+
|
|
173
|
+
### 20. relationships
|
|
174
|
+
- 词性:名词
|
|
175
|
+
- 含义:人际关系、关系
|
|
176
|
+
- 例句:Teenagers often have problems with peer relationships.
|
|
177
|
+
(青少年经常在人际关系方面有问题。)
|
|
178
|
+
|
|
179
|
+
### 21. internal conflict
|
|
180
|
+
- 词性:名词短语
|
|
181
|
+
- 含义:内心矛盾、内心冲突
|
|
182
|
+
- 例句:He had an internal conflict about telling the truth.
|
|
183
|
+
(他内心很矛盾,不知道该不该说实话。)
|
|
184
|
+
- 记忆技巧:internal(内心的)+ conflict(冲突)= 内心冲突
|
|
185
|
+
|
|
186
|
+
### 22. personal growth
|
|
187
|
+
- 词性:名词短语
|
|
188
|
+
- 含义:个人成长
|
|
189
|
+
- 例句:Reading books helps with personal growth.
|
|
190
|
+
(阅读有助于个人成长。)
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 故事串联
|
|
195
|
+
所有间谍主题词汇串联:
|
|
196
|
+
|
|
197
|
+
**The Mission(任务)**
|
|
198
|
+
|
|
199
|
+
A secret **agent** got a **mission brief** on his computer.
|
|
200
|
+
|
|
201
|
+
The **brief** said: go **undercover** and do some **espionage**.
|
|
202
|
+
|
|
203
|
+
But the bad guys had the whole building under **surveillance**!
|
|
204
|
+
|
|
205
|
+
He used a cool **gadget** to get inside...
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 初中生相关词汇整理
|
|
210
|
+
|
|
211
|
+
### 青春期主题词汇:
|
|
212
|
+
- adolescence 青春期
|
|
213
|
+
- coming of age 长大成人
|
|
214
|
+
- identity 身份
|
|
215
|
+
- independence 独立
|
|
216
|
+
- peer pressure 同伴压力
|
|
217
|
+
- belonging 归属感
|
|
218
|
+
- self-discovery 自我发现
|
|
219
|
+
- relationships 人际关系
|
|
220
|
+
- internal conflict 内心矛盾
|
|
221
|
+
- personal growth 个人成长
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
备注:学习者是初中生,英语水平一般。采用联想记忆、谐音记忆、词源解释等多种方法帮助记忆。
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* build-manifest.js - 自动构建 patch-manifest.json
|
|
6
|
+
* ============================================================================
|
|
7
|
+
*
|
|
8
|
+
* 扫描 agent-list/ 和 skills/ 目录,自动生成统一注入清单。
|
|
9
|
+
* 已有的 strategy 设置会保留(不会被覆盖)。
|
|
10
|
+
*
|
|
11
|
+
* 在 publish.sh 中自动调用,无需手动维护 manifest。
|
|
12
|
+
* ============================================================================
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const MANIFEST_PATH = path.join(__dirname, 'patch-manifest.json');
|
|
19
|
+
const AGENT_LIST_DIR = path.join(__dirname, 'agent-list');
|
|
20
|
+
const SKILLS_DIR = path.join(__dirname, 'skills');
|
|
21
|
+
|
|
22
|
+
// 默认 config patches(硬编码,不从目录扫描)
|
|
23
|
+
const DEFAULT_CONFIG = {
|
|
24
|
+
strategy: 'overwrite',
|
|
25
|
+
patches: {
|
|
26
|
+
'session.reset.mode': 'idle',
|
|
27
|
+
'session.reset.idleMinutes': 90720,
|
|
28
|
+
'tools.exec.ask': 'off',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function buildManifest() {
|
|
33
|
+
// 读取已有 manifest(保留手动设置的 strategy)
|
|
34
|
+
let existing = { agents: [], skills: [], config: DEFAULT_CONFIG };
|
|
35
|
+
if (fs.existsSync(MANIFEST_PATH)) {
|
|
36
|
+
try {
|
|
37
|
+
existing = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 建立已有策略映射(保留手动覆盖)
|
|
42
|
+
const saved = {};
|
|
43
|
+
for (const list of [existing.agents || [], existing.skills || []]) {
|
|
44
|
+
for (const item of list) {
|
|
45
|
+
const key = item.id || item.name;
|
|
46
|
+
if (key && item.strategy) saved[key] = item.strategy;
|
|
47
|
+
if (key && item.description) saved[key + '_desc'] = item.description;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// === 1. 扫描 agent-list/ ===
|
|
52
|
+
const agents = [];
|
|
53
|
+
if (fs.existsSync(AGENT_LIST_DIR)) {
|
|
54
|
+
const dirs = fs.readdirSync(AGENT_LIST_DIR, { withFileTypes: true })
|
|
55
|
+
.filter(d => d.isDirectory() && d.name.startsWith('workspace-'))
|
|
56
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
57
|
+
|
|
58
|
+
for (const d of dirs) {
|
|
59
|
+
const agentId = d.name.replace(/^workspace-/, '');
|
|
60
|
+
agents.push({
|
|
61
|
+
id: agentId,
|
|
62
|
+
workspace: d.name,
|
|
63
|
+
strategy: saved[agentId] || 'auto',
|
|
64
|
+
description: saved[agentId + '_desc'] || agentId,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// === 2. 扫描 skills/ ===
|
|
70
|
+
const skills = [];
|
|
71
|
+
if (fs.existsSync(SKILLS_DIR)) {
|
|
72
|
+
const dirs = fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
73
|
+
.filter(d => d.isDirectory())
|
|
74
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
75
|
+
|
|
76
|
+
for (const d of dirs) {
|
|
77
|
+
// 必须有 SKILL.md 才是合法 skill
|
|
78
|
+
const skillMd = path.join(SKILLS_DIR, d.name, 'SKILL.md');
|
|
79
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
80
|
+
|
|
81
|
+
skills.push({
|
|
82
|
+
name: d.name,
|
|
83
|
+
strategy: saved[d.name] || 'auto',
|
|
84
|
+
description: saved[d.name + '_desc'] || d.name,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// === 3. 保留 config ===
|
|
90
|
+
const config = existing.config || DEFAULT_CONFIG;
|
|
91
|
+
|
|
92
|
+
const manifest = {
|
|
93
|
+
_doc: 'MyClaw 注入清单 (auto-generated)。strategy: auto | overwrite | off',
|
|
94
|
+
_generated: new Date().toISOString(),
|
|
95
|
+
agents,
|
|
96
|
+
skills,
|
|
97
|
+
config,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
101
|
+
|
|
102
|
+
console.log('📋 patch-manifest.json 已生成');
|
|
103
|
+
console.log(' 智能体: ' + agents.length + ' 个');
|
|
104
|
+
for (const a of agents) {
|
|
105
|
+
console.log(' • ' + a.id + ' [' + a.strategy + ']');
|
|
106
|
+
}
|
|
107
|
+
console.log(' 技能: ' + skills.length + ' 个');
|
|
108
|
+
for (const s of skills) {
|
|
109
|
+
console.log(' • ' + s.name + ' [' + s.strategy + ']');
|
|
110
|
+
}
|
|
111
|
+
console.log(' 配置: ' + Object.keys(config.patches || {}).length + ' 项 [' + (config.strategy || 'overwrite') + ']');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
buildManifest();
|
package/index.js
CHANGED
|
@@ -533,27 +533,46 @@ function runPatch() {
|
|
|
533
533
|
console.log('');
|
|
534
534
|
const skillResult = patchSkills();
|
|
535
535
|
|
|
536
|
-
// 3.
|
|
536
|
+
// 3. 智能体上传(从 agent-list/ 复制到 ~/.openclaw/)
|
|
537
|
+
console.log('');
|
|
538
|
+
const { patchAgents } = require('./patch-agent');
|
|
539
|
+
const agentResult = patchAgents();
|
|
540
|
+
|
|
541
|
+
// 4. Config 补丁(从 manifest 读取,修改 openclaw.json)
|
|
537
542
|
console.log('');
|
|
538
543
|
try {
|
|
539
|
-
const {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
+
const { loadManifest } = require('./patch-agent');
|
|
545
|
+
const manifest = loadManifest();
|
|
546
|
+
if (manifest && manifest.config && manifest.config.patches) {
|
|
547
|
+
const configStrategy = manifest.config.strategy || 'overwrite';
|
|
548
|
+
if (configStrategy !== 'off') {
|
|
549
|
+
// 将 dot-notation patch 展开为嵌套对象
|
|
550
|
+
const patches = manifest.config.patches;
|
|
551
|
+
const nested = {};
|
|
552
|
+
for (const [dotKey, value] of Object.entries(patches)) {
|
|
553
|
+
const parts = dotKey.split('.');
|
|
554
|
+
let current = nested;
|
|
555
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
556
|
+
if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
|
|
557
|
+
current[parts[i]] = {};
|
|
558
|
+
}
|
|
559
|
+
current = current[parts[i]];
|
|
560
|
+
}
|
|
561
|
+
current[parts[parts.length - 1]] = value;
|
|
562
|
+
}
|
|
563
|
+
const { config, configPath } = patchConfig(nested);
|
|
564
|
+
for (const [key, value] of Object.entries(patches)) {
|
|
565
|
+
console.log('[myclaw-config] ✅ ' + key + ' → ' + JSON.stringify(value));
|
|
544
566
|
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
ask: "off"
|
|
567
|
+
} else {
|
|
568
|
+
console.log('[myclaw-config] ⏭️ Config 补丁已禁用 [disabled]');
|
|
548
569
|
}
|
|
549
|
-
}
|
|
550
|
-
console.log('[myclaw-config] ✅ session.reset → idle (90720 分钟)');
|
|
551
|
-
console.log('[myclaw-config] ✅ exec.ask → off');
|
|
570
|
+
}
|
|
552
571
|
} catch (err) {
|
|
553
572
|
console.log('[myclaw-config] ⚠️ 配置补丁失败: ' + err.message);
|
|
554
573
|
}
|
|
555
574
|
|
|
556
|
-
if (uiResult.success || skillResult.success) {
|
|
575
|
+
if (uiResult.success || skillResult.success || (agentResult && agentResult.success)) {
|
|
557
576
|
console.log('');
|
|
558
577
|
console.log(bar);
|
|
559
578
|
console.log('下一步: 运行 ' + colors.yellow + 'myclaw restart' + colors.nc + ' 重启 Gateway 使注入生效');
|
|
@@ -705,6 +724,102 @@ function runUpdate() {
|
|
|
705
724
|
console.log('');
|
|
706
725
|
}
|
|
707
726
|
|
|
727
|
+
// ============================================================================
|
|
728
|
+
// 资源管理列表
|
|
729
|
+
// ============================================================================
|
|
730
|
+
|
|
731
|
+
function runList() {
|
|
732
|
+
const fs = require('fs');
|
|
733
|
+
const path = require('path');
|
|
734
|
+
const os = require('os');
|
|
735
|
+
const { loadManifest } = require('./patch-agent');
|
|
736
|
+
const bar = '----------------------------------------';
|
|
737
|
+
|
|
738
|
+
console.log('');
|
|
739
|
+
console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + '注入资源管理' + colors.nc);
|
|
740
|
+
console.log(bar);
|
|
741
|
+
|
|
742
|
+
const manifest = loadManifest();
|
|
743
|
+
if (!manifest) {
|
|
744
|
+
console.log('');
|
|
745
|
+
console.log('[' + colors.yellow + '提示' + colors.nc + '] patch-manifest.json 不存在');
|
|
746
|
+
console.log(' 运行 ' + colors.yellow + 'myclaw patch' + colors.nc + ' 将自动生成');
|
|
747
|
+
console.log('');
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const openclawDir = path.join(os.homedir(), '.openclaw');
|
|
752
|
+
const configPath = path.join(openclawDir, 'openclaw.json');
|
|
753
|
+
let registeredIds = new Set();
|
|
754
|
+
try {
|
|
755
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
756
|
+
if (cfg.agents && Array.isArray(cfg.agents.list)) {
|
|
757
|
+
for (const a of cfg.agents.list) {
|
|
758
|
+
if (a && a.id) registeredIds.add(a.id);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
} catch {}
|
|
762
|
+
|
|
763
|
+
// === Agents ===
|
|
764
|
+
console.log('');
|
|
765
|
+
console.log(' ' + colors.blue + '🤖 智能体 (Agents)' + colors.nc);
|
|
766
|
+
if (manifest.agents && manifest.agents.length > 0) {
|
|
767
|
+
for (const a of manifest.agents) {
|
|
768
|
+
const wsPath = path.join(openclawDir, a.workspace || ('workspace-' + a.id));
|
|
769
|
+
const wsExists = fs.existsSync(wsPath);
|
|
770
|
+
const registered = registeredIds.has(a.id);
|
|
771
|
+
const status = wsExists && registered ? colors.green + '✔ 已安装' + colors.nc
|
|
772
|
+
: wsExists ? colors.yellow + '⚠ 文件存在未注册' + colors.nc
|
|
773
|
+
: colors.red + '✗ 未安装' + colors.nc;
|
|
774
|
+
const strat = a.strategy === 'off' ? colors.red + a.strategy + colors.nc : a.strategy;
|
|
775
|
+
console.log(' ' + padRight(a.id, 20) + status + ' [' + strat + ']');
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
console.log(' (无)');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// === Skills ===
|
|
782
|
+
console.log('');
|
|
783
|
+
console.log(' ' + colors.blue + '🧩 技能 (Skills)' + colors.nc);
|
|
784
|
+
if (manifest.skills && manifest.skills.length > 0) {
|
|
785
|
+
const skillsDir = path.join(openclawDir, 'skills');
|
|
786
|
+
for (const s of manifest.skills) {
|
|
787
|
+
const installed = fs.existsSync(path.join(skillsDir, s.name));
|
|
788
|
+
const status = installed ? colors.green + '✔ 已安装' + colors.nc : colors.red + '✗ 未安装' + colors.nc;
|
|
789
|
+
const strat = s.strategy === 'off' ? colors.red + s.strategy + colors.nc : s.strategy;
|
|
790
|
+
console.log(' ' + padRight(s.name, 20) + status + ' [' + strat + ']');
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
console.log(' (无)');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// === Config ===
|
|
797
|
+
console.log('');
|
|
798
|
+
console.log(' ' + colors.blue + '⚙️ 配置 (Config Patches)' + colors.nc);
|
|
799
|
+
if (manifest.config && manifest.config.patches) {
|
|
800
|
+
const strat = manifest.config.strategy || 'overwrite';
|
|
801
|
+
const stratDisplay = strat === 'off' ? colors.red + strat + colors.nc : strat;
|
|
802
|
+
console.log(' 策略: [' + stratDisplay + ']');
|
|
803
|
+
for (const [key, value] of Object.entries(manifest.config.patches)) {
|
|
804
|
+
console.log(' ' + padRight(key, 30) + '→ ' + JSON.stringify(value));
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
console.log(' (无)');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
console.log('');
|
|
811
|
+
console.log(bar);
|
|
812
|
+
console.log('策略说明: ' + colors.green + 'auto' + colors.nc + '=不存在才注入 '
|
|
813
|
+
+ colors.yellow + 'overwrite' + colors.nc + '=覆盖 '
|
|
814
|
+
+ colors.red + 'off' + colors.nc + '=关闭');
|
|
815
|
+
console.log('');
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function padRight(str, len) {
|
|
819
|
+
if (str.length >= len) return str + ' ';
|
|
820
|
+
return str + ' '.repeat(len - str.length);
|
|
821
|
+
}
|
|
822
|
+
|
|
708
823
|
// ============================================================================
|
|
709
824
|
// 帮助信息
|
|
710
825
|
// ============================================================================
|
|
@@ -732,7 +847,9 @@ function showHelp() {
|
|
|
732
847
|
console.log(' open 打开浏览器控制台(自动带 token)');
|
|
733
848
|
console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows)');
|
|
734
849
|
console.log(' bat 在桌面生成一键启动脚本 (仅限 Windows)');
|
|
735
|
-
console.log('
|
|
850
|
+
console.log(' list 查看注入资源管理列表(智能体/技能/配置)');
|
|
851
|
+
console.log(' pull 从 ~/.openclaw 拉取最新资源到源目录(开发用)');
|
|
852
|
+
console.log(' patch 注入 MyClaw UI + 技能 + 智能体 + 配置');
|
|
736
853
|
console.log(' unpatch 回滚 UI 注入(恢复原版)');
|
|
737
854
|
console.log(' minimax 注入 MiniMax 模型配置 (可选: --key sk-xxx)');
|
|
738
855
|
console.log(' restart 重启 OpenClaw Gateway');
|
|
@@ -791,6 +908,11 @@ if (!command || command === 'help' || command === '--help' || command === '-h')
|
|
|
791
908
|
runLaunch();
|
|
792
909
|
} else if (command === 'bat') {
|
|
793
910
|
runBat();
|
|
911
|
+
} else if (command === 'list') {
|
|
912
|
+
runList();
|
|
913
|
+
} else if (command === 'pull') {
|
|
914
|
+
const pull = require('./pull');
|
|
915
|
+
pull.run();
|
|
794
916
|
} else if (command === 'patch') {
|
|
795
917
|
runPatch();
|
|
796
918
|
} else if (command === 'unpatch') {
|
package/package.json
CHANGED
package/patch-agent.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* MyClaw Patch Agent Engine - 智能体上传
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* 1. 读取 patch-manifest.json 中的 agents 列表
|
|
8
|
+
* 2. 根据 strategy 决定行为:
|
|
9
|
+
* - auto: 目标 workspace 不存在时才注入(默认)
|
|
10
|
+
* - overwrite: 始终覆盖更新 workspace 文件
|
|
11
|
+
* - off: 跳过,不注入
|
|
12
|
+
* 3. 将 workspace 复制到 ~/.openclaw/ 下
|
|
13
|
+
* 4. 在 openclaw.json 的 agents.list 中注册
|
|
14
|
+
* 5. 创建 agent 目录结构
|
|
15
|
+
*
|
|
16
|
+
* ============================================================================
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 递归复制目录(幂等覆盖)
|
|
25
|
+
*/
|
|
26
|
+
function copyDirSync(src, dest) {
|
|
27
|
+
if (!fs.existsSync(dest)) {
|
|
28
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const srcPath = path.join(src, entry.name);
|
|
34
|
+
const destPath = path.join(dest, entry.name);
|
|
35
|
+
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
copyDirSync(srcPath, destPath);
|
|
38
|
+
} else {
|
|
39
|
+
fs.copyFileSync(srcPath, destPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 加载 manifest
|
|
46
|
+
*/
|
|
47
|
+
function loadManifest() {
|
|
48
|
+
const manifestPath = path.join(__dirname, 'patch-manifest.json');
|
|
49
|
+
if (!fs.existsSync(manifestPath)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 注入所有预打包智能体
|
|
57
|
+
*/
|
|
58
|
+
function patchAgents() {
|
|
59
|
+
const openclawDir = path.join(os.homedir(), '.openclaw');
|
|
60
|
+
const configPath = path.join(openclawDir, 'openclaw.json');
|
|
61
|
+
|
|
62
|
+
// 检查 openclaw 是否已初始化
|
|
63
|
+
if (!fs.existsSync(configPath)) {
|
|
64
|
+
console.log('[myclaw-agent] ❌ 未找到 openclaw.json');
|
|
65
|
+
console.log('[myclaw-agent] 提示: 请先运行 openclaw onboard 完成初始化');
|
|
66
|
+
return { success: false, reason: 'config-not-found' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 加载 manifest
|
|
70
|
+
const manifest = loadManifest();
|
|
71
|
+
if (!manifest || !Array.isArray(manifest.agents) || manifest.agents.length === 0) {
|
|
72
|
+
console.log('[myclaw-agent] ⚠️ 无智能体可上传 (manifest 为空或无 agents)');
|
|
73
|
+
return { success: true, count: 0 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 加载配置
|
|
77
|
+
let configData;
|
|
78
|
+
try {
|
|
79
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
80
|
+
configData = JSON.parse(raw);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.log('[myclaw-agent] ❌ openclaw.json 读取失败: ' + err.message);
|
|
83
|
+
return { success: false, reason: 'config-read-error' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 确保 agents 结构存在
|
|
87
|
+
if (!configData.agents) {
|
|
88
|
+
configData.agents = {};
|
|
89
|
+
}
|
|
90
|
+
if (!Array.isArray(configData.agents.list)) {
|
|
91
|
+
configData.agents.list = [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 已注册的 agent ID 集合
|
|
95
|
+
const existingIds = new Set(
|
|
96
|
+
configData.agents.list
|
|
97
|
+
.filter(e => e && typeof e === 'object')
|
|
98
|
+
.map(e => e.id)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const agentListDir = path.join(__dirname, 'agent-list');
|
|
102
|
+
|
|
103
|
+
console.log('[myclaw-agent] 📋 Manifest: ' + manifest.agents.length + ' 个智能体');
|
|
104
|
+
|
|
105
|
+
let uploaded = 0;
|
|
106
|
+
let updated = 0;
|
|
107
|
+
let skipped = 0;
|
|
108
|
+
let configChanged = false;
|
|
109
|
+
|
|
110
|
+
for (const entry of manifest.agents) {
|
|
111
|
+
const agentId = entry.id;
|
|
112
|
+
const workspaceName = entry.workspace || ('workspace-' + agentId);
|
|
113
|
+
const strategy = entry.strategy || 'auto';
|
|
114
|
+
const desc = entry.description || agentId;
|
|
115
|
+
|
|
116
|
+
// off → 跳过
|
|
117
|
+
if (strategy === 'off') {
|
|
118
|
+
console.log('[myclaw-agent] ⏭️ 跳过: ' + agentId + ' (' + desc + ') [off]');
|
|
119
|
+
skipped++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const srcWorkspace = path.join(agentListDir, workspaceName);
|
|
124
|
+
const destWorkspace = path.join(openclawDir, workspaceName);
|
|
125
|
+
const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
|
|
126
|
+
|
|
127
|
+
// 检查源文件是否存在
|
|
128
|
+
if (!fs.existsSync(srcWorkspace)) {
|
|
129
|
+
console.log('[myclaw-agent] ⚠️ 源目录不存在: ' + workspaceName + ',跳过');
|
|
130
|
+
skipped++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const alreadyExists = fs.existsSync(destWorkspace);
|
|
135
|
+
|
|
136
|
+
// auto → 已存在则跳过
|
|
137
|
+
if (strategy === 'auto' && alreadyExists) {
|
|
138
|
+
console.log('[myclaw-agent] ✔️ 已存在: ' + agentId + ' (' + desc + ') [auto, 跳过]');
|
|
139
|
+
skipped++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 执行复制
|
|
144
|
+
copyDirSync(srcWorkspace, destWorkspace);
|
|
145
|
+
|
|
146
|
+
// 确保 agent 目录结构存在
|
|
147
|
+
if (!fs.existsSync(agentDir)) {
|
|
148
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
const sessionsDir = path.join(openclawDir, 'agents', agentId, 'sessions');
|
|
151
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
152
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 注册到 openclaw.json
|
|
156
|
+
if (!existingIds.has(agentId)) {
|
|
157
|
+
configData.agents.list.push({
|
|
158
|
+
id: agentId,
|
|
159
|
+
name: agentId,
|
|
160
|
+
workspace: destWorkspace,
|
|
161
|
+
agentDir: agentDir,
|
|
162
|
+
});
|
|
163
|
+
existingIds.add(agentId);
|
|
164
|
+
configChanged = true;
|
|
165
|
+
uploaded++;
|
|
166
|
+
console.log('[myclaw-agent] ✅ 已上传并注册: ' + agentId + ' (' + desc + ') [' + strategy + ']');
|
|
167
|
+
} else if (alreadyExists) {
|
|
168
|
+
updated++;
|
|
169
|
+
console.log('[myclaw-agent] 🔄 已覆盖更新: ' + agentId + ' (' + desc + ') [' + strategy + ']');
|
|
170
|
+
} else {
|
|
171
|
+
updated++;
|
|
172
|
+
console.log('[myclaw-agent] 🔄 已更新: ' + agentId + ' (' + desc + ')');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 写回配置文件
|
|
177
|
+
if (configChanged) {
|
|
178
|
+
try {
|
|
179
|
+
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + '\n', 'utf8');
|
|
180
|
+
console.log('[myclaw-agent] 💾 openclaw.json 已更新');
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.log('[myclaw-agent] ❌ openclaw.json 写入失败: ' + err.message);
|
|
183
|
+
return { success: false, reason: 'config-write-error' };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('[myclaw-agent] 🎉 智能体上传完成: ' + uploaded + ' 新建, ' + updated + ' 更新, ' + skipped + ' 跳过');
|
|
188
|
+
return { success: true, uploaded, updated, skipped, total: uploaded + updated };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 列出 manifest 中所有智能体及状态
|
|
193
|
+
*/
|
|
194
|
+
function listAgents() {
|
|
195
|
+
const openclawDir = path.join(os.homedir(), '.openclaw');
|
|
196
|
+
const manifest = loadManifest();
|
|
197
|
+
|
|
198
|
+
if (!manifest || !Array.isArray(manifest.agents)) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return manifest.agents.map(entry => {
|
|
203
|
+
const workspaceName = entry.workspace || ('workspace-' + entry.id);
|
|
204
|
+
const destWorkspace = path.join(openclawDir, workspaceName);
|
|
205
|
+
return {
|
|
206
|
+
id: entry.id,
|
|
207
|
+
description: entry.description || entry.id,
|
|
208
|
+
strategy: entry.strategy || 'if-absent',
|
|
209
|
+
installed: fs.existsSync(destWorkspace),
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { patchAgents, listAgents, loadManifest };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": "MyClaw 注入清单 (auto-generated)。strategy: auto | overwrite | off",
|
|
3
|
+
"_generated": "2026-04-01T12:06:06.605Z",
|
|
4
|
+
"agents": [
|
|
5
|
+
{
|
|
6
|
+
"id": "danci",
|
|
7
|
+
"workspace": "workspace-danci",
|
|
8
|
+
"strategy": "auto",
|
|
9
|
+
"description": "danci"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"skills": [
|
|
13
|
+
{
|
|
14
|
+
"name": "minimax-inject",
|
|
15
|
+
"strategy": "auto",
|
|
16
|
+
"description": "minimax-inject"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"config": {
|
|
20
|
+
"strategy": "overwrite",
|
|
21
|
+
"patches": {
|
|
22
|
+
"session.reset.mode": "idle",
|
|
23
|
+
"session.reset.idleMinutes": 90720,
|
|
24
|
+
"tools.exec.ask": "off"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/patch-skill.js
CHANGED
|
@@ -19,49 +19,24 @@ const fs = require('fs');
|
|
|
19
19
|
const path = require('path');
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* 获取 skills 目录:~/.openclaw/skills/
|
|
23
|
+
* 不存在则创建,失败则返回 null
|
|
24
24
|
*/
|
|
25
25
|
function findSkillsDir() {
|
|
26
26
|
const os = require('os');
|
|
27
|
+
const openclawDir = path.join(os.homedir(), '.openclaw');
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// 1. 环境变量优先
|
|
32
|
-
if (process.env.OPENCLAW_HOME) {
|
|
33
|
-
openclawRoots.push(process.env.OPENCLAW_HOME);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 2. 标准路径 ~/.openclaw/
|
|
37
|
-
openclawRoots.push(path.join(os.homedir(), '.openclaw'));
|
|
38
|
-
|
|
39
|
-
// 3. WSL 下 /root/.openclaw/
|
|
40
|
-
if (os.platform() === 'linux') {
|
|
41
|
-
openclawRoots.push(path.join('/root', '.openclaw'));
|
|
29
|
+
if (!fs.existsSync(openclawDir)) {
|
|
30
|
+
return null;
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const skillsDir = path.join(root, 'skills');
|
|
49
|
-
// 如果 skills 目录不存在,试探性创建
|
|
50
|
-
if (!fs.existsSync(skillsDir)) {
|
|
51
|
-
try {
|
|
52
|
-
fs.mkdirSync(skillsDir, { recursive: true });
|
|
53
|
-
console.log('[myclaw-skill] 📁 已创建 skills 目录: ' + skillsDir);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.log('[myclaw-skill] ⚠️ 无法创建 skills 目录: ' + err.message);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return skillsDir;
|
|
60
|
-
}
|
|
61
|
-
} catch {}
|
|
33
|
+
const skillsDir = path.join(openclawDir, 'skills');
|
|
34
|
+
if (!fs.existsSync(skillsDir)) {
|
|
35
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
36
|
+
console.log('[myclaw-skill] 📁 已创建 skills 目录: ' + skillsDir);
|
|
62
37
|
}
|
|
63
38
|
|
|
64
|
-
return
|
|
39
|
+
return skillsDir;
|
|
65
40
|
}
|
|
66
41
|
|
|
67
42
|
/**
|
package/publish.sh
CHANGED
package/pull.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* MyClaw Pull - 从 ~/.openclaw/ 拉取最新资源到 myclaw 源目录
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 反向同步:将线上运行环境中的智能体、技能拷贝回 myclaw 包内,
|
|
7
|
+
* 确保下次 publish 时打包的是最新版本。
|
|
8
|
+
*
|
|
9
|
+
* 拉取范围:
|
|
10
|
+
* 1. manifest 中已有的 agents → 从 ~/.openclaw/workspace-* 回拷到 agent-list/
|
|
11
|
+
* 2. manifest 中已有的 skills → 从 ~/.openclaw/skills/* 回拷到 skills/
|
|
12
|
+
* 3. 如果发现 ~/.openclaw/ 中有新的 workspace-* 不在 manifest 中,提示是否纳入
|
|
13
|
+
*
|
|
14
|
+
* ============================================================================
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
|
|
21
|
+
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
22
|
+
const AGENT_LIST_DIR = path.join(__dirname, 'agent-list');
|
|
23
|
+
const SKILLS_DIR = path.join(__dirname, 'skills');
|
|
24
|
+
const MANIFEST_PATH = path.join(__dirname, 'patch-manifest.json');
|
|
25
|
+
|
|
26
|
+
// 不应拉取的 workspace(排除列表)
|
|
27
|
+
const WORKSPACE_IGNORE = new Set([
|
|
28
|
+
'workspace', // 默认 workspace(非 agent)
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 递归复制目录(幂等覆盖)
|
|
33
|
+
* 跳过 .openclaw/ 和 node_modules/ 等运行时目录
|
|
34
|
+
*/
|
|
35
|
+
function copyDirSync(src, dest, skipDirs = ['.openclaw', 'node_modules', '.git']) {
|
|
36
|
+
if (!fs.existsSync(dest)) {
|
|
37
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (skipDirs.includes(entry.name)) continue;
|
|
43
|
+
|
|
44
|
+
const srcPath = path.join(src, entry.name);
|
|
45
|
+
const destPath = path.join(dest, entry.name);
|
|
46
|
+
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
copyDirSync(srcPath, destPath, skipDirs);
|
|
49
|
+
} else {
|
|
50
|
+
fs.copyFileSync(srcPath, destPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 统计目录内文件数量
|
|
57
|
+
*/
|
|
58
|
+
function countFiles(dir) {
|
|
59
|
+
if (!fs.existsSync(dir)) return 0;
|
|
60
|
+
let count = 0;
|
|
61
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
62
|
+
for (const e of entries) {
|
|
63
|
+
if (e.isDirectory()) {
|
|
64
|
+
count += countFiles(path.join(dir, e.name));
|
|
65
|
+
} else {
|
|
66
|
+
count++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return count;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 加载 manifest
|
|
74
|
+
*/
|
|
75
|
+
function loadManifest() {
|
|
76
|
+
if (!fs.existsSync(MANIFEST_PATH)) return null;
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
|
79
|
+
} catch { return null; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 扫描 ~/.openclaw/ 下所有 workspace-* 目录
|
|
84
|
+
*/
|
|
85
|
+
function scanLiveWorkspaces() {
|
|
86
|
+
if (!fs.existsSync(OPENCLAW_DIR)) return [];
|
|
87
|
+
return fs.readdirSync(OPENCLAW_DIR, { withFileTypes: true })
|
|
88
|
+
.filter(d => d.isDirectory() && d.name.startsWith('workspace-') && !WORKSPACE_IGNORE.has(d.name))
|
|
89
|
+
.map(d => ({
|
|
90
|
+
dirName: d.name,
|
|
91
|
+
id: d.name.replace(/^workspace-/, ''),
|
|
92
|
+
fullPath: path.join(OPENCLAW_DIR, d.name),
|
|
93
|
+
}))
|
|
94
|
+
.filter(ws => !AGENT_LIST_DIR.startsWith(ws.fullPath)); // 避免无限递归(比如拉取源码所在的 workspace)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 扫描 ~/.openclaw/skills/ 下所有技能目录
|
|
99
|
+
*/
|
|
100
|
+
function scanLiveSkills() {
|
|
101
|
+
const skillsDir = path.join(OPENCLAW_DIR, 'skills');
|
|
102
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
103
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
104
|
+
.filter(d => d.isDirectory())
|
|
105
|
+
.map(d => ({
|
|
106
|
+
name: d.name,
|
|
107
|
+
fullPath: path.join(skillsDir, d.name),
|
|
108
|
+
hasSkillMd: fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md')),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 执行拉取
|
|
114
|
+
*/
|
|
115
|
+
function run() {
|
|
116
|
+
const bar = '----------------------------------------';
|
|
117
|
+
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log('[\x1b[34mMyClaw\x1b[0m] \x1b[34mPull - 拉取最新资源\x1b[0m');
|
|
120
|
+
console.log(bar);
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
if (!fs.existsSync(OPENCLAW_DIR)) {
|
|
124
|
+
console.log('[\x1b[31m错误\x1b[0m] ~/.openclaw 目录不存在');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 确保目标目录存在
|
|
129
|
+
if (!fs.existsSync(AGENT_LIST_DIR)) {
|
|
130
|
+
fs.mkdirSync(AGENT_LIST_DIR, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const manifest = loadManifest();
|
|
134
|
+
const manifestAgentIds = new Set();
|
|
135
|
+
const manifestSkillNames = new Set();
|
|
136
|
+
|
|
137
|
+
if (manifest) {
|
|
138
|
+
if (manifest.agents) manifest.agents.forEach(a => manifestAgentIds.add(a.id));
|
|
139
|
+
if (manifest.skills) manifest.skills.forEach(s => manifestSkillNames.add(s.name));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// === 1. 拉取智能体 ===
|
|
143
|
+
console.log('\x1b[34m🤖 智能体 (Agents)\x1b[0m');
|
|
144
|
+
|
|
145
|
+
const liveWorkspaces = scanLiveWorkspaces();
|
|
146
|
+
let agentPulled = 0;
|
|
147
|
+
for (const ws of liveWorkspaces) {
|
|
148
|
+
const destDir = path.join(AGENT_LIST_DIR, ws.dirName);
|
|
149
|
+
const inManifest = manifestAgentIds.has(ws.id);
|
|
150
|
+
|
|
151
|
+
if (inManifest) {
|
|
152
|
+
const fileCount = countFiles(ws.fullPath);
|
|
153
|
+
copyDirSync(ws.fullPath, destDir);
|
|
154
|
+
console.log(' 🔄 已同步: ' + ws.id + ' (' + fileCount + ' 文件)');
|
|
155
|
+
agentPulled++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (agentPulled === 0) {
|
|
160
|
+
console.log(' (没有需要同步的智能体)');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// === 2. 拉取技能 ===
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('\x1b[34m🧩 技能 (Skills)\x1b[0m');
|
|
166
|
+
|
|
167
|
+
const liveSkills = scanLiveSkills();
|
|
168
|
+
let skillPulled = 0;
|
|
169
|
+
|
|
170
|
+
for (const sk of liveSkills) {
|
|
171
|
+
const inManifest = manifestSkillNames.has(sk.name);
|
|
172
|
+
|
|
173
|
+
if (inManifest) {
|
|
174
|
+
if (!sk.hasSkillMd) {
|
|
175
|
+
console.log(' ⚠️ 跳过无 SKILL.md 的技能: ' + sk.name);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const destDir = path.join(SKILLS_DIR, sk.name);
|
|
179
|
+
const fileCount = countFiles(sk.fullPath);
|
|
180
|
+
copyDirSync(sk.fullPath, destDir);
|
|
181
|
+
console.log(' 🔄 已同步: ' + sk.name + ' (' + fileCount + ' 文件)');
|
|
182
|
+
skillPulled++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (skillPulled === 0) {
|
|
187
|
+
console.log(' (没有需要同步的技能)');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// === 汇总 ===
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(bar);
|
|
193
|
+
console.log('✅ Pull 完毕: 同步了 ' + agentPulled + ' 个智能体, ' + skillPulled + ' 个技能');
|
|
194
|
+
console.log('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { run };
|