codehere 0.2.1 → 0.2.2
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/dist/monitoring.js
CHANGED
|
@@ -230,21 +230,11 @@ export function getMetrics() {
|
|
|
230
230
|
* Format health status for display
|
|
231
231
|
*/
|
|
232
232
|
export function formatHealthStatus(health) {
|
|
233
|
-
const statusIcon =
|
|
234
|
-
|
|
235
|
-
degraded: '⚠',
|
|
236
|
-
unhealthy: '✗',
|
|
237
|
-
}[health.status];
|
|
238
|
-
const statusColor = {
|
|
239
|
-
healthy: 'green',
|
|
240
|
-
degraded: 'yellow',
|
|
241
|
-
unhealthy: 'red',
|
|
242
|
-
}[health.status];
|
|
243
|
-
let output = `${statusIcon} System Status: ${health.status.toUpperCase()}\n\n`;
|
|
244
|
-
output += 'Component Checks:\n';
|
|
233
|
+
const statusIcon = health.status === 'healthy' ? '✓' : health.status === 'degraded' ? '!' : '✗';
|
|
234
|
+
let output = ` ${statusIcon} ${health.status.toUpperCase()}\n\n`;
|
|
245
235
|
for (const [component, check] of Object.entries(health.checks)) {
|
|
246
|
-
const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '
|
|
247
|
-
output += ` ${icon} ${component}
|
|
236
|
+
const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '!' : '✗';
|
|
237
|
+
output += ` ${icon} ${component.padEnd(12)} ${check.message}\n`;
|
|
248
238
|
}
|
|
249
239
|
return output;
|
|
250
240
|
}
|
|
@@ -9,14 +9,14 @@ import { encoding_for_model } from 'tiktoken';
|
|
|
9
9
|
// Load .env
|
|
10
10
|
config();
|
|
11
11
|
const enc = encoding_for_model('gpt-3.5-turbo');
|
|
12
|
-
const CHUNK_SIZE =
|
|
12
|
+
const CHUNK_SIZE = 2000; // ~500 tokens. OpenAI handles 8191 tokens per embedding.
|
|
13
13
|
const OVERLAP = 50;
|
|
14
14
|
// Rate limiting state
|
|
15
15
|
let lastApiCallTime = 0;
|
|
16
16
|
let apiCallCount = 0;
|
|
17
17
|
let monthlyApiCallCount = 0;
|
|
18
|
-
const MIN_DELAY_MS =
|
|
19
|
-
const MONTHLY_LIMIT =
|
|
18
|
+
const MIN_DELAY_MS = 200; // 200ms between calls (OpenAI allows 3000 RPM for embeddings)
|
|
19
|
+
const MONTHLY_LIMIT = 50000; // OpenAI production limit is much higher
|
|
20
20
|
/**
|
|
21
21
|
* Rate-limited API call wrapper
|
|
22
22
|
*/
|
|
@@ -42,8 +42,8 @@ async function rateLimitedCall(fn) {
|
|
|
42
42
|
catch (error) {
|
|
43
43
|
// If rate limit error, wait longer and retry once
|
|
44
44
|
if (error instanceof Error && error.message.includes('429')) {
|
|
45
|
-
console.log('Rate limit hit, waiting
|
|
46
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
45
|
+
console.log('Rate limit hit, waiting 5 seconds...');
|
|
46
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
47
47
|
lastApiCallTime = Date.now();
|
|
48
48
|
monthlyApiCallCount++;
|
|
49
49
|
return await fn();
|
|
@@ -363,8 +363,8 @@ function findCodeFiles(dir) {
|
|
|
363
363
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
364
364
|
for (const entry of entries) {
|
|
365
365
|
const fullPath = join(dir, entry.name);
|
|
366
|
-
// Skip
|
|
367
|
-
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') {
|
|
366
|
+
// Skip build artifacts, caches, and hidden dirs
|
|
367
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build' || entry.name === '.codehere-cache' || entry.name === '.codehere') {
|
|
368
368
|
continue;
|
|
369
369
|
}
|
|
370
370
|
if (entry.isDirectory()) {
|
|
@@ -444,6 +444,8 @@ async function embedTextLocal(text, retries = 3) {
|
|
|
444
444
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
445
445
|
try {
|
|
446
446
|
if (process.env.OPENAI_API_KEY) {
|
|
447
|
+
const controller = new AbortController();
|
|
448
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
447
449
|
const response = await fetch('https://api.openai.com/v1/embeddings', {
|
|
448
450
|
method: 'POST',
|
|
449
451
|
headers: {
|
|
@@ -451,7 +453,9 @@ async function embedTextLocal(text, retries = 3) {
|
|
|
451
453
|
'Content-Type': 'application/json',
|
|
452
454
|
},
|
|
453
455
|
body: JSON.stringify({ input: text, model: 'text-embedding-3-small' }),
|
|
456
|
+
signal: controller.signal,
|
|
454
457
|
});
|
|
458
|
+
clearTimeout(timeout);
|
|
455
459
|
if (!response.ok)
|
|
456
460
|
throw new Error(`OpenAI embedding error: ${response.status}`);
|
|
457
461
|
const data = await response.json();
|
|
@@ -239,27 +239,26 @@ export async function executeStatusCommand(options = {}) {
|
|
|
239
239
|
}
|
|
240
240
|
else {
|
|
241
241
|
// Activity table
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
// Pad number BEFORE applying color, so ANSI codes don't break alignment
|
|
243
|
+
const fmtNum = (n, color) => {
|
|
244
|
+
const padded = String(n).padStart(10);
|
|
245
|
+
return color && n > 0 ? color(padded) : colors.dim(padded);
|
|
245
246
|
};
|
|
246
247
|
const labelW = 12;
|
|
247
|
-
const numW = 10;
|
|
248
248
|
const label = (s) => s.padEnd(labelW);
|
|
249
|
-
const
|
|
250
|
-
const header = ` ${label('')}${num('Last 24h')}${num('This week')}${num('All time')}`;
|
|
249
|
+
const header = ` ${label('')}${'Last 24h'.padStart(10)}${'This week'.padStart(10)}${'All time'.padStart(10)}`;
|
|
251
250
|
console.log(colors.dim(header));
|
|
252
|
-
console.log(colors.dim(` ${'-'.repeat(labelW +
|
|
253
|
-
console.log(` ${label('Scans')}${
|
|
254
|
-
console.log(` ${label('Blocked')}${
|
|
251
|
+
console.log(colors.dim(` ${'-'.repeat(labelW + 30)}`));
|
|
252
|
+
console.log(` ${label('Scans')}${fmtNum(s24.total)}${fmtNum(s7d.total)}${fmtNum(sAll.total)}`);
|
|
253
|
+
console.log(` ${label('Blocked')}${fmtNum(s24.blocked, colors.red)}${fmtNum(s7d.blocked, colors.red)}${fmtNum(sAll.blocked, colors.red)}`);
|
|
255
254
|
if (sAll.critical > 0) {
|
|
256
|
-
console.log(` ${label('Critical')}${
|
|
255
|
+
console.log(` ${label('Critical')}${fmtNum(s24.critical, colors.red)}${fmtNum(s7d.critical, colors.red)}${fmtNum(sAll.critical, colors.red)}`);
|
|
257
256
|
}
|
|
258
257
|
newline();
|
|
259
258
|
// Recent blocks
|
|
260
259
|
const recentBlocked = [...entries].reverse().filter(e => e.blocked).slice(0, 5);
|
|
261
260
|
if (recentBlocked.length > 0) {
|
|
262
|
-
console.log(colors.
|
|
261
|
+
console.log(colors.dim(' RECENT BLOCKS'));
|
|
263
262
|
for (const entry of recentBlocked) {
|
|
264
263
|
const tool = colors.cyan(entry.toolName.padEnd(10));
|
|
265
264
|
const time = colors.dim(timeAgo(entry.timestamp).padEnd(8));
|
|
@@ -275,11 +274,12 @@ export async function executeStatusCommand(options = {}) {
|
|
|
275
274
|
}
|
|
276
275
|
}
|
|
277
276
|
// Skills status
|
|
277
|
+
console.log(colors.dim(' ─────'));
|
|
278
278
|
if (hasSkills) {
|
|
279
|
-
console.log(colors.dim('
|
|
279
|
+
console.log(colors.dim(' /codehere:trust /codehere:report /codehere:audit /codehere:hook-status'));
|
|
280
280
|
}
|
|
281
281
|
else {
|
|
282
|
-
console.log(colors.dim('
|
|
282
|
+
console.log(colors.dim(' Run codehere hook install to add slash commands'));
|
|
283
283
|
}
|
|
284
284
|
newline();
|
|
285
285
|
// Multi-agent session analysis
|
|
@@ -206,59 +206,58 @@ export async function executeTrustCommand(target, options = {}) {
|
|
|
206
206
|
newline();
|
|
207
207
|
sectionHeader('codehere Trust Report');
|
|
208
208
|
newline();
|
|
209
|
+
// Score
|
|
209
210
|
console.log(` Score ${scoreBar(overallScore)} ${colors.bold(String(overallScore))}/100 ${gradeColor(grade)}`);
|
|
210
211
|
newline();
|
|
211
|
-
// Findings summary
|
|
212
|
-
const
|
|
213
|
-
criticalCount > 0 ?
|
|
214
|
-
highCount > 0 ?
|
|
215
|
-
mediumCount > 0 ?
|
|
216
|
-
lowCount > 0 ?
|
|
212
|
+
// Findings summary (inline, compact)
|
|
213
|
+
const parts = [
|
|
214
|
+
criticalCount > 0 ? colors.red(`${criticalCount} critical`) : null,
|
|
215
|
+
highCount > 0 ? colors.yellow(`${highCount} high`) : null,
|
|
216
|
+
mediumCount > 0 ? colors.dim(`${mediumCount} medium`) : null,
|
|
217
|
+
lowCount > 0 ? colors.dim(`${lowCount} low`) : null,
|
|
217
218
|
].filter(Boolean);
|
|
218
|
-
if (
|
|
219
|
+
if (parts.length === 0) {
|
|
219
220
|
console.log(` ${colors.green('✓')} ${randomCleanScan()}`);
|
|
220
221
|
}
|
|
221
222
|
else {
|
|
222
|
-
|
|
223
|
+
console.log(` ${parts.join(colors.dim(' · '))}`);
|
|
223
224
|
}
|
|
224
225
|
newline();
|
|
225
226
|
// Per-file results (show worst first)
|
|
226
227
|
if (results.length > 1) {
|
|
227
228
|
const sorted = [...results].sort((a, b) => a.score - b.score);
|
|
228
|
-
console.log(colors.
|
|
229
|
+
console.log(colors.dim(' FILES'));
|
|
229
230
|
for (const r of sorted.slice(0, 10)) {
|
|
230
|
-
const icon = r.score >= 75 ? colors.green('✓') : r.score >= 60 ? colors.yellow('
|
|
231
|
-
console.log(` ${icon} ${gradeColor(r.grade)} ${String(r.score).padStart(3)}/100 ${
|
|
231
|
+
const icon = r.score >= 75 ? colors.green('✓') : r.score >= 60 ? colors.yellow('!') : colors.red('✗');
|
|
232
|
+
console.log(` ${icon} ${gradeColor(r.grade)} ${String(r.score).padStart(3)}/100 ${r.filepath}`);
|
|
232
233
|
}
|
|
233
234
|
if (sorted.length > 10) {
|
|
234
|
-
console.log(colors.dim(`
|
|
235
|
+
console.log(colors.dim(` + ${sorted.length - 10} more files`));
|
|
235
236
|
}
|
|
236
237
|
newline();
|
|
237
238
|
}
|
|
238
|
-
//
|
|
239
|
+
// Findings detail
|
|
239
240
|
if (options.verbose) {
|
|
241
|
+
console.log(colors.dim(' FINDINGS'));
|
|
240
242
|
for (const r of results) {
|
|
241
243
|
if (r.findings.length === 0)
|
|
242
244
|
continue;
|
|
243
|
-
console.log(colors.bold(r.filepath)
|
|
245
|
+
console.log(` ${colors.bold(r.filepath)}`);
|
|
244
246
|
for (const f of r.findings) {
|
|
245
|
-
const sev = f.severity === 'critical' ? colors.red(
|
|
246
|
-
: f.severity === 'high' ? colors.yellow(
|
|
247
|
-
: colors.dim(f.severity);
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
console.log(` ${sev} ${f.category}${cwe}${line} — ${f.description}`);
|
|
247
|
+
const sev = f.severity === 'critical' ? colors.red('crit')
|
|
248
|
+
: f.severity === 'high' ? colors.yellow('high')
|
|
249
|
+
: colors.dim(f.severity.slice(0, 4));
|
|
250
|
+
const ln = f.line ? colors.dim(`L${f.line}`) : '';
|
|
251
|
+
console.log(` ${sev} ${ln.padEnd(6)} ${f.description}`);
|
|
251
252
|
}
|
|
252
|
-
newline();
|
|
253
253
|
}
|
|
254
|
+
newline();
|
|
254
255
|
}
|
|
255
256
|
else if (criticalCount + highCount > 0) {
|
|
256
|
-
|
|
257
|
-
console.log(colors.bold('Top findings:'));
|
|
257
|
+
console.log(colors.dim(' FINDINGS'));
|
|
258
258
|
const allFindings = results.flatMap(r => r.findings
|
|
259
259
|
.filter(f => f.severity === 'critical' || f.severity === 'high')
|
|
260
260
|
.map(f => ({ ...f, filepath: r.filepath })));
|
|
261
|
-
// Show critical first, then high
|
|
262
261
|
const sorted = allFindings.sort((a, b) => {
|
|
263
262
|
if (a.severity === 'critical' && b.severity !== 'critical')
|
|
264
263
|
return -1;
|
|
@@ -267,9 +266,13 @@ export async function executeTrustCommand(target, options = {}) {
|
|
|
267
266
|
return 0;
|
|
268
267
|
});
|
|
269
268
|
for (const f of sorted.slice(0, 5)) {
|
|
270
|
-
const sev = f.severity === 'critical' ? colors.red(
|
|
271
|
-
const
|
|
272
|
-
console.log(` ${sev} ${f.filepath}${
|
|
269
|
+
const sev = f.severity === 'critical' ? colors.red('crit') : colors.yellow('high');
|
|
270
|
+
const ln = f.line ? `:${f.line}` : '';
|
|
271
|
+
console.log(` ${sev} ${f.filepath}${ln}`);
|
|
272
|
+
console.log(` ${colors.dim(f.description)}`);
|
|
273
|
+
}
|
|
274
|
+
if (sorted.length > 5) {
|
|
275
|
+
console.log(colors.dim(` + ${sorted.length - 5} more findings (use --verbose to see all)`));
|
|
273
276
|
}
|
|
274
277
|
newline();
|
|
275
278
|
}
|
|
@@ -277,10 +280,11 @@ export async function executeTrustCommand(target, options = {}) {
|
|
|
277
280
|
const recColor = criticalCount > 0 ? colors.red : highCount > 0 ? colors.yellow : colors.green;
|
|
278
281
|
console.log(recColor(recommendation));
|
|
279
282
|
newline();
|
|
280
|
-
//
|
|
283
|
+
// Tips footer (quiet, separated)
|
|
281
284
|
if (results.length > 0 && !options.ci) {
|
|
282
|
-
console.log(colors.dim('
|
|
283
|
-
console.log(colors.dim('
|
|
285
|
+
console.log(colors.dim(' ─────'));
|
|
286
|
+
console.log(colors.dim(' trust --badge Add a badge to your README'));
|
|
287
|
+
console.log(colors.dim(' trust --ci Exit 1 on critical findings'));
|
|
284
288
|
newline();
|
|
285
289
|
}
|
|
286
290
|
if (ciFailed) {
|
package/dist/session.js
CHANGED
|
@@ -254,6 +254,14 @@ export async function startSession() {
|
|
|
254
254
|
}
|
|
255
255
|
// Enter
|
|
256
256
|
if (key === '\r' || key === '\n') {
|
|
257
|
+
// If palette is visible, complete the selected command first
|
|
258
|
+
if (pLines > 0 && line.startsWith('/') && !line.includes(' ')) {
|
|
259
|
+
const filter = line.slice(1).toLowerCase();
|
|
260
|
+
const filtered = PALETTE_CMDS.filter(c => c.name.startsWith(filter)).slice(0, maxPalette());
|
|
261
|
+
if (filtered[selected]) {
|
|
262
|
+
line = `/${filtered[selected].name}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
257
265
|
stdout.write('\r\x1b[J\n');
|
|
258
266
|
const trimmed = line.trim();
|
|
259
267
|
if (trimmed) {
|
|
@@ -302,29 +310,42 @@ export async function startSession() {
|
|
|
302
310
|
// ── Initial render ──
|
|
303
311
|
render();
|
|
304
312
|
// ── Stdin with escape buffering ──
|
|
313
|
+
// CRITICAL: raw mode can deliver multiple chars in one data event (fast typing, paste).
|
|
314
|
+
// We must process each character individually, except escape sequences which are multi-byte.
|
|
305
315
|
stdin.on('data', (raw) => {
|
|
306
316
|
if (processing)
|
|
307
317
|
return;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
318
|
+
// If the whole chunk is a known escape sequence, handle it directly
|
|
319
|
+
if (raw.startsWith('\x1b') && raw.length >= 2) {
|
|
320
|
+
handle(raw);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Otherwise iterate character by character
|
|
324
|
+
for (let i = 0; i < raw.length; i++) {
|
|
325
|
+
if (processing)
|
|
326
|
+
return; // stop if a command started executing mid-chunk
|
|
327
|
+
const ch = raw[i];
|
|
328
|
+
if (escBuf.length > 0) {
|
|
329
|
+
escBuf += ch;
|
|
330
|
+
if (escTimer)
|
|
331
|
+
clearTimeout(escTimer);
|
|
332
|
+
if (escBuf.length >= 3) {
|
|
333
|
+
const seq = escBuf;
|
|
334
|
+
escBuf = '';
|
|
335
|
+
handle(seq);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
escTimer = setTimeout(() => { const seq = escBuf; escBuf = ''; handle(seq); }, 50);
|
|
339
|
+
}
|
|
340
|
+
continue;
|
|
316
341
|
}
|
|
317
|
-
|
|
342
|
+
if (ch === '\x1b') {
|
|
343
|
+
escBuf = ch;
|
|
318
344
|
escTimer = setTimeout(() => { const seq = escBuf; escBuf = ''; handle(seq); }, 50);
|
|
345
|
+
continue;
|
|
319
346
|
}
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
if (raw === '\x1b') {
|
|
323
|
-
escBuf = raw;
|
|
324
|
-
escTimer = setTimeout(() => { const seq = escBuf; escBuf = ''; handle(seq); }, 50);
|
|
325
|
-
return;
|
|
347
|
+
handle(ch);
|
|
326
348
|
}
|
|
327
|
-
handle(raw);
|
|
328
349
|
});
|
|
329
350
|
}
|
|
330
351
|
// ── Command list (for bare / or help) ──────────────────────────────────────
|
|
@@ -363,13 +384,14 @@ async function executeInput(input, state) {
|
|
|
363
384
|
stdout.write('\x1b[2J\x1b[H');
|
|
364
385
|
return;
|
|
365
386
|
}
|
|
366
|
-
// Bare command names: "
|
|
367
|
-
|
|
387
|
+
// Bare command names: "trust ." or "status" → same as "/trust ." or "/status"
|
|
388
|
+
// Only match unambiguous commands — skip words that are common English ("help", "fix", "run", "plan", "report", "test")
|
|
389
|
+
const BARE_SAFE = ['trust', 'hook', 'status', 'health', 'setup', 'serve', 'models', 'config', 'index', 'explain', 'review', 'cost', 'undo'];
|
|
368
390
|
const lower = input.toLowerCase();
|
|
369
391
|
const firstWord = lower.split(/\s+/)[0];
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
await handleSlash(firstWord,
|
|
392
|
+
const restArgs = input.slice(firstWord.length).trim();
|
|
393
|
+
if (BARE_SAFE.includes(firstWord)) {
|
|
394
|
+
await handleSlash(firstWord, restArgs, state);
|
|
373
395
|
return;
|
|
374
396
|
}
|
|
375
397
|
// Everything else → AI query (the router decides what to do)
|
|
@@ -448,7 +470,13 @@ async function handleSlash(cmd, args, state) {
|
|
|
448
470
|
break;
|
|
449
471
|
}
|
|
450
472
|
case 'test': {
|
|
451
|
-
|
|
473
|
+
if (!args) {
|
|
474
|
+
console.log(colors.dim(' Usage: /test <file>'));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (!(await requireKey()))
|
|
478
|
+
return;
|
|
479
|
+
await handleQuery(`Generate tests for ${args}`, state);
|
|
452
480
|
break;
|
|
453
481
|
}
|
|
454
482
|
case 'docs': {
|
|
@@ -457,7 +485,13 @@ async function handleSlash(cmd, args, state) {
|
|
|
457
485
|
break;
|
|
458
486
|
}
|
|
459
487
|
case 'refactor': {
|
|
460
|
-
|
|
488
|
+
if (!args) {
|
|
489
|
+
console.log(colors.dim(' Usage: /refactor <goal>'));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (!(await requireKey()))
|
|
493
|
+
return;
|
|
494
|
+
await handleQuery('Refactor: ' + args, state);
|
|
461
495
|
break;
|
|
462
496
|
}
|
|
463
497
|
case 'orchestrate': {
|
|
@@ -479,12 +513,25 @@ async function handleSlash(cmd, args, state) {
|
|
|
479
513
|
break;
|
|
480
514
|
}
|
|
481
515
|
case 'git-review': {
|
|
482
|
-
|
|
516
|
+
if (!(await requireKey()))
|
|
517
|
+
return;
|
|
518
|
+
await handleQuery('Review the recent git changes in this project', state);
|
|
483
519
|
break;
|
|
484
520
|
}
|
|
485
|
-
case 'index':
|
|
486
|
-
|
|
521
|
+
case 'index': {
|
|
522
|
+
if (!(await requireKey()))
|
|
523
|
+
return;
|
|
524
|
+
const { indexRepositoryParallel } = await import('./embed.js');
|
|
525
|
+
const spinner = startSpinner('Indexing codebase...');
|
|
526
|
+
try {
|
|
527
|
+
await indexRepositoryParallel(process.cwd(), { useCache: true, maxConcurrency: 5 });
|
|
528
|
+
spinner.succeed(colors.green('Index built'));
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
spinner.fail(error('Index failed: ' + (err instanceof Error ? err.message : String(err))));
|
|
532
|
+
}
|
|
487
533
|
break;
|
|
534
|
+
}
|
|
488
535
|
case 'health': {
|
|
489
536
|
const { performHealthCheck, formatHealthStatus } = await import('./monitoring.js');
|
|
490
537
|
console.log(formatHealthStatus(performHealthCheck()));
|
|
@@ -496,7 +543,8 @@ async function handleSlash(cmd, args, state) {
|
|
|
496
543
|
break;
|
|
497
544
|
}
|
|
498
545
|
case 'serve': {
|
|
499
|
-
console.log(colors.dim('
|
|
546
|
+
console.log(colors.dim(' MCP server must run in its own process:'));
|
|
547
|
+
console.log(colors.cyan(' codehere serve'));
|
|
500
548
|
break;
|
|
501
549
|
}
|
|
502
550
|
case 'config': {
|
|
@@ -516,7 +564,8 @@ async function handleSlash(cmd, args, state) {
|
|
|
516
564
|
break;
|
|
517
565
|
}
|
|
518
566
|
case 'undo': {
|
|
519
|
-
|
|
567
|
+
const { executeUndoCommand } = await import('./presentation/cli/commands/undo-command.js');
|
|
568
|
+
await executeUndoCommand(args ? 'undo' : 'list', args || undefined);
|
|
520
569
|
break;
|
|
521
570
|
}
|
|
522
571
|
case 'clear':
|
|
@@ -584,6 +633,9 @@ async function handleQuery(query, state) {
|
|
|
584
633
|
else if (result.error) {
|
|
585
634
|
console.log(error(' ' + result.error));
|
|
586
635
|
}
|
|
636
|
+
else {
|
|
637
|
+
console.log(colors.dim(' No response. Try rephrasing, or run "codehere index" first.'));
|
|
638
|
+
}
|
|
587
639
|
state.history.push({ command: query, response: result.message || result.error || '', timestamp: new Date().toISOString() });
|
|
588
640
|
const entities = conversationContextManager.extractEntities(query);
|
|
589
641
|
conversationContextManager.addTurn(sessionId, query, result.message || '', entities, result.intent);
|
package/dist/ui.js
CHANGED
|
@@ -19,7 +19,12 @@ export function isMinimalStyle() {
|
|
|
19
19
|
}
|
|
20
20
|
// Minimal header for commands (used in minimal style)
|
|
21
21
|
export function printMinimalHeader(command, query) {
|
|
22
|
-
|
|
22
|
+
if (query) {
|
|
23
|
+
console.log(colors.cyan(`codehere ${command}`) + colors.dim(' ▸ ') + query);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log(colors.cyan(`codehere ${command}`));
|
|
27
|
+
}
|
|
23
28
|
console.log();
|
|
24
29
|
}
|
|
25
30
|
// Minimal safety note (used at end of AI responses)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codehere",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Know what your AI agent actually did — scans every AI write before it touches your codebase",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
"start:api": "npm run build && node dist/api/server.js",
|
|
20
20
|
"dev": "tsx src/index.ts",
|
|
21
21
|
"dev:api": "tsx api/server.ts",
|
|
22
|
-
"test": "npm run build &&
|
|
23
|
-
"test:
|
|
24
|
-
"test:execution": "npm run build && tsx ./test/run-comprehensive-test-execution.ts",
|
|
25
|
-
"test:suite": "npm run build && tsx test/test-suite.ts",
|
|
26
|
-
"test:integration": "npm run build && tsx test/integration/run-integration-tests.ts",
|
|
22
|
+
"test": "npm run build && vitest run",
|
|
23
|
+
"test:unit": "npm run build && tsx test/unit/run-unit-tests.ts",
|
|
27
24
|
"test:cli": "npm run build && tsx test/integration/run-cli-tests.ts",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "eslint src/ --ext .ts",
|
|
27
|
+
"format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
|
|
28
|
+
"format:check": "prettier --check 'src/**/*.ts' 'test/**/*.ts'",
|
|
29
|
+
"test:integration": "npm run build && tsx test/integration/run-integration-tests.ts",
|
|
28
30
|
"test:scenarios": "npm run build && tsx test/scenarios/real-world-scenarios.ts",
|
|
29
31
|
"test:security": "npm run build && tsx test/security/sast-scanner.test.ts",
|
|
30
32
|
"test:security-gate": "npm run build && tsx test/security/security-gate.test.ts",
|
|
@@ -36,7 +38,6 @@
|
|
|
36
38
|
"test:compound-instruction": "npm run build && tsx test/compound-instruction/compound-instruction.test.ts",
|
|
37
39
|
"check-api": "npm run build && tsx -e \"import('./dist/utils/check-api-status.js').then(m => m.checkAPIStatus().then(s => { console.log(s.canProceed ? '✅ ' + s.message : '❌ ' + s.message); process.exit(s.canProceed ? 0 : 1); }))\"",
|
|
38
40
|
"test:all": "npm run build && npm run test:integration && npm run test:scenarios && npm run test:security && npm run test:security-gate && npm run test:git-workflow && npm run test:memory && npm run test:performance",
|
|
39
|
-
"test:unit": "npm run build && tsx test/unit/run-unit-tests.ts",
|
|
40
41
|
"test:coverage": "c8 npm run test:unit && c8 report --reporter=text --reporter=html",
|
|
41
42
|
"test:rerank": "tsx test/rerank-integration.test.ts",
|
|
42
43
|
"eval:command-r": "tsx test/command-a-evaluation.test.ts",
|
|
@@ -97,6 +98,7 @@
|
|
|
97
98
|
"wrap-ansi": "^9.0.2"
|
|
98
99
|
},
|
|
99
100
|
"devDependencies": {
|
|
101
|
+
"@eslint/js": "^10.0.1",
|
|
100
102
|
"@types/cli-progress": "^3.11.5",
|
|
101
103
|
"@types/cors": "^2.8.19",
|
|
102
104
|
"@types/diff": "^5.0.8",
|
|
@@ -105,8 +107,12 @@
|
|
|
105
107
|
"@types/sql.js": "^1.4.8",
|
|
106
108
|
"@types/supertest": "^6.0.3",
|
|
107
109
|
"c8": "^10.1.3",
|
|
110
|
+
"eslint": "^10.2.0",
|
|
111
|
+
"prettier": "^3.8.3",
|
|
108
112
|
"supertest": "^7.1.4",
|
|
109
113
|
"tsx": "^4.21.0",
|
|
110
|
-
"typescript": "^5.9.3"
|
|
114
|
+
"typescript": "^5.9.3",
|
|
115
|
+
"typescript-eslint": "^8.58.2",
|
|
116
|
+
"vitest": "^4.1.4"
|
|
111
117
|
}
|
|
112
118
|
}
|