cipher-security 5.0.0 → 5.2.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 CHANGED
@@ -23,60 +23,81 @@ import { spawn } from 'node:child_process';
23
23
  // ---------------------------------------------------------------------------
24
24
 
25
25
  const args = process.argv.slice(2);
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
26
28
 
27
29
  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
30
  console.log(pkg.version);
31
31
  process.exit(0);
32
32
  }
33
33
 
34
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:
35
+ const CYN = '\x1b[36m';
36
+ const B = '\x1b[1m';
37
+ const D = '\x1b[2m';
38
+ const R = '\x1b[0m';
39
+ const GRN = '\x1b[32m';
40
+
41
+ console.log(`${CYN}${B}
42
+ ██████╗██╗██████╗ ██╗ ██╗███████╗██████╗
43
+ ██╔════╝██║██╔══██╗██║ ██║██╔════╝██╔══██╗
44
+ ██║ ██║██████╔╝███████║█████╗ ██████╔╝
45
+ ██║ ██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗
46
+ ╚██████╗██║██║ ██║ ██║███████╗██║ ██║
47
+ ╚═════╝╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝${R}
48
+ ${D}Claude Integrated Privacy & Hardening Expert Resource${R} ${D}v${pkg.version}${R}
49
+
50
+ ${B}Usage:${R} cipher [command] [options]
51
+ cipher <query> ${D}Freeform security query${R}
52
+
53
+ ${B}Commands:${R}
54
+ ${GRN}query${R} Run a security query
55
+ ${GRN}scan${R} Run a security scan
56
+ ${GRN}compliance${R} Run compliance checks (39 frameworks)
57
+ ${GRN}search${R} Search security data
58
+ ${GRN}osint${R} OSINT intelligence tools
59
+ ${GRN}diff${R} Analyze git diff for security issues
60
+ ${GRN}sarif${R} SARIF report tools
61
+
62
+ ${GRN}status${R} Show system status
63
+ ${GRN}doctor${R} Diagnose installation health
64
+ ${GRN}version${R} Print version information
65
+ ${GRN}setup${R} Run setup wizard
66
+ ${GRN}update${R} Update CIPHER to latest version
67
+
68
+ ${GRN}domains${R} List skill domains
69
+ ${GRN}skills${R} Search skills
70
+ ${GRN}store${R} Store findings in memory
71
+ ${GRN}stats${R} Show statistics
72
+ ${GRN}leaderboard${R} Skill effectiveness metrics
73
+ ${GRN}marketplace${R} Browse skill marketplace
74
+
75
+ ${GRN}api${R} Start REST API server
76
+ ${GRN}mcp${R} Start MCP server (stdio)
77
+ ${GRN}bot${R} Start Signal bot
78
+
79
+ ${GRN}workflow${R} Generate CI/CD security workflow
80
+ ${GRN}memory-export${R} Export memory to JSON
81
+ ${GRN}memory-import${R} Import memory from JSON
82
+ ${GRN}feedback${R} Run skill improvement loop
83
+ ${GRN}ingest${R} Re-index knowledge base
84
+ ${GRN}plugin${R} Manage plugins
85
+ ${GRN}setup-signal${R} Configure Signal integration
86
+ ${GRN}score${R} Score response quality
87
+ ${GRN}dashboard${R} System dashboard
88
+ ${GRN}web${R} Web interface (use cipher api)
89
+
90
+ ${B}Options:${R}
73
91
  --version, -V Print version and exit
74
92
  --help, -h Print this help and exit
93
+ --autonomous Run in autonomous mode (cipher blue --autonomous "task")
94
+ --backend Override LLM backend (ollama, claude, litellm)
95
+ --no-stream Disable streaming output
75
96
 
76
- Environment:
97
+ ${B}Environment:${R}
77
98
  CIPHER_DEBUG=1 Enable verbose debug logging
78
99
 
79
- Documentation: https://github.com/defconxt/CIPHER`);
100
+ ${D}Documentation: https://github.com/defconxt/CIPHER${R}`);
80
101
  process.exit(0);
81
102
  }
82
103
 
@@ -179,7 +200,7 @@ const knownCommands = new Set([
179
200
  // Pipeline
180
201
  'scan', 'search', 'store', 'diff', 'workflow', 'stats', 'domains',
181
202
  'skills', 'score', 'marketplace', 'compliance', 'leaderboard',
182
- 'feedback', 'memory-export', 'memory-import', 'sarif', 'osint',
203
+ 'feedback', 'memory-export', 'memory-import', 'sarif', 'osint', 'update',
183
204
  // Services
184
205
  'bot', 'mcp', 'api',
185
206
  ]);
