@hasna/terminal 1.3.5 → 1.3.7
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 +13 -9
- package/dist/cli.js +17 -5
- package/package.json +1 -1
- package/src/ai.ts +11 -8
- package/src/cli.tsx +16 -5
package/dist/ai.js
CHANGED
|
@@ -211,23 +211,27 @@ export async function translateToCommand(nl, perms, sessionEntries, onToken) {
|
|
|
211
211
|
let cleaned = text.trim();
|
|
212
212
|
// Remove ALL markdown code blocks and their content markers
|
|
213
213
|
cleaned = cleaned.replace(/```(?:bash|sh|shell)?\n?/g, "").replace(/```/g, "");
|
|
214
|
-
// Split into lines and find the FIRST one that looks like a
|
|
214
|
+
// Split into lines and find the FIRST one that looks like a SHELL COMMAND
|
|
215
215
|
const lines = cleaned.split("\n");
|
|
216
216
|
let command = "";
|
|
217
217
|
for (const line of lines) {
|
|
218
218
|
const t = line.trim();
|
|
219
219
|
if (!t)
|
|
220
220
|
continue;
|
|
221
|
-
// Skip
|
|
222
|
-
if (/^(Based on|I |This |The |Let me|Here|Note:|Since|Looking|To |However|BLOCKED:|If |You |We |For )/.test(t))
|
|
221
|
+
// Skip lines that are clearly English prose, not commands
|
|
222
|
+
if (/^(Based on|I |This |The |Let me|Here|Note:|Since|Looking|To |However|BLOCKED:|If |You |We |For |It |A |An |That )/.test(t))
|
|
223
223
|
continue;
|
|
224
|
-
if (/^[A-Z][a-z].*[
|
|
225
|
-
continue;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
if (/^[A-Z][a-z].*[.;:!?,]/.test(t))
|
|
225
|
+
continue; // English sentence with punctuation anywhere
|
|
226
|
+
if (t.split(" ").length > 15 && !/[|&;><$]/.test(t))
|
|
227
|
+
continue; // Long line without shell operators = prose
|
|
228
|
+
// Must start with a plausible command character (lowercase, /, ., $, or common tool)
|
|
229
|
+
if (/^[a-z./$~(]/.test(t) || /^[A-Z]+[_=]/.test(t)) {
|
|
230
|
+
command = t;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
229
233
|
}
|
|
230
|
-
cleaned = command ||
|
|
234
|
+
cleaned = command || lines[0]?.trim() || cleaned;
|
|
231
235
|
cacheSet(nl, cleaned);
|
|
232
236
|
return cleaned;
|
|
233
237
|
}
|
package/dist/cli.js
CHANGED
|
@@ -421,6 +421,21 @@ else if (args.length > 0) {
|
|
|
421
421
|
command = await translateToCommand(prompt, perms, []);
|
|
422
422
|
}
|
|
423
423
|
catch (e) {
|
|
424
|
+
// If BLOCKED, try fallback: read README or package.json for conceptual questions
|
|
425
|
+
if (e.message?.startsWith("BLOCKED:")) {
|
|
426
|
+
try {
|
|
427
|
+
const { existsSync, readFileSync } = await import("fs");
|
|
428
|
+
if (existsSync("README.md")) {
|
|
429
|
+
const readme = readFileSync("README.md", "utf8").slice(0, 3000);
|
|
430
|
+
const processed = await processOutput("cat README.md", readme, prompt);
|
|
431
|
+
if (processed.aiProcessed) {
|
|
432
|
+
console.log(processed.summary);
|
|
433
|
+
process.exit(0);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch { }
|
|
438
|
+
}
|
|
424
439
|
console.error(e.message);
|
|
425
440
|
process.exit(1);
|
|
426
441
|
}
|
|
@@ -457,11 +472,8 @@ else if (args.length > 0) {
|
|
|
457
472
|
const rawTokens = estimateTokens(raw);
|
|
458
473
|
recordUsage(rawTokens);
|
|
459
474
|
// Test output detection
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
console.log(formatWatchResult(result));
|
|
463
|
-
process.exit(0);
|
|
464
|
-
}
|
|
475
|
+
// Test output: skip watchlist, let AI framing handle it
|
|
476
|
+
// The AI reads "42 pass, 0 fail" better than regex parsing bun's mixed output
|
|
465
477
|
// Frame-first pipeline: AI answers the question, lazy is fallback
|
|
466
478
|
// For question-type prompts, answer framing runs BEFORE lazy mode
|
|
467
479
|
const isQuestion = /^(what|which|how|is|are|does|do|can|should|where|who|why|am|was|were|has|have|will)\b/i.test(prompt) || prompt.includes("?");
|
package/package.json
CHANGED
package/src/ai.ts
CHANGED
|
@@ -253,20 +253,23 @@ export async function translateToCommand(
|
|
|
253
253
|
let cleaned = text.trim();
|
|
254
254
|
// Remove ALL markdown code blocks and their content markers
|
|
255
255
|
cleaned = cleaned.replace(/```(?:bash|sh|shell)?\n?/g, "").replace(/```/g, "");
|
|
256
|
-
// Split into lines and find the FIRST one that looks like a
|
|
256
|
+
// Split into lines and find the FIRST one that looks like a SHELL COMMAND
|
|
257
257
|
const lines = cleaned.split("\n");
|
|
258
258
|
let command = "";
|
|
259
259
|
for (const line of lines) {
|
|
260
260
|
const t = line.trim();
|
|
261
261
|
if (!t) continue;
|
|
262
|
-
// Skip
|
|
263
|
-
if (/^(Based on|I |This |The |Let me|Here|Note:|Since|Looking|To |However|BLOCKED:|If |You |We |For )/.test(t)) continue;
|
|
264
|
-
if (/^[A-Z][a-z].*[
|
|
265
|
-
|
|
266
|
-
command
|
|
267
|
-
|
|
262
|
+
// Skip lines that are clearly English prose, not commands
|
|
263
|
+
if (/^(Based on|I |This |The |Let me|Here|Note:|Since|Looking|To |However|BLOCKED:|If |You |We |For |It |A |An |That )/.test(t)) continue;
|
|
264
|
+
if (/^[A-Z][a-z].*[.;:!?,]/.test(t)) continue; // English sentence with punctuation anywhere
|
|
265
|
+
if (t.split(" ").length > 15 && !/[|&;><$]/.test(t)) continue; // Long line without shell operators = prose
|
|
266
|
+
// Must start with a plausible command character (lowercase, /, ., $, or common tool)
|
|
267
|
+
if (/^[a-z./$~(]/.test(t) || /^[A-Z]+[_=]/.test(t)) {
|
|
268
|
+
command = t;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
268
271
|
}
|
|
269
|
-
cleaned = command ||
|
|
272
|
+
cleaned = command || lines[0]?.trim() || cleaned;
|
|
270
273
|
|
|
271
274
|
cacheSet(nl, cleaned);
|
|
272
275
|
return cleaned;
|
package/src/cli.tsx
CHANGED
|
@@ -402,6 +402,20 @@ else if (args.length > 0) {
|
|
|
402
402
|
try {
|
|
403
403
|
command = await translateToCommand(prompt, perms, []);
|
|
404
404
|
} catch (e: any) {
|
|
405
|
+
// If BLOCKED, try fallback: read README or package.json for conceptual questions
|
|
406
|
+
if (e.message?.startsWith("BLOCKED:")) {
|
|
407
|
+
try {
|
|
408
|
+
const { existsSync, readFileSync } = await import("fs");
|
|
409
|
+
if (existsSync("README.md")) {
|
|
410
|
+
const readme = readFileSync("README.md", "utf8").slice(0, 3000);
|
|
411
|
+
const processed = await processOutput("cat README.md", readme, prompt);
|
|
412
|
+
if (processed.aiProcessed) {
|
|
413
|
+
console.log(processed.summary);
|
|
414
|
+
process.exit(0);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} catch {}
|
|
418
|
+
}
|
|
405
419
|
console.error(e.message);
|
|
406
420
|
process.exit(1);
|
|
407
421
|
}
|
|
@@ -440,11 +454,8 @@ else if (args.length > 0) {
|
|
|
440
454
|
recordUsage(rawTokens);
|
|
441
455
|
|
|
442
456
|
// Test output detection
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
console.log(formatWatchResult(result));
|
|
446
|
-
process.exit(0);
|
|
447
|
-
}
|
|
457
|
+
// Test output: skip watchlist, let AI framing handle it
|
|
458
|
+
// The AI reads "42 pass, 0 fail" better than regex parsing bun's mixed output
|
|
448
459
|
|
|
449
460
|
// Frame-first pipeline: AI answers the question, lazy is fallback
|
|
450
461
|
// For question-type prompts, answer framing runs BEFORE lazy mode
|