dxkj 0.1.0 → 0.1.2

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.
@@ -1,50 +1,50 @@
1
- import path from 'node:path';
2
- import { loadConfig, expandHome } from './config.js';
3
-
4
- /** 把 id/名称转成安全的目录名 */
5
- export function slugify(str) {
6
- return String(str || '')
7
- .replace(/@v?/g, '-v')
8
- .replace(/[^a-zA-Z0-9._-]+/g, '-')
9
- .replace(/^-+|-+$/g, '')
10
- .toLowerCase();
11
- }
12
-
13
- /**
14
- * 解析某条目在指定 target 下的安装目录。
15
- * target 三种:ai-editor | navlink | generic
16
- * @returns {{target:string, dir:string, hint:string}}
17
- */
18
- export function resolveTargetDir(item, opts = {}, cfg = loadConfig()) {
19
- const target = (opts.target || cfg.target || 'generic').toLowerCase();
20
- const key = slugify(item.name || item.id || `item-${item.number}`);
21
-
22
- if (target === 'ai-editor' || target === 'ai' || target === 'editor') {
23
- const editor = (opts.editor || cfg.editor || 'opencode').toLowerCase();
24
- const preset = cfg.targets?.aiEditor?.[editor];
25
- if (!preset) {
26
- throw new Error(`未知编辑器 ${editor}(支持 opencode / claude),可用 dxkj config set targets.aiEditor.${editor}.base <路径> 自定义`);
27
- }
28
- const sub = preset.map?.[item.kind] || item.kind || 'misc';
29
- const base = expandHome(preset.base);
30
- return {
31
- target: 'ai-editor',
32
- dir: path.join(base, sub, key),
33
- hint: `${editor}:${sub}`
34
- };
35
- }
36
-
37
- if (target === 'navlink') {
38
- const dirBase = expandHome(opts.dir || cfg.targets?.navlink?.pluginsDir);
39
- if (!dirBase) throw new Error('navlink 目标未配置安装目录,请用 --dir <NavLink 的 plugins 目录> 或 dxkj config set targets.navlink.pluginsDir <路径>');
40
- return { target: 'navlink', dir: path.join(dirBase, key), hint: 'navlink:plugins' };
41
- }
42
-
43
- if (target === 'generic') {
44
- const base = expandHome(opts.dir || cfg.targets?.generic?.dir);
45
- if (!base) throw new Error('generic 目标未配置目录,请用 --dir <目标目录> 或 dxkj config set targets.generic.dir <路径>');
46
- return { target: 'generic', dir: path.join(base, key), hint: 'generic' };
47
- }
48
-
49
- throw new Error(`未知 target:${target}(支持 ai-editor / navlink / generic)`);
50
- }
1
+ import path from 'node:path';
2
+ import { loadConfig, expandHome } from './config.js';
3
+
4
+ /** 把 id/名称转成安全的目录名 */
5
+ export function slugify(str) {
6
+ return String(str || '')
7
+ .replace(/@v?/g, '-v')
8
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
9
+ .replace(/^-+|-+$/g, '')
10
+ .toLowerCase();
11
+ }
12
+
13
+ /**
14
+ * 解析某条目在指定 target 下的安装目录。
15
+ * target 三种:ai-editor | navlink | generic
16
+ * @returns {{target:string, dir:string, hint:string}}
17
+ */
18
+ export function resolveTargetDir(item, opts = {}, cfg = loadConfig()) {
19
+ const target = (opts.target || cfg.target || 'generic').toLowerCase();
20
+ const key = slugify(item.name || item.id || `item-${item.number}`);
21
+
22
+ if (target === 'ai-editor' || target === 'ai' || target === 'editor') {
23
+ const editor = (opts.editor || cfg.editor || 'opencode').toLowerCase();
24
+ const preset = cfg.targets?.aiEditor?.[editor];
25
+ if (!preset) {
26
+ throw new Error(`未知编辑器 ${editor}(支持 opencode / claude),可用 dxkj config set targets.aiEditor.${editor}.base <路径> 自定义`);
27
+ }
28
+ const sub = preset.map?.[item.kind] || item.kind || 'misc';
29
+ const base = expandHome(preset.base);
30
+ return {
31
+ target: 'ai-editor',
32
+ dir: path.join(base, sub, key),
33
+ hint: `${editor}:${sub}`
34
+ };
35
+ }
36
+
37
+ if (target === 'navlink') {
38
+ const dirBase = expandHome(opts.dir || cfg.targets?.navlink?.pluginsDir);
39
+ if (!dirBase) throw new Error('navlink 目标未配置安装目录,请用 --dir <NavLink 的 plugins 目录> 或 dxkj config set targets.navlink.pluginsDir <路径>');
40
+ return { target: 'navlink', dir: path.join(dirBase, key), hint: 'navlink:plugins' };
41
+ }
42
+
43
+ if (target === 'generic') {
44
+ const base = expandHome(opts.dir || cfg.targets?.generic?.dir);
45
+ if (!base) throw new Error('generic 目标未配置目录,请用 --dir <目标目录> 或 dxkj config set targets.generic.dir <路径>');
46
+ return { target: 'generic', dir: path.join(base, key), hint: 'generic' };
47
+ }
48
+
49
+ throw new Error(`未知 target:${target}(支持 ai-editor / navlink / generic)`);
50
+ }
@@ -1,84 +1,84 @@
1
- /**
2
- * 收录.md 规范 Linter
3
- * 依据:Skill & Agent Tool 分类与命名规范 v1.0
4
- */
5
-
6
- const CATEGORY_CODES = ['IR', 'CL', 'CG', 'DO', 'SI', 'ES', 'AC'];
7
- const KINDS = ['tool', 'skill', 'prompt', 'agent', 'plugin'];
8
- const STATUSES = ['experimental', 'active', 'deprecated', 'archived'];
9
- const ID_RE = /^(tool|skill|prompt|agent|plugin)\.(IR|CL|CG|DO|SI|ES|AC)\.[a-z0-9_]+(\.[a-z0-9_]+)*@v\d+\.\d+$/;
10
- const VERSION_RE = /^v?\d+\.\d+$/;
11
- const CJK_RE = /[\u4e00-\u9fff]/;
12
-
13
- /**
14
- * @param {object} item
15
- * @returns {{errors: string[], warnings: string[]}}
16
- */
17
- export function validateItem(item, { isNew = true } = {}) {
18
- const errors = [];
19
- const warnings = [];
20
- if (!item || typeof item !== 'object') {
21
- return { errors: ['条目不是合法对象'], warnings };
22
- }
23
-
24
- // 1. id / 命名格式
25
- if (!item.id) {
26
- errors.push('缺少必填字段 id');
27
- } else {
28
- if (CJK_RE.test(item.id)) errors.push('id 禁止包含中文');
29
- if (!ID_RE.test(item.id)) {
30
- errors.push(`id 不符合命名规范:${item.id}(应形如 tool.CL.math_calculator@v1.2 或 skill.CG.ns.name@v1.1)`);
31
- } else {
32
- const prefix = item.id.split('.')[0];
33
- const code = item.id.split('.')[1];
34
- if (item.kind && prefix !== item.kind) errors.push(`id 前缀(${prefix})与 kind(${item.kind}) 不一致`);
35
- if (!CATEGORY_CODES.includes(code)) errors.push(`分类简写非法:${code}`);
36
- }
37
- }
38
-
39
- // 2. 必填字段(收录.md 三)
40
- for (const f of ['name', 'category', 'description', 'status', 'author']) {
41
- if (!item[f] || (typeof item[f] === 'string' && !item[f].trim())) errors.push(`缺少必填字段 ${f}`);
42
- }
43
- if (isNew) {
44
- for (const f of ['created_at', 'updated_at']) {
45
- if (!item[f]) warnings.push(`建议补充字段 ${f}`);
46
- }
47
- }
48
-
49
- // 3. kind
50
- if (item.kind && !KINDS.includes(item.kind)) errors.push(`kind 非法:${item.kind}(应为 ${KINDS.join('/')})`);
51
-
52
- // 4. status 生命周期
53
- if (item.status && !STATUSES.includes(item.status)) errors.push(`status 非法:${item.status}(应为 ${STATUSES.join('/')})`);
54
- if (item.status === 'deprecated' && !item.deprecated_reason) errors.push('status=deprecated 时必须填写 deprecated_reason');
55
- if (item.deprecated_reason && item.status !== 'deprecated') warnings.push('存在 deprecated_reason 但 status 不是 deprecated');
56
-
57
- // 5. 版本号
58
- if (item.version && !VERSION_RE.test(String(item.version))) warnings.push(`version 格式建议为 major.minor:${item.version}`);
59
-
60
- // 6. tags
61
- if (item.tags != null && !Array.isArray(item.tags)) errors.push('tags 必须是字符串数组');
62
- if (Array.isArray(item.tags) && item.tags.some((t) => typeof t !== 'string')) errors.push('tags 必须是字符串数组');
63
-
64
- // 7. 展示区(本需求)
65
- if (item.images != null && !Array.isArray(item.images)) errors.push('images 必须是数组');
66
- if (item.intro == null || item.intro === '') warnings.push('建议补充简介 intro(展示页简介区)');
67
-
68
- // 8. 安装源
69
- const install = item.install || {};
70
- if (!install.url) warnings.push('缺少安装包地址 install.url,CLI 将无法安装');
71
-
72
- return { errors, warnings };
73
- }
74
-
75
- export function formatReport(name, { errors, warnings }) {
76
- const lines = [];
77
- if (errors.length === 0 && warnings.length === 0) {
78
- lines.push(`✓ ${name} 校验通过`);
79
- return { ok: true, text: lines.join('\n') };
80
- }
81
- for (const e of errors) lines.push(`✗ [错误] ${e}`);
82
- for (const w of warnings) lines.push(`! [警告] ${w}`);
83
- return { ok: errors.length === 0, text: lines.join('\n') };
84
- }
1
+ /**
2
+ * 收录.md 规范 Linter
3
+ * 依据:Skill & Agent Tool 分类与命名规范 v1.0
4
+ */
5
+
6
+ const CATEGORY_CODES = ['IR', 'CL', 'CG', 'DO', 'SI', 'ES', 'AC'];
7
+ const KINDS = ['tool', 'skill', 'prompt', 'agent', 'plugin'];
8
+ const STATUSES = ['experimental', 'active', 'deprecated', 'archived'];
9
+ const ID_RE = /^(tool|skill|prompt|agent|plugin)\.(IR|CL|CG|DO|SI|ES|AC)\.[a-z0-9_]+(\.[a-z0-9_]+)*@v\d+\.\d+$/;
10
+ const VERSION_RE = /^v?\d+\.\d+$/;
11
+ const CJK_RE = /[\u4e00-\u9fff]/;
12
+
13
+ /**
14
+ * @param {object} item
15
+ * @returns {{errors: string[], warnings: string[]}}
16
+ */
17
+ export function validateItem(item, { isNew = true } = {}) {
18
+ const errors = [];
19
+ const warnings = [];
20
+ if (!item || typeof item !== 'object') {
21
+ return { errors: ['条目不是合法对象'], warnings };
22
+ }
23
+
24
+ // 1. id / 命名格式
25
+ if (!item.id) {
26
+ errors.push('缺少必填字段 id');
27
+ } else {
28
+ if (CJK_RE.test(item.id)) errors.push('id 禁止包含中文');
29
+ if (!ID_RE.test(item.id)) {
30
+ errors.push(`id 不符合命名规范:${item.id}(应形如 tool.CL.math_calculator@v1.2 或 skill.CG.ns.name@v1.1)`);
31
+ } else {
32
+ const prefix = item.id.split('.')[0];
33
+ const code = item.id.split('.')[1];
34
+ if (item.kind && prefix !== item.kind) errors.push(`id 前缀(${prefix})与 kind(${item.kind}) 不一致`);
35
+ if (!CATEGORY_CODES.includes(code)) errors.push(`分类简写非法:${code}`);
36
+ }
37
+ }
38
+
39
+ // 2. 必填字段(收录.md 三)
40
+ for (const f of ['name', 'category', 'description', 'status', 'author']) {
41
+ if (!item[f] || (typeof item[f] === 'string' && !item[f].trim())) errors.push(`缺少必填字段 ${f}`);
42
+ }
43
+ if (isNew) {
44
+ for (const f of ['created_at', 'updated_at']) {
45
+ if (!item[f]) warnings.push(`建议补充字段 ${f}`);
46
+ }
47
+ }
48
+
49
+ // 3. kind
50
+ if (item.kind && !KINDS.includes(item.kind)) errors.push(`kind 非法:${item.kind}(应为 ${KINDS.join('/')})`);
51
+
52
+ // 4. status 生命周期
53
+ if (item.status && !STATUSES.includes(item.status)) errors.push(`status 非法:${item.status}(应为 ${STATUSES.join('/')})`);
54
+ if (item.status === 'deprecated' && !item.deprecated_reason) errors.push('status=deprecated 时必须填写 deprecated_reason');
55
+ if (item.deprecated_reason && item.status !== 'deprecated') warnings.push('存在 deprecated_reason 但 status 不是 deprecated');
56
+
57
+ // 5. 版本号
58
+ if (item.version && !VERSION_RE.test(String(item.version))) warnings.push(`version 格式建议为 major.minor:${item.version}`);
59
+
60
+ // 6. tags
61
+ if (item.tags != null && !Array.isArray(item.tags)) errors.push('tags 必须是字符串数组');
62
+ if (Array.isArray(item.tags) && item.tags.some((t) => typeof t !== 'string')) errors.push('tags 必须是字符串数组');
63
+
64
+ // 7. 展示区(本需求)
65
+ if (item.images != null && !Array.isArray(item.images)) errors.push('images 必须是数组');
66
+ if (item.intro == null || item.intro === '') warnings.push('建议补充简介 intro(展示页简介区)');
67
+
68
+ // 8. 安装源
69
+ const install = item.install || {};
70
+ if (!install.url) warnings.push('缺少安装包地址 install.url,CLI 将无法安装');
71
+
72
+ return { errors, warnings };
73
+ }
74
+
75
+ export function formatReport(name, { errors, warnings }) {
76
+ const lines = [];
77
+ if (errors.length === 0 && warnings.length === 0) {
78
+ lines.push(`✓ ${name} 校验通过`);
79
+ return { ok: true, text: lines.join('\n') };
80
+ }
81
+ for (const e of errors) lines.push(`✗ [错误] ${e}`);
82
+ for (const w of warnings) lines.push(`! [警告] ${w}`);
83
+ return { ok: errors.length === 0, text: lines.join('\n') };
84
+ }
@@ -0,0 +1,8 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ /** 版本号唯一来源:package.json,避免与硬编码漂移 */
4
+ export const VERSION = JSON.parse(
5
+ readFileSync(new URL('../../package.json', import.meta.url), 'utf-8')
6
+ ).version;
7
+
8
+ export const USER_AGENT = `dxkj-cli/${VERSION}`;
package/src/opencode.js CHANGED
@@ -1,71 +1,71 @@
1
- import { execFile } from 'node:child_process';
2
-
3
- /**
4
- * opencode 插件壳
5
- * 注册方式(opencode.json):{ "plugin": ["dxkj"] }
6
- * 该模块即包的默认导出(package.json 的 main/exports["."])。
7
- */
8
-
9
- async function tryToolHelper() {
10
- try {
11
- const mod = await import('@opencode-ai/plugin');
12
- return mod.tool || null;
13
- } catch {
14
- return null;
15
- }
16
- }
17
-
18
- function runCli(args) {
19
- return new Promise((resolve) => {
20
- execFile('npx', ['-y', 'dxkj', ...args], { timeout: 120000, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
21
- const out = (stdout || '').toString().trim();
22
- const errText = (stderr || '').toString().trim();
23
- if (err && !out) resolve(errText || err.message);
24
- else resolve([out, errText].filter(Boolean).join('\n'));
25
- });
26
- });
27
- }
28
-
29
- export async function dxkjPlugin() {
30
- const tool = await tryToolHelper();
31
- const schema = tool?.schema;
32
- const def = (spec) => (tool ? tool(spec) : spec);
33
-
34
- const tools = {
35
- dxkj_catalog: def({
36
- description: 'List or search the dxkj catalog of prompts/skills/agents/plugins/tools by keyword and/or kind.',
37
- args: schema
38
- ? { keyword: schema.string().optional(), kind: schema.string().optional() }
39
- : { keyword: { type: 'string' }, kind: { type: 'string' } },
40
- execute: async (args = {}) => {
41
- const cliArgs = ['list'];
42
- if (args.kind) cliArgs.push('--kind', String(args.kind));
43
- const out = await runCli(cliArgs);
44
- if (!args.keyword) return out;
45
- const kw = String(args.keyword).toLowerCase();
46
- return out
47
- .split(/\r?\n/)
48
- .filter((line) => line.toLowerCase().includes(kw) || /^\s*\d+\s/.test(line))
49
- .join('\n');
50
- }
51
- }),
52
- dxkj_install: def({
53
- description: 'Install one or more catalog items by number/id using the dxkj CLI.',
54
- args: schema
55
- ? { refs: schema.string().describe('逗号分隔的编号或 id,例如 "3,7"'), target: schema.string().optional(), dir: schema.string().optional() }
56
- : { refs: { type: 'string' }, target: { type: 'string' }, dir: { type: 'string' } },
57
- execute: async (args = {}) => {
58
- const refs = String(args.refs || '').split(',').map((s) => s.trim()).filter(Boolean);
59
- if (refs.length === 0) return '请提供编号,例如 refs="3,7"';
60
- const cliArgs = ['install', ...refs];
61
- if (args.target) cliArgs.push('--target', String(args.target));
62
- if (args.dir) cliArgs.push('--dir', String(args.dir));
63
- return runCli(cliArgs);
64
- }
65
- })
66
- };
67
-
68
- return { tool: tools };
69
- }
70
-
71
- export default dxkjPlugin;
1
+ import { execFile } from 'node:child_process';
2
+
3
+ /**
4
+ * opencode 插件壳
5
+ * 注册方式(opencode.json):{ "plugin": ["dxkj"] }
6
+ * 该模块即包的默认导出(package.json 的 main/exports["."])。
7
+ */
8
+
9
+ async function tryToolHelper() {
10
+ try {
11
+ const mod = await import('@opencode-ai/plugin');
12
+ return mod.tool || null;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ function runCli(args) {
19
+ return new Promise((resolve) => {
20
+ execFile('npx', ['-y', 'dxkj', ...args], { timeout: 120000, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
21
+ const out = (stdout || '').toString().trim();
22
+ const errText = (stderr || '').toString().trim();
23
+ if (err && !out) resolve(errText || err.message);
24
+ else resolve([out, errText].filter(Boolean).join('\n'));
25
+ });
26
+ });
27
+ }
28
+
29
+ export async function dxkjPlugin() {
30
+ const tool = await tryToolHelper();
31
+ const schema = tool?.schema;
32
+ const def = (spec) => (tool ? tool(spec) : spec);
33
+
34
+ const tools = {
35
+ dxkj_catalog: def({
36
+ description: 'List or search the dxkj catalog of prompts/skills/agents/plugins/tools by keyword and/or kind.',
37
+ args: schema
38
+ ? { keyword: schema.string().optional(), kind: schema.string().optional() }
39
+ : { keyword: { type: 'string' }, kind: { type: 'string' } },
40
+ execute: async (args = {}) => {
41
+ const cliArgs = ['list'];
42
+ if (args.kind) cliArgs.push('--kind', String(args.kind));
43
+ const out = await runCli(cliArgs);
44
+ if (!args.keyword) return out;
45
+ const kw = String(args.keyword).toLowerCase();
46
+ return out
47
+ .split(/\r?\n/)
48
+ .filter((line) => line.toLowerCase().includes(kw) || /^\s*\d+\s/.test(line))
49
+ .join('\n');
50
+ }
51
+ }),
52
+ dxkj_install: def({
53
+ description: 'Install one or more catalog items by number/id using the dxkj CLI.',
54
+ args: schema
55
+ ? { refs: schema.string().describe('逗号分隔的编号或 id,例如 "3,7"'), target: schema.string().optional(), dir: schema.string().optional() }
56
+ : { refs: { type: 'string' }, target: { type: 'string' }, dir: { type: 'string' } },
57
+ execute: async (args = {}) => {
58
+ const refs = String(args.refs || '').split(',').map((s) => s.trim()).filter(Boolean);
59
+ if (refs.length === 0) return '请提供编号,例如 refs="3,7"';
60
+ const cliArgs = ['install', ...refs];
61
+ if (args.target) cliArgs.push('--target', String(args.target));
62
+ if (args.dir) cliArgs.push('--dir', String(args.dir));
63
+ return runCli(cliArgs);
64
+ }
65
+ })
66
+ };
67
+
68
+ return { tool: tools };
69
+ }
70
+
71
+ export default dxkjPlugin;