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.
- package/README.md +403 -8
- package/bin/chainlesschain.js +4 -0
- package/package.json +7 -2
- package/src/commands/agent.js +30 -0
- package/src/commands/ask.js +114 -0
- package/src/commands/audit.js +286 -0
- package/src/commands/auth.js +387 -0
- package/src/commands/browse.js +184 -0
- package/src/commands/chat.js +35 -0
- package/src/commands/db.js +152 -0
- package/src/commands/did.js +376 -0
- package/src/commands/encrypt.js +233 -0
- package/src/commands/export.js +125 -0
- package/src/commands/git.js +215 -0
- package/src/commands/import.js +259 -0
- package/src/commands/instinct.js +202 -0
- package/src/commands/llm.js +288 -0
- package/src/commands/mcp.js +302 -0
- package/src/commands/memory.js +282 -0
- package/src/commands/note.js +489 -0
- package/src/commands/org.js +505 -0
- package/src/commands/p2p.js +274 -0
- package/src/commands/plugin.js +398 -0
- package/src/commands/search.js +237 -0
- package/src/commands/session.js +238 -0
- package/src/commands/skill.js +479 -0
- package/src/commands/sync.js +249 -0
- package/src/commands/tokens.js +214 -0
- package/src/commands/wallet.js +416 -0
- package/src/index.js +65 -0
- package/src/lib/audit-logger.js +364 -0
- package/src/lib/bm25-search.js +322 -0
- package/src/lib/browser-automation.js +216 -0
- package/src/lib/crypto-manager.js +246 -0
- package/src/lib/did-manager.js +270 -0
- package/src/lib/ensure-utf8.js +59 -0
- package/src/lib/git-integration.js +220 -0
- package/src/lib/instinct-manager.js +190 -0
- package/src/lib/knowledge-exporter.js +302 -0
- package/src/lib/knowledge-importer.js +293 -0
- package/src/lib/llm-providers.js +325 -0
- package/src/lib/mcp-client.js +413 -0
- package/src/lib/memory-manager.js +211 -0
- package/src/lib/note-versioning.js +244 -0
- package/src/lib/org-manager.js +424 -0
- package/src/lib/p2p-manager.js +317 -0
- package/src/lib/pdf-parser.js +96 -0
- package/src/lib/permission-engine.js +374 -0
- package/src/lib/plan-mode.js +333 -0
- package/src/lib/platform.js +15 -0
- package/src/lib/plugin-manager.js +312 -0
- package/src/lib/response-cache.js +156 -0
- package/src/lib/session-manager.js +189 -0
- package/src/lib/sync-manager.js +347 -0
- package/src/lib/token-tracker.js +200 -0
- package/src/lib/wallet-manager.js +348 -0
- package/src/repl/agent-repl.js +912 -0
- package/src/repl/chat-repl.js +262 -0
- package/src/runtime/bootstrap.js +159 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive AI chat command
|
|
3
|
+
* chainlesschain chat [--model qwen2:7b] [--provider ollama] [--agent]
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { startChatRepl } from "../repl/chat-repl.js";
|
|
7
|
+
import { startAgentRepl } from "../repl/agent-repl.js";
|
|
8
|
+
|
|
9
|
+
export function registerChatCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("chat")
|
|
12
|
+
.description("Start an interactive AI chat session")
|
|
13
|
+
.option("--model <model>", "Model name", "qwen2:7b")
|
|
14
|
+
.option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
|
|
15
|
+
.option("--base-url <url>", "API base URL")
|
|
16
|
+
.option("--api-key <key>", "API key")
|
|
17
|
+
.option(
|
|
18
|
+
"--agent",
|
|
19
|
+
"Agentic mode - AI can read/write files and run commands (like Claude Code)",
|
|
20
|
+
)
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
const replOptions = {
|
|
23
|
+
model: options.model,
|
|
24
|
+
provider: options.provider,
|
|
25
|
+
baseUrl: options.baseUrl,
|
|
26
|
+
apiKey: options.apiKey,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (options.agent) {
|
|
30
|
+
await startAgentRepl(replOptions);
|
|
31
|
+
} else {
|
|
32
|
+
await startChatRepl(replOptions);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database management commands
|
|
3
|
+
* chainlesschain db init|info|backup|restore|migrate
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { logger } from "../lib/logger.js";
|
|
9
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
10
|
+
|
|
11
|
+
export function registerDbCommand(program) {
|
|
12
|
+
const db = program.command("db").description("Database management");
|
|
13
|
+
|
|
14
|
+
// db init
|
|
15
|
+
db.command("init")
|
|
16
|
+
.description("Initialize the database")
|
|
17
|
+
.option("--path <path>", "Custom database path")
|
|
18
|
+
.option("--force", "Overwrite existing database")
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const spinner = ora("Initializing database...").start();
|
|
21
|
+
try {
|
|
22
|
+
const ctx = await bootstrap({
|
|
23
|
+
dbPath: options.path,
|
|
24
|
+
verbose: program.opts().verbose,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!ctx.db) {
|
|
28
|
+
spinner.fail("Failed to initialize database");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const info = ctx.db.getInfo();
|
|
33
|
+
spinner.succeed("Database initialized");
|
|
34
|
+
logger.log(` Path: ${chalk.cyan(info.path)}`);
|
|
35
|
+
logger.log(` Driver: ${chalk.cyan(info.driver)}`);
|
|
36
|
+
logger.log(` Tables: ${chalk.cyan(info.tableCount)}`);
|
|
37
|
+
|
|
38
|
+
await shutdown();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
spinner.fail(`Database init failed: ${err.message}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// db info
|
|
46
|
+
db.command("info")
|
|
47
|
+
.description("Show database information")
|
|
48
|
+
.option("--json", "Output as JSON")
|
|
49
|
+
.action(async (options) => {
|
|
50
|
+
try {
|
|
51
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
52
|
+
|
|
53
|
+
if (!ctx.db) {
|
|
54
|
+
logger.error("No database available");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const info = ctx.db.getInfo();
|
|
59
|
+
|
|
60
|
+
if (options.json) {
|
|
61
|
+
console.log(JSON.stringify(info, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
logger.log(chalk.bold("Database Info:"));
|
|
64
|
+
logger.log(` Path: ${chalk.cyan(info.path)}`);
|
|
65
|
+
logger.log(` Driver: ${chalk.cyan(info.driver)}`);
|
|
66
|
+
logger.log(
|
|
67
|
+
` Encrypted: ${info.encrypted ? chalk.green("Yes") : chalk.gray("No")}`,
|
|
68
|
+
);
|
|
69
|
+
logger.log(` Size: ${chalk.cyan(info.fileSizeMB + " MB")}`);
|
|
70
|
+
logger.log(` Tables: ${chalk.cyan(info.tableCount)}`);
|
|
71
|
+
if (info.tables.length > 0) {
|
|
72
|
+
logger.log(
|
|
73
|
+
` Table list: ${chalk.gray(info.tables.slice(0, 10).join(", "))}${info.tables.length > 10 ? "..." : ""}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await shutdown();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.error(`Failed to get db info: ${err.message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// db backup
|
|
86
|
+
db.command("backup")
|
|
87
|
+
.description("Create database backup")
|
|
88
|
+
.argument("[output]", "Backup file path")
|
|
89
|
+
.action(async (output) => {
|
|
90
|
+
const spinner = ora("Creating backup...").start();
|
|
91
|
+
try {
|
|
92
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
93
|
+
|
|
94
|
+
if (!ctx.db) {
|
|
95
|
+
spinner.fail("No database to backup");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
100
|
+
const backupPath =
|
|
101
|
+
output || `${ctx.env.dataDir}/chainlesschain.db.backup.${timestamp}`;
|
|
102
|
+
|
|
103
|
+
ctx.db.backup(backupPath);
|
|
104
|
+
spinner.succeed(`Backup created: ${chalk.cyan(backupPath)}`);
|
|
105
|
+
|
|
106
|
+
await shutdown();
|
|
107
|
+
} catch (err) {
|
|
108
|
+
spinner.fail(`Backup failed: ${err.message}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// db restore
|
|
114
|
+
db.command("restore")
|
|
115
|
+
.description("Restore database from backup")
|
|
116
|
+
.argument("<backup>", "Backup file path")
|
|
117
|
+
.option("--force", "Skip confirmation")
|
|
118
|
+
.action(async (backup, options) => {
|
|
119
|
+
try {
|
|
120
|
+
const fs = await import("fs");
|
|
121
|
+
if (!fs.existsSync(backup)) {
|
|
122
|
+
logger.error(`Backup file not found: ${backup}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!options.force) {
|
|
127
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
128
|
+
const ok = await confirm({
|
|
129
|
+
message: "This will overwrite the current database. Continue?",
|
|
130
|
+
});
|
|
131
|
+
if (!ok) {
|
|
132
|
+
logger.info("Restore cancelled");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const ctx = await bootstrap({
|
|
138
|
+
skipDb: true,
|
|
139
|
+
verbose: program.opts().verbose,
|
|
140
|
+
});
|
|
141
|
+
const dbPath = `${ctx.env.dataDir}/chainlesschain.db`;
|
|
142
|
+
|
|
143
|
+
fs.copyFileSync(backup, dbPath);
|
|
144
|
+
logger.success(`Database restored from ${chalk.cyan(backup)}`);
|
|
145
|
+
|
|
146
|
+
await shutdown();
|
|
147
|
+
} catch (err) {
|
|
148
|
+
logger.error(`Restore failed: ${err.message}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|