terminal-ai-helper 2.0.1 → 2.1.0
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 +12 -4
- package/package.json +1 -1
- package/readme.md +7 -14
- package/tai/cli.py +65 -22
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0
|
|
1
|
+
2.1.0
|
package/install.sh
CHANGED
|
@@ -23,11 +23,19 @@ else
|
|
|
23
23
|
exit 1
|
|
24
24
|
}
|
|
25
25
|
if [ -d "$TAI_DIR/.git" ]; then
|
|
26
|
-
# The one-liner is also the update path: an existing checkout is
|
|
27
|
-
#
|
|
28
|
-
#
|
|
26
|
+
# The one-liner is also the update path: an existing checkout is refetched
|
|
27
|
+
# and reinstalled, never cloned over. The checkout is this install's own —
|
|
28
|
+
# nobody develops in it — so the update is one commit deep every time:
|
|
29
|
+
# fetch --depth 1, reset to what origin named, prune what is left behind.
|
|
30
|
+
# A pull here would download every commit made since the checkout was cut,
|
|
31
|
+
# and a checkout that predates --depth 1 would carry its whole history
|
|
32
|
+
# forever. Offline, the fetch fails and set -e stops the install loudly —
|
|
33
|
+
# the old rule stands: never install old code on the strength of a guess.
|
|
29
34
|
echo "→ updating the checkout at $TAI_DIR…"
|
|
30
|
-
git -C "$TAI_DIR"
|
|
35
|
+
_branch="$(git -C "$TAI_DIR" rev-parse --abbrev-ref HEAD)"
|
|
36
|
+
git -C "$TAI_DIR" fetch --depth 1 --quiet origin "$_branch"
|
|
37
|
+
git -C "$TAI_DIR" reset --hard --quiet FETCH_HEAD
|
|
38
|
+
git -C "$TAI_DIR" gc --prune=now --quiet 2>/dev/null || true
|
|
31
39
|
elif [ -e "$TAI_DIR" ]; then
|
|
32
40
|
echo "tai: $TAI_DIR exists and is not a tai checkout — remove it or set TAI_DIR" >&2
|
|
33
41
|
exit 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-ai-helper",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
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
|
@@ -28,20 +28,6 @@ curl -fsSL https://raw.githubusercontent.com/mlibre/Terminal-AI-Helper/main/inst
|
|
|
28
28
|
exec zsh
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Or using npm
|
|
32
|
-
|
|
33
|
-
```sh
|
|
34
|
-
npm i -g terminal-ai-helper@latest --allow-scripts=terminal-ai-helper
|
|
35
|
-
exec zsh
|
|
36
|
-
```
|
|
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
|
-
|
|
41
|
-
The line downloads TAI to `~/.tai` and runs the installer from there; run it
|
|
42
|
-
again later and it updates in place. It enables both shells, imports your
|
|
43
|
-
history, and builds the first index:
|
|
44
|
-
|
|
45
31
|
```text
|
|
46
32
|
→ cloning tai to /home/you/.tai…
|
|
47
33
|
→ learning from your history, building the index…
|
|
@@ -62,6 +48,13 @@ that switched doors could leave the shell's `source` line naming the
|
|
|
62
48
|
older tree — code updates never reach it; `tai update` retargets that
|
|
63
49
|
line, and `tai version` says where it points.
|
|
64
50
|
|
|
51
|
+
Or using npm
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npm i -g terminal-ai-helper@latest --allow-scripts=terminal-ai-helper
|
|
55
|
+
exec zsh
|
|
56
|
+
```
|
|
57
|
+
|
|
65
58
|
## Commands
|
|
66
59
|
|
|
67
60
|
```sh
|
package/tai/cli.py
CHANGED
|
@@ -266,6 +266,7 @@ def cmd_update(a) -> int:
|
|
|
266
266
|
|
|
267
267
|
import shutil
|
|
268
268
|
import subprocess
|
|
269
|
+
from pathlib import Path
|
|
269
270
|
|
|
270
271
|
git = shutil.which("git")
|
|
271
272
|
if not git:
|
|
@@ -281,15 +282,49 @@ def cmd_update(a) -> int:
|
|
|
281
282
|
return subprocess.run([git, "-C", str(repo), "rev-parse", "--short", "HEAD"],
|
|
282
283
|
capture_output=True, text=True).stdout.strip()
|
|
283
284
|
|
|
285
|
+
def run_git(*argv: str) -> subprocess.CompletedProcess:
|
|
286
|
+
return subprocess.run([git, "-C", str(repo), *argv],
|
|
287
|
+
capture_output=True, text=True)
|
|
288
|
+
|
|
289
|
+
# The checkout the one-liner cloned is this install's own tree, not a
|
|
290
|
+
# development branch: nobody commits in it, so the update is a fetch one
|
|
291
|
+
# commit deep and a reset to what origin named — the same three commands
|
|
292
|
+
# install.sh's piped door runs. A pull here would download every commit
|
|
293
|
+
# made since the checkout was cut, and one that predates --depth 1 would
|
|
294
|
+
# carry its whole history forever. A checkout anywhere else is somebody's
|
|
295
|
+
# tree — maybe a fork they develop in — and it keeps the careful pull:
|
|
296
|
+
# fast-forward only, a divergence fails loudly rather than resetting
|
|
297
|
+
# anybody's work away.
|
|
298
|
+
managed = (repo.resolve()
|
|
299
|
+
== Path(os.environ.get("TAI_DIR")
|
|
300
|
+
or Path.home() / ".tai").resolve())
|
|
301
|
+
|
|
284
302
|
before = head()
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
if managed:
|
|
304
|
+
branch = run_git("rev-parse", "--abbrev-ref", "HEAD").stdout.strip()
|
|
305
|
+
failed = None
|
|
306
|
+
for argv in (("fetch", "--depth", "1", "--quiet", "origin", branch),
|
|
307
|
+
("reset", "--hard", "--quiet", "FETCH_HEAD")):
|
|
308
|
+
done = run_git(*argv)
|
|
309
|
+
if done.returncode != 0:
|
|
310
|
+
failed = argv[0]
|
|
311
|
+
break
|
|
312
|
+
if failed:
|
|
313
|
+
print(f"tai: git {failed} failed:")
|
|
314
|
+
print((done.stderr or done.stdout).strip())
|
|
315
|
+
return 1
|
|
316
|
+
# Hygiene, not a step: what the old history weighed is already
|
|
317
|
+
# unreachable after the reset, and a failed prune costs nothing.
|
|
318
|
+
run_git("gc", "--prune=now", "--quiet")
|
|
319
|
+
else:
|
|
320
|
+
pull = run_git("pull", "--ff-only")
|
|
321
|
+
if pull.returncode != 0:
|
|
322
|
+
# A local edit in the checkout is the usual cause, and git's own
|
|
323
|
+
# message says exactly what to do about it, so pass it through
|
|
324
|
+
# rather than guess.
|
|
325
|
+
print("tai: git pull failed:")
|
|
326
|
+
print((pull.stderr or pull.stdout).strip())
|
|
327
|
+
return 1
|
|
293
328
|
after = head()
|
|
294
329
|
# Flushed, because the installer below writes to the same terminal from a
|
|
295
330
|
# child process and would otherwise appear ahead of this line.
|
|
@@ -587,21 +622,29 @@ def cmd_uninstall(a) -> int:
|
|
|
587
622
|
removed.append(str(rc))
|
|
588
623
|
# The piped door clones the code itself to TAI_DIR (default ~/.tai), and
|
|
589
624
|
# leaving it behind would leave the program installed in every way but the
|
|
590
|
-
# wrapper — the same rule the rc blocks above are held to.
|
|
591
|
-
#
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
#
|
|
595
|
-
#
|
|
625
|
+
# wrapper — the same rule the rc blocks above are held to. The name is the
|
|
626
|
+
# identity: the path is tai's namespace, created by tai's installer and
|
|
627
|
+
# claimed by nothing else, so whatever the checkout has grown into — a
|
|
628
|
+
# shape an older door left half-made, files the user dropped beside the
|
|
629
|
+
# code — goes with the uninstall, and the report says which kind it was.
|
|
630
|
+
# One floor stands: never a directory the home lives in or under, so a
|
|
631
|
+
# mistyped TAI_DIR cannot name more than tai owns. Last on purpose: this
|
|
632
|
+
# command usually runs from inside the checkout (the wrapper execs this
|
|
633
|
+
# very file), so removing it has to be the last thing the run does.
|
|
596
634
|
checkout = Path(os.environ.get("TAI_DIR") or home / ".tai")
|
|
597
|
-
|
|
635
|
+
held = ""
|
|
598
636
|
if checkout.exists():
|
|
599
|
-
if (
|
|
600
|
-
|
|
601
|
-
shutil.rmtree(checkout)
|
|
602
|
-
removed.append(str(checkout))
|
|
637
|
+
if home.is_relative_to(checkout):
|
|
638
|
+
held = f"left {checkout} — it holds the home directory"
|
|
603
639
|
else:
|
|
604
|
-
|
|
640
|
+
ours = ((checkout / "tai" / "cli.py").exists()
|
|
641
|
+
and (checkout / "install.sh").exists())
|
|
642
|
+
if checkout.is_dir():
|
|
643
|
+
shutil.rmtree(checkout)
|
|
644
|
+
else:
|
|
645
|
+
checkout.unlink()
|
|
646
|
+
removed.append(str(checkout) + ("" if ours
|
|
647
|
+
else " (not a tai checkout — removed anyway)"))
|
|
605
648
|
# The npm door's code is npm's to remove, and the running tree says the
|
|
606
649
|
# same thing the wrapper did — this process may itself be executing from
|
|
607
650
|
# the package npm owns. Last, for the same reason the checkout is: the
|
|
@@ -622,10 +665,10 @@ def cmd_uninstall(a) -> int:
|
|
|
622
665
|
print("tai removed" if removed else "tai was not installed")
|
|
623
666
|
for path in removed:
|
|
624
667
|
print(f" removed {path}")
|
|
625
|
-
if left:
|
|
626
|
-
print(f" left {left} — not a tai checkout")
|
|
627
668
|
if npm_note:
|
|
628
669
|
print(f" {npm_note}")
|
|
670
|
+
if held:
|
|
671
|
+
print(f" {held}")
|
|
629
672
|
return 0
|
|
630
673
|
|
|
631
674
|
|