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.
Files changed (39) hide show
  1. package/README.md +166 -10
  2. package/package.json +1 -1
  3. package/src/commands/a2a.js +374 -0
  4. package/src/commands/bi.js +240 -0
  5. package/src/commands/cowork.js +317 -0
  6. package/src/commands/economy.js +375 -0
  7. package/src/commands/evolution.js +398 -0
  8. package/src/commands/hmemory.js +273 -0
  9. package/src/commands/hook.js +260 -0
  10. package/src/commands/init.js +184 -0
  11. package/src/commands/lowcode.js +320 -0
  12. package/src/commands/plugin.js +55 -2
  13. package/src/commands/sandbox.js +366 -0
  14. package/src/commands/skill.js +254 -201
  15. package/src/commands/workflow.js +359 -0
  16. package/src/commands/zkp.js +277 -0
  17. package/src/index.js +44 -0
  18. package/src/lib/a2a-protocol.js +371 -0
  19. package/src/lib/agent-coordinator.js +273 -0
  20. package/src/lib/agent-economy.js +369 -0
  21. package/src/lib/app-builder.js +377 -0
  22. package/src/lib/bi-engine.js +299 -0
  23. package/src/lib/cowork/ab-comparator-cli.js +180 -0
  24. package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
  25. package/src/lib/cowork/debate-review-cli.js +144 -0
  26. package/src/lib/cowork/decision-kb-cli.js +153 -0
  27. package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
  28. package/src/lib/cowork-adapter.js +106 -0
  29. package/src/lib/evolution-system.js +508 -0
  30. package/src/lib/hierarchical-memory.js +471 -0
  31. package/src/lib/hook-manager.js +387 -0
  32. package/src/lib/plugin-manager.js +118 -0
  33. package/src/lib/project-detector.js +53 -0
  34. package/src/lib/sandbox-v2.js +503 -0
  35. package/src/lib/service-container.js +183 -0
  36. package/src/lib/skill-loader.js +274 -0
  37. package/src/lib/workflow-engine.js +503 -0
  38. package/src/lib/zkp-engine.js +241 -0
  39. 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
+ }