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 CHANGED
@@ -1 +1 @@
1
- 2.2.0
1
+ 2.3.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-ai-helper",
3
- "version": "2.2.0",
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": {
@@ -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 `tai web`.** A localhost dashboard of everything it learned, with a
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
- # Sorted here, best first, because a shell now reads the order as the
417
- # ranking and not only as a tiebreak: the zsh menu shows a key's values in
418
- # the order they are stored, while _tai_best still re-ranks by
419
- # _TAI_SCORE to pick one. It used to add `count * 2.5` to a milli-score,
420
- # which is 0.08% of the value it was added to: the list looked
421
- # sequence-ranked and was really just base-score ranked with extra steps.
422
- values = [cmd for cmd, _ in sorted(
423
- ((cmd, scores.get(cmd, 0)) for cmd in counter),
424
- key=lambda x: (-x[1], x[0]))[:SEQ_CANDIDATE_CAP]]
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