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
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-layer skill loader for CLI
|
|
3
|
+
*
|
|
4
|
+
* 4-layer priority system (highest wins on name collision):
|
|
5
|
+
* 0 (lowest) bundled — desktop-app-vue/.../skills/builtin/
|
|
6
|
+
* 1 marketplace — <userData>/marketplace/skills/
|
|
7
|
+
* 2 managed — <userData>/skills/
|
|
8
|
+
* 3 (highest) workspace — <projectRoot>/.chainlesschain/skills/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { getElectronUserDataDir } from "./paths.js";
|
|
15
|
+
import { findProjectRoot } from "./project-detector.js";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
/** Layer names in priority order (lowest → highest) */
|
|
20
|
+
export const LAYER_NAMES = ["bundled", "marketplace", "managed", "workspace"];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Simple YAML frontmatter parser (no dependencies)
|
|
24
|
+
* Shared utility extracted from skill.js
|
|
25
|
+
*/
|
|
26
|
+
export function parseSkillMd(content) {
|
|
27
|
+
const lines = content.split("\n");
|
|
28
|
+
if (lines[0].trim() !== "---") return { data: {}, body: content };
|
|
29
|
+
|
|
30
|
+
let endIndex = -1;
|
|
31
|
+
for (let i = 1; i < lines.length; i++) {
|
|
32
|
+
if (lines[i].trim() === "---") {
|
|
33
|
+
endIndex = i;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (endIndex === -1) return { data: {}, body: content };
|
|
39
|
+
|
|
40
|
+
const yamlLines = lines.slice(1, endIndex);
|
|
41
|
+
const body = lines
|
|
42
|
+
.slice(endIndex + 1)
|
|
43
|
+
.join("\n")
|
|
44
|
+
.trim();
|
|
45
|
+
const data = {};
|
|
46
|
+
|
|
47
|
+
let currentKey = null;
|
|
48
|
+
let currentArray = null;
|
|
49
|
+
|
|
50
|
+
for (const line of yamlLines) {
|
|
51
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
52
|
+
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
|
|
55
|
+
if (trimmed.startsWith("- ")) {
|
|
56
|
+
const value = trimmed
|
|
57
|
+
.slice(2)
|
|
58
|
+
.trim()
|
|
59
|
+
.replace(/^['"]|['"]$/g, "");
|
|
60
|
+
if (currentArray) currentArray.push(value);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const colonIndex = trimmed.indexOf(":");
|
|
65
|
+
if (colonIndex > 0) {
|
|
66
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
67
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
68
|
+
|
|
69
|
+
// Convert kebab-case to camelCase
|
|
70
|
+
const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
71
|
+
|
|
72
|
+
if (value === "") {
|
|
73
|
+
currentKey = camelKey;
|
|
74
|
+
currentArray = null;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle inline arrays [a, b, c]
|
|
79
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
80
|
+
data[camelKey] = value
|
|
81
|
+
.slice(1, -1)
|
|
82
|
+
.split(",")
|
|
83
|
+
.map((v) => v.trim().replace(/^['"]|['"]$/g, ""))
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
currentArray = null;
|
|
86
|
+
currentKey = null;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle booleans and numbers
|
|
91
|
+
if (value === "true") value = true;
|
|
92
|
+
else if (value === "false") value = false;
|
|
93
|
+
else if (value === "null") value = null;
|
|
94
|
+
else if (/^\d+(\.\d+)?$/.test(value)) value = parseFloat(value);
|
|
95
|
+
else value = value.replace(/^['"]|['"]$/g, "");
|
|
96
|
+
|
|
97
|
+
data[camelKey] = value;
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(data[camelKey])) {
|
|
100
|
+
currentArray = data[camelKey];
|
|
101
|
+
} else {
|
|
102
|
+
currentArray = null;
|
|
103
|
+
}
|
|
104
|
+
currentKey = camelKey;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { data, body };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Multi-layer CLI skill loader
|
|
113
|
+
*/
|
|
114
|
+
export class CLISkillLoader {
|
|
115
|
+
constructor() {
|
|
116
|
+
this._cache = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get paths for each layer
|
|
121
|
+
* @returns {{ layer: string, path: string, exists: boolean }[]}
|
|
122
|
+
*/
|
|
123
|
+
getLayerPaths() {
|
|
124
|
+
const layers = [];
|
|
125
|
+
|
|
126
|
+
// Layer 0: bundled — desktop-app-vue builtin skills
|
|
127
|
+
const bundledCandidates = [
|
|
128
|
+
path.resolve(
|
|
129
|
+
__dirname,
|
|
130
|
+
"../../../../desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
|
|
131
|
+
),
|
|
132
|
+
path.resolve(
|
|
133
|
+
process.cwd(),
|
|
134
|
+
"desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
|
|
135
|
+
),
|
|
136
|
+
];
|
|
137
|
+
let bundledPath = null;
|
|
138
|
+
for (const c of bundledCandidates) {
|
|
139
|
+
if (fs.existsSync(c)) {
|
|
140
|
+
bundledPath = c;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
layers.push({
|
|
145
|
+
layer: "bundled",
|
|
146
|
+
path: bundledPath || bundledCandidates[0],
|
|
147
|
+
exists: bundledPath !== null,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Layer 1: marketplace — <userData>/marketplace/skills/
|
|
151
|
+
const userData = getElectronUserDataDir();
|
|
152
|
+
const marketplacePath = path.join(userData, "marketplace", "skills");
|
|
153
|
+
layers.push({
|
|
154
|
+
layer: "marketplace",
|
|
155
|
+
path: marketplacePath,
|
|
156
|
+
exists: fs.existsSync(marketplacePath),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Layer 2: managed — <userData>/skills/
|
|
160
|
+
const managedPath = path.join(userData, "skills");
|
|
161
|
+
layers.push({
|
|
162
|
+
layer: "managed",
|
|
163
|
+
path: managedPath,
|
|
164
|
+
exists: fs.existsSync(managedPath),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Layer 3: workspace — <projectRoot>/.chainlesschain/skills/
|
|
168
|
+
const projectRoot = findProjectRoot();
|
|
169
|
+
if (projectRoot) {
|
|
170
|
+
const workspacePath = path.join(projectRoot, ".chainlesschain", "skills");
|
|
171
|
+
layers.push({
|
|
172
|
+
layer: "workspace",
|
|
173
|
+
path: workspacePath,
|
|
174
|
+
exists: fs.existsSync(workspacePath),
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
layers.push({
|
|
178
|
+
layer: "workspace",
|
|
179
|
+
path: null,
|
|
180
|
+
exists: false,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return layers;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Load skills from a single directory
|
|
189
|
+
* @param {string} dir - Directory to scan
|
|
190
|
+
* @param {string} layer - Layer name for source tracking
|
|
191
|
+
* @returns {object[]} Array of skill metadata
|
|
192
|
+
*/
|
|
193
|
+
_loadFromDir(dir, layer) {
|
|
194
|
+
const skills = [];
|
|
195
|
+
if (!dir || !fs.existsSync(dir)) return skills;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const dirs = fs.readdirSync(dir, { withFileTypes: true });
|
|
199
|
+
for (const entry of dirs) {
|
|
200
|
+
if (!entry.isDirectory()) continue;
|
|
201
|
+
|
|
202
|
+
const skillMd = path.join(dir, entry.name, "SKILL.md");
|
|
203
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const content = fs.readFileSync(skillMd, "utf-8");
|
|
207
|
+
const { data, body } = parseSkillMd(content);
|
|
208
|
+
|
|
209
|
+
skills.push({
|
|
210
|
+
id: data.name || entry.name,
|
|
211
|
+
displayName: data.displayName || entry.name,
|
|
212
|
+
description: data.description || "",
|
|
213
|
+
version: data.version || "1.0.0",
|
|
214
|
+
category: data.category || "uncategorized",
|
|
215
|
+
tags: data.tags || [],
|
|
216
|
+
userInvocable: data.userInvocable !== false,
|
|
217
|
+
handler: data.handler || null,
|
|
218
|
+
capabilities: data.capabilities || [],
|
|
219
|
+
os: data.os || [],
|
|
220
|
+
dirName: entry.name,
|
|
221
|
+
hasHandler: fs.existsSync(path.join(dir, entry.name, "handler.js")),
|
|
222
|
+
body,
|
|
223
|
+
source: layer,
|
|
224
|
+
skillDir: path.join(dir, entry.name),
|
|
225
|
+
});
|
|
226
|
+
} catch {
|
|
227
|
+
// Skip malformed skill files
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Directory unreadable
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return skills;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Load all skills from all layers, applying priority override
|
|
239
|
+
* Higher-priority layers override same-name skills from lower layers.
|
|
240
|
+
* @returns {object[]} Resolved skill list
|
|
241
|
+
*/
|
|
242
|
+
loadAll() {
|
|
243
|
+
const layers = this.getLayerPaths();
|
|
244
|
+
const skillMap = new Map();
|
|
245
|
+
|
|
246
|
+
// Process in priority order (lowest first, so higher layers overwrite)
|
|
247
|
+
for (const { layer, path: layerPath, exists } of layers) {
|
|
248
|
+
if (!exists) continue;
|
|
249
|
+
const skills = this._loadFromDir(layerPath, layer);
|
|
250
|
+
for (const skill of skills) {
|
|
251
|
+
skillMap.set(skill.id, skill);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this._cache = Array.from(skillMap.values());
|
|
256
|
+
return this._cache;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get resolved skills (uses cache if available)
|
|
261
|
+
* @returns {object[]}
|
|
262
|
+
*/
|
|
263
|
+
getResolvedSkills() {
|
|
264
|
+
if (this._cache) return this._cache;
|
|
265
|
+
return this.loadAll();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Clear the cache
|
|
270
|
+
*/
|
|
271
|
+
clearCache() {
|
|
272
|
+
this._cache = null;
|
|
273
|
+
}
|
|
274
|
+
}
|