terminal-ai-helper 2.2.0 → 2.3.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/VERSION +1 -1
- package/package.json +1 -1
- package/plugins/zsh/widgets.zsh +63 -0
- package/readme.md +14 -2
- package/tai/index.py +13 -9
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.3.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-ai-helper",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "The terminal that knows your next command Bash/Zsh suggestions that learn from your own history Offline and private with no account required",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/plugins/zsh/widgets.zsh
CHANGED
|
@@ -114,6 +114,26 @@ _tai_update() {
|
|
|
114
114
|
fi
|
|
115
115
|
return
|
|
116
116
|
fi
|
|
117
|
+
# The accept echo is the one redraw that paints nothing. When the line is
|
|
118
|
+
# accepted, zsh re-renders it as it will run, and that render fires this
|
|
119
|
+
# hook — which answered it like any keystroke: it ran the query for the
|
|
120
|
+
# line one last time and painted the hint back onto the row the shell was
|
|
121
|
+
# leaving. The report: Enters on an empty prompt stacked gray `cd …` lines
|
|
122
|
+
# that read as commands the user ran, and a scrollback reader believed
|
|
123
|
+
# them. So the accept path raises _TAI_ACCEPT, and this hook takes tai's
|
|
124
|
+
# own paint down and stands aside: the echo shows what was typed, and what
|
|
125
|
+
# tai drew goes down with the line it rode in on. line-init lowers the
|
|
126
|
+
# flag with everything else it resets — the next line paints as usual.
|
|
127
|
+
if (( _TAI_ACCEPT )); then
|
|
128
|
+
_TAI_SUGGESTION=""
|
|
129
|
+
(( _TAI_MENU_IDX )) && _tai_menu_close
|
|
130
|
+
if (( _TAI_DREW )); then
|
|
131
|
+
POSTDISPLAY=""
|
|
132
|
+
region_highlight=()
|
|
133
|
+
_TAI_DREW=0
|
|
134
|
+
fi
|
|
135
|
+
return
|
|
136
|
+
fi
|
|
117
137
|
local -i ours_was=$_TAI_DREW
|
|
118
138
|
# What the last widget was decides whether this buffer is typed. The hook
|
|
119
139
|
# runs after every widget, and the names here are the ones that mean an
|
|
@@ -348,10 +368,32 @@ tai-enter() {
|
|
|
348
368
|
# A glimpsed menu still swallows the selection; Enter at a disarmed menu is
|
|
349
369
|
# the line's Enter.
|
|
350
370
|
if (( _TAI_MENU_IDX )); then _tai_menu_close; POSTDISPLAY=""; region_highlight=(); fi
|
|
371
|
+
# What runs is what was typed, and the echo of it must say so. zsh's final
|
|
372
|
+
# render of the accepted line fires the redraw hook, which would answer it
|
|
373
|
+
# with a fresh query and paint the hint back onto the row being left; the
|
|
374
|
+
# flag stands the hook down for that one render. See _tai_update.
|
|
375
|
+
_TAI_ACCEPT=1
|
|
351
376
|
zle accept-line
|
|
352
377
|
}
|
|
353
378
|
zle -N tai-enter
|
|
354
379
|
|
|
380
|
+
# Enter is not the only door to a running line: TAI_NO_MENU=1 leaves ^M on
|
|
381
|
+
# stock accept-line, and so can a user binding of their own. The wrapper
|
|
382
|
+
# raises the same flag tai-enter raises, so the accept echo paints nothing
|
|
383
|
+
# in those shells either. It is registered only while the name is still the
|
|
384
|
+
# builtin — an accept-line another plugin wrapped is theirs, their clear
|
|
385
|
+
# already answers their own hint, and tai-enter raises the flag itself
|
|
386
|
+
# before it calls accept-line by name, so the hook stands down either way.
|
|
387
|
+
typeset -gi _TAI_ACCEPT=0
|
|
388
|
+
tai-accept-line() {
|
|
389
|
+
_TAI_ACCEPT=1
|
|
390
|
+
zle .accept-line
|
|
391
|
+
}
|
|
392
|
+
zle -N tai-accept-line
|
|
393
|
+
case "${widgets[accept-line]:-}" in
|
|
394
|
+
builtin*) zle -N accept-line tai-accept-line ;;
|
|
395
|
+
esac
|
|
396
|
+
|
|
355
397
|
# Right arrow accepts the suggestion at the end of the line, and moves the
|
|
356
398
|
# cursor one character anywhere else. Bound straight to tai-accept it took
|
|
357
399
|
# forward-char away entirely: with a ghost on screen there was then no key left
|
|
@@ -560,8 +602,29 @@ _tai_line_init() {
|
|
|
560
602
|
# exit hook, and a flag carried into the next line would stand tai down for
|
|
561
603
|
# every keystroke of it. The hook pair is the truth; this is the belt.
|
|
562
604
|
_TAI_ISEARCH=0
|
|
605
|
+
# Nor is a line still being accepted: the accept flag stands the redraw
|
|
606
|
+
# hook down for the echo of the line that is leaving, and a new line is
|
|
607
|
+
# painted on again from its first redraw.
|
|
608
|
+
_TAI_ACCEPT=0
|
|
563
609
|
region_highlight=()
|
|
564
610
|
_tai_menu_close
|
|
611
|
+
# The prophecy. _TAI_SEQ answers "what did I run after this", and the fresh
|
|
612
|
+
# prompt is where that answer is taken — so the reset above is followed by
|
|
613
|
+
# the question, through the same _tai_update the redraw hook runs: it looks
|
|
614
|
+
# the empty buffer up against _TAI_LAST, judges the head's relative paths
|
|
615
|
+
# where the prompt stands, and paints the gray line the arrow accepts. The
|
|
616
|
+
# report: the sequence table held `sudo …` after the denied `systemctl …`
|
|
617
|
+
# and the prompt said nothing, because line-init only ever cleared, and no
|
|
618
|
+
# widget-triggered redraw fires before the first keystroke — by which time
|
|
619
|
+
# the buffer is no longer empty and the ordinary prefix lookup has replaced
|
|
620
|
+
# the question. Nothing here can answer before the first command has run:
|
|
621
|
+
# _TAI_LAST is set by precmd, so a shell that just opened queries an empty
|
|
622
|
+
# last command and paints nothing — the prophecy is learned, never a
|
|
623
|
+
# default. The query is cached, so the first real redraw of this line
|
|
624
|
+
# restores the answer instead of recomputing it, and every guard the redraw
|
|
625
|
+
# path applies (isearch stand-down, ours-only clearing, the paste rule) is
|
|
626
|
+
# this path's guard too, because it is the same call.
|
|
627
|
+
_tai_update
|
|
565
628
|
}
|
|
566
629
|
|
|
567
630
|
# The isearch flag the redraw hook stands down under, and the pair that
|
package/readme.md
CHANGED
|
@@ -14,6 +14,9 @@ autocomplete that knows *you*:
|
|
|
14
14
|
systemctl <→> restart nginx ← the hint, taken
|
|
15
15
|
git <Tab> pull status ← your habits, ranked
|
|
16
16
|
cd D<Tab> Desktop/ Documents/ Downloads/
|
|
17
|
+
|
|
18
|
+
systemctl stop cups.path
|
|
19
|
+
❯ sudo systemctl stop cups.path ← what you did last time, already waiting
|
|
17
20
|
```
|
|
18
21
|
|
|
19
22
|
![tai in action — the hint, the ranked list, the filesystem, a typo
|
|
@@ -77,7 +80,11 @@ tai uninstall # remove everything tai added
|
|
|
77
80
|
`Ctrl-F` takes it whole, `Alt-F` one word.
|
|
78
81
|
2. **Press `Tab`.** On a first word your history ranks the lines it knows;
|
|
79
82
|
`Enter` fills one in, a second `Enter` runs it.
|
|
80
|
-
3. **Run
|
|
83
|
+
3. **Run a command and look at the next prompt.** When a command fails the way
|
|
84
|
+
it failed before, the empty line already shows, in grey, what you did about
|
|
85
|
+
it last time — `sudo …` after the denied `systemctl …`, `rm -r …` after the
|
|
86
|
+
refused `rm`. `→` takes it; typing replaces it.
|
|
87
|
+
4. **Run `tai web`.** A localhost dashboard of everything it learned, with a
|
|
81
88
|
try box that answers exactly what the prompt would.
|
|
82
89
|
|
|
83
90
|
Every command you run teaches it — secrets, multi-line pastes and stray keys
|
|
@@ -88,7 +95,7 @@ machine. New commands show up within seconds; no restart, ever.
|
|
|
88
95
|
|
|
89
96
|
| key | what it does |
|
|
90
97
|
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
91
|
-
| `→` | take the hint at the end of the line, else move right
|
|
98
|
+
| `→` | take the hint at the end of the line, else move right — on a fresh prompt the hint is what followed the command you just ran |
|
|
92
99
|
| `Ctrl-F` | take the hint |
|
|
93
100
|
| `Alt-F` / `Ctrl-Right` | take **one word** of the hint, leaving the rest to type (bash: `Ctrl-Right`) |
|
|
94
101
|
| `Tab` | write what every candidate shares, then cycle files, folders and options; on a bare first word the learned lines come first, ranked like the dashboard |
|
|
@@ -137,6 +144,11 @@ memory, an array hit in microseconds.
|
|
|
137
144
|
exists, here, on your PATH.
|
|
138
145
|
- **A tool you have never run still gets an answer:** `tool --help`. Run it
|
|
139
146
|
once and the real `--help` is learned in the background.
|
|
147
|
+
- **A fresh prompt carries what came next.** After a command, the empty line
|
|
148
|
+
shows in grey what your own history followed it with — ranked by how often
|
|
149
|
+
it followed, judged where the prompt stands like every suggestion. It is
|
|
150
|
+
learned or it is nothing: nothing built in, nothing before your first
|
|
151
|
+
command, and a follower whose path does not resolve here is not offered.
|
|
140
152
|
|
|
141
153
|
- **If another plugin also draws hints, both work.** `→`, `Ctrl-F` and
|
|
142
154
|
`Alt-F` take whatever is on screen, and tai never overwrites a hint it did
|
package/tai/index.py
CHANGED
|
@@ -413,15 +413,19 @@ def build(max_commands: int = 20000) -> int:
|
|
|
413
413
|
|
|
414
414
|
seq: dict[str, list[str]] = defaultdict(list)
|
|
415
415
|
for prev, counter in eng.seq.items():
|
|
416
|
-
#
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
416
|
+
# Ordered by how often the pair happened, then by how good the line is,
|
|
417
|
+
# best first — because the head of this list is the line a fresh prompt
|
|
418
|
+
# offers, and "what did I run after this" is answered by what followed
|
|
419
|
+
# *most*, not by the best-scoring line that ever did. The engine's own
|
|
420
|
+
# sequence factor is the pair's normalized count for the same reason;
|
|
421
|
+
# ordering the list by base score alone (an earlier shape, with the
|
|
422
|
+
# count discarded here) made the prophecy disagree with the engine on
|
|
423
|
+
# exactly the key the prophecy exists for: after `git add -A`, the
|
|
424
|
+
# history's `git commit -m …` lost the head to a bare `ls` that had
|
|
425
|
+
# merely followed once and scored well on its own.
|
|
426
|
+
values = [cmd for cmd, _, _ in sorted(
|
|
427
|
+
((cmd, counter[cmd], scores.get(cmd, 0)) for cmd in counter),
|
|
428
|
+
key=lambda x: (-x[1], -x[2], x[0]))[:SEQ_CANDIDATE_CAP]]
|
|
425
429
|
# A key whose every follower was stale has no answer left, and writing
|
|
426
430
|
# it as an empty string puts `_TAI_SEQ[key] = ''` in the file. The zsh
|
|
427
431
|
# plugin splits that into one empty line and the bash one reads it as an
|