chainlesschain 0.37.8 → 0.37.10

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 (59) hide show
  1. package/README.md +403 -8
  2. package/bin/chainlesschain.js +4 -0
  3. package/package.json +7 -2
  4. package/src/commands/agent.js +30 -0
  5. package/src/commands/ask.js +114 -0
  6. package/src/commands/audit.js +286 -0
  7. package/src/commands/auth.js +387 -0
  8. package/src/commands/browse.js +184 -0
  9. package/src/commands/chat.js +35 -0
  10. package/src/commands/db.js +152 -0
  11. package/src/commands/did.js +376 -0
  12. package/src/commands/encrypt.js +233 -0
  13. package/src/commands/export.js +125 -0
  14. package/src/commands/git.js +215 -0
  15. package/src/commands/import.js +259 -0
  16. package/src/commands/instinct.js +202 -0
  17. package/src/commands/llm.js +288 -0
  18. package/src/commands/mcp.js +302 -0
  19. package/src/commands/memory.js +282 -0
  20. package/src/commands/note.js +489 -0
  21. package/src/commands/org.js +505 -0
  22. package/src/commands/p2p.js +274 -0
  23. package/src/commands/plugin.js +398 -0
  24. package/src/commands/search.js +237 -0
  25. package/src/commands/session.js +238 -0
  26. package/src/commands/skill.js +479 -0
  27. package/src/commands/sync.js +249 -0
  28. package/src/commands/tokens.js +214 -0
  29. package/src/commands/wallet.js +416 -0
  30. package/src/index.js +65 -0
  31. package/src/lib/audit-logger.js +364 -0
  32. package/src/lib/bm25-search.js +322 -0
  33. package/src/lib/browser-automation.js +216 -0
  34. package/src/lib/crypto-manager.js +246 -0
  35. package/src/lib/did-manager.js +270 -0
  36. package/src/lib/ensure-utf8.js +59 -0
  37. package/src/lib/git-integration.js +220 -0
  38. package/src/lib/instinct-manager.js +190 -0
  39. package/src/lib/knowledge-exporter.js +302 -0
  40. package/src/lib/knowledge-importer.js +293 -0
  41. package/src/lib/llm-providers.js +325 -0
  42. package/src/lib/mcp-client.js +413 -0
  43. package/src/lib/memory-manager.js +211 -0
  44. package/src/lib/note-versioning.js +244 -0
  45. package/src/lib/org-manager.js +424 -0
  46. package/src/lib/p2p-manager.js +317 -0
  47. package/src/lib/pdf-parser.js +96 -0
  48. package/src/lib/permission-engine.js +374 -0
  49. package/src/lib/plan-mode.js +333 -0
  50. package/src/lib/platform.js +15 -0
  51. package/src/lib/plugin-manager.js +312 -0
  52. package/src/lib/response-cache.js +156 -0
  53. package/src/lib/session-manager.js +189 -0
  54. package/src/lib/sync-manager.js +347 -0
  55. package/src/lib/token-tracker.js +200 -0
  56. package/src/lib/wallet-manager.js +348 -0
  57. package/src/repl/agent-repl.js +912 -0
  58. package/src/repl/chat-repl.js +262 -0
  59. package/src/runtime/bootstrap.js +159 -0
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Instinct Manager — learns user preferences from agent interactions.
3
+ * Tracks patterns like preferred tools, coding style, response format, etc.
4
+ */
5
+
6
+ /**
7
+ * Ensure instincts table exists.
8
+ */
9
+ export function ensureInstinctsTable(db) {
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS instincts (
12
+ id TEXT PRIMARY KEY,
13
+ category TEXT NOT NULL,
14
+ pattern TEXT NOT NULL,
15
+ confidence REAL DEFAULT 0.5,
16
+ occurrences INTEGER DEFAULT 1,
17
+ last_seen TEXT DEFAULT (datetime('now')),
18
+ created_at TEXT DEFAULT (datetime('now'))
19
+ )
20
+ `);
21
+ }
22
+
23
+ function generateId() {
24
+ const hex = () =>
25
+ Math.floor(Math.random() * 0x10000)
26
+ .toString(16)
27
+ .padStart(4, "0");
28
+ return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`;
29
+ }
30
+
31
+ /**
32
+ * Instinct categories.
33
+ */
34
+ export const INSTINCT_CATEGORIES = {
35
+ TOOL_PREFERENCE: "tool_preference",
36
+ CODING_STYLE: "coding_style",
37
+ RESPONSE_FORMAT: "response_format",
38
+ LANGUAGE: "language",
39
+ WORKFLOW: "workflow",
40
+ BEHAVIOR: "behavior",
41
+ };
42
+
43
+ /**
44
+ * Record an instinct observation.
45
+ * If an instinct with the same category+pattern exists, increment its confidence and occurrences.
46
+ */
47
+ export function recordInstinct(db, category, pattern) {
48
+ ensureInstinctsTable(db);
49
+
50
+ // Check if exists
51
+ const existing = db
52
+ .prepare("SELECT * FROM instincts WHERE category = ? AND pattern = ?")
53
+ .get(category, pattern);
54
+
55
+ if (existing) {
56
+ // Boost confidence (asymptotic approach to 1.0)
57
+ const newConfidence = Math.min(
58
+ 0.99,
59
+ existing.confidence + (1 - existing.confidence) * 0.1,
60
+ );
61
+ db.prepare(
62
+ "UPDATE instincts SET confidence = ?, occurrences = occurrences + 1, last_seen = datetime('now') WHERE id = ?",
63
+ ).run(newConfidence, existing.id);
64
+
65
+ return {
66
+ id: existing.id,
67
+ category,
68
+ pattern,
69
+ confidence: newConfidence,
70
+ occurrences: (existing.occurrences || 1) + 1,
71
+ isNew: false,
72
+ };
73
+ }
74
+
75
+ // Create new instinct
76
+ const id = generateId();
77
+ db.prepare(
78
+ "INSERT INTO instincts (id, category, pattern, confidence, occurrences) VALUES (?, ?, ?, ?, ?)",
79
+ ).run(id, category, pattern, 0.5, 1);
80
+
81
+ return {
82
+ id,
83
+ category,
84
+ pattern,
85
+ confidence: 0.5,
86
+ occurrences: 1,
87
+ isNew: true,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Get all instincts, optionally filtered by category.
93
+ */
94
+ export function getInstincts(db, options = {}) {
95
+ ensureInstinctsTable(db);
96
+
97
+ let sql = "SELECT * FROM instincts";
98
+ const params = [];
99
+
100
+ if (options.category) {
101
+ sql += " WHERE category = ?";
102
+ params.push(options.category);
103
+ }
104
+
105
+ sql += " ORDER BY confidence DESC";
106
+
107
+ if (options.limit) {
108
+ sql += " LIMIT ?";
109
+ params.push(options.limit);
110
+ }
111
+
112
+ return db.prepare(sql).all(...params);
113
+ }
114
+
115
+ /**
116
+ * Get top instincts (confidence >= threshold).
117
+ */
118
+ export function getStrongInstincts(db, threshold = 0.7) {
119
+ ensureInstinctsTable(db);
120
+ return db
121
+ .prepare(
122
+ "SELECT * FROM instincts WHERE confidence >= ? ORDER BY confidence DESC",
123
+ )
124
+ .all(threshold);
125
+ }
126
+
127
+ /**
128
+ * Delete an instinct by ID or prefix.
129
+ */
130
+ export function deleteInstinct(db, id) {
131
+ ensureInstinctsTable(db);
132
+ const result = db
133
+ .prepare("DELETE FROM instincts WHERE id LIKE ?")
134
+ .run(`${id}%`);
135
+ return result.changes > 0;
136
+ }
137
+
138
+ /**
139
+ * Reset all instincts (clear the table).
140
+ */
141
+ export function resetInstincts(db) {
142
+ ensureInstinctsTable(db);
143
+ const result = db.prepare("DELETE FROM instincts WHERE 1=1").run();
144
+ return result.changes;
145
+ }
146
+
147
+ /**
148
+ * Decay instincts that haven't been seen recently.
149
+ * Reduces confidence of old instincts over time.
150
+ */
151
+ export function decayInstincts(db, daysThreshold = 30) {
152
+ ensureInstinctsTable(db);
153
+ // Simple decay: multiply confidence by 0.9 for old instincts
154
+ const rows = db.prepare("SELECT * FROM instincts").all();
155
+ let decayed = 0;
156
+
157
+ const cutoff = new Date();
158
+ cutoff.setDate(cutoff.getDate() - daysThreshold);
159
+ const cutoffStr = cutoff.toISOString().replace("T", " ").slice(0, 19);
160
+
161
+ for (const row of rows) {
162
+ if (row.last_seen && row.last_seen < cutoffStr) {
163
+ const newConfidence = Math.max(0.1, (row.confidence || 0.5) * 0.9);
164
+ db.prepare("UPDATE instincts SET confidence = ? WHERE id = ?").run(
165
+ newConfidence,
166
+ row.id,
167
+ );
168
+ decayed++;
169
+ }
170
+ }
171
+
172
+ return decayed;
173
+ }
174
+
175
+ /**
176
+ * Generate a system prompt fragment from strong instincts.
177
+ */
178
+ export function generateInstinctPrompt(db) {
179
+ const strong = getStrongInstincts(db, 0.6);
180
+ if (strong.length === 0) return "";
181
+
182
+ const lines = ["Based on learned preferences:"];
183
+ for (const inst of strong) {
184
+ lines.push(
185
+ `- [${inst.category}] ${inst.pattern} (confidence: ${(inst.confidence * 100).toFixed(0)}%)`,
186
+ );
187
+ }
188
+
189
+ return lines.join("\n");
190
+ }
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Knowledge exporter — export notes to Markdown files or static HTML site.
3
+ */
4
+
5
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
6
+ import { join } from "path";
7
+
8
+ /**
9
+ * Ensure the notes table exists
10
+ */
11
+ function ensureNotesTable(db) {
12
+ db.exec(`
13
+ CREATE TABLE IF NOT EXISTS notes (
14
+ id TEXT PRIMARY KEY,
15
+ title TEXT NOT NULL,
16
+ content TEXT DEFAULT '',
17
+ tags TEXT DEFAULT '[]',
18
+ category TEXT DEFAULT 'general',
19
+ created_at TEXT DEFAULT (datetime('now')),
20
+ updated_at TEXT DEFAULT (datetime('now')),
21
+ deleted_at TEXT DEFAULT NULL
22
+ )
23
+ `);
24
+ }
25
+
26
+ /**
27
+ * Fetch all active notes from the database.
28
+ */
29
+ export function fetchNotes(db, { category, tag, limit } = {}) {
30
+ ensureNotesTable(db);
31
+
32
+ let sql = "SELECT * FROM notes WHERE deleted_at IS NULL";
33
+ const params = [];
34
+
35
+ if (category) {
36
+ sql += " AND category = ?";
37
+ params.push(category);
38
+ }
39
+
40
+ sql += " ORDER BY created_at DESC";
41
+
42
+ if (limit) {
43
+ sql += " LIMIT ?";
44
+ params.push(limit);
45
+ }
46
+
47
+ let notes = db.prepare(sql).all(...params);
48
+
49
+ // Filter by tag in-memory
50
+ if (tag) {
51
+ notes = notes.filter((n) => {
52
+ try {
53
+ const tags = JSON.parse(n.tags || "[]");
54
+ return tags.includes(tag);
55
+ } catch {
56
+ return false;
57
+ }
58
+ });
59
+ }
60
+
61
+ return notes;
62
+ }
63
+
64
+ /**
65
+ * Sanitize a filename (remove invalid characters).
66
+ */
67
+ export function sanitizeFilename(name) {
68
+ return name
69
+ .replace(/[<>:"/\\|?*]/g, "")
70
+ .replace(/\s+/g, "-")
71
+ .substring(0, 200);
72
+ }
73
+
74
+ // ─── Markdown Export ────────────────────────────────────────────────
75
+
76
+ /**
77
+ * Convert a note to markdown with YAML frontmatter.
78
+ */
79
+ export function noteToMarkdown(note) {
80
+ const tags = JSON.parse(note.tags || "[]");
81
+ const lines = [
82
+ "---",
83
+ `title: "${note.title.replace(/"/g, '\\"')}"`,
84
+ `category: ${note.category || "general"}`,
85
+ `tags: [${tags.map((t) => `"${t}"`).join(", ")}]`,
86
+ `date: ${note.created_at || ""}`,
87
+ `id: ${note.id}`,
88
+ "---",
89
+ "",
90
+ `# ${note.title}`,
91
+ "",
92
+ note.content || "",
93
+ ];
94
+
95
+ return lines.join("\n");
96
+ }
97
+
98
+ /**
99
+ * Export notes to a directory as individual markdown files.
100
+ * Groups notes by category into subdirectories.
101
+ */
102
+ export function exportToMarkdown(db, outputDir, options = {}) {
103
+ const notes = fetchNotes(db, options);
104
+ if (!existsSync(outputDir)) {
105
+ mkdirSync(outputDir, { recursive: true });
106
+ }
107
+
108
+ const exported = [];
109
+
110
+ for (const note of notes) {
111
+ const category = note.category || "general";
112
+ const catDir = join(outputDir, sanitizeFilename(category));
113
+ if (!existsSync(catDir)) {
114
+ mkdirSync(catDir, { recursive: true });
115
+ }
116
+
117
+ const filename = `${sanitizeFilename(note.title)}.md`;
118
+ const filePath = join(catDir, filename);
119
+ const markdown = noteToMarkdown(note);
120
+
121
+ writeFileSync(filePath, markdown, "utf-8");
122
+ exported.push({
123
+ id: note.id,
124
+ title: note.title,
125
+ path: `${category}/${filename}`,
126
+ });
127
+ }
128
+
129
+ return exported;
130
+ }
131
+
132
+ // ─── Static HTML Site Export ────────────────────────────────────────
133
+
134
+ /**
135
+ * Generate a minimal HTML page for a note.
136
+ */
137
+ export function noteToHtml(note) {
138
+ const tags = JSON.parse(note.tags || "[]");
139
+ const tagsHtml = tags
140
+ .map((t) => `<span class="tag">${escapeHtml(t)}</span>`)
141
+ .join(" ");
142
+ const contentHtml = markdownToSimpleHtml(note.content || "");
143
+
144
+ return `<!DOCTYPE html>
145
+ <html lang="en">
146
+ <head>
147
+ <meta charset="UTF-8">
148
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
149
+ <title>${escapeHtml(note.title)}</title>
150
+ <link rel="stylesheet" href="../style.css">
151
+ </head>
152
+ <body>
153
+ <nav><a href="../index.html">Home</a></nav>
154
+ <article>
155
+ <h1>${escapeHtml(note.title)}</h1>
156
+ <div class="meta">
157
+ <time>${note.created_at || ""}</time>
158
+ <span class="category">${escapeHtml(note.category || "general")}</span>
159
+ ${tagsHtml}
160
+ </div>
161
+ <div class="content">${contentHtml}</div>
162
+ </article>
163
+ </body>
164
+ </html>`;
165
+ }
166
+
167
+ /**
168
+ * Generate the index page listing all notes.
169
+ */
170
+ export function generateIndexHtml(
171
+ notes,
172
+ siteTitle = "ChainlessChain Knowledge Base",
173
+ ) {
174
+ const noteLinks = notes
175
+ .map((n) => {
176
+ const tags = JSON.parse(n.tags || "[]");
177
+ const tagsHtml = tags
178
+ .map((t) => `<span class="tag">${escapeHtml(t)}</span>`)
179
+ .join(" ");
180
+ const cat = sanitizeFilename(n.category || "general");
181
+ const file = sanitizeFilename(n.title) + ".html";
182
+ return `<li>
183
+ <a href="${cat}/${file}">${escapeHtml(n.title)}</a>
184
+ <span class="category">${escapeHtml(n.category || "general")}</span>
185
+ ${tagsHtml}
186
+ <time>${n.created_at || ""}</time>
187
+ </li>`;
188
+ })
189
+ .join("\n");
190
+
191
+ return `<!DOCTYPE html>
192
+ <html lang="en">
193
+ <head>
194
+ <meta charset="UTF-8">
195
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
196
+ <title>${escapeHtml(siteTitle)}</title>
197
+ <link rel="stylesheet" href="style.css">
198
+ </head>
199
+ <body>
200
+ <header><h1>${escapeHtml(siteTitle)}</h1></header>
201
+ <main>
202
+ <p>${notes.length} notes</p>
203
+ <ul class="note-list">${noteLinks}</ul>
204
+ </main>
205
+ </body>
206
+ </html>`;
207
+ }
208
+
209
+ /**
210
+ * Generate a minimal CSS stylesheet.
211
+ */
212
+ export function generateStyleCss() {
213
+ return `* { margin: 0; padding: 0; box-sizing: border-box; }
214
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; color: #333; line-height: 1.6; }
215
+ nav { margin-bottom: 2rem; }
216
+ nav a { color: #0066cc; text-decoration: none; }
217
+ h1 { margin-bottom: 1rem; }
218
+ .meta { color: #666; margin-bottom: 1.5rem; font-size: 0.9rem; }
219
+ .tag { background: #e8f0fe; color: #1a73e8; padding: 2px 8px; border-radius: 12px; font-size: 0.8rem; margin-right: 4px; }
220
+ .category { color: #5f6368; margin-right: 8px; }
221
+ .content { line-height: 1.8; }
222
+ .content p { margin-bottom: 1rem; }
223
+ .content pre { background: #f5f5f5; padding: 1rem; border-radius: 4px; overflow-x: auto; margin-bottom: 1rem; }
224
+ .content code { background: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-size: 0.9em; }
225
+ .note-list { list-style: none; }
226
+ .note-list li { padding: 0.75rem 0; border-bottom: 1px solid #eee; }
227
+ .note-list a { color: #1a73e8; text-decoration: none; font-weight: 500; margin-right: 8px; }
228
+ time { color: #999; font-size: 0.85rem; }
229
+ header { border-bottom: 2px solid #1a73e8; padding-bottom: 1rem; margin-bottom: 2rem; }`;
230
+ }
231
+
232
+ /**
233
+ * Export notes as a static HTML site.
234
+ */
235
+ export function exportToSite(db, outputDir, options = {}) {
236
+ const notes = fetchNotes(db, options);
237
+ if (!existsSync(outputDir)) {
238
+ mkdirSync(outputDir, { recursive: true });
239
+ }
240
+
241
+ // Write CSS
242
+ writeFileSync(join(outputDir, "style.css"), generateStyleCss(), "utf-8");
243
+
244
+ // Write index
245
+ writeFileSync(
246
+ join(outputDir, "index.html"),
247
+ generateIndexHtml(notes, options.title),
248
+ "utf-8",
249
+ );
250
+
251
+ // Write individual pages
252
+ const exported = [];
253
+ for (const note of notes) {
254
+ const category = note.category || "general";
255
+ const catDir = join(outputDir, sanitizeFilename(category));
256
+ if (!existsSync(catDir)) {
257
+ mkdirSync(catDir, { recursive: true });
258
+ }
259
+
260
+ const filename = `${sanitizeFilename(note.title)}.html`;
261
+ const filePath = join(catDir, filename);
262
+ writeFileSync(filePath, noteToHtml(note), "utf-8");
263
+
264
+ exported.push({
265
+ id: note.id,
266
+ title: note.title,
267
+ path: `${sanitizeFilename(category)}/${filename}`,
268
+ });
269
+ }
270
+
271
+ return exported;
272
+ }
273
+
274
+ // ─── Helpers ────────────────────────────────────────────────────────
275
+
276
+ function escapeHtml(str) {
277
+ return (str || "")
278
+ .replace(/&/g, "&amp;")
279
+ .replace(/</g, "&lt;")
280
+ .replace(/>/g, "&gt;")
281
+ .replace(/"/g, "&quot;");
282
+ }
283
+
284
+ /**
285
+ * Very simple markdown→HTML for note content.
286
+ * Handles headings, paragraphs, code blocks, bold, italic, links.
287
+ */
288
+ function markdownToSimpleHtml(md) {
289
+ return md
290
+ .replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>")
291
+ .replace(/`([^`]+)`/g, "<code>$1</code>")
292
+ .replace(/^### (.+)$/gm, "<h3>$1</h3>")
293
+ .replace(/^## (.+)$/gm, "<h2>$1</h2>")
294
+ .replace(/^# (.+)$/gm, "<h1>$1</h1>")
295
+ .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
296
+ .replace(/\*(.+?)\*/g, "<em>$1</em>")
297
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
298
+ .replace(/^- (.+)$/gm, "<li>$1</li>")
299
+ .replace(/\n\n/g, "</p><p>")
300
+ .replace(/^/, "<p>")
301
+ .replace(/$/, "</p>");
302
+ }