dbx-plugin-skill 0.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.
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dbx-plugin-skill — 安装/卸载 DBX 插件开发 skill
4
+ *
5
+ * 用法:
6
+ * npx dbx-plugin-skill install [--target all|dsh,claude,agents] [--scope user|project] [--link] [--force]
7
+ * npx dbx-plugin-skill uninstall [--target ...] [--scope user|project] [--force]
8
+ * npx dbx-plugin-skill status [--target ...] [--scope user|project] [--json]
9
+ * npx dbx-plugin-skill doctor [--json]
10
+ * npx dbx-plugin-skill path [--target dsh|claude|agents] [--scope user|project]
11
+ */
12
+
13
+ import { execFileSync } from "node:child_process";
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ import { join, resolve } from "node:path";
16
+ import {
17
+ install,
18
+ uninstall,
19
+ status,
20
+ resolveTargets,
21
+ targetRoot,
22
+ installPath,
23
+ sourceDir,
24
+ packageVersion,
25
+ SKILL_NAME,
26
+ TARGET_NAMES,
27
+ } from "../lib/installer.mjs";
28
+
29
+ const PACKAGE_NAME = "dbx-plugin-skill";
30
+
31
+ const USAGE = `dbx-plugin-skill — DBX 插件开发 skill 安装器
32
+
33
+ 用法:
34
+ dbx-plugin-skill <command> [options]
35
+
36
+ 命令:
37
+ install 安装 skill 到技能目录(默认 dsh + claude + agents)
38
+ uninstall 移除本安装器写入的 skill
39
+ status 查看各目标的安装状态
40
+ doctor 环境自检(Node、dbx-plugin CLI、skill 安装、当前项目)
41
+ path 打印某个目标的安装路径
42
+
43
+ 选项:
44
+ --target LIST all(默认)或逗号分隔: ${TARGET_NAMES.join(", ")}
45
+ --scope SCOPE user(默认,家目录)或 project(当前目录)
46
+ --link 安装为符号链接(便于从源码仓库开发)
47
+ --force 覆盖非本安装器写入的同名目录 / 强制卸载
48
+ --json 以 JSON 输出(status / doctor)
49
+ --offline doctor 跳过 npm 版本检查
50
+ -h, --help 显示帮助
51
+ -v, --version 显示版本
52
+ `;
53
+
54
+ function parseArgs(argv) {
55
+ const options = {
56
+ target: "all",
57
+ scope: "user",
58
+ link: false,
59
+ force: false,
60
+ json: false,
61
+ offline: false,
62
+ positional: [],
63
+ };
64
+ for (let i = 0; i < argv.length; i += 1) {
65
+ const arg = argv[i];
66
+ const take = () => {
67
+ i += 1;
68
+ const value = argv[i];
69
+ if (value === undefined) fail(`${arg} 需要一个值`);
70
+ return value;
71
+ };
72
+ if (arg === "--target" || arg === "-t") options.target = take();
73
+ else if (arg === "--scope") options.scope = take();
74
+ else if (arg === "--link") options.link = true;
75
+ else if (arg === "--force" || arg === "-f") options.force = true;
76
+ else if (arg === "--json") options.json = true;
77
+ else if (arg === "--offline") options.offline = true;
78
+ else if (arg === "-h" || arg === "--help") options.help = true;
79
+ else if (arg === "-v" || arg === "--version") options.version = true;
80
+ else if (arg.startsWith("-")) fail(`未知选项: ${arg}`);
81
+ else options.positional.push(arg);
82
+ }
83
+ if (!["user", "project"].includes(options.scope)) fail(`--scope 只能是 user 或 project`);
84
+ return options;
85
+ }
86
+
87
+ function fail(message) {
88
+ process.stderr.write(`${message}\n\n${USAGE}`);
89
+ process.exit(2);
90
+ }
91
+
92
+ function commandExists(command) {
93
+ try {
94
+ execFileSync(command, ["--version"], { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" });
95
+ return true;
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ function pluginCliVersion() {
102
+ for (const [command, args] of [
103
+ ["dbx-plugin", ["version"]],
104
+ ["npx", ["--no-install", "@dbx-app/plugin-cli", "version"]],
105
+ ]) {
106
+ try {
107
+ const output = execFileSync(command, args, { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }).trim();
108
+ if (output) return { command: `${command} ${args.join(" ")}`, output };
109
+ } catch {
110
+ /* 试下一个 */
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+
116
+ function commandInstall(options) {
117
+ const targets = resolveTargets(options.target);
118
+ const results = install({
119
+ targets,
120
+ scope: options.scope,
121
+ link: options.link,
122
+ force: options.force,
123
+ log: (line) => process.stdout.write(`${line}\n`),
124
+ });
125
+ process.stdout.write(`\n已安装 skill "${SKILL_NAME}" v${packageVersion()} 到 ${results.length} 个目标。\n`);
126
+ process.stdout.write(`重启 agent 会话后生效(技能目录在会话启动时扫描)。\n`);
127
+ }
128
+
129
+ function commandUninstall(options) {
130
+ const targets = resolveTargets(options.target);
131
+ const results = uninstall({
132
+ targets,
133
+ scope: options.scope,
134
+ force: options.force,
135
+ log: (line) => process.stdout.write(`${line}\n`),
136
+ });
137
+ const removed = results.filter((r) => r.removed).length;
138
+ process.stdout.write(`\n已移除 ${removed} 个目标。\n`);
139
+ }
140
+
141
+ function commandStatus(options) {
142
+ const targets = resolveTargets(options.target);
143
+ const rows = status({ targets, scope: options.scope });
144
+ if (options.json) {
145
+ process.stdout.write(`${JSON.stringify({ skill: SKILL_NAME, version: packageVersion(), scope: options.scope, targets: rows }, null, 2)}\n`);
146
+ return;
147
+ }
148
+ process.stdout.write(`skill: ${SKILL_NAME} 包版本: ${packageVersion()} scope: ${options.scope}\n\n`);
149
+ for (const row of rows) {
150
+ const state =
151
+ row.state === "installed"
152
+ ? `已安装${row.version ? ` v${row.version}` : ""}${row.mode === "link" ? "(符号链接)" : ""}${row.files ? ` · ${row.files} 个文件` : ""}`
153
+ : row.state === "unmanaged"
154
+ ? "已存在(非本安装器写入)"
155
+ : row.state === "broken-link"
156
+ ? "符号链接已失效"
157
+ : row.state === "occupied"
158
+ ? "目录被占用"
159
+ : "未安装";
160
+ process.stdout.write(`${row.label} (${row.target})\n ${row.path}\n ${state}\n`);
161
+ }
162
+ }
163
+
164
+ /** 查询 npm 上的最新版本;区分「已发布」「尚未发布」「无法连接」三种情况 */
165
+ async function latestPublishedVersion() {
166
+ let response;
167
+ try {
168
+ response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
169
+ signal: AbortSignal.timeout(4000),
170
+ headers: { accept: "application/json" },
171
+ });
172
+ } catch {
173
+ return { status: "unreachable" };
174
+ }
175
+ if (response.status === 404) return { status: "not-published" };
176
+ if (!response.ok) return { status: "unreachable" };
177
+ try {
178
+ const payload = await response.json();
179
+ return typeof payload.version === "string" ? { status: "ok", version: payload.version } : { status: "unreachable" };
180
+ } catch {
181
+ return { status: "unreachable" };
182
+ }
183
+ }
184
+
185
+ async function commandDoctor(options) {
186
+ const targets = resolveTargets(options.target);
187
+ const checks = [];
188
+ const current = packageVersion();
189
+
190
+ const nodeMajor = Number(process.versions.node.split(".")[0]);
191
+ checks.push({
192
+ name: "Node.js",
193
+ ok: nodeMajor >= 18,
194
+ detail: `v${process.versions.node}${nodeMajor >= 22 ? "(满足 dev 要求)" : "(<22,dbx-plugin dev 需要 22+)"}`,
195
+ level: nodeMajor >= 18 ? "ok" : "error",
196
+ });
197
+ checks.push({
198
+ name: "Node.js 22+(dev 子命令)",
199
+ ok: nodeMajor >= 22,
200
+ detail: nodeMajor >= 22 ? "满足" : "dbx-plugin dev 需要 Node.js 22+",
201
+ level: nodeMajor >= 22 ? "ok" : "warn",
202
+ });
203
+
204
+ const cli = pluginCliVersion();
205
+ checks.push({
206
+ name: "dbx-plugin CLI",
207
+ ok: Boolean(cli),
208
+ detail: cli ? `${cli.output.split("\n")[0]}(${cli.command})` : "未找到。安装: npm install --global @dbx-app/plugin-cli",
209
+ level: cli ? "ok" : "warn",
210
+ });
211
+
212
+ const skillSource = sourceDir();
213
+ checks.push({
214
+ name: "skill 源目录",
215
+ ok: existsSync(join(skillSource, "SKILL.md")),
216
+ detail: skillSource,
217
+ level: existsSync(join(skillSource, "SKILL.md")) ? "ok" : "error",
218
+ });
219
+
220
+ if (options.offline) {
221
+ checks.push({ name: "skill 版本", ok: true, detail: `本机 v${current}(已跳过在线检查)`, level: "info" });
222
+ } else {
223
+ const latest = await latestPublishedVersion();
224
+ if (latest.status === "not-published") {
225
+ checks.push({
226
+ name: "skill 版本",
227
+ ok: true,
228
+ detail: `本机 v${current}(${PACKAGE_NAME} 尚未发布到 npm)`,
229
+ level: "info",
230
+ });
231
+ } else if (latest.status === "unreachable") {
232
+ checks.push({ name: "skill 版本", ok: true, detail: `本机 v${current}(无法连接 npm,已跳过)`, level: "info" });
233
+ } else if (latest.version === current) {
234
+ checks.push({ name: "skill 版本", ok: true, detail: `v${current}(已是 npm 最新)`, level: "ok" });
235
+ } else {
236
+ checks.push({
237
+ name: "skill 版本",
238
+ ok: true,
239
+ detail: `本机 v${current},npm 最新 v${latest.version}。升级: npm i -g ${PACKAGE_NAME}@latest && ${PACKAGE_NAME} install`,
240
+ level: "warn",
241
+ });
242
+ }
243
+ }
244
+
245
+ for (const row of status({ targets, scope: options.scope })) {
246
+ checks.push({
247
+ name: `skill 安装 · ${row.label}`,
248
+ ok: row.state === "installed",
249
+ detail: row.state === "installed" ? `${row.path}${row.mode === "link" ? "(link)" : ""}` : `${row.path} — ${row.state}`,
250
+ level: row.state === "installed" ? "ok" : "warn",
251
+ });
252
+ }
253
+
254
+ const cwd = process.cwd();
255
+ const projectManifest = join(cwd, "manifest.json");
256
+ const projectToml = join(cwd, "dbx-plugin.toml");
257
+ const isProject = existsSync(projectManifest) && existsSync(projectToml);
258
+ checks.push({
259
+ name: "当前目录是 DBX 插件项目",
260
+ ok: isProject,
261
+ detail: isProject ? cwd : `${cwd}(缺少 manifest.json 或 dbx-plugin.toml)`,
262
+ level: isProject ? "ok" : "info",
263
+ });
264
+
265
+ const failed = checks.filter((c) => c.level === "error");
266
+ const warned = checks.filter((c) => c.level === "warn");
267
+
268
+ if (options.json) {
269
+ process.stdout.write(`${JSON.stringify({ ok: failed.length === 0, checks }, null, 2)}\n`);
270
+ } else {
271
+ for (const check of checks) {
272
+ const tag = check.level === "error" ? "ERROR" : check.level === "warn" ? "WARN " : check.level === "info" ? "info " : "OK ";
273
+ process.stdout.write(`[${tag}] ${check.name}: ${check.detail}\n`);
274
+ }
275
+ process.stdout.write(`\n${failed.length} 个错误, ${warned.length} 个警告\n`);
276
+ if (isProject) {
277
+ process.stdout.write(`\n建议下一步: node ${join(skillSource, "scripts", "check-project.mjs")} .\n`);
278
+ }
279
+ }
280
+ process.exit(failed.length === 0 ? 0 : 1);
281
+ }
282
+
283
+ function commandPath(options) {
284
+ const targets = resolveTargets(options.target);
285
+ for (const name of targets) {
286
+ const root = targetRoot(name, options.scope);
287
+ process.stdout.write(options.json ? `${JSON.stringify({ target: name, root, path: installPath(name, options.scope) })}\n` : `${installPath(name, options.scope)}\n`);
288
+ }
289
+ }
290
+
291
+ async function main() {
292
+ const [command, ...rest] = process.argv.slice(2);
293
+ const options = parseArgs(rest);
294
+
295
+ if (options.help || !command || command === "help") {
296
+ process.stdout.write(USAGE);
297
+ return;
298
+ }
299
+ if (options.version || command === "version") {
300
+ process.stdout.write(`${packageVersion()}\n`);
301
+ return;
302
+ }
303
+
304
+ switch (command) {
305
+ case "install":
306
+ commandInstall(options);
307
+ break;
308
+ case "uninstall":
309
+ case "remove":
310
+ commandUninstall(options);
311
+ break;
312
+ case "status":
313
+ case "list":
314
+ commandStatus(options);
315
+ break;
316
+ case "doctor":
317
+ await commandDoctor(options);
318
+ break;
319
+ case "path":
320
+ commandPath(options);
321
+ break;
322
+ default:
323
+ fail(`未知命令: ${command}`);
324
+ }
325
+ }
326
+
327
+ main().catch((error) => {
328
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
329
+ process.exit(1);
330
+ });
@@ -0,0 +1,256 @@
1
+ /**
2
+ * installer.mjs — 把 skill 安装到各 agent 的技能目录
3
+ *
4
+ * 支持的 target:
5
+ * dsh → <root>/.dsh/skills/dbx-plugin
6
+ * claude → <root>/.claude/skills/dbx-plugin
7
+ * agents → <root>/.agents/skills/dbx-plugin
8
+ *
9
+ * <root> 在 user scope 下是 home 目录,在 project scope 下是当前工作目录。
10
+ */
11
+
12
+ import {
13
+ existsSync,
14
+ mkdirSync,
15
+ cpSync,
16
+ rmSync,
17
+ readFileSync,
18
+ writeFileSync,
19
+ symlinkSync,
20
+ lstatSync,
21
+ readdirSync,
22
+ realpathSync,
23
+ } from "node:fs";
24
+ import { join, dirname, resolve } from "node:path";
25
+ import { homedir } from "node:os";
26
+ import { fileURLToPath } from "node:url";
27
+
28
+ export const SKILL_NAME = "dbx-plugin";
29
+ export const MARKER_FILE = ".dbx-plugin-skill.json";
30
+
31
+ /**
32
+ * 每个 target 的技能目录。
33
+ * 注意:DSH_HOME 指向 harness 的 home 目录本身(如 ~/.dsh),技能在 $DSH_HOME/skills;
34
+ * DSH_AGENTS_HOME 同理指向 agents home(默认 ~/.agents),技能在 $DSH_AGENTS_HOME/skills。
35
+ */
36
+ export const TARGETS = {
37
+ dsh: { label: "DeepSeek Harness", homeDir: ".dsh", homeEnv: "DSH_HOME" },
38
+ claude: { label: "Claude Code", homeDir: ".claude", homeEnv: null },
39
+ agents: { label: "共享 agents 目录", homeDir: ".agents", homeEnv: "DSH_AGENTS_HOME" },
40
+ };
41
+
42
+ export const TARGET_NAMES = Object.keys(TARGETS);
43
+
44
+ /** 解析包内 skill 源目录 */
45
+ export function sourceDir() {
46
+ const here = dirname(fileURLToPath(import.meta.url));
47
+ return resolve(here, "..", "skill");
48
+ }
49
+
50
+ export function packageVersion() {
51
+ const here = dirname(fileURLToPath(import.meta.url));
52
+ const pkg = JSON.parse(readFileSync(resolve(here, "..", "package.json"), "utf8"));
53
+ return pkg.version;
54
+ }
55
+
56
+ export function resolveTargets(selection) {
57
+ if (!selection || selection === "all") return [...TARGET_NAMES];
58
+ const names = String(selection)
59
+ .split(",")
60
+ .map((v) => v.trim())
61
+ .filter(Boolean);
62
+ const unknown = names.filter((name) => !TARGET_NAMES.includes(name));
63
+ if (unknown.length) {
64
+ throw new Error(`未知 target: ${unknown.join(", ")}(可用: ${TARGET_NAMES.join(", ")}, all)`);
65
+ }
66
+ return names;
67
+ }
68
+
69
+ /** 计算某个 target 的安装根目录(skills 目录本身) */
70
+ export function targetRoot(name, scope, cwd = process.cwd()) {
71
+ const target = TARGETS[name];
72
+ if (!target) throw new Error(`未知 target: ${name}`);
73
+ if (scope === "project") return join(cwd, target.homeDir, "skills");
74
+ const base =
75
+ (target.homeEnv && process.env[target.homeEnv]) || join(homedir(), target.homeDir);
76
+ return join(base, "skills");
77
+ }
78
+
79
+ export function installPath(name, scope, cwd) {
80
+ return join(targetRoot(name, scope, cwd), SKILL_NAME);
81
+ }
82
+
83
+ /** 判断目录里是否已有一份本 skill(读 marker,或退回检查 SKILL.md 的 name 字段) */
84
+ export function inspectInstall(dir) {
85
+ if (!existsSync(dir)) return { state: "absent" };
86
+ let stats;
87
+ try {
88
+ stats = lstatSync(dir);
89
+ } catch {
90
+ return { state: "absent" };
91
+ }
92
+ const linked = stats.isSymbolicLink();
93
+ let resolved = dir;
94
+ if (linked) {
95
+ try {
96
+ resolved = realpathSync(dir);
97
+ } catch {
98
+ return { state: "broken-link", linked: true };
99
+ }
100
+ }
101
+ const markerPath = join(resolved, MARKER_FILE);
102
+ if (existsSync(markerPath)) {
103
+ try {
104
+ const marker = JSON.parse(readFileSync(markerPath, "utf8"));
105
+ if (marker.skill === SKILL_NAME) {
106
+ return { state: "installed", linked, marker, resolved };
107
+ }
108
+ } catch {
109
+ /* marker 损坏,继续按 SKILL.md 判断 */
110
+ }
111
+ }
112
+ const skillFile = join(resolved, "SKILL.md");
113
+ if (existsSync(skillFile)) {
114
+ const head = readFileSync(skillFile, "utf8").slice(0, 500);
115
+ if (/^name:\s*dbx-plugin\s*$/m.test(head)) {
116
+ // link 模式刻意不写 marker(写入会穿过符号链接污染源目录),
117
+ // 因此指向本 skill 的符号链接直接视为本安装器写入。
118
+ // 非链接的同名目录可能是用户手工复制的,保持 unmanaged 需要 --force。
119
+ return linked
120
+ ? { state: "installed", linked, mode: "link", resolved }
121
+ : { state: "unmanaged", linked, resolved };
122
+ }
123
+ }
124
+ return { state: "occupied", linked, resolved };
125
+ }
126
+
127
+ function writeMarker(dir, { mode, version }) {
128
+ const marker = {
129
+ skill: SKILL_NAME,
130
+ package: "dbx-plugin-skill",
131
+ version,
132
+ mode,
133
+ installedAt: new Date().toISOString(),
134
+ };
135
+ writeFileSync(join(dir, MARKER_FILE), `${JSON.stringify(marker, null, 2)}\n`);
136
+ return marker;
137
+ }
138
+
139
+ export function install({ targets, scope = "user", link = false, force = false, cwd = process.cwd(), log = () => {} }) {
140
+ const source = sourceDir();
141
+ if (!existsSync(join(source, "SKILL.md"))) {
142
+ throw new Error(`包内找不到 skill 源目录: ${source}`);
143
+ }
144
+ const version = packageVersion();
145
+ const results = [];
146
+
147
+ for (const name of targets) {
148
+ const root = targetRoot(name, scope, cwd);
149
+ const destination = join(root, SKILL_NAME);
150
+ const existing = inspectInstall(destination);
151
+
152
+ if (existing.state === "broken-link") {
153
+ log(`清理失效的符号链接: ${destination}`);
154
+ rmSync(destination, { force: true });
155
+ } else if (existing.state === "installed") {
156
+ // 幂等重装:先移除再写
157
+ rmSync(destination, { recursive: true, force: true });
158
+ } else if (existing.state === "unmanaged" || existing.state === "occupied") {
159
+ if (!force) {
160
+ throw new Error(
161
+ `${destination} 已存在且不是本安装器写入的(${existing.state === "unmanaged" ? "疑似手工安装" : "目录被占用"})。` +
162
+ `加 --force 覆盖,或先手工删除。`,
163
+ );
164
+ }
165
+ rmSync(destination, { recursive: true, force: true });
166
+ }
167
+
168
+ mkdirSync(root, { recursive: true });
169
+
170
+ let installedVersion = version;
171
+ if (link) {
172
+ try {
173
+ symlinkSync(source, destination, process.platform === "win32" ? "junction" : "dir");
174
+ } catch (err) {
175
+ throw new Error(`创建符号链接失败(${destination}): ${err.message}。去掉 --link 可改用复制安装。`);
176
+ }
177
+ // link 模式不写 marker:目标通常是本包的源码目录,写入会顺着链接污染源码。
178
+ // inspectInstall 通过「符号链接 + SKILL.md 的 name 字段」识别这种安装。
179
+ } else {
180
+ mkdirSync(destination, { recursive: true });
181
+ cpSync(source, destination, { recursive: true });
182
+ installedVersion = writeMarker(destination, { mode: "copy", version }).version;
183
+ }
184
+
185
+ results.push({
186
+ target: name,
187
+ label: TARGETS[name].label,
188
+ path: destination,
189
+ mode: link ? "link" : "copy",
190
+ version: installedVersion,
191
+ });
192
+ log(`✔ ${TARGETS[name].label} → ${destination}${link ? "(符号链接)" : ""}`);
193
+ }
194
+
195
+ return results;
196
+ }
197
+
198
+ export function uninstall({ targets, scope = "user", force = false, cwd = process.cwd(), log = () => {} }) {
199
+ const results = [];
200
+ for (const name of targets) {
201
+ const destination = installPath(name, scope, cwd);
202
+ const existing = inspectInstall(destination);
203
+ if (existing.state === "absent") {
204
+ results.push({ target: name, path: destination, removed: false, reason: "未安装" });
205
+ log(`· ${TARGETS[name].label}: 未安装,跳过`);
206
+ continue;
207
+ }
208
+ if (existing.state === "unmanaged" || existing.state === "occupied") {
209
+ if (!force) {
210
+ throw new Error(
211
+ `${destination} 不是本安装器写入的(${existing.state}),拒绝删除。确实要删请加 --force。`,
212
+ );
213
+ }
214
+ }
215
+ rmSync(destination, { recursive: true, force: true });
216
+ results.push({ target: name, path: destination, removed: true });
217
+ log(`✔ 已移除 ${TARGETS[name].label}: ${destination}`);
218
+ }
219
+ return results;
220
+ }
221
+
222
+ export function status({ targets, scope = "user", cwd = process.cwd() }) {
223
+ return targets.map((name) => {
224
+ const root = targetRoot(name, scope, cwd);
225
+ const destination = join(root, SKILL_NAME);
226
+ const info = inspectInstall(destination);
227
+ let files = 0;
228
+ if (info.state === "installed" || info.state === "unmanaged") {
229
+ try {
230
+ files = countFiles(info.resolved);
231
+ } catch {
232
+ files = 0;
233
+ }
234
+ }
235
+ return {
236
+ target: name,
237
+ label: TARGETS[name].label,
238
+ scope,
239
+ root,
240
+ path: destination,
241
+ state: info.state,
242
+ mode: info.linked ? "link" : info.marker?.mode ?? null,
243
+ version: info.marker?.version ?? null,
244
+ files,
245
+ };
246
+ });
247
+ }
248
+
249
+ function countFiles(dir) {
250
+ let total = 0;
251
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
252
+ if (entry.isDirectory()) total += countFiles(join(dir, entry.name));
253
+ else if (entry.isFile()) total += 1;
254
+ }
255
+ return total;
256
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "dbx-plugin-skill",
3
+ "version": "0.1.0",
4
+ "description": "DBX 插件开发 skill:覆盖插件创建、开发、调试、打包、发布到 dbx-store 上架的全流程,安装到 DSH / Claude Code / agents 技能目录",
5
+ "type": "module",
6
+ "bin": {
7
+ "dbx-plugin-skill": "bin/dbx-plugin-skill.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ "skill",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "test": "node test/smoke.mjs",
21
+ "verify:package": "node tools/verify-package.mjs",
22
+ "upstream:check": "node tools/upstream-drift.mjs",
23
+ "upstream:update": "node tools/upstream-drift.mjs --update",
24
+ "release:patch": "npm version patch",
25
+ "release:minor": "npm version minor",
26
+ "prepublishOnly": "npm test && node tools/verify-package.mjs"
27
+ },
28
+ "keywords": [
29
+ "dbx",
30
+ "dbx-plugin",
31
+ "plugin",
32
+ "skill",
33
+ "agent-skill",
34
+ "deepseek-harness",
35
+ "claude-code"
36
+ ],
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/eryajf/dbx-plugin-skill.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/eryajf/dbx-plugin-skill/issues"
44
+ },
45
+ "homepage": "https://github.com/eryajf/dbx-plugin-skill#readme",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }