centient 2.20.0 → 2.25.0

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 (120) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +134 -21
  3. package/dist/cli.d.ts +38 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +816 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands.d.ts +18 -0
  8. package/dist/commands.d.ts.map +1 -0
  9. package/dist/commands.js +341 -0
  10. package/dist/commands.js.map +1 -0
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +86 -18
  14. package/dist/index.js.map +1 -1
  15. package/dist/server.d.ts +4 -0
  16. package/dist/server.d.ts.map +1 -1
  17. package/dist/server.js +192 -88
  18. package/dist/server.js.map +1 -1
  19. package/dist/telemetry/tracer.d.ts.map +1 -1
  20. package/dist/telemetry/tracer.js +15 -1
  21. package/dist/telemetry/tracer.js.map +1 -1
  22. package/dist/tools/knowledge/promoteDecision.d.ts.map +1 -1
  23. package/dist/tools/knowledge/promoteDecision.js +7 -1
  24. package/dist/tools/knowledge/promoteDecision.js.map +1 -1
  25. package/dist/tools/knowledge/promoteLearning.d.ts.map +1 -1
  26. package/dist/tools/knowledge/promoteLearning.js +7 -1
  27. package/dist/tools/knowledge/promoteLearning.js.map +1 -1
  28. package/dist/tools/memory-bank/pushToMemoryBank.d.ts +9 -0
  29. package/dist/tools/memory-bank/pushToMemoryBank.d.ts.map +1 -0
  30. package/dist/tools/memory-bank/pushToMemoryBank.js +195 -0
  31. package/dist/tools/memory-bank/pushToMemoryBank.js.map +1 -0
  32. package/dist/tools/patterns/executeSkill.js +0 -41
  33. package/dist/tools/patterns/executeSkill.js.map +1 -1
  34. package/dist/tools/patterns/loadSkill.js +0 -17
  35. package/dist/tools/patterns/loadSkill.js.map +1 -1
  36. package/dist/tools/patterns/searchPatterns.js +0 -15
  37. package/dist/tools/patterns/searchPatterns.js.map +1 -1
  38. package/dist/tools/patterns/signPattern.js +0 -20
  39. package/dist/tools/patterns/signPattern.js.map +1 -1
  40. package/dist/tools/trails/executeTrailScript.d.ts +19 -0
  41. package/dist/tools/trails/executeTrailScript.d.ts.map +1 -0
  42. package/dist/tools/trails/executeTrailScript.js +65 -0
  43. package/dist/tools/trails/executeTrailScript.js.map +1 -0
  44. package/dist/tools/trails/index.d.ts +9 -0
  45. package/dist/tools/trails/index.d.ts.map +1 -0
  46. package/dist/tools/trails/index.js +9 -0
  47. package/dist/tools/trails/index.js.map +1 -0
  48. package/dist/tools/trails/trailDiscoverAuto.d.ts +9 -0
  49. package/dist/tools/trails/trailDiscoverAuto.d.ts.map +1 -0
  50. package/dist/tools/trails/trailDiscoverAuto.js +76 -0
  51. package/dist/tools/trails/trailDiscoverAuto.js.map +1 -0
  52. package/dist/tools/trails/trailDiscoverConvergence.d.ts +9 -0
  53. package/dist/tools/trails/trailDiscoverConvergence.d.ts.map +1 -0
  54. package/dist/tools/trails/trailDiscoverConvergence.js +66 -0
  55. package/dist/tools/trails/trailDiscoverConvergence.js.map +1 -0
  56. package/dist/tools/trails/trailDiscoverInfluence.d.ts +9 -0
  57. package/dist/tools/trails/trailDiscoverInfluence.d.ts.map +1 -0
  58. package/dist/tools/trails/trailDiscoverInfluence.js +79 -0
  59. package/dist/tools/trails/trailDiscoverInfluence.js.map +1 -0
  60. package/dist/tools/trails/trailList.d.ts +9 -0
  61. package/dist/tools/trails/trailList.d.ts.map +1 -0
  62. package/dist/tools/trails/trailList.js +74 -0
  63. package/dist/tools/trails/trailList.js.map +1 -0
  64. package/dist/tools/trails/trailQueryByTopic.d.ts +9 -0
  65. package/dist/tools/trails/trailQueryByTopic.d.ts.map +1 -0
  66. package/dist/tools/trails/trailQueryByTopic.js +40 -0
  67. package/dist/tools/trails/trailQueryByTopic.js.map +1 -0
  68. package/dist/tools/trails/trailQueryIntersect.d.ts +9 -0
  69. package/dist/tools/trails/trailQueryIntersect.d.ts.map +1 -0
  70. package/dist/tools/trails/trailQueryIntersect.js +54 -0
  71. package/dist/tools/trails/trailQueryIntersect.js.map +1 -0
  72. package/dist/tools/trails/trailQueryTopics.d.ts +9 -0
  73. package/dist/tools/trails/trailQueryTopics.d.ts.map +1 -0
  74. package/dist/tools/trails/trailQueryTopics.js +44 -0
  75. package/dist/tools/trails/trailQueryTopics.js.map +1 -0
  76. package/dist/tools/trails/trailView.d.ts +9 -0
  77. package/dist/tools/trails/trailView.d.ts.map +1 -0
  78. package/dist/tools/trails/trailView.js +94 -0
  79. package/dist/tools/trails/trailView.js.map +1 -0
  80. package/dist/utils/AuditLogger.d.ts +25 -18
  81. package/dist/utils/AuditLogger.d.ts.map +1 -1
  82. package/dist/utils/AuditLogger.js +261 -150
  83. package/dist/utils/AuditLogger.js.map +1 -1
  84. package/dist/utils/GeminiEmbeddingService.d.ts +25 -0
  85. package/dist/utils/GeminiEmbeddingService.d.ts.map +1 -0
  86. package/dist/utils/GeminiEmbeddingService.js +115 -0
  87. package/dist/utils/GeminiEmbeddingService.js.map +1 -0
  88. package/dist/utils/InstanceRegistry.d.ts +47 -0
  89. package/dist/utils/InstanceRegistry.d.ts.map +1 -0
  90. package/dist/utils/InstanceRegistry.js +195 -0
  91. package/dist/utils/InstanceRegistry.js.map +1 -0
  92. package/dist/utils/MetaKnowledgeEncryption.d.ts +21 -0
  93. package/dist/utils/MetaKnowledgeEncryption.d.ts.map +1 -0
  94. package/dist/utils/MetaKnowledgeEncryption.js +125 -0
  95. package/dist/utils/MetaKnowledgeEncryption.js.map +1 -0
  96. package/dist/utils/MetaKnowledgeManager.d.ts +18 -0
  97. package/dist/utils/MetaKnowledgeManager.d.ts.map +1 -1
  98. package/dist/utils/MetaKnowledgeManager.js +110 -6
  99. package/dist/utils/MetaKnowledgeManager.js.map +1 -1
  100. package/dist/utils/PatternIndexer.d.ts +1 -2
  101. package/dist/utils/PatternIndexer.d.ts.map +1 -1
  102. package/dist/utils/PatternIndexer.js +40 -72
  103. package/dist/utils/PatternIndexer.js.map +1 -1
  104. package/dist/utils/SessionCoordinator.d.ts +0 -1
  105. package/dist/utils/SessionCoordinator.d.ts.map +1 -1
  106. package/dist/utils/SessionCoordinator.js +3 -61
  107. package/dist/utils/SessionCoordinator.js.map +1 -1
  108. package/dist/utils/VertexSync.d.ts +12 -0
  109. package/dist/utils/VertexSync.d.ts.map +1 -0
  110. package/dist/utils/VertexSync.js +173 -0
  111. package/dist/utils/VertexSync.js.map +1 -0
  112. package/dist/utils/config.d.ts +60 -0
  113. package/dist/utils/config.d.ts.map +1 -0
  114. package/dist/utils/config.js +246 -0
  115. package/dist/utils/config.js.map +1 -0
  116. package/dist/utils/rlvr/RewardHistoryStore.d.ts +0 -2
  117. package/dist/utils/rlvr/RewardHistoryStore.d.ts.map +1 -1
  118. package/dist/utils/rlvr/RewardHistoryStore.js +5 -18
  119. package/dist/utils/rlvr/RewardHistoryStore.js.map +1 -1
  120. package/package.json +1 -1
