chainlesschain 0.37.10 → 0.37.12
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 +166 -10
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/init.js +184 -0
- package/src/commands/lowcode.js +320 -0
- package/src/commands/plugin.js +55 -2
- package/src/commands/sandbox.js +366 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +44 -0
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/plugin-manager.js +118 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +117 -112
package/src/commands/skill.js
CHANGED
|
@@ -1,192 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill management and execution commands
|
|
3
|
-
* chainlesschain skill list|info|run|search|categories
|
|
3
|
+
* chainlesschain skill list|info|run|search|categories|add|remove|sources
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Uses multi-layer skill loader:
|
|
6
|
+
* bundled < marketplace < managed (global) < workspace (project)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import ora from "ora";
|
|
11
11
|
import fs from "fs";
|
|
12
12
|
import path from "path";
|
|
13
|
-
import { fileURLToPath } from "url";
|
|
14
13
|
import { logger } from "../lib/logger.js";
|
|
14
|
+
import { CLISkillLoader, LAYER_NAMES } from "../lib/skill-loader.js";
|
|
15
|
+
import { getElectronUserDataDir } from "../lib/paths.js";
|
|
16
|
+
import { findProjectRoot } from "../lib/project-detector.js";
|
|
15
17
|
|
|
16
|
-
const
|
|
18
|
+
const LAYER_LABELS = {
|
|
19
|
+
bundled: chalk.blue("[bundled]"),
|
|
20
|
+
marketplace: chalk.magenta("[marketplace]"),
|
|
21
|
+
managed: chalk.yellow("[global]"),
|
|
22
|
+
workspace: chalk.green("[project]"),
|
|
23
|
+
};
|
|
17
24
|
|
|
18
25
|
/**
|
|
19
|
-
*
|
|
26
|
+
* Check if a skill can run on current platform
|
|
20
27
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
path.resolve(
|
|
25
|
-
__dirname,
|
|
26
|
-
"../../../../desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
|
|
27
|
-
),
|
|
28
|
-
path.resolve(
|
|
29
|
-
process.cwd(),
|
|
30
|
-
"desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
|
|
31
|
-
),
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
for (const dir of candidates) {
|
|
35
|
-
if (fs.existsSync(dir)) return dir;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return null;
|
|
28
|
+
function canRunOnPlatform(skill) {
|
|
29
|
+
if (!skill.os || skill.os.length === 0) return true;
|
|
30
|
+
return skill.os.includes(process.platform);
|
|
39
31
|
}
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
endIndex = i;
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (endIndex === -1) return { data: {}, body: content };
|
|
57
|
-
|
|
58
|
-
const yamlLines = lines.slice(1, endIndex);
|
|
59
|
-
const body = lines
|
|
60
|
-
.slice(endIndex + 1)
|
|
61
|
-
.join("\n")
|
|
62
|
-
.trim();
|
|
63
|
-
const data = {};
|
|
64
|
-
|
|
65
|
-
let currentKey = null;
|
|
66
|
-
let currentArray = null;
|
|
67
|
-
|
|
68
|
-
for (const line of yamlLines) {
|
|
69
|
-
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
70
|
-
|
|
71
|
-
const trimmed = line.trim();
|
|
72
|
-
|
|
73
|
-
if (trimmed.startsWith("- ")) {
|
|
74
|
-
const value = trimmed
|
|
75
|
-
.slice(2)
|
|
76
|
-
.trim()
|
|
77
|
-
.replace(/^['"]|['"]$/g, "");
|
|
78
|
-
if (currentArray) currentArray.push(value);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const colonIndex = trimmed.indexOf(":");
|
|
83
|
-
if (colonIndex > 0) {
|
|
84
|
-
const key = trimmed.slice(0, colonIndex).trim();
|
|
85
|
-
let value = trimmed.slice(colonIndex + 1).trim();
|
|
86
|
-
|
|
87
|
-
// Convert kebab-case to camelCase
|
|
88
|
-
const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
89
|
-
|
|
90
|
-
if (value === "") {
|
|
91
|
-
// Could be start of nested object or array
|
|
92
|
-
currentKey = camelKey;
|
|
93
|
-
currentArray = null;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
33
|
+
const SKILL_TEMPLATE_MD = (name) => `---
|
|
34
|
+
name: ${name}
|
|
35
|
+
display-name: ${name}
|
|
36
|
+
description: Custom skill — edit this description
|
|
37
|
+
version: 1.0.0
|
|
38
|
+
category: custom
|
|
39
|
+
tags: [custom]
|
|
40
|
+
user-invocable: true
|
|
41
|
+
handler: handler.js
|
|
42
|
+
---
|
|
96
43
|
|
|
97
|
-
|
|
98
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
99
|
-
data[camelKey] = value
|
|
100
|
-
.slice(1, -1)
|
|
101
|
-
.split(",")
|
|
102
|
-
.map((v) => v.trim().replace(/^['"]|['"]$/g, ""))
|
|
103
|
-
.filter(Boolean);
|
|
104
|
-
currentArray = null;
|
|
105
|
-
currentKey = null;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
44
|
+
# ${name}
|
|
108
45
|
|
|
109
|
-
|
|
110
|
-
if (value === "true") value = true;
|
|
111
|
-
else if (value === "false") value = false;
|
|
112
|
-
else if (value === "null") value = null;
|
|
113
|
-
else if (/^\d+(\.\d+)?$/.test(value)) value = parseFloat(value);
|
|
114
|
-
else value = value.replace(/^['"]|['"]$/g, "");
|
|
46
|
+
Describe what this skill does and how to use it.
|
|
115
47
|
|
|
116
|
-
|
|
48
|
+
## Usage
|
|
117
49
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
currentArray = null;
|
|
123
|
-
}
|
|
124
|
-
currentKey = camelKey;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return { data, body };
|
|
129
|
-
}
|
|
50
|
+
\`\`\`
|
|
51
|
+
chainlesschain skill run ${name} "your input"
|
|
52
|
+
\`\`\`
|
|
53
|
+
`;
|
|
130
54
|
|
|
131
|
-
|
|
132
|
-
*
|
|
55
|
+
const SKILL_TEMPLATE_HANDLER = (name) => `/**
|
|
56
|
+
* Handler for ${name} skill
|
|
133
57
|
*/
|
|
134
|
-
function loadSkillMetadata(skillsDir) {
|
|
135
|
-
const skills = [];
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
139
|
-
|
|
140
|
-
for (const dir of dirs) {
|
|
141
|
-
if (!dir.isDirectory()) continue;
|
|
142
58
|
|
|
143
|
-
|
|
144
|
-
|
|
59
|
+
const handler = {
|
|
60
|
+
async init(skill) {
|
|
61
|
+
// Optional initialization
|
|
62
|
+
},
|
|
145
63
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const { data, body } = parseSkillMd(content);
|
|
149
|
-
|
|
150
|
-
skills.push({
|
|
151
|
-
id: data.name || dir.name,
|
|
152
|
-
displayName: data.displayName || dir.name,
|
|
153
|
-
description: data.description || "",
|
|
154
|
-
version: data.version || "1.0.0",
|
|
155
|
-
category: data.category || "uncategorized",
|
|
156
|
-
tags: data.tags || [],
|
|
157
|
-
userInvocable: data.userInvocable !== false,
|
|
158
|
-
handler: data.handler || null,
|
|
159
|
-
capabilities: data.capabilities || [],
|
|
160
|
-
os: data.os || [],
|
|
161
|
-
dirName: dir.name,
|
|
162
|
-
hasHandler: fs.existsSync(
|
|
163
|
-
path.join(skillsDir, dir.name, "handler.js"),
|
|
164
|
-
),
|
|
165
|
-
body,
|
|
166
|
-
});
|
|
167
|
-
} catch {
|
|
168
|
-
// Skip malformed skill files
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} catch (err) {
|
|
172
|
-
logger.error(`Failed to read skills directory: ${err.message}`);
|
|
173
|
-
}
|
|
64
|
+
async execute(task, context, skill) {
|
|
65
|
+
const input = task.input || task.params?.input || "";
|
|
174
66
|
|
|
175
|
-
|
|
176
|
-
|
|
67
|
+
// TODO: Implement skill logic
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
message: \`${name} executed successfully\`,
|
|
71
|
+
result: { input },
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
177
75
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*/
|
|
181
|
-
function canRunOnPlatform(skill) {
|
|
182
|
-
if (!skill.os || skill.os.length === 0) return true;
|
|
183
|
-
return skill.os.includes(process.platform);
|
|
184
|
-
}
|
|
76
|
+
export default handler;
|
|
77
|
+
`;
|
|
185
78
|
|
|
186
79
|
export function registerSkillCommand(program) {
|
|
187
80
|
const skill = program
|
|
188
81
|
.command("skill")
|
|
189
|
-
.description(
|
|
82
|
+
.description(
|
|
83
|
+
"Manage and run AI skills (multi-layer: bundled + global + project)",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const loader = new CLISkillLoader();
|
|
190
87
|
|
|
191
88
|
// skill list
|
|
192
89
|
skill
|
|
@@ -195,20 +92,23 @@ export function registerSkillCommand(program) {
|
|
|
195
92
|
.option("--category <category>", "Filter by category")
|
|
196
93
|
.option("--tag <tag>", "Filter by tag")
|
|
197
94
|
.option("--runnable", "Only show skills that can run headless")
|
|
95
|
+
.option(
|
|
96
|
+
"--source <layer>",
|
|
97
|
+
"Filter by source layer (bundled, marketplace, managed, workspace)",
|
|
98
|
+
)
|
|
198
99
|
.option("--json", "Output as JSON")
|
|
199
100
|
.action(async (options) => {
|
|
200
|
-
const
|
|
201
|
-
|
|
101
|
+
const spinner = ora("Loading skills...").start();
|
|
102
|
+
let skills = loader.loadAll();
|
|
103
|
+
spinner.stop();
|
|
104
|
+
|
|
105
|
+
if (skills.length === 0) {
|
|
202
106
|
logger.error(
|
|
203
|
-
"
|
|
107
|
+
"No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
|
|
204
108
|
);
|
|
205
109
|
process.exit(1);
|
|
206
110
|
}
|
|
207
111
|
|
|
208
|
-
const spinner = ora("Loading skills...").start();
|
|
209
|
-
let skills = loadSkillMetadata(skillsDir);
|
|
210
|
-
spinner.stop();
|
|
211
|
-
|
|
212
112
|
// Filter
|
|
213
113
|
if (options.category) {
|
|
214
114
|
skills = skills.filter(
|
|
@@ -225,11 +125,14 @@ export function registerSkillCommand(program) {
|
|
|
225
125
|
if (options.runnable) {
|
|
226
126
|
skills = skills.filter((s) => s.hasHandler && canRunOnPlatform(s));
|
|
227
127
|
}
|
|
128
|
+
if (options.source) {
|
|
129
|
+
skills = skills.filter((s) => s.source === options.source);
|
|
130
|
+
}
|
|
228
131
|
|
|
229
132
|
if (options.json) {
|
|
230
133
|
console.log(
|
|
231
134
|
JSON.stringify(
|
|
232
|
-
skills.map(({ body, ...rest }) => rest),
|
|
135
|
+
skills.map(({ body, skillDir, ...rest }) => rest),
|
|
233
136
|
null,
|
|
234
137
|
2,
|
|
235
138
|
),
|
|
@@ -252,8 +155,9 @@ export function registerSkillCommand(program) {
|
|
|
252
155
|
for (const s of catSkills) {
|
|
253
156
|
const handler = s.hasHandler ? chalk.green("●") : chalk.gray("○");
|
|
254
157
|
const name = chalk.cyan(s.id.padEnd(30));
|
|
255
|
-
const desc = chalk.gray((s.description || "").substring(0,
|
|
256
|
-
|
|
158
|
+
const desc = chalk.gray((s.description || "").substring(0, 40));
|
|
159
|
+
const label = LAYER_LABELS[s.source] || chalk.gray(`[${s.source}]`);
|
|
160
|
+
logger.log(` ${handler} ${name} ${desc} ${label}`);
|
|
257
161
|
}
|
|
258
162
|
logger.log("");
|
|
259
163
|
}
|
|
@@ -268,13 +172,12 @@ export function registerSkillCommand(program) {
|
|
|
268
172
|
.command("categories")
|
|
269
173
|
.description("List skill categories")
|
|
270
174
|
.action(async () => {
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
logger.error("
|
|
175
|
+
const skills = loader.loadAll();
|
|
176
|
+
if (skills.length === 0) {
|
|
177
|
+
logger.error("No skills found.");
|
|
274
178
|
process.exit(1);
|
|
275
179
|
}
|
|
276
180
|
|
|
277
|
-
const skills = loadSkillMetadata(skillsDir);
|
|
278
181
|
const cats = {};
|
|
279
182
|
for (const s of skills) {
|
|
280
183
|
const cat = s.category || "uncategorized";
|
|
@@ -295,13 +198,7 @@ export function registerSkillCommand(program) {
|
|
|
295
198
|
.argument("<name>", "Skill name")
|
|
296
199
|
.option("--json", "Output as JSON")
|
|
297
200
|
.action(async (name, options) => {
|
|
298
|
-
const
|
|
299
|
-
if (!skillsDir) {
|
|
300
|
-
logger.error("Skills directory not found.");
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const skills = loadSkillMetadata(skillsDir);
|
|
201
|
+
const skills = loader.loadAll();
|
|
305
202
|
const s = skills.find(
|
|
306
203
|
(s) => s.id === name || s.dirName === name || s.id.includes(name),
|
|
307
204
|
);
|
|
@@ -318,7 +215,7 @@ export function registerSkillCommand(program) {
|
|
|
318
215
|
}
|
|
319
216
|
|
|
320
217
|
if (options.json) {
|
|
321
|
-
const { body, ...rest } = s;
|
|
218
|
+
const { body, skillDir, ...rest } = s;
|
|
322
219
|
console.log(JSON.stringify(rest, null, 2));
|
|
323
220
|
return;
|
|
324
221
|
}
|
|
@@ -326,6 +223,7 @@ export function registerSkillCommand(program) {
|
|
|
326
223
|
logger.log(chalk.bold(`\n${s.displayName}`));
|
|
327
224
|
logger.log(chalk.gray(`ID: ${s.id} v${s.version}`));
|
|
328
225
|
logger.log(chalk.gray(`Category: ${s.category}`));
|
|
226
|
+
logger.log(chalk.gray(`Source: ${s.source}`));
|
|
329
227
|
if (s.tags.length > 0) {
|
|
330
228
|
logger.log(chalk.gray(`Tags: ${s.tags.join(", ")}`));
|
|
331
229
|
}
|
|
@@ -351,13 +249,7 @@ export function registerSkillCommand(program) {
|
|
|
351
249
|
.description("Search skills by keyword")
|
|
352
250
|
.argument("<query>", "Search query")
|
|
353
251
|
.action(async (query) => {
|
|
354
|
-
const
|
|
355
|
-
if (!skillsDir) {
|
|
356
|
-
logger.error("Skills directory not found.");
|
|
357
|
-
process.exit(1);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const skills = loadSkillMetadata(skillsDir);
|
|
252
|
+
const skills = loader.loadAll();
|
|
361
253
|
const q = query.toLowerCase();
|
|
362
254
|
const matches = skills.filter(
|
|
363
255
|
(s) =>
|
|
@@ -377,8 +269,9 @@ export function registerSkillCommand(program) {
|
|
|
377
269
|
);
|
|
378
270
|
for (const s of matches) {
|
|
379
271
|
const handler = s.hasHandler ? chalk.green("●") : chalk.gray("○");
|
|
272
|
+
const label = LAYER_LABELS[s.source] || "";
|
|
380
273
|
logger.log(
|
|
381
|
-
` ${handler} ${chalk.cyan(s.id.padEnd(30))} ${chalk.gray(s.description.substring(0,
|
|
274
|
+
` ${handler} ${chalk.cyan(s.id.padEnd(30))} ${chalk.gray(s.description.substring(0, 40))} ${label}`,
|
|
382
275
|
);
|
|
383
276
|
}
|
|
384
277
|
logger.log("");
|
|
@@ -392,13 +285,7 @@ export function registerSkillCommand(program) {
|
|
|
392
285
|
.argument("[input...]", "Input for the skill")
|
|
393
286
|
.option("--json", "Output as JSON")
|
|
394
287
|
.action(async (name, inputParts, options) => {
|
|
395
|
-
const
|
|
396
|
-
if (!skillsDir) {
|
|
397
|
-
logger.error("Skills directory not found.");
|
|
398
|
-
process.exit(1);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const skills = loadSkillMetadata(skillsDir);
|
|
288
|
+
const skills = loader.loadAll();
|
|
402
289
|
const s = skills.find((sk) => sk.id === name || sk.dirName === name);
|
|
403
290
|
|
|
404
291
|
if (!s) {
|
|
@@ -422,19 +309,16 @@ export function registerSkillCommand(program) {
|
|
|
422
309
|
const input = inputParts.join(" ");
|
|
423
310
|
|
|
424
311
|
try {
|
|
425
|
-
|
|
426
|
-
const handlerPath = path.join(skillsDir, s.dirName, "handler.js");
|
|
312
|
+
const handlerPath = path.join(s.skillDir, "handler.js");
|
|
427
313
|
const imported = await import(
|
|
428
314
|
`file://${handlerPath.replace(/\\/g, "/")}`
|
|
429
315
|
);
|
|
430
316
|
const handler = imported.default || imported;
|
|
431
317
|
|
|
432
|
-
// Initialize if needed
|
|
433
318
|
if (handler.init) {
|
|
434
319
|
await handler.init(s);
|
|
435
320
|
}
|
|
436
321
|
|
|
437
|
-
// Execute
|
|
438
322
|
const task = {
|
|
439
323
|
params: { input },
|
|
440
324
|
input,
|
|
@@ -454,7 +338,6 @@ export function registerSkillCommand(program) {
|
|
|
454
338
|
} else if (result.success) {
|
|
455
339
|
logger.success(result.message || "Done");
|
|
456
340
|
if (result.result && typeof result.result === "object") {
|
|
457
|
-
// Pretty print result
|
|
458
341
|
for (const [key, val] of Object.entries(result.result)) {
|
|
459
342
|
if (typeof val === "string" && val.length > 200) {
|
|
460
343
|
logger.log(` ${chalk.cyan(key)}: ${val.substring(0, 200)}...`);
|
|
@@ -476,4 +359,174 @@ export function registerSkillCommand(program) {
|
|
|
476
359
|
process.exit(1);
|
|
477
360
|
}
|
|
478
361
|
});
|
|
362
|
+
|
|
363
|
+
// skill add — create a custom skill scaffold
|
|
364
|
+
skill
|
|
365
|
+
.command("add")
|
|
366
|
+
.description("Create a custom skill")
|
|
367
|
+
.argument("<name>", "Skill name")
|
|
368
|
+
.option(
|
|
369
|
+
"--global",
|
|
370
|
+
"Create as a global (managed) skill instead of project-level",
|
|
371
|
+
)
|
|
372
|
+
.action(async (name, options) => {
|
|
373
|
+
let targetDir;
|
|
374
|
+
|
|
375
|
+
if (options.global) {
|
|
376
|
+
const userData = getElectronUserDataDir();
|
|
377
|
+
targetDir = path.join(userData, "skills", name);
|
|
378
|
+
} else {
|
|
379
|
+
const projectRoot = findProjectRoot();
|
|
380
|
+
if (!projectRoot) {
|
|
381
|
+
logger.error(
|
|
382
|
+
'Not inside a ChainlessChain project. Run "chainlesschain init" first, or use --global.',
|
|
383
|
+
);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
targetDir = path.join(projectRoot, ".chainlesschain", "skills", name);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (fs.existsSync(targetDir)) {
|
|
390
|
+
logger.error(`Skill already exists: ${targetDir}`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
396
|
+
fs.writeFileSync(
|
|
397
|
+
path.join(targetDir, "SKILL.md"),
|
|
398
|
+
SKILL_TEMPLATE_MD(name),
|
|
399
|
+
"utf-8",
|
|
400
|
+
);
|
|
401
|
+
fs.writeFileSync(
|
|
402
|
+
path.join(targetDir, "handler.js"),
|
|
403
|
+
SKILL_TEMPLATE_HANDLER(name),
|
|
404
|
+
"utf-8",
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const scope = options.global ? "global" : "project";
|
|
408
|
+
logger.success(`Created ${scope} skill: ${chalk.cyan(name)}`);
|
|
409
|
+
logger.log(` ${chalk.gray(targetDir)}`);
|
|
410
|
+
logger.log("");
|
|
411
|
+
logger.log(chalk.bold("Files created:"));
|
|
412
|
+
logger.log(
|
|
413
|
+
` ${chalk.gray("SKILL.md")} — Skill metadata and documentation`,
|
|
414
|
+
);
|
|
415
|
+
logger.log(` ${chalk.gray("handler.js")} — Skill execution logic`);
|
|
416
|
+
logger.log("");
|
|
417
|
+
logger.log(
|
|
418
|
+
`Edit these files, then run: ${chalk.cyan(`chainlesschain skill run ${name} "test input"`)}`,
|
|
419
|
+
);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
logger.error(`Failed to create skill: ${err.message}`);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// skill remove — delete a custom skill
|
|
427
|
+
skill
|
|
428
|
+
.command("remove")
|
|
429
|
+
.description("Remove a custom skill (project or global)")
|
|
430
|
+
.argument("<name>", "Skill name")
|
|
431
|
+
.option("--global", "Remove from global (managed) skills")
|
|
432
|
+
.option("--force", "Skip confirmation")
|
|
433
|
+
.action(async (name, options) => {
|
|
434
|
+
let targetDir;
|
|
435
|
+
|
|
436
|
+
if (options.global) {
|
|
437
|
+
const userData = getElectronUserDataDir();
|
|
438
|
+
targetDir = path.join(userData, "skills", name);
|
|
439
|
+
} else {
|
|
440
|
+
const projectRoot = findProjectRoot();
|
|
441
|
+
if (!projectRoot) {
|
|
442
|
+
logger.error("Not inside a ChainlessChain project.");
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
targetDir = path.join(projectRoot, ".chainlesschain", "skills", name);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!fs.existsSync(targetDir)) {
|
|
449
|
+
logger.error(`Skill not found: ${name}`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!options.force) {
|
|
454
|
+
try {
|
|
455
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
456
|
+
const ok = await confirm({
|
|
457
|
+
message: `Remove skill "${name}" from ${targetDir}?`,
|
|
458
|
+
});
|
|
459
|
+
if (!ok) {
|
|
460
|
+
logger.info("Cancelled");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
} catch {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
470
|
+
logger.success(`Removed skill: ${name}`);
|
|
471
|
+
} catch (err) {
|
|
472
|
+
logger.error(`Failed to remove skill: ${err.message}`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// skill sources — show layer paths and counts
|
|
478
|
+
skill
|
|
479
|
+
.command("sources")
|
|
480
|
+
.description("Show skill source layers, paths, and counts")
|
|
481
|
+
.option("--json", "Output as JSON")
|
|
482
|
+
.action(async (options) => {
|
|
483
|
+
const layers = loader.getLayerPaths();
|
|
484
|
+
|
|
485
|
+
// Count skills per layer
|
|
486
|
+
const layerCounts = {};
|
|
487
|
+
const allSkills = loader.loadAll();
|
|
488
|
+
for (const s of allSkills) {
|
|
489
|
+
layerCounts[s.source] = (layerCounts[s.source] || 0) + 1;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (options.json) {
|
|
493
|
+
console.log(
|
|
494
|
+
JSON.stringify(
|
|
495
|
+
layers.map((l) => ({
|
|
496
|
+
...l,
|
|
497
|
+
count: layerCounts[l.layer] || 0,
|
|
498
|
+
})),
|
|
499
|
+
null,
|
|
500
|
+
2,
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
logger.log(chalk.bold("\nSkill Source Layers:\n"));
|
|
507
|
+
logger.log(
|
|
508
|
+
chalk.gray(
|
|
509
|
+
" Priority: workspace (highest) > managed > marketplace > bundled (lowest)\n",
|
|
510
|
+
),
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
514
|
+
const l = layers[i];
|
|
515
|
+
const count = layerCounts[l.layer] || 0;
|
|
516
|
+
const status = l.exists
|
|
517
|
+
? chalk.green("active")
|
|
518
|
+
: chalk.gray("not found");
|
|
519
|
+
const label = LAYER_LABELS[l.layer] || l.layer;
|
|
520
|
+
const priority = `(priority ${LAYER_NAMES.indexOf(l.layer)})`;
|
|
521
|
+
|
|
522
|
+
logger.log(` ${label} ${chalk.gray(priority)}`);
|
|
523
|
+
logger.log(` Path: ${chalk.gray(l.path || "(none)")}`);
|
|
524
|
+
logger.log(` Status: ${status} Skills: ${count}`);
|
|
525
|
+
logger.log("");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
logger.log(
|
|
529
|
+
` ${chalk.bold("Total:")} ${allSkills.length} skills resolved\n`,
|
|
530
|
+
);
|
|
531
|
+
});
|
|
479
532
|
}
|