chainlesschain 0.37.9 → 0.37.11

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 (84) hide show
  1. package/README.md +309 -19
  2. package/bin/chainlesschain.js +4 -0
  3. package/package.json +1 -1
  4. package/src/commands/a2a.js +374 -0
  5. package/src/commands/audit.js +286 -0
  6. package/src/commands/auth.js +387 -0
  7. package/src/commands/bi.js +240 -0
  8. package/src/commands/browse.js +184 -0
  9. package/src/commands/cowork.js +317 -0
  10. package/src/commands/did.js +376 -0
  11. package/src/commands/economy.js +375 -0
  12. package/src/commands/encrypt.js +233 -0
  13. package/src/commands/evolution.js +398 -0
  14. package/src/commands/export.js +125 -0
  15. package/src/commands/git.js +215 -0
  16. package/src/commands/hmemory.js +273 -0
  17. package/src/commands/hook.js +260 -0
  18. package/src/commands/import.js +259 -0
  19. package/src/commands/init.js +184 -0
  20. package/src/commands/instinct.js +202 -0
  21. package/src/commands/llm.js +155 -4
  22. package/src/commands/lowcode.js +320 -0
  23. package/src/commands/mcp.js +302 -0
  24. package/src/commands/memory.js +282 -0
  25. package/src/commands/note.js +187 -0
  26. package/src/commands/org.js +505 -0
  27. package/src/commands/p2p.js +274 -0
  28. package/src/commands/plugin.js +451 -0
  29. package/src/commands/sandbox.js +366 -0
  30. package/src/commands/search.js +237 -0
  31. package/src/commands/session.js +238 -0
  32. package/src/commands/skill.js +254 -201
  33. package/src/commands/sync.js +249 -0
  34. package/src/commands/tokens.js +214 -0
  35. package/src/commands/wallet.js +416 -0
  36. package/src/commands/workflow.js +359 -0
  37. package/src/commands/zkp.js +277 -0
  38. package/src/index.js +93 -1
  39. package/src/lib/a2a-protocol.js +371 -0
  40. package/src/lib/agent-coordinator.js +273 -0
  41. package/src/lib/agent-economy.js +369 -0
  42. package/src/lib/app-builder.js +377 -0
  43. package/src/lib/audit-logger.js +364 -0
  44. package/src/lib/bi-engine.js +299 -0
  45. package/src/lib/bm25-search.js +322 -0
  46. package/src/lib/browser-automation.js +216 -0
  47. package/src/lib/cowork/ab-comparator-cli.js +180 -0
  48. package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
  49. package/src/lib/cowork/debate-review-cli.js +144 -0
  50. package/src/lib/cowork/decision-kb-cli.js +153 -0
  51. package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
  52. package/src/lib/cowork-adapter.js +106 -0
  53. package/src/lib/crypto-manager.js +246 -0
  54. package/src/lib/did-manager.js +270 -0
  55. package/src/lib/ensure-utf8.js +59 -0
  56. package/src/lib/evolution-system.js +508 -0
  57. package/src/lib/git-integration.js +220 -0
  58. package/src/lib/hierarchical-memory.js +471 -0
  59. package/src/lib/hook-manager.js +387 -0
  60. package/src/lib/instinct-manager.js +190 -0
  61. package/src/lib/knowledge-exporter.js +302 -0
  62. package/src/lib/knowledge-importer.js +293 -0
  63. package/src/lib/llm-providers.js +325 -0
  64. package/src/lib/mcp-client.js +413 -0
  65. package/src/lib/memory-manager.js +211 -0
  66. package/src/lib/note-versioning.js +244 -0
  67. package/src/lib/org-manager.js +424 -0
  68. package/src/lib/p2p-manager.js +317 -0
  69. package/src/lib/pdf-parser.js +96 -0
  70. package/src/lib/permission-engine.js +374 -0
  71. package/src/lib/plan-mode.js +333 -0
  72. package/src/lib/plugin-manager.js +430 -0
  73. package/src/lib/project-detector.js +53 -0
  74. package/src/lib/response-cache.js +156 -0
  75. package/src/lib/sandbox-v2.js +503 -0
  76. package/src/lib/service-container.js +183 -0
  77. package/src/lib/session-manager.js +189 -0
  78. package/src/lib/skill-loader.js +274 -0
  79. package/src/lib/sync-manager.js +347 -0
  80. package/src/lib/token-tracker.js +200 -0
  81. package/src/lib/wallet-manager.js +348 -0
  82. package/src/lib/workflow-engine.js +503 -0
  83. package/src/lib/zkp-engine.js +241 -0
  84. package/src/repl/agent-repl.js +259 -124
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Session manager for CLI
3
+ *
4
+ * Persists chat/agent conversations to DB for resume and export.
5
+ * Lightweight port of desktop-app-vue/src/main/llm/session-manager.js
6
+ */
7
+
8
+ import { createHash } from "crypto";
9
+
10
+ function ensureSessionsTable(db) {
11
+ db.exec(`
12
+ CREATE TABLE IF NOT EXISTS llm_sessions (
13
+ id TEXT PRIMARY KEY,
14
+ title TEXT DEFAULT 'Untitled',
15
+ provider TEXT DEFAULT '',
16
+ model TEXT DEFAULT '',
17
+ message_count INTEGER DEFAULT 0,
18
+ messages TEXT DEFAULT '[]',
19
+ summary TEXT DEFAULT '',
20
+ created_at TEXT DEFAULT (datetime('now')),
21
+ updated_at TEXT DEFAULT (datetime('now'))
22
+ )
23
+ `);
24
+ }
25
+
26
+ /**
27
+ * Create a new session
28
+ */
29
+ export function createSession(db, options = {}) {
30
+ ensureSessionsTable(db);
31
+
32
+ const id =
33
+ options.id ||
34
+ `session-${Date.now()}-${createHash("sha256").update(Math.random().toString()).digest("hex").slice(0, 6)}`;
35
+
36
+ db.prepare(
37
+ `INSERT INTO llm_sessions (id, title, provider, model, messages) VALUES (?, ?, ?, ?, ?)`,
38
+ ).run(
39
+ id,
40
+ options.title || "Untitled",
41
+ options.provider || "",
42
+ options.model || "",
43
+ JSON.stringify(options.messages || []),
44
+ );
45
+
46
+ return { id, title: options.title || "Untitled" };
47
+ }
48
+
49
+ /**
50
+ * Add a message to a session
51
+ */
52
+ export function addMessage(db, sessionId, role, content) {
53
+ ensureSessionsTable(db);
54
+
55
+ const session = db
56
+ .prepare("SELECT messages, message_count FROM llm_sessions WHERE id = ?")
57
+ .get(sessionId);
58
+
59
+ if (!session) return null;
60
+
61
+ const messages = JSON.parse(session.messages || "[]");
62
+ messages.push({ role, content, timestamp: new Date().toISOString() });
63
+
64
+ db.prepare(
65
+ `UPDATE llm_sessions SET messages = ?, message_count = ?, updated_at = datetime('now') WHERE id = ?`,
66
+ ).run(JSON.stringify(messages), messages.length, sessionId);
67
+
68
+ return { messageCount: messages.length };
69
+ }
70
+
71
+ /**
72
+ * Save all messages at once (batch update)
73
+ */
74
+ export function saveMessages(db, sessionId, messages) {
75
+ ensureSessionsTable(db);
76
+
77
+ const result = db
78
+ .prepare(
79
+ `UPDATE llm_sessions SET messages = ?, message_count = ?, updated_at = datetime('now') WHERE id = ?`,
80
+ )
81
+ .run(JSON.stringify(messages), messages.length, sessionId);
82
+
83
+ return { messageCount: messages.length, updated: result.changes > 0 };
84
+ }
85
+
86
+ /**
87
+ * Get a session by ID
88
+ */
89
+ export function getSession(db, sessionId) {
90
+ ensureSessionsTable(db);
91
+
92
+ // Try exact match first, then prefix match
93
+ let session = db
94
+ .prepare("SELECT * FROM llm_sessions WHERE id = ?")
95
+ .get(sessionId);
96
+
97
+ if (!session) {
98
+ session = db
99
+ .prepare("SELECT * FROM llm_sessions WHERE id LIKE ? LIMIT 1")
100
+ .get(`${sessionId}%`);
101
+ }
102
+
103
+ if (!session) return null;
104
+
105
+ return {
106
+ ...session,
107
+ messages: JSON.parse(session.messages || "[]"),
108
+ };
109
+ }
110
+
111
+ /**
112
+ * List all sessions
113
+ */
114
+ export function listSessions(db, options = {}) {
115
+ ensureSessionsTable(db);
116
+
117
+ const limit = options.limit || 20;
118
+
119
+ return db
120
+ .prepare(
121
+ `SELECT id, title, provider, model, message_count, summary, created_at, updated_at
122
+ FROM llm_sessions
123
+ ORDER BY updated_at DESC
124
+ LIMIT ?`,
125
+ )
126
+ .all(limit);
127
+ }
128
+
129
+ /**
130
+ * Update session title or summary
131
+ */
132
+ export function updateSession(db, sessionId, updates) {
133
+ ensureSessionsTable(db);
134
+
135
+ if (updates.title) {
136
+ db.prepare(
137
+ "UPDATE llm_sessions SET title = ?, updated_at = datetime('now') WHERE id = ?",
138
+ ).run(updates.title, sessionId);
139
+ }
140
+ if (updates.summary) {
141
+ db.prepare(
142
+ "UPDATE llm_sessions SET summary = ?, updated_at = datetime('now') WHERE id = ?",
143
+ ).run(updates.summary, sessionId);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Delete a session
149
+ */
150
+ export function deleteSession(db, sessionId) {
151
+ ensureSessionsTable(db);
152
+ const result = db
153
+ .prepare("DELETE FROM llm_sessions WHERE id = ?")
154
+ .run(sessionId);
155
+ return result.changes > 0;
156
+ }
157
+
158
+ /**
159
+ * Export session as markdown
160
+ */
161
+ export function exportSessionMarkdown(session) {
162
+ const lines = [
163
+ `# ${session.title}`,
164
+ "",
165
+ `- **Created**: ${session.created_at}`,
166
+ `- **Provider**: ${session.provider || "unknown"}`,
167
+ `- **Model**: ${session.model || "unknown"}`,
168
+ `- **Messages**: ${session.message_count}`,
169
+ "",
170
+ "---",
171
+ "",
172
+ ];
173
+
174
+ const messages =
175
+ typeof session.messages === "string"
176
+ ? JSON.parse(session.messages)
177
+ : session.messages || [];
178
+
179
+ for (const msg of messages) {
180
+ if (msg.role === "system") continue;
181
+ const label = msg.role === "user" ? "**You**" : "**AI**";
182
+ lines.push(`### ${label}`);
183
+ lines.push("");
184
+ lines.push(msg.content || "");
185
+ lines.push("");
186
+ }
187
+
188
+ return lines.join("\n");
189
+ }
@@ -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
+ }