@boses/skillink 1.0.5 → 1.1.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/README.md +48 -43
- package/README.zh-CN.md +111 -0
- package/dist/bin/skillink.js +1 -1
- package/dist/{chunk-WG3NDRGG.js → chunk-2UL2JQ4R.js} +266 -74
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +4 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,51 +1,50 @@
|
|
|
1
1
|
# Skillink 🚀
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Skillink** is a skill linker for AI tools.
|
|
6
|
+
Write skills in one place (`.agents/skills`) and sync to multiple tool directories with symlinks/junctions.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
> Core idea: **Write once, use everywhere.**
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
- **🔗 零克隆开销**:采用符号链接技术,目标目录的文件只是源文件的引用。修改源文件,AI 工具立即感知,无需等待同步。
|
|
11
|
-
- **🛠️ 极致 DX**:
|
|
12
|
-
- **交互式初始化**:一键引导配置。
|
|
13
|
-
- **自动探测与创建**:自动管理 AI 工具的配置目录。
|
|
14
|
-
- **实时监视**:支持 `--watch` 模式,动态响应技能的增删。
|
|
15
|
-
- **🛡️ 安全可靠**:仅操作符号链接,不轻易改动或删除用户的原始文件。
|
|
10
|
+
## Features
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
- Minimal architecture with Node.js 20+ and TypeScript
|
|
13
|
+
- Symlink-based sync (no copy, instant effect)
|
|
14
|
+
- Interactive `init` flow
|
|
15
|
+
- `sync --watch` for real-time skill folder changes
|
|
16
|
+
- Safe clean behavior (only removes links under source boundary)
|
|
17
|
+
- CLI localization via config (`en` / `zh-CN`)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Install as a dev dependency:
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
|
-
#
|
|
24
|
+
# pnpm
|
|
23
25
|
pnpm add -D @boses/skillink
|
|
24
26
|
|
|
25
|
-
#
|
|
27
|
+
# npm
|
|
26
28
|
npm install -D @boses/skillink
|
|
27
29
|
|
|
28
|
-
#
|
|
30
|
+
# yarn
|
|
29
31
|
yarn add -D @boses/skillink
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
### 1. 初始化项目
|
|
34
|
+
## Quick Start
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
### 1) Initialize
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
npx skillink init
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
- 创建 `.agents/skills` 目录并添加一个示例技能。
|
|
44
|
-
- 生成 `skillink.config.ts` 配置文件。
|
|
42
|
+
The first step in `init` asks language (`English / 简体中文`), then it creates:
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
- `.agents/skills` (with an example skill)
|
|
45
|
+
- `skillink.config.ts`
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
### 2) Write skills
|
|
49
48
|
|
|
50
49
|
```text
|
|
51
50
|
.agents/skills/
|
|
@@ -53,54 +52,60 @@ npx skillink init
|
|
|
53
52
|
└── SKILL.md
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
### 3
|
|
55
|
+
### 3) Sync
|
|
57
56
|
|
|
58
57
|
```bash
|
|
59
58
|
npx skillink sync
|
|
60
59
|
```
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
Watch mode:
|
|
63
62
|
|
|
64
63
|
```bash
|
|
65
64
|
npx skillink sync --watch
|
|
66
65
|
```
|
|
67
66
|
|
|
68
|
-
##
|
|
67
|
+
## Commands
|
|
69
68
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
| `init`
|
|
73
|
-
| `sync`
|
|
74
|
-
| `status` |
|
|
75
|
-
| `clean`
|
|
76
|
-
| `check`
|
|
69
|
+
| Command | Description |
|
|
70
|
+
| :------- | :----------------------------------------------------------------------------------- |
|
|
71
|
+
| `init` | Initialize project and create config. |
|
|
72
|
+
| `sync` | Sync skills to all enabled targets (`--watch` supported). |
|
|
73
|
+
| `status` | Show detailed sync status. |
|
|
74
|
+
| `clean` | Remove generated symlinks from configured targets. |
|
|
75
|
+
| `check` | Check updates by semantic versions from npm `versions` (no `latest` tag dependency). |
|
|
77
76
|
|
|
78
|
-
##
|
|
77
|
+
## Configuration (`skillink.config.ts`)
|
|
79
78
|
|
|
80
79
|
```typescript
|
|
81
80
|
import { defineConfig } from '@boses/skillink';
|
|
82
81
|
|
|
83
82
|
export default defineConfig({
|
|
84
|
-
//
|
|
83
|
+
// CLI locale: 'en' | 'zh-CN' (default: 'en')
|
|
84
|
+
locale: 'en',
|
|
85
|
+
// Skills source directory
|
|
85
86
|
source: '.agents/skills',
|
|
86
|
-
//
|
|
87
|
+
// Sync targets
|
|
87
88
|
targets: [
|
|
88
89
|
{
|
|
89
90
|
name: 'cursor',
|
|
90
91
|
path: '.cursor/rules',
|
|
91
|
-
// 是否启用该目标(默认为 true)
|
|
92
|
-
// 设置为 false 后,sync 和 status 命令将忽略此目标
|
|
93
92
|
enabled: true,
|
|
94
93
|
},
|
|
95
94
|
{
|
|
96
95
|
name: 'gemini',
|
|
97
|
-
path: '.gemini/
|
|
96
|
+
path: '.gemini/skills',
|
|
98
97
|
enabled: true,
|
|
99
|
-
}
|
|
98
|
+
},
|
|
100
99
|
],
|
|
101
100
|
});
|
|
102
101
|
```
|
|
103
102
|
|
|
104
|
-
##
|
|
103
|
+
## Git Recommendation
|
|
104
|
+
|
|
105
|
+
- Commit: `skillink.config.ts`, `.agents/skills/**`
|
|
106
|
+
- Avoid committing generated link targets (for example: `.cursor/rules`, `.gemini/skills`)
|
|
107
|
+
- `init` will remind you to add target directories to `.gitignore`
|
|
108
|
+
|
|
109
|
+
## License
|
|
105
110
|
|
|
106
|
-
MIT
|
|
111
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Skillink 🚀
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
**Skillink** 是一个 AI Skills 链接工具。
|
|
6
|
+
你可以在统一目录(`.agents/skills`)编写技能,并通过符号链接(Symlink/Junction)同步到多个 AI 工具目录。
|
|
7
|
+
|
|
8
|
+
> 核心理念:**一次编写,处处生效。**
|
|
9
|
+
|
|
10
|
+
## 特性
|
|
11
|
+
|
|
12
|
+
- Node.js 20+ + TypeScript 的简洁架构
|
|
13
|
+
- 基于符号链接同步,零拷贝、即时生效
|
|
14
|
+
- 交互式 `init` 初始化流程
|
|
15
|
+
- `sync --watch` 支持实时监听技能目录变化
|
|
16
|
+
- 安全清理策略(仅清理位于 source 边界内的链接)
|
|
17
|
+
- 支持 CLI 国际化输出(`en` / `zh-CN`)
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
推荐安装为开发依赖:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# pnpm
|
|
25
|
+
pnpm add -D @boses/skillink
|
|
26
|
+
|
|
27
|
+
# npm
|
|
28
|
+
npm install -D @boses/skillink
|
|
29
|
+
|
|
30
|
+
# yarn
|
|
31
|
+
yarn add -D @boses/skillink
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 快速开始
|
|
35
|
+
|
|
36
|
+
### 1)初始化
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx skillink init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`init` 第一步会先询问语言(`English / 简体中文`),然后自动创建:
|
|
43
|
+
|
|
44
|
+
- `.agents/skills`(包含示例技能)
|
|
45
|
+
- `skillink.config.ts`
|
|
46
|
+
|
|
47
|
+
### 2)编写技能
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
.agents/skills/
|
|
51
|
+
└── react-expert/
|
|
52
|
+
└── SKILL.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3)同步
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx skillink sync
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
监听模式:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx skillink sync --watch
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 命令
|
|
68
|
+
|
|
69
|
+
| 命令 | 说明 |
|
|
70
|
+
| :------- | :----------------------------------------------------------------- |
|
|
71
|
+
| `init` | 初始化项目并生成配置。 |
|
|
72
|
+
| `sync` | 同步技能到所有启用目标(支持 `--watch`)。 |
|
|
73
|
+
| `status` | 显示详细同步状态。 |
|
|
74
|
+
| `clean` | 清理配置目标中的已生成符号链接。 |
|
|
75
|
+
| `check` | 基于 npm `versions` 的语义化版本检查更新(不依赖 `latest` 标签)。 |
|
|
76
|
+
|
|
77
|
+
## 配置说明(`skillink.config.ts`)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { defineConfig } from '@boses/skillink';
|
|
81
|
+
|
|
82
|
+
export default defineConfig({
|
|
83
|
+
// CLI 语言: 'en' | 'zh-CN'(默认: 'en')
|
|
84
|
+
locale: 'en',
|
|
85
|
+
// 技能源目录
|
|
86
|
+
source: '.agents/skills',
|
|
87
|
+
// 同步目标
|
|
88
|
+
targets: [
|
|
89
|
+
{
|
|
90
|
+
name: 'cursor',
|
|
91
|
+
path: '.cursor/rules',
|
|
92
|
+
enabled: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'gemini',
|
|
96
|
+
path: '.gemini/skills',
|
|
97
|
+
enabled: true,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Git 建议
|
|
104
|
+
|
|
105
|
+
- 推荐提交:`skillink.config.ts`、`.agents/skills/**`
|
|
106
|
+
- 不建议提交:链接产物目录(如 `.cursor/rules`、`.gemini/skills`)
|
|
107
|
+
- `init` 完成后会提示将目标目录加入 `.gitignore`
|
|
108
|
+
|
|
109
|
+
## 许可证
|
|
110
|
+
|
|
111
|
+
MIT
|
package/dist/bin/skillink.js
CHANGED
|
@@ -9,7 +9,7 @@ var require_package = __commonJS({
|
|
|
9
9
|
"package.json"(exports, module) {
|
|
10
10
|
module.exports = {
|
|
11
11
|
name: "@boses/skillink",
|
|
12
|
-
version: "1.0
|
|
12
|
+
version: "1.1.0",
|
|
13
13
|
description: "\u7EDF\u4E00 AI Skills \u7BA1\u7406\u5DE5\u5177 - \u50CF pnpm \u4E00\u6837\u94FE\u63A5\u5230\u5404 AI \u5DE5\u5177\u76EE\u5F55",
|
|
14
14
|
type: "module",
|
|
15
15
|
main: "dist/index.js",
|
|
@@ -35,7 +35,8 @@ var require_package = __commonJS({
|
|
|
35
35
|
start: "node dist/bin/skillink.js",
|
|
36
36
|
lint: "tsc --noEmit",
|
|
37
37
|
check: "eslint",
|
|
38
|
-
|
|
38
|
+
test: "vitest run",
|
|
39
|
+
format: "prettier --write ."
|
|
39
40
|
},
|
|
40
41
|
keywords: [
|
|
41
42
|
"ai",
|
|
@@ -71,7 +72,8 @@ var require_package = __commonJS({
|
|
|
71
72
|
prettier: "^3.8.1",
|
|
72
73
|
tsup: "^8.5.1",
|
|
73
74
|
typescript: "^5.9.3",
|
|
74
|
-
"typescript-eslint": "^8.54.0"
|
|
75
|
+
"typescript-eslint": "^8.54.0",
|
|
76
|
+
vitest: "^4.0.18"
|
|
75
77
|
},
|
|
76
78
|
engines: {
|
|
77
79
|
node: ">=20.0.0"
|
|
@@ -87,7 +89,7 @@ import { cac } from "cac";
|
|
|
87
89
|
import path from "path";
|
|
88
90
|
import fs2 from "fs/promises";
|
|
89
91
|
import { existsSync as existsSync2 } from "fs";
|
|
90
|
-
import { checkbox, confirm } from "@inquirer/prompts";
|
|
92
|
+
import { checkbox, confirm, select } from "@inquirer/prompts";
|
|
91
93
|
|
|
92
94
|
// src/utils/fs.ts
|
|
93
95
|
import fs from "fs/promises";
|
|
@@ -105,18 +107,40 @@ function isSymlink(p) {
|
|
|
105
107
|
}
|
|
106
108
|
async function createSymlink(target, path5) {
|
|
107
109
|
const type = process.platform === "win32" ? "junction" : "dir";
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
try {
|
|
111
|
+
const stats = await fs.lstat(path5);
|
|
112
|
+
if (stats.isSymbolicLink()) {
|
|
110
113
|
await fs.unlink(path5);
|
|
111
114
|
} else {
|
|
112
115
|
throw new Error(`\u8DEF\u5F84 ${path5} \u5DF2\u5B58\u5728\u4E14\u4E0D\u662F\u7B26\u53F7\u94FE\u63A5\uFF0C\u8BF7\u624B\u52A8\u6E05\u7406\u3002`);
|
|
113
116
|
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error.code !== "ENOENT") {
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
114
121
|
}
|
|
115
122
|
await fs.symlink(target, path5, type);
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
// src/utils/locale.ts
|
|
126
|
+
function resolveLocale(locale) {
|
|
127
|
+
return locale === "zh-CN" ? "zh-CN" : "en";
|
|
128
|
+
}
|
|
129
|
+
function isChineseLocale(locale) {
|
|
130
|
+
return locale === "zh-CN";
|
|
131
|
+
}
|
|
132
|
+
|
|
118
133
|
// src/commands/init.ts
|
|
119
|
-
var
|
|
134
|
+
var TEMPLATE_SKILL_EN = `---
|
|
135
|
+
name: Example Skill
|
|
136
|
+
description: This is an example skill generated by Skillink.
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
# Usage
|
|
140
|
+
|
|
141
|
+
Enable this skill to use it.
|
|
142
|
+
`;
|
|
143
|
+
var TEMPLATE_SKILL_ZH = `---
|
|
120
144
|
name: \u793A\u4F8B\u6280\u80FD
|
|
121
145
|
description: \u8FD9\u662F\u4E00\u4E2A\u7531 Skillink \u751F\u6210\u7684\u6F14\u793A\u6280\u80FD\u3002
|
|
122
146
|
---
|
|
@@ -129,47 +153,88 @@ var DEFAULT_TARGETS = [
|
|
|
129
153
|
{ name: "Cursor", value: "cursor", path: ".cursor/rules" },
|
|
130
154
|
{ name: "Windsurf", value: "windsurf", path: ".windsurf/rules" },
|
|
131
155
|
{ name: "VSCode", value: "vscode", path: ".vscode/skills" },
|
|
132
|
-
{ name: "Gemini", value: "gemini", path: ".gemini/
|
|
156
|
+
{ name: "Gemini", value: "gemini", path: ".gemini/skills" }
|
|
133
157
|
];
|
|
158
|
+
function getInitText(locale) {
|
|
159
|
+
if (isChineseLocale(locale)) {
|
|
160
|
+
return {
|
|
161
|
+
title: "\u2728 Skillink \u521D\u59CB\u5316",
|
|
162
|
+
createSkillsDir: (skillsDir) => `\u662F\u5426\u5728 ${skillsDir} \u521B\u5EFA\u6280\u80FD\u76EE\u5F55\uFF1F`,
|
|
163
|
+
exampleSkillCreated: "\u2705 \u5DF2\u521B\u5EFA\u793A\u4F8B\u6280\u80FD\u3002",
|
|
164
|
+
skillsDirExists: "\u2139\uFE0F \u6280\u80FD\u76EE\u5F55\u5DF2\u5B58\u5728\u3002",
|
|
165
|
+
selectTargets: "\u9009\u62E9\u8981\u540C\u6B65\u7684 AI \u5DE5\u5177\uFF1A",
|
|
166
|
+
noTargets: "\u26A0\uFE0F \u672A\u9009\u62E9\u4EFB\u4F55\u76EE\u6807\u3002\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684\u76EE\u6807\u5217\u8868\u5C06\u4E3A\u7A7A\u3002",
|
|
167
|
+
overwriteConfig: "\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u76D6\uFF1F",
|
|
168
|
+
initCancelled: "\u274C \u521D\u59CB\u5316\u5DF2\u53D6\u6D88\u3002",
|
|
169
|
+
configCreated: "\u2705 \u5DF2\u521B\u5EFA skillink.config.ts",
|
|
170
|
+
gitAdvice: (paths) => `\u{1F4A1} Git \u5EFA\u8BAE\uFF1A\u8BF7\u5C06\u76EE\u6807\u76EE\u5F55\uFF08${paths}\uFF09\u52A0\u5165 .gitignore\uFF0C\u53EA\u63D0\u4EA4 .agents/skills \u4E0E\u914D\u7F6E\u6587\u4EF6\u3002`,
|
|
171
|
+
nextStep: '\n\u{1F449} \u8FD0\u884C "npx skillink sync" \u5F00\u59CB\u540C\u6B65\uFF01'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
title: "\u2728 Skillink Initialization",
|
|
176
|
+
createSkillsDir: (skillsDir) => `Create skills directory at ${skillsDir}?`,
|
|
177
|
+
exampleSkillCreated: "\u2705 Example skill created.",
|
|
178
|
+
skillsDirExists: "\u2139\uFE0F Skills directory already exists.",
|
|
179
|
+
selectTargets: "Select AI tools to sync:",
|
|
180
|
+
noTargets: "\u26A0\uFE0F No targets selected. The config will contain an empty targets list.",
|
|
181
|
+
overwriteConfig: "Configuration file already exists. Overwrite?",
|
|
182
|
+
initCancelled: "\u274C Initialization cancelled.",
|
|
183
|
+
configCreated: "\u2705 Created skillink.config.ts",
|
|
184
|
+
gitAdvice: (paths) => `\u{1F4A1} Git tip: Add target directories (${paths}) to .gitignore. Commit only .agents/skills and config files.`,
|
|
185
|
+
nextStep: '\n\u{1F449} Run "npx skillink sync" to start syncing!'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
134
188
|
async function initCommand(cwd = process.cwd()) {
|
|
135
|
-
|
|
189
|
+
const locale = await select({
|
|
190
|
+
message: "Select language / \u9009\u62E9\u8BED\u8A00",
|
|
191
|
+
choices: [
|
|
192
|
+
{ name: "English", value: "en" },
|
|
193
|
+
{ name: "\u7B80\u4F53\u4E2D\u6587", value: "zh-CN" }
|
|
194
|
+
],
|
|
195
|
+
default: "en"
|
|
196
|
+
});
|
|
197
|
+
const text = getInitText(locale);
|
|
198
|
+
const templateSkill = isChineseLocale(locale) ? TEMPLATE_SKILL_ZH : TEMPLATE_SKILL_EN;
|
|
199
|
+
console.log(text.title);
|
|
136
200
|
const skillsDir = path.join(cwd, ".agents", "skills");
|
|
137
201
|
const configFile = path.join(cwd, "skillink.config.ts");
|
|
138
202
|
if (!existsSync2(skillsDir)) {
|
|
139
203
|
const create = await confirm({
|
|
140
|
-
message:
|
|
204
|
+
message: text.createSkillsDir(skillsDir),
|
|
141
205
|
default: true
|
|
142
206
|
});
|
|
143
207
|
if (create) {
|
|
144
208
|
await ensureDir(skillsDir);
|
|
145
209
|
const exampleDir = path.join(skillsDir, "example-skill");
|
|
146
210
|
await ensureDir(exampleDir);
|
|
147
|
-
await fs2.writeFile(path.join(exampleDir, "SKILL.md"),
|
|
148
|
-
console.log(
|
|
211
|
+
await fs2.writeFile(path.join(exampleDir, "SKILL.md"), templateSkill);
|
|
212
|
+
console.log(text.exampleSkillCreated);
|
|
149
213
|
}
|
|
150
214
|
} else {
|
|
151
|
-
console.log(
|
|
215
|
+
console.log(text.skillsDirExists);
|
|
152
216
|
}
|
|
153
217
|
const selectedTargets = await checkbox({
|
|
154
|
-
message:
|
|
218
|
+
message: text.selectTargets,
|
|
155
219
|
choices: DEFAULT_TARGETS.map((t) => ({ name: t.name, value: t }))
|
|
156
220
|
});
|
|
157
221
|
if (selectedTargets.length === 0) {
|
|
158
|
-
console.log(
|
|
222
|
+
console.log(text.noTargets);
|
|
159
223
|
}
|
|
160
224
|
if (existsSync2(configFile)) {
|
|
161
225
|
const overwrite = await confirm({
|
|
162
|
-
message:
|
|
226
|
+
message: text.overwriteConfig,
|
|
163
227
|
default: false
|
|
164
228
|
});
|
|
165
229
|
if (!overwrite) {
|
|
166
|
-
console.log(
|
|
230
|
+
console.log(text.initCancelled);
|
|
167
231
|
return;
|
|
168
232
|
}
|
|
169
233
|
}
|
|
170
234
|
const configContent = `import { defineConfig } from '@boses/skillink';
|
|
171
235
|
|
|
172
236
|
export default defineConfig({
|
|
237
|
+
locale: '${locale}',
|
|
173
238
|
source: '.agents/skills',
|
|
174
239
|
targets: [
|
|
175
240
|
${selectedTargets.map(
|
|
@@ -183,8 +248,12 @@ ${selectedTargets.map(
|
|
|
183
248
|
});
|
|
184
249
|
`;
|
|
185
250
|
await fs2.writeFile(configFile, configContent);
|
|
186
|
-
console.log(
|
|
187
|
-
|
|
251
|
+
console.log(text.configCreated);
|
|
252
|
+
if (selectedTargets.length > 0) {
|
|
253
|
+
const targetPaths = selectedTargets.map((t) => t.path).join(", ");
|
|
254
|
+
console.log(text.gitAdvice(targetPaths));
|
|
255
|
+
}
|
|
256
|
+
console.log(text.nextStep);
|
|
188
257
|
}
|
|
189
258
|
|
|
190
259
|
// src/commands/sync.ts
|
|
@@ -334,7 +403,11 @@ var Linker = class {
|
|
|
334
403
|
* 清理所有由 Skillink 创建的符号链接
|
|
335
404
|
*/
|
|
336
405
|
async cleanAll() {
|
|
337
|
-
const targets = this.config.targets
|
|
406
|
+
const targets = this.config.targets;
|
|
407
|
+
const absSourceDir = path2.resolve(
|
|
408
|
+
this.root,
|
|
409
|
+
this.config.source || ".agents/skills"
|
|
410
|
+
);
|
|
338
411
|
for (const target of targets) {
|
|
339
412
|
const targetDir = path2.resolve(this.root, target.path);
|
|
340
413
|
if (!existsSync3(targetDir)) continue;
|
|
@@ -345,11 +418,8 @@ var Linker = class {
|
|
|
345
418
|
try {
|
|
346
419
|
const linkTarget = await fs3.readlink(itemPath);
|
|
347
420
|
const absLinkTarget = path2.resolve(targetDir, linkTarget);
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
this.config.source || ".agents/skills"
|
|
351
|
-
);
|
|
352
|
-
if (absLinkTarget.startsWith(absSourceDir)) {
|
|
421
|
+
const relative = path2.relative(absSourceDir, absLinkTarget);
|
|
422
|
+
if (relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative)) {
|
|
353
423
|
await fs3.unlink(itemPath);
|
|
354
424
|
console.log(`\u5DF2\u4ECE ${target.name} \u79FB\u9664 ${item.name}`);
|
|
355
425
|
}
|
|
@@ -372,13 +442,23 @@ var Linker = class {
|
|
|
372
442
|
import pc from "picocolors";
|
|
373
443
|
async function syncCommand(options) {
|
|
374
444
|
const cwd = options.cwd || process.cwd();
|
|
445
|
+
const fallbackLocale = resolveLocale();
|
|
446
|
+
const fallbackChinese = isChineseLocale(fallbackLocale);
|
|
375
447
|
const config = await loadConfig(cwd);
|
|
376
448
|
if (!config) {
|
|
377
|
-
console.error(
|
|
449
|
+
console.error(
|
|
450
|
+
pc.red(
|
|
451
|
+
fallbackChinese ? '\u274C \u672A\u627E\u5230\u914D\u7F6E\u3002\u8BF7\u5148\u8FD0\u884C "skillink init"\u3002' : '\u274C Configuration not found. Run "skillink init" first.'
|
|
452
|
+
)
|
|
453
|
+
);
|
|
378
454
|
process.exit(1);
|
|
379
455
|
}
|
|
456
|
+
const locale = resolveLocale(config.locale);
|
|
457
|
+
const isChinese = isChineseLocale(locale);
|
|
380
458
|
const linker = new Linker(cwd, config);
|
|
381
|
-
console.log(
|
|
459
|
+
console.log(
|
|
460
|
+
pc.cyan(isChinese ? "\u{1F504} \u6B63\u5728\u540C\u6B65\u6280\u80FD..." : "\u{1F504} Syncing skills...")
|
|
461
|
+
);
|
|
382
462
|
const results = await linker.sync();
|
|
383
463
|
let changes = 0;
|
|
384
464
|
results.forEach((r) => {
|
|
@@ -388,33 +468,67 @@ async function syncCommand(options) {
|
|
|
388
468
|
);
|
|
389
469
|
changes++;
|
|
390
470
|
} else if (r.status === "failed") {
|
|
391
|
-
console.error(
|
|
471
|
+
console.error(
|
|
472
|
+
pc.red(
|
|
473
|
+
isChinese ? `\u274C ${r.skill} -> ${r.target}: ${r.message}` : `\u274C ${r.skill} -> ${r.target}: ${r.message}`
|
|
474
|
+
)
|
|
475
|
+
);
|
|
392
476
|
}
|
|
393
477
|
});
|
|
394
478
|
if (changes === 0) {
|
|
395
|
-
console.log(
|
|
479
|
+
console.log(
|
|
480
|
+
pc.gray(
|
|
481
|
+
isChinese ? "\u65E0\u9700\u66F4\u6539\u3002\u6240\u6709\u6280\u80FD\u5DF2\u540C\u6B65\u3002" : "No changes needed. All skills are already synced."
|
|
482
|
+
)
|
|
483
|
+
);
|
|
396
484
|
} else {
|
|
397
|
-
console.log(
|
|
485
|
+
console.log(
|
|
486
|
+
pc.green(
|
|
487
|
+
isChinese ? `\u2705 \u5DF2\u540C\u6B65 ${changes} \u5904\u53D8\u66F4\u3002` : `\u2705 Synced ${changes} change(s).`
|
|
488
|
+
)
|
|
489
|
+
);
|
|
398
490
|
}
|
|
399
491
|
if (options.watch) {
|
|
400
|
-
console.log(
|
|
492
|
+
console.log(
|
|
493
|
+
pc.cyan(
|
|
494
|
+
isChinese ? "\n\u{1F440} \u6B63\u5728\u76D1\u89C6\u53D8\u66F4... \u6309 Ctrl+C \u505C\u6B62\u3002" : "\n\u{1F440} Watching for changes... Press Ctrl+C to stop."
|
|
495
|
+
)
|
|
496
|
+
);
|
|
401
497
|
const sourceDir = path3.resolve(cwd, config.source || ".agents/skills");
|
|
402
498
|
const watcher = chokidar.watch(sourceDir, {
|
|
403
499
|
ignoreInitial: true,
|
|
404
|
-
depth:
|
|
500
|
+
depth: 1,
|
|
405
501
|
awaitWriteFinish: {
|
|
406
502
|
stabilityThreshold: 100,
|
|
407
503
|
pollInterval: 100
|
|
408
504
|
}
|
|
409
505
|
});
|
|
410
506
|
watcher.on("all", async (event, filePath) => {
|
|
507
|
+
if (path3.dirname(filePath) !== sourceDir) return;
|
|
411
508
|
const fileName = path3.basename(filePath);
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
509
|
+
if (!fileName || fileName.startsWith(".")) return;
|
|
510
|
+
try {
|
|
511
|
+
if (event === "addDir") {
|
|
512
|
+
console.log(
|
|
513
|
+
pc.green(
|
|
514
|
+
isChinese ? `+ \u68C0\u6D4B\u5230\u65B0\u6280\u80FD: ${fileName}` : `+ New skill detected: ${fileName}`
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
await linker.syncSkillToAll(fileName);
|
|
518
|
+
} else if (event === "unlinkDir") {
|
|
519
|
+
console.log(
|
|
520
|
+
pc.red(
|
|
521
|
+
isChinese ? `- \u6280\u80FD\u5DF2\u79FB\u9664: ${fileName}` : `- Skill removed: ${fileName}`
|
|
522
|
+
)
|
|
523
|
+
);
|
|
524
|
+
await linker.removeSkillFromAll(fileName);
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
console.error(
|
|
528
|
+
pc.red(
|
|
529
|
+
isChinese ? `\u274C \u5904\u7406\u76D1\u89C6\u4E8B\u4EF6\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}` : `\u274C Failed to process watch event: ${error instanceof Error ? error.message : String(error)}`
|
|
530
|
+
)
|
|
531
|
+
);
|
|
418
532
|
}
|
|
419
533
|
});
|
|
420
534
|
}
|
|
@@ -440,56 +554,110 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
440
554
|
async function statusCommand(options) {
|
|
441
555
|
const cwd = options.cwd || process.cwd();
|
|
442
556
|
const config = await loadConfig(cwd);
|
|
557
|
+
const locale = resolveLocale(config?.locale);
|
|
558
|
+
const isChinese = isChineseLocale(locale);
|
|
443
559
|
if (!config) {
|
|
444
|
-
logger.error(
|
|
560
|
+
logger.error(
|
|
561
|
+
isChinese ? '\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\u3002\u8BF7\u5148\u8FD0\u884C "skillink init"\u3002' : 'Configuration file not found. Run "skillink init" first.'
|
|
562
|
+
);
|
|
445
563
|
return;
|
|
446
564
|
}
|
|
447
|
-
logger.title("Skillink \u540C\u6B65\u72B6\u6001");
|
|
565
|
+
logger.title(isChinese ? "Skillink \u540C\u6B65\u72B6\u6001" : "Skillink Sync Status");
|
|
448
566
|
logger.newline();
|
|
449
567
|
const sourcePath = path4.resolve(cwd, config.source || ".agents/skills");
|
|
450
|
-
logger.info(
|
|
568
|
+
logger.info(
|
|
569
|
+
isChinese ? `\u6E90\u76EE\u5F55: ${sourcePath}` : `Source directory: ${sourcePath}`
|
|
570
|
+
);
|
|
451
571
|
if (!existsSync4(sourcePath)) {
|
|
452
|
-
logger.error(
|
|
572
|
+
logger.error(
|
|
573
|
+
isChinese ? "\u6E90\u76EE\u5F55\u4E0D\u5B58\u5728\uFF01" : "Source directory does not exist!"
|
|
574
|
+
);
|
|
453
575
|
return;
|
|
454
576
|
}
|
|
455
577
|
const skills = await fs4.readdir(sourcePath, { withFileTypes: true });
|
|
456
578
|
const validSkills = skills.filter((s) => s.isDirectory() && !s.name.startsWith(".")).map((s) => s.name);
|
|
457
|
-
logger.gray(
|
|
579
|
+
logger.gray(
|
|
580
|
+
isChinese ? `\u627E\u5230 ${validSkills.length} \u4E2A\u6280\u80FD\u3002` : `Found ${validSkills.length} skill(s).`
|
|
581
|
+
);
|
|
458
582
|
logger.newline();
|
|
459
|
-
logger.info("\u76EE\u6807\u5DE5\u5177:");
|
|
583
|
+
logger.info(isChinese ? "\u76EE\u6807\u5DE5\u5177:" : "Targets:");
|
|
460
584
|
for (const target of config.targets) {
|
|
461
585
|
if (target.enabled === false) continue;
|
|
462
586
|
const targetDir = path4.resolve(cwd, target.path);
|
|
463
587
|
console.log(`${pc3.bold(target.name)} [${targetDir}]`);
|
|
464
588
|
if (!existsSync4(targetDir)) {
|
|
465
|
-
console.log(
|
|
589
|
+
console.log(
|
|
590
|
+
pc3.red(
|
|
591
|
+
isChinese ? " - \u76EE\u5F55\u7F3A\u5931\uFF08\u8FD0\u884C sync \u547D\u4EE4\u4EE5\u521B\u5EFA\uFF09" : " - Missing directory (run sync to create it)"
|
|
592
|
+
)
|
|
593
|
+
);
|
|
466
594
|
continue;
|
|
467
595
|
}
|
|
468
596
|
let syncedCount = 0;
|
|
469
597
|
let missingCount = 0;
|
|
470
598
|
let brokenCount = 0;
|
|
599
|
+
let mismatchedCount = 0;
|
|
600
|
+
let occupiedCount = 0;
|
|
471
601
|
for (const skill of validSkills) {
|
|
472
602
|
const linkPath = path4.join(targetDir, skill);
|
|
473
|
-
|
|
603
|
+
const sourceSkillPath = path4.resolve(sourcePath, skill);
|
|
604
|
+
try {
|
|
605
|
+
const stats = await fs4.lstat(linkPath);
|
|
606
|
+
if (!stats.isSymbolicLink()) {
|
|
607
|
+
occupiedCount++;
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
474
610
|
try {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
611
|
+
const currentTarget = await fs4.readlink(linkPath);
|
|
612
|
+
const absCurrentTarget = path4.resolve(targetDir, currentTarget);
|
|
613
|
+
if (absCurrentTarget === sourceSkillPath) {
|
|
614
|
+
syncedCount++;
|
|
478
615
|
} else {
|
|
479
|
-
|
|
616
|
+
mismatchedCount++;
|
|
480
617
|
}
|
|
481
618
|
} catch {
|
|
619
|
+
brokenCount++;
|
|
620
|
+
}
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (error.code === "ENOENT") {
|
|
482
623
|
missingCount++;
|
|
624
|
+
continue;
|
|
483
625
|
}
|
|
484
|
-
|
|
485
|
-
syncedCount++;
|
|
626
|
+
missingCount++;
|
|
486
627
|
}
|
|
487
628
|
}
|
|
488
|
-
if (missingCount > 0)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
629
|
+
if (missingCount > 0)
|
|
630
|
+
console.log(
|
|
631
|
+
pc3.yellow(
|
|
632
|
+
isChinese ? ` - ${missingCount} \u4E2A\u7F3A\u5931` : ` - ${missingCount} missing`
|
|
633
|
+
)
|
|
634
|
+
);
|
|
635
|
+
if (brokenCount > 0)
|
|
636
|
+
console.log(
|
|
637
|
+
pc3.red(
|
|
638
|
+
isChinese ? ` - ${brokenCount} \u4E2A\u5931\u6548\u94FE\u63A5` : ` - ${brokenCount} broken link(s)`
|
|
639
|
+
)
|
|
640
|
+
);
|
|
641
|
+
if (mismatchedCount > 0)
|
|
642
|
+
console.log(
|
|
643
|
+
pc3.red(
|
|
644
|
+
isChinese ? ` - ${mismatchedCount} \u4E2A\u9519\u8BEF\u6307\u5411\u94FE\u63A5` : ` - ${mismatchedCount} mismatched link(s)`
|
|
645
|
+
)
|
|
646
|
+
);
|
|
647
|
+
if (occupiedCount > 0)
|
|
648
|
+
console.log(
|
|
649
|
+
pc3.yellow(
|
|
650
|
+
isChinese ? ` - ${occupiedCount} \u4E2A\u540C\u540D\u5360\u4F4D\uFF08\u975E\u94FE\u63A5\uFF09` : ` - ${occupiedCount} occupied by non-link`
|
|
651
|
+
)
|
|
652
|
+
);
|
|
653
|
+
if (syncedCount > 0)
|
|
654
|
+
console.log(
|
|
655
|
+
pc3.green(
|
|
656
|
+
isChinese ? ` - ${syncedCount} \u4E2A\u5DF2\u540C\u6B65` : ` - ${syncedCount} synced`
|
|
657
|
+
)
|
|
658
|
+
);
|
|
659
|
+
if (missingCount === 0 && brokenCount === 0 && mismatchedCount === 0 && occupiedCount === 0)
|
|
660
|
+
console.log(pc3.green(isChinese ? " \u72B6\u6001\u826F\u597D\uFF01" : " Healthy!"));
|
|
493
661
|
console.log("");
|
|
494
662
|
}
|
|
495
663
|
}
|
|
@@ -499,19 +667,21 @@ import { confirm as confirm2 } from "@inquirer/prompts";
|
|
|
499
667
|
async function cleanCommand(options = {}) {
|
|
500
668
|
const cwd = options.cwd || process.cwd();
|
|
501
669
|
const config = await loadConfig(cwd);
|
|
670
|
+
const locale = resolveLocale(config?.locale);
|
|
671
|
+
const isChinese = isChineseLocale(locale);
|
|
502
672
|
if (!config) {
|
|
503
|
-
logger.error("\u672A\u627E\u5230\u914D\u7F6E\u3002");
|
|
673
|
+
logger.error(isChinese ? "\u672A\u627E\u5230\u914D\u7F6E\u3002" : "Configuration not found.");
|
|
504
674
|
return;
|
|
505
675
|
}
|
|
506
676
|
const answer = await confirm2({
|
|
507
|
-
message: "\u786E\u5B9A\u8981\u79FB\u9664\u6240\u6709\u5DF2\u540C\u6B65\u7684\u6280\u80FD\u94FE\u63A5\u5417\uFF1F",
|
|
677
|
+
message: isChinese ? "\u786E\u5B9A\u8981\u79FB\u9664\u6240\u6709\u5DF2\u540C\u6B65\u7684\u6280\u80FD\u94FE\u63A5\u5417\uFF1F" : "Remove all synced skill links?",
|
|
508
678
|
default: false
|
|
509
679
|
});
|
|
510
680
|
if (!answer) return;
|
|
511
681
|
const linker = new Linker(cwd, config);
|
|
512
|
-
logger.info("\u6B63\u5728\u6E05\u7406\u94FE\u63A5...");
|
|
682
|
+
logger.info(isChinese ? "\u6B63\u5728\u6E05\u7406\u94FE\u63A5..." : "Cleaning links...");
|
|
513
683
|
await linker.cleanAll();
|
|
514
|
-
logger.success("\u6E05\u7406\u5B8C\u6210\u3002");
|
|
684
|
+
logger.success(isChinese ? "\u6E05\u7406\u5B8C\u6210\u3002" : "Clean completed.");
|
|
515
685
|
}
|
|
516
686
|
|
|
517
687
|
// src/commands/check.ts
|
|
@@ -522,11 +692,21 @@ import semver from "semver";
|
|
|
522
692
|
var pkg = require_package();
|
|
523
693
|
var currentVersion = pkg.version;
|
|
524
694
|
var pkgName = pkg.name;
|
|
695
|
+
function resolveLatestSemverVersion(versions) {
|
|
696
|
+
const validStableVersions = versions.filter(
|
|
697
|
+
(version) => semver.valid(version) && semver.prerelease(version) === null
|
|
698
|
+
);
|
|
699
|
+
const sorted = semver.rsort(validStableVersions);
|
|
700
|
+
if (sorted.length === 0) {
|
|
701
|
+
throw new Error("\u672A\u627E\u5230\u53EF\u7528\u7684\u7A33\u5B9A\u8BED\u4E49\u5316\u7248\u672C");
|
|
702
|
+
}
|
|
703
|
+
return sorted[0];
|
|
704
|
+
}
|
|
525
705
|
async function checkUpdate() {
|
|
526
706
|
const controller = new AbortController();
|
|
527
707
|
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
528
708
|
try {
|
|
529
|
-
const res = await fetch(`https://registry.npmjs.org/${pkg.name}
|
|
709
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg.name}`, {
|
|
530
710
|
signal: controller.signal
|
|
531
711
|
});
|
|
532
712
|
clearTimeout(timeoutId);
|
|
@@ -534,12 +714,14 @@ async function checkUpdate() {
|
|
|
534
714
|
throw new Error(`\u8BF7\u6C42\u5931\u8D25: ${res.status} ${res.statusText}`);
|
|
535
715
|
}
|
|
536
716
|
const data = await res.json();
|
|
537
|
-
const latestVersion =
|
|
717
|
+
const latestVersion = resolveLatestSemverVersion(
|
|
718
|
+
Object.keys(data.versions ?? {})
|
|
719
|
+
);
|
|
538
720
|
return {
|
|
539
721
|
latest: latestVersion,
|
|
540
722
|
current: currentVersion,
|
|
541
723
|
hasUpdate: semver.gt(latestVersion, currentVersion),
|
|
542
|
-
name:
|
|
724
|
+
name: pkgName
|
|
543
725
|
};
|
|
544
726
|
} catch (error) {
|
|
545
727
|
clearTimeout(timeoutId);
|
|
@@ -549,7 +731,12 @@ async function checkUpdate() {
|
|
|
549
731
|
|
|
550
732
|
// src/commands/check.ts
|
|
551
733
|
async function checkCommand() {
|
|
552
|
-
|
|
734
|
+
const config = await loadConfig();
|
|
735
|
+
const locale = resolveLocale(config?.locale);
|
|
736
|
+
const isChinese = isChineseLocale(locale);
|
|
737
|
+
logger.info(
|
|
738
|
+
isChinese ? "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\uFF08\u8BED\u4E49\u5316\u7248\u672C\u6BD4\u8F83\uFF09..." : "Checking updates (semantic version comparison)..."
|
|
739
|
+
);
|
|
553
740
|
try {
|
|
554
741
|
const info = await checkUpdate();
|
|
555
742
|
if (info.hasUpdate) {
|
|
@@ -562,7 +749,7 @@ async function checkCommand() {
|
|
|
562
749
|
);
|
|
563
750
|
console.log(
|
|
564
751
|
pc4.yellow(
|
|
565
|
-
` \u2502 \u53D1\u73B0\u65B0\u7248\u672C ${pc4.green(info.latest)} (\u5F53\u524D ${pc4.gray(info.current)}) \u2502`
|
|
752
|
+
isChinese ? ` \u2502 \u53D1\u73B0\u65B0\u7248\u672C ${pc4.green(info.latest)} (\u5F53\u524D ${pc4.gray(info.current)}) \u2502` : ` \u2502 New version ${pc4.green(info.latest)} (current ${pc4.gray(info.current)}) \u2502`
|
|
566
753
|
)
|
|
567
754
|
);
|
|
568
755
|
console.log(
|
|
@@ -570,7 +757,7 @@ async function checkCommand() {
|
|
|
570
757
|
);
|
|
571
758
|
console.log(
|
|
572
759
|
pc4.yellow(
|
|
573
|
-
` \u2502
|
|
760
|
+
isChinese ? ` \u2502 \u8BF7\u8FD0\u884C ${pc4.cyan(`npm install -D ${info.name}@${info.latest}`)} \u66F4\u65B0 \u2502` : ` \u2502 Run ${pc4.cyan(`npm install -D ${info.name}@${info.latest}`)} to update \u2502`
|
|
574
761
|
)
|
|
575
762
|
);
|
|
576
763
|
console.log(
|
|
@@ -581,11 +768,13 @@ async function checkCommand() {
|
|
|
581
768
|
);
|
|
582
769
|
console.log();
|
|
583
770
|
} else {
|
|
584
|
-
logger.success(
|
|
771
|
+
logger.success(
|
|
772
|
+
isChinese ? `\u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7A33\u5B9A\u7248\u672C (${pc4.green(info.current)})` : `You are on the latest stable version (${pc4.green(info.current)})`
|
|
773
|
+
);
|
|
585
774
|
}
|
|
586
775
|
} catch (error) {
|
|
587
776
|
logger.error(
|
|
588
|
-
`\u68C0\u67E5\u66F4\u65B0\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
|
|
777
|
+
isChinese ? `\u68C0\u67E5\u66F4\u65B0\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}` : `Update check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
589
778
|
);
|
|
590
779
|
}
|
|
591
780
|
}
|
|
@@ -593,11 +782,14 @@ async function checkCommand() {
|
|
|
593
782
|
// src/cli.ts
|
|
594
783
|
var cli = cac("skillink");
|
|
595
784
|
cli.version(currentVersion);
|
|
596
|
-
cli.command(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
cli.command("
|
|
785
|
+
cli.command(
|
|
786
|
+
"init",
|
|
787
|
+
"Initialize Skillink configuration / \u521D\u59CB\u5316 Skillink \u914D\u7F6E"
|
|
788
|
+
).action(() => initCommand());
|
|
789
|
+
cli.command("sync", "Sync skills to configured targets / \u540C\u6B65\u6280\u80FD\u5230\u76EE\u6807\u5DE5\u5177").option("-w, --watch", "Watch file changes / \u76D1\u89C6\u6587\u4EF6\u53D8\u66F4").action((options) => syncCommand(options));
|
|
790
|
+
cli.command("status", "Show sync status / \u663E\u793A\u540C\u6B65\u72B6\u6001").action(() => statusCommand({}));
|
|
791
|
+
cli.command("clean", "Remove generated symlinks / \u79FB\u9664\u751F\u6210\u7684\u7B26\u53F7\u94FE\u63A5").action(() => cleanCommand());
|
|
792
|
+
cli.command("check", "Check package updates / \u68C0\u67E5\u7248\u672C\u66F4\u65B0").action(() => checkCommand());
|
|
601
793
|
cli.help();
|
|
602
794
|
try {
|
|
603
795
|
cli.parse();
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type Locale = 'en' | 'zh-CN';
|
|
1
2
|
interface SyncTarget {
|
|
2
3
|
/** 目标名称(如 'Cursor', 'VSCode') */
|
|
3
4
|
name: string;
|
|
@@ -9,10 +10,10 @@ interface SyncTarget {
|
|
|
9
10
|
interface SkillinkConfig {
|
|
10
11
|
/** 技能源目录(默认为 .agents/skills) */
|
|
11
12
|
source?: string;
|
|
13
|
+
/** CLI 输出语言(默认 en) */
|
|
14
|
+
locale?: Locale;
|
|
12
15
|
/** 同步目标列表 */
|
|
13
16
|
targets: SyncTarget[];
|
|
14
|
-
/** 忽略的文件模式(暂未使用) */
|
|
15
|
-
ignore?: string[];
|
|
16
17
|
}
|
|
17
18
|
interface Skill {
|
|
18
19
|
/** 技能名称(文件夹名) */
|
|
@@ -43,4 +44,4 @@ declare function loadConfig(cwd?: string): Promise<SkillinkConfig | null>;
|
|
|
43
44
|
*/
|
|
44
45
|
declare function defineConfig(config: SkillinkConfig): SkillinkConfig;
|
|
45
46
|
|
|
46
|
-
export { type Skill, type SkillinkConfig, type SyncResult, type SyncTarget, defineConfig, loadConfig };
|
|
47
|
+
export { type Locale, type Skill, type SkillinkConfig, type SyncResult, type SyncTarget, defineConfig, loadConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boses/skillink",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "统一 AI Skills 管理工具 - 像 pnpm 一样链接到各 AI 工具目录",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"prettier": "^3.8.1",
|
|
54
54
|
"tsup": "^8.5.1",
|
|
55
55
|
"typescript": "^5.9.3",
|
|
56
|
-
"typescript-eslint": "^8.54.0"
|
|
56
|
+
"typescript-eslint": "^8.54.0",
|
|
57
|
+
"vitest": "^4.0.18"
|
|
57
58
|
},
|
|
58
59
|
"engines": {
|
|
59
60
|
"node": ">=20.0.0"
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"start": "node dist/bin/skillink.js",
|
|
65
66
|
"lint": "tsc --noEmit",
|
|
66
67
|
"check": "eslint",
|
|
67
|
-
"
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"format": "prettier --write ."
|
|
68
70
|
}
|
|
69
71
|
}
|