ai-memory-cli 0.1.7__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.7
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
@@ -68,6 +68,8 @@ Use `python -m ai_memory_cli run -- COMMAND` when you only want to record one co
68
68
  On Windows, `python -m ai_memory_cli ...` is the safest form because it avoids PATH issues and Device Guard policies that can block pip's generated `ai-memory.exe` launcher. Also avoid angle bracket placeholders in CMD because they are treated as file redirection.
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
+ 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.
71
73
 
72
74
  ## Background agent
73
75
 
@@ -46,6 +46,8 @@ Use `python -m ai_memory_cli run -- COMMAND` when you only want to record one co
46
46
  On Windows, `python -m ai_memory_cli ...` is the safest form because it avoids PATH issues and Device Guard policies that can block pip's generated `ai-memory.exe` launcher. Also avoid angle bracket placeholders in CMD because they are treated as file redirection.
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
+ 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.
49
51
 
50
52
  ## Background agent
51
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.7"
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.7"
3
+ __version__ = "0.1.9"
@@ -42,6 +42,8 @@ SHELL_NOT_FOUND_PATTERNS = [
42
42
  "no such file or directory",
43
43
  "command not found",
44
44
  ]
45
+ CLEAR_COMMANDS = {"cls", "clear"}
46
+ INTERACTIVE_SHELLS = {"powershell", "pwsh", "cmd", "bash", "sh", "zsh", "fish"}
45
47
 
46
48
 
47
49
  def utc_now() -> str:
@@ -321,6 +323,56 @@ def clean_watch_command(command: str) -> str:
321
323
  return cleaned
322
324
 
323
325
 
326
+ def is_clear_command(command: str) -> bool:
327
+ return normalize_command(command).lower() in CLEAR_COMMANDS
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
+
368
+ def clear_console() -> None:
369
+ command = "cls" if os.name == "nt" else "clear"
370
+ try:
371
+ subprocess.call(command, shell=True)
372
+ except Exception:
373
+ print("\033[2J\033[H", end="")
374
+
375
+
324
376
  def normalize_output(stdout: str, stderr: str) -> str:
325
377
  combined = f"stdout:\n{stdout}\nstderr:\n{stderr}"
326
378
  lines = [line.rstrip() for line in combined.replace("\r\n", "\n").replace("\r", "\n").split("\n")]
@@ -328,10 +380,15 @@ def normalize_output(stdout: str, stderr: str) -> str:
328
380
 
329
381
 
330
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"
331
388
  shell = os.getenv("SHELL") or os.getenv("COMSPEC") or ""
332
389
  if shell:
333
390
  return Path(shell).name
334
- return "powershell" if os.name == "nt" else "sh"
391
+ return "sh"
335
392
 
336
393
 
337
394
  def command_line(parts: list[str]) -> str:
@@ -632,25 +689,27 @@ def sync_events(home: Path, config: dict[str, Any], limit: int = 50, quiet: bool
632
689
 
633
690
 
634
691
  def capture_command(home: Path, config: dict[str, Any], command: str, include_excluded: bool, source: str) -> int:
692
+ if is_clear_command(command):
693
+ clear_console()
694
+ return 0
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
+
635
700
  require_verified_auth(home, config)
636
701
  workspace = Path(str(config.get("workspace_path") or ".")).expanduser()
637
702
  cwd = workspace if workspace.exists() else Path.cwd()
638
703
 
639
704
  if is_excluded(command, config) and not include_excluded:
640
705
  print(f"ai-memory: running without capture because this command is excluded: {command}", file=sys.stderr)
641
- return subprocess.call(command, shell=True, cwd=str(cwd))
706
+ return int(run_external_command(command, cwd, capture=False))
642
707
 
643
708
  started_at = utc_now()
644
709
  started_monotonic = time.monotonic()
645
- completed = subprocess.run(
646
- command,
647
- shell=True,
648
- cwd=str(cwd),
649
- capture_output=True,
650
- text=True,
651
- encoding="utf-8",
652
- errors="replace",
653
- )
710
+ completed = run_external_command(command, cwd, capture=True)
711
+ if isinstance(completed, int):
712
+ return completed
654
713
  ended_at = utc_now()
655
714
  duration_ms = int((time.monotonic() - started_monotonic) * 1000)
656
715
 
@@ -868,6 +927,12 @@ def command_watch(args: argparse.Namespace) -> int:
868
927
  command = cleaned_command
869
928
  if command.lower() in {"exit", "quit"}:
870
929
  break
930
+ if is_clear_command(command):
931
+ clear_console()
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
871
936
  capture_command(home, config, command, args.include_excluded, "watch")
872
937
  return 0
873
938
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-memory-cli
3
- Version: 0.1.7
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
@@ -68,6 +68,8 @@ Use `python -m ai_memory_cli run -- COMMAND` when you only want to record one co
68
68
  On Windows, `python -m ai_memory_cli ...` is the safest form because it avoids PATH issues and Device Guard policies that can block pip's generated `ai-memory.exe` launcher. Also avoid angle bracket placeholders in CMD because they are treated as file redirection.
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
+ 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.
71
73
 
72
74
  ## Background agent
73
75
 
File without changes
File without changes