@yottameta/yotta-intel 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/LICENSE +20 -20
- package/NOTICE +1 -1
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/SKILL.md +6 -2
- package/bin/install.js +245 -163
- package/install.sh +131 -131
- package/package.json +9 -3
- package/references/defang-rules.md +1 -1
- package/references/faq.md +58 -0
- package/references/ioc-spec.md +2 -2
- package/references/stix-lite-spec.md +2 -2
- package/scripts/test_yotta_intel.py +1 -1
- package/scripts/yotta_intel.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## v0.2.0 (2026-09-09)
|
|
4
|
+
|
|
5
|
+
- 评测完善批 2:新增 references/faq.md(12 条常见问题 + 速查索引 + 安装排障);SKILL.md 增加 FAQ 速查节。
|
|
6
|
+
- 安装器错误处理:用法/目标/安装错误统一退出码与修复建议;新增 test/install.test.js。
|
|
7
|
+
- package.json 补 npm test 脚本;版本对齐 0.2.0(package / SKILL / CHANGELOG / CLI)。
|
|
8
|
+
|
|
3
9
|
## v0.1.1 (2026-08-29)
|
|
4
10
|
|
|
5
11
|
- 安装方式统一为四方式(对齐发布规范 §3.3.1):方式一 `npx -y @yottameta/yotta-intel --agent <name>` / `--dir <dir>`(推荐,走 npm 源);方式二 `git clone https://github.com/YottaMeta/yotta-intel.git`;方式三 GitHub Download ZIP;方式四 `bash install.sh --agent/--dir/--list`。移除 `npx skills` 与 `-g` 推荐;中英双 README 安装节同步。
|
|
@@ -19,4 +25,4 @@
|
|
|
19
25
|
- 误报控制:域名 TLD 白名单 + 文件名过滤(README.md / test.py 不算域名)+ 中文标点截断 + 哈希长度校验。
|
|
20
26
|
- 四种输出:text / JSON / CSV / STIX-lite(STIX 2.1 Bundle + indicator pattern,uuid5 确定性)。
|
|
21
27
|
- 测试:103 个用例全绿;含 CLI 端到端(stdin / 文件 / 退出码)。
|
|
22
|
-
- 文档:SKILL.md + README 中英双版 + references(ioc-spec / defang-rules / stix-lite-spec)。
|
|
28
|
+
- 文档:SKILL.md + README 中英双版 + references(ioc-spec / defang-rules / stix-lite-spec)。
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 YottaMeta
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YottaMeta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
package/NOTICE
CHANGED
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
2. 不得暗示派生作品由 YottaMeta 官方维护、认可或与之存在关联;
|
|
9
9
|
3. 建议在派生作品中明确声明「与 YottaMeta 官方无关联」。
|
|
10
10
|
|
|
11
|
-
上游来源致谢:本技能由 YottaMeta 全新实现(零依赖自研 + 中文教学),威胁情报 IOC 提取 / defang / STIX 映射方向参考开源社区 threat-intelligence / IOC-extraction 类技能思路,无上游代码。
|
|
11
|
+
上游来源致谢:本技能由 YottaMeta 全新实现(零依赖自研 + 中文教学),威胁情报 IOC 提取 / defang / STIX 映射方向参考开源社区 threat-intelligence / IOC-extraction 类技能思路,无上游代码。
|
package/README.md
CHANGED
package/README.zh-CN.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
## 这是什么
|
|
26
26
|
|
|
27
27
|
威胁情报分析经常从「一堆文本里找指标」开始:这份报告里有哪些可疑 IP?钓鱼邮件里的域名 / 链接是什么?
|
|
28
|
-
样本哈希是多少、属于哪种算法?元情把这些能力打包成零依赖引擎——不需要 MISP / OpenCTI /
|
|
28
|
+
样本哈希是多少、属于哪种算法?元情把这些能力打包成零依赖引擎——不需要 MISP / OpenCTI / 厂商情报平台,
|
|
29
29
|
用纯 Python 标准库就能完成 IOC 提取、defang/refang、去重、归一化与格式转换。
|
|
30
30
|
|
|
31
31
|
它不是任何单一平台的专属工具:它是一套与智能体无关的工具包,任何支持 Agent Skills 的智能体都能用。
|
|
@@ -99,7 +99,7 @@ python3 scripts/yotta_intel.py refang --path safe.txt
|
|
|
99
99
|
输出示例(text):
|
|
100
100
|
|
|
101
101
|
```
|
|
102
|
-
元情 yotta-intel v0.
|
|
102
|
+
元情 yotta-intel v0.2.0 —— IOC 提取结果
|
|
103
103
|
共发现 2 个 IOC:
|
|
104
104
|
|
|
105
105
|
■ IPv4 地址(ipv4)
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-intel
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.2.0
|
|
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
|
---
|
|
@@ -110,7 +110,11 @@ defang / refang 成功均为 **0**。
|
|
|
110
110
|
- references/defang-rules.md — defang / refang 规则与安全共享建议
|
|
111
111
|
- references/stix-lite-spec.md — STIX-lite 输出规范与 pattern 映射
|
|
112
112
|
|
|
113
|
+
## 常见问题(速查)
|
|
114
|
+
|
|
115
|
+
装不上、提取为空、defang 没还原、退出码看不懂时,先看 references/faq.md(含速查索引与安装排障)。
|
|
116
|
+
|
|
113
117
|
## 法律声明
|
|
114
118
|
|
|
115
119
|
本技能仅用于**已获明确授权**的安全分析(自有资产、授权测试、CTF 靶场、教学环境)。
|
|
116
|
-
未经授权分析他人系统数据违反中国《网络安全法》与《刑法》相关条款,使用者自行承担法律责任。
|
|
120
|
+
未经授权分析他人系统数据违反中国《网络安全法》与《刑法》相关条款,使用者自行承担法律责任。
|
package/bin/install.js
CHANGED
|
@@ -1,163 +1,245 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* yotta-intel
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* npx -y @yottameta/yotta-intel --
|
|
7
|
-
* npx -y @yottameta/yotta-intel
|
|
8
|
-
* npx -y @yottameta/yotta-intel
|
|
9
|
-
* npx -y @yottameta/yotta-intel
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const AGENT_DIRS = {
|
|
23
|
-
claude: { label: 'Claude Code', dirs: ['.claude/skills'] },
|
|
24
|
-
cursor: { label: 'Cursor', dirs: ['.cursor/skills', '.agents/skills'] },
|
|
25
|
-
codex: { label: 'Codex', dirs: ['.codex/skills'] },
|
|
26
|
-
gemini: { label: 'Gemini CLI', dirs: ['.gemini/skills', '.agents/skills'] },
|
|
27
|
-
goose: { label: 'Goose', dirs: ['.config/goose/skills', '.agents/skills'] },
|
|
28
|
-
amp: { label: 'Amp', dirs: ['.config/agents/skills', '.agents/skills'] },
|
|
29
|
-
opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] },
|
|
30
|
-
windsurf: { label: 'Windsurf', dirs: ['.codeium/windsurf/skills'] },
|
|
31
|
-
workbuddy: { label: 'WorkBuddy', dirs: ['.workbuddy/skills'] },
|
|
32
|
-
kiro: { label: 'Kiro', dirs: ['.kiro/skills'] },
|
|
33
|
-
trae: { label: 'Trae Code CLI', dirs: ['.traecli/skills'] },
|
|
34
|
-
'trae-cn': { label: 'Trae IDE
|
|
35
|
-
qwen: { label: 'Qwen Code', dirs: ['.qwen/skills'] },
|
|
36
|
-
comate: { label: 'Comate
|
|
37
|
-
codebuddy: { label: 'CodeBuddy Code', dirs: ['.codebuddy/skills'] },
|
|
38
|
-
kimi: { label: 'Kimi Code CLI', dirs: ['.kimi/skills'] },
|
|
39
|
-
agents: { label: '
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} else if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
'.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @yottameta/yotta-intel cross-platform installer.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
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
|
+
*/
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
const SKILL_NAME = 'yotta-intel';
|
|
19
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
20
|
+
const COPY_SKIP = new Set(['package.json', 'bin', 'node_modules', '.git', '.github', 'test']);
|
|
21
|
+
|
|
22
|
+
const AGENT_DIRS = {
|
|
23
|
+
claude: { label: 'Claude Code', dirs: ['.claude/skills'] },
|
|
24
|
+
cursor: { label: 'Cursor', dirs: ['.cursor/skills', '.agents/skills'] },
|
|
25
|
+
codex: { label: 'Codex', dirs: ['.codex/skills'] },
|
|
26
|
+
gemini: { label: 'Gemini CLI', dirs: ['.gemini/skills', '.agents/skills'] },
|
|
27
|
+
goose: { label: 'Goose', dirs: ['.config/goose/skills', '.agents/skills'] },
|
|
28
|
+
amp: { label: 'Amp', dirs: ['.config/agents/skills', '.agents/skills'] },
|
|
29
|
+
opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] },
|
|
30
|
+
windsurf: { label: 'Windsurf', dirs: ['.codeium/windsurf/skills'] },
|
|
31
|
+
workbuddy: { label: 'WorkBuddy', dirs: ['.workbuddy/skills'] },
|
|
32
|
+
kiro: { label: 'Kiro', dirs: ['.kiro/skills'] },
|
|
33
|
+
trae: { label: 'Trae Code CLI', dirs: ['.traecli/skills'] },
|
|
34
|
+
'trae-cn': { label: 'Trae IDE', dirs: ['.trae-cn/skills'] },
|
|
35
|
+
qwen: { label: 'Qwen Code', dirs: ['.qwen/skills'] },
|
|
36
|
+
comate: { label: 'Comate', dirs: ['.comate/skills'] },
|
|
37
|
+
codebuddy: { label: 'CodeBuddy Code', dirs: ['.codebuddy/skills'] },
|
|
38
|
+
kimi: { label: 'Kimi Code CLI', dirs: ['.kimi/skills'] },
|
|
39
|
+
agents: { label: 'Generic AGENTS.md', dirs: ['.agents/skills'] },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
class UsageError extends Error {}
|
|
43
|
+
class TargetError extends Error {}
|
|
44
|
+
class InstallError extends Error {}
|
|
45
|
+
|
|
46
|
+
function usage() {
|
|
47
|
+
console.log('yotta-intel installer');
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log('Usage:');
|
|
50
|
+
console.log(' npx -y @yottameta/yotta-intel --agent <name> Install to an agent default directory');
|
|
51
|
+
console.log(' npx -y @yottameta/yotta-intel --dir <path> Install to a custom directory');
|
|
52
|
+
console.log(' npx -y @yottameta/yotta-intel -g Install to all known agent directories');
|
|
53
|
+
console.log(' npx -y @yottameta/yotta-intel Auto-detect project-level directories');
|
|
54
|
+
console.log(' npx -y @yottameta/yotta-intel --list List supported agent directories');
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log('Options:');
|
|
57
|
+
console.log(' --agent <name> Agent key, see --list');
|
|
58
|
+
console.log(' --dir <path> Custom skills directory');
|
|
59
|
+
console.log(' -g, --global Install to all known user-level directories');
|
|
60
|
+
console.log(' --list, -l List supported agents');
|
|
61
|
+
console.log(' -h, --help Show this help');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseArgs(argv) {
|
|
65
|
+
const opts = { help: false, list: false, global: false, dir: null, agent: null };
|
|
66
|
+
for (let i = 0; i < argv.length; i++) {
|
|
67
|
+
const arg = argv[i];
|
|
68
|
+
if (arg === '--help' || arg === '-h') opts.help = true;
|
|
69
|
+
else if (arg === '--list' || arg === '-l') opts.list = true;
|
|
70
|
+
else if (arg === '--global' || arg === '-g') opts.global = true;
|
|
71
|
+
else if (arg === '--dir') {
|
|
72
|
+
const value = argv[++i];
|
|
73
|
+
if (!value) throw new UsageError('--dir requires a non-empty path');
|
|
74
|
+
opts.dir = value;
|
|
75
|
+
} else if (arg === '--agent') {
|
|
76
|
+
const value = argv[++i];
|
|
77
|
+
if (!value) throw new UsageError('--agent requires a non-empty name');
|
|
78
|
+
opts.agent = value.toLowerCase();
|
|
79
|
+
} else {
|
|
80
|
+
throw new UsageError('Unknown argument: ' + arg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!opts.help) {
|
|
84
|
+
const selected = [opts.dir, opts.agent, opts.global].filter(Boolean).length;
|
|
85
|
+
if (selected > 1) throw new UsageError('Use only one of --dir, --agent, or -g');
|
|
86
|
+
}
|
|
87
|
+
return opts;
|
|
88
|
+
}
|
|
89
|
+
|
|
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
|
+
function printList() {
|
|
112
|
+
console.log('Agent -> default skill directory:');
|
|
113
|
+
for (const [key, value] of Object.entries(AGENT_DIRS)) {
|
|
114
|
+
console.log(' ' + key.padEnd(12) + value.label.padEnd(20) + value.dirs.map(displayDir).join(', '));
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('Use --dir <path> for agents not listed. CODEX_HOME and XDG_CONFIG_HOME are respected.');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assertSafeTarget(target) {
|
|
121
|
+
const rel = path.relative(PKG_ROOT, target);
|
|
122
|
+
if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {
|
|
123
|
+
throw new UsageError('Target directory must be outside the skill source directory');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function copyDir(src, dst, skip) {
|
|
128
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
129
|
+
if (skip.has(entry.name)) continue;
|
|
130
|
+
const from = path.join(src, entry.name);
|
|
131
|
+
const to = path.join(dst, entry.name);
|
|
132
|
+
try {
|
|
133
|
+
if (entry.isDirectory()) {
|
|
134
|
+
fs.mkdirSync(to, { recursive: true });
|
|
135
|
+
copyDir(from, to, skip);
|
|
136
|
+
} else if (entry.isFile()) {
|
|
137
|
+
fs.copyFileSync(from, to);
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
throw new InstallError('Failed to copy ' + from + ' -> ' + to + ': ' + err.message);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function installTo(dest) {
|
|
146
|
+
if (!dest || typeof dest !== 'string') throw new UsageError('Destination directory is required');
|
|
147
|
+
const target = path.resolve(dest, SKILL_NAME);
|
|
148
|
+
assertSafeTarget(target);
|
|
149
|
+
try {
|
|
150
|
+
fs.mkdirSync(target, { recursive: true });
|
|
151
|
+
copyDir(PKG_ROOT, target, COPY_SKIP);
|
|
152
|
+
if (!fs.existsSync(path.join(target, 'SKILL.md'))) {
|
|
153
|
+
throw new InstallError('Installed directory is missing SKILL.md');
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err instanceof UsageError || err instanceof InstallError) throw err;
|
|
157
|
+
throw new InstallError('Cannot install to ' + target + ': ' + err.message);
|
|
158
|
+
}
|
|
159
|
+
console.log('installed -> ' + target);
|
|
160
|
+
return target;
|
|
161
|
+
}
|
|
162
|
+
|
|
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
|
+
function run() {
|
|
186
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
187
|
+
if (opts.help) { usage(); return; }
|
|
188
|
+
if (opts.list) { printList(); return; }
|
|
189
|
+
|
|
190
|
+
if (opts.dir) { installTo(opts.dir); return; }
|
|
191
|
+
|
|
192
|
+
if (opts.agent) {
|
|
193
|
+
const info = AGENT_DIRS[opts.agent];
|
|
194
|
+
if (!info) {
|
|
195
|
+
throw new UsageError('Unknown agent: ' + opts.agent + '. Available: ' + Object.keys(AGENT_DIRS).join(', ') + '. Use --dir for a custom directory.');
|
|
196
|
+
}
|
|
197
|
+
installTo(resolveUserDir(info.dirs[0]));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (opts.global) {
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
let count = 0;
|
|
204
|
+
for (const value of Object.values(AGENT_DIRS)) {
|
|
205
|
+
for (const dir of value.dirs) {
|
|
206
|
+
if (seen.has(dir)) continue;
|
|
207
|
+
seen.add(dir);
|
|
208
|
+
installTo(resolveUserDir(dir));
|
|
209
|
+
count++;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
console.log('Installed to ' + count + ' directories.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const dirs = projectDirs();
|
|
217
|
+
if (!dirs.length) {
|
|
218
|
+
throw new TargetError('No project-level agent directory detected. Use --agent <name> or --dir <path>.');
|
|
219
|
+
}
|
|
220
|
+
for (const dir of dirs) installTo(dir);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function main() {
|
|
224
|
+
try {
|
|
225
|
+
run();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (err instanceof UsageError) {
|
|
228
|
+
console.error('Usage error: ' + err.message);
|
|
229
|
+
usage();
|
|
230
|
+
process.exitCode = 2;
|
|
231
|
+
} else if (err instanceof TargetError) {
|
|
232
|
+
console.error('Target error: ' + err.message);
|
|
233
|
+
process.exitCode = 4;
|
|
234
|
+
} else if (err instanceof InstallError) {
|
|
235
|
+
console.error('Install failed: ' + err.message);
|
|
236
|
+
console.error('Fix: check directory permissions and free space, then retry. Use --dir to choose another directory.');
|
|
237
|
+
process.exitCode = 1;
|
|
238
|
+
} else {
|
|
239
|
+
console.error('Unexpected error: ' + (err && err.message ? err.message : String(err)));
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
main();
|
package/install.sh
CHANGED
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# yotta-intel 多智能体安装脚本(YottaSkills 模板)
|
|
3
|
-
# 用法:
|
|
4
|
-
# bash install.sh --agent <name> # 按智能体默认用户级目录安装
|
|
5
|
-
# bash install.sh --dir <path> # 装到指定目录(用户改过目录的智能体)
|
|
6
|
-
# bash install.sh -g # 装到全部已知智能体用户级目录
|
|
7
|
-
# bash install.sh # 检测并安装到已存在的项目级目录
|
|
8
|
-
# bash install.sh --list # 列出智能体 -> 默认目录
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
SKILL_NAME="yotta-intel"
|
|
12
|
-
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
-
case "$(uname -s)" in
|
|
14
|
-
MINGW*|MSYS*)
|
|
15
|
-
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -W)"
|
|
16
|
-
;;
|
|
17
|
-
esac
|
|
18
|
-
|
|
19
|
-
# 智能体 -> 用户级默认目录(--agent 装到第一个)
|
|
20
|
-
# .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
|
-
dirs_for() {
|
|
27
|
-
case "$1" in
|
|
28
|
-
claude) echo ".claude/skills" ;;
|
|
29
|
-
cursor) echo ".cursor/skills .agents/skills" ;;
|
|
30
|
-
codex) echo "__CODEX__" ;;
|
|
31
|
-
gemini) echo ".gemini/skills .agents/skills" ;;
|
|
32
|
-
goose) echo ".config/goose/skills .agents/skills" ;;
|
|
33
|
-
amp) echo ".config/agents/skills .agents/skills" ;;
|
|
34
|
-
opencode) echo "__OPENCODE__" ;;
|
|
35
|
-
windsurf) echo ".codeium/windsurf/skills" ;;
|
|
36
|
-
workbuddy) echo ".workbuddy/skills" ;;
|
|
37
|
-
kiro) echo ".kiro/skills" ;;
|
|
38
|
-
trae) echo ".traecli/skills" ;;
|
|
39
|
-
trae-cn) echo ".trae-cn/skills" ;;
|
|
40
|
-
qwen) echo ".qwen/skills" ;;
|
|
41
|
-
comate) echo ".comate/skills" ;;
|
|
42
|
-
codebuddy) echo ".codebuddy/skills" ;;
|
|
43
|
-
kimi) echo ".kimi/skills" ;;
|
|
44
|
-
agents) echo ".agents/skills" ;;
|
|
45
|
-
*) return 1 ;;
|
|
46
|
-
esac
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
codex_dir() {
|
|
50
|
-
if [ -n "${CODEX_HOME:-}" ]; then printf '%s' "$CODEX_HOME/skills"; else printf '%s' "$HOME/.codex/skills"; fi
|
|
51
|
-
}
|
|
52
|
-
opencode_dir() {
|
|
53
|
-
if [ -n "${XDG_CONFIG_HOME:-}" ]; then printf '%s' "$XDG_CONFIG_HOME/opencode/skills"; else printf '%s' "$HOME/.config/opencode/skills"; fi
|
|
54
|
-
}
|
|
55
|
-
resolve_user() {
|
|
56
|
-
case "$1" in
|
|
57
|
-
__CODEX__) codex_dir ;;
|
|
58
|
-
__OPENCODE__) opencode_dir ;;
|
|
59
|
-
*) printf '%s' "$HOME/$1" ;;
|
|
60
|
-
esac
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
install_to() {
|
|
64
|
-
mkdir -p "$1/$SKILL_NAME"
|
|
65
|
-
cp -r "$SOURCE_DIR/." "$1/$SKILL_NAME/"
|
|
66
|
-
rm -rf "$1/$SKILL_NAME/.git"
|
|
67
|
-
echo "installed -> $1/$SKILL_NAME"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
list() {
|
|
71
|
-
echo "智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):"
|
|
72
|
-
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
73
|
-
local dirs first
|
|
74
|
-
dirs="$(dirs_for "$a")"
|
|
75
|
-
first="${dirs%% *}"
|
|
76
|
-
case "$first" in
|
|
77
|
-
__CODEX__) first=".codex/skills" ;;
|
|
78
|
-
__OPENCODE__) first=".config/opencode/skills" ;;
|
|
79
|
-
esac
|
|
80
|
-
if [ "$_IS_WINDOWS" = "1" ]; then
|
|
81
|
-
first="%USERPROFILE%\\${first//\//\\}"
|
|
82
|
-
else
|
|
83
|
-
first="~/$first"
|
|
84
|
-
fi
|
|
85
|
-
printf ' %-10s %s\n' "$a" "$first"
|
|
86
|
-
done
|
|
87
|
-
echo '说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。'
|
|
88
|
-
echo '改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。'
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
main() {
|
|
92
|
-
local agent="" dir="" global=0 show_list=0
|
|
93
|
-
while [ $# -gt 0 ]; do
|
|
94
|
-
case "$1" in
|
|
95
|
-
--agent) shift; agent="${1:-}" ;;
|
|
96
|
-
--dir) shift; dir="${1:-}" ;;
|
|
97
|
-
-g|--global) global=1 ;;
|
|
98
|
-
--list|-l) show_list=1 ;;
|
|
99
|
-
*) echo "未知参数: $1" >&2; exit 2 ;;
|
|
100
|
-
esac
|
|
101
|
-
shift
|
|
102
|
-
done
|
|
103
|
-
|
|
104
|
-
if [ "$show_list" = "1" ]; then list; return; fi
|
|
105
|
-
if [ -n "$dir" ]; then install_to "$dir"; echo "完成。"; return; fi
|
|
106
|
-
if [ -n "$agent" ]; then
|
|
107
|
-
local dirs first
|
|
108
|
-
if ! dirs="$(dirs_for "$agent")"; then
|
|
109
|
-
echo "未收录智能体: $agent。请用 --dir <路径> 指定技能目录。" >&2; exit 2
|
|
110
|
-
fi
|
|
111
|
-
first="${dirs%% *}"
|
|
112
|
-
install_to "$(resolve_user "$first")"; echo "完成。"; return
|
|
113
|
-
fi
|
|
114
|
-
if [ "$global" = "1" ]; then
|
|
115
|
-
echo "安装到全部已知智能体用户级目录..."
|
|
116
|
-
local dirs rel
|
|
117
|
-
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
118
|
-
dirs="$(dirs_for "$a")"
|
|
119
|
-
for rel in $dirs; do install_to "$(resolve_user "$rel")"; done
|
|
120
|
-
done
|
|
121
|
-
echo "完成。"; return
|
|
122
|
-
fi
|
|
123
|
-
local installed=0 d
|
|
124
|
-
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
|
|
125
|
-
if [ -d "$d" ]; then install_to "$d"; installed=1; fi
|
|
126
|
-
done
|
|
127
|
-
if [ "$installed" = "0" ]; then
|
|
128
|
-
echo "未检测到项目级智能体目录。可用 --agent <name> / -g 装到用户级,或 --dir 指定。"
|
|
129
|
-
fi
|
|
130
|
-
}
|
|
131
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# yotta-intel 多智能体安装脚本(YottaSkills 模板)
|
|
3
|
+
# 用法:
|
|
4
|
+
# bash install.sh --agent <name> # 按智能体默认用户级目录安装
|
|
5
|
+
# bash install.sh --dir <path> # 装到指定目录(用户改过目录的智能体)
|
|
6
|
+
# bash install.sh -g # 装到全部已知智能体用户级目录
|
|
7
|
+
# bash install.sh # 检测并安装到已存在的项目级目录
|
|
8
|
+
# bash install.sh --list # 列出智能体 -> 默认目录
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SKILL_NAME="yotta-intel"
|
|
12
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
case "$(uname -s)" in
|
|
14
|
+
MINGW*|MSYS*)
|
|
15
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -W)"
|
|
16
|
+
;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
# 智能体 -> 用户级默认目录(--agent 装到第一个)
|
|
20
|
+
# .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
|
+
dirs_for() {
|
|
27
|
+
case "$1" in
|
|
28
|
+
claude) echo ".claude/skills" ;;
|
|
29
|
+
cursor) echo ".cursor/skills .agents/skills" ;;
|
|
30
|
+
codex) echo "__CODEX__" ;;
|
|
31
|
+
gemini) echo ".gemini/skills .agents/skills" ;;
|
|
32
|
+
goose) echo ".config/goose/skills .agents/skills" ;;
|
|
33
|
+
amp) echo ".config/agents/skills .agents/skills" ;;
|
|
34
|
+
opencode) echo "__OPENCODE__" ;;
|
|
35
|
+
windsurf) echo ".codeium/windsurf/skills" ;;
|
|
36
|
+
workbuddy) echo ".workbuddy/skills" ;;
|
|
37
|
+
kiro) echo ".kiro/skills" ;;
|
|
38
|
+
trae) echo ".traecli/skills" ;;
|
|
39
|
+
trae-cn) echo ".trae-cn/skills" ;;
|
|
40
|
+
qwen) echo ".qwen/skills" ;;
|
|
41
|
+
comate) echo ".comate/skills" ;;
|
|
42
|
+
codebuddy) echo ".codebuddy/skills" ;;
|
|
43
|
+
kimi) echo ".kimi/skills" ;;
|
|
44
|
+
agents) echo ".agents/skills" ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
codex_dir() {
|
|
50
|
+
if [ -n "${CODEX_HOME:-}" ]; then printf '%s' "$CODEX_HOME/skills"; else printf '%s' "$HOME/.codex/skills"; fi
|
|
51
|
+
}
|
|
52
|
+
opencode_dir() {
|
|
53
|
+
if [ -n "${XDG_CONFIG_HOME:-}" ]; then printf '%s' "$XDG_CONFIG_HOME/opencode/skills"; else printf '%s' "$HOME/.config/opencode/skills"; fi
|
|
54
|
+
}
|
|
55
|
+
resolve_user() {
|
|
56
|
+
case "$1" in
|
|
57
|
+
__CODEX__) codex_dir ;;
|
|
58
|
+
__OPENCODE__) opencode_dir ;;
|
|
59
|
+
*) printf '%s' "$HOME/$1" ;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
install_to() {
|
|
64
|
+
mkdir -p "$1/$SKILL_NAME"
|
|
65
|
+
cp -r "$SOURCE_DIR/." "$1/$SKILL_NAME/"
|
|
66
|
+
rm -rf "$1/$SKILL_NAME/.git"
|
|
67
|
+
echo "installed -> $1/$SKILL_NAME"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
list() {
|
|
71
|
+
echo "智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):"
|
|
72
|
+
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
73
|
+
local dirs first
|
|
74
|
+
dirs="$(dirs_for "$a")"
|
|
75
|
+
first="${dirs%% *}"
|
|
76
|
+
case "$first" in
|
|
77
|
+
__CODEX__) first=".codex/skills" ;;
|
|
78
|
+
__OPENCODE__) first=".config/opencode/skills" ;;
|
|
79
|
+
esac
|
|
80
|
+
if [ "$_IS_WINDOWS" = "1" ]; then
|
|
81
|
+
first="%USERPROFILE%\\${first//\//\\}"
|
|
82
|
+
else
|
|
83
|
+
first="~/$first"
|
|
84
|
+
fi
|
|
85
|
+
printf ' %-10s %s\n' "$a" "$first"
|
|
86
|
+
done
|
|
87
|
+
echo '说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。'
|
|
88
|
+
echo '改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
main() {
|
|
92
|
+
local agent="" dir="" global=0 show_list=0
|
|
93
|
+
while [ $# -gt 0 ]; do
|
|
94
|
+
case "$1" in
|
|
95
|
+
--agent) shift; agent="${1:-}" ;;
|
|
96
|
+
--dir) shift; dir="${1:-}" ;;
|
|
97
|
+
-g|--global) global=1 ;;
|
|
98
|
+
--list|-l) show_list=1 ;;
|
|
99
|
+
*) echo "未知参数: $1" >&2; exit 2 ;;
|
|
100
|
+
esac
|
|
101
|
+
shift
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
if [ "$show_list" = "1" ]; then list; return; fi
|
|
105
|
+
if [ -n "$dir" ]; then install_to "$dir"; echo "完成。"; return; fi
|
|
106
|
+
if [ -n "$agent" ]; then
|
|
107
|
+
local dirs first
|
|
108
|
+
if ! dirs="$(dirs_for "$agent")"; then
|
|
109
|
+
echo "未收录智能体: $agent。请用 --dir <路径> 指定技能目录。" >&2; exit 2
|
|
110
|
+
fi
|
|
111
|
+
first="${dirs%% *}"
|
|
112
|
+
install_to "$(resolve_user "$first")"; echo "完成。"; return
|
|
113
|
+
fi
|
|
114
|
+
if [ "$global" = "1" ]; then
|
|
115
|
+
echo "安装到全部已知智能体用户级目录..."
|
|
116
|
+
local dirs rel
|
|
117
|
+
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
118
|
+
dirs="$(dirs_for "$a")"
|
|
119
|
+
for rel in $dirs; do install_to "$(resolve_user "$rel")"; done
|
|
120
|
+
done
|
|
121
|
+
echo "完成。"; return
|
|
122
|
+
fi
|
|
123
|
+
local installed=0 d
|
|
124
|
+
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
|
|
125
|
+
if [ -d "$d" ]; then install_to "$d"; installed=1; fi
|
|
126
|
+
done
|
|
127
|
+
if [ "$installed" = "0" ]; then
|
|
128
|
+
echo "未检测到项目级智能体目录。可用 --agent <name> / -g 装到用户级,或 --dir 指定。"
|
|
129
|
+
fi
|
|
130
|
+
}
|
|
131
|
+
|
|
132
132
|
main "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-intel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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": [
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"assets",
|
|
19
19
|
"bin",
|
|
20
20
|
"NOTICE",
|
|
21
|
-
"CHANGELOG.md"
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"!scripts/__pycache__",
|
|
23
|
+
"!**/__pycache__",
|
|
24
|
+
"!**/*.pyc"
|
|
22
25
|
],
|
|
23
26
|
"repository": {
|
|
24
27
|
"type": "git",
|
|
@@ -29,5 +32,8 @@
|
|
|
29
32
|
},
|
|
30
33
|
"bin": {
|
|
31
34
|
"yotta-intel": "bin/install.js"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --test \"test/*.test.js\" && python scripts/test_yotta_intel.py"
|
|
32
38
|
}
|
|
33
|
-
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# 元情常见问题
|
|
2
|
+
|
|
3
|
+
## 速查索引
|
|
4
|
+
|
|
5
|
+
- **提取**:为什么没提出 IOC · 哈希为什么漏了 · 域名为什么误报
|
|
6
|
+
- **格式**:defang 没还原 · CSV/JSON 怎么选 · STIX 怎么用
|
|
7
|
+
- **报错**:文件读不了 · 退出码是什么意思
|
|
8
|
+
- **安装**:装到哪个目录 · 和元钥/元察怎么分工
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. 为什么文本里有地址却一个 IOC 都没提取?
|
|
13
|
+
|
|
14
|
+
先确认文本是普通报告、钓鱼邮件还是日志:纯文本用 `extract --path`,管道内容用 `extract --stdin`。若原文被 `[.]`、`hxxp` 等去活性写法包裹,`extract` 会先做 refang 预处理,理论上也能命中;仍为空时可先用 `refang` 看还原结果,再检查 IOC 是否属于被类型过滤或 `--min-count` 过滤的范围。
|
|
15
|
+
|
|
16
|
+
## 2. 提取结果把 `README.md`、`test.py` 当域名了?
|
|
17
|
+
|
|
18
|
+
不会。引擎对常见的文件名后缀(`.md`、`.py`、`.sh` 等)做了过滤,也要求域名至少两级且 TLD 在白名单内;单标签、纯数字 TLD 都会被拒绝。
|
|
19
|
+
|
|
20
|
+
## 3. 为什么某些 32/40 位十六进制没有被当哈希?
|
|
21
|
+
|
|
22
|
+
哈希必须满足长度且不能被识别为 UUID 等其它格式;若十六进制串嵌在普通句子或代码里,可能被当作普通 token 跳过。要确认某段是否真的是哈希,可以把它单独放一行或用 `verify` 类工具复核;本引擎只提取明确形态。
|
|
23
|
+
|
|
24
|
+
## 4. defang 文本没有被还原?
|
|
25
|
+
|
|
26
|
+
先确认写法是否属于支持的形态:`[.]`、`(.)`、`[dot]`、`hxxp`、`[@]`、`[:]`、`[/]` 等均支持;大小写不敏感。若用了自定义分隔符(如 ` DOT `、URL 编码),引擎不会猜测,需先还原成标准形态。
|
|
27
|
+
|
|
28
|
+
## 5. CSV、JSON、STIX 怎么选?
|
|
29
|
+
|
|
30
|
+
给表格/平台导入用 CSV;给程序或自动化下游用 JSON(结构完整、含来源与行号);要生成 STIX 2.1 Bundle 供 SIEM 或情报平台导入用 `--format stix`。
|
|
31
|
+
|
|
32
|
+
## 6. STIX 输出和 JSON 里的 IOC 数量为什么可能不同?
|
|
33
|
+
|
|
34
|
+
JSON/CSV 会保留去重后的七类 IOC;STIX-lite 也按同源生成 indicator,字段以规范文档为准。若你在 STIX 里看不到某类型,先检查该类型是否被 `--types` 过滤或引擎判定为无效格式。
|
|
35
|
+
|
|
36
|
+
## 7. 提示文件不存在/读取失败,退出码是什么?
|
|
37
|
+
|
|
38
|
+
文件不存在、无输入、未知类型等用法或读取错误统一为退出码 4;`extract` 成功但未发现 IOC 为 0,发现 IOC 为 1。`--stdin` 模式请确保确实有管道输入。
|
|
39
|
+
|
|
40
|
+
## 8. 可以传目录吗?
|
|
41
|
+
|
|
42
|
+
`extract` 面向单文件或 stdin;要批量处理目录请由 AI 遍历文件后逐个提取,或在脚本里对每个文件调用一次。`defang/refang` 同样按单文件/流处理。
|
|
43
|
+
|
|
44
|
+
## 9. 输出里 `count` 和 `first_line` 怎么理解?
|
|
45
|
+
|
|
46
|
+
`count` 是同一 IOC 在文本中出现的次数;`first_line` 是首次出现的行号;`snippet` 是上下文片段。用 `--min-count 2` 可过滤只出现一次的噪音。
|
|
47
|
+
|
|
48
|
+
## 10. 我能在邮件/群聊里直接分享原始 IOC 吗?
|
|
49
|
+
|
|
50
|
+
建议先 defang。原始 URL/邮箱/域名可能被平台自动转成可点击链接,误触或触发沙箱外联;`defang` 会保留可读性并去掉活性。
|
|
51
|
+
|
|
52
|
+
## 11. 安装不上/智能体没识别怎么办?
|
|
53
|
+
|
|
54
|
+
用 `npx -y @yottameta/yotta-intel --agent <名称>` 装到已知智能体,或 `--dir <技能目录>` 装到自定义路径;未收录智能体不要靠猜目录,直接 `--dir`。若走 npm 镜像遇到传播延迟,临时加 `--registry=https://registry.npmjs.org/`。
|
|
55
|
+
|
|
56
|
+
## 12. 元情和元钥、元察怎么分工?
|
|
57
|
+
|
|
58
|
+
元情只做威胁情报文本里的 IOC 提取/归一/格式转换;元钥负责源码/配置里的密钥泄露扫描;元察负责安全日志检测。输入是报告/邮件/日志文本时优先元情,输入是代码/配置仓库时用元钥。
|
package/references/ioc-spec.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# IOC 类型与判定规则(ioc-spec)
|
|
2
2
|
|
|
3
|
-
> 元情 yotta-intel v0.
|
|
3
|
+
> 元情 yotta-intel v0.2.0 内部规范:七类 IOC 的提取 / 归一 / 误报控制规则。
|
|
4
4
|
> 本文件是引擎行为的权威说明;SKILL.md 只放入口,细节在这里。
|
|
5
5
|
|
|
6
6
|
## 1. 支持的类型
|
|
@@ -63,4 +63,4 @@
|
|
|
63
63
|
|
|
64
64
|
- 纯本地离线:不联网查证、不下载样本、不主动扫描任何系统;只做文本提取与规范化。
|
|
65
65
|
- 提取结果只是「候选指标」,是否恶意需人工 / 其他情报源核实;本引擎不给恶意定性。
|
|
66
|
-
- 仅用于已获授权 / 自有资产 / 教学环境的安全分析。
|
|
66
|
+
- 仅用于已获授权 / 自有资产 / 教学环境的安全分析。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# STIX-lite 输出规范
|
|
2
2
|
|
|
3
|
-
> 元情 yotta-intel v0.
|
|
3
|
+
> 元情 yotta-intel v0.2.0:`extract --format stix` 的产物说明。
|
|
4
4
|
> 「lite」= 只生成 STIX 2.1 的 Indicator 对象 + 自定义扩展属性,不实现完整 STIX 库。
|
|
5
5
|
|
|
6
6
|
## 1. 为什么叫 lite
|
|
@@ -73,4 +73,4 @@ pattern 内使用原始规范值(机器可解析);人看的 defang 形态
|
|
|
73
73
|
- **共享**:与报告一起发时,把 `defanged` 列给读者看,把 STIX 文件给机器用。
|
|
74
74
|
- **人工核对**:`labels` 是默认值,接入生产情报库前应由分析师复核并补充 Confidence / 来源。
|
|
75
75
|
- **升级提示**:本输出是 lite 子集,不含 Sighting / Relationship / Kill Chain;需要完整图谱时
|
|
76
|
-
请用专业 STIX 工具链。
|
|
76
|
+
请用专业 STIX 工具链。
|
|
@@ -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.
|
|
491
|
+
self.assertIn("0.2.0", 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")
|