chainlesschain 0.37.9 → 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 (51) 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/audit.js +286 -0
  5. package/src/commands/auth.js +387 -0
  6. package/src/commands/browse.js +184 -0
  7. package/src/commands/did.js +376 -0
  8. package/src/commands/encrypt.js +233 -0
  9. package/src/commands/export.js +125 -0
  10. package/src/commands/git.js +215 -0
  11. package/src/commands/import.js +259 -0
  12. package/src/commands/instinct.js +202 -0
  13. package/src/commands/llm.js +155 -4
  14. package/src/commands/mcp.js +302 -0
  15. package/src/commands/memory.js +282 -0
  16. package/src/commands/note.js +187 -0
  17. package/src/commands/org.js +505 -0
  18. package/src/commands/p2p.js +274 -0
  19. package/src/commands/plugin.js +398 -0
  20. package/src/commands/search.js +237 -0
  21. package/src/commands/session.js +238 -0
  22. package/src/commands/sync.js +249 -0
  23. package/src/commands/tokens.js +214 -0
  24. package/src/commands/wallet.js +416 -0
  25. package/src/index.js +49 -1
  26. package/src/lib/audit-logger.js +364 -0
  27. package/src/lib/bm25-search.js +322 -0
  28. package/src/lib/browser-automation.js +216 -0
  29. package/src/lib/crypto-manager.js +246 -0
  30. package/src/lib/did-manager.js +270 -0
  31. package/src/lib/ensure-utf8.js +59 -0
  32. package/src/lib/git-integration.js +220 -0
  33. package/src/lib/instinct-manager.js +190 -0
  34. package/src/lib/knowledge-exporter.js +302 -0
  35. package/src/lib/knowledge-importer.js +293 -0
  36. package/src/lib/llm-providers.js +325 -0
  37. package/src/lib/mcp-client.js +413 -0
  38. package/src/lib/memory-manager.js +211 -0
  39. package/src/lib/note-versioning.js +244 -0
  40. package/src/lib/org-manager.js +424 -0
  41. package/src/lib/p2p-manager.js +317 -0
  42. package/src/lib/pdf-parser.js +96 -0
  43. package/src/lib/permission-engine.js +374 -0
  44. package/src/lib/plan-mode.js +333 -0
  45. package/src/lib/plugin-manager.js +312 -0
  46. package/src/lib/response-cache.js +156 -0
  47. package/src/lib/session-manager.js +189 -0
  48. package/src/lib/sync-manager.js +347 -0
  49. package/src/lib/token-tracker.js +200 -0
  50. package/src/lib/wallet-manager.js +348 -0
  51. package/src/repl/agent-repl.js +142 -12
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Auth / RBAC commands
3
+ * chainlesschain auth roles|grant|revoke|check|permissions|users
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import { logger } from "../lib/logger.js";
8
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
+ import {
10
+ getRoles,
11
+ createRole,
12
+ deleteRole,
13
+ grantRole,
14
+ revokeRole,
15
+ grantPermission,
16
+ revokePermission,
17
+ getUserPermissions,
18
+ checkPermission,
19
+ listUserRoles,
20
+ PERMISSION_SCOPES,
21
+ } from "../lib/permission-engine.js";
22
+
23
+ export function registerAuthCommand(program) {
24
+ const auth = program
25
+ .command("auth")
26
+ .description("RBAC permission management");
27
+
28
+ // auth roles
29
+ auth
30
+ .command("roles", { isDefault: true })
31
+ .description("List all roles")
32
+ .option("--json", "Output as JSON")
33
+ .action(async (options) => {
34
+ try {
35
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
36
+ if (!ctx.db) {
37
+ logger.error("Database not available");
38
+ process.exit(1);
39
+ }
40
+ const db = ctx.db.getDatabase();
41
+ const roles = getRoles(db);
42
+
43
+ if (options.json) {
44
+ console.log(JSON.stringify(roles, null, 2));
45
+ } else if (roles.length === 0) {
46
+ logger.info("No roles defined");
47
+ } else {
48
+ logger.log(chalk.bold(`Roles (${roles.length}):\n`));
49
+ for (const role of roles) {
50
+ const tag = role.isBuiltin
51
+ ? chalk.gray(" [built-in]")
52
+ : chalk.blue(" [custom]");
53
+ logger.log(
54
+ ` ${chalk.cyan(role.name.padEnd(15))}${tag} ${role.description || ""}`,
55
+ );
56
+ logger.log(
57
+ ` ${chalk.gray("permissions:")} ${role.permissions.join(", ")}`,
58
+ );
59
+ }
60
+ }
61
+
62
+ await shutdown();
63
+ } catch (err) {
64
+ logger.error(`Failed: ${err.message}`);
65
+ process.exit(1);
66
+ }
67
+ });
68
+
69
+ // auth create-role
70
+ auth
71
+ .command("create-role")
72
+ .description("Create a custom role")
73
+ .argument("<name>", "Role name")
74
+ .option("-d, --description <desc>", "Role description")
75
+ .option("-p, --permissions <perms>", "Comma-separated permissions")
76
+ .action(async (name, options) => {
77
+ try {
78
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
79
+ if (!ctx.db) {
80
+ logger.error("Database not available");
81
+ process.exit(1);
82
+ }
83
+ const db = ctx.db.getDatabase();
84
+
85
+ const perms = options.permissions
86
+ ? options.permissions.split(",").map((s) => s.trim())
87
+ : [];
88
+ const role = createRole(db, name, options.description, perms);
89
+
90
+ logger.success(`Role created: ${role.name}`);
91
+ if (role.permissions.length > 0) {
92
+ logger.log(
93
+ ` ${chalk.bold("Permissions:")} ${role.permissions.join(", ")}`,
94
+ );
95
+ }
96
+
97
+ await shutdown();
98
+ } catch (err) {
99
+ logger.error(`Failed: ${err.message}`);
100
+ process.exit(1);
101
+ }
102
+ });
103
+
104
+ // auth delete-role
105
+ auth
106
+ .command("delete-role")
107
+ .description("Delete a custom role")
108
+ .argument("<name>", "Role name")
109
+ .option("--force", "Skip confirmation")
110
+ .action(async (name, options) => {
111
+ try {
112
+ if (!options.force) {
113
+ const { confirm } = await import("@inquirer/prompts");
114
+ const ok = await confirm({
115
+ message: `Delete role "${name}"? All grants for this role will be removed.`,
116
+ });
117
+ if (!ok) {
118
+ logger.info("Cancelled");
119
+ return;
120
+ }
121
+ }
122
+
123
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
124
+ if (!ctx.db) {
125
+ logger.error("Database not available");
126
+ process.exit(1);
127
+ }
128
+ const db = ctx.db.getDatabase();
129
+ const ok = deleteRole(db, name);
130
+
131
+ if (ok) {
132
+ logger.success(`Role deleted: ${name}`);
133
+ } else {
134
+ logger.error(`Role not found or is built-in: ${name}`);
135
+ }
136
+
137
+ await shutdown();
138
+ } catch (err) {
139
+ logger.error(`Failed: ${err.message}`);
140
+ process.exit(1);
141
+ }
142
+ });
143
+
144
+ // auth grant
145
+ auth
146
+ .command("grant")
147
+ .description("Grant a role to a user")
148
+ .argument("<user-did>", "User DID")
149
+ .argument("<role>", "Role name")
150
+ .option("--expires <date>", "Expiration date (ISO 8601)")
151
+ .action(async (userDid, role, options) => {
152
+ try {
153
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
154
+ if (!ctx.db) {
155
+ logger.error("Database not available");
156
+ process.exit(1);
157
+ }
158
+ const db = ctx.db.getDatabase();
159
+ const grant = grantRole(db, userDid, role, null, options.expires);
160
+
161
+ logger.success(`Granted role "${role}" to ${userDid}`);
162
+ if (grant.expiresAt) {
163
+ logger.log(` ${chalk.bold("Expires:")} ${grant.expiresAt}`);
164
+ }
165
+
166
+ await shutdown();
167
+ } catch (err) {
168
+ logger.error(`Failed: ${err.message}`);
169
+ process.exit(1);
170
+ }
171
+ });
172
+
173
+ // auth revoke
174
+ auth
175
+ .command("revoke")
176
+ .description("Revoke a role from a user")
177
+ .argument("<user-did>", "User DID")
178
+ .argument("<role>", "Role name")
179
+ .action(async (userDid, role) => {
180
+ try {
181
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
182
+ if (!ctx.db) {
183
+ logger.error("Database not available");
184
+ process.exit(1);
185
+ }
186
+ const db = ctx.db.getDatabase();
187
+ const ok = revokeRole(db, userDid, role);
188
+
189
+ if (ok) {
190
+ logger.success(`Revoked role "${role}" from ${userDid}`);
191
+ } else {
192
+ logger.error("Grant not found");
193
+ }
194
+
195
+ await shutdown();
196
+ } catch (err) {
197
+ logger.error(`Failed: ${err.message}`);
198
+ process.exit(1);
199
+ }
200
+ });
201
+
202
+ // auth grant-permission (direct permission)
203
+ auth
204
+ .command("grant-permission")
205
+ .description("Grant a direct permission to a user")
206
+ .argument("<user-did>", "User DID")
207
+ .argument("<permission>", "Permission scope (e.g., note:read)")
208
+ .action(async (userDid, permission) => {
209
+ try {
210
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
211
+ if (!ctx.db) {
212
+ logger.error("Database not available");
213
+ process.exit(1);
214
+ }
215
+ const db = ctx.db.getDatabase();
216
+ grantPermission(db, userDid, permission);
217
+ logger.success(`Granted permission "${permission}" to ${userDid}`);
218
+
219
+ await shutdown();
220
+ } catch (err) {
221
+ logger.error(`Failed: ${err.message}`);
222
+ process.exit(1);
223
+ }
224
+ });
225
+
226
+ // auth revoke-permission
227
+ auth
228
+ .command("revoke-permission")
229
+ .description("Revoke a direct permission from a user")
230
+ .argument("<user-did>", "User DID")
231
+ .argument("<permission>", "Permission scope")
232
+ .action(async (userDid, permission) => {
233
+ try {
234
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
235
+ if (!ctx.db) {
236
+ logger.error("Database not available");
237
+ process.exit(1);
238
+ }
239
+ const db = ctx.db.getDatabase();
240
+ const ok = revokePermission(db, userDid, permission);
241
+
242
+ if (ok) {
243
+ logger.success(`Revoked permission "${permission}" from ${userDid}`);
244
+ } else {
245
+ logger.error("Permission grant not found");
246
+ }
247
+
248
+ await shutdown();
249
+ } catch (err) {
250
+ logger.error(`Failed: ${err.message}`);
251
+ process.exit(1);
252
+ }
253
+ });
254
+
255
+ // auth check
256
+ auth
257
+ .command("check")
258
+ .description("Check if a user has a specific permission")
259
+ .argument("<user-did>", "User DID")
260
+ .argument("<permission>", "Permission to check")
261
+ .option("--json", "Output as JSON")
262
+ .action(async (userDid, permission, options) => {
263
+ try {
264
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
265
+ if (!ctx.db) {
266
+ logger.error("Database not available");
267
+ process.exit(1);
268
+ }
269
+ const db = ctx.db.getDatabase();
270
+ const allowed = checkPermission(db, userDid, permission);
271
+
272
+ if (options.json) {
273
+ console.log(
274
+ JSON.stringify({ userDid, permission, allowed }, null, 2),
275
+ );
276
+ } else if (allowed) {
277
+ logger.success(
278
+ `${chalk.green("ALLOWED")} — ${userDid} has permission: ${permission}`,
279
+ );
280
+ } else {
281
+ logger.log(
282
+ `${chalk.red("DENIED")} — ${userDid} does not have permission: ${permission}`,
283
+ );
284
+ }
285
+
286
+ await shutdown();
287
+ } catch (err) {
288
+ logger.error(`Failed: ${err.message}`);
289
+ process.exit(1);
290
+ }
291
+ });
292
+
293
+ // auth permissions
294
+ auth
295
+ .command("permissions")
296
+ .description("Show all permissions for a user")
297
+ .argument("<user-did>", "User DID")
298
+ .option("--json", "Output as JSON")
299
+ .action(async (userDid, options) => {
300
+ try {
301
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
302
+ if (!ctx.db) {
303
+ logger.error("Database not available");
304
+ process.exit(1);
305
+ }
306
+ const db = ctx.db.getDatabase();
307
+ const perms = getUserPermissions(db, userDid);
308
+
309
+ if (options.json) {
310
+ console.log(JSON.stringify(perms, null, 2));
311
+ } else {
312
+ logger.log(chalk.bold(`Permissions for ${chalk.cyan(userDid)}:\n`));
313
+ if (perms.isAdmin) {
314
+ logger.log(` ${chalk.green("ADMIN")} — Full access (wildcard *)`);
315
+ }
316
+ if (perms.roles.length > 0) {
317
+ logger.log(` ${chalk.bold("Roles:")} ${perms.roles.join(", ")}`);
318
+ }
319
+ if (perms.directPermissions.length > 0) {
320
+ logger.log(
321
+ ` ${chalk.bold("Direct:")} ${perms.directPermissions.join(", ")}`,
322
+ );
323
+ }
324
+ if (perms.effectivePermissions.length > 0) {
325
+ logger.log(
326
+ ` ${chalk.bold("Effective:")} ${perms.effectivePermissions.join(", ")}`,
327
+ );
328
+ } else {
329
+ logger.log(` ${chalk.gray("No permissions assigned")}`);
330
+ }
331
+ }
332
+
333
+ await shutdown();
334
+ } catch (err) {
335
+ logger.error(`Failed: ${err.message}`);
336
+ process.exit(1);
337
+ }
338
+ });
339
+
340
+ // auth users
341
+ auth
342
+ .command("users")
343
+ .description("List all users with role assignments")
344
+ .option("--json", "Output as JSON")
345
+ .action(async (options) => {
346
+ try {
347
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
348
+ if (!ctx.db) {
349
+ logger.error("Database not available");
350
+ process.exit(1);
351
+ }
352
+ const db = ctx.db.getDatabase();
353
+ const users = listUserRoles(db);
354
+
355
+ if (options.json) {
356
+ console.log(JSON.stringify(users, null, 2));
357
+ } else if (users.length === 0) {
358
+ logger.info("No role assignments yet");
359
+ } else {
360
+ logger.log(chalk.bold(`Users with roles (${users.length}):\n`));
361
+ for (const u of users) {
362
+ logger.log(` ${chalk.cyan(u.userDid)}`);
363
+ logger.log(` ${chalk.gray("roles:")} ${u.roles.join(", ")}`);
364
+ }
365
+ }
366
+
367
+ await shutdown();
368
+ } catch (err) {
369
+ logger.error(`Failed: ${err.message}`);
370
+ process.exit(1);
371
+ }
372
+ });
373
+
374
+ // auth scopes
375
+ auth
376
+ .command("scopes")
377
+ .description("List all available permission scopes")
378
+ .action(async () => {
379
+ logger.log(chalk.bold("Available Permission Scopes:\n"));
380
+ for (const scope of PERMISSION_SCOPES) {
381
+ const [resource, action] = scope.split(":");
382
+ logger.log(
383
+ ` ${chalk.cyan(resource.padEnd(12))}:${chalk.white(action)}`,
384
+ );
385
+ }
386
+ });
387
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Browser automation commands
3
+ * chainlesschain browse <url> | browse scrape <url> | browse screenshot <url>
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import { logger } from "../lib/logger.js";
9
+ import {
10
+ fetchPage,
11
+ extractText,
12
+ extractTitle,
13
+ extractMeta,
14
+ querySelectorAll,
15
+ extractLinks,
16
+ takeScreenshot,
17
+ } from "../lib/browser-automation.js";
18
+
19
+ export function registerBrowseCommand(program) {
20
+ const browse = program
21
+ .command("browse")
22
+ .description("Headless browser automation and web scraping");
23
+
24
+ // browse fetch — fetch and display page content
25
+ browse
26
+ .command("fetch")
27
+ .description("Fetch a URL and display text content")
28
+ .argument("<url>", "URL to fetch")
29
+ .option("--html", "Show raw HTML instead of text")
30
+ .option("--links", "Extract links only")
31
+ .option("--json", "Output as JSON")
32
+ .action(async (url, options) => {
33
+ try {
34
+ const spinner = ora(`Fetching ${url}...`).start();
35
+ const result = await fetchPage(url);
36
+ spinner.stop();
37
+
38
+ const title = extractTitle(result.html);
39
+ const text = extractText(result.html);
40
+ const description = extractMeta(result.html);
41
+
42
+ if (options.json) {
43
+ const output = {
44
+ url: result.url,
45
+ status: result.status,
46
+ title,
47
+ description,
48
+ size: result.size,
49
+ };
50
+ if (options.html) output.html = result.html;
51
+ else if (options.links)
52
+ output.links = extractLinks(result.html, result.url);
53
+ else output.text = text;
54
+ console.log(JSON.stringify(output, null, 2));
55
+ return;
56
+ }
57
+
58
+ if (options.links) {
59
+ const links = extractLinks(result.html, result.url);
60
+ logger.log(
61
+ chalk.bold(`Links from ${title || url} (${links.length}):\n`),
62
+ );
63
+ for (const link of links.slice(0, 50)) {
64
+ logger.log(
65
+ ` ${chalk.cyan(link.text.substring(0, 60).padEnd(62))} ${chalk.gray(link.href)}`,
66
+ );
67
+ }
68
+ if (links.length > 50)
69
+ logger.log(chalk.gray(` ... and ${links.length - 50} more`));
70
+ return;
71
+ }
72
+
73
+ if (options.html) {
74
+ console.log(result.html);
75
+ return;
76
+ }
77
+
78
+ logger.log(chalk.bold(title || url));
79
+ if (description) logger.log(chalk.gray(description));
80
+ logger.log(
81
+ chalk.gray(`${result.size} bytes | ${result.contentType}\n`),
82
+ );
83
+ logger.log(text.substring(0, 5000));
84
+ if (text.length > 5000) {
85
+ logger.log(
86
+ chalk.gray(`\n... truncated (${text.length} chars total)`),
87
+ );
88
+ }
89
+ } catch (err) {
90
+ logger.error(`Fetch failed: ${err.message}`);
91
+ process.exit(1);
92
+ }
93
+ });
94
+
95
+ // browse scrape — scrape elements matching a CSS selector
96
+ browse
97
+ .command("scrape")
98
+ .description("Scrape elements from a URL using CSS selector")
99
+ .argument("<url>", "URL to scrape")
100
+ .requiredOption("-s, --selector <css>", "CSS selector (tag, .class, #id)")
101
+ .option("-n, --limit <n>", "Max results", "20")
102
+ .option("--json", "Output as JSON")
103
+ .action(async (url, options) => {
104
+ try {
105
+ const spinner = ora(`Scraping ${url}...`).start();
106
+ const result = await fetchPage(url);
107
+ const elements = querySelectorAll(result.html, options.selector);
108
+ spinner.stop();
109
+
110
+ const limit = parseInt(options.limit) || 20;
111
+ const limited = elements.slice(0, limit);
112
+
113
+ if (options.json) {
114
+ console.log(
115
+ JSON.stringify(
116
+ {
117
+ url: result.url,
118
+ selector: options.selector,
119
+ count: elements.length,
120
+ results: limited.map((e) => ({ text: e.text })),
121
+ },
122
+ null,
123
+ 2,
124
+ ),
125
+ );
126
+ return;
127
+ }
128
+
129
+ if (elements.length === 0) {
130
+ logger.info(`No elements matching "${options.selector}"`);
131
+ } else {
132
+ logger.log(
133
+ chalk.bold(
134
+ `Scraped ${elements.length} elements matching "${options.selector}":\n`,
135
+ ),
136
+ );
137
+ for (let i = 0; i < limited.length; i++) {
138
+ const text = limited[i].text.substring(0, 200).replace(/\n/g, " ");
139
+ logger.log(` ${chalk.gray(`[${i + 1}]`)} ${text}`);
140
+ }
141
+ if (elements.length > limit) {
142
+ logger.log(chalk.gray(` ... ${elements.length - limit} more`));
143
+ }
144
+ }
145
+ } catch (err) {
146
+ logger.error(`Scrape failed: ${err.message}`);
147
+ process.exit(1);
148
+ }
149
+ });
150
+
151
+ // browse screenshot — take a screenshot (requires playwright)
152
+ browse
153
+ .command("screenshot")
154
+ .description("Take a screenshot of a URL (requires playwright)")
155
+ .argument("<url>", "URL to screenshot")
156
+ .option("-o, --output <path>", "Output file path", "screenshot.png")
157
+ .option("--width <n>", "Viewport width", "1280")
158
+ .option("--height <n>", "Viewport height", "720")
159
+ .option("--full-page", "Capture full page")
160
+ .option("--json", "Output as JSON")
161
+ .action(async (url, options) => {
162
+ try {
163
+ const spinner = ora(`Taking screenshot of ${url}...`).start();
164
+ const result = await takeScreenshot(url, options.output, {
165
+ width: parseInt(options.width),
166
+ height: parseInt(options.height),
167
+ fullPage: !!options.fullPage,
168
+ });
169
+ spinner.stop();
170
+
171
+ if (options.json) {
172
+ console.log(JSON.stringify(result, null, 2));
173
+ } else if (result.success) {
174
+ logger.success(`Screenshot saved: ${chalk.cyan(result.path)}`);
175
+ } else {
176
+ logger.error(result.error);
177
+ logger.info("Install playwright: npm install -g playwright");
178
+ }
179
+ } catch (err) {
180
+ logger.error(`Screenshot failed: ${err.message}`);
181
+ process.exit(1);
182
+ }
183
+ });
184
+ }