@hasna/terminal 1.1.1 → 1.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/dist/ai.js +4 -0
- package/dist/cli.js +9 -2
- package/dist/lazy-executor.js +1 -1
- package/dist/output-processor.js +11 -15
- package/dist/test-watchlist.js +7 -4
- package/package.json +1 -1
- package/src/ai.ts +4 -0
- package/src/cli.tsx +10 -2
- package/src/lazy-executor.ts +1 -1
- package/src/output-processor.ts +11 -14
- package/src/test-watchlist.ts +6 -4
package/dist/ai.js
CHANGED
|
@@ -36,6 +36,10 @@ const IRREVERSIBLE_PATTERNS = [
|
|
|
36
36
|
/\brm\s/, /\brmdir\b/, /\btruncate\b/, /\bdrop\s+table\b/i,
|
|
37
37
|
/\bdelete\s+from\b/i, /\bmv\b.*\/dev\/null/, /\b>\s*[^>]/,
|
|
38
38
|
/\bdd\b/, /\bmkfs\b/, /\bformat\b/, /\bshred\b/,
|
|
39
|
+
// Process/service killing
|
|
40
|
+
/\bkill\b/, /\bkillall\b/, /\bpkill\b/,
|
|
41
|
+
// Git push/force operations
|
|
42
|
+
/\bgit\s+push\b/, /\bgit\s+reset\s+--hard\b/, /\bgit\s+force\b/,
|
|
39
43
|
];
|
|
40
44
|
export function isIrreversible(command) {
|
|
41
45
|
return IRREVERSIBLE_PATTERNS.some((r) => r.test(command));
|
package/dist/cli.js
CHANGED
|
@@ -430,6 +430,13 @@ else if (args.length > 0) {
|
|
|
430
430
|
console.error(`blocked: ${blocked}`);
|
|
431
431
|
process.exit(1);
|
|
432
432
|
}
|
|
433
|
+
// Safety: warn about irreversible commands (kill, push, rm, etc.)
|
|
434
|
+
if (isIrreversible(command)) {
|
|
435
|
+
console.error(`⚠ IRREVERSIBLE: $ ${command}`);
|
|
436
|
+
console.error(` This command may kill processes, push code, or delete data.`);
|
|
437
|
+
console.error(` Run with terminal exec "${command}" to bypass, or use the TUI for confirmation.`);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
433
440
|
// Show what we're running
|
|
434
441
|
console.error(`$ ${command}`);
|
|
435
442
|
// Step 2: Rewrite for optimization
|
|
@@ -450,7 +457,7 @@ else if (args.length > 0) {
|
|
|
450
457
|
const rawTokens = estimateTokens(raw);
|
|
451
458
|
recordUsage(rawTokens);
|
|
452
459
|
// Test output detection
|
|
453
|
-
if (isTestOutput(clean)) {
|
|
460
|
+
if (isTestOutput(clean, actualCmd)) {
|
|
454
461
|
const result = trackTests(process.cwd(), clean);
|
|
455
462
|
console.log(formatWatchResult(result));
|
|
456
463
|
process.exit(0);
|
|
@@ -466,7 +473,7 @@ else if (args.length > 0) {
|
|
|
466
473
|
}
|
|
467
474
|
// AI summary for medium-large output
|
|
468
475
|
if (shouldProcess(clean)) {
|
|
469
|
-
const processed = await processOutput(actualCmd, clean);
|
|
476
|
+
const processed = await processOutput(actualCmd, clean, prompt);
|
|
470
477
|
if (processed.aiProcessed && processed.tokensSaved > 30) {
|
|
471
478
|
recordSaving("compressed", processed.tokensSaved);
|
|
472
479
|
console.log(processed.summary);
|
package/dist/lazy-executor.js
CHANGED
|
@@ -39,7 +39,7 @@ export function toLazy(output, command) {
|
|
|
39
39
|
count: lines.length,
|
|
40
40
|
sample,
|
|
41
41
|
categories: Object.keys(categories).length > 1 ? categories : undefined,
|
|
42
|
-
hint: `${lines.length} results. Showing first 20. Use
|
|
42
|
+
hint: `${lines.length} results. Showing first 20. Use a more specific query to narrow results.`,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
/** Get a slice of output */
|
package/dist/output-processor.js
CHANGED
|
@@ -5,26 +5,22 @@ import { estimateTokens } from "./parsers/index.js";
|
|
|
5
5
|
import { recordSaving } from "./economy.js";
|
|
6
6
|
const MIN_LINES_TO_PROCESS = 15;
|
|
7
7
|
const MAX_OUTPUT_FOR_AI = 8000; // chars to send to AI (truncate if longer)
|
|
8
|
-
const SUMMARIZE_PROMPT = `You are an
|
|
8
|
+
const SUMMARIZE_PROMPT = `You are an intelligent terminal assistant. Given a user's original question and the command output, ANSWER THE QUESTION directly.
|
|
9
9
|
|
|
10
10
|
RULES:
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- For ANY output: keep errors/failures/warnings, drop verbose/repetitive/progress lines
|
|
20
|
-
- Use symbols: ✓ for success, ✗ for failure, ⚠ for warnings
|
|
21
|
-
- Maximum 8 lines in your summary
|
|
22
|
-
- If there are errors, ALWAYS include them verbatim`;
|
|
11
|
+
- If the user asked a YES/NO question, start with Yes or No, then explain briefly
|
|
12
|
+
- If the user asked "how many", give the number first, then context
|
|
13
|
+
- If the user asked "show me X", show only X, not everything
|
|
14
|
+
- ANSWER the question using the data — don't just summarize the raw output
|
|
15
|
+
- Use symbols: ✓ for success/yes, ✗ for failure/no, ⚠ for warnings
|
|
16
|
+
- Maximum 8 lines
|
|
17
|
+
- Keep errors/failures verbatim
|
|
18
|
+
- Be direct and concise — the user wants an ANSWER, not a data dump`;
|
|
23
19
|
/**
|
|
24
20
|
* Process command output through AI summarization.
|
|
25
21
|
* Cheap AI call (~100 tokens) saves 1000+ tokens downstream.
|
|
26
22
|
*/
|
|
27
|
-
export async function processOutput(command, output) {
|
|
23
|
+
export async function processOutput(command, output, originalPrompt) {
|
|
28
24
|
const lines = output.split("\n");
|
|
29
25
|
// Short output — pass through, no AI needed
|
|
30
26
|
if (lines.length <= MIN_LINES_TO_PROCESS) {
|
|
@@ -50,7 +46,7 @@ export async function processOutput(command, output) {
|
|
|
50
46
|
}
|
|
51
47
|
try {
|
|
52
48
|
const provider = getProvider();
|
|
53
|
-
const summary = await provider.complete(`Command: ${command}\nOutput (${lines.length} lines):\n${toSummarize}`, {
|
|
49
|
+
const summary = await provider.complete(`${originalPrompt ? `User asked: ${originalPrompt}\n` : ""}Command: ${command}\nOutput (${lines.length} lines):\n${toSummarize}`, {
|
|
54
50
|
system: SUMMARIZE_PROMPT,
|
|
55
51
|
maxTokens: 300,
|
|
56
52
|
});
|
package/dist/test-watchlist.js
CHANGED
|
@@ -37,10 +37,13 @@ function extractTests(output) {
|
|
|
37
37
|
return tests;
|
|
38
38
|
}
|
|
39
39
|
/** Detect if output looks like test runner output */
|
|
40
|
-
export function isTestOutput(output) {
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
export function isTestOutput(output, command) {
|
|
41
|
+
// If the command is explicitly a test command, trust it
|
|
42
|
+
if (command && /\b(bun\s+test|npm\s+test|jest|vitest|pytest|cargo\s+test|go\s+test)\b/.test(command))
|
|
43
|
+
return true;
|
|
44
|
+
// Otherwise require BOTH a summary line AND a test runner marker in the output
|
|
45
|
+
const summaryLine = /(?:\d+\s+pass|\d+\s+fail|Tests?:\s+\d+|Ran\s+\d+\s+tests?)\s*$/im;
|
|
46
|
+
const testMarkers = /(?:✓|✗|✔|✕|PASS\s+\S+\.test|FAIL\s+\S+\.test|bun test v|jest|vitest|pytest)/;
|
|
44
47
|
return summaryLine.test(output) && testMarkers.test(output);
|
|
45
48
|
}
|
|
46
49
|
/** Track test results and return only changes */
|
package/package.json
CHANGED
package/src/ai.ts
CHANGED
|
@@ -44,6 +44,10 @@ const IRREVERSIBLE_PATTERNS = [
|
|
|
44
44
|
/\brm\s/, /\brmdir\b/, /\btruncate\b/, /\bdrop\s+table\b/i,
|
|
45
45
|
/\bdelete\s+from\b/i, /\bmv\b.*\/dev\/null/, /\b>\s*[^>]/,
|
|
46
46
|
/\bdd\b/, /\bmkfs\b/, /\bformat\b/, /\bshred\b/,
|
|
47
|
+
// Process/service killing
|
|
48
|
+
/\bkill\b/, /\bkillall\b/, /\bpkill\b/,
|
|
49
|
+
// Git push/force operations
|
|
50
|
+
/\bgit\s+push\b/, /\bgit\s+reset\s+--hard\b/, /\bgit\s+force\b/,
|
|
47
51
|
];
|
|
48
52
|
|
|
49
53
|
export function isIrreversible(command: string): boolean {
|
package/src/cli.tsx
CHANGED
|
@@ -410,6 +410,14 @@ else if (args.length > 0) {
|
|
|
410
410
|
const blocked = checkPermissions(command, perms);
|
|
411
411
|
if (blocked) { console.error(`blocked: ${blocked}`); process.exit(1); }
|
|
412
412
|
|
|
413
|
+
// Safety: warn about irreversible commands (kill, push, rm, etc.)
|
|
414
|
+
if (isIrreversible(command)) {
|
|
415
|
+
console.error(`⚠ IRREVERSIBLE: $ ${command}`);
|
|
416
|
+
console.error(` This command may kill processes, push code, or delete data.`);
|
|
417
|
+
console.error(` Run with terminal exec "${command}" to bypass, or use the TUI for confirmation.`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
|
|
413
421
|
// Show what we're running
|
|
414
422
|
console.error(`$ ${command}`);
|
|
415
423
|
|
|
@@ -432,7 +440,7 @@ else if (args.length > 0) {
|
|
|
432
440
|
recordUsage(rawTokens);
|
|
433
441
|
|
|
434
442
|
// Test output detection
|
|
435
|
-
if (isTestOutput(clean)) {
|
|
443
|
+
if (isTestOutput(clean, actualCmd)) {
|
|
436
444
|
const result = trackTests(process.cwd(), clean);
|
|
437
445
|
console.log(formatWatchResult(result));
|
|
438
446
|
process.exit(0);
|
|
@@ -449,7 +457,7 @@ else if (args.length > 0) {
|
|
|
449
457
|
|
|
450
458
|
// AI summary for medium-large output
|
|
451
459
|
if (shouldProcess(clean)) {
|
|
452
|
-
const processed = await processOutput(actualCmd, clean);
|
|
460
|
+
const processed = await processOutput(actualCmd, clean, prompt);
|
|
453
461
|
if (processed.aiProcessed && processed.tokensSaved > 30) {
|
|
454
462
|
recordSaving("compressed", processed.tokensSaved);
|
|
455
463
|
console.log(processed.summary);
|
package/src/lazy-executor.ts
CHANGED
|
@@ -54,7 +54,7 @@ export function toLazy(output: string, command: string): LazyResult {
|
|
|
54
54
|
count: lines.length,
|
|
55
55
|
sample,
|
|
56
56
|
categories: Object.keys(categories).length > 1 ? categories : undefined,
|
|
57
|
-
hint: `${lines.length} results. Showing first 20. Use
|
|
57
|
+
hint: `${lines.length} results. Showing first 20. Use a more specific query to narrow results.`,
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
package/src/output-processor.ts
CHANGED
|
@@ -29,21 +29,17 @@ export interface ProcessedOutput {
|
|
|
29
29
|
const MIN_LINES_TO_PROCESS = 15;
|
|
30
30
|
const MAX_OUTPUT_FOR_AI = 8000; // chars to send to AI (truncate if longer)
|
|
31
31
|
|
|
32
|
-
const SUMMARIZE_PROMPT = `You are an
|
|
32
|
+
const SUMMARIZE_PROMPT = `You are an intelligent terminal assistant. Given a user's original question and the command output, ANSWER THE QUESTION directly.
|
|
33
33
|
|
|
34
34
|
RULES:
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
- For ANY output: keep errors/failures/warnings, drop verbose/repetitive/progress lines
|
|
44
|
-
- Use symbols: ✓ for success, ✗ for failure, ⚠ for warnings
|
|
45
|
-
- Maximum 8 lines in your summary
|
|
46
|
-
- If there are errors, ALWAYS include them verbatim`;
|
|
35
|
+
- If the user asked a YES/NO question, start with Yes or No, then explain briefly
|
|
36
|
+
- If the user asked "how many", give the number first, then context
|
|
37
|
+
- If the user asked "show me X", show only X, not everything
|
|
38
|
+
- ANSWER the question using the data — don't just summarize the raw output
|
|
39
|
+
- Use symbols: ✓ for success/yes, ✗ for failure/no, ⚠ for warnings
|
|
40
|
+
- Maximum 8 lines
|
|
41
|
+
- Keep errors/failures verbatim
|
|
42
|
+
- Be direct and concise — the user wants an ANSWER, not a data dump`;
|
|
47
43
|
|
|
48
44
|
/**
|
|
49
45
|
* Process command output through AI summarization.
|
|
@@ -52,6 +48,7 @@ RULES:
|
|
|
52
48
|
export async function processOutput(
|
|
53
49
|
command: string,
|
|
54
50
|
output: string,
|
|
51
|
+
originalPrompt?: string,
|
|
55
52
|
): Promise<ProcessedOutput> {
|
|
56
53
|
const lines = output.split("\n");
|
|
57
54
|
|
|
@@ -82,7 +79,7 @@ export async function processOutput(
|
|
|
82
79
|
try {
|
|
83
80
|
const provider = getProvider();
|
|
84
81
|
const summary = await provider.complete(
|
|
85
|
-
`Command: ${command}\nOutput (${lines.length} lines):\n${toSummarize}`,
|
|
82
|
+
`${originalPrompt ? `User asked: ${originalPrompt}\n` : ""}Command: ${command}\nOutput (${lines.length} lines):\n${toSummarize}`,
|
|
86
83
|
{
|
|
87
84
|
system: SUMMARIZE_PROMPT,
|
|
88
85
|
maxTokens: 300,
|
package/src/test-watchlist.ts
CHANGED
|
@@ -63,10 +63,12 @@ function extractTests(output: string): TestStatus[] {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/** Detect if output looks like test runner output */
|
|
66
|
-
export function isTestOutput(output: string): boolean {
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
export function isTestOutput(output: string, command?: string): boolean {
|
|
67
|
+
// If the command is explicitly a test command, trust it
|
|
68
|
+
if (command && /\b(bun\s+test|npm\s+test|jest|vitest|pytest|cargo\s+test|go\s+test)\b/.test(command)) return true;
|
|
69
|
+
// Otherwise require BOTH a summary line AND a test runner marker in the output
|
|
70
|
+
const summaryLine = /(?:\d+\s+pass|\d+\s+fail|Tests?:\s+\d+|Ran\s+\d+\s+tests?)\s*$/im;
|
|
71
|
+
const testMarkers = /(?:✓|✗|✔|✕|PASS\s+\S+\.test|FAIL\s+\S+\.test|bun test v|jest|vitest|pytest)/;
|
|
70
72
|
return summaryLine.test(output) && testMarkers.test(output);
|
|
71
73
|
}
|
|
72
74
|
|