ai-memory-cli 0.1.8__tar.gz → 0.1.9__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-memory-cli
3
- Version: 0.1.8
3
+ Version: 0.1.9
4
4
  Summary: Python CLI for AI Memory terminal capture and offline sync.
5
5
  Author: AI Memory
6
6
  License-Expression: MIT
@@ -69,6 +69,7 @@ On Windows, `python -m ai_memory_cli ...` is the safest form because it avoids P
69
69
 
70
70
  Inside `watch`, type the real command you want to capture, for example `python --version`. Do not type `python -m ai_memory_cli run -- ...` inside `watch`, or you will capture the nested CLI command too.
71
71
  Use `cls` on Windows or `clear` on Unix shells to clear the watch screen; those control commands are not stored or synced.
72
+ On Windows, commands run through PowerShell, so type `ls` directly. Do not type `powershell` or `cmd` inside `watch`; nested shell launchers are ignored and not stored.
72
73
 
73
74
  ## Background agent
74
75
 
@@ -47,6 +47,7 @@ On Windows, `python -m ai_memory_cli ...` is the safest form because it avoids P
47
47
 
48
48
  Inside `watch`, type the real command you want to capture, for example `python --version`. Do not type `python -m ai_memory_cli run -- ...` inside `watch`, or you will capture the nested CLI command too.
49
49
  Use `cls` on Windows or `clear` on Unix shells to clear the watch screen; those control commands are not stored or synced.
50
+ On Windows, commands run through PowerShell, so type `ls` directly. Do not type `powershell` or `cmd` inside `watch`; nested shell launchers are ignored and not stored.
50
51
 
51
52
  ## Background agent
52
53
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ai-memory-cli"
7
- version = "0.1.8"
7
+ version = "0.1.9"
8
8
  description = "Python CLI for AI Memory terminal capture and offline sync."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """AI Memory terminal capture CLI."""
2
2
 
3
- __version__ = "0.1.8"
3
+ __version__ = "0.1.9"
@@ -43,6 +43,7 @@ SHELL_NOT_FOUND_PATTERNS = [
43
43
  "command not found",
44
44
  ]
45
45
  CLEAR_COMMANDS = {"cls", "clear"}
46
+ INTERACTIVE_SHELLS = {"powershell", "pwsh", "cmd", "bash", "sh", "zsh", "fish"}
46
47
 
47
48
 
48
49
  def utc_now() -> str:
@@ -326,6 +327,44 @@ def is_clear_command(command: str) -> bool:
326
327
  return normalize_command(command).lower() in CLEAR_COMMANDS
327
328
 
328
329
 
