@yottameta/yotta-logs 0.2.2 → 0.3.1
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 +17 -4
- package/LICENSE +20 -20
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/SKILL.md +2 -1
- package/bin/install.js +245 -163
- package/install.sh +131 -131
- package/package.json +9 -2
- package/references/agent-formats.md +7 -7
- package/references/cli.md +1 -1
- package/references/faq.md +54 -0
- package/scripts/yotta_logs.py +23 -10
- package/scripts/test_yotta_logs.py +0 -757
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## v0.3.1 (2026-09-13)
|
|
2
|
+
|
|
3
|
+
- 修复本机专属路径硬编码:移除本机自定义数据目录候选;opencode 只认 `XDG_DATA_HOME` / `OPENCODE_DATA` / 官方默认路径,Codex notes 只认 `$CODEX_HOME/memories` 或 `~/.codex/memories`。
|
|
4
|
+
- 配置路径改为平台无关:`$YOTTA_LOGS_CONFIG` > Windows `%APPDATA%` > Unix `$XDG_CONFIG_HOME` / `~/.config`。
|
|
5
|
+
- 新增便携覆盖回归:`YOTTA_MEMORY_HOME`、`CODEX_HOME`、`YOTTA_LOGS_CONFIG`;并加本机自定义目录反向断言。
|
|
6
|
+
|
|
7
|
+
## v0.3.0 (2026-09-08)
|
|
8
|
+
|
|
9
|
+
**评测驱动完善**:新增 FAQ,安装器错误处理与测试补齐。
|
|
10
|
+
|
|
11
|
+
- 新增 references/faq.md,覆盖格式、范围、脱敏、只读边界与安装排障。
|
|
12
|
+
- 安装器支持 --help、参数校验、统一退出码与人话错误提示。
|
|
13
|
+
|
|
1
14
|
# 更新日志
|
|
2
15
|
|
|
3
16
|
## v0.2.2 (2026-08-29)
|
|
@@ -8,7 +21,7 @@
|
|
|
8
21
|
|
|
9
22
|
## v0.2.1 (2026-08-27)
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
文档中英双版:
|
|
12
25
|
|
|
13
26
|
- **README.md 改为英文**:作为 GitHub / npm / ClawHub 首页的英文门面(翻译 + 精简,覆盖定位 / 核心价值 / 命令 / 快速使用 / 安装 / 使用示例 / 开发校验全流程)。
|
|
14
27
|
- **新增 README.zh-CN.md**:原中文完整主文档整体平移,继续服务中文用户,顶部加语言切换链接。
|
|
@@ -18,7 +31,7 @@
|
|
|
18
31
|
|
|
19
32
|
## v0.2.0 (2026-08-27)
|
|
20
33
|
|
|
21
|
-
多格式通用化:不再只认 JSONL,按「格式族 × 字段别名归一 +
|
|
34
|
+
多格式通用化:不再只认 JSONL,按「格式族 × 字段别名归一 + 配置兜底」适配一切格式:
|
|
22
35
|
|
|
23
36
|
- **五大格式族 reader**:JSONL(支持嵌套子目录与 Codex rollout payload 形态)/ 单文件 JSON(数组、dict-of-lists)/ SQLite(opencode schema 实测 + 通用列映射兜底,只读 mode=ro)/ Markdown(YAML frontmatter 结构化记忆 + 自由笔记)/ 二进制(只降级读标题不崩)。
|
|
24
37
|
- **统一 Record 模型**:{source, format, kind, session, time, role, text, path, meta};字段别名归一(time / role / text / session / title),秒 / 毫秒自动推断,JSON 字符串解包。
|
|
@@ -30,9 +43,9 @@
|
|
|
30
43
|
|
|
31
44
|
## v0.1.0 (2026-08-27)
|
|
32
45
|
|
|
33
|
-
YottaMeta 自有实现首版(历史会话日志检索方向参考开源社区 session-logs
|
|
46
|
+
YottaMeta 自有实现首版(历史会话日志检索方向参考开源社区 session-logs 类技能思路,已完全重写):
|
|
34
47
|
|
|
35
48
|
- 元史(yotta-logs)—— 零依赖跨智能体会话日志检索引擎(Python 3.8+ 标准库)。
|
|
36
49
|
- 能力:locate / scan / search / session / stats / tools / version;关键词 / 正则 / 日期 / 会话 ID / 别名 / 角色过滤;默认脱敏(--no-redact 关闭);--json 结构化输出;只读安全;容错 JSONL 解析(含 sessions.json 索引别名)。
|
|
37
50
|
- 测试:scripts/test_yotta_logs.py 75 项全绿。
|
|
38
|
-
- 版权:YottaMeta 纯自有 MIT + NOTICE
|
|
51
|
+
- 版权:YottaMeta 纯自有 MIT + NOTICE 品牌声明。
|
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/README.md
CHANGED
|
@@ -162,12 +162,19 @@ bash install.sh --list # list agents -> default directories
|
|
|
162
162
|
|
|
163
163
|
## Changelog
|
|
164
164
|
|
|
165
|
+
- v0.3.1 (2026-09-13): Removed machine-specific hardcoded paths; discovery and config now follow environment variables and platform defaults, with regression coverage.
|
|
166
|
+
- v0.3.0 (2026-09-08): Evaluation-driven refinement — added FAQ, hardened installer error handling and exit codes, and added installer tests.
|
|
167
|
+
|
|
165
168
|
- v0.2.2 (2026-08-29): Install docs alignment — unified four install methods (npx -y @yottameta/yotta-logs --agent/--dir, git clone, GitHub Download ZIP, install.sh --agent/--dir/--list), removed the legacy GitHub-clone installer and global-install (-g) recommendations; bilingual README install section synced to 发布规范 §3.3.1. No functional change.
|
|
166
169
|
|
|
167
170
|
- v0.2.1 (2026-08-27): Bilingual documentation — English README as the GitHub / npm / ClawHub homepage, full Chinese doc moved to README.zh-CN.md, English npm description.
|
|
168
171
|
- v0.2.0 (2026-08-27): Multi-format generalization — JSONL / single-file JSON / SQLite (opencode etc.) / Markdown (memory + free notes) / binary; unified Record + field-alias normalization + config fallback; discover; new --source / --kind / --format filters and default search scope (session + structured memory on, free notes / binary logs off). See CHANGELOG.md.
|
|
169
172
|
- v0.1.0 (2026-08-27): Initial release — zero-dependency JSONL session log search engine (locate / scan / search / session / stats / tools / version + default redaction + sessions.json alias + read-only).
|
|
170
173
|
|
|
174
|
+
## References
|
|
175
|
+
|
|
176
|
+
- references/faq.md
|
|
177
|
+
|
|
171
178
|
## License
|
|
172
179
|
|
|
173
180
|
MIT © YottaMeta — see [LICENSE](./LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -162,11 +162,18 @@ bash install.sh --list # 列出智能体 -> 默认目录
|
|
|
162
162
|
|
|
163
163
|
## 更新日志
|
|
164
164
|
|
|
165
|
+
- v0.3.1(2026-09-13):移除本机专属硬编码路径;discovery 与配置统一走环境变量和平台默认位置,并补回归覆盖。
|
|
166
|
+
- v0.3.0(2026-09-08):评测驱动完善——新增常见问题,安装器错误处理与退出码加固,并补齐安装器测试。
|
|
167
|
+
|
|
165
168
|
- v0.2.2(2026-08-29):安装方式统一为四方式(对齐发布规范 §3.3.1)——方式一 npx -y @yottameta/yotta-logs --agent / --dir(推荐,走 npm 源);方式二 git clone;方式三 GitHub Download ZIP;方式四 bash install.sh --agent/--dir/--list。移除旧式 GitHub 克隆安装器与全局安装(-g)推荐;中英 README 安装节同步。无功能变更。
|
|
166
169
|
|
|
167
170
|
- v0.2.0(2026-08-27):多格式通用化——JSONL / 单文件 JSON / SQLite(opencode 等)/ Markdown(记忆 + 自由笔记)/ 二进制五大格式族,统一 Record + 字段别名归一 + 配置兜底,discover 全源登记,新增 --source / --kind / --format 过滤与默认检索范围(会话 + 结构化记忆开、自由笔记 / 二进制日志关)。详见 CHANGELOG.md。
|
|
168
171
|
- v0.1.0(2026-08-27):首版——零依赖 JSONL 会话日志检索引擎(locate / scan / search / session / stats / tools / version + 默认脱敏 + sessions.json 别名 + 只读)。
|
|
169
172
|
|
|
173
|
+
## 参考文档
|
|
174
|
+
|
|
175
|
+
- references/faq.md
|
|
176
|
+
|
|
170
177
|
## 许可证
|
|
171
178
|
|
|
172
179
|
MIT © YottaMeta —— 详见 [LICENSE](./LICENSE)。
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-logs
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.3.1
|
|
4
4
|
description: 元史 —— 跨智能体的历史会话 / 记忆日志检索技能:零依赖检索 / 分析 JSONL、JSON、SQLite、Markdown 多格式会话与记忆文件,回溯旧对话与父会话上下文,为跨会话追溯提供原始日志依据。触发:用户问起先前聊过的内容 / 父会话 / 历史上下文、要查以前说过的结论、跨会话回溯某次讨论、需要从会话日志或记忆文件定位某段决策时。边界:仅读取本机自己的会话日志 / 记忆文件;不修改、不删除;只查本地不联网上传。
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
@@ -84,6 +84,7 @@ python3 scripts/yotta_logs.py tools --dir /path/to/logs --format sqlite
|
|
|
84
84
|
- references/format.md — 统一 Record 模型与各格式族读取规则
|
|
85
85
|
- references/cli.md — CLI 子命令 / 参数 / 退出码 / JSON schema / 配置详解
|
|
86
86
|
- references/security.md — 安全边界 / 脱敏规则 / 与元忆的差异化
|
|
87
|
+
- references/faq.md — 常见问题与安装排障
|
|
87
88
|
|
|
88
89
|
## 责任声明
|
|
89
90
|
|
package/bin/install.js
CHANGED
|
@@ -1,163 +1,245 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* yotta-logs
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* npx -y @yottameta/yotta-logs --
|
|
7
|
-
* npx -y @yottameta/yotta-logs
|
|
8
|
-
* npx -y @yottameta/yotta-logs
|
|
9
|
-
* npx -y @yottameta/yotta-logs
|
|
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-logs cross-platform installer.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx -y @yottameta/yotta-logs --agent <name>
|
|
7
|
+
* npx -y @yottameta/yotta-logs --dir <path>
|
|
8
|
+
* npx -y @yottameta/yotta-logs -g
|
|
9
|
+
* npx -y @yottameta/yotta-logs
|
|
10
|
+
* npx -y @yottameta/yotta-logs --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-logs';
|
|
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-logs installer');
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log('Usage:');
|
|
50
|
+
console.log(' npx -y @yottameta/yotta-logs --agent <name> Install to an agent default directory');
|
|
51
|
+
console.log(' npx -y @yottameta/yotta-logs --dir <path> Install to a custom directory');
|
|
52
|
+
console.log(' npx -y @yottameta/yotta-logs -g Install to all known agent directories');
|
|
53
|
+
console.log(' npx -y @yottameta/yotta-logs Auto-detect project-level directories');
|
|
54
|
+
console.log(' npx -y @yottameta/yotta-logs --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();
|