@hasna/terminal 1.2.1 → 1.3.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 +7 -3
- package/dist/cli.js +17 -9
- package/dist/output-processor.js +2 -2
- package/package.json +1 -1
- package/src/ai.ts +7 -3
- package/src/cli.tsx +15 -9
- package/src/output-processor.ts +2 -2
package/dist/ai.js
CHANGED
|
@@ -43,6 +43,8 @@ const IRREVERSIBLE_PATTERNS = [
|
|
|
43
43
|
// Code modification / package installation (security risk)
|
|
44
44
|
/\bnpx\s+\S+/, /\bnpm\s+install\b/, /\bbun\s+add\b/, /\bpip\s+install\b/,
|
|
45
45
|
/\bcodemod\b/, /\bsed\s+-i\b/, /\bawk\s.*>/, /\bperl\s+-[pi]\b/,
|
|
46
|
+
// File creation/modification (READ-ONLY terminal)
|
|
47
|
+
/\btouch\b/, /\bmkdir\b/, /\becho\s.*>/, /\btee\b/, /\bcp\b/, /\bmv\b/,
|
|
46
48
|
];
|
|
47
49
|
export function isIrreversible(command) {
|
|
48
50
|
return IRREVERSIBLE_PATTERNS.some((r) => r.test(command));
|
|
@@ -109,12 +111,12 @@ function detectProjectContext() {
|
|
|
109
111
|
// Top-level dirs
|
|
110
112
|
const topLevel = execSync("ls -1", { cwd, encoding: "utf8", timeout: 2000 }).trim();
|
|
111
113
|
parts.push(`Top-level: ${topLevel.split("\n").join(", ")}`);
|
|
112
|
-
// src/ structure
|
|
114
|
+
// src/ structure — include FILES so AI knows exact filenames + extensions
|
|
113
115
|
for (const srcDir of ["src", "lib", "app"]) {
|
|
114
116
|
if (existsSync(join(cwd, srcDir))) {
|
|
115
|
-
const tree = execSync(`find ${srcDir} -maxdepth
|
|
117
|
+
const tree = execSync(`find ${srcDir} -maxdepth 3 -not -path '*/node_modules/*' -not -path '*/dist/*' -not -name '*.test.*' -not -name '*.spec.*' 2>/dev/null | sort | head -60`, { cwd, encoding: "utf8", timeout: 2000 }).trim();
|
|
116
118
|
if (tree)
|
|
117
|
-
parts.push(`
|
|
119
|
+
parts.push(`Files in ${srcDir}/:\n${tree}`);
|
|
118
120
|
break;
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -166,6 +168,8 @@ RULES:
|
|
|
166
168
|
- NEVER install packages (npx, npm install, pip install, brew install). This is a READ-ONLY terminal.
|
|
167
169
|
- NEVER modify source code (sed -i, codemod, awk with redirect). Only observe, never change.
|
|
168
170
|
- Search src/ directory, NOT dist/ or node_modules/ for code queries.
|
|
171
|
+
- For compound questions ("how many X and are they Y"), prefer ONE command that captures all info. Do NOT chain with &&.
|
|
172
|
+
- Use exact file paths from the project context below. Do NOT guess paths.
|
|
169
173
|
cwd: ${process.cwd()}
|
|
170
174
|
shell: zsh / macOS${projectContext}${restrictionBlock}${contextBlock}`;
|
|
171
175
|
}
|
package/dist/cli.js
CHANGED
|
@@ -471,17 +471,20 @@ else if (args.length > 0) {
|
|
|
471
471
|
console.log(JSON.stringify(lazy, null, 2));
|
|
472
472
|
process.exit(0);
|
|
473
473
|
}
|
|
474
|
-
// AI
|
|
475
|
-
|
|
474
|
+
// AI answer framing — ALWAYS use in NL mode (even for small output)
|
|
475
|
+
// The AI needs to ANSWER the question, not just pass through data
|
|
476
|
+
if (clean.length > 10) {
|
|
476
477
|
const processed = await processOutput(actualCmd, clean, prompt);
|
|
477
|
-
if (processed.aiProcessed
|
|
478
|
-
|
|
478
|
+
if (processed.aiProcessed) {
|
|
479
|
+
if (processed.tokensSaved > 0)
|
|
480
|
+
recordSaving("compressed", processed.tokensSaved);
|
|
479
481
|
console.log(processed.summary);
|
|
480
|
-
|
|
482
|
+
if (processed.tokensSaved > 10)
|
|
483
|
+
console.error(`[open-terminal] ${rawTokens} → ${rawTokens - processed.tokensSaved} tokens (saved ${processed.tokensSaved})`);
|
|
481
484
|
process.exit(0);
|
|
482
485
|
}
|
|
483
486
|
}
|
|
484
|
-
//
|
|
487
|
+
// Fallback: AI unavailable — pass through clean
|
|
485
488
|
console.log(clean);
|
|
486
489
|
const saved = rawTokens - estimateTokens(clean);
|
|
487
490
|
if (saved > 10) {
|
|
@@ -490,9 +493,14 @@ else if (args.length > 0) {
|
|
|
490
493
|
}
|
|
491
494
|
}
|
|
492
495
|
catch (e) {
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
+
// Empty result (grep exit 1 = no matches) — not a real error
|
|
497
|
+
const errStdout = e.stdout?.toString() ?? "";
|
|
498
|
+
const errStderr = e.stderr?.toString() ?? "";
|
|
499
|
+
if (e.status === 1 && !errStdout.trim() && !errStderr.trim()) {
|
|
500
|
+
console.log(`No results found for: ${prompt}`);
|
|
501
|
+
process.exit(0);
|
|
502
|
+
}
|
|
503
|
+
const combined = errStderr && errStdout.includes(errStderr.trim()) ? errStdout : errStdout + errStderr;
|
|
496
504
|
console.log(stripNoise(stripAnsi(combined)).cleaned);
|
|
497
505
|
process.exit(e.status ?? 1);
|
|
498
506
|
}
|
package/dist/output-processor.js
CHANGED
|
@@ -22,8 +22,8 @@ RULES:
|
|
|
22
22
|
*/
|
|
23
23
|
export async function processOutput(command, output, originalPrompt) {
|
|
24
24
|
const lines = output.split("\n");
|
|
25
|
-
// Short output —
|
|
26
|
-
if (lines.length <= MIN_LINES_TO_PROCESS) {
|
|
25
|
+
// Short output — skip AI UNLESS we have an original prompt (NL mode needs answer framing)
|
|
26
|
+
if (lines.length <= MIN_LINES_TO_PROCESS && !originalPrompt) {
|
|
27
27
|
return {
|
|
28
28
|
summary: output,
|
|
29
29
|
full: output,
|
package/package.json
CHANGED
package/src/ai.ts
CHANGED
|
@@ -51,6 +51,8 @@ const IRREVERSIBLE_PATTERNS = [
|
|
|
51
51
|
// Code modification / package installation (security risk)
|
|
52
52
|
/\bnpx\s+\S+/, /\bnpm\s+install\b/, /\bbun\s+add\b/, /\bpip\s+install\b/,
|
|
53
53
|
/\bcodemod\b/, /\bsed\s+-i\b/, /\bawk\s.*>/, /\bperl\s+-[pi]\b/,
|
|
54
|
+
// File creation/modification (READ-ONLY terminal)
|
|
55
|
+
/\btouch\b/, /\bmkdir\b/, /\becho\s.*>/, /\btee\b/, /\bcp\b/, /\bmv\b/,
|
|
54
56
|
];
|
|
55
57
|
|
|
56
58
|
export function isIrreversible(command: string): boolean {
|
|
@@ -138,14 +140,14 @@ function detectProjectContext(): string {
|
|
|
138
140
|
const topLevel = execSync("ls -1", { cwd, encoding: "utf8", timeout: 2000 }).trim();
|
|
139
141
|
parts.push(`Top-level: ${topLevel.split("\n").join(", ")}`);
|
|
140
142
|
|
|
141
|
-
// src/ structure
|
|
143
|
+
// src/ structure — include FILES so AI knows exact filenames + extensions
|
|
142
144
|
for (const srcDir of ["src", "lib", "app"]) {
|
|
143
145
|
if (existsSync(join(cwd, srcDir))) {
|
|
144
146
|
const tree = execSync(
|
|
145
|
-
`find ${srcDir} -maxdepth
|
|
147
|
+
`find ${srcDir} -maxdepth 3 -not -path '*/node_modules/*' -not -path '*/dist/*' -not -name '*.test.*' -not -name '*.spec.*' 2>/dev/null | sort | head -60`,
|
|
146
148
|
{ cwd, encoding: "utf8", timeout: 2000 }
|
|
147
149
|
).trim();
|
|
148
|
-
if (tree) parts.push(`
|
|
150
|
+
if (tree) parts.push(`Files in ${srcDir}/:\n${tree}`);
|
|
149
151
|
break;
|
|
150
152
|
}
|
|
151
153
|
}
|
|
@@ -201,6 +203,8 @@ RULES:
|
|
|
201
203
|
- NEVER install packages (npx, npm install, pip install, brew install). This is a READ-ONLY terminal.
|
|
202
204
|
- NEVER modify source code (sed -i, codemod, awk with redirect). Only observe, never change.
|
|
203
205
|
- Search src/ directory, NOT dist/ or node_modules/ for code queries.
|
|
206
|
+
- For compound questions ("how many X and are they Y"), prefer ONE command that captures all info. Do NOT chain with &&.
|
|
207
|
+
- Use exact file paths from the project context below. Do NOT guess paths.
|
|
204
208
|
cwd: ${process.cwd()}
|
|
205
209
|
shell: zsh / macOS${projectContext}${restrictionBlock}${contextBlock}`;
|
|
206
210
|
}
|
package/src/cli.tsx
CHANGED
|
@@ -455,25 +455,31 @@ else if (args.length > 0) {
|
|
|
455
455
|
process.exit(0);
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
// AI
|
|
459
|
-
|
|
458
|
+
// AI answer framing — ALWAYS use in NL mode (even for small output)
|
|
459
|
+
// The AI needs to ANSWER the question, not just pass through data
|
|
460
|
+
if (clean.length > 10) {
|
|
460
461
|
const processed = await processOutput(actualCmd, clean, prompt);
|
|
461
|
-
if (processed.aiProcessed
|
|
462
|
-
recordSaving("compressed", processed.tokensSaved);
|
|
462
|
+
if (processed.aiProcessed) {
|
|
463
|
+
if (processed.tokensSaved > 0) recordSaving("compressed", processed.tokensSaved);
|
|
463
464
|
console.log(processed.summary);
|
|
464
|
-
console.error(`[open-terminal] ${rawTokens} → ${rawTokens - processed.tokensSaved} tokens (saved ${processed.tokensSaved})`);
|
|
465
|
+
if (processed.tokensSaved > 10) console.error(`[open-terminal] ${rawTokens} → ${rawTokens - processed.tokensSaved} tokens (saved ${processed.tokensSaved})`);
|
|
465
466
|
process.exit(0);
|
|
466
467
|
}
|
|
467
468
|
}
|
|
468
469
|
|
|
469
|
-
//
|
|
470
|
+
// Fallback: AI unavailable — pass through clean
|
|
470
471
|
console.log(clean);
|
|
471
472
|
const saved = rawTokens - estimateTokens(clean);
|
|
472
473
|
if (saved > 10) { recordSaving("compressed", saved); console.error(`[open-terminal] saved ${saved} tokens`); }
|
|
473
474
|
} catch (e: any) {
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
const
|
|
475
|
+
// Empty result (grep exit 1 = no matches) — not a real error
|
|
476
|
+
const errStdout = e.stdout?.toString() ?? "";
|
|
477
|
+
const errStderr = e.stderr?.toString() ?? "";
|
|
478
|
+
if (e.status === 1 && !errStdout.trim() && !errStderr.trim()) {
|
|
479
|
+
console.log(`No results found for: ${prompt}`);
|
|
480
|
+
process.exit(0);
|
|
481
|
+
}
|
|
482
|
+
const combined = errStderr && errStdout.includes(errStderr.trim()) ? errStdout : errStdout + errStderr;
|
|
477
483
|
console.log(stripNoise(stripAnsi(combined)).cleaned);
|
|
478
484
|
process.exit(e.status ?? 1);
|
|
479
485
|
}
|
package/src/output-processor.ts
CHANGED
|
@@ -52,8 +52,8 @@ export async function processOutput(
|
|
|
52
52
|
): Promise<ProcessedOutput> {
|
|
53
53
|
const lines = output.split("\n");
|
|
54
54
|
|
|
55
|
-
// Short output —
|
|
56
|
-
if (lines.length <= MIN_LINES_TO_PROCESS) {
|
|
55
|
+
// Short output — skip AI UNLESS we have an original prompt (NL mode needs answer framing)
|
|
56
|
+
if (lines.length <= MIN_LINES_TO_PROCESS && !originalPrompt) {
|
|
57
57
|
return {
|
|
58
58
|
summary: output,
|
|
59
59
|
full: output,
|