330
+ def is_interactive_shell_command(command: str) -> bool:
331
+ tokens = normalize_command(command).lower().split()
332
+ if not tokens:
333
+ return False
334
+ launcher = tokens[0].removesuffix(".exe")
335
+ if launcher not in INTERACTIVE_SHELLS:
336
+ return False
337
+ if launcher in {"powershell", "pwsh"}:
338
+ return len(tokens) == 1 or "-noexit" in tokens
339
+ if launcher == "cmd":
340
+ return len(tokens) == 1 or "/k" in tokens
341
+ return len(tokens) == 1
342
+
343
+
344
+ def command_invocation(command: str) -> tuple[str | list[str], bool]:
345
+ if os.name == "nt":
346
+ powershell = shutil.which("powershell") or shutil.which("pwsh")
347
+ if powershell:
348
+ return [powershell, "-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command], False
349
+ return command, True
350
+
351
+
352
+ def run_external_command(command: str, cwd: Path, capture: bool) -> subprocess.CompletedProcess[str] | int:
353
+ invocation, use_shell = command_invocation(command)
354
+ if capture:
355
+ return subprocess.run(
356
+ invocation,
357
+ shell=use_shell,
358
+ cwd=str(cwd),
359
+ stdin=subprocess.DEVNULL,
360
+ capture_output=True,
361
+ text=True,
362
+ encoding="utf-8",
363
+ errors="replace",
364
+ )
365
+ return subprocess.call(invocation, shell=use_shell, cwd=str(cwd), stdin=subprocess.DEVNULL)
366
+
367
+
329
368
  def clear_console() -> None:
330
369
  command = "cls" if os.name == "nt" else "clear"
331
370
  try:
@@ -341,10 +380,15 @@ def normalize_output(stdout: str, stderr: str) -> str:
341
380
 
342
381
 
343
382
  def selected_shell() -> str:
383
+ if os.name == "nt":
384
+ if shutil.which("powershell"):
385
+ return "powershell"
386
+ if shutil.which("pwsh"):
387
+ return "pwsh"
344
388
  shell = os.getenv("SHELL") or os.getenv("COMSPEC") or ""
345
389
  if shell:
346
390
  return Path(shell).name
347
- return "powershell" if os.name == "nt" else "sh"
391
+ return "sh"
348
392
 
349
393
 
350
394
  def command_line(parts: list[str]) -> str:
@@ -649,25 +693,23 @@ def capture_command(home: Path, config: dict[str, Any], command: str, include_ex
649
693
  clear_console()
650
694
  return 0
651
695
 
696
+ if is_interactive_shell_command(command):
697
+ print("ai-memory: watch already runs commands through a shell. Type the command directly, for example: ls")
698
+ return 0
699
+
652
700
  require_verified_auth(home, config)
653
701
  workspace = Path(str(config.get("workspace_path") or ".")).expanduser()
654
702
  cwd = workspace if workspace.exists() else Path.cwd()
655
703
 
656
704
  if is_excluded(command, config) and not include_excluded:
657
705
  print(f"ai-memory: running without capture because this command is excluded: {command}", file=sys.stderr)
658
- return subprocess.call(command, shell=True, cwd=str(cwd))
706
+ return int(run_external_command(command, cwd, capture=False))
659
707
 
660
708
  started_at = utc_now()
661
709
  started_monotonic = time.monotonic()
662
- completed = subprocess.run(
663
- command,
664
- shell=True,
665
- cwd=str(cwd),
666
- capture_output=True,
667
- text=True,
668
- encoding="utf-8",
669
- errors="replace",
670
- )
710
+ completed = run_external_command(command, cwd, capture=True)
711
+ if isinstance(completed, int):
712
+ return completed
671
713
  ended_at = utc_now()
672
714
  duration_ms = int((time.monotonic() - started_monotonic) * 1000)
673
715
 
@@ -888,6 +930,9 @@ def command_watch(args: argparse.Namespace) -> int:
888
930
  if is_clear_command(command):
889
931
  clear_console()
890
932
  continue
933
+ if is_interactive_shell_command(command):
934
+ print("ai-memory: do not start a nested shell here. Type commands directly, for example: ls")
935
+ continue
891
936
  capture_command(home, config, command, args.include_excluded, "watch")
892
937
  return 0
893
938
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-memory-cli
3
- Version: 0.1.8
3
+ Version: 0.1.9
4
4
  Summary: Python CLI for AI Memory terminal capture and offline sync.
5
5
  Author: AI Memory
6
6
  License-Expression: MIT
@@ -69,6 +69,7 @@ On Windows, `python -m ai_memory_cli ...` is the safest form because it avoids P
69
69
 
70
70
  Inside `watch`, type the real command you want to capture, for example `python --version`. Do not type `python -m ai_memory_cli run -- ...` inside `watch`, or you will capture the nested CLI command too.
71
71
  Use `cls` on Windows or `clear` on Unix shells to clear the watch screen; those control commands are not stored or synced.
72
+ On Windows, commands run through PowerShell, so type `ls` directly. Do not type `powershell` or `cmd` inside `watch`; nested shell launchers are ignored and not stored.
72
73
 
73
74
  ## Background agent
74
75
 
File without changes
File without changes