cipher-security 5.0.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.
- package/bin/cipher.js +465 -0
- package/lib/api/billing.js +321 -0
- package/lib/api/compliance.js +693 -0
- package/lib/api/controls.js +1401 -0
- package/lib/api/index.js +49 -0
- package/lib/api/marketplace.js +467 -0
- package/lib/api/openai-proxy.js +383 -0
- package/lib/api/server.js +685 -0
- package/lib/autonomous/feedback-loop.js +554 -0
- package/lib/autonomous/framework.js +512 -0
- package/lib/autonomous/index.js +97 -0
- package/lib/autonomous/leaderboard.js +594 -0
- package/lib/autonomous/modes/architect.js +412 -0
- package/lib/autonomous/modes/blue.js +386 -0
- package/lib/autonomous/modes/incident.js +684 -0
- package/lib/autonomous/modes/privacy.js +369 -0
- package/lib/autonomous/modes/purple.js +294 -0
- package/lib/autonomous/modes/recon.js +250 -0
- package/lib/autonomous/parallel.js +587 -0
- package/lib/autonomous/researcher.js +583 -0
- package/lib/autonomous/runner.js +955 -0
- package/lib/autonomous/scheduler.js +615 -0
- package/lib/autonomous/task-parser.js +127 -0
- package/lib/autonomous/validators/forensic.js +266 -0
- package/lib/autonomous/validators/osint.js +216 -0
- package/lib/autonomous/validators/privacy.js +296 -0
- package/lib/autonomous/validators/purple.js +298 -0
- package/lib/autonomous/validators/sigma.js +248 -0
- package/lib/autonomous/validators/threat-model.js +363 -0
- package/lib/benchmark/agent.js +119 -0
- package/lib/benchmark/baselines.js +43 -0
- package/lib/benchmark/builder.js +143 -0
- package/lib/benchmark/config.js +35 -0
- package/lib/benchmark/coordinator.js +91 -0
- package/lib/benchmark/index.js +20 -0
- package/lib/benchmark/llm.js +58 -0
- package/lib/benchmark/models.js +137 -0
- package/lib/benchmark/reporter.js +103 -0
- package/lib/benchmark/runner.js +103 -0
- package/lib/benchmark/sandbox.js +96 -0
- package/lib/benchmark/scorer.js +32 -0
- package/lib/benchmark/solver.js +166 -0
- package/lib/benchmark/tools.js +62 -0
- package/lib/bot/bot.js +130 -0
- package/lib/commands.js +99 -0
- package/lib/complexity.js +377 -0
- package/lib/config.js +213 -0
- package/lib/gateway/client.js +309 -0
- package/lib/gateway/commands.js +830 -0
- package/lib/gateway/config-validate.js +109 -0
- package/lib/gateway/gateway.js +367 -0
- package/lib/gateway/index.js +62 -0
- package/lib/gateway/mode.js +309 -0
- package/lib/gateway/plugins.js +222 -0
- package/lib/gateway/prompt.js +214 -0
- package/lib/mcp/server.js +262 -0
- package/lib/memory/compressor.js +425 -0
- package/lib/memory/engine.js +763 -0
- package/lib/memory/evolution.js +668 -0
- package/lib/memory/index.js +58 -0
- package/lib/memory/orchestrator.js +506 -0
- package/lib/memory/retriever.js +515 -0
- package/lib/memory/synthesizer.js +333 -0
- package/lib/pipeline/async-scanner.js +510 -0
- package/lib/pipeline/binary-analysis.js +1043 -0
- package/lib/pipeline/dom-xss-scanner.js +435 -0
- package/lib/pipeline/github-actions.js +792 -0
- package/lib/pipeline/index.js +124 -0
- package/lib/pipeline/osint.js +498 -0
- package/lib/pipeline/sarif.js +373 -0
- package/lib/pipeline/scanner.js +880 -0
- package/lib/pipeline/template-manager.js +525 -0
- package/lib/pipeline/xss-scanner.js +353 -0
- package/lib/setup-wizard.js +229 -0
- package/package.json +30 -0
package/bin/cipher.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright (c) 2026 defconxt. All rights reserved.
|
|
3
|
+
// Licensed under AGPL-3.0 — see LICENSE file for details.
|
|
4
|
+
// CIPHER is a trademark of defconxt.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* cipher — CLI entry point for the CIPHER security engineering platform.
|
|
8
|
+
*
|
|
9
|
+
* Fast path: --version / -V / --help / -h use only node:fs + node:path
|
|
10
|
+
* (zero dynamic imports, <200ms cold start).
|
|
11
|
+
*
|
|
12
|
+
* Command path: dynamically imports python-bridge.js, spawns the Python
|
|
13
|
+
* gateway, routes JSON-RPC commands, prints results, and exits.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { resolve, dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Fast path — no dynamic imports, must complete in <200ms
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
|
|
27
|
+
if (args[0] === '--version' || args[0] === '-V') {
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
30
|
+
console.log(pkg.version);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
35
|
+
console.log(`CIPHER — AI Security Engineering Platform
|
|
36
|
+
|
|
37
|
+
Usage: cipher [command] [options]
|
|
38
|
+
cipher <query> Freeform security query
|
|
39
|
+
|
|
40
|
+
Commands:
|
|
41
|
+
query Run a security query
|
|
42
|
+
ingest Ingest security data
|
|
43
|
+
status Show system status
|
|
44
|
+
doctor Diagnose installation health
|
|
45
|
+
setup Run setup wizard
|
|
46
|
+
setup-signal Configure Signal integration
|
|
47
|
+
dashboard Open the dashboard
|
|
48
|
+
web Start the web interface
|
|
49
|
+
version Print version information
|
|
50
|
+
plugin Manage plugins
|
|
51
|
+
scan Run a security scan
|
|
52
|
+
search Search security data
|
|
53
|
+
store Manage data stores
|
|
54
|
+
diff Compare security states
|
|
55
|
+
workflow Manage workflows
|
|
56
|
+
stats Show statistics
|
|
57
|
+
domains Manage domains
|
|
58
|
+
skills Manage skills
|
|
59
|
+
score Show security score
|
|
60
|
+
marketplace Browse marketplace
|
|
61
|
+
compliance Run compliance checks
|
|
62
|
+
leaderboard Show leaderboard
|
|
63
|
+
feedback Submit feedback
|
|
64
|
+
memory-export Export memory
|
|
65
|
+
memory-import Import memory
|
|
66
|
+
sarif SARIF report tools
|
|
67
|
+
osint OSINT intelligence tools
|
|
68
|
+
bot Manage bot integrations
|
|
69
|
+
mcp MCP server tools
|
|
70
|
+
api API management
|
|
71
|
+
|
|
72
|
+
Options:
|
|
73
|
+
--version, -V Print version and exit
|
|
74
|
+
--help, -h Print this help and exit
|
|
75
|
+
|
|
76
|
+
Environment:
|
|
77
|
+
CIPHER_DEBUG=1 Enable verbose debug logging
|
|
78
|
+
|
|
79
|
+
Documentation: https://github.com/defconxt/CIPHER`);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// No arguments: check if config exists → wizard or help
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
if (args.length === 0) {
|
|
88
|
+
try {
|
|
89
|
+
const { configExists } = await import('../lib/config.js');
|
|
90
|
+
if (!configExists()) {
|
|
91
|
+
const { runSetupWizard } = await import('../lib/setup-wizard.js');
|
|
92
|
+
await runSetupWizard();
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`Setup wizard failed: ${err.message || err}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
// Config exists but no command — show help hint
|
|
100
|
+
console.error('No command specified. Run `cipher --help` for usage.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Node.js-native commands — handled before the Python bridge is loaded
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
const nativeCommands = new Set(['setup']);
|
|
109
|
+
|
|
110
|
+
if (nativeCommands.has(args[0])) {
|
|
111
|
+
try {
|
|
112
|
+
const { runSetupWizard } = await import('../lib/setup-wizard.js');
|
|
113
|
+
await runSetupWizard();
|
|
114
|
+
process.exit(0);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error(`Setup wizard failed: ${err.message || err}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Extract query-specific flags before general command parsing
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
let queryBackendOverride = null;
|
|
126
|
+
let queryNoStream = false;
|
|
127
|
+
let autonomousMode = null;
|
|
128
|
+
|
|
129
|
+
/** Set of valid autonomous mode names (lowercased). */
|
|
130
|
+
const MODE_NAMES = new Set(['red', 'blue', 'incident', 'purple', 'recon', 'privacy', 'architect']);
|
|
131
|
+
|
|
132
|
+
/** Clean args with --backend, --no-stream, and --autonomous extracted */
|
|
133
|
+
const cleanedArgs = [];
|
|
134
|
+
for (let i = 0; i < args.length; i++) {
|
|
135
|
+
if (args[i] === '--backend' && i + 1 < args.length) {
|
|
136
|
+
queryBackendOverride = args[i + 1];
|
|
137
|
+
i++; // skip value
|
|
138
|
+
} else if (args[i] === '--no-stream') {
|
|
139
|
+
queryNoStream = true;
|
|
140
|
+
} else if (args[i] === '--autonomous') {
|
|
141
|
+
// --autonomous flag found — look for a mode name in the remaining args.
|
|
142
|
+
// The mode name can be adjacent (cipher --autonomous blue "task")
|
|
143
|
+
// or positional-first (cipher blue --autonomous "task").
|
|
144
|
+
// We'll collect it after the loop from cleanedArgs.
|
|
145
|
+
autonomousMode = '__pending__';
|
|
146
|
+
} else {
|
|
147
|
+
cleanedArgs.push(args[i]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If --autonomous was found, extract the mode name from cleaned positional args
|
|
152
|
+
if (autonomousMode === '__pending__') {
|
|
153
|
+
// Check the first positional arg (non-flag) for a mode name
|
|
154
|
+
const firstPositional = cleanedArgs.find(a => !a.startsWith('-'));
|
|
155
|
+
if (firstPositional && MODE_NAMES.has(firstPositional.toLowerCase())) {
|
|
156
|
+
autonomousMode = firstPositional.toLowerCase();
|
|
157
|
+
// Remove the mode name from cleanedArgs so parseCommand() doesn't see it
|
|
158
|
+
const idx = cleanedArgs.indexOf(firstPositional);
|
|
159
|
+
cleanedArgs.splice(idx, 1);
|
|
160
|
+
} else {
|
|
161
|
+
console.error(
|
|
162
|
+
`Error: --autonomous requires a valid mode name.\n` +
|
|
163
|
+
`Available modes: ${[...MODE_NAMES].sort().join(', ')}\n\n` +
|
|
164
|
+
`Usage: cipher <mode> --autonomous "task description"\n` +
|
|
165
|
+
`Example: cipher blue --autonomous "Generate Sigma rules for T1059.001"`
|
|
166
|
+
);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Command routing — dynamic imports below this point
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
const knownCommands = new Set([
|
|
176
|
+
// Gateway
|
|
177
|
+
'query', 'ingest', 'status', 'doctor', 'setup-signal',
|
|
178
|
+
'dashboard', 'web', 'version', 'plugin',
|
|
179
|
+
// Pipeline
|
|
180
|
+
'scan', 'search', 'store', 'diff', 'workflow', 'stats', 'domains',
|
|
181
|
+
'skills', 'score', 'marketplace', 'compliance', 'leaderboard',
|
|
182
|
+
'feedback', 'memory-export', 'memory-import', 'sarif', 'osint',
|
|
183
|
+
// Services
|
|
184
|
+
'bot', 'mcp', 'api',
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Parse argv into a command name and remaining args.
|
|
189
|
+
* First non-flag argument is checked against knownCommands.
|
|
190
|
+
* If not a known command, treat all non-flag words as a bare query.
|
|
191
|
+
*
|
|
192
|
+
* @param {string[]} argv
|
|
193
|
+
* @returns {{ command: string, commandArgs: string[] }}
|
|
194
|
+
*/
|
|
195
|
+
function parseCommand(argv) {
|
|
196
|
+
const flags = [];
|
|
197
|
+
const positional = [];
|
|
198
|
+
|
|
199
|
+
for (const arg of argv) {
|
|
200
|
+
if (arg.startsWith('-')) {
|
|
201
|
+
flags.push(arg);
|
|
202
|
+
} else {
|
|
203
|
+
positional.push(arg);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (positional.length === 0) {
|
|
208
|
+
// No positional args — show help
|
|
209
|
+
console.error('No command specified. Run `cipher --help` for usage.');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const first = positional[0];
|
|
214
|
+
|
|
215
|
+
if (knownCommands.has(first)) {
|
|
216
|
+
return {
|
|
217
|
+
command: first,
|
|
218
|
+
commandArgs: [...positional.slice(1), ...flags],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Bare query — join all positional words as query text
|
|
223
|
+
return {
|
|
224
|
+
command: 'query',
|
|
225
|
+
commandArgs: [...positional, ...flags],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Bridge result formatter
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Format and print a bridge command result, then exit if needed.
|
|
235
|
+
*
|
|
236
|
+
* Result shapes from bridge.py:
|
|
237
|
+
* 1. {output, exit_code, error: true} — command error: print output to stderr, exit with code
|
|
238
|
+
* 2. {output, exit_code} — text-mode result: print output directly
|
|
239
|
+
* 3. Plain object (no output field) — structured JSON: pretty-print as JSON
|
|
240
|
+
* 4. String — legacy: print directly
|
|
241
|
+
* 5. null/undefined — no output
|
|
242
|
+
*
|
|
243
|
+
* @param {*} result - Bridge command result
|
|
244
|
+
*/
|
|
245
|
+
function formatBridgeResult(result) {
|
|
246
|
+
if (result === null || result === undefined) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (typeof result === 'string') {
|
|
251
|
+
console.log(result);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (typeof result === 'object') {
|
|
256
|
+
// Error result — print to stderr and exit with error code
|
|
257
|
+
if (result.error === true) {
|
|
258
|
+
if (result.output) {
|
|
259
|
+
process.stderr.write(result.output + '\n');
|
|
260
|
+
}
|
|
261
|
+
process.exit(result.exit_code || 1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Text-mode result — print output directly (pre-formatted from CliRunner)
|
|
265
|
+
if ('output' in result) {
|
|
266
|
+
if (result.output) {
|
|
267
|
+
console.log(result.output);
|
|
268
|
+
}
|
|
269
|
+
if (result.exit_code && result.exit_code !== 0) {
|
|
270
|
+
process.exit(result.exit_code);
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Structured JSON result — pretty-print
|
|
276
|
+
console.log(JSON.stringify(result, null, 2));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Fallback for unexpected types
|
|
281
|
+
console.log(String(result));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Dispatch helpers + autonomous/standard routing
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
const { command, commandArgs } = parseCommand(cleanedArgs);
|
|
289
|
+
|
|
290
|
+
const debug = process.env.CIPHER_DEBUG === '1';
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Autonomous mode dispatch — bypasses normal command routing entirely
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
if (autonomousMode) {
|
|
298
|
+
// Build task text from remaining non-flag args
|
|
299
|
+
const taskText = commandArgs.filter(a => !a.startsWith('-')).join(' ')
|
|
300
|
+
|| (command === 'query' ? commandArgs.join(' ') : '');
|
|
301
|
+
|
|
302
|
+
if (debug) {
|
|
303
|
+
process.stderr.write(
|
|
304
|
+
`[bridge:node] Autonomous dispatch: mode=${autonomousMode} task="${taskText}"\n`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const { runAutonomous } = await import('../lib/autonomous/runner.js');
|
|
310
|
+
const { parseTaskInput } = await import('../lib/autonomous/task-parser.js');
|
|
311
|
+
|
|
312
|
+
const taskInput = parseTaskInput(autonomousMode.toUpperCase(), taskText);
|
|
313
|
+
|
|
314
|
+
const result = await runAutonomous(
|
|
315
|
+
autonomousMode,
|
|
316
|
+
taskInput,
|
|
317
|
+
queryBackendOverride,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Format result
|
|
321
|
+
if (result.error) {
|
|
322
|
+
process.stderr.write(`Error: ${result.error}\n`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Print output
|
|
327
|
+
if (result.outputText) {
|
|
328
|
+
process.stdout.write(result.outputText + '\n');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Print validation warnings to stderr
|
|
332
|
+
if (result.validation && !result.validation.valid) {
|
|
333
|
+
process.stderr.write('Validation warnings:\n');
|
|
334
|
+
for (const err of (result.validation.errors || [])) {
|
|
335
|
+
process.stderr.write(` - ${err}\n`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
const msg = err.message || String(err);
|
|
340
|
+
process.stderr.write(`Error: ${msg}\n`);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
process.exit(0);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// Standard dispatch: passthrough vs native
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
const { getCommandMode } = await import('../lib/commands.js');
|
|
352
|
+
const mode = getCommandMode(command);
|
|
353
|
+
|
|
354
|
+
if (mode === 'native') {
|
|
355
|
+
// ── Native dispatch — Node.js handler functions (no Python) ────────────
|
|
356
|
+
if (debug) {
|
|
357
|
+
process.stderr.write(`[bridge:node] Dispatching command=${command} mode=native\n`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Convert kebab-case command name to handler function name.
|
|
362
|
+
* 'memory-export' → 'handleMemoryExport'
|
|
363
|
+
* 'stats' → 'handleStats'
|
|
364
|
+
*
|
|
365
|
+
* @param {string} cmd
|
|
366
|
+
* @returns {string}
|
|
367
|
+
*/
|
|
368
|
+
function toHandlerName(cmd) {
|
|
369
|
+
return 'handle' + cmd
|
|
370
|
+
.split('-')
|
|
371
|
+
.map(w => w[0].toUpperCase() + w.slice(1))
|
|
372
|
+
.join('');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
// ── API server command: long-running HTTP server (no Python) ──────────
|
|
377
|
+
if (command === 'api') {
|
|
378
|
+
const { createAPIServer, APIConfig } = await import('../lib/api/server.js');
|
|
379
|
+
const apiPort = commandArgs.find((a, i) => commandArgs[i - 1] === '--port') || '8443';
|
|
380
|
+
const apiHost = commandArgs.find((a, i) => commandArgs[i - 1] === '--host') || '127.0.0.1';
|
|
381
|
+
const noAuth = commandArgs.includes('--no-auth');
|
|
382
|
+
const cfg = new APIConfig({
|
|
383
|
+
port: parseInt(apiPort, 10),
|
|
384
|
+
host: apiHost,
|
|
385
|
+
requireAuth: !noAuth,
|
|
386
|
+
});
|
|
387
|
+
const api = createAPIServer(cfg);
|
|
388
|
+
const actualPort = await api.start();
|
|
389
|
+
console.log(`CIPHER API listening on ${cfg.host}:${actualPort}`);
|
|
390
|
+
// Keep process alive — Ctrl+C to stop
|
|
391
|
+
process.on('SIGINT', async () => {
|
|
392
|
+
await api.stop();
|
|
393
|
+
console.log('\nShutdown complete.');
|
|
394
|
+
process.exit(0);
|
|
395
|
+
});
|
|
396
|
+
process.on('SIGTERM', async () => {
|
|
397
|
+
await api.stop();
|
|
398
|
+
process.exit(0);
|
|
399
|
+
});
|
|
400
|
+
// ── MCP server command: JSON-RPC over stdio ──────────────────────────
|
|
401
|
+
} else if (command === 'mcp') {
|
|
402
|
+
const { CipherMCPServer } = await import('../lib/mcp/server.js');
|
|
403
|
+
const mcp = new CipherMCPServer();
|
|
404
|
+
mcp.startStdio();
|
|
405
|
+
// ── Bot command: Signal bot ──────────────────────────────────────────
|
|
406
|
+
} else if (command === 'bot') {
|
|
407
|
+
const { runBot } = await import('../lib/bot/bot.js');
|
|
408
|
+
runBot({
|
|
409
|
+
signalService: commandArgs.find((a, i) => commandArgs[i - 1] === '--service') || '',
|
|
410
|
+
phoneNumber: commandArgs.find((a, i) => commandArgs[i - 1] === '--phone') || '',
|
|
411
|
+
});
|
|
412
|
+
// ── Query command: streaming via Gateway or non-streaming via handler ──
|
|
413
|
+
} else if (command === 'query') {
|
|
414
|
+
const queryText = commandArgs.filter(a => !a.startsWith('-')).join(' ');
|
|
415
|
+
|
|
416
|
+
if (queryNoStream) {
|
|
417
|
+
// --no-stream: use handleQuery for non-streaming response
|
|
418
|
+
const { handleQuery } = await import('../lib/gateway/commands.js');
|
|
419
|
+
const result = await handleQuery({ message: queryText });
|
|
420
|
+
formatBridgeResult(result);
|
|
421
|
+
} else {
|
|
422
|
+
// Streaming path — Gateway.sendStream() → async iterator → stdout
|
|
423
|
+
const { Gateway } = await import('../lib/gateway/gateway.js');
|
|
424
|
+
const gw = new Gateway({
|
|
425
|
+
backendOverride: queryBackendOverride || undefined,
|
|
426
|
+
rag: true,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
for await (const token of gw.sendStream(queryText)) {
|
|
430
|
+
process.stdout.write(token);
|
|
431
|
+
}
|
|
432
|
+
process.stdout.write('\n');
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
// ── Non-query native commands ──────────────────────────────────────
|
|
436
|
+
const handlers = await import('../lib/gateway/commands.js');
|
|
437
|
+
const handlerName = toHandlerName(command);
|
|
438
|
+
const handler = handlers[handlerName];
|
|
439
|
+
|
|
440
|
+
if (!handler) {
|
|
441
|
+
console.error(`Unknown native command: ${command}`);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const result = await handler(commandArgs);
|
|
446
|
+
|
|
447
|
+
// Native handlers return {error: true, message: "..."} for errors.
|
|
448
|
+
// formatBridgeResult expects {error: true, output: "...", exit_code: N}.
|
|
449
|
+
// Bridge the shape difference here.
|
|
450
|
+
if (result && result.error === true && result.message && !('output' in result)) {
|
|
451
|
+
process.stderr.write(result.message + '\n');
|
|
452
|
+
process.exit(result.exit_code || 1);
|
|
453
|
+
} else {
|
|
454
|
+
formatBridgeResult(result);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
} catch (err) {
|
|
458
|
+
console.error(`Error: ${err.message || err}`);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
// Unknown command mode — should not happen
|
|
463
|
+
console.error(`Unknown command: ${command}. Run \`cipher --help\` for usage.`);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|