@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/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yobekasbah/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kasbah Guard — unified command-line interface. Talks to the local or remote api-server.",
|
|
5
|
+
"bin": { "kasbah": "bin/kasbah.js" },
|
|
6
|
+
"main": "bin/kasbah.js",
|
|
7
|
+
"scripts": { "test": "node bin/kasbah.js --help" },
|
|
8
|
+
"engines": { "node": ">=18" },
|
|
9
|
+
"license": "MIT"
|
|
10
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kasbah Setup Validation
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive test suite:
|
|
8
|
+
* 1. API connectivity (Anthropic, Alibaba)
|
|
9
|
+
* 2. MCP server responsiveness
|
|
10
|
+
* 3. SDK instrumentation working
|
|
11
|
+
* 4. Cost tracking enabled
|
|
12
|
+
* 5. Audit receipt generation
|
|
13
|
+
* 6. All model integration
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* kasbah validate [--verbose] [--models X,Y,Z]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const http = require('http');
|
|
20
|
+
const { CostTracker } = require('../kasbah-sdk/src/cost-tracker');
|
|
21
|
+
|
|
22
|
+
const c = (color, text) => {
|
|
23
|
+
const colors = {
|
|
24
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
25
|
+
dim: '\x1b[2m', reset: '\x1b[0m', bold: '\x1b[1m'
|
|
26
|
+
};
|
|
27
|
+
return (colors[color] || '') + text + colors.reset;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const log = (msg, level = 'info') => {
|
|
31
|
+
const prefix = { ok: '✓', err: '✕', warn: '⚠', info: '→' }[level] || '→';
|
|
32
|
+
const color = level === 'ok' ? 'green' : level === 'err' ? 'red' : level === 'warn' ? 'yellow' : 'cyan';
|
|
33
|
+
console.log(c(color, prefix + ' ') + msg);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
async function testAPIConnectivity() {
|
|
37
|
+
log('Testing API connectivity...');
|
|
38
|
+
const tests = {
|
|
39
|
+
anthropic: !!process.env.ANTHROPIC_API_KEY,
|
|
40
|
+
alibaba: !!process.env.ALIBABA_API_KEY
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (!tests.anthropic && !tests.alibaba) {
|
|
44
|
+
log('No API keys found. Set ANTHROPIC_API_KEY or ALIBABA_API_KEY', 'err');
|
|
45
|
+
return { ok: false };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let ok = true;
|
|
49
|
+
for (const [provider, found] of Object.entries(tests)) {
|
|
50
|
+
if (found) {
|
|
51
|
+
log(`${provider}: configured`, 'ok');
|
|
52
|
+
} else {
|
|
53
|
+
log(`${provider}: not configured`, 'warn');
|
|
54
|
+
ok = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ok, providers: tests };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function testMCPServer() {
|
|
62
|
+
log('Testing MCP server...');
|
|
63
|
+
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const req = http.get('http://127.0.0.1:8788/v1/health', (res) => {
|
|
66
|
+
if (res.statusCode === 200) {
|
|
67
|
+
log('MCP server responsive', 'ok');
|
|
68
|
+
resolve({ ok: true });
|
|
69
|
+
} else {
|
|
70
|
+
log(`MCP server returned ${res.statusCode}`, 'err');
|
|
71
|
+
resolve({ ok: false, status: res.statusCode });
|
|
72
|
+
}
|
|
73
|
+
}).on('error', (e) => {
|
|
74
|
+
log(`MCP server not running: ${e.message}`, 'err');
|
|
75
|
+
resolve({ ok: false, error: e.message });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function testCostTracking() {
|
|
81
|
+
log('Testing cost tracking...');
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const tracker = new CostTracker();
|
|
85
|
+
|
|
86
|
+
// Record a test call
|
|
87
|
+
const testCall = tracker.recordCall(
|
|
88
|
+
'claude-haiku',
|
|
89
|
+
100, // input tokens
|
|
90
|
+
50, // output tokens
|
|
91
|
+
250, // latency ms
|
|
92
|
+
{ test: true }
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (testCall) {
|
|
96
|
+
log(`Cost tracking: $${testCall.cost.toFixed(6)} recorded`, 'ok');
|
|
97
|
+
const report = tracker.getDailyReport();
|
|
98
|
+
log(`Today's cost: $${report.totalCost.toFixed(4)}`, 'ok');
|
|
99
|
+
return { ok: true, cost: testCall.cost };
|
|
100
|
+
} else {
|
|
101
|
+
log('Cost tracking failed to record', 'err');
|
|
102
|
+
return { ok: false };
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
log(`Cost tracking error: ${e.message}`, 'err');
|
|
106
|
+
return { ok: false, error: e.message };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function testAuditReceipts() {
|
|
111
|
+
log('Testing audit receipt generation...');
|
|
112
|
+
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
const payload = JSON.stringify({
|
|
115
|
+
policyDecision: 'allow',
|
|
116
|
+
sourceDomain: 'claude.ai',
|
|
117
|
+
riskScore: 0
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const req = http.request({
|
|
121
|
+
hostname: '127.0.0.1',
|
|
122
|
+
port: 8788,
|
|
123
|
+
path: '/v1/audit/receipt',
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': payload.length }
|
|
126
|
+
}, (res) => {
|
|
127
|
+
let data = '';
|
|
128
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
129
|
+
res.on('end', () => {
|
|
130
|
+
if (res.statusCode === 200) {
|
|
131
|
+
try {
|
|
132
|
+
const receipt = JSON.parse(data);
|
|
133
|
+
if (receipt.receipt || receipt.kasbah_receipt) {
|
|
134
|
+
log('Audit receipt generated', 'ok');
|
|
135
|
+
resolve({ ok: true, receipt });
|
|
136
|
+
} else {
|
|
137
|
+
log('Receipt format unexpected', 'warn');
|
|
138
|
+
resolve({ ok: true, receipt, partial: true });
|
|
139
|
+
}
|
|
140
|
+
} catch (e) {
|
|
141
|
+
log(`Receipt parsing failed: ${e.message}`, 'err');
|
|
142
|
+
resolve({ ok: false });
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
log(`Receipt endpoint returned ${res.statusCode}`, 'err');
|
|
146
|
+
resolve({ ok: false, status: res.statusCode });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}).on('error', (e) => {
|
|
150
|
+
log(`Receipt generation failed: ${e.message}`, 'err');
|
|
151
|
+
resolve({ ok: false, error: e.message });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
req.write(payload);
|
|
155
|
+
req.end();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function testMultiModelSupport() {
|
|
160
|
+
log('Testing multi-model support...');
|
|
161
|
+
|
|
162
|
+
const { PRICING } = require('../kasbah-sdk/src/cost-tracker');
|
|
163
|
+
const models = Object.keys(PRICING);
|
|
164
|
+
|
|
165
|
+
const anthropic = models.filter(m => PRICING[m].provider === 'anthropic');
|
|
166
|
+
const alibaba = models.filter(m => PRICING[m].provider === 'alibaba');
|
|
167
|
+
|
|
168
|
+
console.log(c('cyan', ' Anthropic models:'));
|
|
169
|
+
for (const m of anthropic) {
|
|
170
|
+
const p = PRICING[m];
|
|
171
|
+
log(` ${m}: $${(p.output * 1_000_000).toFixed(2)}/1M output tokens`, 'ok');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(c('cyan', ' Alibaba models:'));
|
|
175
|
+
for (const m of alibaba) {
|
|
176
|
+
const p = PRICING[m];
|
|
177
|
+
log(` ${m}: $${(p.output * 1_000_000).toFixed(2)}/1M output tokens`, 'ok');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { ok: true, models: { anthropic, alibaba } };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function main() {
|
|
184
|
+
const argv = process.argv.slice(2);
|
|
185
|
+
const verbose = argv.includes('--verbose');
|
|
186
|
+
|
|
187
|
+
console.log(c('bold', '\nKasbah Setup Validation\n'));
|
|
188
|
+
|
|
189
|
+
const results = {
|
|
190
|
+
timestamp: new Date().toISOString(),
|
|
191
|
+
tests: {}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Run all tests
|
|
195
|
+
results.tests.apiConnectivity = await testAPIConnectivity();
|
|
196
|
+
results.tests.mcpServer = await testMCPServer();
|
|
197
|
+
results.tests.costTracking = await testCostTracking();
|
|
198
|
+
results.tests.auditReceipts = await testAuditReceipts();
|
|
199
|
+
results.tests.multiModel = await testMultiModelSupport();
|
|
200
|
+
|
|
201
|
+
// Summary
|
|
202
|
+
const allOk = Object.values(results.tests).every(t => t.ok);
|
|
203
|
+
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(c('dim', '═'.repeat(50)));
|
|
206
|
+
if (allOk) {
|
|
207
|
+
log('All tests passed! ✓', 'ok');
|
|
208
|
+
console.log('\nNext steps:');
|
|
209
|
+
console.log(' 1. kasbah status — view cost dashboard');
|
|
210
|
+
console.log(' 2. kasbah dashboard daily — today\'s breakdown');
|
|
211
|
+
console.log(' 3. kasbah dashboard savings — routing recommendations\n');
|
|
212
|
+
} else {
|
|
213
|
+
log('Some tests failed. See above for details.', 'err');
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (verbose) {
|
|
218
|
+
console.log('\nDetailed results:');
|
|
219
|
+
console.log(JSON.stringify(results, null, 2));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (require.main === module) {
|
|
224
|
+
main().catch((e) => {
|
|
225
|
+
console.error('Validation failed:', e.message);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { testAPIConnectivity, testMCPServer, testCostTracking };
|