@adonis0123/weekly-report 1.0.5
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/.claude-skill.json +46 -0
- package/README.md +63 -0
- package/SKILL.md +174 -0
- package/install-skill.js +315 -0
- package/package.json +35 -0
- package/references/WEEKLY_REPORT_FORMAT.md +116 -0
- package/src/__init__.py +3 -0
- package/src/config_manager.py +171 -0
- package/src/date_utils.py +272 -0
- package/src/git_analyzer.py +342 -0
- package/src/report_generator.py +257 -0
- package/src/storage.py +491 -0
- package/uninstall-skill.js +191 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "weekly-report",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"package": "@adonis0123/weekly-report",
|
|
5
|
+
"files": {
|
|
6
|
+
"references": "references/",
|
|
7
|
+
"src": "src/"
|
|
8
|
+
},
|
|
9
|
+
"targets": {
|
|
10
|
+
"claude-code": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"paths": {
|
|
13
|
+
"global": ".claude/skills",
|
|
14
|
+
"project": ".claude/skills"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"cursor": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"paths": {
|
|
20
|
+
"global": ".cursor/skills",
|
|
21
|
+
"project": ".cursor/skills"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"windsurf": {
|
|
25
|
+
"enabled": false,
|
|
26
|
+
"paths": {
|
|
27
|
+
"global": ".windsurf/skills",
|
|
28
|
+
"project": ".windsurf/skills"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"aider": {
|
|
32
|
+
"enabled": false,
|
|
33
|
+
"paths": {
|
|
34
|
+
"global": ".aider/skills",
|
|
35
|
+
"project": ".aider/skills"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"custom": {
|
|
39
|
+
"enabled": false,
|
|
40
|
+
"paths": {
|
|
41
|
+
"global": ".ai-skills",
|
|
42
|
+
"project": ".ai-skills"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @adonis0123/weekly-report
|
|
2
|
+
|
|
3
|
+
> Claude Code 技能 - 自动读取 Git 提交记录生成周报
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@adonis0123/weekly-report)
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @adonis0123/weekly-report
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 使用
|
|
14
|
+
|
|
15
|
+
在任意 Git 项目目录中,对 Claude Code 说:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/weekly-report
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 功能
|
|
22
|
+
|
|
23
|
+
- 自动读取 Git 提交记录
|
|
24
|
+
- 支持多仓库汇总
|
|
25
|
+
- 智能过滤琐碎提交(typo、merge、format)
|
|
26
|
+
- 按项目分组生成结构化周报
|
|
27
|
+
- 灵活时间范围(本周、上周、自定义)
|
|
28
|
+
- 周报自动保存到 `~/.weekly-reports/`
|
|
29
|
+
|
|
30
|
+
## 输出示例
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
# 周报 (2026-01-06 ~ 2026-01-12)
|
|
34
|
+
|
|
35
|
+
project-frontend
|
|
36
|
+
- 构建工具升级改造
|
|
37
|
+
- 核心功能开发流程跟进
|
|
38
|
+
|
|
39
|
+
project-backend
|
|
40
|
+
- 自定义类型化消息渲染
|
|
41
|
+
- 断线重连流程梳理
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 配置
|
|
45
|
+
|
|
46
|
+
配置文件:`~/.weekly-reports/config.json`
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"repos": [
|
|
51
|
+
{ "name": "project-a", "path": "/path/to/project-a" }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 更多技能
|
|
57
|
+
|
|
58
|
+
- [@adonis0123/agent-browser](https://www.npmjs.com/package/@adonis0123/agent-browser) - 浏览器自动化
|
|
59
|
+
- [@adonis0123/react-best-practices](https://www.npmjs.com/package/@adonis0123/react-best-practices) - React 最佳实践
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: weekly-report
|
|
3
|
+
description: 自动读取 Git 提交记录生成周报。用于生成工作周报、项目总结、团队协作汇报。支持多仓库汇总。
|
|
4
|
+
allowed-tools: Read, Write, Bash(git:*), Bash(python:*)
|
|
5
|
+
metadata:
|
|
6
|
+
author: adonis
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 周报生成技能
|
|
11
|
+
|
|
12
|
+
自动读取 Git 提交记录,按项目分组生成结构化周报。
|
|
13
|
+
|
|
14
|
+
## 功能特性
|
|
15
|
+
|
|
16
|
+
- 自动读取 Git 提交记录
|
|
17
|
+
- 支持多仓库汇总
|
|
18
|
+
- 自动识别当前用户 (`git config user.name`)
|
|
19
|
+
- 按项目分组,生成结构化周报
|
|
20
|
+
- 过滤琐碎提交(typo、merge、format 等)
|
|
21
|
+
- 支持添加补充说明
|
|
22
|
+
- 周报统一存储在 `~/.weekly-reports/` 目录
|
|
23
|
+
|
|
24
|
+
## 使用方式
|
|
25
|
+
|
|
26
|
+
### 基本用法
|
|
27
|
+
|
|
28
|
+
在任意 Git 项目目录中执行:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/weekly-report
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 执行流程
|
|
35
|
+
|
|
36
|
+
1. **选择时间范围**
|
|
37
|
+
- 本周 (显示具体日期,如 2026-01-06 ~ 2026-01-12)
|
|
38
|
+
- 上周 (显示具体日期,如 2025-12-30 ~ 2026-01-05)
|
|
39
|
+
- 前半年 (显示具体日期,如 2025-07-13 ~ 2026-01-13)
|
|
40
|
+
- 自定义周报(输入周一日期)
|
|
41
|
+
- 自定义时间段(输入起始日期,截止到今天)
|
|
42
|
+
|
|
43
|
+
**重要**:选择时必须显示具体的日期范围,让用户确认是否正确
|
|
44
|
+
|
|
45
|
+
2. **选择仓库**(如已配置多仓库)
|
|
46
|
+
- 显示已配置的仓库列表
|
|
47
|
+
- 可多选要包含的仓库
|
|
48
|
+
- 可添加当前目录为新仓库
|
|
49
|
+
|
|
50
|
+
3. **添加补充内容**(可选)
|
|
51
|
+
- 输入额外的工作内容
|
|
52
|
+
- 如:参与会议、技术分享等
|
|
53
|
+
|
|
54
|
+
4. **生成周报**
|
|
55
|
+
- 读取选定仓库的 Git 提交(必须覆盖所有分支/远端跟踪分支,避免漏提交)
|
|
56
|
+
- 按项目分组
|
|
57
|
+
- 过滤琐碎提交
|
|
58
|
+
- 生成 Markdown 格式周报
|
|
59
|
+
- 周报保存到 `~/.weekly-reports/{year}/week-{week}.md`
|
|
60
|
+
- 时间段报告保存到 `~/.weekly-reports/periods/{start_date}_to_{end_date}.md`
|
|
61
|
+
|
|
62
|
+
## Git 提交读取(重要)
|
|
63
|
+
|
|
64
|
+
为避免“只读取当前分支而漏掉其它分支(例如 `credits-lite*`)”的问题,读取提交时必须使用 `--all`(覆盖本地分支 + 远端跟踪分支),并确保截止时间包含结束日当天:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 关键点:
|
|
68
|
+
# - 用 --all 覆盖所有本地 refs(包含 remotes/origin/*)
|
|
69
|
+
# - --until 用 “结束日 23:59:59” 避免漏掉结束日当天提交
|
|
70
|
+
# - --author 建议用 name + email 联合匹配,避免不同身份写法漏掉本人提交
|
|
71
|
+
|
|
72
|
+
AUTHOR_PATTERN="(your-name|your@email.com)" # 或仅用你的 name/email
|
|
73
|
+
git log --all \
|
|
74
|
+
--author="$AUTHOR_PATTERN" \
|
|
75
|
+
--since="$START_DATE 00:00:00" \
|
|
76
|
+
--until="$END_DATE 23:59:59" \
|
|
77
|
+
--pretty=format:"%H|%s|%an|%ad" \
|
|
78
|
+
--date=short
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
如果 `git branch -a` 看不到目标远端分支(说明本地没有对应的远端跟踪引用),需要先 `git fetch --all --prune`(在用户同意且网络可用时执行),否则无法读取到“本地不存在的分支”的提交。
|
|
82
|
+
|
|
83
|
+
## 输出格式
|
|
84
|
+
|
|
85
|
+
周报采用层级列表结构,**必须包含日期范围标题**,按项目分组:
|
|
86
|
+
|
|
87
|
+
### 周报格式
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
# 周报 (2026-01-06 ~ 2026-01-12)
|
|
91
|
+
|
|
92
|
+
项目名称
|
|
93
|
+
- 主要工作点(10字以内)
|
|
94
|
+
- 补充说明(可选)
|
|
95
|
+
- 另一个工作点
|
|
96
|
+
|
|
97
|
+
其他
|
|
98
|
+
- 不属于特定项目的工作内容
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 时间段报告格式
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
# 工作总结 (2025-07-13 ~ 2026-01-13)
|
|
105
|
+
|
|
106
|
+
项目名称
|
|
107
|
+
- 主要工作点(10字以内)
|
|
108
|
+
- 补充说明(可选)
|
|
109
|
+
- 另一个工作点
|
|
110
|
+
|
|
111
|
+
其他
|
|
112
|
+
- 不属于特定项目的工作内容
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 示例输出
|
|
116
|
+
|
|
117
|
+
```markdown
|
|
118
|
+
# 周报 (2026-01-06 ~ 2026-01-12)
|
|
119
|
+
|
|
120
|
+
project-frontend
|
|
121
|
+
- 构建工具升级改造
|
|
122
|
+
- 核心功能开发流程跟进
|
|
123
|
+
- 方案合理性优化
|
|
124
|
+
- 脚本国际化优化
|
|
125
|
+
|
|
126
|
+
project-backend
|
|
127
|
+
- 自定义类型化消息渲染
|
|
128
|
+
- 断线重连流程梳理
|
|
129
|
+
|
|
130
|
+
其他
|
|
131
|
+
- 新版国际化方案讨论
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 配置文件
|
|
135
|
+
|
|
136
|
+
配置文件位于 `~/.weekly-reports/config.json`:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"repos": [
|
|
141
|
+
{
|
|
142
|
+
"name": "project-a",
|
|
143
|
+
"path": "/home/user/projects/project-a"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "project-b",
|
|
147
|
+
"path": "/home/user/projects/project-b"
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"default_author": "auto",
|
|
151
|
+
"output_format": "markdown"
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 总结原则
|
|
156
|
+
|
|
157
|
+
### 必须遵守
|
|
158
|
+
|
|
159
|
+
- **事实导向**:只总结实际完成的工作
|
|
160
|
+
- **简洁精炼**:主要工作点控制在 10 字以内
|
|
161
|
+
- **重点突出**:过滤琐碎修改
|
|
162
|
+
- **按项目分组**:相同项目的工作归类
|
|
163
|
+
- **层级清晰**:用缩进表示从属关系
|
|
164
|
+
|
|
165
|
+
### 过滤规则
|
|
166
|
+
|
|
167
|
+
以下提交不会单独列出:
|
|
168
|
+
- 纯格式化/代码风格调整
|
|
169
|
+
- 简单的 typo 修复
|
|
170
|
+
- 依赖版本小幅更新
|
|
171
|
+
- Merge 提交
|
|
172
|
+
- 重复性的相似提交
|
|
173
|
+
|
|
174
|
+
详细格式规范见 [周报格式规范](references/WEEKLY_REPORT_FORMAT.md)
|
package/install-skill.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// shared/src/install-skill.ts
|
|
26
|
+
var import_fs2 = __toESM(require("fs"));
|
|
27
|
+
var import_path2 = __toESM(require("path"));
|
|
28
|
+
var import_os2 = __toESM(require("os"));
|
|
29
|
+
var import_child_process = require("child_process");
|
|
30
|
+
|
|
31
|
+
// shared/src/utils.ts
|
|
32
|
+
var import_fs = __toESM(require("fs"));
|
|
33
|
+
var import_path = __toESM(require("path"));
|
|
34
|
+
var import_os = __toESM(require("os"));
|
|
35
|
+
var CWD = process.env.INIT_CWD || process.cwd();
|
|
36
|
+
var DEFAULT_TARGET = {
|
|
37
|
+
name: "claude-code",
|
|
38
|
+
paths: {
|
|
39
|
+
global: ".claude/skills",
|
|
40
|
+
project: ".claude/skills"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function getEnabledTargets(config) {
|
|
44
|
+
if (!config.targets) {
|
|
45
|
+
return [DEFAULT_TARGET];
|
|
46
|
+
}
|
|
47
|
+
return Object.entries(config.targets).filter(([_, target]) => target.enabled).map(([name, target]) => ({
|
|
48
|
+
name,
|
|
49
|
+
paths: target.paths
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
function extractSkillName(packageName) {
|
|
53
|
+
if (packageName.startsWith("@")) {
|
|
54
|
+
return packageName.split("/")[1] || packageName;
|
|
55
|
+
}
|
|
56
|
+
return packageName;
|
|
57
|
+
}
|
|
58
|
+
function detectInstallLocation(targetPaths, isGlobal) {
|
|
59
|
+
if (isGlobal) {
|
|
60
|
+
return {
|
|
61
|
+
type: "personal",
|
|
62
|
+
base: import_path.default.join(import_os.default.homedir(), targetPaths.global)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
let projectRoot = CWD;
|
|
66
|
+
while (projectRoot !== import_path.default.dirname(projectRoot)) {
|
|
67
|
+
const hasPackageJson = import_fs.default.existsSync(import_path.default.join(projectRoot, "package.json"));
|
|
68
|
+
const hasGit = import_fs.default.existsSync(import_path.default.join(projectRoot, ".git"));
|
|
69
|
+
const isInNodeModules = projectRoot.includes("/node_modules/") || import_path.default.basename(projectRoot) === "node_modules";
|
|
70
|
+
if ((hasPackageJson || hasGit) && !isInNodeModules) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
projectRoot = import_path.default.dirname(projectRoot);
|
|
74
|
+
}
|
|
75
|
+
const finalIsInNodeModules = projectRoot.includes("/node_modules/") || import_path.default.basename(projectRoot) === "node_modules";
|
|
76
|
+
if (finalIsInNodeModules) {
|
|
77
|
+
console.warn("\u26A0 Warning: Could not find project root directory, using current directory");
|
|
78
|
+
projectRoot = CWD;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
type: "project",
|
|
82
|
+
base: import_path.default.join(projectRoot, targetPaths.project)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function isGlobalInstall() {
|
|
86
|
+
return process.env.npm_config_global === "true";
|
|
87
|
+
}
|
|
88
|
+
function copyDir(src, dest) {
|
|
89
|
+
import_fs.default.mkdirSync(dest, { recursive: true });
|
|
90
|
+
const entries = import_fs.default.readdirSync(src, { withFileTypes: true });
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const srcPath = import_path.default.join(src, entry.name);
|
|
93
|
+
const destPath = import_path.default.join(dest, entry.name);
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
copyDir(srcPath, destPath);
|
|
96
|
+
} else {
|
|
97
|
+
import_fs.default.copyFileSync(srcPath, destPath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function ensureDir(dir) {
|
|
102
|
+
if (!import_fs.default.existsSync(dir)) {
|
|
103
|
+
import_fs.default.mkdirSync(dir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function removeDir(dir) {
|
|
107
|
+
if (import_fs.default.existsSync(dir)) {
|
|
108
|
+
import_fs.default.rmSync(dir, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function readSkillConfig(dir) {
|
|
112
|
+
const configPath = import_path.default.join(dir, ".claude-skill.json");
|
|
113
|
+
if (!import_fs.default.existsSync(configPath)) {
|
|
114
|
+
throw new Error(".claude-skill.json not found");
|
|
115
|
+
}
|
|
116
|
+
return JSON.parse(import_fs.default.readFileSync(configPath, "utf-8"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// shared/src/install-skill.ts
|
|
120
|
+
function fetchFromRemote(tempDir, remoteSource) {
|
|
121
|
+
try {
|
|
122
|
+
console.log(` \u{1F310} Fetching latest from ${remoteSource}...`);
|
|
123
|
+
(0, import_child_process.execSync)(`npx degit ${remoteSource} "${tempDir}" --force`, {
|
|
124
|
+
stdio: "pipe",
|
|
125
|
+
timeout: 6e4
|
|
126
|
+
});
|
|
127
|
+
if (import_fs2.default.existsSync(import_path2.default.join(tempDir, "SKILL.md"))) {
|
|
128
|
+
console.log(" \u2713 Fetched latest version from remote");
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
console.warn(" \u26A0 Remote fetch succeeded but SKILL.md not found");
|
|
132
|
+
return false;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
console.warn(` \u26A0 Could not fetch from remote: ${message}`);
|
|
136
|
+
console.log(" \u2139 Using bundled version as fallback");
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function getSourceDir(config, packageDir) {
|
|
141
|
+
if (!config.remoteSource) {
|
|
142
|
+
return {
|
|
143
|
+
sourceDir: packageDir,
|
|
144
|
+
cleanup: () => {
|
|
145
|
+
},
|
|
146
|
+
isRemote: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const tempDir = import_path2.default.join(import_os2.default.tmpdir(), `skill-fetch-${Date.now()}`);
|
|
150
|
+
const remoteSuccess = fetchFromRemote(tempDir, config.remoteSource);
|
|
151
|
+
if (remoteSuccess) {
|
|
152
|
+
return {
|
|
153
|
+
sourceDir: tempDir,
|
|
154
|
+
cleanup: () => {
|
|
155
|
+
try {
|
|
156
|
+
import_fs2.default.rmSync(tempDir, { recursive: true, force: true });
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
isRemote: true
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
import_fs2.default.rmSync(tempDir, { recursive: true, force: true });
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
sourceDir: packageDir,
|
|
169
|
+
cleanup: () => {
|
|
170
|
+
},
|
|
171
|
+
isRemote: false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
175
|
+
const manifestPath = import_path2.default.join(skillsDir, ".skills-manifest.json");
|
|
176
|
+
let manifest = { skills: {} };
|
|
177
|
+
if (import_fs2.default.existsSync(manifestPath)) {
|
|
178
|
+
try {
|
|
179
|
+
manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
|
|
180
|
+
} catch {
|
|
181
|
+
console.warn(" Warning: Could not parse existing manifest, creating new one");
|
|
182
|
+
manifest = { skills: {} };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const skillName = extractSkillName(config.name);
|
|
186
|
+
manifest.skills[config.name] = {
|
|
187
|
+
version: config.version,
|
|
188
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
|
+
package: config.package || config.name,
|
|
190
|
+
path: import_path2.default.join(skillsDir, skillName),
|
|
191
|
+
target: targetName,
|
|
192
|
+
...config.remoteSource && { source: config.remoteSource }
|
|
193
|
+
};
|
|
194
|
+
import_fs2.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
195
|
+
}
|
|
196
|
+
function installToTarget(target, config, sourceDir, isRemote) {
|
|
197
|
+
var _a;
|
|
198
|
+
console.log(`
|
|
199
|
+
\u{1F4E6} Installing to ${target.name}...`);
|
|
200
|
+
const isGlobal = isGlobalInstall();
|
|
201
|
+
const location = detectInstallLocation(target.paths, isGlobal);
|
|
202
|
+
const skillName = extractSkillName(config.name);
|
|
203
|
+
const targetDir = import_path2.default.join(location.base, skillName);
|
|
204
|
+
const altTargetDir = import_path2.default.join(location.base, config.name);
|
|
205
|
+
console.log(` Type: ${location.type}${isGlobal ? " (global)" : " (project)"}`);
|
|
206
|
+
console.log(` Directory: ${targetDir}`);
|
|
207
|
+
if (import_fs2.default.existsSync(altTargetDir) && altTargetDir !== targetDir) {
|
|
208
|
+
console.log(" \u{1F9F9} Cleaning up alternative path format...");
|
|
209
|
+
removeDir(altTargetDir);
|
|
210
|
+
console.log(` \u2713 Removed directory: ${config.name}`);
|
|
211
|
+
}
|
|
212
|
+
ensureDir(targetDir);
|
|
213
|
+
const skillMdSource = import_path2.default.join(sourceDir, "SKILL.md");
|
|
214
|
+
if (!import_fs2.default.existsSync(skillMdSource)) {
|
|
215
|
+
throw new Error("SKILL.md is required but not found");
|
|
216
|
+
}
|
|
217
|
+
import_fs2.default.copyFileSync(skillMdSource, import_path2.default.join(targetDir, "SKILL.md"));
|
|
218
|
+
console.log(" \u2713 Copied SKILL.md");
|
|
219
|
+
const filesToCopy = config.files || {};
|
|
220
|
+
for (const [source, dest] of Object.entries(filesToCopy)) {
|
|
221
|
+
const sourcePath = import_path2.default.join(sourceDir, source);
|
|
222
|
+
if (!import_fs2.default.existsSync(sourcePath)) {
|
|
223
|
+
console.warn(` \u26A0 Warning: ${source} not found, skipping`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const destPath = import_path2.default.join(targetDir, dest);
|
|
227
|
+
if (import_fs2.default.statSync(sourcePath).isDirectory()) {
|
|
228
|
+
copyDir(sourcePath, destPath);
|
|
229
|
+
console.log(` \u2713 Copied directory: ${source}`);
|
|
230
|
+
} else {
|
|
231
|
+
const destDir = import_path2.default.dirname(destPath);
|
|
232
|
+
ensureDir(destDir);
|
|
233
|
+
import_fs2.default.copyFileSync(sourcePath, destPath);
|
|
234
|
+
console.log(` \u2713 Copied file: ${source}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
updateManifest(location.base, config, target.name, isRemote);
|
|
238
|
+
if ((_a = config.hooks) == null ? void 0 : _a.postinstall) {
|
|
239
|
+
console.log(" \u{1F527} Running postinstall hook...");
|
|
240
|
+
try {
|
|
241
|
+
(0, import_child_process.execSync)(config.hooks.postinstall, {
|
|
242
|
+
cwd: targetDir,
|
|
243
|
+
stdio: "pipe"
|
|
244
|
+
});
|
|
245
|
+
console.log(" \u2713 Postinstall hook completed");
|
|
246
|
+
} catch {
|
|
247
|
+
console.warn(" \u26A0 Warning: postinstall hook failed");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
console.log(` \u2705 Installed to ${target.name}`);
|
|
251
|
+
return targetDir;
|
|
252
|
+
}
|
|
253
|
+
function installSkill() {
|
|
254
|
+
console.log("\u{1F680} Installing AI Coding Skill...\n");
|
|
255
|
+
const packageDir = __dirname;
|
|
256
|
+
const config = readSkillConfig(packageDir);
|
|
257
|
+
const enabledTargets = getEnabledTargets(config);
|
|
258
|
+
if (enabledTargets.length === 0) {
|
|
259
|
+
console.warn("\u26A0 No targets enabled in configuration");
|
|
260
|
+
console.warn("Please enable at least one target in .claude-skill.json");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
console.log(`Installing skill "${config.name}" to ${enabledTargets.length} target(s):`);
|
|
264
|
+
enabledTargets.forEach((target) => {
|
|
265
|
+
console.log(` \u2022 ${target.name}`);
|
|
266
|
+
});
|
|
267
|
+
const { sourceDir, cleanup, isRemote } = getSourceDir(config, packageDir);
|
|
268
|
+
if (isRemote) {
|
|
269
|
+
console.log(`
|
|
270
|
+
\u{1F4E1} Source: Remote (${config.remoteSource})`);
|
|
271
|
+
} else {
|
|
272
|
+
console.log("\n\u{1F4E6} Source: Bundled (local)");
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const installedPaths = [];
|
|
276
|
+
for (const target of enabledTargets) {
|
|
277
|
+
try {
|
|
278
|
+
const installPath = installToTarget(target, config, sourceDir, isRemote);
|
|
279
|
+
installedPaths.push({ target: target.name, path: installPath });
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
+
console.error(`
|
|
283
|
+
\u274C Failed to install to ${target.name}:`, message);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
console.log("\n" + "=".repeat(60));
|
|
287
|
+
console.log("\u2705 Installation Complete!");
|
|
288
|
+
console.log("=".repeat(60));
|
|
289
|
+
if (installedPaths.length > 0) {
|
|
290
|
+
console.log("\nInstalled to:");
|
|
291
|
+
installedPaths.forEach(({ target, path: installPath }) => {
|
|
292
|
+
console.log(` \u2022 ${target}: ${installPath}`);
|
|
293
|
+
});
|
|
294
|
+
console.log("\n\u{1F4D6} Next Steps:");
|
|
295
|
+
console.log(" 1. Restart your AI coding tool(s)");
|
|
296
|
+
console.log(' 2. Ask: "What skills are available?"');
|
|
297
|
+
console.log(" 3. Start using your skill!");
|
|
298
|
+
}
|
|
299
|
+
} finally {
|
|
300
|
+
cleanup();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
installSkill();
|
|
305
|
+
} catch (error) {
|
|
306
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
307
|
+
console.error("\n\u274C Failed to install skill:", message);
|
|
308
|
+
console.error("\nTroubleshooting:");
|
|
309
|
+
console.error("- Ensure .claude-skill.json exists and is valid JSON");
|
|
310
|
+
console.error("- Ensure SKILL.md exists");
|
|
311
|
+
console.error("- Check file permissions for target directories");
|
|
312
|
+
console.error("- Verify at least one target is enabled in .claude-skill.json");
|
|
313
|
+
console.error("- Try running with sudo for global installation (if needed)");
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adonis0123/weekly-report",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Claude Code Skill - 自动读取 Git 提交记录生成周报,支持多仓库汇总和智能过滤",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"postinstall": "node install-skill.js",
|
|
7
|
+
"preuninstall": "node uninstall-skill.js",
|
|
8
|
+
"test": "node install-skill.js && echo 'Installation test completed.'"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"SKILL.md",
|
|
12
|
+
"references/",
|
|
13
|
+
"src/",
|
|
14
|
+
"install-skill.js",
|
|
15
|
+
"uninstall-skill.js",
|
|
16
|
+
".claude-skill.json",
|
|
17
|
+
"utils.js"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"claude-code",
|
|
21
|
+
"skill",
|
|
22
|
+
"weekly-report",
|
|
23
|
+
"git",
|
|
24
|
+
"commit-analyzer",
|
|
25
|
+
"automation"
|
|
26
|
+
],
|
|
27
|
+
"author": "adonis",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Adonis0123/agent-skill-npm-boilerplate.git",
|
|
32
|
+
"directory": "packages/weekly-report"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/Adonis0123/agent-skill-npm-boilerplate/tree/main/packages/weekly-report"
|
|
35
|
+
}
|