chainlesschain 0.37.9 → 0.37.11
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 +309 -19
- package/bin/chainlesschain.js +4 -0
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/audit.js +286 -0
- package/src/commands/auth.js +387 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/browse.js +184 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/did.js +376 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/encrypt.js +233 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/export.js +125 -0
- package/src/commands/git.js +215 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/import.js +259 -0
- package/src/commands/init.js +184 -0
- package/src/commands/instinct.js +202 -0
- package/src/commands/llm.js +155 -4
- package/src/commands/lowcode.js +320 -0
- package/src/commands/mcp.js +302 -0
- package/src/commands/memory.js +282 -0
- package/src/commands/note.js +187 -0
- package/src/commands/org.js +505 -0
- package/src/commands/p2p.js +274 -0
- package/src/commands/plugin.js +451 -0
- package/src/commands/sandbox.js +366 -0
- package/src/commands/search.js +237 -0
- package/src/commands/session.js +238 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/sync.js +249 -0
- package/src/commands/tokens.js +214 -0
- package/src/commands/wallet.js +416 -0
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +93 -1
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/audit-logger.js +364 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/bm25-search.js +322 -0
- package/src/lib/browser-automation.js +216 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -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/evolution-system.js +508 -0
- package/src/lib/git-integration.js +220 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -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/plugin-manager.js +430 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/response-cache.js +156 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/session-manager.js +189 -0
- package/src/lib/skill-loader.js +274 -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/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +259 -124
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A (Agent-to-Agent) Protocol commands
|
|
3
|
+
* chainlesschain a2a register|discover|submit|status|complete|fail|peers|cards|negotiate
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import { logger } from "../lib/logger.js";
|
|
9
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
10
|
+
import {
|
|
11
|
+
registerCard,
|
|
12
|
+
updateCard,
|
|
13
|
+
discoverAgents,
|
|
14
|
+
sendTask,
|
|
15
|
+
completeTask,
|
|
16
|
+
failTask,
|
|
17
|
+
getTaskStatus,
|
|
18
|
+
negotiateCapability,
|
|
19
|
+
listPeers,
|
|
20
|
+
} from "../lib/a2a-protocol.js";
|
|
21
|
+
|
|
22
|
+
export function registerA2aCommand(program) {
|
|
23
|
+
const a2a = program
|
|
24
|
+
.command("a2a")
|
|
25
|
+
.description("A2A Protocol — agent-to-agent communication");
|
|
26
|
+
|
|
27
|
+
// a2a register <name>
|
|
28
|
+
a2a
|
|
29
|
+
.command("register")
|
|
30
|
+
.description("Register an agent card")
|
|
31
|
+
.argument("<name>", "Agent name")
|
|
32
|
+
.option("--description <desc>", "Agent description", "")
|
|
33
|
+
.option("--url <url>", "Agent endpoint URL", "")
|
|
34
|
+
.option("--capabilities <csv>", "Comma-separated capabilities", "")
|
|
35
|
+
.option("--skills <csv>", "Comma-separated skills", "")
|
|
36
|
+
.option("--json", "Output as JSON")
|
|
37
|
+
.action(async (name, options) => {
|
|
38
|
+
try {
|
|
39
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
40
|
+
if (!ctx.db) {
|
|
41
|
+
logger.error("Database not available");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const db = ctx.db.getDatabase();
|
|
45
|
+
const card = registerCard(db, {
|
|
46
|
+
name,
|
|
47
|
+
description: options.description,
|
|
48
|
+
url: options.url,
|
|
49
|
+
capabilities: options.capabilities
|
|
50
|
+
? options.capabilities.split(",").map((s) => s.trim())
|
|
51
|
+
: [],
|
|
52
|
+
skills: options.skills
|
|
53
|
+
? options.skills.split(",").map((s) => s.trim())
|
|
54
|
+
: [],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (options.json) {
|
|
58
|
+
console.log(JSON.stringify(card, null, 2));
|
|
59
|
+
} else {
|
|
60
|
+
logger.success(
|
|
61
|
+
`Agent registered: ${chalk.cyan(card.name)} ${chalk.gray(card.id)}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await shutdown();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logger.error(`Failed: ${err.message}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// a2a discover
|
|
73
|
+
a2a
|
|
74
|
+
.command("discover")
|
|
75
|
+
.description("Discover agents by capability or skill")
|
|
76
|
+
.option("--capability <name>", "Filter by capability")
|
|
77
|
+
.option("--skill <name>", "Filter by skill")
|
|
78
|
+
.option("--name <filter>", "Filter by name")
|
|
79
|
+
.option("--json", "Output as JSON")
|
|
80
|
+
.action(async (options) => {
|
|
81
|
+
try {
|
|
82
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
83
|
+
if (!ctx.db) {
|
|
84
|
+
logger.error("Database not available");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const db = ctx.db.getDatabase();
|
|
88
|
+
const agents = discoverAgents(db, {
|
|
89
|
+
capability: options.capability,
|
|
90
|
+
skill: options.skill,
|
|
91
|
+
name: options.name,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (options.json) {
|
|
95
|
+
console.log(JSON.stringify(agents, null, 2));
|
|
96
|
+
} else if (agents.length === 0) {
|
|
97
|
+
logger.info("No agents found matching criteria");
|
|
98
|
+
} else {
|
|
99
|
+
logger.log(chalk.bold(`Discovered ${agents.length} agents:\n`));
|
|
100
|
+
for (const a of agents) {
|
|
101
|
+
logger.log(` ${chalk.cyan(a.name)} ${chalk.gray(a.id)}`);
|
|
102
|
+
if (a.description) logger.log(` ${chalk.white(a.description)}`);
|
|
103
|
+
if (a.capabilities.length)
|
|
104
|
+
logger.log(
|
|
105
|
+
` Capabilities: ${chalk.yellow(a.capabilities.join(", "))}`,
|
|
106
|
+
);
|
|
107
|
+
if (a.skills.length)
|
|
108
|
+
logger.log(` Skills: ${chalk.yellow(a.skills.join(", "))}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await shutdown();
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logger.error(`Failed: ${err.message}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// a2a submit <agent-id> <input>
|
|
120
|
+
a2a
|
|
121
|
+
.command("submit")
|
|
122
|
+
.description("Submit a task to an agent")
|
|
123
|
+
.argument("<agent-id>", "Target agent ID")
|
|
124
|
+
.argument("<input>", "Task input")
|
|
125
|
+
.option("--json", "Output as JSON")
|
|
126
|
+
.action(async (agentId, input, options) => {
|
|
127
|
+
try {
|
|
128
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
129
|
+
if (!ctx.db) {
|
|
130
|
+
logger.error("Database not available");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const db = ctx.db.getDatabase();
|
|
134
|
+
const result = sendTask(db, agentId, input);
|
|
135
|
+
|
|
136
|
+
if (options.json) {
|
|
137
|
+
console.log(JSON.stringify(result, null, 2));
|
|
138
|
+
} else {
|
|
139
|
+
logger.success(
|
|
140
|
+
`Task submitted: ${chalk.gray(result.taskId)} [${chalk.yellow(result.status)}]`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await shutdown();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
logger.error(`Failed: ${err.message}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// a2a status <task-id>
|
|
152
|
+
a2a
|
|
153
|
+
.command("status")
|
|
154
|
+
.description("Get task status")
|
|
155
|
+
.argument("<task-id>", "Task ID")
|
|
156
|
+
.option("--json", "Output as JSON")
|
|
157
|
+
.action(async (taskId, options) => {
|
|
158
|
+
try {
|
|
159
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
160
|
+
if (!ctx.db) {
|
|
161
|
+
logger.error("Database not available");
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
const db = ctx.db.getDatabase();
|
|
165
|
+
const task = getTaskStatus(db, taskId);
|
|
166
|
+
|
|
167
|
+
if (options.json) {
|
|
168
|
+
console.log(JSON.stringify(task, null, 2));
|
|
169
|
+
} else {
|
|
170
|
+
logger.log(chalk.bold("Task Status:\n"));
|
|
171
|
+
logger.log(` ID: ${chalk.gray(task.id)}`);
|
|
172
|
+
logger.log(` Agent: ${chalk.cyan(task.agent_id)}`);
|
|
173
|
+
logger.log(` Status: ${chalk.yellow(task.status)}`);
|
|
174
|
+
if (task.input)
|
|
175
|
+
logger.log(
|
|
176
|
+
` Input: ${chalk.white(task.input.substring(0, 100))}`,
|
|
177
|
+
);
|
|
178
|
+
if (task.output)
|
|
179
|
+
logger.log(
|
|
180
|
+
` Output: ${chalk.green(task.output.substring(0, 100))}`,
|
|
181
|
+
);
|
|
182
|
+
if (task.error) logger.log(` Error: ${chalk.red(task.error)}`);
|
|
183
|
+
if (task.history.length) {
|
|
184
|
+
logger.log(" History:");
|
|
185
|
+
for (const h of task.history) {
|
|
186
|
+
logger.log(
|
|
187
|
+
` ${chalk.gray(h.timestamp)} → ${chalk.yellow(h.status)}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await shutdown();
|
|
194
|
+
} catch (err) {
|
|
195
|
+
logger.error(`Failed: ${err.message}`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// a2a complete <task-id> <output>
|
|
201
|
+
a2a
|
|
202
|
+
.command("complete")
|
|
203
|
+
.description("Mark a task as completed")
|
|
204
|
+
.argument("<task-id>", "Task ID")
|
|
205
|
+
.argument("<output>", "Task output")
|
|
206
|
+
.action(async (taskId, output) => {
|
|
207
|
+
try {
|
|
208
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
209
|
+
if (!ctx.db) {
|
|
210
|
+
logger.error("Database not available");
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
const db = ctx.db.getDatabase();
|
|
214
|
+
const result = completeTask(db, taskId, output);
|
|
215
|
+
logger.success(
|
|
216
|
+
`Task ${chalk.gray(taskId)} → ${chalk.green(result.status)}`,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await shutdown();
|
|
220
|
+
} catch (err) {
|
|
221
|
+
logger.error(`Failed: ${err.message}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// a2a fail <task-id> <error>
|
|
227
|
+
a2a
|
|
228
|
+
.command("fail")
|
|
229
|
+
.description("Mark a task as failed")
|
|
230
|
+
.argument("<task-id>", "Task ID")
|
|
231
|
+
.argument("<error>", "Error message")
|
|
232
|
+
.action(async (taskId, error) => {
|
|
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 result = failTask(db, taskId, error);
|
|
241
|
+
logger.success(
|
|
242
|
+
`Task ${chalk.gray(taskId)} → ${chalk.red(result.status)}`,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await shutdown();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
logger.error(`Failed: ${err.message}`);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// a2a peers
|
|
253
|
+
a2a
|
|
254
|
+
.command("peers")
|
|
255
|
+
.description("List all registered agents")
|
|
256
|
+
.option("--json", "Output as JSON")
|
|
257
|
+
.action(async (options) => {
|
|
258
|
+
try {
|
|
259
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
260
|
+
if (!ctx.db) {
|
|
261
|
+
logger.error("Database not available");
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
const db = ctx.db.getDatabase();
|
|
265
|
+
const peers = listPeers(db);
|
|
266
|
+
|
|
267
|
+
if (options.json) {
|
|
268
|
+
console.log(JSON.stringify(peers, null, 2));
|
|
269
|
+
} else if (peers.length === 0) {
|
|
270
|
+
logger.info("No agents registered. Use 'a2a register' to add one.");
|
|
271
|
+
} else {
|
|
272
|
+
logger.log(chalk.bold(`${peers.length} registered agents:\n`));
|
|
273
|
+
for (const p of peers) {
|
|
274
|
+
const statusColor =
|
|
275
|
+
p.status === "active" ? chalk.green : chalk.gray;
|
|
276
|
+
logger.log(
|
|
277
|
+
` ${chalk.cyan(p.name)} ${chalk.gray(p.id)} ${statusColor(p.status)}`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await shutdown();
|
|
283
|
+
} catch (err) {
|
|
284
|
+
logger.error(`Failed: ${err.message}`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// a2a cards
|
|
290
|
+
a2a
|
|
291
|
+
.command("cards")
|
|
292
|
+
.description("List all agent cards with details")
|
|
293
|
+
.option("--json", "Output as JSON")
|
|
294
|
+
.action(async (options) => {
|
|
295
|
+
try {
|
|
296
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
297
|
+
if (!ctx.db) {
|
|
298
|
+
logger.error("Database not available");
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
const db = ctx.db.getDatabase();
|
|
302
|
+
const peers = listPeers(db);
|
|
303
|
+
|
|
304
|
+
if (options.json) {
|
|
305
|
+
console.log(JSON.stringify(peers, null, 2));
|
|
306
|
+
} else if (peers.length === 0) {
|
|
307
|
+
logger.info("No agent cards registered.");
|
|
308
|
+
} else {
|
|
309
|
+
logger.log(chalk.bold(`${peers.length} agent cards:\n`));
|
|
310
|
+
for (const p of peers) {
|
|
311
|
+
logger.log(` ${chalk.cyan(p.name)} ${chalk.gray(p.id)}`);
|
|
312
|
+
if (p.description) logger.log(` Description: ${p.description}`);
|
|
313
|
+
if (p.url) logger.log(` URL: ${chalk.blue(p.url)}`);
|
|
314
|
+
logger.log(` Auth: ${p.auth_type}`);
|
|
315
|
+
if (p.capabilities.length)
|
|
316
|
+
logger.log(
|
|
317
|
+
` Capabilities: ${chalk.yellow(p.capabilities.join(", "))}`,
|
|
318
|
+
);
|
|
319
|
+
if (p.skills.length)
|
|
320
|
+
logger.log(` Skills: ${chalk.yellow(p.skills.join(", "))}`);
|
|
321
|
+
logger.log("");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
await shutdown();
|
|
326
|
+
} catch (err) {
|
|
327
|
+
logger.error(`Failed: ${err.message}`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// a2a negotiate <agent-id> <capabilities>
|
|
333
|
+
a2a
|
|
334
|
+
.command("negotiate")
|
|
335
|
+
.description("Check if an agent supports required capabilities")
|
|
336
|
+
.argument("<agent-id>", "Agent ID")
|
|
337
|
+
.argument("<capabilities>", "Comma-separated required capabilities")
|
|
338
|
+
.option("--json", "Output as JSON")
|
|
339
|
+
.action(async (agentId, capabilities, options) => {
|
|
340
|
+
try {
|
|
341
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
342
|
+
if (!ctx.db) {
|
|
343
|
+
logger.error("Database not available");
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
const db = ctx.db.getDatabase();
|
|
347
|
+
const required = capabilities.split(",").map((s) => s.trim());
|
|
348
|
+
const result = negotiateCapability(db, agentId, required);
|
|
349
|
+
|
|
350
|
+
if (options.json) {
|
|
351
|
+
console.log(JSON.stringify(result, null, 2));
|
|
352
|
+
} else {
|
|
353
|
+
if (result.compatible) {
|
|
354
|
+
logger.success("Agent is fully compatible");
|
|
355
|
+
} else {
|
|
356
|
+
logger.warn("Agent is not fully compatible");
|
|
357
|
+
}
|
|
358
|
+
if (result.supported.length) {
|
|
359
|
+
logger.log(
|
|
360
|
+
` Supported: ${chalk.green(result.supported.join(", "))}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (result.missing.length) {
|
|
364
|
+
logger.log(` Missing: ${chalk.red(result.missing.join(", "))}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
await shutdown();
|
|
369
|
+
} catch (err) {
|
|
370
|
+
logger.error(`Failed: ${err.message}`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit log commands
|
|
3
|
+
* chainlesschain audit log|search|stats|export|purge
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import { logger } from "../lib/logger.js";
|
|
9
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
10
|
+
import {
|
|
11
|
+
getRecentEvents,
|
|
12
|
+
queryLogs,
|
|
13
|
+
getStatistics,
|
|
14
|
+
exportLogs,
|
|
15
|
+
purgeLogs,
|
|
16
|
+
EVENT_TYPES,
|
|
17
|
+
RISK_LEVELS,
|
|
18
|
+
} from "../lib/audit-logger.js";
|
|
19
|
+
|
|
20
|
+
const RISK_COLORS = {
|
|
21
|
+
low: chalk.gray,
|
|
22
|
+
medium: chalk.yellow,
|
|
23
|
+
high: chalk.red,
|
|
24
|
+
critical: chalk.bgRed.white,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function formatLogEntry(log) {
|
|
28
|
+
const riskColor = RISK_COLORS[log.risk_level] || chalk.gray;
|
|
29
|
+
const status = log.success ? chalk.green("OK") : chalk.red("FAIL");
|
|
30
|
+
const time = log.created_at || "";
|
|
31
|
+
|
|
32
|
+
return [
|
|
33
|
+
` ${chalk.gray(log.id.slice(0, 8))} ${chalk.gray(time)}`,
|
|
34
|
+
` ${chalk.cyan(log.event_type.padEnd(12))} ${chalk.white(log.operation)} ${status} ${riskColor(`[${log.risk_level}]`)}`,
|
|
35
|
+
log.actor ? ` ${chalk.gray("actor:")} ${log.actor}` : null,
|
|
36
|
+
log.target ? ` ${chalk.gray("target:")} ${log.target}` : null,
|
|
37
|
+
log.error_message
|
|
38
|
+
? ` ${chalk.red("error:")} ${log.error_message}`
|
|
39
|
+
: null,
|
|
40
|
+
]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function registerAuditCommand(program) {
|
|
46
|
+
const audit = program
|
|
47
|
+
.command("audit")
|
|
48
|
+
.description("Audit log — security event tracking and compliance");
|
|
49
|
+
|
|
50
|
+
// audit log (default)
|
|
51
|
+
audit
|
|
52
|
+
.command("log", { isDefault: true })
|
|
53
|
+
.description("Show recent audit events")
|
|
54
|
+
.option("-n, --limit <n>", "Number of events to show", "20")
|
|
55
|
+
.option("--type <type>", "Filter by event type")
|
|
56
|
+
.option("--risk <level>", "Filter by risk level")
|
|
57
|
+
.option("--json", "Output as JSON")
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
try {
|
|
60
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
61
|
+
if (!ctx.db) {
|
|
62
|
+
logger.error("Database not available");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const db = ctx.db.getDatabase();
|
|
66
|
+
|
|
67
|
+
const filters = {
|
|
68
|
+
limit: parseInt(options.limit) || 20,
|
|
69
|
+
};
|
|
70
|
+
if (options.type) filters.eventType = options.type;
|
|
71
|
+
if (options.risk) filters.riskLevel = options.risk;
|
|
72
|
+
|
|
73
|
+
const logs = queryLogs(db, filters);
|
|
74
|
+
|
|
75
|
+
if (options.json) {
|
|
76
|
+
console.log(JSON.stringify(logs, null, 2));
|
|
77
|
+
} else if (logs.length === 0) {
|
|
78
|
+
logger.info("No audit events found");
|
|
79
|
+
} else {
|
|
80
|
+
logger.log(chalk.bold(`Audit Log (${logs.length} events):\n`));
|
|
81
|
+
for (const log of logs) {
|
|
82
|
+
logger.log(formatLogEntry(log));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await shutdown();
|
|
87
|
+
} catch (err) {
|
|
88
|
+
logger.error(`Failed: ${err.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// audit search
|
|
94
|
+
audit
|
|
95
|
+
.command("search")
|
|
96
|
+
.description("Search audit logs")
|
|
97
|
+
.argument("<query>", "Search query")
|
|
98
|
+
.option("-n, --limit <n>", "Max results", "50")
|
|
99
|
+
.option("--type <type>", "Filter by event type")
|
|
100
|
+
.option("--risk <level>", "Filter by risk level")
|
|
101
|
+
.option("--from <date>", "Start date (ISO 8601)")
|
|
102
|
+
.option("--to <date>", "End date (ISO 8601)")
|
|
103
|
+
.option("--failures", "Show only failed events")
|
|
104
|
+
.option("--json", "Output as JSON")
|
|
105
|
+
.action(async (query, options) => {
|
|
106
|
+
try {
|
|
107
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
108
|
+
if (!ctx.db) {
|
|
109
|
+
logger.error("Database not available");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const db = ctx.db.getDatabase();
|
|
113
|
+
|
|
114
|
+
const filters = {
|
|
115
|
+
search: query,
|
|
116
|
+
limit: parseInt(options.limit) || 50,
|
|
117
|
+
};
|
|
118
|
+
if (options.type) filters.eventType = options.type;
|
|
119
|
+
if (options.risk) filters.riskLevel = options.risk;
|
|
120
|
+
if (options.from) filters.startDate = options.from;
|
|
121
|
+
if (options.to) filters.endDate = options.to;
|
|
122
|
+
if (options.failures) filters.success = false;
|
|
123
|
+
|
|
124
|
+
const logs = queryLogs(db, filters);
|
|
125
|
+
|
|
126
|
+
if (options.json) {
|
|
127
|
+
console.log(JSON.stringify(logs, null, 2));
|
|
128
|
+
} else if (logs.length === 0) {
|
|
129
|
+
logger.info(`No audit events matching "${query}"`);
|
|
130
|
+
} else {
|
|
131
|
+
logger.log(chalk.bold(`Search Results (${logs.length}):\n`));
|
|
132
|
+
for (const log of logs) {
|
|
133
|
+
logger.log(formatLogEntry(log));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await shutdown();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
logger.error(`Failed: ${err.message}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// audit stats
|
|
145
|
+
audit
|
|
146
|
+
.command("stats")
|
|
147
|
+
.description("Show audit statistics")
|
|
148
|
+
.option("--from <date>", "Start date")
|
|
149
|
+
.option("--to <date>", "End date")
|
|
150
|
+
.option("--json", "Output as JSON")
|
|
151
|
+
.action(async (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 stats = getStatistics(db, options.from, options.to);
|
|
160
|
+
|
|
161
|
+
if (options.json) {
|
|
162
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
163
|
+
} else {
|
|
164
|
+
logger.log(chalk.bold("Audit Statistics:\n"));
|
|
165
|
+
logger.log(` ${chalk.bold("Total events:")} ${stats.total}`);
|
|
166
|
+
logger.log(
|
|
167
|
+
` ${chalk.bold("Failures:")} ${chalk.red(stats.failures)}`,
|
|
168
|
+
);
|
|
169
|
+
logger.log(
|
|
170
|
+
` ${chalk.bold("High risk:")} ${chalk.red(stats.highRisk)}`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (Object.keys(stats.byEventType).length > 0) {
|
|
174
|
+
logger.log(`\n ${chalk.bold("By Event Type:")}`);
|
|
175
|
+
for (const [type, count] of Object.entries(stats.byEventType)) {
|
|
176
|
+
logger.log(` ${chalk.cyan(type.padEnd(15))} ${count}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (Object.keys(stats.byRiskLevel).length > 0) {
|
|
181
|
+
logger.log(`\n ${chalk.bold("By Risk Level:")}`);
|
|
182
|
+
for (const [level, count] of Object.entries(stats.byRiskLevel)) {
|
|
183
|
+
const color = RISK_COLORS[level] || chalk.gray;
|
|
184
|
+
logger.log(` ${color(level.padEnd(15))} ${count}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await shutdown();
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.error(`Failed: ${err.message}`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// audit export
|
|
197
|
+
audit
|
|
198
|
+
.command("export")
|
|
199
|
+
.description("Export audit logs to file")
|
|
200
|
+
.option("-o, --output <path>", "Output file path")
|
|
201
|
+
.option("-f, --format <fmt>", "Format: json or csv", "json")
|
|
202
|
+
.option("--from <date>", "Start date")
|
|
203
|
+
.option("--to <date>", "End date")
|
|
204
|
+
.option("-n, --limit <n>", "Max events", "10000")
|
|
205
|
+
.action(async (options) => {
|
|
206
|
+
try {
|
|
207
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
208
|
+
if (!ctx.db) {
|
|
209
|
+
logger.error("Database not available");
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
const db = ctx.db.getDatabase();
|
|
213
|
+
|
|
214
|
+
const filters = { limit: parseInt(options.limit) || 10000 };
|
|
215
|
+
if (options.from) filters.startDate = options.from;
|
|
216
|
+
if (options.to) filters.endDate = options.to;
|
|
217
|
+
|
|
218
|
+
const data = exportLogs(db, options.format, filters);
|
|
219
|
+
|
|
220
|
+
if (options.output) {
|
|
221
|
+
fs.writeFileSync(options.output, data, "utf8");
|
|
222
|
+
logger.success(`Exported to ${options.output}`);
|
|
223
|
+
} else {
|
|
224
|
+
console.log(data);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await shutdown();
|
|
228
|
+
} catch (err) {
|
|
229
|
+
logger.error(`Failed: ${err.message}`);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// audit purge
|
|
235
|
+
audit
|
|
236
|
+
.command("purge")
|
|
237
|
+
.description("Delete old audit logs")
|
|
238
|
+
.option("--days <n>", "Keep logs from last N days", "90")
|
|
239
|
+
.option("--force", "Skip confirmation")
|
|
240
|
+
.action(async (options) => {
|
|
241
|
+
try {
|
|
242
|
+
const days = parseInt(options.days) || 90;
|
|
243
|
+
|
|
244
|
+
if (!options.force) {
|
|
245
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
246
|
+
const ok = await confirm({
|
|
247
|
+
message: `Delete audit logs older than ${days} days? This cannot be undone.`,
|
|
248
|
+
});
|
|
249
|
+
if (!ok) {
|
|
250
|
+
logger.info("Cancelled");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
256
|
+
if (!ctx.db) {
|
|
257
|
+
logger.error("Database not available");
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
const db = ctx.db.getDatabase();
|
|
261
|
+
const deleted = purgeLogs(db, days);
|
|
262
|
+
logger.success(`Purged ${deleted} old audit events`);
|
|
263
|
+
|
|
264
|
+
await shutdown();
|
|
265
|
+
} catch (err) {
|
|
266
|
+
logger.error(`Failed: ${err.message}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// audit types
|
|
272
|
+
audit
|
|
273
|
+
.command("types")
|
|
274
|
+
.description("List available event types and risk levels")
|
|
275
|
+
.action(async () => {
|
|
276
|
+
logger.log(chalk.bold("Event Types:\n"));
|
|
277
|
+
for (const [key, value] of Object.entries(EVENT_TYPES)) {
|
|
278
|
+
logger.log(` ${chalk.cyan(value.padEnd(15))} ${chalk.gray(key)}`);
|
|
279
|
+
}
|
|
280
|
+
logger.log(chalk.bold("\nRisk Levels:\n"));
|
|
281
|
+
for (const [key, value] of Object.entries(RISK_LEVELS)) {
|
|
282
|
+
const color = RISK_COLORS[value] || chalk.gray;
|
|
283
|
+
logger.log(` ${color(value.padEnd(15))} ${chalk.gray(key)}`);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|