@yobekasbah/cli 1.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/kasbah 2.js +766 -0
- package/bin/kasbah.js +802 -0
- package/deploy.js +454 -0
- package/init-setup.js +264 -0
- package/package.json +10 -0
- package/validate-setup.js +230 -0
package/bin/kasbah 2.js
ADDED
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kasbah Guard — unified CLI
|
|
6
|
+
*
|
|
7
|
+
* kasbah scan "<text>" — input governance
|
|
8
|
+
* kasbah govern --prompt "<text>" [--agent X]
|
|
9
|
+
* kasbah explain "<text>" — causal trace for a verdict
|
|
10
|
+
* kasbah multiverse "<text>" — fuse the full engine
|
|
11
|
+
* kasbah ethics evaluate "<scenario>" — Nomos parliament
|
|
12
|
+
* kasbah ethics debate "<scenario>" — Nomos debate mode
|
|
13
|
+
* kasbah products list — list reference detectors
|
|
14
|
+
* kasbah products run <slug> "<text>" — run a specific product
|
|
15
|
+
* kasbah algorithms list [--category X] — list 63 algorithms
|
|
16
|
+
* kasbah algorithms run <slug> --input "<text>"
|
|
17
|
+
* kasbah agents list — list active agent sessions
|
|
18
|
+
* kasbah redteam suite — list adversarial suite
|
|
19
|
+
* kasbah redteam run — run adversarial suite
|
|
20
|
+
* kasbah bench [--limit N] [--systems s1,s2] — run detection benchmark
|
|
21
|
+
* kasbah bench corpus — corpus summary
|
|
22
|
+
* kasbah bench last — most recent result
|
|
23
|
+
* kasbah receipt verify <proof> — offline verification
|
|
24
|
+
* kasbah os check --content "<text>" [--agent X --verb QUERY]
|
|
25
|
+
* kasbah os metrics — live OS metrics
|
|
26
|
+
* kasbah os status — OS status
|
|
27
|
+
* kasbah swarm — swarm-radar snapshot
|
|
28
|
+
* kasbah forecast [--window 3600000] — threat forecast
|
|
29
|
+
* kasbah biometric <fingerprint|rppg|voice|codec> --json '{...}'
|
|
30
|
+
* kasbah zk <generate|verify|commitment|range> --json '{...}'
|
|
31
|
+
* kasbah security <consent|shamir-split|shamir-combine|threshold> --json '{...}'
|
|
32
|
+
* kasbah honeytokens deploy [--service X]
|
|
33
|
+
* kasbah honeytokens check "<text>"
|
|
34
|
+
* kasbah agent gcmdp --action X --state '{...}'
|
|
35
|
+
* kasbah agent maqasid --content "<text>"
|
|
36
|
+
* kasbah kernel gate --operation X --context '{...}'
|
|
37
|
+
* kasbah policy [get|put --file P] — policy editor
|
|
38
|
+
* kasbah health
|
|
39
|
+
*
|
|
40
|
+
* Global flags:
|
|
41
|
+
* --api <url> — override KASBAH_API_URL (default http://127.0.0.1:8788)
|
|
42
|
+
* --json — emit raw JSON (default: pretty summary)
|
|
43
|
+
* --quiet — suppress headers, useful in pipelines
|
|
44
|
+
*
|
|
45
|
+
* Honest scope: this is a thin HTTP wrapper. Every command calls one route
|
|
46
|
+
* on the api-server. Zero local logic, zero new endpoints.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const http = require('http');
|
|
50
|
+
const https = require('https');
|
|
51
|
+
const fs = require('fs');
|
|
52
|
+
const url = require('url');
|
|
53
|
+
|
|
54
|
+
const PKG_VERSION = '1.0.0';
|
|
55
|
+
|
|
56
|
+
// ─── arg parsing ────────────────────────────────────────────────────────────
|
|
57
|
+
const argv = process.argv.slice(2);
|
|
58
|
+
const flags = {};
|
|
59
|
+
const positional = [];
|
|
60
|
+
for (let i = 0; i < argv.length; i++) {
|
|
61
|
+
const a = argv[i];
|
|
62
|
+
if (a.startsWith('--')) {
|
|
63
|
+
const key = a.slice(2);
|
|
64
|
+
const next = argv[i + 1];
|
|
65
|
+
if (next === undefined || next.startsWith('--')) { flags[key] = true; }
|
|
66
|
+
else { flags[key] = next; i++; }
|
|
67
|
+
} else positional.push(a);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const API_BASE = flags.api || process.env.KASBAH_API_URL || 'http://127.0.0.1:8788';
|
|
71
|
+
const EMIT_JSON = !!flags.json;
|
|
72
|
+
const QUIET = !!flags.quiet;
|
|
73
|
+
|
|
74
|
+
// ─── http ───────────────────────────────────────────────────────────────────
|
|
75
|
+
function request(method, path, body = null) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const u = new URL(API_BASE + path);
|
|
78
|
+
const lib = u.protocol === 'https:' ? https : http;
|
|
79
|
+
const data = body !== null ? JSON.stringify(body) : null;
|
|
80
|
+
const req = lib.request({
|
|
81
|
+
hostname: u.hostname, port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
82
|
+
path: u.pathname + (u.search || ''), method,
|
|
83
|
+
headers: Object.assign(
|
|
84
|
+
{ Accept: 'application/json' },
|
|
85
|
+
data ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } : {},
|
|
86
|
+
process.env.KASBAH_API_KEY ? { Authorization: 'Bearer ' + process.env.KASBAH_API_KEY } : {}
|
|
87
|
+
)
|
|
88
|
+
}, (res) => {
|
|
89
|
+
let chunks = '';
|
|
90
|
+
res.on('data', c => chunks += c);
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
const ok = res.statusCode >= 200 && res.statusCode < 300;
|
|
93
|
+
let parsed = null;
|
|
94
|
+
try { parsed = chunks ? JSON.parse(chunks) : null; } catch { parsed = chunks; }
|
|
95
|
+
if (!ok) reject(Object.assign(new Error('HTTP ' + res.statusCode), { status: res.statusCode, body: parsed }));
|
|
96
|
+
else resolve(parsed);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
req.on('error', reject);
|
|
100
|
+
req.setTimeout(60000, () => req.destroy(new Error('timeout')));
|
|
101
|
+
if (data) req.write(data);
|
|
102
|
+
req.end();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── output helpers ─────────────────────────────────────────────────────────
|
|
107
|
+
const C = { reset:'\x1b[0m', dim:'\x1b[2m', bold:'\x1b[1m',
|
|
108
|
+
red:'\x1b[31m', green:'\x1b[32m', yellow:'\x1b[33m',
|
|
109
|
+
blue:'\x1b[34m', cyan:'\x1b[36m', gray:'\x1b[90m' };
|
|
110
|
+
const usingColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
111
|
+
const c = (color, s) => usingColor ? C[color] + s + C.reset : s;
|
|
112
|
+
function header(msg) { if (!QUIET && !EMIT_JSON) console.error(c('dim', msg)); }
|
|
113
|
+
function output(obj) {
|
|
114
|
+
if (EMIT_JSON) { console.log(JSON.stringify(obj, null, 2)); return; }
|
|
115
|
+
console.log(typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
|
|
116
|
+
}
|
|
117
|
+
function verdictColor(v) {
|
|
118
|
+
const u = String(v || '').toUpperCase();
|
|
119
|
+
if (u.includes('DENY') || u.includes('BLOCK') || u.includes('PROHIBIT')) return 'red';
|
|
120
|
+
if (u.includes('WARN') || u.includes('FLAG')) return 'yellow';
|
|
121
|
+
if (u.includes('ALLOW') || u.includes('PASS') || u.includes('CLEAN')) return 'green';
|
|
122
|
+
return 'cyan';
|
|
123
|
+
}
|
|
124
|
+
function deriveVerdict(d) {
|
|
125
|
+
if (!d) return '?';
|
|
126
|
+
const raw = d.verdict || d.decision || d.classification;
|
|
127
|
+
if (raw) return String(raw).toUpperCase();
|
|
128
|
+
const risk = typeof d.risk === 'number' ? d.risk : typeof d.risk_score === 'number' ? d.risk_score : null;
|
|
129
|
+
if (risk !== null) return risk >= 0.6 ? 'DENY' : risk >= 0.3 ? 'WARN' : 'ALLOW';
|
|
130
|
+
if (Array.isArray(d.threats)) return d.threats.length ? 'WARN' : 'ALLOW';
|
|
131
|
+
if (typeof d.safe === 'boolean') return d.safe ? 'ALLOW' : 'WARN';
|
|
132
|
+
return '?';
|
|
133
|
+
}
|
|
134
|
+
function summary(d, primary = []) {
|
|
135
|
+
const v = deriveVerdict(d);
|
|
136
|
+
const r = typeof d.risk === 'number' ? d.risk : typeof d.risk_score === 'number' ? d.risk_score : null;
|
|
137
|
+
let line = c(verdictColor(v), v);
|
|
138
|
+
if (r !== null) line += ' ' + c('dim', 'risk ' + (r * 100).toFixed(0) + '%');
|
|
139
|
+
if (Array.isArray(d.threats) && d.threats.length) line += ' ' + c('dim', d.threats.slice(0, 3).map(t => typeof t === 'string' ? t : (t.type || t.name)).join(', '));
|
|
140
|
+
if (typeof d.latency_ms === 'number' || typeof d.latencyMs === 'number') {
|
|
141
|
+
line += ' ' + c('dim', (d.latency_ms || d.latencyMs) + 'ms');
|
|
142
|
+
}
|
|
143
|
+
console.log(line);
|
|
144
|
+
for (const k of primary) if (k in d) console.log(c('gray', ' ' + k + ': ') + (typeof d[k] === 'object' ? JSON.stringify(d[k]).slice(0, 120) : d[k]));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── smart-inference (mirrors desktop UI) ───────────────────────────────────
|
|
148
|
+
function detectUseCase(t) {
|
|
149
|
+
if (!t) return null;
|
|
150
|
+
const low = t.toLowerCase();
|
|
151
|
+
const lines = t.split('\n').map(l => l.trim()).filter(Boolean);
|
|
152
|
+
if (lines.length >= 2) {
|
|
153
|
+
const verbLines = lines.filter(l => /^(EXTRACT|FORMAT|SEND|READ|WRITE|EXEC|SCAN|LIST|DELETE|UPLOAD|DOWNLOAD|QUERY|SEARCH|CREATE|MODIFY|MOVE|COPY)\s/.test(l));
|
|
154
|
+
if (verbLines.length >= 2 && verbLines.length >= lines.length * 0.5)
|
|
155
|
+
return { slug: 'intent-analyzer', label: 'an agent trajectory' };
|
|
156
|
+
}
|
|
157
|
+
if (/\b(experience|education|skills|employment|résumé|resume|curriculum|cv)\b/.test(low)
|
|
158
|
+
&& /^\s*[-•·]\s/m.test(t) && lines.length >= 5)
|
|
159
|
+
return { slug: 'resume-authentic', label: 'a resume or CV' };
|
|
160
|
+
if (/\b(swipe|tinder|bumble|hinge|match|profile|bio|love bombing)\b/.test(low)
|
|
161
|
+
|| /\b(send money|western union|gift card|crypto|wire transfer).{0,100}\b(urgent|emergency|stuck|hospital|airport)\b/.test(low))
|
|
162
|
+
return { slug: 'dating-guard', label: 'a dating or romance scenario' };
|
|
163
|
+
if (/\b(you always|you never|i never said|that didn't happen|you're imagining|you're crazy|stop overreacting|after everything I)\b/.test(low))
|
|
164
|
+
return { slug: 'relationship-guard', label: 'a manipulative conversation' };
|
|
165
|
+
if (/\b(plaintiff|defendant|whereas|hereby|witnessed|notarized|jurisdiction|affidavit|statute)\b/.test(low)
|
|
166
|
+
|| /\b(article \d|section \d|clause \d|exhibit [A-Z])\b/i.test(t))
|
|
167
|
+
return { slug: 'legal-evidence', label: 'a legal document' };
|
|
168
|
+
if (/\b(dao|on-chain|tokenholders|quorum|snapshot|governance vote|treasury allocation|ratify|abstain)\b/.test(low))
|
|
169
|
+
return { slug: 'dao-vote-guard', label: 'a DAO governance proposal' };
|
|
170
|
+
if (/^@\w+:?/m.test(t) || /\b(retweet|repost|engagement farm|coordinated|botnet|sockpuppet)\b/.test(low))
|
|
171
|
+
return { slug: 'botnet-radar', label: 'a social-media thread' };
|
|
172
|
+
if (/AKIA[A-Z0-9]{16}|sk-proj-|sk_live_|ghp_|-----BEGIN [A-Z ]+PRIVATE KEY-----|rm\s+-rf|drop\s+table|ignore\s+(all|previous)\s+instructions/i.test(t))
|
|
173
|
+
return null;
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function usage() {
|
|
178
|
+
console.error(`Kasbah Guard CLI v${PKG_VERSION} · ${c('dim', 'api: ' + API_BASE)}
|
|
179
|
+
|
|
180
|
+
${c('bold', '⚙️ DEPLOYMENT')}
|
|
181
|
+
${c('bold', 'kasbah deploy')} init <aws|gcp|azure> [--region REGION]
|
|
182
|
+
${c('bold', 'kasbah deploy')} apply [--auto-approve]
|
|
183
|
+
${c('bold', 'kasbah deploy')} status | endpoint | verify
|
|
184
|
+
${c('bold', 'kasbah deploy')} logs [--tail N] | scale --replicas N | destroy [--force]
|
|
185
|
+
|
|
186
|
+
${c('bold', 'SETUP')}
|
|
187
|
+
${c('bold', 'kasbah init')} --auto-setup
|
|
188
|
+
${c('bold', 'kasbah validate')}
|
|
189
|
+
|
|
190
|
+
${c('bold', '🛡️ DETECTION')}
|
|
191
|
+
${c('bold', 'kasbah scan')} "<text>"
|
|
192
|
+
${c('bold', 'kasbah govern')} --prompt "<text>" [--agent X]
|
|
193
|
+
${c('bold', 'kasbah explain')} "<text>"
|
|
194
|
+
${c('bold', 'kasbah multiverse')} "<text>"
|
|
195
|
+
${c('bold', 'kasbah ethics')} <evaluate|debate> "<scenario>"
|
|
196
|
+
${c('bold', 'kasbah products')} <list | run <slug> "<text>">
|
|
197
|
+
${c('bold', 'kasbah algorithms')} <list [--category X] | run <slug> --input "<text>">
|
|
198
|
+
${c('bold', 'kasbah agents')} list
|
|
199
|
+
${c('bold', 'kasbah redteam')} <suite | run>
|
|
200
|
+
${c('bold', 'kasbah bench')} [--limit N | corpus | last]
|
|
201
|
+
${c('bold', 'kasbah receipt verify')} <proof>
|
|
202
|
+
${c('bold', 'kasbah os')} <ingest|health|retrieve|keygen|sign|verify|binary|audit|merkle|pipeline|modules|ping>
|
|
203
|
+
${c('bold', 'kasbah os crypto')} <keygen|sign|verify|pubkey>
|
|
204
|
+
${c('bold', 'kasbah swarm')}
|
|
205
|
+
${c('bold', 'kasbah forecast')} [--window 3600000]
|
|
206
|
+
${c('bold', 'kasbah compliance')} <VERB> [--target T] [--content "..."] [--packs ...]
|
|
207
|
+
${c('bold', 'kasbah silent-speech')} <VERB> ${c('dim', '— block silent-speech/biometric surveillance (BIOMETRIC_GUARD)')}
|
|
208
|
+
${c('bold', 'kasbah biometric')} <fingerprint|rppg|voice|codec> --json '{...}'
|
|
209
|
+
${c('bold', 'kasbah zk')} <generate|verify|commitment|range> --json '{...}'
|
|
210
|
+
${c('bold', 'kasbah security')} <consent|shamir-split|shamir-combine|threshold> --json '{...}'
|
|
211
|
+
${c('bold', 'kasbah honeytokens')} <deploy [--service X] | check "<text>">
|
|
212
|
+
${c('bold', 'kasbah agent')} <gcmdp|maqasid> ...
|
|
213
|
+
${c('bold', 'kasbah kernel gate')} --operation X --context '{...}'
|
|
214
|
+
${c('bold', 'kasbah policy')} [get|put --file P]
|
|
215
|
+
${c('bold', 'kasbah health')}
|
|
216
|
+
|
|
217
|
+
Flags: ${c('dim', '--api <url> --json --quiet')}
|
|
218
|
+
Env: ${c('dim', 'KASBAH_API_URL KASBAH_API_KEY')}
|
|
219
|
+
`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── command dispatch ───────────────────────────────────────────────────────
|
|
224
|
+
const cmd = positional[0];
|
|
225
|
+
const sub = positional[1];
|
|
226
|
+
const rest = positional.slice(2);
|
|
227
|
+
|
|
228
|
+
async function main() {
|
|
229
|
+
if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') return usage();
|
|
230
|
+
|
|
231
|
+
// Setup / init
|
|
232
|
+
if (cmd === 'init') {
|
|
233
|
+
const init = require('./init-setup.js');
|
|
234
|
+
if (sub === '--auto-setup' || flags['auto-setup']) {
|
|
235
|
+
return await init();
|
|
236
|
+
}
|
|
237
|
+
console.error('Usage: kasbah init --auto-setup');
|
|
238
|
+
process.exit(2);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (cmd === 'validate') {
|
|
242
|
+
const validate = require('./validate-setup.js');
|
|
243
|
+
return await validate();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Deployment management
|
|
247
|
+
if (cmd === 'deploy') {
|
|
248
|
+
const { DeploymentManager } = require('./deploy.js');
|
|
249
|
+
const manager = new DeploymentManager();
|
|
250
|
+
|
|
251
|
+
const options = {};
|
|
252
|
+
for (const key in flags) {
|
|
253
|
+
options[key] = flags[key];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (sub === 'init') {
|
|
257
|
+
const cloud = rest[0];
|
|
258
|
+
if (!cloud) { console.error('Usage: kasbah deploy init <cloud> [--region REGION]'); process.exit(2); }
|
|
259
|
+
return await manager.init(cloud, options);
|
|
260
|
+
}
|
|
261
|
+
if (sub === 'apply') return await manager.apply(options);
|
|
262
|
+
if (sub === 'status') return await manager.status();
|
|
263
|
+
if (sub === 'endpoint') return await manager.endpoint();
|
|
264
|
+
if (sub === 'logs') return await manager.logs(options);
|
|
265
|
+
if (sub === 'scale') return await manager.scale(options);
|
|
266
|
+
if (sub === 'verify') return await manager.verify();
|
|
267
|
+
if (sub === 'destroy') return await manager.destroy(options);
|
|
268
|
+
|
|
269
|
+
console.error(`
|
|
270
|
+
Usage:
|
|
271
|
+
kasbah deploy init <cloud> [--region REGION]
|
|
272
|
+
kasbah deploy apply [--auto-approve]
|
|
273
|
+
kasbah deploy status
|
|
274
|
+
kasbah deploy endpoint
|
|
275
|
+
kasbah deploy logs [--tail N]
|
|
276
|
+
kasbah deploy scale --replicas N
|
|
277
|
+
kasbah deploy verify
|
|
278
|
+
kasbah deploy destroy [--force]
|
|
279
|
+
|
|
280
|
+
Supported clouds: aws, gcp, azure
|
|
281
|
+
`);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Single-word commands first
|
|
286
|
+
if (cmd === 'health') return summary(await request('GET', '/v1/health'));
|
|
287
|
+
if (cmd === 'swarm') return output(await request('GET', '/v1/swarm/status'));
|
|
288
|
+
if (cmd === 'forecast') return output(await request('GET', '/v1/forecast?window=' + (flags.window || 3600000)));
|
|
289
|
+
|
|
290
|
+
if (cmd === 'scan') {
|
|
291
|
+
const text = sub || flags.text || flags.prompt;
|
|
292
|
+
if (!text) { console.error(' kasbah scan "<text>"'); process.exit(2); }
|
|
293
|
+
// Smart inference — quietly routes to the right product when the content shape is obvious.
|
|
294
|
+
// Disable with --no-infer or pass --as <slug> to force a specific product.
|
|
295
|
+
const explicit = flags.as;
|
|
296
|
+
const inferred = (flags['no-infer'] || explicit) ? null : detectUseCase(text);
|
|
297
|
+
const slug = explicit || inferred?.slug;
|
|
298
|
+
if (slug && slug !== 'general') {
|
|
299
|
+
if (!QUIET && !EMIT_JSON) console.error(c('dim', ' detected: ' + (inferred?.label || slug) + (explicit ? ' (forced)' : '')));
|
|
300
|
+
if (slug === 'intent-analyzer') {
|
|
301
|
+
return summary(await request('POST', '/v1/algorithms/intent-analyzer/run', { steps: text, input: text }), ['summary']);
|
|
302
|
+
}
|
|
303
|
+
let body;
|
|
304
|
+
if (slug === 'dating-guard') body = { input: text, profile: { bio: text } };
|
|
305
|
+
else if (slug === 'botnet-radar') body = { accounts:[{id:'a1',posts:[text]},{id:'a2',posts:[text]},{id:'a3',posts:[text]}] };
|
|
306
|
+
else body = { input: text };
|
|
307
|
+
return summary(await request('POST', '/v1/products/' + slug + '/analyze', body), ['summary']);
|
|
308
|
+
}
|
|
309
|
+
return summary(await request('POST', '/v1/scan', { text, prompt: text }));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (cmd === 'govern') {
|
|
313
|
+
const prompt = flags.prompt || sub;
|
|
314
|
+
if (!prompt) { console.error(' kasbah govern --prompt "<text>" [--agent X]'); process.exit(2); }
|
|
315
|
+
return summary(await request('POST', '/v1/govern', { prompt, agent: flags.agent || 'cli' }));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (cmd === 'compliance') {
|
|
319
|
+
// Evaluate an action against compliance + biometric-surveillance rule packs.
|
|
320
|
+
const verb = flags.verb || sub;
|
|
321
|
+
if (!verb) { console.error(' kasbah compliance <VERB> [--target T] [--content "..."] [--packs HIPAA,GDPR,SOC2,BIOMETRIC_GUARD]'); process.exit(2); }
|
|
322
|
+
const packs = flags.packs || 'HIPAA,GDPR,SOC2,BIOMETRIC_GUARD';
|
|
323
|
+
return summary(await request('POST', '/v1/algorithms/policy-engine/run', {
|
|
324
|
+
verb, target: flags.target || '', content: flags.content || rest.join(' ') || '', packs
|
|
325
|
+
}), ['decision', 'reason', 'violations']);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (cmd === 'explain') {
|
|
329
|
+
const text = sub || flags.text;
|
|
330
|
+
if (!text) { console.error(' kasbah explain "<text>"'); process.exit(2); }
|
|
331
|
+
const r = await request('POST', '/v1/explain', { prompt: text });
|
|
332
|
+
summary(r, ['explanation', 'categories']);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (cmd === 'multiverse') {
|
|
337
|
+
const input = sub || flags.input;
|
|
338
|
+
if (!input) { console.error(' kasbah multiverse "<text>"'); process.exit(2); }
|
|
339
|
+
const r = await request('POST', '/v1/multiverse/analyze', { input });
|
|
340
|
+
summary(r, ['summary']);
|
|
341
|
+
if (r.superposition?.results && !EMIT_JSON) {
|
|
342
|
+
console.log(c('gray', ' universes:'));
|
|
343
|
+
for (const [k, v] of Object.entries(r.superposition.results)) {
|
|
344
|
+
if (v.error) console.log(' ' + k + ' ' + c('red', 'error'));
|
|
345
|
+
else {
|
|
346
|
+
const risk = v.risk_score ?? v.risk ?? v.score ?? null;
|
|
347
|
+
const r2 = risk !== null ? (risk * 100).toFixed(0) + '%' : '—';
|
|
348
|
+
console.log(' ' + k.padEnd(22) + c('dim', r2));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (cmd === 'ethics') {
|
|
356
|
+
const text = rest.join(' ') || flags.scenario || flags.content;
|
|
357
|
+
if (!text) { console.error(' kasbah ethics <evaluate|debate> "<scenario>"'); process.exit(2); }
|
|
358
|
+
if (sub === 'evaluate' || !sub) {
|
|
359
|
+
const r = await request('POST', '/v1/nomos/evaluate', { content: text, context: {} });
|
|
360
|
+
console.log(c(verdictColor(r.verdict), r.verdict || '?'));
|
|
361
|
+
const trads = r.traditions || {};
|
|
362
|
+
for (const [k, t] of Object.entries(trads)) {
|
|
363
|
+
const v = t.verdict || t.judgment || '?';
|
|
364
|
+
console.log(' ' + k.padEnd(15) + c(verdictColor(v), v));
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (sub === 'debate') {
|
|
369
|
+
const pre = await request('POST', '/v1/nomos/evaluate', { content: text, context: {} }).catch(() => null);
|
|
370
|
+
const initialVerdict = pre?.verdict || 'CONTESTED';
|
|
371
|
+
const r = await request('POST', '/v1/nomos/debate', {
|
|
372
|
+
content: text, initialVerdict,
|
|
373
|
+
challenge: flags.challenge || 'Argue the opposite verdict.'
|
|
374
|
+
});
|
|
375
|
+
output(r);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (cmd === 'products') {
|
|
381
|
+
if (sub === 'list' || !sub) {
|
|
382
|
+
const r = await request('GET', '/v1/products');
|
|
383
|
+
if (EMIT_JSON) return output(r);
|
|
384
|
+
console.log(c('bold', (r.count || 0) + ' products, ' + (r.available || 0) + ' available'));
|
|
385
|
+
for (const p of r.products || []) {
|
|
386
|
+
const dot = p.available ? c('green', '●') : c('red', '○');
|
|
387
|
+
console.log(' ' + dot + ' ' + p.slug.padEnd(22) + c('dim', p.name));
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (sub === 'run') {
|
|
392
|
+
const slug = rest[0];
|
|
393
|
+
const text = rest.slice(1).join(' ') || flags.input;
|
|
394
|
+
if (!slug || !text) { console.error(' kasbah products run <slug> "<text>"'); process.exit(2); }
|
|
395
|
+
const r = await request('POST', '/v1/products/' + slug + '/analyze', { input: text });
|
|
396
|
+
console.log(c(verdictColor(r.verdict), r.verdict || '?') + ' ' + c('dim', (r.summary || '').slice(0, 100)));
|
|
397
|
+
if (typeof r.score === 'number') console.log(' ' + c('gray', 'score: ') + r.score.toFixed(3));
|
|
398
|
+
if (typeof r.latencyMs === 'number') console.log(' ' + c('gray', 'latency: ') + r.latencyMs + 'ms');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (cmd === 'algorithms') {
|
|
404
|
+
if (sub === 'list' || !sub) {
|
|
405
|
+
const r = await request('GET', '/v1/algorithms');
|
|
406
|
+
if (EMIT_JSON) return output(r);
|
|
407
|
+
console.log(c('bold', (r.count || 0) + ' algorithms · ') + c('dim', (r.categories || []).join(' · ')));
|
|
408
|
+
const cat = flags.category;
|
|
409
|
+
for (const a of r.algorithms || []) {
|
|
410
|
+
if (cat && a.category !== cat) continue;
|
|
411
|
+
console.log(' ' + a.slug.padEnd(28) + c('gray', a.category.padEnd(15)) + c('dim', a.name));
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (sub === 'run') {
|
|
416
|
+
const slug = rest[0];
|
|
417
|
+
const input = flags.input || rest.slice(1).join(' ');
|
|
418
|
+
if (!slug) { console.error(' kasbah algorithms run <slug> --input "<text>"'); process.exit(2); }
|
|
419
|
+
const body = flags.json ? JSON.parse(flags.json) : { input };
|
|
420
|
+
const r = await request('POST', '/v1/algorithms/' + slug + '/run', body);
|
|
421
|
+
summary(r);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (cmd === 'agents') {
|
|
427
|
+
if (sub === 'list' || !sub) {
|
|
428
|
+
const r = await request('GET', '/v1/agents');
|
|
429
|
+
if (EMIT_JSON) return output(r);
|
|
430
|
+
const sessions = r.sessions || [];
|
|
431
|
+
console.log(c('bold', sessions.length + ' active session' + (sessions.length === 1 ? '' : 's')));
|
|
432
|
+
for (const s of sessions) console.log(' ' + (s.sessionId || s.id || '?').padEnd(20) + c('dim', s.status || ''));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (cmd === 'redteam') {
|
|
438
|
+
if (sub === 'suite') return output(await request('GET', '/v1/redteam/suite'));
|
|
439
|
+
if (sub === 'run' || !sub) {
|
|
440
|
+
const r = await request('POST', '/v1/redteam/run', flags.json ? JSON.parse(flags.json) : {});
|
|
441
|
+
if (EMIT_JSON) return output(r);
|
|
442
|
+
const cases = r.results || r.cases || [];
|
|
443
|
+
const passed = cases.filter(c => c.passed || c.caught).length;
|
|
444
|
+
const pct = cases.length ? Math.round(passed / cases.length * 100) : 0;
|
|
445
|
+
const col = pct >= 80 ? 'green' : pct >= 50 ? 'yellow' : 'red';
|
|
446
|
+
console.log(c(col, pct + '%') + ' ' + passed + '/' + cases.length + ' attacks caught');
|
|
447
|
+
for (const cc of cases.filter(x => !(x.passed || x.caught)).slice(0, 10)) {
|
|
448
|
+
console.log(' ' + c('red', '✕') + ' ' + (cc.id || cc.name || '?').padEnd(20) + c('dim', (cc.prompt || cc.input || '').toString().slice(0, 60)));
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (cmd === 'bench') {
|
|
455
|
+
if (sub === 'corpus') return output(await request('GET', '/v1/bench/corpus'));
|
|
456
|
+
if (sub === 'last') return output(await request('GET', '/v1/bench/last'));
|
|
457
|
+
// run
|
|
458
|
+
const body = { systems: (flags.systems || 'scan,govern,baselines').split(',') };
|
|
459
|
+
if (flags.limit) body.limit = parseInt(flags.limit);
|
|
460
|
+
const r = await request('POST', '/v1/bench/run', body);
|
|
461
|
+
if (EMIT_JSON) return output(r);
|
|
462
|
+
const s = r.summary || {};
|
|
463
|
+
const systems = Object.keys(s).sort((a, b) => (s[b].f1 || 0) - (s[a].f1 || 0));
|
|
464
|
+
console.log(c('bold', 'System'.padEnd(28)) + ['Prec','Rec','F1','Acc'].map(h => c('bold', h.padStart(7))).join(' '));
|
|
465
|
+
for (const name of systems) {
|
|
466
|
+
const m = s[name];
|
|
467
|
+
const isK = !name.startsWith('baseline:');
|
|
468
|
+
const f1col = m.f1 >= 0.75 ? 'green' : m.f1 >= 0.55 ? 'yellow' : 'red';
|
|
469
|
+
console.log(
|
|
470
|
+
(isK ? c('cyan', name) : name).padEnd(28) +
|
|
471
|
+
(m.precision * 100).toFixed(0).padStart(6) + '% ' +
|
|
472
|
+
(m.recall * 100).toFixed(0).padStart(6) + '% ' +
|
|
473
|
+
c(f1col, (m.f1 * 100).toFixed(0).padStart(6) + '%') + ' ' +
|
|
474
|
+
(m.accuracy * 100).toFixed(0).padStart(6) + '%'
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (cmd === 'receipt') {
|
|
481
|
+
if (sub === 'verify') {
|
|
482
|
+
const proof = rest[0];
|
|
483
|
+
if (!proof) { console.error(' kasbah receipt verify <proof>'); process.exit(2); }
|
|
484
|
+
return summary(await request('POST', '/v1/receipt/verify', { receipt: proof }));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (cmd === 'os') {
|
|
489
|
+
if (sub === 'status') return summary(await request('GET', '/v1/os/status'));
|
|
490
|
+
if (sub === 'metrics') return output(await request('GET', '/v1/os/metrics'));
|
|
491
|
+
if (sub === 'check') {
|
|
492
|
+
const content = flags.content || rest.join(' ');
|
|
493
|
+
if (!content) { console.error(' kasbah os check --content "<text>" [--agent X --verb QUERY]'); process.exit(2); }
|
|
494
|
+
return summary(await request('POST', '/v1/os/check', {
|
|
495
|
+
agent: flags.agent || 'cli', verb: flags.verb || 'QUERY', target: 'content', content
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (cmd === 'biometric') {
|
|
501
|
+
const map = { fingerprint: 'neural-fingerprint', rppg: 'rppg', voice: 'voice-clone', codec: 'neural-codec' };
|
|
502
|
+
const route = map[sub];
|
|
503
|
+
if (!route) { console.error(' kasbah biometric <fingerprint|rppg|voice|codec> --json \'{...}\''); process.exit(2); }
|
|
504
|
+
const body = flags.json ? JSON.parse(flags.json) : {};
|
|
505
|
+
return output(await request('POST', '/v1/biometric/' + route, body));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (cmd === 'zk') {
|
|
509
|
+
const map = { generate: 'proof/generate', verify: 'proof/verify', commitment: 'commitment', range: 'range-proof' };
|
|
510
|
+
const route = map[sub];
|
|
511
|
+
if (!route) { console.error(' kasbah zk <generate|verify|commitment|range> --json \'{...}\''); process.exit(2); }
|
|
512
|
+
const body = flags.json ? JSON.parse(flags.json) : {};
|
|
513
|
+
return output(await request('POST', '/v1/zk/' + route, body));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (cmd === 'security') {
|
|
517
|
+
const map = { consent: 'consent/check', 'shamir-split': 'shamir/split', 'shamir-combine': 'shamir/combine', threshold: 'threshold/adjust', 'cross-modal': 'cross-modal/verify' };
|
|
518
|
+
const route = map[sub];
|
|
519
|
+
if (!route) { console.error(' kasbah security <consent|shamir-split|shamir-combine|threshold|cross-modal> --json \'{...}\''); process.exit(2); }
|
|
520
|
+
const body = flags.json ? JSON.parse(flags.json) : {};
|
|
521
|
+
return output(await request('POST', '/v1/security/' + route, body));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (cmd === 'honeytokens') {
|
|
525
|
+
if (sub === 'deploy') return output(await request('POST', '/v1/honeytokens/deploy', { service: flags.service || 'cli' }));
|
|
526
|
+
if (sub === 'check') return output(await request('POST', '/v1/honeytokens/check', { text: rest.join(' ') }));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (cmd === 'agent') {
|
|
530
|
+
if (sub === 'gcmdp') {
|
|
531
|
+
const state = flags.state ? JSON.parse(flags.state) : {};
|
|
532
|
+
return output(await request('POST', '/v1/agent/gcmdp', { action: flags.action || 'READ', state, constraints: {} }));
|
|
533
|
+
}
|
|
534
|
+
if (sub === 'maqasid') {
|
|
535
|
+
return output(await request('POST', '/v1/agent/maqasid', { content: flags.content || rest.join(' ') }));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (cmd === 'kernel' && sub === 'gate') {
|
|
540
|
+
const context = flags.context ? JSON.parse(flags.context) : {};
|
|
541
|
+
return output(await request('POST', '/v1/kernel/gate', { operation: flags.operation || 'exec', context }));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (cmd === 'policy') {
|
|
545
|
+
if (sub === 'put') {
|
|
546
|
+
if (!flags.file) { console.error(' kasbah policy put --file <path>'); process.exit(2); }
|
|
547
|
+
const policy = JSON.parse(fs.readFileSync(flags.file, 'utf8'));
|
|
548
|
+
return output(await request('PUT', '/v1/policy', policy));
|
|
549
|
+
}
|
|
550
|
+
return output(await request('GET', '/v1/policy'));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ── New v3 endpoints ─────────────────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
if (cmd === 'forge' && sub === 'ksi') {
|
|
556
|
+
return output(await request('POST', '/v1/forge/ksi', { text: rest.join(' ') }));
|
|
557
|
+
}
|
|
558
|
+
if (cmd === 'nexus') {
|
|
559
|
+
if (sub === 'status') return output(await request('GET', '/v1/nexus/status'));
|
|
560
|
+
return output(await request('POST', '/v1/nexus/command', { command: rest.join(' ') }));
|
|
561
|
+
}
|
|
562
|
+
if (cmd === 'cortex') {
|
|
563
|
+
return output(await request('POST', '/v1/cortex/exec', { command: rest.join(' ') }));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (cmd === 'products' && sub === 'status') {
|
|
567
|
+
return output(await request('GET', '/v1/products/status'));
|
|
568
|
+
}
|
|
569
|
+
if (cmd === 'products' && sub === 'scan') {
|
|
570
|
+
const product = rest[0];
|
|
571
|
+
const text = rest.slice(1).join(' ');
|
|
572
|
+
if (!product || !text) { console.error(' kasbah products scan <product> "<text>"'); process.exit(2); }
|
|
573
|
+
return output(await request('POST', `/v1/products/${product}`, { text }));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (cmd === 'crypto') {
|
|
577
|
+
if (sub === 'shamir-split') {
|
|
578
|
+
const body = flags.json ? JSON.parse(flags.json) : { secret: rest[0], n: parseInt(rest[1]) || 5, k: parseInt(rest[2]) || 3 };
|
|
579
|
+
return output(await request('POST', '/v1/crypto/shamir/split', body));
|
|
580
|
+
}
|
|
581
|
+
if (sub === 'shamir-combine') {
|
|
582
|
+
const body = flags.json ? JSON.parse(flags.json) : { shares: rest };
|
|
583
|
+
return output(await request('POST', '/v1/crypto/shamir/combine', body));
|
|
584
|
+
}
|
|
585
|
+
if (sub === 'mmr-append') {
|
|
586
|
+
return output(await request('POST', '/v1/crypto/mmr/append', { data: rest.join(' ') }));
|
|
587
|
+
}
|
|
588
|
+
if (sub === 'vdf-eval') {
|
|
589
|
+
return output(await request('POST', '/v1/crypto/vdf/eval', { input: rest.join(' '), iterations: parseInt(flags.iterations) || 1000 }));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (cmd === 'silent-speech') {
|
|
594
|
+
// DEFENSIVE: Kasbah protects users AGAINST silent-speech / facial-micro-expression /
|
|
595
|
+
// subvocal / BCI / QAI intent-surveillance. It does NOT capture or analyze facial
|
|
596
|
+
// data. This checks whether an action is such a surveillance attempt and BLOCKS it
|
|
597
|
+
// via the BIOMETRIC_GUARD pack.
|
|
598
|
+
const verb = flags.verb || sub || 'CAPTURE_VIDEO';
|
|
599
|
+
return summary(await request('POST', '/v1/algorithms/policy-engine/run', {
|
|
600
|
+
verb,
|
|
601
|
+
target: flags.target || 'facial_expression',
|
|
602
|
+
content: flags.content || rest.join(' ') || 'silent-speech intent inference',
|
|
603
|
+
packs: 'BIOMETRIC_GUARD'
|
|
604
|
+
}), ['decision', 'reason', 'violations']);
|
|
605
|
+
}
|
|
606
|
+
if (cmd === 'audit-receipt') {
|
|
607
|
+
const body = flags.json ? JSON.parse(flags.json) : { policyDecision: 'block', sourceDomain: rest[0] || 'api', riskScore: 50 };
|
|
608
|
+
return output(await request('POST', '/v1/audit/receipt', body));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (cmd === 'receipt-sign') {
|
|
612
|
+
const body = flags.json ? JSON.parse(flags.json) : { verdict: 'ALLOW', risk: 0 };
|
|
613
|
+
return output(await request('POST', '/v1/sign', body));
|
|
614
|
+
}
|
|
615
|
+
if (cmd === 'receipt-sign-v3') {
|
|
616
|
+
const body = flags.json ? JSON.parse(flags.json) : { verdict: 'ALLOW', risk: 0 };
|
|
617
|
+
return output(await request('POST', '/v1/sign?v=3', body));
|
|
618
|
+
}
|
|
619
|
+
if (cmd === 'receipt-verify-batch') {
|
|
620
|
+
const body = flags.json ? JSON.parse(flags.json) : {};
|
|
621
|
+
return output(await request('POST', '/v1/receipt/verify-batch', body));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (cmd === 'kasbah-os') {
|
|
625
|
+
// v2.0
|
|
626
|
+
if (sub === 'ingest') {
|
|
627
|
+
return output(await request('POST', '/v1/kasbah-os/ingest', { secret_text: rest.join(' '), source: 'cli' }));
|
|
628
|
+
}
|
|
629
|
+
if (sub === 'health') return output(await request('GET', '/v1/kasbah-os/health'));
|
|
630
|
+
if (sub === 'retrieve') {
|
|
631
|
+
return output(await request('POST', '/v1/kasbah-os/retrieve', { receipt_id: rest[0] }));
|
|
632
|
+
}
|
|
633
|
+
// v4.0 crypto
|
|
634
|
+
if (sub === 'keygen') return output(await request('POST', '/v1/kasbah-os/crypto/keygen'));
|
|
635
|
+
if (sub === 'sign') return output(await request('POST', '/v1/kasbah-os/crypto/sign',
|
|
636
|
+
{ message_hex: rest[0], seed_hex: rest[1] }));
|
|
637
|
+
if (sub === 'verify') return output(await request('POST', '/v1/kasbah-os/crypto/verify',
|
|
638
|
+
{ message_hex: rest[0], signature_hex: rest[1], public_key_hex: rest[2] }));
|
|
639
|
+
if (sub === 'pubkey') return output(await request('POST', '/v1/kasbah-os/crypto/pubkey',
|
|
640
|
+
{ seed_hex: rest[0] }));
|
|
641
|
+
// v4.0 binary
|
|
642
|
+
if (sub === 'binary') {
|
|
643
|
+
const binary_hex = rest[0];
|
|
644
|
+
const sample_id = rest[1] || 'cli';
|
|
645
|
+
return output(await request('POST', '/v1/kasbah-os/binary/analyze', { binary_hex, sample_id }));
|
|
646
|
+
}
|
|
647
|
+
// v4.0 pipeline
|
|
648
|
+
if (sub === 'pipeline') return output(await request('POST', '/v1/kasbah-os/pipeline/run',
|
|
649
|
+
{ binary_hex: rest[0], target_info: rest[1] ? JSON.parse(rest.slice(1).join(' ')) : {} }));
|
|
650
|
+
// v4.0 audit
|
|
651
|
+
if (sub === 'audit') {
|
|
652
|
+
if (rest[0] === 'report') return output(await request('GET', '/v1/kasbah-os/audit/report'));
|
|
653
|
+
if (rest[0] === 'chain') return output(await request('GET', '/v1/kasbah-os/audit/chain'));
|
|
654
|
+
if (rest[0] === 'event') {
|
|
655
|
+
return output(await request('POST', '/v1/kasbah-os/audit/event', {
|
|
656
|
+
engine: rest[1] || 'cli', event_type: rest[2] || 'manual', data: rest[3] ? JSON.parse(rest[3]) : {}
|
|
657
|
+
}));
|
|
658
|
+
}
|
|
659
|
+
if (rest[0] === 'trust') return output(await request('POST', '/v1/kasbah-os/audit/trust',
|
|
660
|
+
{ engine: rest[1] || 'bridge' }));
|
|
661
|
+
}
|
|
662
|
+
// v4.0 merkle
|
|
663
|
+
if (sub === 'merkle') {
|
|
664
|
+
if (rest[0] === 'build') return output(await request('POST', '/v1/kasbah-os/merkle/build',
|
|
665
|
+
{ entries: rest.slice(1) }));
|
|
666
|
+
if (rest[0] === 'verify') return output(await request('POST', '/v1/kasbah-os/merkle/verify',
|
|
667
|
+
{ root_hex: rest[1], leaf_hex: rest[2], proof_hex: rest.slice(3) }));
|
|
668
|
+
}
|
|
669
|
+
// v4.0 system
|
|
670
|
+
if (sub === 'modules') return output(await request('GET', '/v1/kasbah-os/modules'));
|
|
671
|
+
if (sub === 'ping') return output(await request('GET', '/v1/kasbah-os/ping'));
|
|
672
|
+
// v4.0 governance
|
|
673
|
+
if (sub === 'governance') {
|
|
674
|
+
if (rest[0] === 'health') return output(await request('GET', '/v1/kasbah-os/governance/health'));
|
|
675
|
+
if (rest[0] === 'process') {
|
|
676
|
+
const msgs = rest[1] ? JSON.parse(rest[1]) : [];
|
|
677
|
+
return output(await request('POST', '/v1/kasbah-os/governance/process', {
|
|
678
|
+
messages: msgs, tools: [], clearance: parseInt(rest[2]) || 2
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
if (rest[0] === 'approve') {
|
|
682
|
+
return output(await request('POST', '/v1/kasbah-os/governance/approve/' + (rest[1] || ''));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (cmd === 'pipeline' && sub === 'unified') {
|
|
688
|
+
return output(await request('POST', '/v1/pipeline/unified', { text: rest.join(' ') }));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (cmd === 'mcp' && sub === 'servers') return output(await request('GET', '/v1/mcp/servers'));
|
|
692
|
+
if (cmd === 'mcp' && sub === 'teams') return output(await request('GET', '/v1/mcp/servers'));
|
|
693
|
+
if (cmd === 'mcp' && sub === 'policies') return output(await request('GET', '/v1/mcp/policies'));
|
|
694
|
+
|
|
695
|
+
if (cmd === 'keys') return output(await request('GET', '/v1/keys'));
|
|
696
|
+
if (cmd === 'models') return output(await request('GET', '/v1/models'));
|
|
697
|
+
|
|
698
|
+
// Cost tracking
|
|
699
|
+
if (cmd === 'dashboard') {
|
|
700
|
+
const { CostTracker } = require('../kasbah-sdk/src/cost-tracker');
|
|
701
|
+
const tracker = new CostTracker();
|
|
702
|
+
|
|
703
|
+
if (sub === 'daily' || !sub) {
|
|
704
|
+
const report = tracker.getDailyReport();
|
|
705
|
+
return output(report);
|
|
706
|
+
} else if (sub === 'monthly') {
|
|
707
|
+
const report = tracker.getMonthlyReport();
|
|
708
|
+
return output(report);
|
|
709
|
+
} else if (sub === 'session') {
|
|
710
|
+
const report = tracker.getSessionReport();
|
|
711
|
+
return output(report);
|
|
712
|
+
} else if (sub === 'savings') {
|
|
713
|
+
const savings = tracker.calculateRoutingSavings();
|
|
714
|
+
return output(savings);
|
|
715
|
+
}
|
|
716
|
+
console.error('Usage: kasbah dashboard [daily|monthly|session|savings]');
|
|
717
|
+
process.exit(2);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (cmd === 'status') {
|
|
721
|
+
const { CostTracker } = require('../kasbah-sdk/src/cost-tracker');
|
|
722
|
+
const tracker = new CostTracker();
|
|
723
|
+
const stats = tracker.getAllTimeStats();
|
|
724
|
+
const today = tracker.getDailyReport();
|
|
725
|
+
|
|
726
|
+
const status = {
|
|
727
|
+
allTime: stats,
|
|
728
|
+
today: {
|
|
729
|
+
cost: today.totalCost,
|
|
730
|
+
calls: today.totalCalls,
|
|
731
|
+
tokens: today.totalTokens
|
|
732
|
+
},
|
|
733
|
+
savings: tracker.calculateRoutingSavings()
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
if (EMIT_JSON) return output(status);
|
|
737
|
+
|
|
738
|
+
console.log(c('bold', '\nKasbah Status'));
|
|
739
|
+
console.log(c('dim', '═'.repeat(50)));
|
|
740
|
+
console.log('All-time stats:');
|
|
741
|
+
console.log(` Calls: ${stats.totalCalls}`);
|
|
742
|
+
console.log(` Tokens: ${stats.totalTokens}`);
|
|
743
|
+
console.log(` Cost: $${stats.totalCost.toFixed(2)}`);
|
|
744
|
+
console.log(` Avg/day: $${stats.avgDailyCost}`);
|
|
745
|
+
console.log('');
|
|
746
|
+
console.log('Today:');
|
|
747
|
+
console.log(` Cost: $${today.totalCost.toFixed(4)}`);
|
|
748
|
+
console.log(` Calls: ${today.totalCalls}`);
|
|
749
|
+
console.log(` Tokens: ${today.totalTokens}`);
|
|
750
|
+
console.log('');
|
|
751
|
+
console.log('Routing savings (vs Opus):');
|
|
752
|
+
for (const [model, saving] of Object.entries(status.savings || {})) {
|
|
753
|
+
console.log(` ${model}: ${saving.pctCheaper}% cheaper`);
|
|
754
|
+
}
|
|
755
|
+
console.log('');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
console.error('Unknown command: ' + cmd);
|
|
759
|
+
usage();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
main().catch((e) => {
|
|
763
|
+
if (EMIT_JSON) console.log(JSON.stringify({ error: e.message, status: e.status, body: e.body }, null, 2));
|
|
764
|
+
else console.error(c('red', '✕ ') + e.message + (e.status ? c('dim', ' (HTTP ' + e.status + ')') : ''));
|
|
765
|
+
process.exit(1);
|
|
766
|
+
});
|