@yottameta/yotta-intel 0.2.1 → 0.2.2
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/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/SKILL.md +1 -1
- package/bin/install.js +212 -121
- package/install.sh +216 -40
- package/package.json +1 -1
- package/scripts/test_yotta_intel.py +1 -1
- package/scripts/yotta_intel.py +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -108,8 +108,8 @@ Pick any of the four methods below; the order is the recommended priority. Skill
|
|
|
108
108
|
|
|
109
109
|
```text
|
|
110
110
|
# Optional China mirror: npm config set registry https://registry.npmmirror.com
|
|
111
|
-
npx -y @yottameta/yotta-intel@0.2.
|
|
112
|
-
npx -y @yottameta/yotta-intel@0.2.
|
|
111
|
+
npx -y @yottameta/yotta-intel@0.2.2 --agent <agent-name> # install to the agent's default user-level skills dir
|
|
112
|
+
npx -y @yottameta/yotta-intel@0.2.2 --dir <your-skills-dir> # point to the skills dir itself (e.g. ~/.codex/skills)
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
- `--agent <name>` installs to that agent's default user-level directory; `--list` shows each agent's default directory.
|
package/README.zh-CN.md
CHANGED
|
@@ -116,8 +116,8 @@ python3 scripts/yotta_intel.py refang --path safe.txt
|
|
|
116
116
|
|
|
117
117
|
```text
|
|
118
118
|
# 可选国内加速:npm config set registry https://registry.npmmirror.com
|
|
119
|
-
npx -y @yottameta/yotta-intel@0.2.
|
|
120
|
-
npx -y @yottameta/yotta-intel@0.2.
|
|
119
|
+
npx -y @yottameta/yotta-intel@0.2.2 --agent <智能体名称> # 装到指定智能体默认用户级技能目录
|
|
120
|
+
npx -y @yottameta/yotta-intel@0.2.2 --dir <智能体的技能目录> # 指到技能目录本身(如 ~/.codex/skills)
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
- `--agent <name>` 自动装到该智能体默认用户级目录;`--list` 可查看各智能体默认目录。
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-intel
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.2
|
|
4
4
|
description: 元情 —— 跨智能体的威胁情报 IOC 提取与规范化技能:零依赖自研从文本 / 日志 / 报告中提取 IP(IPv4/IPv6)、域名、URL、邮箱、哈希(MD5/SHA1/SHA256/SHA512)与 CVE 编号,识别并还原 defang 写法,去重、归一化后输出 CSV / JSON / STIX-lite。触发:用户给出含可疑 IP / 域名 / URL / 哈希的威胁情报文本、恶意样本分析报告、钓鱼邮件或日志,要提取 IOC、规范化、去重、转格式、共享情报时。边界:纯本地离线提取与规范化;不联网查证、不下载样本、不主动扫描任何系统;仅用于已获授权 / 自有资产 / 教学环境的安全分析。
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
package/bin/install.js
CHANGED
|
@@ -1,223 +1,314 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* yotta-intel 跨平台安装器(YottaSkills)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* npx -y @yottameta/yotta-intel --agent <name>
|
|
7
|
-
* npx -y @yottameta/yotta-intel --dir <path>
|
|
8
|
-
* npx -y @yottameta/yotta-intel -g
|
|
9
|
-
* npx -y @yottameta/yotta-intel
|
|
10
|
-
* npx -y @yottameta/yotta-intel --list
|
|
5
|
+
* 用法:
|
|
6
|
+
* npx -y @yottameta/yotta-intel --agent <name> # 按智能体默认用户级目录安装(推荐)
|
|
7
|
+
* npx -y @yottameta/yotta-intel --dir <path> # 装到指定目录(用户改了目录的智能体)
|
|
8
|
+
* npx -y @yottameta/yotta-intel -g # 安装到全部已知智能体用户级目录
|
|
9
|
+
* npx -y @yottameta/yotta-intel # 安装到检测到的项目级目录
|
|
10
|
+
* npx -y @yottameta/yotta-intel --list # 列出智能体 -> 默认目录
|
|
11
|
+
* npx -y @yottameta/yotta-intel --dry-run # 只显示将写入的目标,不写文件
|
|
12
|
+
* npx -y @yottameta/yotta-intel --version # 显示版本
|
|
13
|
+
* npx -y @yottameta/yotta-intel --help # 显示帮助
|
|
14
|
+
*
|
|
15
|
+
* 退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误
|
|
11
16
|
*/
|
|
12
17
|
'use strict';
|
|
13
|
-
|
|
14
18
|
const fs = require('fs');
|
|
15
19
|
const path = require('path');
|
|
16
20
|
const os = require('os');
|
|
17
21
|
|
|
18
22
|
const SKILL_NAME = 'yotta-intel';
|
|
19
23
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
20
|
-
const COPY_SKIP = new Set(['package.json', 'bin', 'node_modules', '.git', '.github', 'test']);
|
|
21
24
|
|
|
25
|
+
// 顶层开发文件 / 目录:只在安装包顶层跳过;技能包内嵌套同名载荷必须保留。
|
|
26
|
+
// 与 install.sh 同口径:开发件与运行时不相关目录一律不落目标。
|
|
27
|
+
const TOP_SKIP = [
|
|
28
|
+
'package.json', 'package-lock.json', 'bin', 'lib', 'test',
|
|
29
|
+
'.github', '.git', '.gitignore', '.npmignore', '.gitattributes',
|
|
30
|
+
'.yotta', '.tmp', 'install.sh', 'node_modules',
|
|
31
|
+
];
|
|
32
|
+
const TOP_SKIP_SET = new Set(TOP_SKIP);
|
|
33
|
+
// 缓存 / 编译产物:任意层级跳过,安装时同样清理。
|
|
34
|
+
const CACHE_DIRS = new Set(['__pycache__', '.pytest_cache', '.mypy_cache']);
|
|
35
|
+
|
|
36
|
+
class UsageError extends Error {}
|
|
37
|
+
class TargetError extends Error {}
|
|
38
|
+
class InstallError extends Error {}
|
|
39
|
+
|
|
40
|
+
// 智能体 -> 用户级默认技能目录(dirs 按优先级排列;--agent 装到第一个)
|
|
41
|
+
// 依据官方文档:.agents/skills 并非通用目录,被 OpenCode / Cursor / Cline / Amp /
|
|
42
|
+
// Kimi / Gemini CLI / GitHub Copilot 等读取;Claude Code 与 Codex 默认不读 .agents。
|
|
22
43
|
const AGENT_DIRS = {
|
|
23
44
|
claude: { label: 'Claude Code', dirs: ['.claude/skills'] },
|
|
24
45
|
cursor: { label: 'Cursor', dirs: ['.cursor/skills', '.agents/skills'] },
|
|
25
|
-
codex: { label: 'Codex', dirs: ['.codex/skills'] },
|
|
46
|
+
codex: { label: 'Codex', dirs: ['.codex/skills'] }, // 特判:$CODEX_HOME/skills
|
|
26
47
|
gemini: { label: 'Gemini CLI', dirs: ['.gemini/skills', '.agents/skills'] },
|
|
27
48
|
goose: { label: 'Goose', dirs: ['.config/goose/skills', '.agents/skills'] },
|
|
28
49
|
amp: { label: 'Amp', dirs: ['.config/agents/skills', '.agents/skills'] },
|
|
29
|
-
opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] },
|
|
50
|
+
opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] }, // 特判:$XDG_CONFIG_HOME
|
|
30
51
|
windsurf: { label: 'Windsurf', dirs: ['.codeium/windsurf/skills'] },
|
|
31
52
|
workbuddy: { label: 'WorkBuddy', dirs: ['.workbuddy/skills'] },
|
|
32
53
|
kiro: { label: 'Kiro', dirs: ['.kiro/skills'] },
|
|
33
54
|
trae: { label: 'Trae Code CLI', dirs: ['.traecli/skills'] },
|
|
34
|
-
'trae-cn': { label: 'Trae IDE',
|
|
55
|
+
'trae-cn': { label: 'Trae IDE(国内)', dirs: ['.trae-cn/skills'] },
|
|
35
56
|
qwen: { label: 'Qwen Code', dirs: ['.qwen/skills'] },
|
|
36
|
-
comate: { label: 'Comate',
|
|
57
|
+
comate: { label: 'Comate 文心快码', dirs: ['.comate/skills'] },
|
|
37
58
|
codebuddy: { label: 'CodeBuddy Code', dirs: ['.codebuddy/skills'] },
|
|
38
59
|
kimi: { label: 'Kimi Code CLI', dirs: ['.kimi/skills'] },
|
|
39
|
-
agents: { label: '
|
|
60
|
+
agents: { label: '通用 AGENTS.md', dirs: ['.agents/skills'] },
|
|
40
61
|
};
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
const PROJECT_DIRS = [
|
|
64
|
+
'.claude/skills',
|
|
65
|
+
'.cursor/skills',
|
|
66
|
+
'.codex/skills',
|
|
67
|
+
'.config/goose/skills',
|
|
68
|
+
'.config/agents/skills',
|
|
69
|
+
'.opencode/skills',
|
|
70
|
+
'.codeium/windsurf/skills',
|
|
71
|
+
'.workbuddy/skills',
|
|
72
|
+
'.kiro/skills',
|
|
73
|
+
'.traecli/skills',
|
|
74
|
+
'.gemini/skills',
|
|
75
|
+
'.trae-cn/skills',
|
|
76
|
+
'.qwen/skills',
|
|
77
|
+
'.comate/skills',
|
|
78
|
+
'.codebuddy/skills',
|
|
79
|
+
'.kimi/skills',
|
|
80
|
+
'.agents/skills',
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// Codex 用户级目录特判:优先 $CODEX_HOME/skills,否则 ~/.codex/skills
|
|
84
|
+
function codexUserDir() {
|
|
85
|
+
const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
86
|
+
return path.join(base, 'skills');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// OpenCode 用户级目录特判:优先 $XDG_CONFIG_HOME/opencode/skills,否则 ~/.config/opencode/skills
|
|
90
|
+
function opencodeUserDir() {
|
|
91
|
+
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
92
|
+
return path.join(base, 'opencode', 'skills');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveUserDir(rel) {
|
|
96
|
+
if (rel === '.codex/skills') return codexUserDir();
|
|
97
|
+
if (rel === '.config/opencode/skills') return opencodeUserDir();
|
|
98
|
+
return path.join(os.homedir(), rel);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function displayDir(rel) {
|
|
102
|
+
if (process.platform === 'win32') return '%USERPROFILE%\\' + rel.replace(/\//g, '\\');
|
|
103
|
+
return '~/' + rel;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function skillVersion() {
|
|
107
|
+
try {
|
|
108
|
+
const data = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
109
|
+
return data.version || null;
|
|
110
|
+
} catch (_) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
45
114
|
|
|
46
115
|
function usage() {
|
|
47
|
-
console.log('
|
|
116
|
+
console.log(SKILL_NAME + ' 安装器(YottaSkills)');
|
|
48
117
|
console.log('');
|
|
49
|
-
console.log('
|
|
50
|
-
console.log('
|
|
51
|
-
console.log('
|
|
52
|
-
console.log('
|
|
53
|
-
console.log('
|
|
54
|
-
console.log(' npx -y @yottameta/yotta-intel --list List supported agent directories');
|
|
118
|
+
console.log('用法:');
|
|
119
|
+
console.log(' node bin/install.js --agent <name> 按智能体默认用户级目录安装(推荐)');
|
|
120
|
+
console.log(' node bin/install.js --dir <path> 装到指定目录(用户改了目录的智能体)');
|
|
121
|
+
console.log(' node bin/install.js -g 安装到全部已知智能体用户级目录');
|
|
122
|
+
console.log(' node bin/install.js 安装到检测到的项目级目录');
|
|
55
123
|
console.log('');
|
|
56
|
-
console.log('
|
|
57
|
-
console.log(' --agent <name>
|
|
58
|
-
console.log(' --dir <path>
|
|
59
|
-
console.log(' -g, --global
|
|
60
|
-
console.log(' --list, -l
|
|
61
|
-
console.log(' -
|
|
124
|
+
console.log('参数:');
|
|
125
|
+
console.log(' --agent <name> 智能体键名,见 --list');
|
|
126
|
+
console.log(' --dir <path> 自定义技能目录');
|
|
127
|
+
console.log(' -g, --global 安装到全部已知用户级目录');
|
|
128
|
+
console.log(' --list, -l 列出支持的智能体目录');
|
|
129
|
+
console.log(' --dry-run 只显示将写入的目标,不写文件');
|
|
130
|
+
console.log(' --version, -v 显示版本');
|
|
131
|
+
console.log(' --help, -h 显示帮助');
|
|
132
|
+
console.log(' --yes, -y 兼容参数(-g 不再强制要求)');
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log('退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误');
|
|
62
135
|
}
|
|
63
136
|
|
|
64
137
|
function parseArgs(argv) {
|
|
65
|
-
const opts = { help: false, list: false, global: false, dir: null, agent: null };
|
|
138
|
+
const opts = { help: false, version: false, list: false, global: false, dryRun: false, yes: false, dir: null, agent: null };
|
|
66
139
|
for (let i = 0; i < argv.length; i++) {
|
|
67
140
|
const arg = argv[i];
|
|
68
141
|
if (arg === '--help' || arg === '-h') opts.help = true;
|
|
142
|
+
else if (arg === '--version' || arg === '-v') opts.version = true;
|
|
69
143
|
else if (arg === '--list' || arg === '-l') opts.list = true;
|
|
70
144
|
else if (arg === '--global' || arg === '-g') opts.global = true;
|
|
145
|
+
else if (arg === '--dry-run') opts.dryRun = true;
|
|
146
|
+
else if (arg === '--yes' || arg === '-y') opts.yes = true;
|
|
71
147
|
else if (arg === '--dir') {
|
|
72
148
|
const value = argv[++i];
|
|
73
|
-
if (!value) throw new UsageError('--dir
|
|
149
|
+
if (!value) throw new UsageError('--dir 需要一个非空路径');
|
|
74
150
|
opts.dir = value;
|
|
75
151
|
} else if (arg === '--agent') {
|
|
76
152
|
const value = argv[++i];
|
|
77
|
-
if (!value) throw new UsageError('--agent
|
|
78
|
-
opts.agent = value.toLowerCase();
|
|
153
|
+
if (!value) throw new UsageError('--agent 需要一个非空名称');
|
|
154
|
+
opts.agent = String(value).toLowerCase();
|
|
79
155
|
} else {
|
|
80
|
-
throw new UsageError('
|
|
156
|
+
throw new UsageError('未知参数: ' + arg);
|
|
81
157
|
}
|
|
82
158
|
}
|
|
83
|
-
if (!opts.help) {
|
|
159
|
+
if (!opts.help && !opts.version) {
|
|
84
160
|
const selected = [opts.dir, opts.agent, opts.global].filter(Boolean).length;
|
|
85
|
-
if (selected > 1) throw new UsageError('
|
|
161
|
+
if (selected > 1) throw new UsageError('--dir / --agent / -g 只能选一个');
|
|
86
162
|
}
|
|
87
163
|
return opts;
|
|
88
164
|
}
|
|
89
165
|
|
|
90
|
-
function codexUserDir() {
|
|
91
|
-
const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
92
|
-
return path.join(base, 'skills');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function opencodeUserDir() {
|
|
96
|
-
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
97
|
-
return path.join(base, 'opencode', 'skills');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function resolveUserDir(rel) {
|
|
101
|
-
if (rel === '.codex/skills') return codexUserDir();
|
|
102
|
-
if (rel === '.config/opencode/skills') return opencodeUserDir();
|
|
103
|
-
return path.join(os.homedir(), rel);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function displayDir(rel) {
|
|
107
|
-
if (process.platform === 'win32') return '%USERPROFILE%\\' + rel.replace(/\//g, '\\');
|
|
108
|
-
return '~/' + rel;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
166
|
function printList() {
|
|
112
|
-
console.log('
|
|
167
|
+
console.log('智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):');
|
|
113
168
|
for (const [key, value] of Object.entries(AGENT_DIRS)) {
|
|
114
|
-
|
|
169
|
+
const resolved = value.dirs.map(displayDir);
|
|
170
|
+
console.log(' ' + key.padEnd(10) + value.label.padEnd(18) + resolved.join('、'));
|
|
115
171
|
}
|
|
116
|
-
console.log('');
|
|
117
|
-
console.log('
|
|
172
|
+
console.log('\n说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。');
|
|
173
|
+
console.log('改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。');
|
|
118
174
|
}
|
|
119
175
|
|
|
120
176
|
function assertSafeTarget(target) {
|
|
121
177
|
const rel = path.relative(PKG_ROOT, target);
|
|
122
178
|
if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {
|
|
123
|
-
throw new UsageError('
|
|
179
|
+
throw new UsageError('目标目录不能在技能源目录内(防止自装自毁)');
|
|
124
180
|
}
|
|
125
181
|
}
|
|
126
182
|
|
|
127
|
-
function
|
|
183
|
+
function shouldSkipCache(name, isFile) {
|
|
184
|
+
if (CACHE_DIRS.has(name)) return true;
|
|
185
|
+
if (isFile && (name.endsWith('.pyc') || name.endsWith('.pyo'))) return true;
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function copyDir(src, dst, topLevel) {
|
|
128
190
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
// 只跳过安装包顶层开发文件;template/ 等嵌套同名载荷必须保留。
|
|
192
|
+
if (topLevel && TOP_SKIP_SET.has(entry.name)) continue;
|
|
193
|
+
if (shouldSkipCache(entry.name, entry.isFile())) continue;
|
|
194
|
+
const s = path.join(src, entry.name);
|
|
195
|
+
const d = path.join(dst, entry.name);
|
|
196
|
+
if (entry.isDirectory()) {
|
|
197
|
+
fs.mkdirSync(d, { recursive: true });
|
|
198
|
+
copyDir(s, d, false);
|
|
199
|
+
} else if (entry.isFile()) {
|
|
200
|
+
fs.copyFileSync(s, d);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 清理旧版安装残留(fail-closed 白名单):
|
|
206
|
+
// 仅在目标目录已存在且含 SKILL.md 时触发;只删顶层开发项 + 任意层级缓存;
|
|
207
|
+
// 不整目录删除、不跟随符号链接。
|
|
208
|
+
function cleanResidue(target) {
|
|
209
|
+
const removed = [];
|
|
210
|
+
let stat = null;
|
|
211
|
+
try { stat = fs.statSync(target); } catch (_) { return removed; }
|
|
212
|
+
if (!stat.isDirectory()) return removed;
|
|
213
|
+
if (!fs.existsSync(path.join(target, 'SKILL.md'))) return removed;
|
|
214
|
+
for (const name of TOP_SKIP) {
|
|
215
|
+
const p = path.join(target, name);
|
|
216
|
+
let entry = null;
|
|
217
|
+
try { entry = fs.lstatSync(p); } catch (_) { continue; }
|
|
218
|
+
if (entry.isSymbolicLink()) continue;
|
|
219
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
220
|
+
removed.push(name);
|
|
221
|
+
}
|
|
222
|
+
const walk = (dir) => {
|
|
223
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
224
|
+
const p = path.join(dir, entry.name);
|
|
225
|
+
if (entry.isSymbolicLink()) continue;
|
|
133
226
|
if (entry.isDirectory()) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
227
|
+
if (CACHE_DIRS.has(entry.name)) {
|
|
228
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
229
|
+
removed.push(path.relative(target, p));
|
|
230
|
+
} else {
|
|
231
|
+
walk(p);
|
|
232
|
+
}
|
|
233
|
+
} else if (entry.isFile() && (entry.name.endsWith('.pyc') || entry.name.endsWith('.pyo'))) {
|
|
234
|
+
fs.unlinkSync(p);
|
|
235
|
+
removed.push(path.relative(target, p));
|
|
138
236
|
}
|
|
139
|
-
} catch (err) {
|
|
140
|
-
throw new InstallError('Failed to copy ' + from + ' -> ' + to + ': ' + err.message);
|
|
141
237
|
}
|
|
142
|
-
}
|
|
238
|
+
};
|
|
239
|
+
walk(target);
|
|
240
|
+
return removed;
|
|
143
241
|
}
|
|
144
242
|
|
|
145
|
-
function installTo(dest) {
|
|
146
|
-
if (!dest || typeof dest !== 'string') throw new UsageError('
|
|
243
|
+
function installTo(dest, opts) {
|
|
244
|
+
if (!dest || typeof dest !== 'string') throw new UsageError('目标目录不能为空');
|
|
147
245
|
const target = path.resolve(dest, SKILL_NAME);
|
|
148
246
|
assertSafeTarget(target);
|
|
247
|
+
if (opts.dryRun) {
|
|
248
|
+
console.log('[dry-run] 将安装到 -> ' + target);
|
|
249
|
+
return target;
|
|
250
|
+
}
|
|
149
251
|
try {
|
|
252
|
+
for (const rel of cleanResidue(target)) console.log('已清理残留: ' + rel);
|
|
150
253
|
fs.mkdirSync(target, { recursive: true });
|
|
151
|
-
copyDir(PKG_ROOT, target,
|
|
254
|
+
copyDir(PKG_ROOT, target, true);
|
|
152
255
|
if (!fs.existsSync(path.join(target, 'SKILL.md'))) {
|
|
153
|
-
throw new InstallError('
|
|
256
|
+
throw new InstallError('安装结果缺少 SKILL.md');
|
|
154
257
|
}
|
|
155
258
|
} catch (err) {
|
|
156
|
-
if (err instanceof UsageError || err instanceof InstallError) throw err;
|
|
157
|
-
throw new InstallError('
|
|
259
|
+
if (err instanceof UsageError || err instanceof TargetError || err instanceof InstallError) throw err;
|
|
260
|
+
throw new InstallError('安装到 ' + target + ' 失败: ' + err.message);
|
|
158
261
|
}
|
|
159
262
|
console.log('installed -> ' + target);
|
|
160
263
|
return target;
|
|
161
264
|
}
|
|
162
265
|
|
|
163
|
-
function projectDirs() {
|
|
164
|
-
return [
|
|
165
|
-
'.claude/skills',
|
|
166
|
-
'.cursor/skills',
|
|
167
|
-
'.codex/skills',
|
|
168
|
-
'.config/goose/skills',
|
|
169
|
-
'.config/agents/skills',
|
|
170
|
-
'.opencode/skills',
|
|
171
|
-
'.codeium/windsurf/skills',
|
|
172
|
-
'.workbuddy/skills',
|
|
173
|
-
'.kiro/skills',
|
|
174
|
-
'.traecli/skills',
|
|
175
|
-
'.gemini/skills',
|
|
176
|
-
'.trae-cn/skills',
|
|
177
|
-
'.qwen/skills',
|
|
178
|
-
'.comate/skills',
|
|
179
|
-
'.codebuddy/skills',
|
|
180
|
-
'.kimi/skills',
|
|
181
|
-
'.agents/skills',
|
|
182
|
-
].filter((dir) => fs.existsSync(dir));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
266
|
function run() {
|
|
186
267
|
const opts = parseArgs(process.argv.slice(2));
|
|
187
268
|
if (opts.help) { usage(); return; }
|
|
269
|
+
if (opts.version) {
|
|
270
|
+
const version = skillVersion();
|
|
271
|
+
if (!version) {
|
|
272
|
+
console.error(SKILL_NAME + ' 版本未知(未找到 package.json)');
|
|
273
|
+
process.exitCode = 1;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
console.log(SKILL_NAME + ' v' + version);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
188
279
|
if (opts.list) { printList(); return; }
|
|
189
280
|
|
|
190
|
-
if (opts.dir) { installTo(opts.dir); return; }
|
|
281
|
+
if (opts.dir) { installTo(opts.dir, opts); return; }
|
|
191
282
|
|
|
192
283
|
if (opts.agent) {
|
|
193
284
|
const info = AGENT_DIRS[opts.agent];
|
|
194
285
|
if (!info) {
|
|
195
|
-
throw new UsageError('
|
|
286
|
+
throw new UsageError('未收录智能体: ' + opts.agent + '。可用: ' + Object.keys(AGENT_DIRS).join(', ') + ';自定义目录请用 --dir <路径>。');
|
|
196
287
|
}
|
|
197
|
-
installTo(resolveUserDir(info.dirs[0]));
|
|
288
|
+
installTo(resolveUserDir(info.dirs[0]), opts);
|
|
289
|
+
console.log('完成。');
|
|
198
290
|
return;
|
|
199
291
|
}
|
|
200
292
|
|
|
201
293
|
if (opts.global) {
|
|
202
294
|
const seen = new Set();
|
|
203
|
-
let count = 0;
|
|
204
295
|
for (const value of Object.values(AGENT_DIRS)) {
|
|
205
|
-
for (const
|
|
206
|
-
if (seen.has(
|
|
207
|
-
seen.add(
|
|
208
|
-
installTo(resolveUserDir(
|
|
209
|
-
count++;
|
|
296
|
+
for (const rel of value.dirs) {
|
|
297
|
+
if (seen.has(rel)) continue;
|
|
298
|
+
seen.add(rel);
|
|
299
|
+
installTo(resolveUserDir(rel), opts);
|
|
210
300
|
}
|
|
211
301
|
}
|
|
212
|
-
console.log('
|
|
302
|
+
if (!opts.dryRun) console.log('完成。');
|
|
213
303
|
return;
|
|
214
304
|
}
|
|
215
305
|
|
|
216
|
-
const dirs =
|
|
306
|
+
const dirs = PROJECT_DIRS.filter((d) => fs.existsSync(d));
|
|
217
307
|
if (!dirs.length) {
|
|
218
|
-
throw new TargetError('
|
|
308
|
+
throw new TargetError('未检测到项目级智能体目录。可用 --agent <name> 装到用户级,或用 --dir <路径> 指定目录。');
|
|
219
309
|
}
|
|
220
|
-
for (const dir of dirs) installTo(dir);
|
|
310
|
+
for (const dir of dirs) installTo(dir, opts);
|
|
311
|
+
if (!opts.dryRun) console.log('完成。');
|
|
221
312
|
}
|
|
222
313
|
|
|
223
314
|
function main() {
|
|
@@ -225,18 +316,18 @@ function main() {
|
|
|
225
316
|
run();
|
|
226
317
|
} catch (err) {
|
|
227
318
|
if (err instanceof UsageError) {
|
|
228
|
-
console.error('
|
|
319
|
+
console.error('用法错误: ' + err.message);
|
|
229
320
|
usage();
|
|
230
321
|
process.exitCode = 2;
|
|
231
322
|
} else if (err instanceof TargetError) {
|
|
232
|
-
console.error('
|
|
323
|
+
console.error('目标错误: ' + err.message);
|
|
233
324
|
process.exitCode = 4;
|
|
234
325
|
} else if (err instanceof InstallError) {
|
|
235
|
-
console.error('
|
|
236
|
-
console.error('
|
|
326
|
+
console.error('安装失败: ' + err.message);
|
|
327
|
+
console.error('修复建议: 检查目录权限与磁盘空间后重试,或用 --dir 换一个目录。');
|
|
237
328
|
process.exitCode = 1;
|
|
238
329
|
} else {
|
|
239
|
-
console.error('
|
|
330
|
+
console.error('未知错误: ' + (err && err.message ? err.message : String(err)));
|
|
240
331
|
process.exitCode = 1;
|
|
241
332
|
}
|
|
242
333
|
}
|
package/install.sh
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# yotta-intel 多智能体安装脚本(YottaSkills
|
|
2
|
+
# yotta-intel 多智能体安装脚本(YottaSkills)
|
|
3
3
|
# 用法:
|
|
4
4
|
# bash install.sh --agent <name> # 按智能体默认用户级目录安装
|
|
5
5
|
# bash install.sh --dir <path> # 装到指定目录(用户改过目录的智能体)
|
|
6
6
|
# bash install.sh -g # 装到全部已知智能体用户级目录
|
|
7
|
-
# bash install.sh
|
|
7
|
+
# bash install.sh # 检测并安装到已存在的项目级目录
|
|
8
8
|
# bash install.sh --list # 列出智能体 -> 默认目录
|
|
9
|
+
# bash install.sh --dry-run # 只显示将写入的目标,不写文件
|
|
10
|
+
# bash install.sh --version # 显示版本
|
|
11
|
+
# bash install.sh --help # 显示帮助
|
|
12
|
+
# 退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误
|
|
9
13
|
set -euo pipefail
|
|
10
14
|
|
|
11
15
|
SKILL_NAME="yotta-intel"
|
|
12
16
|
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
_IS_WINDOWS=0
|
|
13
18
|
case "$(uname -s)" in
|
|
14
19
|
MINGW*|MSYS*)
|
|
15
20
|
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -W)"
|
|
21
|
+
_IS_WINDOWS=1
|
|
16
22
|
;;
|
|
23
|
+
CYGWIN*) _IS_WINDOWS=1 ;;
|
|
17
24
|
esac
|
|
18
25
|
|
|
19
26
|
# 智能体 -> 用户级默认目录(--agent 装到第一个)
|
|
20
27
|
# .agents/skills 并非通用目录:OpenCode / Cursor / Cline / Amp / Kimi / Gemini CLI / GitHub Copilot 读取。
|
|
21
|
-
# 判断当前环境:Windows Git Bash 用 %USERPROFILE%,Unix 用 ~
|
|
22
|
-
_IS_WINDOWS=0
|
|
23
|
-
case "$(uname -s)" in
|
|
24
|
-
MINGW*|MSYS*|CYGWIN*) _IS_WINDOWS=1 ;;
|
|
25
|
-
esac
|
|
26
28
|
dirs_for() {
|
|
27
29
|
case "$1" in
|
|
28
30
|
claude) echo ".claude/skills" ;;
|
|
@@ -60,36 +62,153 @@ resolve_user() {
|
|
|
60
62
|
esac
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
local
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
""
|
|
65
|
+
skill_version() {
|
|
66
|
+
local pkg="$SOURCE_DIR/package.json" v=""
|
|
67
|
+
[ -f "$pkg" ] || return 0
|
|
68
|
+
if command -v awk >/dev/null 2>&1; then
|
|
69
|
+
v="$(awk -F'"' '{for (i=1;i<=NF;i++) if ($i=="version") {print $(i+2); exit}}' "$pkg" 2>/dev/null || true)"
|
|
70
|
+
fi
|
|
71
|
+
if [ -z "$v" ]; then
|
|
72
|
+
v="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$pkg" 2>/dev/null | head -n 1 || true)"
|
|
73
|
+
fi
|
|
74
|
+
printf '%s' "$v"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
usage() {
|
|
78
|
+
echo "$SKILL_NAME 安装脚本(YottaSkills)"
|
|
79
|
+
echo ""
|
|
80
|
+
echo "用法:"
|
|
81
|
+
echo " bash install.sh --agent <name> 按智能体默认用户级目录安装(推荐)"
|
|
82
|
+
echo " bash install.sh --dir <path> 装到指定目录(用户改了目录的智能体)"
|
|
83
|
+
echo " bash install.sh -g 安装到全部已知智能体用户级目录"
|
|
84
|
+
echo " bash install.sh 安装到检测到的项目级目录"
|
|
85
|
+
echo ""
|
|
86
|
+
echo "参数:"
|
|
87
|
+
echo " --agent <name> 智能体键名,见 --list"
|
|
88
|
+
echo " --dir <path> 自定义技能目录"
|
|
89
|
+
echo " -g, --global 安装到全部已知用户级目录"
|
|
90
|
+
echo " --list, -l 列出支持的智能体目录"
|
|
91
|
+
echo " --dry-run 只显示将写入的目标,不写文件"
|
|
92
|
+
echo " --version, -v 显示版本"
|
|
93
|
+
echo " --help, -h 显示帮助"
|
|
94
|
+
echo " --yes, -y 兼容参数(-g 不再强制要求)"
|
|
95
|
+
echo ""
|
|
96
|
+
echo "退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resolve_path() {
|
|
100
|
+
# 与 SOURCE_DIR 同一风格(Windows Git Bash 用 pwd -W),供路径包含判断使用
|
|
101
|
+
local p="$1"
|
|
102
|
+
if [ "$_IS_WINDOWS" = "1" ]; then
|
|
103
|
+
(cd "$p" 2>/dev/null && pwd -W)
|
|
104
|
+
else
|
|
105
|
+
(cd "$p" 2>/dev/null && pwd -P)
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
assert_safe_target() {
|
|
110
|
+
local dest="$1" src_real dest_real parent
|
|
111
|
+
src_real="$(resolve_path "$SOURCE_DIR")" || return 0
|
|
112
|
+
if [ -d "$dest" ]; then
|
|
113
|
+
dest_real="$(resolve_path "$dest")" || return 0
|
|
114
|
+
else
|
|
115
|
+
parent="$(dirname "$dest")"
|
|
116
|
+
dest_real="$(resolve_path "$parent")" || return 0
|
|
117
|
+
dest_real="$dest_real/$(basename "$dest")"
|
|
118
|
+
fi
|
|
119
|
+
case "$dest_real" in
|
|
120
|
+
"$src_real"|"$src_real"/*)
|
|
121
|
+
echo "安装失败:目标目录不能在技能源目录内(防止自装自毁)" >&2
|
|
122
|
+
return 1
|
|
123
|
+
;;
|
|
68
124
|
esac
|
|
125
|
+
return 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# 清理旧版安装残留(fail-closed 白名单):仅在目标目录已存在且含 SKILL.md 时触发;
|
|
129
|
+
# 只删顶层开发项 + 任意层级缓存;不整目录删除、不跟随符号链接。
|
|
130
|
+
DEV_SKIP="package.json package-lock.json bin lib test .github .git .gitignore .npmignore .gitattributes .yotta .tmp install.sh node_modules"
|
|
131
|
+
clean_residue() {
|
|
132
|
+
local dest="$1" name p
|
|
133
|
+
[ -d "$dest" ] || return 0
|
|
134
|
+
[ -f "$dest/SKILL.md" ] || return 0
|
|
135
|
+
for name in $DEV_SKIP; do
|
|
136
|
+
p="$dest/$name"
|
|
137
|
+
[ -L "$p" ] && continue
|
|
138
|
+
if [ -e "$p" ]; then
|
|
139
|
+
rm -rf -- "$p"
|
|
140
|
+
echo "已清理残留: $name"
|
|
141
|
+
fi
|
|
142
|
+
done
|
|
143
|
+
find "$dest" -type d \( -name '__pycache__' -o -name '.pytest_cache' -o -name '.mypy_cache' \) -prune -exec rm -rf -- {} + 2>/dev/null || true
|
|
144
|
+
find "$dest" -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete 2>/dev/null || true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# 复制技能本体:顶层跳过开发件与运行时不相关目录(嵌套同名保留);
|
|
148
|
+
# 任意层级跳过缓存(__pycache__ / .pytest_cache / .mypy_cache / *.pyc / *.pyo)。
|
|
149
|
+
copy_tree() {
|
|
150
|
+
local src="$1" dst="$2" top="$3" entry base
|
|
151
|
+
mkdir -p "$dst"
|
|
152
|
+
for entry in "$src"/* "$src"/.[!.]* "$src"/..?*; do
|
|
153
|
+
[ -e "$entry" ] || [ -L "$entry" ] || continue
|
|
154
|
+
base="$(basename "$entry")"
|
|
155
|
+
case "$base" in
|
|
156
|
+
__pycache__|.pytest_cache|.mypy_cache) continue ;;
|
|
157
|
+
esac
|
|
158
|
+
if [ "$top" = "1" ]; then
|
|
159
|
+
case " $DEV_SKIP " in
|
|
160
|
+
*" $base "*) continue ;;
|
|
161
|
+
esac
|
|
162
|
+
fi
|
|
163
|
+
if [ -d "$entry" ] && [ ! -L "$entry" ]; then
|
|
164
|
+
copy_tree "$entry" "$dst/$base" 0 || return 1
|
|
165
|
+
else
|
|
166
|
+
case "$base" in
|
|
167
|
+
*.pyc|*.pyo) continue ;;
|
|
168
|
+
esac
|
|
169
|
+
cp -RP "$entry" "$dst/$base" || return 1
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
install_to() {
|
|
175
|
+
local base="$1" dry="$2" dest
|
|
176
|
+
dest="$base/$SKILL_NAME"
|
|
69
177
|
if [ -L "$base" ]; then
|
|
70
|
-
echo "安装失败:目标目录是符号链接,拒绝跟随:$base" >&2
|
|
178
|
+
echo "安装失败:目标目录是符号链接,拒绝跟随:$base" >&2
|
|
179
|
+
return 1
|
|
180
|
+
fi
|
|
181
|
+
if [ -e "$base" ] && [ ! -d "$base" ]; then
|
|
182
|
+
echo "安装失败:目标路径已存在且不是目录:$base" >&2
|
|
183
|
+
return 1
|
|
71
184
|
fi
|
|
72
|
-
mkdir -p "$base"
|
|
73
|
-
dest="$base/$SKILL_NAME"
|
|
74
185
|
if [ -L "$dest" ]; then
|
|
75
|
-
echo "安装失败:技能目录是符号链接,拒绝跟随:$dest" >&2
|
|
186
|
+
echo "安装失败:技能目录是符号链接,拒绝跟随:$dest" >&2
|
|
187
|
+
return 1
|
|
76
188
|
fi
|
|
77
189
|
if [ -e "$dest" ] && [ ! -d "$dest" ]; then
|
|
78
|
-
echo "安装失败:技能路径已存在且不是目录:$dest" >&2
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
190
|
+
echo "安装失败:技能路径已存在且不是目录:$dest" >&2
|
|
191
|
+
return 1
|
|
192
|
+
fi
|
|
193
|
+
assert_safe_target "$dest" || return 1
|
|
194
|
+
if [ "$dry" = "1" ]; then
|
|
195
|
+
echo "[dry-run] 将安装到 -> $dest"
|
|
196
|
+
return 0
|
|
197
|
+
fi
|
|
198
|
+
clean_residue "$dest"
|
|
199
|
+
mkdir -p "$dest" || return 1
|
|
200
|
+
copy_tree "$SOURCE_DIR" "$dest" 1 || return 1
|
|
201
|
+
if [ ! -f "$dest/SKILL.md" ]; then
|
|
202
|
+
echo "安装失败:结果缺少 SKILL.md" >&2
|
|
203
|
+
return 1
|
|
204
|
+
fi
|
|
86
205
|
echo "installed -> $dest"
|
|
87
206
|
}
|
|
88
207
|
|
|
89
208
|
list() {
|
|
90
209
|
echo "智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):"
|
|
210
|
+
local a dirs first
|
|
91
211
|
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
92
|
-
local dirs first
|
|
93
212
|
dirs="$(dirs_for "$a")"
|
|
94
213
|
first="${dirs%% *}"
|
|
95
214
|
case "$first" in
|
|
@@ -108,44 +227,101 @@ list() {
|
|
|
108
227
|
}
|
|
109
228
|
|
|
110
229
|
main() {
|
|
111
|
-
local agent="" dir="" global=0 show_list=0
|
|
230
|
+
local agent="" dir="" global=0 show_list=0 show_help=0 show_version=0 dry=0
|
|
112
231
|
while [ $# -gt 0 ]; do
|
|
113
232
|
case "$1" in
|
|
114
|
-
--
|
|
115
|
-
--
|
|
116
|
-
-g|--global) global=1 ;;
|
|
233
|
+
--help|-h) show_help=1 ;;
|
|
234
|
+
--version|-v) show_version=1 ;;
|
|
117
235
|
--list|-l) show_list=1 ;;
|
|
236
|
+
-g|--global) global=1 ;;
|
|
237
|
+
--dry-run) dry=1 ;;
|
|
238
|
+
--yes|-y) : ;;
|
|
239
|
+
--agent)
|
|
240
|
+
shift
|
|
241
|
+
if [ $# -eq 0 ] || [ -z "${1:-}" ]; then
|
|
242
|
+
echo "用法错误: --agent 需要一个非空名称" >&2
|
|
243
|
+
exit 2
|
|
244
|
+
fi
|
|
245
|
+
agent="$1"
|
|
246
|
+
;;
|
|
247
|
+
--dir)
|
|
248
|
+
shift
|
|
249
|
+
if [ $# -eq 0 ] || [ -z "${1:-}" ]; then
|
|
250
|
+
echo "用法错误: --dir 需要一个非空路径" >&2
|
|
251
|
+
exit 2
|
|
252
|
+
fi
|
|
253
|
+
dir="$1"
|
|
254
|
+
;;
|
|
118
255
|
*) echo "未知参数: $1" >&2; exit 2 ;;
|
|
119
256
|
esac
|
|
120
257
|
shift
|
|
121
258
|
done
|
|
122
259
|
|
|
123
|
-
if [ "$
|
|
124
|
-
if [
|
|
260
|
+
if [ "$show_help" = "1" ]; then usage; return 0; fi
|
|
261
|
+
if [ "$show_version" = "1" ]; then
|
|
262
|
+
local v
|
|
263
|
+
v="$(skill_version)"
|
|
264
|
+
if [ -z "$v" ]; then
|
|
265
|
+
echo "$SKILL_NAME 版本未知(未找到 package.json)" >&2
|
|
266
|
+
exit 1
|
|
267
|
+
fi
|
|
268
|
+
echo "$SKILL_NAME v$v"
|
|
269
|
+
return 0
|
|
270
|
+
fi
|
|
271
|
+
if [ "$show_list" = "1" ]; then list; return 0; fi
|
|
272
|
+
|
|
273
|
+
local selected=0
|
|
274
|
+
[ -n "$dir" ] && selected=$((selected + 1))
|
|
275
|
+
[ -n "$agent" ] && selected=$((selected + 1))
|
|
276
|
+
[ "$global" = "1" ] && selected=$((selected + 1))
|
|
277
|
+
if [ "$selected" -gt 1 ]; then
|
|
278
|
+
echo "用法错误: --dir / --agent / -g 只能选一个" >&2
|
|
279
|
+
exit 2
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
if [ -n "$dir" ]; then
|
|
283
|
+
install_to "$dir" "$dry" || exit 1
|
|
284
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
285
|
+
return 0
|
|
286
|
+
fi
|
|
125
287
|
if [ -n "$agent" ]; then
|
|
126
288
|
local dirs first
|
|
127
289
|
if ! dirs="$(dirs_for "$agent")"; then
|
|
128
|
-
echo "未收录智能体: $agent
|
|
290
|
+
echo "未收录智能体: $agent。可用 --list 查看,或 --dir <路径> 指定。" >&2
|
|
291
|
+
exit 2
|
|
129
292
|
fi
|
|
130
293
|
first="${dirs%% *}"
|
|
131
|
-
install_to "$(resolve_user "$first")"
|
|
294
|
+
install_to "$(resolve_user "$first")" "$dry" || exit 1
|
|
295
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
296
|
+
return 0
|
|
132
297
|
fi
|
|
133
298
|
if [ "$global" = "1" ]; then
|
|
134
|
-
|
|
135
|
-
local dirs rel
|
|
299
|
+
local a dirs rel seen=" "
|
|
136
300
|
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
137
|
-
dirs="$(dirs_for "$a")"
|
|
138
|
-
for rel in $dirs; do
|
|
301
|
+
dirs="$(dirs_for "$a")" || continue
|
|
302
|
+
for rel in $dirs; do
|
|
303
|
+
case "$seen" in
|
|
304
|
+
*" $rel "*) continue ;;
|
|
305
|
+
esac
|
|
306
|
+
seen="$seen$rel "
|
|
307
|
+
install_to "$(resolve_user "$rel")" "$dry" || exit 1
|
|
308
|
+
done
|
|
139
309
|
done
|
|
140
|
-
echo "完成。"
|
|
310
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
311
|
+
return 0
|
|
141
312
|
fi
|
|
142
313
|
local installed=0 d
|
|
143
314
|
for d in .claude/skills .cursor/skills .codex/skills .config/goose/skills .config/agents/skills .opencode/skills .codeium/windsurf/skills .workbuddy/skills .kiro/skills .traecli/skills .gemini/skills .trae-cn/skills .qwen/skills .comate/skills .codebuddy/skills .kimi/skills .agents/skills; do
|
|
144
|
-
if [ -d "$d" ]; then
|
|
315
|
+
if [ -d "$d" ]; then
|
|
316
|
+
install_to "$d" "$dry" || exit 1
|
|
317
|
+
installed=1
|
|
318
|
+
fi
|
|
145
319
|
done
|
|
146
320
|
if [ "$installed" = "0" ]; then
|
|
147
|
-
echo "未检测到项目级智能体目录。可用 --agent <name>
|
|
321
|
+
echo "目标错误: 未检测到项目级智能体目录。可用 --agent <name> 装到用户级,或用 --dir <路径> 指定目录。" >&2
|
|
322
|
+
exit 4
|
|
148
323
|
fi
|
|
324
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
149
325
|
}
|
|
150
326
|
|
|
151
327
|
main "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-intel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Yuanqing — a zero-dependency threat-intel IOC extraction & normalization engine for AI agents: extracts IP (IPv4/IPv6), domains, URLs, emails, hashes (MD5/SHA1/SHA256/SHA512) and CVE IDs from threat reports, phishing emails and logs; recognizes and reverses defanged forms (hxxp / [.] / (.) / [dot] / [:] / [@]); deduplicates, normalizes (IDN punycode, default-port stripping) and outputs CSV / JSON / STIX-lite (STIX 2.1 Bundle) with the pure Python 3.8+ standard library. Triggers when the user provides text containing suspicious IPs / domains / URLs / hashes and needs IOC extraction, normalization, dedup, format conversion, or safe sharing. Boundaries: fully local and offline; no reputation lookups, no sample downloads, no proactive scanning; authorized / own-asset / educational use only.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -488,7 +488,7 @@ class TestCLI(unittest.TestCase):
|
|
|
488
488
|
def test_cli_version(self):
|
|
489
489
|
r = run_cli("--version")
|
|
490
490
|
self.assertEqual(r.returncode, 0)
|
|
491
|
-
self.assertIn("0.2.
|
|
491
|
+
self.assertIn("0.2.2", r.stdout)
|
|
492
492
|
|
|
493
493
|
def test_cli_unknown_type_exit4(self):
|
|
494
494
|
r = run_cli("extract", "--stdin", "--types", "bogus", stdin="1.2.3.4")
|