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,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive AI chat REPL with streaming output
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Streaming token output
|
|
6
|
+
* - Slash commands: /exit, /model, /provider, /clear, /history
|
|
7
|
+
* - Conversation history
|
|
8
|
+
* - Session auto-save
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import readline from "readline";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { logger } from "../lib/logger.js";
|
|
14
|
+
|
|
15
|
+
const SLASH_COMMANDS = {
|
|
16
|
+
"/exit": "Exit the chat",
|
|
17
|
+
"/quit": "Exit the chat",
|
|
18
|
+
"/model": "Show or change model (/model [name])",
|
|
19
|
+
"/provider": "Show or change provider (/provider [name])",
|
|
20
|
+
"/clear": "Clear conversation history",
|
|
21
|
+
"/history": "Show conversation history",
|
|
22
|
+
"/help": "Show available commands",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Stream a response from Ollama
|
|
27
|
+
*/
|
|
28
|
+
async function streamOllama(messages, model, baseUrl, onToken) {
|
|
29
|
+
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
model,
|
|
34
|
+
messages,
|
|
35
|
+
stream: true,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const reader = response.body.getReader();
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
let fullResponse = "";
|
|
46
|
+
|
|
47
|
+
while (true) {
|
|
48
|
+
const { done, value } = await reader.read();
|
|
49
|
+
if (done) break;
|
|
50
|
+
|
|
51
|
+
const text = decoder.decode(value, { stream: true });
|
|
52
|
+
const lines = text.split("\n").filter(Boolean);
|
|
53
|
+
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
try {
|
|
56
|
+
const json = JSON.parse(line);
|
|
57
|
+
if (json.message?.content) {
|
|
58
|
+
fullResponse += json.message.content;
|
|
59
|
+
onToken(json.message.content);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Partial JSON, skip
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return fullResponse;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stream a response from OpenAI-compatible API
|
|
72
|
+
*/
|
|
73
|
+
async function streamOpenAI(messages, model, baseUrl, apiKey, onToken) {
|
|
74
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
Authorization: `Bearer ${apiKey}`,
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
model,
|
|
82
|
+
messages,
|
|
83
|
+
stream: true,
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const reader = response.body.getReader();
|
|
92
|
+
const decoder = new TextDecoder();
|
|
93
|
+
let fullResponse = "";
|
|
94
|
+
|
|
95
|
+
while (true) {
|
|
96
|
+
const { done, value } = await reader.read();
|
|
97
|
+
if (done) break;
|
|
98
|
+
|
|
99
|
+
const text = decoder.decode(value, { stream: true });
|
|
100
|
+
const lines = text.split("\n").filter(Boolean);
|
|
101
|
+
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
if (line.startsWith("data: ")) {
|
|
104
|
+
const data = line.slice(6);
|
|
105
|
+
if (data === "[DONE]") continue;
|
|
106
|
+
try {
|
|
107
|
+
const json = JSON.parse(data);
|
|
108
|
+
const content = json.choices?.[0]?.delta?.content;
|
|
109
|
+
if (content) {
|
|
110
|
+
fullResponse += content;
|
|
111
|
+
onToken(content);
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Partial data
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return fullResponse;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Start the interactive chat REPL
|
|
125
|
+
* @param {object} options
|
|
126
|
+
*/
|
|
127
|
+
export async function startChatRepl(options = {}) {
|
|
128
|
+
let model = options.model || "qwen2:7b";
|
|
129
|
+
let provider = options.provider || "ollama";
|
|
130
|
+
const baseUrl = options.baseUrl || "http://localhost:11434";
|
|
131
|
+
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
|
|
132
|
+
|
|
133
|
+
const messages = [];
|
|
134
|
+
|
|
135
|
+
const rl = readline.createInterface({
|
|
136
|
+
input: process.stdin,
|
|
137
|
+
output: process.stdout,
|
|
138
|
+
prompt: chalk.green("you> "),
|
|
139
|
+
terminal: true,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
logger.log(chalk.bold("\nChainlessChain AI Chat"));
|
|
143
|
+
logger.log(chalk.gray(`Model: ${model} Provider: ${provider}`));
|
|
144
|
+
logger.log(chalk.gray("Type /help for commands, /exit to quit\n"));
|
|
145
|
+
|
|
146
|
+
rl.prompt();
|
|
147
|
+
|
|
148
|
+
rl.on("line", async (input) => {
|
|
149
|
+
const trimmed = input.trim();
|
|
150
|
+
|
|
151
|
+
if (!trimmed) {
|
|
152
|
+
rl.prompt();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle slash commands
|
|
157
|
+
if (trimmed.startsWith("/")) {
|
|
158
|
+
const [cmd, ...args] = trimmed.split(" ");
|
|
159
|
+
const arg = args.join(" ").trim();
|
|
160
|
+
|
|
161
|
+
switch (cmd) {
|
|
162
|
+
case "/exit":
|
|
163
|
+
case "/quit":
|
|
164
|
+
logger.log(chalk.gray("\nGoodbye!"));
|
|
165
|
+
rl.close();
|
|
166
|
+
return;
|
|
167
|
+
|
|
168
|
+
case "/model":
|
|
169
|
+
if (arg) {
|
|
170
|
+
model = arg;
|
|
171
|
+
logger.info(`Model changed to: ${chalk.cyan(model)}`);
|
|
172
|
+
} else {
|
|
173
|
+
logger.info(`Current model: ${chalk.cyan(model)}`);
|
|
174
|
+
}
|
|
175
|
+
rl.prompt();
|
|
176
|
+
return;
|
|
177
|
+
|
|
178
|
+
case "/provider":
|
|
179
|
+
if (arg) {
|
|
180
|
+
provider = arg;
|
|
181
|
+
logger.info(`Provider changed to: ${chalk.cyan(provider)}`);
|
|
182
|
+
} else {
|
|
183
|
+
logger.info(`Current provider: ${chalk.cyan(provider)}`);
|
|
184
|
+
}
|
|
185
|
+
rl.prompt();
|
|
186
|
+
return;
|
|
187
|
+
|
|
188
|
+
case "/clear":
|
|
189
|
+
messages.length = 0;
|
|
190
|
+
logger.info("Conversation history cleared");
|
|
191
|
+
rl.prompt();
|
|
192
|
+
return;
|
|
193
|
+
|
|
194
|
+
case "/history":
|
|
195
|
+
if (messages.length === 0) {
|
|
196
|
+
logger.info("No conversation history");
|
|
197
|
+
} else {
|
|
198
|
+
for (const msg of messages) {
|
|
199
|
+
const prefix =
|
|
200
|
+
msg.role === "user"
|
|
201
|
+
? chalk.green("you> ")
|
|
202
|
+
: chalk.blue("ai> ");
|
|
203
|
+
logger.log(
|
|
204
|
+
`${prefix}${msg.content.substring(0, 100)}${msg.content.length > 100 ? "..." : ""}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
rl.prompt();
|
|
209
|
+
return;
|
|
210
|
+
|
|
211
|
+
case "/help":
|
|
212
|
+
logger.log(chalk.bold("\nAvailable commands:"));
|
|
213
|
+
for (const [cmd, desc] of Object.entries(SLASH_COMMANDS)) {
|
|
214
|
+
logger.log(` ${chalk.cyan(cmd.padEnd(12))} ${desc}`);
|
|
215
|
+
}
|
|
216
|
+
logger.log("");
|
|
217
|
+
rl.prompt();
|
|
218
|
+
return;
|
|
219
|
+
|
|
220
|
+
default:
|
|
221
|
+
logger.warn(`Unknown command: ${cmd}. Type /help for help.`);
|
|
222
|
+
rl.prompt();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Add user message
|
|
228
|
+
messages.push({ role: "user", content: trimmed });
|
|
229
|
+
|
|
230
|
+
// Stream the response
|
|
231
|
+
process.stdout.write(chalk.blue("ai> "));
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
let response;
|
|
235
|
+
const onToken = (token) => process.stdout.write(token);
|
|
236
|
+
|
|
237
|
+
if (provider === "ollama") {
|
|
238
|
+
response = await streamOllama(messages, model, baseUrl, onToken);
|
|
239
|
+
} else if (provider === "openai") {
|
|
240
|
+
const url =
|
|
241
|
+
baseUrl !== "http://localhost:11434"
|
|
242
|
+
? baseUrl
|
|
243
|
+
: "https://api.openai.com/v1";
|
|
244
|
+
response = await streamOpenAI(messages, model, url, apiKey, onToken);
|
|
245
|
+
} else {
|
|
246
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
process.stdout.write("\n\n");
|
|
250
|
+
messages.push({ role: "assistant", content: response });
|
|
251
|
+
} catch (err) {
|
|
252
|
+
process.stdout.write("\n");
|
|
253
|
+
logger.error(`Error: ${err.message}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
rl.prompt();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
rl.on("close", () => {
|
|
260
|
+
process.exit(0);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless bootstrap - initializes core packages without GUI modules.
|
|
3
|
+
*
|
|
4
|
+
* Stages:
|
|
5
|
+
* 1. Environment detection
|
|
6
|
+
* 2. Path resolution
|
|
7
|
+
* 3. Logger setup
|
|
8
|
+
* 4. Configuration loading
|
|
9
|
+
* 5. Database initialization
|
|
10
|
+
* 6. Service container setup
|
|
11
|
+
* 7. Event bus activation
|
|
12
|
+
* (Stages 8-10 reserved for LLM, AI, and social when extracted)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { logger } from "../lib/logger.js";
|
|
16
|
+
|
|
17
|
+
let _context = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the headless runtime
|
|
21
|
+
* @param {object} [options]
|
|
22
|
+
* @param {boolean} [options.skipDb] - Skip database initialization
|
|
23
|
+
* @param {string} [options.dbPath] - Custom database path
|
|
24
|
+
* @param {boolean} [options.verbose] - Verbose output
|
|
25
|
+
* @returns {Promise<object>} Runtime context
|
|
26
|
+
*/
|
|
27
|
+
export async function bootstrap(options = {}) {
|
|
28
|
+
if (_context) return _context;
|
|
29
|
+
|
|
30
|
+
const ctx = {
|
|
31
|
+
env: null,
|
|
32
|
+
config: null,
|
|
33
|
+
db: null,
|
|
34
|
+
container: null,
|
|
35
|
+
eventBus: null,
|
|
36
|
+
initialized: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Stage 1: Environment
|
|
41
|
+
if (options.verbose) logger.verbose("Stage 1: Detecting environment...");
|
|
42
|
+
const coreEnv = await import("@chainlesschain/core-env");
|
|
43
|
+
ctx.env = {
|
|
44
|
+
runtime: coreEnv.getRuntimeInfo(),
|
|
45
|
+
userDataPath: coreEnv.getUserDataPath(),
|
|
46
|
+
configDir: coreEnv.getConfigDir(),
|
|
47
|
+
dataDir: coreEnv.getDataDir(),
|
|
48
|
+
logsDir: coreEnv.getLogsDir(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Ensure directories exist
|
|
52
|
+
coreEnv.ensureDir(ctx.env.userDataPath);
|
|
53
|
+
coreEnv.ensureDir(ctx.env.configDir);
|
|
54
|
+
coreEnv.ensureDir(ctx.env.dataDir);
|
|
55
|
+
coreEnv.ensureDir(ctx.env.logsDir);
|
|
56
|
+
|
|
57
|
+
// Stage 2-3: Logger (shared-logger optional, CLI logger always works)
|
|
58
|
+
if (options.verbose) logger.verbose("Stage 2-3: Logger ready");
|
|
59
|
+
|
|
60
|
+
// Stage 4: Configuration
|
|
61
|
+
if (options.verbose) logger.verbose("Stage 4: Loading configuration...");
|
|
62
|
+
try {
|
|
63
|
+
const coreConfig = await import("@chainlesschain/core-config");
|
|
64
|
+
coreConfig.setPathResolvers({
|
|
65
|
+
getUserDataPath: () => ctx.env.userDataPath,
|
|
66
|
+
getDataDir: () => ctx.env.dataDir,
|
|
67
|
+
});
|
|
68
|
+
ctx.config = coreConfig.getAppConfig({
|
|
69
|
+
configPath: `${ctx.env.userDataPath}/app-config.json`,
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (options.verbose)
|
|
73
|
+
logger.verbose(`Config package not available: ${err.message}`);
|
|
74
|
+
ctx.config = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Stage 5: Database
|
|
78
|
+
if (!options.skipDb) {
|
|
79
|
+
if (options.verbose) logger.verbose("Stage 5: Initializing database...");
|
|
80
|
+
try {
|
|
81
|
+
const coreDb = await import("@chainlesschain/core-db");
|
|
82
|
+
const dbPath =
|
|
83
|
+
options.dbPath ||
|
|
84
|
+
(ctx.config
|
|
85
|
+
? ctx.config.getDatabasePath()
|
|
86
|
+
: `${ctx.env.dataDir}/chainlesschain.db`);
|
|
87
|
+
|
|
88
|
+
const dbManager = coreDb.getDatabaseManager();
|
|
89
|
+
await dbManager.initialize({ dbPath });
|
|
90
|
+
ctx.db = dbManager;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (options.verbose)
|
|
93
|
+
logger.verbose(`Database init skipped: ${err.message}`);
|
|
94
|
+
ctx.db = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Stage 6: Service container
|
|
99
|
+
if (options.verbose) logger.verbose("Stage 6: Service container...");
|
|
100
|
+
try {
|
|
101
|
+
const coreInfra = await import("@chainlesschain/core-infra");
|
|
102
|
+
ctx.container = coreInfra.getServiceContainer();
|
|
103
|
+
ctx.eventBus = coreInfra.getEventBus();
|
|
104
|
+
|
|
105
|
+
// Register core services
|
|
106
|
+
if (ctx.db) {
|
|
107
|
+
ctx.container.register("database", () => ctx.db, {
|
|
108
|
+
tags: ["core"],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (ctx.config) {
|
|
112
|
+
ctx.container.register("config", () => ctx.config, {
|
|
113
|
+
tags: ["core"],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (options.verbose)
|
|
118
|
+
logger.verbose(`Infra packages not available: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Stage 7: Event bus
|
|
122
|
+
if (options.verbose) logger.verbose("Stage 7: Event bus active");
|
|
123
|
+
|
|
124
|
+
ctx.initialized = true;
|
|
125
|
+
_context = ctx;
|
|
126
|
+
|
|
127
|
+
return ctx;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
logger.error(`Bootstrap failed: ${err.message}`);
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the runtime context (must call bootstrap first)
|
|
136
|
+
*/
|
|
137
|
+
export function getContext() {
|
|
138
|
+
return _context;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gracefully shutdown the runtime
|
|
143
|
+
*/
|
|
144
|
+
export async function shutdown() {
|
|
145
|
+
if (!_context) return;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
if (_context.db) {
|
|
149
|
+
_context.db.close();
|
|
150
|
+
}
|
|
151
|
+
if (_context.container) {
|
|
152
|
+
await _context.container.disposeAll();
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
logger.error(`Shutdown error: ${err.message}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_context = null;
|
|
159
|
+
}
|