aish-cli 1.1.0 → 1.1.1
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/aish.plugin.zsh +10 -3
- package/dist/index.js +73 -38
- package/package.json +1 -1
package/aish.plugin.zsh
CHANGED
|
@@ -327,13 +327,19 @@ Respond with only the updated command."
|
|
|
327
327
|
wait $pid 2>/dev/null
|
|
328
328
|
local exit_code=$?
|
|
329
329
|
cmd=$(<"$tmpfile")
|
|
330
|
-
local
|
|
330
|
+
local stats=$(<"$errfile")
|
|
331
331
|
rm -f "$tmpfile" "$errfile"
|
|
332
332
|
|
|
333
|
+
# Strip ANSI codes from stats for clean display
|
|
334
|
+
stats="${stats//$'\e[2m'/}"
|
|
335
|
+
stats="${stats//$'\e[0m'/}"
|
|
336
|
+
stats="${stats## }" # trim leading space
|
|
337
|
+
stats="${stats%%$'\n'}" # trim trailing newline
|
|
338
|
+
|
|
333
339
|
if [[ $exit_code -ne 0 ]]; then
|
|
334
340
|
# Show error briefly
|
|
335
341
|
BUFFER=""
|
|
336
|
-
POSTDISPLAY=$'\n '"Error: ${
|
|
342
|
+
POSTDISPLAY=$'\n '"Error: ${stats:-aish command failed}"
|
|
337
343
|
region_highlight=()
|
|
338
344
|
zle -R
|
|
339
345
|
sleep 2
|
|
@@ -369,10 +375,11 @@ Command: ${cmd}"
|
|
|
369
375
|
Command: ${cmd}"
|
|
370
376
|
fi
|
|
371
377
|
|
|
372
|
-
# Show result with hints
|
|
378
|
+
# Show result with hints and stats
|
|
373
379
|
BUFFER="$cmd"
|
|
374
380
|
CURSOR=${#BUFFER}
|
|
375
381
|
local hints=$'\n tab: refine │ enter: accept │ esc: cancel'
|
|
382
|
+
[[ -n "$stats" ]] && hints+=" │ ${stats}"
|
|
376
383
|
POSTDISPLAY="$hints"
|
|
377
384
|
# Highlight POSTDISPLAY area (starts after BUFFER)
|
|
378
385
|
local hint_start=${#BUFFER}
|
package/dist/index.js
CHANGED
|
@@ -8,56 +8,59 @@ import { tmpdir, homedir } from "os";
|
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
var SYSTEM_PROMPT = `You are a CLI assistant that converts natural language into the exact shell commands to run.
|
|
10
10
|
|
|
11
|
-
Some project files are provided below for reference so you don't need to read them yourself.
|
|
12
|
-
|
|
13
11
|
GUIDELINES:
|
|
14
|
-
1.
|
|
15
|
-
2.
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
1. For standard commands (git, ls, curl, etc.), respond immediately without reading files.
|
|
13
|
+
2. For project-specific commands (build, test, run scripts, etc.), ALWAYS read README.md first (if it exists), then the relevant config file:
|
|
14
|
+
- Node.js: package.json
|
|
15
|
+
- Python: pyproject.toml, setup.py, or Pipfile
|
|
16
|
+
- Rust: Cargo.toml
|
|
17
|
+
- Go: go.mod
|
|
18
|
+
- Ruby: Gemfile
|
|
19
|
+
- Make: Makefile
|
|
20
|
+
- Other: Justfile, Taskfile.yml
|
|
21
|
+
3. ALWAYS prefer project scripts over direct tool invocation:
|
|
22
|
+
- Use "npm run build" not "tsup" or "tsc"
|
|
23
|
+
- Use "make test" not the underlying test command
|
|
24
|
+
- Use "cargo build" not "rustc" directly
|
|
25
|
+
This ensures correct flags, environment, and project configuration.
|
|
26
|
+
4. If unsure about exact flags, provide multiple options.
|
|
18
27
|
|
|
19
28
|
RESPONSE FORMAT \u2014 THIS IS CRITICAL:
|
|
20
29
|
Your final message MUST be ONLY a JSON object. No prose, no explanation, no "Based on...", no markdown.
|
|
21
30
|
Exactly this format: {"commands": ["command1"]}
|
|
22
31
|
If you are unsure which command the user wants, return multiple options and the user will pick: {"commands": ["option1", "option2"]}
|
|
23
|
-
Prefer existing scripts/targets with correct flags over raw commands.
|
|
24
32
|
For values the user hasn't specified, use <placeholders> like: git commit -m "<message>" or curl <url>. Use descriptive names inside the angle brackets.`;
|
|
25
|
-
var
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"Gemfile"
|
|
33
|
+
var TOOL_HINTS = [
|
|
34
|
+
{ files: ["bun.lockb", "bun.lock"], hint: "Use bun (not npm/yarn/pnpm)" },
|
|
35
|
+
{ files: ["pnpm-lock.yaml"], hint: "Use pnpm (not npm/yarn)" },
|
|
36
|
+
{ files: ["yarn.lock"], hint: "Use yarn (not npm/pnpm)" },
|
|
37
|
+
{ files: ["package-lock.json"], hint: "Use npm (not yarn/pnpm)" },
|
|
38
|
+
{ files: ["Cargo.lock"], hint: "Use cargo for Rust commands" },
|
|
39
|
+
{ files: ["poetry.lock"], hint: "Use poetry (not pip)" },
|
|
40
|
+
{ files: ["Pipfile.lock"], hint: "Use pipenv (not pip)" },
|
|
41
|
+
{ files: ["uv.lock"], hint: "Use uv (not pip/poetry)" },
|
|
42
|
+
{ files: ["Gemfile.lock"], hint: "Use bundle exec for Ruby commands" },
|
|
43
|
+
{ files: ["go.sum"], hint: "Use go modules" },
|
|
44
|
+
{ files: ["flake.lock"], hint: "Nix flake project - use nix commands" }
|
|
35
45
|
];
|
|
36
|
-
var MAX_FILE_SIZE = 4e3;
|
|
37
46
|
async function gatherProjectContext(cwd) {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
content = content.slice(0, MAX_FILE_SIZE) + "\n...(truncated)";
|
|
47
|
+
const hints = [];
|
|
48
|
+
for (const { files, hint } of TOOL_HINTS) {
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
try {
|
|
51
|
+
await access(join(cwd, file));
|
|
52
|
+
hints.push(hint);
|
|
53
|
+
break;
|
|
54
|
+
} catch {
|
|
47
55
|
}
|
|
48
|
-
sections.push(`--- ${file} ---
|
|
49
|
-
${content}`);
|
|
50
|
-
found.push(file);
|
|
51
|
-
} catch {
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
|
-
if (
|
|
55
|
-
if (
|
|
58
|
+
if (hints.length > 0) logVerbose(` hints: ${hints.join(", ")}`);
|
|
59
|
+
if (hints.length === 0) return "";
|
|
56
60
|
return `
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
${sections.join("\n\n")}`;
|
|
62
|
+
Tool hints:
|
|
63
|
+
- ${hints.join("\n- ")}`;
|
|
61
64
|
}
|
|
62
65
|
var verbose = false;
|
|
63
66
|
function setVerbose(v) {
|
|
@@ -176,7 +179,7 @@ async function queryClaude(query, cwd, model) {
|
|
|
176
179
|
const cached = await cacheGet(key);
|
|
177
180
|
if (cached) {
|
|
178
181
|
logVerbose(" cache: hit");
|
|
179
|
-
return cached;
|
|
182
|
+
return { ...cached, stats: { cached: true } };
|
|
180
183
|
}
|
|
181
184
|
const prompt = `${SYSTEM_PROMPT}${context}
|
|
182
185
|
|
|
@@ -192,12 +195,19 @@ User request: ${query}`;
|
|
|
192
195
|
];
|
|
193
196
|
const { stdout } = await spawnWithStdin("claude", args, prompt, cwd);
|
|
194
197
|
let text = stdout.trim();
|
|
198
|
+
let stats = {};
|
|
195
199
|
try {
|
|
196
200
|
const wrapper = JSON.parse(text);
|
|
197
201
|
if (verbose) {
|
|
198
202
|
logVerbose(formatStats(wrapper));
|
|
199
203
|
if (wrapper.result) logVerbose(` result: ${wrapper.result}`);
|
|
200
204
|
}
|
|
205
|
+
stats.durationMs = wrapper.duration_ms;
|
|
206
|
+
stats.cost = wrapper.total_cost_usd;
|
|
207
|
+
if (wrapper.usage) {
|
|
208
|
+
stats.inputTokens = (wrapper.usage.input_tokens || 0) + (wrapper.usage.cache_creation_input_tokens || 0) + (wrapper.usage.cache_read_input_tokens || 0);
|
|
209
|
+
stats.outputTokens = wrapper.usage.output_tokens || 0;
|
|
210
|
+
}
|
|
201
211
|
if (wrapper.result) {
|
|
202
212
|
text = wrapper.result;
|
|
203
213
|
}
|
|
@@ -206,9 +216,11 @@ User request: ${query}`;
|
|
|
206
216
|
}
|
|
207
217
|
const result = parseResponse(text);
|
|
208
218
|
result.cacheKey = key;
|
|
219
|
+
result.stats = stats;
|
|
209
220
|
return result;
|
|
210
221
|
}
|
|
211
222
|
async function queryCodex(query, cwd, model) {
|
|
223
|
+
const startTime = Date.now();
|
|
212
224
|
const context = await gatherProjectContext(cwd);
|
|
213
225
|
const resultFile = join(tmpdir(), `aish-codex-${Date.now()}.txt`);
|
|
214
226
|
const prompt = `${SYSTEM_PROMPT}${context}
|
|
@@ -218,6 +230,8 @@ User request: ${query}`;
|
|
|
218
230
|
"exec",
|
|
219
231
|
"--sandbox",
|
|
220
232
|
"read-only",
|
|
233
|
+
"-c",
|
|
234
|
+
'model_reasoning_effort="low"',
|
|
221
235
|
"-o",
|
|
222
236
|
resultFile,
|
|
223
237
|
"-"
|
|
@@ -230,7 +244,9 @@ User request: ${query}`;
|
|
|
230
244
|
const text = await readFile(resultFile, "utf-8");
|
|
231
245
|
await unlink(resultFile).catch(() => {
|
|
232
246
|
});
|
|
233
|
-
|
|
247
|
+
const result = parseResponse(text);
|
|
248
|
+
result.stats = { durationMs: Date.now() - startTime };
|
|
249
|
+
return result;
|
|
234
250
|
}
|
|
235
251
|
async function queryAi(provider, query, cwd, model) {
|
|
236
252
|
if (provider === "codex") {
|
|
@@ -410,10 +426,12 @@ async function main() {
|
|
|
410
426
|
} : startSpinner("Thinking...");
|
|
411
427
|
let commands;
|
|
412
428
|
let resultCacheKey;
|
|
429
|
+
let stats;
|
|
413
430
|
try {
|
|
414
431
|
const result = await queryAi(provider, query, cwd, model);
|
|
415
432
|
commands = result.commands;
|
|
416
433
|
resultCacheKey = result.cacheKey;
|
|
434
|
+
stats = result.stats;
|
|
417
435
|
} catch (err) {
|
|
418
436
|
stopSpinner();
|
|
419
437
|
if (print) {
|
|
@@ -423,6 +441,23 @@ async function main() {
|
|
|
423
441
|
process.exit(1);
|
|
424
442
|
}
|
|
425
443
|
stopSpinner();
|
|
444
|
+
if (stats) {
|
|
445
|
+
const parts = [];
|
|
446
|
+
if (stats.cached) {
|
|
447
|
+
parts.push("cached");
|
|
448
|
+
} else {
|
|
449
|
+
if (stats.durationMs) parts.push(`${(stats.durationMs / 1e3).toFixed(1)}s`);
|
|
450
|
+
if (stats.inputTokens || stats.outputTokens) {
|
|
451
|
+
parts.push(`${stats.inputTokens || 0}\u2192${stats.outputTokens || 0} tok`);
|
|
452
|
+
}
|
|
453
|
+
if (stats.cost) parts.push(`$${stats.cost.toFixed(4)}`);
|
|
454
|
+
}
|
|
455
|
+
if (parts.length > 0) {
|
|
456
|
+
const output = print ? process.stderr : process.stdout;
|
|
457
|
+
output.write(`${DIM3} ${parts.join(" \xB7 ")}${RESET2}
|
|
458
|
+
`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
426
461
|
if (commands.length === 0) {
|
|
427
462
|
if (print) {
|
|
428
463
|
process.exit(1);
|