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 CHANGED
@@ -1 +1 @@
1
- 2.0.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
- # Replace the managed two-line block when the checkout path changes.
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 += 1
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.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 # print the release this install is running
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 after == before or _npm_configured():
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
- """Say which release this is."""
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
- print(f"tai {_read_version()}")
846
- return 0
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