package/dist/cli.js ADDED
@@ -0,0 +1,816 @@
1
+ import { readFileSync, existsSync, watch, statSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { homedir } from "os";
5
+ import { createReadStream } from "fs";
6
+ import { createInterface } from "readline";
7
+ import { listInstances, getAggregatedStats, formatUptime, isHeartbeatStale, } from "./utils/InstanceRegistry.js";
8
+ import { getAuditLogPath as getConfigAuditLogPath, getMetaKnowledgeConfig } from "./utils/config.js";
9
+ import { getSharedPatternsPath } from "./utils/filesystem.js";
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ function getVersion() {
12
+ try {
13
+ const packagePath = join(__dirname, "..", "package.json");
14
+ const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
15
+ return pkg.version || "unknown";
16
+ }
17
+ catch {
18
+ return "unknown";
19
+ }
20
+ }
21
+ export function printVersion() {
22
+ console.log(`centient ${getVersion()}`);
23
+ }
24
+ export function printHelp() {
25
+ const version = getVersion();
26
+ console.log(`centient v${version} - MCP server for context engineering
27
+
28
+ Usage: centient [command] [options]
29
+
30
+ Commands:
31
+ init Initialize .claude/ directory with commands
32
+ check-commands Show missing/outdated commands
33
+ update-commands Update outdated commands
34
+
35
+ Options:
36
+ -V, --version Show version number
37
+ -h, --help Show this help message
38
+ -l, --list-tools List all available MCP tools
39
+ --health Run health check and exit
40
+ --config Show current configuration
41
+ --stats Show usage statistics and exit
42
+ --force Force overwrite (for init/update-commands)
43
+ --dry-run Show what would be updated without making changes
44
+
45
+ Instance Monitoring (like ps):
46
+ --monitor List all running centient instances
47
+ -p, --project <name> Filter instances by project name
48
+ -f, --follow Watch for activity in real-time
49
+ -j, --json Output in JSON format
50
+ -q, --quiet Output just PIDs (for scripting)
51
+ -s, --summary Show aggregate statistics only
52
+
53
+ Audit Log (per-project):
54
+ --log Tail local audit log
55
+ --last <n> Show last n events (default: 20)
56
+ -f, --follow Continuous tailing mode
57
+
58
+ Running without arguments starts the MCP server (stdio transport).
59
+
60
+ Examples:
61
+ centient init # Initialize project with commands
62
+ centient init --force # Overwrite existing commands
63
+ centient check-commands # Check command status
64
+ centient update-commands # Update outdated commands
65
+ centient update-commands --dry-run # Preview what would be updated
66
+ centient --monitor # Show all running instances
67
+ centient --monitor -f # Watch activity across all instances
68
+ centient --log --last 50 # Show last 50 audit log events
69
+
70
+ Documentation: https://github.com/Szermer/centient
71
+ `);
72
+ }
73
+ export function printTools(tools) {
74
+ const version = getVersion();
75
+ console.log(`centient v${version} - Available MCP Tools (${tools.length} total)\n`);
76
+ const modules = {};
77
+ for (const tool of tools) {
78
+ let module = "other";
79
+ const name = tool.name;
80
+ if (name.includes("Pattern") || name.includes("Skill") || name === "findPatterns") {
81
+ module = "patterns";
82
+ }
83
+ else if (name.includes("Artifact") || (name.includes("Session") && name.includes("load"))) {
84
+ module = "artifacts";
85
+ }
86
+ else if (name.includes("Note") || name === "getDecisions" || name === "getHypotheses") {
87
+ module = "memory";
88
+ }
89
+ else if (name.includes("Compression") || name.includes("Reuse") || name.includes("Cost") || name.includes("Analytics") || name.includes("Audit")) {
90
+ module = "metrics";
91
+ }
92
+ else if (name.includes("semantic") || (name.includes("index") && name.includes("Session")) || name.includes("SearchStats")) {
93
+ module = "search";
94
+ }
95
+ else if (name.includes("session") || name.includes("Session") || name.includes("Constraint") || name.includes("Claim") || name.includes("Citation") || name.includes("Verification") || name.includes("Feasibility") || name.includes("Duplicate") || name.includes("Approval")) {
96
+ module = "session";
97
+ }
98
+ else if (name.includes("Stuck") || name.includes("Recovery")) {
99
+ module = "stuck";
100
+ }
101
+ else if (name.includes("Health") || name.includes("Circuit") || name.includes("RateLimit") || name.includes("Qdrant")) {
102
+ module = "health";
103
+ }
104
+ else if (name.includes("Research") || name.includes("Model")) {
105
+ module = "research";
106
+ }
107
+ else if (name.includes("memory_bank")) {
108
+ module = "memory-bank";
109
+ }
110
+ else if (name.includes("Relationship") || name.includes("Graph") || name.includes("link")) {
111
+ module = "graph";
112
+ }
113
+ else if (name.includes("consult") || name.includes("Consultation") || name.includes("Consensus") || name.includes("Review") || (name.includes("Decision") && name.includes("validate"))) {
114
+ module = "consultation";
115
+ }
116
+ else if (name.includes("Branch") || name.includes("branch") || (name.includes("Decision") && name.includes("Point"))) {
117
+ module = "branching";
118
+ }
119
+ else if (name.includes("rlvr")) {
120
+ module = "rlvr";
121
+ }
122
+ else if (name.includes("Learning") || name.includes("Knowledge") || name.includes("promote")) {
123
+ module = "knowledge";
124
+ }
125
+ else if (name.includes("trail")) {
126
+ module = "trails";
127
+ }
128
+ (modules[module] ??= []).push(tool);
129
+ }
130
+ const moduleOrder = [
131
+ "patterns",
132
+ "artifacts",
133
+ "memory",
134
+ "metrics",
135
+ "search",
136
+ "session",
137
+ "stuck",
138
+ "health",
139
+ "research",
140
+ "memory-bank",
141
+ "graph",
142
+ "consultation",
143
+ "branching",
144
+ "rlvr",
145
+ "knowledge",
146
+ "trails",
147
+ "other",
148
+ ];
149
+ for (const moduleName of moduleOrder) {
150
+ const moduleTools = modules[moduleName];
151
+ if (!moduleTools || moduleTools.length === 0)
152
+ continue;
153
+ console.log(`${moduleName.toUpperCase()} (${moduleTools.length} tools)`);
154
+ console.log("─".repeat(60));
155
+ for (const tool of moduleTools) {
156
+ const desc = tool.description?.split("\n")[0] || "";
157
+ const truncatedDesc = desc.length > 50 ? desc.slice(0, 47) + "..." : desc;
158
+ console.log(` ${tool.name.padEnd(35)} ${truncatedDesc}`);
159
+ }
160
+ console.log("");
161
+ }
162
+ }
163
+ export async function printHealth() {
164
+ const version = getVersion();
165
+ console.log(`centient v${version} health check\n`);
166
+ const envVars = {
167
+ QDRANT_URL: process.env.QDRANT_URL,
168
+ QDRANT_API_KEY: process.env.QDRANT_API_KEY,
169
+ GEMINI_API_KEY: process.env.GEMINI_API_KEY,
170
+ OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,
171
+ };
172
+ console.log("Environment:");
173
+ for (const [key, value] of Object.entries(envVars)) {
174
+ const status = value ? "\x1b[32m✓\x1b[0m configured" : "\x1b[31m✗\x1b[0m not set";
175
+ console.log(` ${key.padEnd(20)} ${status}`);
176
+ }
177
+ console.log("\nServices:");
178
+ if (envVars.QDRANT_URL && envVars.QDRANT_API_KEY) {
179
+ try {
180
+ const response = await fetch(`${envVars.QDRANT_URL}/collections`, {
181
+ headers: {
182
+ "api-key": envVars.QDRANT_API_KEY,
183
+ },
184
+ signal: AbortSignal.timeout(5000),
185
+ });
186
+ if (response.ok) {
187
+ console.log(" Qdrant Cloud \x1b[32m✓\x1b[0m connected");
188
+ }
189
+ else {
190
+ console.log(` Qdrant Cloud \x1b[31m✗\x1b[0m error (${response.status})`);
191
+ }
192
+ }
193
+ catch (error) {
194
+ const msg = error instanceof Error ? error.message : "unknown error";
195
+ console.log(` Qdrant Cloud \x1b[31m✗\x1b[0m ${msg}`);
196
+ }
197
+ }
198
+ else {
199
+ console.log(" Qdrant Cloud \x1b[33m-\x1b[0m not configured");
200
+ }
201
+ if (envVars.GEMINI_API_KEY) {
202
+ try {
203
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${envVars.GEMINI_API_KEY}`, { signal: AbortSignal.timeout(5000) });
204
+ if (response.ok) {
205
+ console.log(" Gemini API \x1b[32m✓\x1b[0m connected");
206
+ }
207
+ else {
208
+ console.log(` Gemini API \x1b[31m✗\x1b[0m error (${response.status})`);
209
+ }
210
+ }
211
+ catch (error) {
212
+ const msg = error instanceof Error ? error.message : "unknown error";
213
+ console.log(` Gemini API \x1b[31m✗\x1b[0m ${msg}`);
214
+ }
215
+ }
216
+ else {
217
+ console.log(" Gemini API \x1b[33m-\x1b[0m not configured");
218
+ }
219
+ if (envVars.OPENROUTER_API_KEY) {
220
+ try {
221
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
222
+ headers: {
223
+ Authorization: `Bearer ${envVars.OPENROUTER_API_KEY}`,
224
+ },
225
+ signal: AbortSignal.timeout(5000),
226
+ });
227
+ if (response.ok) {
228
+ console.log(" OpenRouter API \x1b[32m✓\x1b[0m connected");
229
+ }
230
+ else {
231
+ console.log(` OpenRouter API \x1b[31m✗\x1b[0m error (${response.status})`);
232
+ }
233
+ }
234
+ catch (error) {
235
+ const msg = error instanceof Error ? error.message : "unknown error";
236
+ console.log(` OpenRouter API \x1b[31m✗\x1b[0m ${msg}`);
237
+ }
238
+ }
239
+ else {
240
+ console.log(" OpenRouter API \x1b[33m-\x1b[0m not configured");
241
+ }
242
+ console.log("");
243
+ }
244
+ export function printConfig() {
245
+ const version = getVersion();
246
+ console.log(`centient v${version} configuration\n`);
247
+ function redact(value) {
248
+ if (!value)
249
+ return "(not set)";
250
+ if (value.length <= 8)
251
+ return "***";
252
+ return value.slice(0, 4) + "..." + value.slice(-4);
253
+ }
254
+ console.log("Environment Variables:");
255
+ console.log(` QDRANT_URL ${process.env.QDRANT_URL || "(not set)"}`);
256
+ console.log(` QDRANT_API_KEY ${redact(process.env.QDRANT_API_KEY)}`);
257
+ console.log(` GEMINI_API_KEY ${redact(process.env.GEMINI_API_KEY)}`);
258
+ console.log(` OPENROUTER_API_KEY ${redact(process.env.OPENROUTER_API_KEY)}`);
259
+ console.log("\nPaths:");
260
+ console.log(` Shared Patterns ${getSharedPatternsPath()}`);
261
+ console.log(` MCP Context ${join(homedir(), ".mcp-context")}`);
262
+ console.log(` Audit Log ${getConfigAuditLogPath()}`);
263
+ const metaConfig = getMetaKnowledgeConfig();
264
+ console.log("\nMeta-Knowledge (Tier 3):");
265
+ console.log(` Path ${metaConfig.path}`);
266
+ console.log(` Encryption ${metaConfig.encryption.enabled ? `✓ enabled (${metaConfig.encryption.keySource})` : "✗ disabled"}`);
267
+ console.log(` Vertex Sync ${metaConfig.vertex.enabled ? `✓ enabled (${metaConfig.vertex.projectId || "no project"})` : "✗ disabled"}`);
268
+ console.log(` Retention ${metaConfig.retention.days > 0 ? `${metaConfig.retention.days} days` : "∞ forever"}`);
269
+ if (metaConfig.retention.days > 0) {
270
+ console.log(` Archive Path ${metaConfig.retention.archivePath}`);
271
+ }
272
+ console.log("\nRuntime:");
273
+ console.log(` Node.js ${process.version}`);
274
+ console.log(` Platform ${process.platform} ${process.arch}`);
275
+ console.log(` Working Directory ${process.cwd()}`);
276
+ console.log("");
277
+ }
278
+ const isTTY = process.stdout.isTTY ?? false;
279
+ const colors = isTTY ? {
280
+ reset: "\x1b[0m",
281
+ bright: "\x1b[1m",
282
+ dim: "\x1b[2m",
283
+ red: "\x1b[31m",
284
+ green: "\x1b[32m",
285
+ yellow: "\x1b[33m",
286
+ blue: "\x1b[34m",
287
+ magenta: "\x1b[35m",
288
+ cyan: "\x1b[36m",
289
+ white: "\x1b[37m",
290
+ gray: "\x1b[90m",
291
+ } : {
292
+ reset: "",
293
+ bright: "",
294
+ dim: "",
295
+ red: "",
296
+ green: "",
297
+ yellow: "",
298
+ blue: "",
299
+ magenta: "",
300
+ cyan: "",
301
+ white: "",
302
+ gray: "",
303
+ };
304
+ function formatLogEntry(entry) {
305
+ const time = new Date(entry.timestamp).toLocaleTimeString();
306
+ const outcomeColor = entry.outcome === "success" ? colors.green : colors.red;
307
+ const outcomeSymbol = entry.outcome === "success" ? "✓" : "✗";
308
+ const duration = `${entry.duration}ms`.padStart(6);
309
+ let summary = "";
310
+ if (entry.output) {
311
+ if ("resultCount" in entry.output) {
312
+ summary = `${entry.output.resultCount} results`;
313
+ }
314
+ else if ("errorMessage" in entry.output) {
315
+ const msg = String(entry.output.errorMessage);
316
+ summary = msg.length > 30 ? msg.slice(0, 27) + "..." : msg;
317
+ }
318
+ else if ("tokensUsed" in entry.output) {
319
+ summary = `${entry.output.tokensUsed} tokens`;
320
+ }
321
+ }
322
+ return `${colors.gray}${time}${colors.reset} │ ${colors.cyan}${entry.tool.padEnd(28)}${colors.reset} │ ${outcomeColor}${outcomeSymbol} ${duration}${colors.reset} │ ${summary}`;
323
+ }
324
+ function getAuditLogPath() {
325
+ return getConfigAuditLogPath();
326
+ }
327
+ async function readLastLines(filePath, n) {
328
+ if (!existsSync(filePath)) {
329
+ return [];
330
+ }
331
+ const entries = [];
332
+ const fileStream = createReadStream(filePath);
333
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
334
+ for await (const line of rl) {
335
+ if (line.trim()) {
336
+ try {
337
+ entries.push(JSON.parse(line));
338
+ }
339
+ catch {
340
+ }
341
+ }
342
+ }
343
+ return entries.slice(-n);
344
+ }
345
+ export async function printStats() {
346
+ const version = getVersion();
347
+ console.log(`centient v${version} statistics\n`);
348
+ const logPath = getAuditLogPath();
349
+ if (!existsSync(logPath)) {
350
+ console.log(`${colors.yellow}No audit log found at ${logPath}${colors.reset}`);
351
+ console.log("Statistics will be available after centient processes some requests.\n");
352
+ return;
353
+ }
354
+ const stats = {
355
+ totalEvents: 0,
356
+ successCount: 0,
357
+ failureCount: 0,
358
+ totalDuration: 0,
359
+ toolCounts: {},
360
+ toolTotalDurations: {},
361
+ toolDurationCounts: {},
362
+ eventTypeCounts: {},
363
+ firstTimestamp: null,
364
+ lastTimestamp: null,
365
+ };
366
+ const fileStream = createReadStream(logPath);
367
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
368
+ for await (const line of rl) {
369
+ if (!line.trim())
370
+ continue;
371
+ let entry;
372
+ try {
373
+ entry = JSON.parse(line);
374
+ }
375
+ catch {
376
+ continue;
377
+ }
378
+ stats.totalEvents++;
379
+ if (!stats.firstTimestamp) {
380
+ stats.firstTimestamp = entry.timestamp;
381
+ }
382
+ stats.lastTimestamp = entry.timestamp;
383
+ stats.toolCounts[entry.tool] = (stats.toolCounts[entry.tool] || 0) + 1;
384
+ stats.toolTotalDurations[entry.tool] = (stats.toolTotalDurations[entry.tool] || 0) + entry.duration;
385
+ stats.toolDurationCounts[entry.tool] = (stats.toolDurationCounts[entry.tool] || 0) + 1;
386
+ stats.eventTypeCounts[entry.eventType] = (stats.eventTypeCounts[entry.eventType] || 0) + 1;
387
+ if (entry.outcome === "success") {
388
+ stats.successCount++;
389
+ }
390
+ else {
391
+ stats.failureCount++;
392
+ }
393
+ stats.totalDuration += entry.duration;
394
+ }
395
+ if (stats.totalEvents === 0) {
396
+ console.log(`${colors.yellow}Audit log is empty${colors.reset}\n`);
397
+ return;
398
+ }
399
+ console.log(`${colors.bright}Summary${colors.reset}`);
400
+ console.log("─".repeat(50));
401
+ console.log(` Total events: ${stats.totalEvents}`);
402
+ console.log(` Success rate: ${colors.green}${((stats.successCount / stats.totalEvents) * 100).toFixed(1)}%${colors.reset} (${stats.successCount}/${stats.totalEvents})`);
403
+ console.log(` Total duration: ${(stats.totalDuration / 1000).toFixed(2)}s`);
404
+ console.log(` Avg duration: ${(stats.totalDuration / stats.totalEvents).toFixed(0)}ms`);
405
+ console.log("");
406
+ const sortedTools = Object.entries(stats.toolCounts)
407
+ .sort((a, b) => b[1] - a[1])
408
+ .slice(0, 10);
409
+ console.log(`${colors.bright}Top Tools${colors.reset}`);
410
+ console.log("─".repeat(50));
411
+ for (const [tool, count] of sortedTools) {
412
+ const totalDuration = stats.toolTotalDurations[tool] || 0;
413
+ const durationCount = stats.toolDurationCounts[tool] || 1;
414
+ const avgDuration = totalDuration / durationCount;
415
+ console.log(` ${tool.padEnd(30)} ${String(count).padStart(5)} calls ${avgDuration.toFixed(0).padStart(5)}ms avg`);
416
+ }
417
+ console.log("");
418
+ console.log(`${colors.bright}Event Types${colors.reset}`);
419
+ console.log("─".repeat(50));
420
+ for (const [eventType, count] of Object.entries(stats.eventTypeCounts).sort((a, b) => b[1] - a[1])) {
421
+ console.log(` ${eventType.padEnd(30)} ${String(count).padStart(5)}`);
422
+ }
423
+ console.log("");
424
+ if (stats.firstTimestamp && stats.lastTimestamp) {
425
+ console.log(`${colors.bright}Time Range${colors.reset}`);
426
+ console.log("─".repeat(50));
427
+ console.log(` From: ${new Date(stats.firstTimestamp).toLocaleString()}`);
428
+ console.log(` To: ${new Date(stats.lastTimestamp).toLocaleString()}`);
429
+ }
430
+ console.log("");
431
+ }
432
+ export async function log(options) {
433
+ const version = getVersion();
434
+ const logPath = getAuditLogPath();
435
+ console.log(`${colors.bright}AUDIT LOG${colors.reset} v${version}`);
436
+ console.log("─".repeat(80));
437
+ console.log(`${colors.gray}Time${colors.reset} │ ${colors.gray}Tool${colors.reset} │ ${colors.gray}Status${colors.reset} │ ${colors.gray}Details${colors.reset}`);
438
+ console.log("─".repeat(80));
439
+ if (!existsSync(logPath)) {
440
+ console.log(`\n${colors.yellow}Waiting for audit log to be created...${colors.reset}`);
441
+ console.log(`Expected at: ${logPath}\n`);
442
+ if (!options.follow) {
443
+ return;
444
+ }
445
+ const dir = dirname(logPath);
446
+ if (existsSync(dir)) {
447
+ await new Promise((resolve) => {
448
+ const watcher = watch(dir, (_eventType, filename) => {
449
+ if (filename === ".audit-log.jsonl" && existsSync(logPath)) {
450
+ watcher.close();
451
+ resolve();
452
+ }
453
+ });
454
+ });
455
+ }
456
+ }
457
+ const entries = await readLastLines(logPath, options.last);
458
+ for (const entry of entries) {
459
+ console.log(formatLogEntry(entry));
460
+ }
461
+ if (!options.follow) {
462
+ console.log("─".repeat(80));
463
+ console.log(`${colors.gray}Showing last ${entries.length} events. Use --follow to watch in real-time.${colors.reset}`);
464
+ return;
465
+ }
466
+ console.log("─".repeat(80));
467
+ console.log(`${colors.gray}Watching for new events... (Ctrl+C to exit)${colors.reset}`);
468
+ console.log("─".repeat(80));
469
+ let lastSize = statSync(logPath).size;
470
+ const logWatcher = watch(logPath, async () => {
471
+ try {
472
+ if (!existsSync(logPath)) {
473
+ return;
474
+ }
475
+ const currentSize = statSync(logPath).size;
476
+ if (currentSize > lastSize) {
477
+ const stream = createReadStream(logPath, { start: lastSize });
478
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
479
+ for await (const line of rl) {
480
+ if (line.trim()) {
481
+ try {
482
+ const entry = JSON.parse(line);
483
+ console.log(formatLogEntry(entry));
484
+ }
485
+ catch {
486
+ }
487
+ }
488
+ }
489
+ lastSize = currentSize;
490
+ }
491
+ }
492
+ catch (error) {
493
+ if (error.code !== "ENOENT") {
494
+ console.error(`${colors.red}Error reading log:${colors.reset}`, error);
495
+ }
496
+ }
497
+ });
498
+ const cleanup = () => {
499
+ logWatcher.close();
500
+ console.log(`\n${colors.gray}Monitor stopped.${colors.reset}`);
501
+ process.exit(0);
502
+ };
503
+ process.on("SIGINT", cleanup);
504
+ process.on("SIGTERM", cleanup);
505
+ await new Promise(() => {
506
+ });
507
+ }
508
+ function calculateReqPerMin(instance) {
509
+ if (instance.uptime === 0 || instance.requestCount === 0)
510
+ return 0;
511
+ const minutes = instance.uptime / 60;
512
+ if (minutes < 1)
513
+ return instance.requestCount;
514
+ return Math.round(instance.requestCount / minutes);
515
+ }
516
+ function getStatusIndicator(instance) {
517
+ if (isHeartbeatStale(instance)) {
518
+ return `${colors.yellow}?${colors.reset}`;
519
+ }
520
+ switch (instance.status) {
521
+ case "active":
522
+ return `${colors.green}●${colors.reset}`;
523
+ case "idle":
524
+ return `${colors.gray}○${colors.reset}`;
525
+ case "stopping":
526
+ return `${colors.red}-${colors.reset}`;
527
+ default:
528
+ return `${colors.blue}◐${colors.reset}`;
529
+ }
530
+ }
531
+ function formatInstanceRow(instance) {
532
+ const pid = String(instance.pid).padStart(5);
533
+ const project = instance.projectName.padEnd(24).slice(0, 24);
534
+ const tools = String(instance.toolCount).padStart(5);
535
+ const reqPerMin = String(calculateReqPerMin(instance)).padStart(5);
536
+ const mem = `${instance.memoryUsageMB}MB`.padStart(6);
537
+ const uptime = formatUptime(instance.uptime).padStart(7);
538
+ const status = getStatusIndicator(instance);
539
+ const statusText = instance.status.padEnd(7);
540
+ return ` ${pid} ${project} ${tools} ${reqPerMin} ${mem} ${uptime} ${status} ${statusText}`;
541
+ }
542
+ function printInstanceTableHeader(version, instanceCount) {
543
+ const countText = instanceCount === 1 ? "1 instance" : `${instanceCount} instances`;
544
+ console.log(`${colors.bright}centient-monitor${colors.reset} v${version} ${countText}`);
545
+ console.log("─".repeat(80));
546
+ console.log(`${colors.gray} PID PROJECT TOOLS REQ/M MEM UPTIME STATUS${colors.reset}`);
547
+ console.log("─".repeat(80));
548
+ }
549
+ function printInstancesJson(instances) {
550
+ const stats = getAggregatedStats();
551
+ const output = {
552
+ timestamp: new Date().toISOString(),
553
+ instances: instances.map(i => ({
554
+ ...i,
555
+ reqPerMin: calculateReqPerMin(i),
556
+ stale: isHeartbeatStale(i),
557
+ })),
558
+ summary: stats,
559
+ };
560
+ console.log(JSON.stringify(output, null, 2));
561
+ }
562
+ function printInstancesQuiet(instances) {
563
+ for (const instance of instances) {
564
+ console.log(instance.pid);
565
+ }
566
+ }
567
+ function printInstancesSummary(version) {
568
+ const stats = getAggregatedStats();
569
+ const instances = listInstances();
570
+ console.log(`${colors.bright}centient-monitor${colors.reset} v${version}\n`);
571
+ const activeText = stats.activeInstances > 0
572
+ ? `${colors.green}${stats.activeInstances} active${colors.reset}`
573
+ : "0 active";
574
+ const idleText = stats.idleInstances > 0
575
+ ? `${colors.gray}${stats.idleInstances} idle${colors.reset}`
576
+ : "0 idle";
577
+ console.log(`Instances: ${stats.totalInstances} total (${activeText}, ${idleText})`);
578
+ console.log(`Memory: ${stats.totalMemoryMB} MB total`);
579
+ console.log(`Requests: ${stats.totalRequests} total`);
580
+ if (instances.length > 0) {
581
+ const oldest = instances.reduce((a, b) => a.uptime > b.uptime ? a : b);
582
+ console.log(`Oldest: ${formatUptime(oldest.uptime)} (${oldest.projectName})`);
583
+ }
584
+ console.log("");
585
+ }
586
+ function formatActivityEntry(projectName, entry) {
587
+ const time = new Date(entry.timestamp).toLocaleTimeString();
588
+ const outcomeColor = entry.outcome === "success" ? colors.green : colors.red;
589
+ const outcomeSymbol = entry.outcome === "success" ? "✓" : "✗";
590
+ const duration = `${entry.duration}ms`.padStart(6);
591
+ return ` ${colors.gray}${time}${colors.reset} │ ${colors.cyan}${projectName.padEnd(16).slice(0, 16)}${colors.reset} │ ${entry.tool.padEnd(24).slice(0, 24)} │ ${outcomeColor}${outcomeSymbol}${colors.reset} ${duration}`;
592
+ }
593
+ async function watchAllInstanceLogs(instances, onActivity) {
594
+ const watchers = [];
595
+ const fileSizes = new Map();
596
+ for (const instance of instances) {
597
+ const logPath = join(instance.projectPath, ".audit-log.jsonl");
598
+ if (!existsSync(logPath))
599
+ continue;
600
+ try {
601
+ fileSizes.set(logPath, statSync(logPath).size);
602
+ const watcher = watch(logPath, async () => {
603
+ try {
604
+ if (!existsSync(logPath))
605
+ return;
606
+ const currentSize = statSync(logPath).size;
607
+ const lastSize = fileSizes.get(logPath) || 0;
608
+ if (currentSize > lastSize) {
609
+ const stream = createReadStream(logPath, { start: lastSize });
610
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
611
+ for await (const line of rl) {
612
+ if (line.trim()) {
613
+ try {
614
+ const entry = JSON.parse(line);
615
+ onActivity(instance.projectName, entry);
616
+ }
617
+ catch {
618
+ }
619
+ }
620
+ }
621
+ fileSizes.set(logPath, currentSize);
622
+ }
623
+ }
624
+ catch {
625
+ }
626
+ });
627
+ watchers.push(watcher);
628
+ }
629
+ catch {
630
+ }
631
+ }
632
+ return () => {
633
+ for (const watcher of watchers) {
634
+ watcher.close();
635
+ }
636
+ };
637
+ }
638
+ export async function monitor(options) {
639
+ const version = getVersion();
640
+ let instances = listInstances();
641
+ if (options.project) {
642
+ instances = instances.filter(i => i.projectName.toLowerCase().includes(options.project.toLowerCase()));
643
+ }
644
+ if (options.quiet) {
645
+ printInstancesQuiet(instances);
646
+ return;
647
+ }
648
+ if (options.json && !options.follow) {
649
+ printInstancesJson(instances);
650
+ return;
651
+ }
652
+ if (options.summary) {
653
+ printInstancesSummary(version);
654
+ return;
655
+ }
656
+ printInstanceTableHeader(version, instances.length);
657
+ if (instances.length === 0) {
658
+ console.log(`\n${colors.yellow}No running centient instances found.${colors.reset}`);
659
+ console.log(`${colors.gray}Start a centient MCP server to see it here.${colors.reset}\n`);
660
+ return;
661
+ }
662
+ for (const instance of instances) {
663
+ console.log(formatInstanceRow(instance));
664
+ }
665
+ console.log("─".repeat(80));
666
+ if (!options.follow) {
667
+ return;
668
+ }
669
+ console.log(`\n${colors.bright}Recent Activity${colors.reset}`);
670
+ console.log("─".repeat(80));
671
+ console.log(`${colors.gray}Watching for new events... (Ctrl+C to exit)${colors.reset}\n`);
672
+ const cleanup = await watchAllInstanceLogs(instances, (projectName, entry) => {
673
+ if (options.json) {
674
+ console.log(JSON.stringify({
675
+ type: "activity",
676
+ timestamp: entry.timestamp,
677
+ project: projectName,
678
+ tool: entry.tool,
679
+ duration: entry.duration,
680
+ outcome: entry.outcome,
681
+ }));
682
+ }
683
+ else {
684
+ console.log(formatActivityEntry(projectName, entry));
685
+ }
686
+ });
687
+ const shutdownHandler = () => {
688
+ cleanup();
689
+ console.log(`\n${colors.gray}Monitor stopped.${colors.reset}`);
690
+ process.exit(0);
691
+ };
692
+ process.on("SIGINT", shutdownHandler);
693
+ process.on("SIGTERM", shutdownHandler);
694
+ await new Promise(() => {
695
+ });
696
+ }
697
+ export function parseArgs(args) {
698
+ let logLast = 20;
699
+ let logFollow = false;
700
+ let monitorFollow = false;
701
+ let monitorProject;
702
+ let monitorJson = false;
703
+ let monitorQuiet = false;
704
+ let monitorSummary = false;
705
+ let initForce = false;
706
+ let updateDryRun = false;
707
+ let hasMonitor = false;
708
+ let hasLog = false;
709
+ let hasInit = false;
710
+ let hasCheckCommands = false;
711
+ let hasUpdateCommands = false;
712
+ for (let i = 0; i < args.length; i++) {
713
+ const arg = args[i];
714
+ switch (arg) {
715
+ case "-V":
716
+ case "--version":
717
+ return { action: "version" };
718
+ case "-h":
719
+ case "--help":
720
+ return { action: "help" };
721
+ case "-l":
722
+ case "--list-tools":
723
+ return { action: "list-tools" };
724
+ case "--health":
725
+ return { action: "health" };
726
+ case "--config":
727
+ return { action: "config" };
728
+ case "--stats":
729
+ return { action: "stats" };
730
+ case "init":
731
+ hasInit = true;
732
+ break;
733
+ case "check-commands":
734
+ hasCheckCommands = true;
735
+ break;
736
+ case "update-commands":
737
+ hasUpdateCommands = true;
738
+ break;
739
+ case "--monitor":
740
+ hasMonitor = true;
741
+ break;
742
+ case "--log":
743
+ hasLog = true;
744
+ break;
745
+ case "-f":
746
+ case "--follow":
747
+ monitorFollow = true;
748
+ logFollow = true;
749
+ break;
750
+ case "-j":
751
+ case "--json":
752
+ monitorJson = true;
753
+ break;
754
+ case "-q":
755
+ case "--quiet":
756
+ monitorQuiet = true;
757
+ break;
758
+ case "-s":
759
+ case "--summary":
760
+ monitorSummary = true;
761
+ break;
762
+ case "--force":
763
+ initForce = true;
764
+ break;
765
+ case "--dry-run":
766
+ updateDryRun = true;
767
+ break;
768
+ case "-p":
769
+ case "--project":
770
+ const projectArg = args[i + 1];
771
+ if (projectArg && !projectArg.startsWith("-")) {
772
+ monitorProject = projectArg;
773
+ i++;
774
+ }
775
+ break;
776
+ case "--last":
777
+ const lastArg = args[i + 1];
778
+ if (lastArg && !lastArg.startsWith("-")) {
779
+ logLast = parseInt(lastArg, 10) || 20;
780
+ i++;
781
+ }
782
+ break;
783
+ default:
784
+ if (arg && arg.startsWith("-")) {
785
+ console.error(`Warning: Unknown option '${arg}'`);
786
+ }
787
+ break;
788
+ }
789
+ }
790
+ if (hasLog) {
791
+ return { action: "log", logOptions: { last: logLast, follow: logFollow } };
792
+ }
793
+ if (hasMonitor) {
794
+ return {
795
+ action: "monitor",
796
+ monitorOptions: {
797
+ follow: monitorFollow,
798
+ project: monitorProject,
799
+ json: monitorJson,
800
+ quiet: monitorQuiet,
801
+ summary: monitorSummary,
802
+ },
803
+ };
804
+ }
805
+ if (hasInit) {
806
+ return { action: "init", initOptions: { force: initForce } };
807
+ }
808
+ if (hasCheckCommands) {
809
+ return { action: "check-commands" };
810
+ }
811
+ if (hasUpdateCommands) {
812
+ return { action: "update-commands", updateOptions: { force: initForce, dryRun: updateDryRun } };
813
+ }
814
+ return { action: "server" };
815
+ }
816
+ //# sourceMappingURL=cli.js.map