@@ -242,7 +263,7 @@ function parseCommand(argv) {
242
263
  *
243
264
  * @param {*} result - Bridge command result
244
265
  */
245
- function formatBridgeResult(result) {
266
+ async function formatBridgeResult(result, commandName = '') {
246
267
  if (result === null || result === undefined) {
247
268
  return;
248
269
  }
@@ -255,12 +276,51 @@ function formatBridgeResult(result) {
255
276
  if (typeof result === 'object') {
256
277
  // Error result — print to stderr and exit with error code
257
278
  if (result.error === true) {
258
- if (result.output) {
279
+ if (result.message) {
280
+ process.stderr.write(result.message + '\n');
281
+ } else if (result.output) {
259
282
  process.stderr.write(result.output + '\n');
260
283
  }
261
284
  process.exit(result.exit_code || 1);
262
285
  }
263
286
 
287
+ // Branded output for specific commands
288
+ if (commandName === 'version' && result.version) {
289
+ const { formatVersion } = await import('../lib/brand.js');
290
+ process.stderr.write(formatVersion(result.version) + '\n');
291
+ console.log(JSON.stringify(result, null, 2));
292
+ return;
293
+ }
294
+
295
+ if (commandName === 'doctor' && result.checks) {
296
+ const { header, formatDoctorChecks, divider, success, warn } = await import('../lib/brand.js');
297
+ header('Health Check');
298
+ divider();
299
+ process.stderr.write(formatDoctorChecks(result.checks) + '\n');
300
+ divider();
301
+ if (result.healthy) {
302
+ success(`All checks passed (${result.summary})`);
303
+ } else {
304
+ warn(result.summary);
305
+ }
306
+ console.log(JSON.stringify(result, null, 2));
307
+ return;
308
+ }
309
+
310
+ if (commandName === 'status' && result.status) {
311
+ const { header, status: statusDot, divider } = await import('../lib/brand.js');
312
+ header('Status');
313
+ divider();
314
+ statusDot('Backend', result.backend ? 'ok' : 'warn', result.backend || 'not configured');
315
+ statusDot('Config', result.config?.valid ? 'ok' : 'fail', result.config?.valid ? 'valid' : 'invalid or missing');
316
+ if (result.memory) {
317
+ statusDot('Memory', 'ok', `${result.memory.total} entries (${result.memory.active} active)`);
318
+ }
319
+ divider();
320
+ console.log(JSON.stringify(result, null, 2));
321
+ return;
322
+ }
323
+
264
324
  // Text-mode result — print output directly (pre-formatted from CliRunner)
265
325
  if ('output' in result) {
266
326
  if (result.output) {
@@ -272,8 +332,10 @@ function formatBridgeResult(result) {
272
332
  return;
273
333
  }
274
334
 
275
- // Structured JSON result — pretty-print
276
- console.log(JSON.stringify(result, null, 2));
335
+ // Structured JSON result — pretty-print (skip empty objects from branded commands)
336
+ if (Object.keys(result).length > 0) {
337
+ console.log(JSON.stringify(result, null, 2));
338
+ }
277
339
  return;
278
340
  }
279
341
 
@@ -404,11 +466,28 @@ if (mode === 'native') {
404
466
  mcp.startStdio();
405
467
  // ── Bot command: Signal bot ──────────────────────────────────────────
406
468
  } else if (command === 'bot') {
469
+ const { banner, header, info, warn, divider } = await import('../lib/brand.js');
470
+ const svc = commandArgs.find((a, i) => commandArgs[i - 1] === '--service') || process.env.SIGNAL_SERVICE || '';
471
+ const phone = commandArgs.find((a, i) => commandArgs[i - 1] === '--phone') || process.env.SIGNAL_PHONE_NUMBER || '';
472
+
473
+ banner(pkg.version);
474
+ header('Signal Bot');
475
+
476
+ if (!svc || !phone) {
477
+ divider();
478
+ warn('Signal is not configured.');
479
+ info('Run: cipher setup-signal');
480
+ divider();
481
+ process.exit(1);
482
+ }
483
+
484
+ info(`Service: ${svc}`);
485
+ info(`Phone: ${phone}`);
486
+ divider();
487
+ info('Starting bot...');
488
+
407
489
  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
- });
490
+ runBot({ signalService: svc, phoneNumber: phone });
412
491
  // ── Query command: streaming via Gateway or non-streaming via handler ──
413
492
  } else if (command === 'query') {
414
493
  const queryText = commandArgs.filter(a => !a.startsWith('-')).join(' ');
@@ -417,7 +496,7 @@ if (mode === 'native') {
417
496
  // --no-stream: use handleQuery for non-streaming response
418
497
  const { handleQuery } = await import('../lib/gateway/commands.js');
419
498
  const result = await handleQuery({ message: queryText });
420
- formatBridgeResult(result);
499
+ await formatBridgeResult(result, command);
421
500
  } else {
422
501
  // Streaming path — Gateway.sendStream() → async iterator → stdout
423
502
  const { Gateway } = await import('../lib/gateway/gateway.js');
@@ -451,7 +530,7 @@ if (mode === 'native') {
451
530
  process.stderr.write(result.message + '\n');
452
531
  process.exit(result.exit_code || 1);
453
532
  } else {
454
- formatBridgeResult(result);
533
+ await formatBridgeResult(result, command);
455
534
  }
456
535
  }
457
536
  } catch (err) {
package/lib/brand.js ADDED
@@ -0,0 +1,105 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+
4
+ /**
5
+ * CIPHER branding — ASCII art, status indicators, formatted output.
6
+ */
7
+
8
+ // ANSI colors
9
+ const R = '\x1b[0m'; // reset
10
+ const B = '\x1b[1m'; // bold
11
+ const D = '\x1b[2m'; // dim
12
+ const RED = '\x1b[31m';
13
+ const GRN = '\x1b[32m';
14
+ const YLW = '\x1b[33m';
15
+ const BLU = '\x1b[34m';
16
+ const MAG = '\x1b[35m';
17
+ const CYN = '\x1b[36m';
18
+
19
+ const LOGO = `${CYN}${B}
20
+ ██████╗██╗██████╗ ██╗ ██╗███████╗██████╗
21
+ ██╔════╝██║██╔══██╗██║ ██║██╔════╝██╔══██╗
22
+ ██║ ██║██████╔╝███████║█████╗ ██████╔╝
23
+ ██║ ██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗
24
+ ╚██████╗██║██║ ██║ ██║███████╗██║ ██║
25
+ ╚═════╝╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝${R}`;
26
+
27
+ const LOGO_SMALL = `${CYN}${B}▸ CIPHER${R}`;
28
+
29
+ /** Status indicator dots */
30
+ export const dot = {
31
+ ok: `${GRN}●${R}`,
32
+ warn: `${YLW}●${R}`,
33
+ fail: `${RED}●${R}`,
34
+ info: `${BLU}●${R}`,
35
+ skip: `${D}○${R}`,
36
+ };
37
+
38
+ /** Print the full ASCII banner with version */
39
+ export function banner(version = '') {
40
+ const ver = version ? `${D}v${version}${R}` : '';
41
+ process.stderr.write(`${LOGO}\n ${D}Claude Integrated Privacy & Hardening Expert Resource${R} ${ver}\n\n`);
42
+ }
43
+
44
+ /** Print a compact one-line header */
45
+ export function header(text, version = '') {
46
+ const ver = version ? ` ${D}v${version}${R}` : '';
47
+ process.stderr.write(`${LOGO_SMALL}${ver} ${D}—${R} ${text}\n`);
48
+ }
49
+
50
+ /** Print a status line with colored dot */
51
+ export function status(name, state, detail = '') {
52
+ const d = dot[state] || dot.info;
53
+ const detailStr = detail ? ` ${D}${detail}${R}` : '';
54
+ process.stderr.write(` ${d} ${name}${detailStr}\n`);
55
+ }
56
+
57
+ /** Print a section divider */
58
+ export function divider() {
59
+ process.stderr.write(`${D}${'─'.repeat(50)}${R}\n`);
60
+ }
61
+
62
+ /** Print a success message */
63
+ export function success(msg) {
64
+ process.stderr.write(` ${GRN}✔${R} ${msg}\n`);
65
+ }
66
+
67
+ /** Print a warning message */
68
+ export function warn(msg) {
69
+ process.stderr.write(` ${YLW}⚠${R} ${msg}\n`);
70
+ }
71
+
72
+ /** Print an error message */
73
+ export function error(msg) {
74
+ process.stderr.write(` ${RED}✖${R} ${msg}\n`);
75
+ }
76
+
77
+ /** Print an info message */
78
+ export function info(msg) {
79
+ process.stderr.write(` ${BLU}ℹ${R} ${msg}\n`);
80
+ }
81
+
82
+ /**
83
+ * Format doctor checks with status dots.
84
+ * @param {object[]} checks - Array of {name, status, detail}
85
+ * @returns {string} Formatted output for terminal
86
+ */
87
+ export function formatDoctorChecks(checks) {
88
+ const lines = [];
89
+ for (const c of checks) {
90
+ const state = c.status === 'ok' ? 'ok' : c.status === 'optional' ? 'skip' : c.status === 'missing' ? 'fail' : 'warn';
91
+ const d = dot[state];
92
+ lines.push(` ${d} ${B}${c.name}${R} ${D}${c.detail}${R}`);
93
+ }
94
+ return lines.join('\n');
95
+ }
96
+
97
+ /**
98
+ * Format version output.
99
+ * @param {string} version
100
+ */
101
+ export function formatVersion(version) {
102
+ return `${LOGO_SMALL} ${B}${version}${R}`;
103
+ }
104
+
105
+ export { LOGO, LOGO_SMALL, R, B, D, RED, GRN, YLW, BLU, MAG, CYN };
package/lib/commands.js CHANGED
@@ -44,6 +44,7 @@ export const COMMAND_MODES = {
44
44
  'memory-import': { mode: 'native', description: 'Import memory' },
45
45
  sarif: { mode: 'native', description: 'SARIF report tools' },
46
46
  osint: { mode: 'native', description: 'OSINT intelligence tools' },
47
+ update: { mode: 'native', description: 'Update CIPHER to latest version' },
47
48
 
48
49
  // ── Passthrough commands (direct Python spawn, full terminal) ──────────
49
50
  // These were Python-dependent — now ported to Node.js.
@@ -821,10 +821,64 @@ export async function handleWeb(args = {}) {
821
821
  // ---------------------------------------------------------------------------
822
822
 
823
823
  export async function handleSetupSignal() {
824
- return {
825
- output: JSON.stringify({
826
- status: 'info',
827
- message: 'Signal setup: Set SIGNAL_SERVICE, SIGNAL_PHONE_NUMBER, and SIGNAL_WHITELIST environment variables, then run `cipher bot`.',
828
- }, null, 2),
829
- };
824
+ const brand = await import('../brand.js');
825
+ brand.header('Signal Setup');
826
+ brand.divider();
827
+ brand.info('Set the following environment variables:');
828
+ process.stderr.write('\n');
829
+ process.stderr.write(' SIGNAL_SERVICE Signal API service URL\n');
830
+ process.stderr.write(' SIGNAL_PHONE_NUMBER Bot phone number (E.164 format)\n');
831
+ process.stderr.write(' SIGNAL_WHITELIST Comma-separated allowed numbers\n');
832
+ process.stderr.write('\n');
833
+ brand.info('Then run: cipher bot');
834
+ brand.divider();
835
+ return {};
836
+ }
837
+
838
+ // ---------------------------------------------------------------------------
839
+ // Update command — self-update from npm
840
+ // ---------------------------------------------------------------------------
841
+
842
+ export async function handleUpdate(args = {}) {
843
+ const { execSync } = await import('node:child_process');
844
+ const { readFileSync } = await import('node:fs');
845
+ const { join, dirname } = await import('node:path');
846
+ const brand = await import('../brand.js');
847
+
848
+ const pkgPath = join(dirname(new URL(import.meta.url).pathname), '..', '..', 'package.json');
849
+ let currentVersion = 'unknown';
850
+ try {
851
+ currentVersion = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
852
+ } catch { /* ignore */ }
853
+
854
+ brand.header('Update', currentVersion);
855
+ brand.info(`Current version: ${currentVersion}`);
856
+ brand.info('Checking npm for updates...');
857
+
858
+ try {
859
+ const latest = execSync('npm view cipher-security version', {
860
+ encoding: 'utf-8',
861
+ timeout: 10000,
862
+ }).trim();
863
+
864
+ if (latest === currentVersion) {
865
+ brand.success(`Already up to date (${currentVersion})`);
866
+ return {};
867
+ }
868
+
869
+ brand.warn(`New version available: ${latest}`);
870
+ brand.info('Installing update...');
871
+
872
+ execSync('npm install -g cipher-security@latest', {
873
+ encoding: 'utf-8',
874
+ timeout: 60000,
875
+ stdio: 'inherit',
876
+ });
877
+
878
+ brand.success(`Updated ${currentVersion} → ${latest}`);
879
+ return {};
880
+ } catch (err) {
881
+ brand.error(`Update failed: ${err.message}`);
882
+ return { error: true, message: `Update failed: ${err.message}` };
883
+ }
830
884
  }
@@ -19,7 +19,7 @@
19
19
  */
20
20
 
21
21
  import * as clack from '@clack/prompts';
22
- import { execFileSync } from 'node:child_process';
22
+ import { execFileSync, execSync } from 'node:child_process';
23
23
  import { existsSync } from 'node:fs';
24
24
  import { join } from 'node:path';
25
25
  import { writeConfig, getConfigPaths } from './config.js';
@@ -96,8 +96,9 @@ export async function runSetupWizard() {
96
96
  const provider = await clack.select({
97
97
  message: 'Choose your LLM backend',
98
98
  options: [
99
+ { value: 'claude-code', label: 'Claude Code', hint: 'Running inside Claude Code — no API key needed' },
99
100
  { value: 'ollama', label: 'Ollama', hint: 'Local inference, free, private (requires GPU)' },
100
- { value: 'claude', label: 'Claude API', hint: 'Cloud inference, paid, highest quality' },
101
+ { value: 'claude', label: 'Claude API', hint: 'Standalone use with Anthropic API key' },
101
102
  { value: 'litellm', label: 'LiteLLM', hint: 'Multi-provider (OpenAI, Gemini, llama.cpp, etc.)' },
102
103
  ],
103
104
  });
@@ -105,8 +106,66 @@ export async function runSetupWizard() {
105
106
 
106
107
  let config;
107
108
 
109
+ // ── Claude Code path ──────────────────────────────────────────────
110
+ if (provider === 'claude-code') {
111
+ // Check if claude CLI is installed and authed
112
+ let claudeAuthed = false;
113
+ try {
114
+ const authJson = execSync('claude auth status', { encoding: 'utf-8', timeout: 5000 });
115
+ const auth = JSON.parse(authJson);
116
+ if (auth.loggedIn) {
117
+ clack.log.success(`Authenticated as ${auth.email} (${auth.subscriptionType})`);
118
+ claudeAuthed = true;
119
+ }
120
+ } catch {
121
+ // claude CLI not found or not authed
122
+ }
123
+
124
+ if (!claudeAuthed) {
125
+ clack.log.warn('Claude Code is not authenticated.');
126
+ clack.log.info('Run this in your terminal to log in:\n\n claude login\n');
127
+
128
+ const proceed = await clack.confirm({
129
+ message: 'Continue setup anyway? (you can log in later)',
130
+ initialValue: false,
131
+ });
132
+ handleCancel(proceed);
133
+ if (!proceed) {
134
+ clack.outro('Run `claude login` first, then `cipher setup` again.');
135
+ process.exit(0);
136
+ }
137
+ }
138
+
139
+ clack.log.info(
140
+ 'CIPHER activates automatically via CLAUDE.md inside Claude Code.\n' +
141
+ 'No API key needed — Claude Code provides the LLM.'
142
+ );
143
+
144
+ // Offer Ollama as fallback for standalone use
145
+ const addOllama = await clack.confirm({
146
+ message: 'Also configure Ollama for standalone CLI use?',
147
+ initialValue: true,
148
+ });
149
+ handleCancel(addOllama);
150
+
151
+ if (addOllama) {
152
+ const ollamaPath = whichSync('ollama');
153
+ if (ollamaPath) {
154
+ clack.log.success(`Ollama found at ${ollamaPath}`);
155
+ } else {
156
+ clack.log.info('Ollama not found — install from https://ollama.com/download when ready.');
157
+ }
158
+ }
159
+
160
+ config = {
161
+ llm_backend: addOllama ? 'ollama' : 'claude-code',
162
+ ollama: defaultOllamaSection(),
163
+ claude: defaultClaudeSection(),
164
+ };
165
+ }
166
+
108
167
  // ── Ollama path ────────────────────────────────────────────────────
109
- if (provider === 'ollama') {
168
+ else if (provider === 'ollama') {
110
169
  const ollamaPath = whichSync('ollama');
111
170
  if (ollamaPath) {
112
171
  clack.log.success(`Ollama found at ${ollamaPath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cipher-security",
3
- "version": "5.0.0",
3
+ "version": "5.2.0",
4
4
  "description": "CIPHER — AI Security Engineering Platform CLI",
5
5
  "type": "module",
6
6
  "engines": {