cipher-security 5.0.0 → 5.1.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
@@ -65,6 +65,7 @@ Commands:
65
65
  memory-import Import memory
66
66
  sarif SARIF report tools
67
67
  osint OSINT intelligence tools
68
+ update Update CIPHER to latest version
68
69
  bot Manage bot integrations
69
70
  mcp MCP server tools
70
71
  api API management
@@ -179,7 +180,7 @@ const knownCommands = new Set([
179
180
  // Pipeline
180
181
  'scan', 'search', 'store', 'diff', 'workflow', 'stats', 'domains',
181
182
  'skills', 'score', 'marketplace', 'compliance', 'leaderboard',
182
- 'feedback', 'memory-export', 'memory-import', 'sarif', 'osint',
183
+ 'feedback', 'memory-export', 'memory-import', 'sarif', 'osint', 'update',
183
184
  // Services
184
185
  'bot', 'mcp', 'api',
185
186
  ]);
@@ -242,7 +243,7 @@ function parseCommand(argv) {
242
243
  *
243
244
  * @param {*} result - Bridge command result
244
245
  */
245
- function formatBridgeResult(result) {
246
+ async function formatBridgeResult(result, commandName = '') {
246
247
  if (result === null || result === undefined) {
247
248
  return;
248
249
  }
@@ -255,12 +256,37 @@ function formatBridgeResult(result) {
255
256
  if (typeof result === 'object') {
256
257
  // Error result — print to stderr and exit with error code
257
258
  if (result.error === true) {
258
- if (result.output) {
259
+ if (result.message) {
260
+ process.stderr.write(result.message + '\n');
261
+ } else if (result.output) {
259
262
  process.stderr.write(result.output + '\n');
260
263
  }
261
264
  process.exit(result.exit_code || 1);
262
265
  }
263
266
 
267
+ // Branded output for specific commands
268
+ if (commandName === 'version' && result.version) {
269
+ const { formatVersion } = await import('../lib/brand.js');
270
+ process.stderr.write(formatVersion(result.version) + '\n');
271
+ console.log(JSON.stringify(result, null, 2));
272
+ return;
273
+ }
274
+
275
+ if (commandName === 'doctor' && result.checks) {
276
+ const { header, formatDoctorChecks, divider, success, warn } = await import('../lib/brand.js');
277
+ header('Health Check', result.checks.find(c => c.name === 'Node.js')?.detail || '');
278
+ divider();
279
+ process.stderr.write(formatDoctorChecks(result.checks) + '\n');
280
+ divider();
281
+ if (result.healthy) {
282
+ success(`All checks passed (${result.summary})`);
283
+ } else {
284
+ warn(result.summary);
285
+ }
286
+ console.log(JSON.stringify(result, null, 2));
287
+ return;
288
+ }
289
+
264
290
  // Text-mode result — print output directly (pre-formatted from CliRunner)
265
291
  if ('output' in result) {
266
292
  if (result.output) {
@@ -417,7 +443,7 @@ if (mode === 'native') {
417
443
  // --no-stream: use handleQuery for non-streaming response
418
444
  const { handleQuery } = await import('../lib/gateway/commands.js');
419
445
  const result = await handleQuery({ message: queryText });
420
- formatBridgeResult(result);
446
+ await formatBridgeResult(result, command);
421
447
  } else {
422
448
  // Streaming path — Gateway.sendStream() → async iterator → stdout
423
449
  const { Gateway } = await import('../lib/gateway/gateway.js');
@@ -451,7 +477,7 @@ if (mode === 'native') {
451
477
  process.stderr.write(result.message + '\n');
452
478
  process.exit(result.exit_code || 1);
453
479
  } else {
454
- formatBridgeResult(result);
480
+ await formatBridgeResult(result, command);
455
481
  }
456
482
  }
457
483
  } 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.
@@ -828,3 +828,51 @@ export async function handleSetupSignal() {
828
828
  }, null, 2),
829
829
  };
830
830
  }
831
+
832
+ // ---------------------------------------------------------------------------
833
+ // Update command — self-update from npm
834
+ // ---------------------------------------------------------------------------
835
+
836
+ export async function handleUpdate(args = {}) {
837
+ const { execSync } = await import('node:child_process');
838
+ const { readFileSync } = await import('node:fs');
839
+ const { join, dirname } = await import('node:path');
840
+ const brand = await import('../brand.js');
841
+
842
+ const pkgPath = join(dirname(new URL(import.meta.url).pathname), '..', '..', 'package.json');
843
+ let currentVersion = 'unknown';
844
+ try {
845
+ currentVersion = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
846
+ } catch { /* ignore */ }
847
+
848
+ brand.header('Update', currentVersion);
849
+ brand.info(`Current version: ${currentVersion}`);
850
+ brand.info('Checking npm for updates...');
851
+
852
+ try {
853
+ const latest = execSync('npm view cipher-security version', {
854
+ encoding: 'utf-8',
855
+ timeout: 10000,
856
+ }).trim();
857
+
858
+ if (latest === currentVersion) {
859
+ brand.success(`Already up to date (${currentVersion})`);
860
+ return { output: JSON.stringify({ current: currentVersion, latest, upToDate: true }, null, 2) };
861
+ }
862
+
863
+ brand.warn(`New version available: ${latest}`);
864
+ brand.info('Installing update...');
865
+
866
+ execSync('npm install -g cipher-security@latest', {
867
+ encoding: 'utf-8',
868
+ timeout: 60000,
869
+ stdio: 'inherit',
870
+ });
871
+
872
+ brand.success(`Updated ${currentVersion} → ${latest}`);
873
+ return { output: JSON.stringify({ previous: currentVersion, updated: latest, success: true }, null, 2) };
874
+ } catch (err) {
875
+ brand.error(`Update failed: ${err.message}`);
876
+ return { error: true, message: `Update failed: ${err.message}` };
877
+ }
878
+ }
@@ -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.1.0",
4
4
  "description": "CIPHER — AI Security Engineering Platform CLI",
5
5
  "type": "module",
6
6
  "engines": {