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 +132 -53
- package/lib/brand.js +105 -0
- package/lib/commands.js +1 -0
- package/lib/gateway/commands.js +60 -6
- package/lib/setup-wizard.js +62 -3
- package/package.json +1 -1
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
package/lib/gateway/commands.js
CHANGED
|
@@ -821,10 +821,64 @@ export async function handleWeb(args = {}) {
|
|
|
821
821
|
// ---------------------------------------------------------------------------
|
|
822
822
|
|
|
823
823
|
export async function handleSetupSignal() {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
}
|
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}`);
|