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 +31 -5
- package/lib/brand.js +105 -0
- package/lib/commands.js +1 -0
- package/lib/gateway/commands.js +48 -0
- package/lib/setup-wizard.js +62 -3
- package/package.json +1 -1
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.
|
|
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.
|
package/lib/gateway/commands.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/setup-wizard.js
CHANGED
|
@@ -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: '
|
|
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}`);
|