terminal-ai-helper 1.3.4 → 1.3.6
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/bin/npm-postinstall +3 -2
- package/package.json +1 -1
- package/plugins/zsh/lookup.zsh +119 -22
- package/readme.md +31 -32
- package/tai/cli.py +6 -2
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.3.
|
|
1
|
+
1.3.6
|
package/bin/npm-postinstall
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# npm's postinstall hook: a global install of the published package finishes
|
|
3
|
-
# the job itself, so `npm i -g terminal-ai-helper`
|
|
4
|
-
# same steps, the same welcome, the same report
|
|
3
|
+
# the job itself, so `npm i -g terminal-ai-helper --allow-scripts=terminal-ai-helper`
|
|
4
|
+
# is the whole install — the same steps, the same welcome, the same report
|
|
5
|
+
# as the other two doors.
|
|
5
6
|
#
|
|
6
7
|
# Two guards stand before the installer:
|
|
7
8
|
# * only a *global* install reaches it. npm hands every script the config as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-ai-helper",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
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/lookup.zsh
CHANGED
|
@@ -514,6 +514,15 @@ typeset -gi _TAI_LOOSE_MAX_LINE=200
|
|
|
514
514
|
# the same visible answer for exact text, 85ms rather than 34s on a 6KB token.
|
|
515
515
|
# It is matched, not dropped — see the note where `rxs` is built.
|
|
516
516
|
typeset -gi _TAI_LOOSE_MAX_WORD=32
|
|
517
|
+
# The most lines one tier's loop will regex-test for one word. The filters
|
|
518
|
+
# before each loop are necessary conditions, so what they hand over is bounded
|
|
519
|
+
# only by arithmetic: a word whose letters sit in order across a long history
|
|
520
|
+
# leaves the loop hundreds of candidates and every one of them costs a regex.
|
|
521
|
+
# Measured on a 7,600-line index, one such keystroke cost 1.2s. Past the cap
|
|
522
|
+
# the loop stops testing — the tier answers _TAI_LOOSE_CAP lines either way,
|
|
523
|
+
# and lines the cap cuts were offered to it by coincidence of letters, not by
|
|
524
|
+
# anything the user remembered.
|
|
525
|
+
typeset -gi _TAI_LOOSE_FLOOD=400
|
|
517
526
|
|
|
518
527
|
# The in-order check for one word, as a regex: its letters joined by a bounded
|
|
519
528
|
# gap, each escaped so a word holding `-`, `/` or `[` is letters and not syntax.
|
|
@@ -642,23 +651,43 @@ _tai_loose() {
|
|
|
642
651
|
# A pasted command is not a half-remembered one. Checked before the cache key
|
|
643
652
|
# and before any line is read, so the answer is nothing and the cost is one
|
|
644
653
|
# comparison.
|
|
654
|
+
local mkey="$prefix|$_TAI_LOOSE_GEN" mem_slot
|
|
645
655
|
if (( ${#prefix} > _TAI_LOOSE_MAX_LINE )); then
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
656
|
+
# The answer is nothing, and it is memoised like any other: redraws of the
|
|
657
|
+
# same pasted line ask again, and each of those used to pay the guard's
|
|
658
|
+
# cost of a comparison and then rebuild the empty answer's state.
|
|
659
|
+
_TAI_LOOSE_LINES=()
|
|
660
|
+
_TAI_LOOSE_PAR=""; _TAI_LOOSE_PAR_LOW=()
|
|
661
|
+
_TAI_MEM_PUT_KEY="$mkey"; _TAI_MEM_PUT_VAL=""; _TAI_MEM_PUT_LOW=""
|
|
662
|
+
_tai_loose_memo_put
|
|
649
663
|
return
|
|
650
664
|
fi
|
|
651
|
-
# The same question twice has the same answer
|
|
652
|
-
#
|
|
653
|
-
#
|
|
654
|
-
#
|
|
655
|
-
#
|
|
656
|
-
#
|
|
657
|
-
#
|
|
658
|
-
#
|
|
665
|
+
# The same question twice has the same answer — and in a storm of rapid
|
|
666
|
+
# edits, most questions ARE repeats. The report: a word typed out and then
|
|
667
|
+
# taken apart letter by letter, the fastest way to edit, retraces prefixes
|
|
668
|
+
# the scan has already answered — and every one of them paid the full
|
|
669
|
+
# three-tier scan again, because the parent shortcut below only serves a
|
|
670
|
+
# prefix that *extends* its parent, and a deletion never does. Measured on a
|
|
671
|
+
# 7,600-line index: 171ms per delete key, individual keys past a second, the
|
|
672
|
+
# prompt visibly dead under auto-repeat. So the answers live in a bounded
|
|
673
|
+
# memo instead of a single slot, and a revisit is a lookup. The answer is
|
|
674
|
+
# *restored*, not just skipped — `_tai_query` clears `_TAI_LOOSE_LINES`
|
|
675
|
+
# before it calls, so returning early would hand back an empty list and the
|
|
676
|
+
# glimpse would vanish on the second redraw. The parent state rides along:
|
|
677
|
+
# a memoised prefix was fully scanned under this generation, so an extension
|
|
678
|
+
# of it may scan its survivors, and without the restore the first retype
|
|
679
|
+
# after a delete would pay a full scan for nothing. The generation is part
|
|
680
|
+
# of the key because a reloaded index is a different index with the same
|
|
681
|
+
# prefix: _tai_load_index bumps it, so a memoised answer can never speak for
|
|
659
682
|
# lines that are no longer installed.
|
|
660
|
-
|
|
661
|
-
|
|
683
|
+
for (( mem_slot = 1; mem_slot <= _TAI_LOOSE_MEM_N; mem_slot++ )); do
|
|
684
|
+
[[ "${_TAI_LOOSE_MEM_KEYS[mem_slot]}" == "$mkey" ]] && break
|
|
685
|
+
done
|
|
686
|
+
if (( mem_slot <= _TAI_LOOSE_MEM_N )); then
|
|
687
|
+
_tai_q_split "${_TAI_LOOSE_MEM_VALS[mem_slot]}"; _TAI_LOOSE_LINES=( "${REPLY[@]}" )
|
|
688
|
+
_tai_q_split "${_TAI_LOOSE_MEM_LOWS[mem_slot]}"; _TAI_LOOSE_PAR_LOW=( "${REPLY[@]}" )
|
|
689
|
+
_TAI_LOOSE_PAR="$prefix"
|
|
690
|
+
_TAI_LOOSE_PAR_GEN=$_TAI_LOOSE_GEN
|
|
662
691
|
return
|
|
663
692
|
fi
|
|
664
693
|
for w in "${(@s: :)prefix}"; do
|
|
@@ -798,7 +827,17 @@ _tai_loose() {
|
|
|
798
827
|
(( ${#cand_l[@]} )) || break
|
|
799
828
|
cand_l=( "${(@M)cand_l:#*${~rxs_g[k]}*}" )
|
|
800
829
|
done
|
|
830
|
+
# The flood bound. A word the history holds near-spelled can leave hundreds
|
|
831
|
+
# of candidates holding a one-edit spelling, and the loop below regex-tests
|
|
832
|
+
# them one by one — the answers are capped at _TAI_LOOSE_CAP anyway, so
|
|
833
|
+
# once the tier is full the tests buy nothing. The input cap bounds the
|
|
834
|
+
# same loop from the other side: it can only ever bite where the filter
|
|
835
|
+
# left hundreds of lines, which is the arithmetic-coincidence case this
|
|
836
|
+
# tier exists to be cautious about, not a memory it would have kept.
|
|
837
|
+
(( ${#cand_l[@]} > _TAI_LOOSE_FLOOD )) && \
|
|
838
|
+
cand_l=( "${cand_l[@]:0:_TAI_LOOSE_FLOOD}" )
|
|
801
839
|
for ll in "${cand_l[@]}"; do
|
|
840
|
+
(( ${#var_lw[@]} >= _TAI_LOOSE_CAP )) && break
|
|
802
841
|
ok=1
|
|
803
842
|
for (( k = 1; k <= ${#want}; k++ )); do
|
|
804
843
|
w=$want[k]; rxb=$rxs_b[k]
|
|
@@ -828,8 +867,8 @@ _tai_loose() {
|
|
|
828
867
|
# matcher implies: a line the gap matcher accepts holds every letter of
|
|
829
868
|
# every word in order, so the letters-in-order glob per word is one
|
|
830
869
|
# C-level filter that hands the loop below the lines that can possibly
|
|
831
|
-
# match.
|
|
832
|
-
#
|
|
870
|
+
# match. The loop's cost is its survivors' count — bounded below, once
|
|
871
|
+
# the flood cap was measured into it.
|
|
833
872
|
local -a gap_l
|
|
834
873
|
gap_l=( "${rest3[@]}" )
|
|
835
874
|
for (( k = 1; k <= ${#want}; k++ )); do
|
|
@@ -837,7 +876,18 @@ _tai_loose() {
|
|
|
837
876
|
_tai_word_order_glob "${want[k]}"
|
|
838
877
|
gap_l=( "${(@M)gap_l:#*${~_TAI_RE_OUT}*}" )
|
|
839
878
|
done
|
|
879
|
+
# The same flood bound the one-edit tier got, and for the same measured
|
|
880
|
+
# reason: a word whose letters sit in order across hundreds of long lines
|
|
881
|
+
# (`opencode-s` against a history of `opencode serve --host …`) left the
|
|
882
|
+
# loop below regex-testing all of them — 1.2s for one keystroke on a
|
|
883
|
+
# 7,600-line index. The tier answers at most _TAI_LOOSE_CAP lines, so once
|
|
884
|
+
# it is full the tests buy nothing; and the input cap only bites where
|
|
885
|
+
# hundreds of lines already passed the letters-in-order glob, which is
|
|
886
|
+
# arithmetic offering itself as a memory, not one.
|
|
887
|
+
(( ${#gap_l[@]} > _TAI_LOOSE_FLOOD )) && \
|
|
888
|
+
gap_l=( "${gap_l[@]:0:_TAI_LOOSE_FLOOD}" )
|
|
840
889
|
for ll in "${gap_l[@]}"; do
|
|
890
|
+
(( ${#fuzz_lw[@]} >= _TAI_LOOSE_CAP )) && break
|
|
841
891
|
[[ -z "${_TAI_LOOSE_CLAIMED[$ll]:-}" ]] || continue
|
|
842
892
|
ok=1
|
|
843
893
|
for (( k = 1; k <= ${#want}; k++ )); do
|
|
@@ -886,11 +936,15 @@ _tai_loose() {
|
|
|
886
936
|
local c
|
|
887
937
|
_TAI_LOOSE_LINES=()
|
|
888
938
|
for c in "${ranked[@]}"; do _tai_clean "$c" && _TAI_LOOSE_LINES+=( "$c" ); done
|
|
889
|
-
_TAI_LOOSE_CACHED=( "${_TAI_LOOSE_LINES[@]}" )
|
|
890
|
-
_TAI_LOOSE_FOR="$prefix|$_TAI_LOOSE_GEN"
|
|
891
939
|
_TAI_LOOSE_PAR="$prefix"
|
|
892
940
|
_TAI_LOOSE_PAR_GEN="$_TAI_LOOSE_GEN"
|
|
893
941
|
_TAI_LOOSE_PAR_LOW=( "${hits_lw[@]}" )
|
|
942
|
+
# Into the ring: the answer and the scan set an extension of this prefix
|
|
943
|
+
# may walk, under the key the lookup above reads.
|
|
944
|
+
_TAI_MEM_PUT_KEY="$mkey"
|
|
945
|
+
_TAI_MEM_PUT_VAL="${(pj:\n:)_TAI_LOOSE_LINES}"
|
|
946
|
+
_TAI_MEM_PUT_LOW="${(pj:\n:)_TAI_LOOSE_PAR_LOW}"
|
|
947
|
+
_tai_loose_memo_put
|
|
894
948
|
}
|
|
895
949
|
|
|
896
950
|
# One line into the bounded, best-first list _TAI_RANKED, by _TAI_SCORE. The
|
|
@@ -930,12 +984,55 @@ _tai_ranked_insert() {
|
|
|
930
984
|
(( ${#_TAI_RANKED} > $_TAI_LOOSE_CAP )) && \
|
|
931
985
|
_TAI_RANKED=( "${_TAI_RANKED[@]:0:$_TAI_LOOSE_CAP}" )
|
|
932
986
|
}
|
|
933
|
-
# The generation the
|
|
934
|
-
#
|
|
935
|
-
#
|
|
987
|
+
# The generation the memo is keyed on. A reload bumps the generation because
|
|
988
|
+
# the answer for a prefix is an answer about the lines that were installed
|
|
989
|
+
# when it was asked.
|
|
936
990
|
typeset -gi _TAI_LOOSE_GEN=0
|
|
937
|
-
|
|
938
|
-
|
|
991
|
+
# The answers the scan has given, as a ring: three parallel arrays, one slot
|
|
992
|
+
# per remembered prefix, oldest overwritten first. One keystroke used to be
|
|
993
|
+
# one remembered answer; a delete run used to be none — the report that
|
|
994
|
+
# brought the memo in is at the lookup that reads it. Bounded, because a
|
|
995
|
+
# session edits a handful of words and the memo holds the neighbourhood of
|
|
996
|
+
# the current one: the paste's redraws, the delete run back down the word,
|
|
997
|
+
# the retype. The key carries its own generation, so an answer that outlives
|
|
998
|
+
# its index can never be served — it only waits to be overwritten.
|
|
999
|
+
#
|
|
1000
|
+
# Parallel arrays, and not an associative array, on purpose: the key is the
|
|
1001
|
+
# user's own text, and an assoc key comes back out through pattern and
|
|
1002
|
+
# subscript syntax on every removal — `foo]bar`, `pull --qqzz|0` (the pipe is
|
|
1003
|
+
# glob alternation, and `${(b)}` does not quote it) — where an array slot
|
|
1004
|
+
# holds the same text as plain data, compared with `==` and never parsed.
|
|
1005
|
+
typeset -ga _TAI_LOOSE_MEM_KEYS=()
|
|
1006
|
+
typeset -ga _TAI_LOOSE_MEM_VALS=()
|
|
1007
|
+
typeset -ga _TAI_LOOSE_MEM_LOWS=()
|
|
1008
|
+
typeset -gi _TAI_LOOSE_MEM_N=0
|
|
1009
|
+
typeset -gi _TAI_LOOSE_MEM_I=0
|
|
1010
|
+
typeset -gi _TAI_LOOSE_MEM_MAX=16
|
|
1011
|
+
# The three values a `_tai_loose_memo_put` call writes, set by the caller:
|
|
1012
|
+
# the answer, the scan set an extension of the prefix may walk, and the key
|
|
1013
|
+
# both are filed under.
|
|
1014
|
+
typeset -g _TAI_MEM_PUT_KEY=""
|
|
1015
|
+
typeset -g _TAI_MEM_PUT_VAL=""
|
|
1016
|
+
typeset -g _TAI_MEM_PUT_LOW=""
|
|
1017
|
+
|
|
1018
|
+
# One answer into the ring. Full: the cursor walks the oldest slot over.
|
|
1019
|
+
# An integer slot and plain assignment, never an `unset` through a subscript
|
|
1020
|
+
# built from the user's own text — see the ring note above.
|
|
1021
|
+
_tai_loose_memo_put() {
|
|
1022
|
+
# A cap that moved below the ring's count is honoured on the next put: the
|
|
1023
|
+
# live region is slots 1..N, and the lookup scans only those, so clamping N
|
|
1024
|
+
# is the whole change. Slots beyond it hold nothing the memo can serve.
|
|
1025
|
+
(( _TAI_LOOSE_MEM_N > _TAI_LOOSE_MEM_MAX )) && _TAI_LOOSE_MEM_N=_TAI_LOOSE_MEM_MAX
|
|
1026
|
+
if (( _TAI_LOOSE_MEM_N < _TAI_LOOSE_MEM_MAX )); then
|
|
1027
|
+
_TAI_LOOSE_MEM_I=$((_TAI_LOOSE_MEM_N + 1))
|
|
1028
|
+
_TAI_LOOSE_MEM_N=$((_TAI_LOOSE_MEM_N + 1))
|
|
1029
|
+
else
|
|
1030
|
+
_TAI_LOOSE_MEM_I=$((_TAI_LOOSE_MEM_I % _TAI_LOOSE_MEM_MAX + 1))
|
|
1031
|
+
fi
|
|
1032
|
+
_TAI_LOOSE_MEM_KEYS[_TAI_LOOSE_MEM_I]="$_TAI_MEM_PUT_KEY"
|
|
1033
|
+
_TAI_LOOSE_MEM_VALS[_TAI_LOOSE_MEM_I]="$_TAI_MEM_PUT_VAL"
|
|
1034
|
+
_TAI_LOOSE_MEM_LOWS[_TAI_LOOSE_MEM_I]="$_TAI_MEM_PUT_LOW"
|
|
1035
|
+
}
|
|
939
1036
|
# The lowercase scan copy is rebuilt when the generation moves on, and not
|
|
940
1037
|
# before: the first redraw after a reload pays for it.
|
|
941
1038
|
typeset -gi _TAI_LOOSE_SNAP=-1
|
package/readme.md
CHANGED
|
@@ -21,30 +21,23 @@ answered by the habit it shadows](assets/demo.gif)
|
|
|
21
21
|
|
|
22
22
|
[Docs](docs/architecture.md) · [Development](docs/development.md) · [Docs site](https://mlibre.github.io/Terminal-AI-Helper/) · [فارسی](readme-fa.md)
|
|
23
23
|
|
|
24
|
-
## Install
|
|
24
|
+
## Install or Update
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
27
|
curl -fsSL https://raw.githubusercontent.com/mlibre/Terminal-AI-Helper/main/install.sh | bash
|
|
28
|
-
|
|
29
|
-
npm i -g terminal-ai-helper@latest
|
|
28
|
+
exec zsh
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
Or using npm
|
|
33
32
|
|
|
34
33
|
```sh
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
tai discover # learn --help from installed tools (cached, ~5-35s)
|
|
38
|
-
tai doctor # what is indexed, and what is held back
|
|
39
|
-
tai bench # build time, latency, memory, each shell's index read time
|
|
40
|
-
tai web # a read-only localhost dashboard (alias: tai dashboard)
|
|
41
|
-
tai eval # candidate coverage against your own history
|
|
42
|
-
tai version # print the release this install is running
|
|
43
|
-
tai purge # drop unusable rows, then rebuild
|
|
44
|
-
tai forget git sta # drop every stored row for a command, exactly as typed
|
|
45
|
-
tai uninstall # remove everything tai added
|
|
34
|
+
npm i -g terminal-ai-helper@latest --allow-scripts=terminal-ai-helper
|
|
35
|
+
exec zsh
|
|
46
36
|
```
|
|
47
37
|
|
|
38
|
+
The flag is npm's: newer npm blocks install scripts by default, and without
|
|
39
|
+
it the package unpacks and nothing is wired — no `tai` command, no rc lines.
|
|
40
|
+
|
|
48
41
|
The line downloads TAI to `~/.tai` and runs the installer from there; run it
|
|
49
42
|
again later and it updates in place. It enables both shells, imports your
|
|
50
43
|
history, and builds the first index:
|
|
@@ -66,6 +59,22 @@ history, and builds the first index:
|
|
|
66
59
|
That last line matters: a terminal that is already open keeps the old
|
|
67
60
|
version until you open a new one — same after `tai update`.
|
|
68
61
|
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
tai update # newest version — GitHub for a checkout, npm for an npm install (alias: tai upgrade)
|
|
66
|
+
tai refresh # import new history rows, then rebuild the indexes
|
|
67
|
+
tai discover # learn --help from installed tools (cached, ~5-35s)
|
|
68
|
+
tai doctor # what is indexed, and what is held back
|
|
69
|
+
tai bench # build time, latency, memory, each shell's index read time
|
|
70
|
+
tai web # a read-only localhost dashboard (alias: tai dashboard)
|
|
71
|
+
tai eval # candidate coverage against your own history
|
|
72
|
+
tai version # print the release this install is running
|
|
73
|
+
tai purge # drop unusable rows, then rebuild
|
|
74
|
+
tai forget git sta # drop every stored row for a command, exactly as typed
|
|
75
|
+
tai uninstall # remove everything tai added
|
|
76
|
+
```
|
|
77
|
+
|
|
69
78
|
## Your first minute
|
|
70
79
|
|
|
71
80
|
1. **Type a command you run often.** A grey hint shows the rest; `→` or
|
|
@@ -103,8 +112,7 @@ a list below the line and has no menu key: `Tab` completes directly — after
|
|
|
103
112
|
## What it suggests, and what it will not
|
|
104
113
|
|
|
105
114
|
One loop makes all of it go. The shell appends each command to a local spool
|
|
106
|
-
file and it lands in a local SQLite database seconds later
|
|
107
|
-
multi-line pastes and one-key accidents never become vocabulary. The first
|
|
115
|
+
file and it lands in a local SQLite database seconds later. The first
|
|
108
116
|
time you run an unknown tool, its real `--help` is read in the background, so
|
|
109
117
|
`tool <Tab>` knows its subcommands and flags from then on. `tai refresh`
|
|
110
118
|
turns the database into two index files ranked your way — how often, how
|
|
@@ -143,7 +151,7 @@ memory, an array hit in microseconds.
|
|
|
143
151
|
## A dashboard of what it learned
|
|
144
152
|
|
|
145
153
|
```sh
|
|
146
|
-
tai web #
|
|
154
|
+
tai web # → http://127.0.0.1:8247/
|
|
147
155
|
```
|
|
148
156
|
|
|
149
157
|
![the tai web dashboard — the try box answering `git`, the state of the
|
|
@@ -190,20 +198,11 @@ A ready-to-install `tai_<version>_all.deb` is attached to every GitHub
|
|
|
190
198
|
release; it puts `tai` on `PATH` and prints the two `source` lines for your
|
|
191
199
|
rc file.
|
|
192
200
|
|
|
193
|
-
|
|
194
|
-
tai
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
```sh
|
|
200
|
-
tai uninstall
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Removes the shell integration, the wrapper, the indexes, the SQLite history
|
|
204
|
-
and the cached knowledge. The `~/.tai` folder stays — on an npm install,
|
|
205
|
-
`npm rm -g terminal-ai-helper` removes the package itself. Restart the
|
|
206
|
-
terminal afterwards.
|
|
201
|
+
`tai update` stops rather than overwrite a copy you have edited yourself.
|
|
202
|
+
`tai uninstall` removes the shell integration, the wrapper, the indexes, the
|
|
203
|
+
SQLite history and the cached knowledge. The `~/.tai` folder stays — on an
|
|
204
|
+
npm install, `npm rm -g terminal-ai-helper` removes the package itself.
|
|
205
|
+
Restart the terminal afterwards.
|
|
207
206
|
|
|
208
207
|
## Why it stays fast
|
|
209
208
|
|
package/tai/cli.py
CHANGED
|
@@ -210,6 +210,9 @@ def _update_npm(repo: "Path") -> int:
|
|
|
210
210
|
postinstall hook finishes the reinstall from the new tree unless scripts
|
|
211
211
|
were skipped (--ignore-scripts) or it failed, in which case the installer
|
|
212
212
|
runs here instead: idempotent, and the same report as every other door.
|
|
213
|
+
The allow-scripts flag rides along because npm's newer vintages block
|
|
214
|
+
install scripts by default — the same gate the readme's install line
|
|
215
|
+
opens — and a blocked hook would leave a replaced tree nobody wired.
|
|
213
216
|
"""
|
|
214
217
|
import shutil
|
|
215
218
|
import subprocess
|
|
@@ -217,10 +220,11 @@ def _update_npm(repo: "Path") -> int:
|
|
|
217
220
|
npm = shutil.which("npm")
|
|
218
221
|
if not npm:
|
|
219
222
|
print("tai: npm is not installed, so there is nothing to update from")
|
|
220
|
-
print(" reinstall with: npm i -g terminal-ai-helper@latest")
|
|
223
|
+
print(" reinstall with: npm i -g terminal-ai-helper@latest --allow-scripts=terminal-ai-helper")
|
|
221
224
|
return 1
|
|
222
225
|
before = _read_version()
|
|
223
|
-
pulled = subprocess.run([npm, "install", "-g", "terminal-ai-helper@latest"
|
|
226
|
+
pulled = subprocess.run([npm, "install", "-g", "terminal-ai-helper@latest",
|
|
227
|
+
"--allow-scripts=terminal-ai-helper"])
|
|
224
228
|
if pulled.returncode != 0:
|
|
225
229
|
print("tai: npm install failed — the previous version is still installed")
|
|
226
230
|
return 1
|