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,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2P commands
|
|
3
|
+
* chainlesschain p2p peers|send|inbox|pair|unpair|devices|status
|
|
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
|
+
registerPeer,
|
|
11
|
+
getAllPeers,
|
|
12
|
+
getOnlinePeers,
|
|
13
|
+
sendMessage,
|
|
14
|
+
getInbox,
|
|
15
|
+
markMessageRead,
|
|
16
|
+
getMessageCount,
|
|
17
|
+
pairDevice,
|
|
18
|
+
confirmPairing,
|
|
19
|
+
getPairedDevices,
|
|
20
|
+
unpairDevice,
|
|
21
|
+
generatePeerId,
|
|
22
|
+
P2PBridge,
|
|
23
|
+
} from "../lib/p2p-manager.js";
|
|
24
|
+
|
|
25
|
+
export function registerP2pCommand(program) {
|
|
26
|
+
const p2p = program
|
|
27
|
+
.command("p2p")
|
|
28
|
+
.description("Peer-to-peer messaging and device management");
|
|
29
|
+
|
|
30
|
+
// p2p status
|
|
31
|
+
p2p
|
|
32
|
+
.command("status", { isDefault: true })
|
|
33
|
+
.description("Show P2P connection status")
|
|
34
|
+
.option("--json", "Output as JSON")
|
|
35
|
+
.action(async (options) => {
|
|
36
|
+
try {
|
|
37
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
38
|
+
if (!ctx.db) {
|
|
39
|
+
logger.error("Database not available");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const db = ctx.db.getDatabase();
|
|
43
|
+
const peers = getAllPeers(db);
|
|
44
|
+
const devices = getPairedDevices(db);
|
|
45
|
+
const bridge = new P2PBridge();
|
|
46
|
+
const bridgeAvailable = await bridge.checkBridge();
|
|
47
|
+
|
|
48
|
+
if (options.json) {
|
|
49
|
+
console.log(
|
|
50
|
+
JSON.stringify(
|
|
51
|
+
{ peers: peers.length, devices: devices.length, bridgeAvailable },
|
|
52
|
+
null,
|
|
53
|
+
2,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
logger.log(chalk.bold("P2P Status:\n"));
|
|
58
|
+
logger.log(` ${chalk.bold("Peers:")} ${peers.length}`);
|
|
59
|
+
logger.log(` ${chalk.bold("Devices:")} ${devices.length}`);
|
|
60
|
+
logger.log(
|
|
61
|
+
` ${chalk.bold("Bridge:")} ${bridgeAvailable ? chalk.green("connected") : chalk.gray("not available")}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await shutdown();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logger.error(`Failed: ${err.message}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// p2p peers
|
|
73
|
+
p2p
|
|
74
|
+
.command("peers")
|
|
75
|
+
.description("List known peers")
|
|
76
|
+
.option("--online", "Show only online peers")
|
|
77
|
+
.option("--json", "Output as JSON")
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
try {
|
|
80
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
81
|
+
if (!ctx.db) {
|
|
82
|
+
logger.error("Database not available");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const db = ctx.db.getDatabase();
|
|
86
|
+
const peers = options.online ? getOnlinePeers(db) : getAllPeers(db);
|
|
87
|
+
|
|
88
|
+
if (options.json) {
|
|
89
|
+
console.log(JSON.stringify(peers, null, 2));
|
|
90
|
+
} else if (peers.length === 0) {
|
|
91
|
+
logger.info("No peers found");
|
|
92
|
+
} else {
|
|
93
|
+
logger.log(chalk.bold(`Peers (${peers.length}):\n`));
|
|
94
|
+
for (const p of peers) {
|
|
95
|
+
const status =
|
|
96
|
+
p.status === "online"
|
|
97
|
+
? chalk.green("online")
|
|
98
|
+
: chalk.gray(p.status);
|
|
99
|
+
const name = p.display_name ? ` (${p.display_name})` : "";
|
|
100
|
+
logger.log(` ${chalk.cyan(p.peer_id)}${name} [${status}]`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await shutdown();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
logger.error(`Failed: ${err.message}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// p2p send
|
|
112
|
+
p2p
|
|
113
|
+
.command("send")
|
|
114
|
+
.description("Send a message to a peer")
|
|
115
|
+
.argument("<peer>", "Peer ID to send to")
|
|
116
|
+
.argument("<message>", "Message content")
|
|
117
|
+
.action(async (peer, message) => {
|
|
118
|
+
try {
|
|
119
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
120
|
+
if (!ctx.db) {
|
|
121
|
+
logger.error("Database not available");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const db = ctx.db.getDatabase();
|
|
125
|
+
const myPeerId = generatePeerId();
|
|
126
|
+
registerPeer(db, myPeerId, "CLI User", null, null, "cli");
|
|
127
|
+
const msg = sendMessage(db, myPeerId, peer, message);
|
|
128
|
+
logger.success(`Message sent (${msg.id})`);
|
|
129
|
+
await shutdown();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
logger.error(`Failed: ${err.message}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// p2p inbox
|
|
137
|
+
p2p
|
|
138
|
+
.command("inbox")
|
|
139
|
+
.description("Show received messages")
|
|
140
|
+
.option("--unread", "Show only unread messages")
|
|
141
|
+
.option("--peer-id <id>", "Your peer ID")
|
|
142
|
+
.option("--json", "Output as JSON")
|
|
143
|
+
.action(async (options) => {
|
|
144
|
+
try {
|
|
145
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
146
|
+
if (!ctx.db) {
|
|
147
|
+
logger.error("Database not available");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const db = ctx.db.getDatabase();
|
|
151
|
+
const peerId = options.peerId || "cli-user";
|
|
152
|
+
const messages = getInbox(db, peerId, { unreadOnly: options.unread });
|
|
153
|
+
|
|
154
|
+
if (options.json) {
|
|
155
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
156
|
+
} else if (messages.length === 0) {
|
|
157
|
+
logger.info("No messages");
|
|
158
|
+
} else {
|
|
159
|
+
logger.log(chalk.bold(`Inbox (${messages.length}):\n`));
|
|
160
|
+
for (const m of messages) {
|
|
161
|
+
const read = m.read ? chalk.gray("[read]") : chalk.yellow("[new]");
|
|
162
|
+
logger.log(` ${read} ${chalk.cyan(m.from_peer)}: ${m.content}`);
|
|
163
|
+
logger.log(` ${chalk.gray(m.created_at)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await shutdown();
|
|
168
|
+
} catch (err) {
|
|
169
|
+
logger.error(`Failed: ${err.message}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// p2p pair
|
|
175
|
+
p2p
|
|
176
|
+
.command("pair")
|
|
177
|
+
.description("Pair a new device")
|
|
178
|
+
.argument("<device-name>", "Device name")
|
|
179
|
+
.option("--type <type>", "Device type (desktop/mobile/tablet)", "unknown")
|
|
180
|
+
.action(async (deviceName, options) => {
|
|
181
|
+
try {
|
|
182
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
183
|
+
if (!ctx.db) {
|
|
184
|
+
logger.error("Database not available");
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const db = ctx.db.getDatabase();
|
|
188
|
+
const device = pairDevice(db, deviceName, options.type);
|
|
189
|
+
|
|
190
|
+
logger.success("Device pairing initiated");
|
|
191
|
+
logger.log(
|
|
192
|
+
` ${chalk.bold("Device ID:")} ${chalk.cyan(device.deviceId)}`,
|
|
193
|
+
);
|
|
194
|
+
logger.log(
|
|
195
|
+
` ${chalk.bold("Pairing Code:")} ${chalk.yellow(device.pairingCode)}`,
|
|
196
|
+
);
|
|
197
|
+
logger.log(
|
|
198
|
+
`\n Enter this code on the other device to complete pairing.`,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
await shutdown();
|
|
202
|
+
} catch (err) {
|
|
203
|
+
logger.error(`Failed: ${err.message}`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// p2p devices
|
|
209
|
+
p2p
|
|
210
|
+
.command("devices")
|
|
211
|
+
.description("List paired devices")
|
|
212
|
+
.option("--json", "Output as JSON")
|
|
213
|
+
.action(async (options) => {
|
|
214
|
+
try {
|
|
215
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
216
|
+
if (!ctx.db) {
|
|
217
|
+
logger.error("Database not available");
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
const db = ctx.db.getDatabase();
|
|
221
|
+
const devices = getPairedDevices(db);
|
|
222
|
+
|
|
223
|
+
if (options.json) {
|
|
224
|
+
console.log(JSON.stringify(devices, null, 2));
|
|
225
|
+
} else if (devices.length === 0) {
|
|
226
|
+
logger.info("No paired devices");
|
|
227
|
+
} else {
|
|
228
|
+
logger.log(chalk.bold(`Paired Devices (${devices.length}):\n`));
|
|
229
|
+
for (const d of devices) {
|
|
230
|
+
const status =
|
|
231
|
+
d.status === "active"
|
|
232
|
+
? chalk.green("active")
|
|
233
|
+
: chalk.gray(d.status);
|
|
234
|
+
logger.log(
|
|
235
|
+
` ${chalk.cyan(d.device_id)} - ${d.device_name} [${status}]`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await shutdown();
|
|
241
|
+
} catch (err) {
|
|
242
|
+
logger.error(`Failed: ${err.message}`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// p2p unpair
|
|
248
|
+
p2p
|
|
249
|
+
.command("unpair")
|
|
250
|
+
.description("Unpair a device")
|
|
251
|
+
.argument("<device-id>", "Device ID to unpair")
|
|
252
|
+
.action(async (deviceId) => {
|
|
253
|
+
try {
|
|
254
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
255
|
+
if (!ctx.db) {
|
|
256
|
+
logger.error("Database not available");
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
const db = ctx.db.getDatabase();
|
|
260
|
+
const ok = unpairDevice(db, deviceId);
|
|
261
|
+
|
|
262
|
+
if (ok) {
|
|
263
|
+
logger.success("Device unpaired");
|
|
264
|
+
} else {
|
|
265
|
+
logger.error(`Device not found: ${deviceId}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await shutdown();
|
|
269
|
+
} catch (err) {
|
|
270
|
+
logger.error(`Failed: ${err.message}`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin management commands
|
|
3
|
+
* chainlesschain plugin list|install|remove|enable|disable|update|info|search|registry|summary
|
|
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
|
+
installPlugin,
|
|
11
|
+
getPlugin,
|
|
12
|
+
listPlugins,
|
|
13
|
+
enablePlugin,
|
|
14
|
+
disablePlugin,
|
|
15
|
+
removePlugin,
|
|
16
|
+
updatePlugin,
|
|
17
|
+
getPluginSettings,
|
|
18
|
+
setPluginSetting,
|
|
19
|
+
searchRegistry,
|
|
20
|
+
listRegistry,
|
|
21
|
+
registerInMarketplace,
|
|
22
|
+
getPluginSummary,
|
|
23
|
+
} from "../lib/plugin-manager.js";
|
|
24
|
+
|
|
25
|
+
export function registerPluginCommand(program) {
|
|
26
|
+
const plugin = program
|
|
27
|
+
.command("plugin")
|
|
28
|
+
.description("Plugin and marketplace management");
|
|
29
|
+
|
|
30
|
+
// plugin list
|
|
31
|
+
plugin
|
|
32
|
+
.command("list", { isDefault: true })
|
|
33
|
+
.description("List installed plugins")
|
|
34
|
+
.option("--enabled", "Show only enabled plugins")
|
|
35
|
+
.option("--json", "Output as JSON")
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
try {
|
|
38
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
39
|
+
if (!ctx.db) {
|
|
40
|
+
logger.error("Database not available");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const db = ctx.db.getDatabase();
|
|
44
|
+
const plugins = listPlugins(db, { enabledOnly: options.enabled });
|
|
45
|
+
|
|
46
|
+
if (options.json) {
|
|
47
|
+
console.log(
|
|
48
|
+
JSON.stringify(
|
|
49
|
+
plugins.map((p) => ({
|
|
50
|
+
name: p.name,
|
|
51
|
+
version: p.version,
|
|
52
|
+
enabled: p.enabled === 1,
|
|
53
|
+
status: p.status,
|
|
54
|
+
})),
|
|
55
|
+
null,
|
|
56
|
+
2,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
} else if (plugins.length === 0) {
|
|
60
|
+
logger.info(
|
|
61
|
+
'No plugins installed. Install one with "chainlesschain plugin install <name>"',
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
logger.log(chalk.bold(`Plugins (${plugins.length}):\n`));
|
|
65
|
+
for (const p of plugins) {
|
|
66
|
+
const status = p.enabled
|
|
67
|
+
? chalk.green("enabled")
|
|
68
|
+
: chalk.gray("disabled");
|
|
69
|
+
logger.log(` ${chalk.cyan(p.name)} v${p.version} [${status}]`);
|
|
70
|
+
if (p.description) logger.log(` ${chalk.gray(p.description)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await shutdown();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.error(`Failed: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// plugin install
|
|
82
|
+
plugin
|
|
83
|
+
.command("install")
|
|
84
|
+
.description("Install a plugin")
|
|
85
|
+
.argument("<name>", "Plugin name")
|
|
86
|
+
.option("--version <version>", "Plugin version", "1.0.0")
|
|
87
|
+
.option("--description <desc>", "Plugin description")
|
|
88
|
+
.option("--author <author>", "Plugin author")
|
|
89
|
+
.option("--json", "Output as JSON")
|
|
90
|
+
.action(async (name, options) => {
|
|
91
|
+
try {
|
|
92
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
93
|
+
if (!ctx.db) {
|
|
94
|
+
logger.error("Database not available");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const db = ctx.db.getDatabase();
|
|
98
|
+
const result = installPlugin(db, {
|
|
99
|
+
name,
|
|
100
|
+
version: options.version,
|
|
101
|
+
description: options.description,
|
|
102
|
+
author: options.author,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (options.json) {
|
|
106
|
+
console.log(JSON.stringify(result, null, 2));
|
|
107
|
+
} else {
|
|
108
|
+
logger.success(`Plugin installed: ${result.name} v${result.version}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await shutdown();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logger.error(`Failed: ${err.message}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// plugin remove
|
|
119
|
+
plugin
|
|
120
|
+
.command("remove")
|
|
121
|
+
.description("Remove a plugin")
|
|
122
|
+
.argument("<name>", "Plugin name")
|
|
123
|
+
.option("--force", "Skip confirmation")
|
|
124
|
+
.action(async (name, options) => {
|
|
125
|
+
try {
|
|
126
|
+
if (!options.force) {
|
|
127
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
128
|
+
const ok = await confirm({
|
|
129
|
+
message: `Remove plugin "${name}"?`,
|
|
130
|
+
});
|
|
131
|
+
if (!ok) {
|
|
132
|
+
logger.info("Cancelled");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
138
|
+
if (!ctx.db) {
|
|
139
|
+
logger.error("Database not available");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const db = ctx.db.getDatabase();
|
|
143
|
+
const ok = removePlugin(db, name);
|
|
144
|
+
|
|
145
|
+
if (ok) {
|
|
146
|
+
logger.success(`Plugin removed: ${name}`);
|
|
147
|
+
} else {
|
|
148
|
+
logger.error(`Plugin not found: ${name}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await shutdown();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
logger.error(`Failed: ${err.message}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// plugin enable
|
|
159
|
+
plugin
|
|
160
|
+
.command("enable")
|
|
161
|
+
.description("Enable a plugin")
|
|
162
|
+
.argument("<name>", "Plugin name")
|
|
163
|
+
.action(async (name) => {
|
|
164
|
+
try {
|
|
165
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
166
|
+
if (!ctx.db) {
|
|
167
|
+
logger.error("Database not available");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
const db = ctx.db.getDatabase();
|
|
171
|
+
const ok = enablePlugin(db, name);
|
|
172
|
+
|
|
173
|
+
if (ok) {
|
|
174
|
+
logger.success(`Plugin enabled: ${name}`);
|
|
175
|
+
} else {
|
|
176
|
+
logger.error(`Plugin not found: ${name}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await shutdown();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
logger.error(`Failed: ${err.message}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// plugin disable
|
|
187
|
+
plugin
|
|
188
|
+
.command("disable")
|
|
189
|
+
.description("Disable a plugin")
|
|
190
|
+
.argument("<name>", "Plugin name")
|
|
191
|
+
.action(async (name) => {
|
|
192
|
+
try {
|
|
193
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
194
|
+
if (!ctx.db) {
|
|
195
|
+
logger.error("Database not available");
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const db = ctx.db.getDatabase();
|
|
199
|
+
const ok = disablePlugin(db, name);
|
|
200
|
+
|
|
201
|
+
if (ok) {
|
|
202
|
+
logger.success(`Plugin disabled: ${name}`);
|
|
203
|
+
} else {
|
|
204
|
+
logger.error(`Plugin not found: ${name}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await shutdown();
|
|
208
|
+
} catch (err) {
|
|
209
|
+
logger.error(`Failed: ${err.message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// plugin update
|
|
215
|
+
plugin
|
|
216
|
+
.command("update")
|
|
217
|
+
.description("Update a plugin version")
|
|
218
|
+
.argument("<name>", "Plugin name")
|
|
219
|
+
.argument("<version>", "New version")
|
|
220
|
+
.action(async (name, version) => {
|
|
221
|
+
try {
|
|
222
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
223
|
+
if (!ctx.db) {
|
|
224
|
+
logger.error("Database not available");
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
const db = ctx.db.getDatabase();
|
|
228
|
+
const ok = updatePlugin(db, name, version);
|
|
229
|
+
|
|
230
|
+
if (ok) {
|
|
231
|
+
logger.success(`Plugin updated: ${name} → v${version}`);
|
|
232
|
+
} else {
|
|
233
|
+
logger.error(`Plugin not found: ${name}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await shutdown();
|
|
237
|
+
} catch (err) {
|
|
238
|
+
logger.error(`Failed: ${err.message}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// plugin info
|
|
244
|
+
plugin
|
|
245
|
+
.command("info")
|
|
246
|
+
.description("Show plugin details")
|
|
247
|
+
.argument("<name>", "Plugin name")
|
|
248
|
+
.option("--json", "Output as JSON")
|
|
249
|
+
.action(async (name, options) => {
|
|
250
|
+
try {
|
|
251
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
252
|
+
if (!ctx.db) {
|
|
253
|
+
logger.error("Database not available");
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
const db = ctx.db.getDatabase();
|
|
257
|
+
const p = getPlugin(db, name);
|
|
258
|
+
|
|
259
|
+
if (!p) {
|
|
260
|
+
logger.error(`Plugin not found: ${name}`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const settings = getPluginSettings(db, name);
|
|
265
|
+
|
|
266
|
+
if (options.json) {
|
|
267
|
+
console.log(JSON.stringify({ ...p, settings }, null, 2));
|
|
268
|
+
} else {
|
|
269
|
+
logger.log(chalk.bold("Plugin Info:\n"));
|
|
270
|
+
logger.log(` ${chalk.bold("Name:")} ${chalk.cyan(p.name)}`);
|
|
271
|
+
logger.log(` ${chalk.bold("Version:")} ${p.version}`);
|
|
272
|
+
logger.log(
|
|
273
|
+
` ${chalk.bold("Description:")} ${p.description || chalk.gray("(none)")}`,
|
|
274
|
+
);
|
|
275
|
+
logger.log(
|
|
276
|
+
` ${chalk.bold("Author:")} ${p.author || chalk.gray("(unknown)")}`,
|
|
277
|
+
);
|
|
278
|
+
logger.log(
|
|
279
|
+
` ${chalk.bold("Enabled:")} ${p.enabled ? chalk.green("yes") : chalk.red("no")}`,
|
|
280
|
+
);
|
|
281
|
+
logger.log(` ${chalk.bold("Installed:")} ${p.installed_at}`);
|
|
282
|
+
|
|
283
|
+
if (Object.keys(settings).length > 0) {
|
|
284
|
+
logger.log(`\n ${chalk.bold("Settings:")}`);
|
|
285
|
+
for (const [k, v] of Object.entries(settings)) {
|
|
286
|
+
logger.log(` ${k}: ${v}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
await shutdown();
|
|
292
|
+
} catch (err) {
|
|
293
|
+
logger.error(`Failed: ${err.message}`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// plugin search
|
|
299
|
+
plugin
|
|
300
|
+
.command("search")
|
|
301
|
+
.description("Search plugin registry")
|
|
302
|
+
.argument("<query>", "Search query")
|
|
303
|
+
.option("--json", "Output as JSON")
|
|
304
|
+
.action(async (query, options) => {
|
|
305
|
+
try {
|
|
306
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
307
|
+
if (!ctx.db) {
|
|
308
|
+
logger.error("Database not available");
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
const db = ctx.db.getDatabase();
|
|
312
|
+
const results = searchRegistry(db, query);
|
|
313
|
+
|
|
314
|
+
if (options.json) {
|
|
315
|
+
console.log(JSON.stringify(results, null, 2));
|
|
316
|
+
} else if (results.length === 0) {
|
|
317
|
+
logger.info(`No plugins found for "${query}"`);
|
|
318
|
+
} else {
|
|
319
|
+
logger.log(chalk.bold(`Registry Results (${results.length}):\n`));
|
|
320
|
+
for (const r of results) {
|
|
321
|
+
logger.log(` ${chalk.cyan(r.name)} v${r.latest_version}`);
|
|
322
|
+
if (r.description) logger.log(` ${chalk.gray(r.description)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
await shutdown();
|
|
327
|
+
} catch (err) {
|
|
328
|
+
logger.error(`Failed: ${err.message}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// plugin registry
|
|
334
|
+
plugin
|
|
335
|
+
.command("registry")
|
|
336
|
+
.description("List all plugins in registry")
|
|
337
|
+
.option("--json", "Output as JSON")
|
|
338
|
+
.action(async (options) => {
|
|
339
|
+
try {
|
|
340
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
341
|
+
if (!ctx.db) {
|
|
342
|
+
logger.error("Database not available");
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
const db = ctx.db.getDatabase();
|
|
346
|
+
const registry = listRegistry(db);
|
|
347
|
+
|
|
348
|
+
if (options.json) {
|
|
349
|
+
console.log(JSON.stringify(registry, null, 2));
|
|
350
|
+
} else if (registry.length === 0) {
|
|
351
|
+
logger.info("Registry is empty");
|
|
352
|
+
} else {
|
|
353
|
+
logger.log(chalk.bold(`Plugin Registry (${registry.length}):\n`));
|
|
354
|
+
for (const r of registry) {
|
|
355
|
+
logger.log(
|
|
356
|
+
` ${chalk.cyan(r.name)} v${r.latest_version} - ${r.description || ""}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
await shutdown();
|
|
362
|
+
} catch (err) {
|
|
363
|
+
logger.error(`Failed: ${err.message}`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// plugin summary
|
|
369
|
+
plugin
|
|
370
|
+
.command("summary")
|
|
371
|
+
.description("Show plugin summary statistics")
|
|
372
|
+
.option("--json", "Output as JSON")
|
|
373
|
+
.action(async (options) => {
|
|
374
|
+
try {
|
|
375
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
376
|
+
if (!ctx.db) {
|
|
377
|
+
logger.error("Database not available");
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
const db = ctx.db.getDatabase();
|
|
381
|
+
const summary = getPluginSummary(db);
|
|
382
|
+
|
|
383
|
+
if (options.json) {
|
|
384
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
385
|
+
} else {
|
|
386
|
+
logger.log(chalk.bold("Plugin Summary:\n"));
|
|
387
|
+
logger.log(` ${chalk.bold("Installed:")} ${summary.installed}`);
|
|
388
|
+
logger.log(` ${chalk.bold("Enabled:")} ${summary.enabled}`);
|
|
389
|
+
logger.log(` ${chalk.bold("Registry:")} ${summary.registryCount}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await shutdown();
|
|
393
|
+
} catch (err) {
|
|
394
|
+
logger.error(`Failed: ${err.message}`);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|