terminal-ai-helper 2.0.0 → 2.0.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/install.sh +36 -4
- package/package.json +1 -1
- package/readme.md +5 -2
- package/tai/cli.py +53 -4
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.1
|
package/install.sh
CHANGED
|
@@ -142,12 +142,12 @@ _tai_enable() {
|
|
|
142
142
|
local rc="$1" plugin="$2" source_line="$3" plugin_name
|
|
143
143
|
plugin_name="${plugin##*/}"
|
|
144
144
|
mkdir -p "$(dirname "$rc")"
|
|
145
|
-
python3 - "$rc" "$source_line" "$plugin_name" <<'PY'
|
|
145
|
+
python3 - "$rc" "$source_line" "$plugin_name" "$plugin" <<'PY'
|
|
146
146
|
import os
|
|
147
147
|
import sys
|
|
148
148
|
from pathlib import Path
|
|
149
149
|
|
|
150
|
-
rc, source_line, plugin_name = sys.argv[1:]
|
|
150
|
+
rc, source_line, plugin_name, ours = sys.argv[1:]
|
|
151
151
|
path = Path(rc)
|
|
152
152
|
# surrogateescape, not errors="replace": a dotfile in a legacy encoding has bytes
|
|
153
153
|
# that are not valid UTF-8, and replacing them on read would silently rewrite the
|
|
@@ -156,18 +156,47 @@ text = path.read_text(encoding="utf-8", errors="surrogateescape") if path.exists
|
|
|
156
156
|
marker = "# tai autocomplete (installed by ./install.sh)"
|
|
157
157
|
lines = text.splitlines(keepends=True)
|
|
158
158
|
cleaned = []
|
|
159
|
+
retargeted = []
|
|
159
160
|
i = 0
|
|
160
161
|
while i < len(lines):
|
|
161
162
|
line = lines[i]
|
|
162
163
|
if line.strip() == marker:
|
|
163
|
-
#
|
|
164
|
+
# The managed block: refresh its line in place rather than removing and
|
|
165
|
+
# re-appending, so the block keeps its place and a moved tree rewrites
|
|
166
|
+
# the one line instead of gaining a second one.
|
|
167
|
+
cleaned.append(line)
|
|
164
168
|
i += 1
|
|
165
169
|
if i < len(lines) and plugin_name in lines[i] and "source" in lines[i]:
|
|
166
|
-
i
|
|
170
|
+
lines[i] = source_line + "\n"
|
|
167
171
|
continue
|
|
172
|
+
stripped = line.lstrip()
|
|
173
|
+
if (not stripped.startswith("#") and "source" in line
|
|
174
|
+
and "plugins/" in line and plugin_name in line
|
|
175
|
+
and ours not in line):
|
|
176
|
+
# A tai source line left by an older door: the same plugin name from
|
|
177
|
+
# another tree — the checkout the curl one-liner cloned, still in the
|
|
178
|
+
# rc after npm took over the wrapper. Matching by name alone said
|
|
179
|
+
# "already enabled" and kept the shell sourcing code npm's updates
|
|
180
|
+
# never touch: `tai version` reported one tree while the shell ran
|
|
181
|
+
# another. The line is this install's to own, so it is retargeted.
|
|
182
|
+
old = [t for t in line.split() if plugin_name in t]
|
|
183
|
+
retargeted.append(old[-1].strip("\"'") if old else line.strip())
|
|
184
|
+
line = source_line + "\n"
|
|
168
185
|
cleaned.append(line)
|
|
169
186
|
i += 1
|
|
170
187
|
|
|
188
|
+
# A retarget can meet a stale line twice (a managed block and a stray copy);
|
|
189
|
+
# the line is sourced once, first occurrence wins.
|
|
190
|
+
wanted = source_line + "\n"
|
|
191
|
+
deduped, seen = [], False
|
|
192
|
+
for line in cleaned:
|
|
193
|
+
if line == wanted:
|
|
194
|
+
if seen:
|
|
195
|
+
continue
|
|
196
|
+
seen = True
|
|
197
|
+
deduped.append(line)
|
|
198
|
+
cleaned = deduped
|
|
199
|
+
|
|
171
200
|
# A manually configured integration is already enabled; do not add a second
|
|
172
201
|
# copy when the installer is rerun.
|
|
173
202
|
has_source = any(
|
|
@@ -180,6 +209,9 @@ if not has_source:
|
|
|
180
209
|
cleaned.append(marker + "\n")
|
|
181
210
|
cleaned.append(source_line + "\n")
|
|
182
211
|
|
|
212
|
+
for old in retargeted:
|
|
213
|
+
print(f"tai: retargeted the {plugin_name} source line ({old} — was another install's tree)")
|
|
214
|
+
|
|
183
215
|
# Replaced atomically, the same rule the index builder follows. This file is the
|
|
184
216
|
# user's shell: an interrupt between truncate and write would otherwise leave a
|
|
185
217
|
# half-written .zshrc and a shell that cannot start.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-ai-helper",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.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/readme.md
CHANGED
|
@@ -57,7 +57,10 @@ history, and builds the first index:
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
That last line matters: a terminal that is already open keeps the old
|
|
60
|
-
version until you open a new one — same after `tai update`.
|
|
60
|
+
version until you open a new one — same after `tai update`. An install
|
|
61
|
+
that switched doors could leave the shell's `source` line naming the
|
|
62
|
+
older tree — code updates never reach it; `tai update` retargets that
|
|
63
|
+
line, and `tai version` says where it points.
|
|
61
64
|
|
|
62
65
|
## Commands
|
|
63
66
|
|
|
@@ -69,7 +72,7 @@ tai doctor # what is indexed, and what is held back
|
|
|
69
72
|
tai bench # build time, latency, memory, each shell's index read time
|
|
70
73
|
tai web # a read-only localhost dashboard (alias: tai dashboard)
|
|
71
74
|
tai eval # candidate coverage against your own history
|
|
72
|
-
tai version #
|
|
75
|
+
tai version # this install's release, and where the shell plugin is wired
|
|
73
76
|
tai purge # drop unusable rows, then rebuild
|
|
74
77
|
tai forget git sta # drop every stored row for a command, exactly as typed
|
|
75
78
|
tai uninstall # remove everything tai added
|
package/tai/cli.py
CHANGED
|
@@ -213,6 +213,15 @@ def _update_npm(repo: "Path") -> int:
|
|
|
213
213
|
The allow-scripts flag rides along because npm's newer vintages block
|
|
214
214
|
install scripts by default — the same gate the readme's install line
|
|
215
215
|
opens — and a blocked hook would leave a replaced tree nobody wired.
|
|
216
|
+
|
|
217
|
+
The rerun is decided by the wiring, not by the version. An install that
|
|
218
|
+
switched doors could leave the rc line naming an older tree — the
|
|
219
|
+
checkout the curl one-liner cloned, still sourced after npm took over
|
|
220
|
+
the wrapper — and then every `tai update` reported a version the shell
|
|
221
|
+
was not running, "already up to date" being exactly when the old guard
|
|
222
|
+
(`after == before or configured`) skipped the repair. The installer is
|
|
223
|
+
idempotent and its configured check is the wiring's own test, so it runs
|
|
224
|
+
whenever that test fails, whichever the version says.
|
|
216
225
|
"""
|
|
217
226
|
import shutil
|
|
218
227
|
import subprocess
|
|
@@ -233,7 +242,7 @@ def _update_npm(repo: "Path") -> int:
|
|
|
233
242
|
# progress, and this line is the verdict.
|
|
234
243
|
print("✓ already up to date" if after == before else f"✓ updated to {after}",
|
|
235
244
|
flush=True)
|
|
236
|
-
if
|
|
245
|
+
if _npm_configured():
|
|
237
246
|
return 0
|
|
238
247
|
installer = repo / "install.sh"
|
|
239
248
|
if not installer.exists():
|
|
@@ -694,9 +703,47 @@ def cmd_jev(a) -> int:
|
|
|
694
703
|
|
|
695
704
|
|
|
696
705
|
def cmd_version(a) -> int:
|
|
697
|
-
"""
|
|
706
|
+
"""Which release this is, and where the shell's plugin line points.
|
|
707
|
+
|
|
708
|
+
The second half exists because the two can disagree. An install that
|
|
709
|
+
switched doors — the curl one-liner's checkout replaced by the npm
|
|
710
|
+
package — left the rc line naming the older tree, and the shell went on
|
|
711
|
+
running code `tai version` no longer reported: the release number comes
|
|
712
|
+
from the tree the wrapper execs, the plugin comes from wherever the rc
|
|
713
|
+
says. Both facts are read here, so the mismatch is visible in the same
|
|
714
|
+
command a user runs to answer "which tai is this".
|
|
715
|
+
"""
|
|
698
716
|
version = _read_version()
|
|
699
717
|
print(f"tai {version}")
|
|
718
|
+
from pathlib import Path
|
|
719
|
+
repo = _repo_dir()
|
|
720
|
+
home = Path.home()
|
|
721
|
+
for shell, rc_name, plugin in (("zsh", ".zshrc", "tai.zsh"),
|
|
722
|
+
("bash", ".bashrc", "tai.bash")):
|
|
723
|
+
wired = ""
|
|
724
|
+
try:
|
|
725
|
+
for line in (home / rc_name).read_text(
|
|
726
|
+
encoding="utf-8", errors="surrogateescape").splitlines():
|
|
727
|
+
if line.lstrip().startswith("#") or "source" not in line \
|
|
728
|
+
or plugin not in line:
|
|
729
|
+
continue
|
|
730
|
+
tokens = [t for t in line.split() if plugin in t]
|
|
731
|
+
if tokens:
|
|
732
|
+
wired = tokens[-1].strip("\"'")
|
|
733
|
+
break
|
|
734
|
+
except OSError:
|
|
735
|
+
pass
|
|
736
|
+
if not wired:
|
|
737
|
+
print(f"{shell} plugin not wired")
|
|
738
|
+
continue
|
|
739
|
+
note = ""
|
|
740
|
+
try:
|
|
741
|
+
if Path(wired).expanduser().resolve() != \
|
|
742
|
+
(repo / "plugins" / plugin).resolve():
|
|
743
|
+
note = " — another install; 'tai update' rewires it here"
|
|
744
|
+
except OSError:
|
|
745
|
+
pass
|
|
746
|
+
print(f"{shell} plugin {wired}{note}")
|
|
700
747
|
return 0
|
|
701
748
|
|
|
702
749
|
|
|
@@ -842,8 +889,10 @@ def main() -> int:
|
|
|
842
889
|
if argv and argv[0] == "record" and "--help" not in argv and "-h" not in argv:
|
|
843
890
|
return cmd_record(_fast_record(argv[1:]))
|
|
844
891
|
if argv == ["version"]:
|
|
845
|
-
|
|
846
|
-
|
|
892
|
+
# The report is two small rc reads beside the version file — the lean
|
|
893
|
+
# path can afford what the parser path would print, and both doors
|
|
894
|
+
# must say the same thing about the wiring.
|
|
895
|
+
return cmd_version(None)
|
|
847
896
|
# Any other command is a tai process the user asked for, and a non-empty
|
|
848
897
|
# spool is records waiting for one: drain it here, so `tai suggest`,
|
|
849
898
|
# `tai refresh` or the dashboard never answer from a store that is a few
|