ai-memory-cli 0.1.0__tar.gz → 0.1.5__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.
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-memory-cli
3
+ Version: 0.1.5
4
+ Summary: Python CLI for AI Memory terminal capture and offline sync.
5
+ Author: AI Memory
6
+ License-Expression: MIT
7
+ Keywords: ai,memory,cli,terminal,rag,developer-tools
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development
17
+ Classifier: Topic :: Terminals
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # AI Memory CLI
24
+
25
+ Standalone Python CLI for terminal capture, hashing, offline queueing, and sync to the temporary FastAPI backend.
26
+
27
+ ## Install
28
+
29
+ For any user machine after the package is published:
30
+
31
+ ```powershell
32
+ python -m pip install ai-memory-cli
33
+ ```
34
+
35
+ Before PyPI publish, install from GitHub:
36
+
37
+ ```powershell
38
+ python -m pip install "ai-memory-cli @ git+https://github.com/YOUR_ORG/ai-memory-cli.git"
39
+ ```
40
+
41
+ For local development from this CLI repo:
42
+
43
+ ```powershell
44
+ python -m pip install -e .
45
+ ```
46
+
47
+ ## Basic flow
48
+
49
+ ```powershell
50
+ python -m ai_memory_cli auth --token TOKEN_FROM_WEBSITE --api-url https://api.your-domain.com
51
+ python -m ai_memory_cli init --project my-project --repo owner/repo --workspace .
52
+ python -m ai_memory_cli workspace connect --path . --repo owner/repo --editor vscode --package-manager pip
53
+ watch
54
+ ```
55
+
56
+ `watch` is a shortcut for `python -m ai_memory_cli watch`. If Windows Device Guard blocks the generated launcher, keep using `python -m ai_memory_cli watch`.
57
+ On Windows the shortcut is installed as `watch.cmd`; the Python Scripts folder must be on `PATH` for bare `watch` to resolve.
58
+
59
+ Use `python -m ai_memory_cli run -- COMMAND` when you only want to record one command.
60
+
61
+ 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.
62
+
63
+ 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.
64
+
65
+ ## Background agent
66
+
67
+ The background agent starts at Windows logon and keeps syncing queued terminal hashes whenever the API is reachable:
68
+
69
+ ```powershell
70
+ python -m ai_memory_cli agent install
71
+ python -m ai_memory_cli agent start
72
+ python -m ai_memory_cli agent status
73
+ ```
74
+
75
+ The agent does not secretly capture every terminal on the computer. Commands are captured when they run through:
76
+
77
+ ```powershell
78
+ python -m ai_memory_cli watch
79
+ python -m ai_memory_cli run -- python --version
80
+ ```
81
+
82
+ To remove the startup task:
83
+
84
+ ```powershell
85
+ python -m ai_memory_cli agent stop
86
+ python -m ai_memory_cli agent uninstall
87
+ ```
88
+
89
+ Agent logs are written to `%USERPROFILE%\.ai-memory-cli\logs\agent.log`.
90
+
91
+ ## Storage
92
+
93
+ The CLI stores config and unsynced events in a separate folder:
94
+
95
+ - Windows: `%USERPROFILE%\.ai-memory-cli`
96
+ - macOS/Linux: `~/.ai-memory-cli`
97
+
98
+ Set `AI_MEMORY_CLI_HOME` to override this location.
99
+
100
+ Accepted command observations are also written as plain daily hash logs:
101
+
102
+ - Windows: `%USERPROFILE%\.ai-memory-cli\history\YYYY-MM-DD.log`
103
+ - macOS/Linux: `~/.ai-memory-cli/history/YYYY-MM-DD.log`
104
+
105
+ These files include time/date, event hash, command hash, output hash, source, exit code, and working-folder name. They do not store raw command text or raw output.
106
+
107
+ If a command is clearly invalid, such as a pasted prompt (`ai-memory> python --version`) or a shell "not recognized" error, the CLI skips storing it as an event.
108
+
109
+ ## Privacy and dedupe
110
+
111
+ The CLI does not send raw commands or raw output to the backend. It sends:
112
+
113
+ - `command_hash`
114
+ - `output_hash`
115
+ - `event_hash`
116
+ - timestamps, exit code, shell, project, repo, and local metadata
117
+
118
+ If the same command produces the same output again, the CLI keeps one event hash and increments `duplicate_count`.
119
+
120
+ ## Excluded commands
121
+
122
+ Long-running development commands are not captured by default. They still run, but no hash event is stored.
123
+
124
+ Default excluded patterns include:
125
+
126
+ - `npm run ...`
127
+ - `next dev`
128
+ - `vite`
129
+ - `uvicorn --reload`
130
+ - `python -m uvicorn ... --reload`
131
+
132
+ Use `--include-excluded` on `run` or `watch` if you need to capture them anyway.
133
+
134
+ ## Publish
135
+
136
+ After this folder is pushed as its own public GitHub repo, publish to PyPI with:
137
+
138
+ ```powershell
139
+ .\scripts\publish.ps1 -Repository pypi
140
+ ```
@@ -0,0 +1,118 @@
1
+ # AI Memory CLI
2
+
3
+ Standalone Python CLI for terminal capture, hashing, offline queueing, and sync to the temporary FastAPI backend.
4
+
5
+ ## Install
6
+
7
+ For any user machine after the package is published:
8
+
9
+ ```powershell
10
+ python -m pip install ai-memory-cli
11
+ ```
12
+
13
+ Before PyPI publish, install from GitHub:
14
+
15
+ ```powershell
16
+ python -m pip install "ai-memory-cli @ git+https://github.com/YOUR_ORG/ai-memory-cli.git"
17
+ ```
18
+
19
+ For local development from this CLI repo:
20
+
21
+ ```powershell
22
+ python -m pip install -e .
23
+ ```
24
+
25
+ ## Basic flow
26
+
27
+ ```powershell
28
+ python -m ai_memory_cli auth --token TOKEN_FROM_WEBSITE --api-url https://api.your-domain.com
29
+ python -m ai_memory_cli init --project my-project --repo owner/repo --workspace .
30
+ python -m ai_memory_cli workspace connect --path . --repo owner/repo --editor vscode --package-manager pip
31
+ watch
32
+ ```
33
+
34
+ `watch` is a shortcut for `python -m ai_memory_cli watch`. If Windows Device Guard blocks the generated launcher, keep using `python -m ai_memory_cli watch`.
35
+ On Windows the shortcut is installed as `watch.cmd`; the Python Scripts folder must be on `PATH` for bare `watch` to resolve.
36
+
37
+ Use `python -m ai_memory_cli run -- COMMAND` when you only want to record one command.
38
+
39
+ 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.
40
+
41
+ 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.
42
+
43
+ ## Background agent
44
+
45
+ The background agent starts at Windows logon and keeps syncing queued terminal hashes whenever the API is reachable:
46
+
47
+ ```powershell
48
+ python -m ai_memory_cli agent install
49
+ python -m ai_memory_cli agent start
50
+ python -m ai_memory_cli agent status
51
+ ```
52
+
53
+ The agent does not secretly capture every terminal on the computer. Commands are captured when they run through:
54
+
55
+ ```powershell
56
+ python -m ai_memory_cli watch
57
+ python -m ai_memory_cli run -- python --version
58
+ ```
59
+
60
+ To remove the startup task:
61
+
62
+ ```powershell
63
+ python -m ai_memory_cli agent stop
64
+ python -m ai_memory_cli agent uninstall
65
+ ```
66
+
67
+ Agent logs are written to `%USERPROFILE%\.ai-memory-cli\logs\agent.log`.
68
+
69
+ ## Storage
70
+
71
+ The CLI stores config and unsynced events in a separate folder:
72
+
73
+ - Windows: `%USERPROFILE%\.ai-memory-cli`
74
+ - macOS/Linux: `~/.ai-memory-cli`
75
+
76
+ Set `AI_MEMORY_CLI_HOME` to override this location.
77
+
78
+ Accepted command observations are also written as plain daily hash logs:
79
+
80
+ - Windows: `%USERPROFILE%\.ai-memory-cli\history\YYYY-MM-DD.log`
81
+ - macOS/Linux: `~/.ai-memory-cli/history/YYYY-MM-DD.log`
82
+
83
+ These files include time/date, event hash, command hash, output hash, source, exit code, and working-folder name. They do not store raw command text or raw output.
84
+
85
+ If a command is clearly invalid, such as a pasted prompt (`ai-memory> python --version`) or a shell "not recognized" error, the CLI skips storing it as an event.
86
+
87
+ ## Privacy and dedupe
88
+
89
+ The CLI does not send raw commands or raw output to the backend. It sends:
90
+
91
+ - `command_hash`
92
+ - `output_hash`
93
+ - `event_hash`
94
+ - timestamps, exit code, shell, project, repo, and local metadata
95
+
96
+ If the same command produces the same output again, the CLI keeps one event hash and increments `duplicate_count`.
97
+
98
+ ## Excluded commands
99
+
100
+ Long-running development commands are not captured by default. They still run, but no hash event is stored.
101
+
102
+ Default excluded patterns include:
103
+
104
+ - `npm run ...`
105
+ - `next dev`
106
+ - `vite`
107
+ - `uvicorn --reload`
108
+ - `python -m uvicorn ... --reload`
109
+
110
+ Use `--include-excluded` on `run` or `watch` if you need to capture them anyway.
111
+
112
+ ## Publish
113
+
114
+ After this folder is pushed as its own public GitHub repo, publish to PyPI with:
115
+
116
+ ```powershell
117
+ .\scripts\publish.ps1 -Repository pypi
118
+ ```
@@ -0,0 +1,7 @@
1
+ @echo off
2
+ set "AI_MEMORY_PY=%~dp0python.exe"
3
+ if exist "%AI_MEMORY_PY%" (
4
+ "%AI_MEMORY_PY%" -m ai_memory_cli watch %*
5
+ ) else (
6
+ python -m ai_memory_cli watch %*
7
+ )
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ai-memory-cli"
7
- version = "0.1.0"
7
+ version = "0.1.5"
8
8
  description = "Python CLI for AI Memory terminal capture and offline sync."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -29,5 +29,8 @@ dependencies = []
29
29
  [project.scripts]
30
30
  ai-memory = "ai_memory_cli.cli:main"
31
31
 
32
+ [tool.setuptools]
33
+ script-files = ["bin/watch.cmd"]
34
+
32
35
  [tool.setuptools.packages.find]
33
36
  where = ["src"]
@@ -1,3 +1,3 @@
1
1
  """AI Memory terminal capture CLI."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.5"
@@ -18,6 +18,8 @@ from typing import Any
18
18
  from . import __version__
19
19
 
20
20
  DEFAULT_API_URL = "http://127.0.0.1:8000"
21
+ WINDOWS_AGENT_TASK_NAME = "AI Memory CLI Agent"
22
+ DEFAULT_AGENT_INTERVAL_SECONDS = 60
21
23
  DEFAULT_EXCLUDES = [
22
24
  r"^\s*npm\s+run(\s|$)",
23
25
  r"^\s*npm\s+start(\s|$)",
@@ -30,6 +32,14 @@ DEFAULT_EXCLUDES = [
30
32
  r"uvicorn\b.*\s--reload(\s|$)",
31
33
  r"python(\.exe)?\s+-m\s+uvicorn\b.*\s--reload(\s|$)",
32
34
  ]
35
+ SHELL_NOT_FOUND_PATTERNS = [
36
+ "is not recognized as an internal or external command",
37
+ "is not recognized as the name of a cmdlet",
38
+ "the system cannot find the file specified",
39
+ "the syntax of the command is incorrect",
40
+ "no such file or directory",
41
+ "command not found",
42
+ ]
33
43
 
34
44
 
35
45
  def utc_now() -> str:
@@ -50,7 +60,7 @@ def cli_home() -> Path:
50
60
 
51
61
 
52
62
  def ensure_dirs(home: Path) -> None:
53
- for folder in ["events", "outbox", "sent", "logs"]:
63
+ for folder in ["events", "outbox", "sent", "logs", "history"]:
54
64
  (home / folder).mkdir(parents=True, exist_ok=True)
55
65
 
56
66
 
@@ -70,6 +80,32 @@ def write_json(path: Path, payload: Any) -> None:
70
80
  tmp_path.replace(path)
71
81
 
72
82
 
83
+ def append_log(home: Path, message: str) -> None:
84
+ ensure_dirs(home)
85
+ log_path = home / "logs" / "agent.log"
86
+ with log_path.open("a", encoding="utf-8") as file:
87
+ file.write(f"{utc_now()} {message}\n")
88
+
89
+
90
+ def append_history(home: Path, event: dict[str, Any], state: str) -> None:
91
+ ensure_dirs(home)
92
+ timestamp = str(event.get("observed_at") or event.get("ended_at") or utc_now())
93
+ day = timestamp[:10] if len(timestamp) >= 10 else utc_now()[:10]
94
+ history_path = home / "history" / f"{day}.log"
95
+ fields = [
96
+ timestamp,
97
+ f"state={state}",
98
+ f"source={event.get('source', '-')}",
99
+ f"exit={event.get('exit_code', '-')}",
100
+ f"event={str(event.get('event_hash', ''))[:12]}",
101
+ f"command_hash={event.get('command_hash', '-')}",
102
+ f"output_hash={event.get('output_hash', '-')}",
103
+ f"cwd={event.get('metadata', {}).get('cwd_tail', '-')}",
104
+ ]
105
+ with history_path.open("a", encoding="utf-8") as file:
106
+ file.write(" ".join(fields) + "\n")
107
+
108
+
73
109
  def config_path(home: Path) -> Path:
74
110
  return home / "config.json"
75
111
 
@@ -90,10 +126,92 @@ def save_config(home: Path, config: dict[str, Any]) -> None:
90
126
  write_json(config_path(home), config)
91
127
 
92
128
 
129
+ def agent_state_path(home: Path) -> Path:
130
+ return home / "agent.json"
131
+
132
+
133
+ def scheduler_python_executable(background: bool = True) -> str:
134
+ executable = Path(sys.executable)
135
+ if os.name == "nt" and background:
136
+ pythonw = executable.with_name("pythonw.exe")
137
+ if pythonw.exists():
138
+ return str(pythonw)
139
+ return str(executable)
140
+
141
+
142
+ def windows_startup_dir() -> Path:
143
+ appdata = os.getenv("APPDATA")
144
+ if not appdata:
145
+ raise SystemExit("APPDATA is not set; cannot locate the Windows Startup folder.")
146
+ return Path(appdata) / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup"
147
+
148
+
149
+ def windows_startup_script_path() -> Path:
150
+ return windows_startup_dir() / "AI Memory CLI Agent.vbs"
151
+
152
+
153
+ def write_windows_startup_script(interval: int, limit: int) -> Path:
154
+ startup_dir = windows_startup_dir()
155
+ startup_dir.mkdir(parents=True, exist_ok=True)
156
+ script_path = windows_startup_script_path()
157
+ python_executable = scheduler_python_executable(background=False)
158
+ command = f'"{python_executable}" -m ai_memory_cli agent run --interval {interval} --limit {limit}'
159
+ escaped_command = command.replace('"', '""')
160
+ script_path.write_text(
161
+ "\n".join(
162
+ [
163
+ "Set shell = CreateObject(\"WScript.Shell\")",
164
+ f"shell.Run \"{escaped_command}\", 0, False",
165
+ "",
166
+ ]
167
+ ),
168
+ encoding="utf-8",
169
+ )
170
+ return script_path
171
+
172
+
173
+ def start_detached_agent(interval: int, limit: int) -> int:
174
+ python_executable = scheduler_python_executable(background=True)
175
+ command = [
176
+ python_executable,
177
+ "-m",
178
+ "ai_memory_cli",
179
+ "agent",
180
+ "run",
181
+ "--interval",
182
+ str(interval),
183
+ "--limit",
184
+ str(limit),
185
+ ]
186
+ creationflags = 0
187
+ if os.name == "nt":
188
+ creationflags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
189
+ if hasattr(subprocess, "CREATE_NO_WINDOW"):
190
+ creationflags |= subprocess.CREATE_NO_WINDOW
191
+
192
+ process = subprocess.Popen(
193
+ command,
194
+ stdin=subprocess.DEVNULL,
195
+ stdout=subprocess.DEVNULL,
196
+ stderr=subprocess.DEVNULL,
197
+ close_fds=True,
198
+ creationflags=creationflags,
199
+ )
200
+ return int(process.pid)
201
+
202
+
93
203
  def normalize_command(command: str) -> str:
94
204
  return " ".join(command.strip().split())
95
205
 
96
206
 
207
+ def clean_watch_command(command: str) -> str:
208
+ cleaned = command.strip()
209
+ while cleaned.lower().startswith("ai-memory>"):
210
+ cleaned = cleaned[len("ai-memory>") :].strip()
211
+ cleaned = re.sub(r"^[A-Za-z]:\\[^>]*>\s*", "", cleaned).strip()
212
+ return cleaned
213
+
214
+
97
215
  def normalize_output(stdout: str, stderr: str) -> str:
98
216
  combined = f"stdout:\n{stdout}\nstderr:\n{stderr}"
99
217
  lines = [line.rstrip() for line in combined.replace("\r\n", "\n").replace("\r", "\n").split("\n")]
@@ -123,6 +241,13 @@ def is_excluded(command: str, config: dict[str, Any]) -> bool:
123
241
  return any(re.search(pattern, command, flags=re.IGNORECASE) for pattern in patterns)
124
242
 
125
243
 
244
+ def is_shell_not_found(stdout: str, stderr: str, exit_code: int | None) -> bool:
245
+ if exit_code in (None, 0):
246
+ return False
247
+ combined = f"{stdout}\n{stderr}".lower()
248
+ return any(pattern in combined for pattern in SHELL_NOT_FOUND_PATTERNS)
249
+
250
+
126
251
  def api_url(config: dict[str, Any]) -> str:
127
252
  return str(config.get("api_url") or DEFAULT_API_URL).rstrip("/")
128
253
 
@@ -130,7 +255,7 @@ def api_url(config: dict[str, Any]) -> str:
130
255
  def require_token(config: dict[str, Any]) -> str:
131
256
  token = str(config.get("token") or "").strip()
132
257
  if not token:
133
- raise SystemExit("Run ai-memory auth --token <app-issued-cli-token> first.")
258
+ raise SystemExit("Run python -m ai_memory_cli auth --token TOKEN_FROM_WEBSITE first.")
134
259
  return token
135
260
 
136
261
 
@@ -261,7 +386,7 @@ def sync_events(home: Path, config: dict[str, Any], limit: int = 50, quiet: bool
261
386
  token = str(config.get("token") or "").strip()
262
387
  if not token:
263
388
  if not quiet:
264
- print("No CLI token saved. Events remain queued until ai-memory auth is configured.")
389
+ print("No CLI token saved. Events remain queued until python -m ai_memory_cli auth is configured.")
265
390
  return 0
266
391
  paths = sorted((home / "outbox").glob("*.json"))[:limit]
267
392
  if not paths:
@@ -322,6 +447,10 @@ def capture_command(home: Path, config: dict[str, Any], command: str, include_ex
322
447
  if completed.stderr:
323
448
  print(completed.stderr, end="", file=sys.stderr)
324
449
 
450
+ if is_shell_not_found(completed.stdout or "", completed.stderr or "", completed.returncode):
451
+ print("ai-memory: skipped invalid command; nothing was stored.")
452
+ return completed.returncode
453
+
325
454
  event = make_terminal_event(
326
455
  command=command,
327
456
  stdout=completed.stdout or "",
@@ -336,6 +465,7 @@ def capture_command(home: Path, config: dict[str, Any], command: str, include_ex
336
465
  )
337
466
  created, event_path = store_event(home, event)
338
467
  state = "stored" if created else "deduped"
468
+ append_history(home, event, state)
339
469
  print(f"ai-memory: {state} terminal hash {event['event_hash'][:12]} at {event_path}")
340
470
 
341
471
  try:
@@ -408,7 +538,7 @@ def command_init(args: argparse.Namespace) -> int:
408
538
  print(f"Initialized project: {project.get('id', payload['project'])}")
409
539
  except Exception as exc:
410
540
  print(f"Project config saved locally. Server init will need retry: {exc}", file=sys.stderr)
411
- print("Start terminal capture with: ai-memory watch")
541
+ print("Start terminal capture with: python -m ai_memory_cli watch")
412
542
  return 0
413
543
 
414
544
 
@@ -484,7 +614,7 @@ def command_chat_connect(args: argparse.Namespace) -> int:
484
614
  def command_run(args: argparse.Namespace) -> int:
485
615
  command = command_line(args.command)
486
616
  if not command:
487
- raise SystemExit("Pass a command after --, for example: ai-memory run -- python --version")
617
+ raise SystemExit("Pass a command after --, for example: python -m ai_memory_cli run -- python --version")
488
618
  home = cli_home()
489
619
  config = load_config(home)
490
620
  return capture_command(home, config, command, args.include_excluded, "run")
@@ -502,6 +632,13 @@ def command_watch(args: argparse.Namespace) -> int:
502
632
  break
503
633
  if not command:
504
634
  continue
635
+ cleaned_command = clean_watch_command(command)
636
+ if cleaned_command != command:
637
+ if not cleaned_command:
638
+ print("ai-memory: skipped pasted prompt without a command.")
639
+ continue
640
+ print(f"ai-memory: using command without pasted prompt: {cleaned_command}")
641
+ command = cleaned_command
505
642
  if command.lower() in {"exit", "quit"}:
506
643
  break
507
644
  capture_command(home, config, command, args.include_excluded, "watch")
@@ -557,6 +694,224 @@ def command_sync(args: argparse.Namespace) -> int:
557
694
  return 0
558
695
 
559
696
 
697
+ def command_agent_run(args: argparse.Namespace) -> int:
698
+ home = cli_home()
699
+ ensure_dirs(home)
700
+ interval = max(10, int(args.interval))
701
+ limit = max(1, int(args.limit))
702
+ state = {
703
+ "pid": os.getpid(),
704
+ "version": __version__,
705
+ "started_at": utc_now(),
706
+ "interval_seconds": interval,
707
+ "limit": limit,
708
+ "mode": "once" if args.once else "loop",
709
+ }
710
+ write_json(agent_state_path(home), state)
711
+ append_log(home, f"agent started pid={os.getpid()} interval={interval}s limit={limit}")
712
+
713
+ try:
714
+ while True:
715
+ config = load_config(home)
716
+ try:
717
+ synced = sync_events(home, config, limit=limit, quiet=True)
718
+ if synced:
719
+ append_log(home, f"synced {synced} terminal event(s)")
720
+ except Exception as exc:
721
+ append_log(home, f"sync failed: {exc}")
722
+
723
+ if args.once:
724
+ break
725
+ time.sleep(interval)
726
+ finally:
727
+ state["stopped_at"] = utc_now()
728
+ write_json(agent_state_path(home), state)
729
+ append_log(home, "agent stopped")
730
+
731
+ return 0
732
+
733
+
734
+ def command_agent_install(args: argparse.Namespace) -> int:
735
+ if os.name != "nt":
736
+ raise SystemExit("agent install currently supports Windows Task Scheduler only.")
737
+
738
+ home = cli_home()
739
+ config = load_config(home)
740
+ config["agent"] = {
741
+ "task_name": args.task_name,
742
+ "interval_seconds": args.interval,
743
+ "limit": args.limit,
744
+ "installed_at": utc_now(),
745
+ }
746
+ save_config(home, config)
747
+
748
+ if args.method in {"auto", "task"}:
749
+ python_executable = scheduler_python_executable(background=not args.console)
750
+ task_command = (
751
+ f'"{python_executable}" -m ai_memory_cli agent run '
752
+ f"--interval {int(args.interval)} --limit {int(args.limit)}"
753
+ )
754
+ result = subprocess.run(
755
+ [
756
+ "schtasks",
757
+ "/Create",
758
+ "/TN",
759
+ args.task_name,
760
+ "/SC",
761
+ "ONLOGON",
762
+ "/TR",
763
+ task_command,
764
+ "/F",
765
+ ],
766
+ capture_output=True,
767
+ text=True,
768
+ encoding="utf-8",
769
+ errors="replace",
770
+ )
771
+ if result.returncode == 0:
772
+ append_log(home, f"installed Windows scheduled task: {args.task_name}")
773
+ print(f"Installed startup agent task: {args.task_name}")
774
+ print("It starts when you log in. Start it now with:")
775
+ print("python -m ai_memory_cli agent start")
776
+ return 0
777
+
778
+ if args.method == "task":
779
+ raise SystemExit((result.stderr or result.stdout).strip())
780
+
781
+ print("Task Scheduler install failed; falling back to user Startup folder.")
782
+ print((result.stderr or result.stdout).strip())
783
+
784
+ script_path = write_windows_startup_script(int(args.interval), int(args.limit))
785
+ append_log(home, f"installed Windows startup script: {script_path}")
786
+ print(f"Installed startup agent script: {script_path}")
787
+ print("It starts when you log in. Start it now with:")
788
+ print("python -m ai_memory_cli agent run")
789
+ return 0
790
+
791
+
792
+ def command_agent_uninstall(args: argparse.Namespace) -> int:
793
+ if os.name != "nt":
794
+ raise SystemExit("agent uninstall currently supports Windows Task Scheduler only.")
795
+
796
+ removed = False
797
+ result = subprocess.run(
798
+ ["schtasks", "/Delete", "/TN", args.task_name, "/F"],
799
+ capture_output=True,
800
+ text=True,
801
+ encoding="utf-8",
802
+ errors="replace",
803
+ )
804
+ if result.returncode == 0:
805
+ removed = True
806
+
807
+ script_path = windows_startup_script_path()
808
+ if script_path.exists():
809
+ script_path.unlink()
810
+ removed = True
811
+
812
+ append_log(cli_home(), f"uninstalled Windows scheduled task: {args.task_name}")
813
+ if removed:
814
+ print("Removed startup agent registration.")
815
+ else:
816
+ print("No startup agent registration was found.")
817
+ return 0
818
+
819
+
820
+ def command_agent_start(args: argparse.Namespace) -> int:
821
+ if os.name != "nt":
822
+ raise SystemExit("agent start currently supports Windows Task Scheduler only.")
823
+
824
+ home = cli_home()
825
+ config = load_config(home)
826
+ result = subprocess.run(
827
+ ["schtasks", "/Run", "/TN", args.task_name],
828
+ capture_output=True,
829
+ text=True,
830
+ encoding="utf-8",
831
+ errors="replace",
832
+ )
833
+ if result.returncode == 0:
834
+ print(f"Started agent task: {args.task_name}")
835
+ return 0
836
+
837
+ agent_config = config.get("agent") if isinstance(config.get("agent"), dict) else {}
838
+ interval = int(agent_config.get("interval_seconds") or DEFAULT_AGENT_INTERVAL_SECONDS)
839
+ limit = int(agent_config.get("limit") or 50)
840
+ pid = start_detached_agent(interval, limit)
841
+ append_log(home, f"started detached agent pid={pid}")
842
+ print(f"Started detached agent process: pid={pid}")
843
+ return 0
844
+
845
+
846
+ def command_agent_stop(args: argparse.Namespace) -> int:
847
+ if os.name != "nt":
848
+ raise SystemExit("agent stop currently supports Windows Task Scheduler only.")
849
+
850
+ home = cli_home()
851
+ result = subprocess.run(
852
+ ["schtasks", "/End", "/TN", args.task_name],
853
+ capture_output=True,
854
+ text=True,
855
+ encoding="utf-8",
856
+ errors="replace",
857
+ )
858
+ if result.returncode == 0:
859
+ append_log(home, f"stopped Windows scheduled task: {args.task_name}")
860
+ print(f"Stopped agent task: {args.task_name}")
861
+ return 0
862
+
863
+ state = read_json(agent_state_path(home), {})
864
+ pid = state.get("pid")
865
+ if not pid:
866
+ print("No running detached agent pid was found.")
867
+ return 0
868
+
869
+ kill = subprocess.run(
870
+ ["taskkill", "/PID", str(pid), "/F"],
871
+ capture_output=True,
872
+ text=True,
873
+ encoding="utf-8",
874
+ errors="replace",
875
+ )
876
+ if kill.returncode != 0:
877
+ raise SystemExit((kill.stderr or kill.stdout).strip())
878
+ append_log(home, f"stopped detached agent pid={pid}")
879
+ print(f"Stopped detached agent process: pid={pid}")
880
+ return 0
881
+
882
+
883
+ def command_agent_status(args: argparse.Namespace) -> int:
884
+ home = cli_home()
885
+ config = load_config(home)
886
+ state = read_json(agent_state_path(home), {})
887
+ print(f"Storage: {home}")
888
+ print(f"API: {api_url(config)}")
889
+ print(f"Token: {'saved' if config.get('token') else 'missing'}")
890
+ if state:
891
+ print(f"Agent state: pid={state.get('pid', '-')} started={state.get('started_at', '-')}")
892
+ else:
893
+ print("Agent state: no local agent state file yet")
894
+
895
+ if os.name != "nt":
896
+ return 0
897
+
898
+ result = subprocess.run(
899
+ ["schtasks", "/Query", "/TN", args.task_name, "/FO", "LIST", "/V"],
900
+ capture_output=True,
901
+ text=True,
902
+ encoding="utf-8",
903
+ errors="replace",
904
+ )
905
+ if result.returncode != 0:
906
+ print(f"Windows task: not installed ({args.task_name})")
907
+ else:
908
+ print(result.stdout.strip())
909
+
910
+ script_path = windows_startup_script_path()
911
+ print(f"Startup script: {'installed' if script_path.exists() else 'not installed'} ({script_path})")
912
+ return 0
913
+
914
+
560
915
  def command_status(_: argparse.Namespace) -> int:
561
916
  home = cli_home()
562
917
  config = load_config(home)
@@ -638,6 +993,7 @@ def build_parser() -> argparse.ArgumentParser:
638
993
 
639
994
  watch = subparsers.add_parser("watch", help="Start a managed terminal that captures commands and output.")
640
995
  watch.add_argument("--include-excluded", action="store_true")
996
+ watch.add_argument("--version", action="version", version=f"ai-memory {__version__}")
641
997
  watch.set_defaults(func=command_watch)
642
998
 
643
999
  history = subparsers.add_parser("history", help="History import commands.")
@@ -652,6 +1008,39 @@ def build_parser() -> argparse.ArgumentParser:
652
1008
  sync.add_argument("--limit", type=int, default=50)
653
1009
  sync.set_defaults(func=command_sync)
654
1010
 
1011
+ agent = subparsers.add_parser("agent", help="Background sync agent commands.")
1012
+ agent_subparsers = agent.add_subparsers(dest="agent_command", required=True)
1013
+
1014
+ agent_run = agent_subparsers.add_parser("run", help="Run the background sync loop.")
1015
+ agent_run.add_argument("--interval", type=int, default=DEFAULT_AGENT_INTERVAL_SECONDS)
1016
+ agent_run.add_argument("--limit", type=int, default=50)
1017
+ agent_run.add_argument("--once", action="store_true")
1018
+ agent_run.set_defaults(func=command_agent_run)
1019
+
1020
+ agent_install = agent_subparsers.add_parser("install", help="Install Windows startup task for the sync agent.")
1021
+ agent_install.add_argument("--interval", type=int, default=DEFAULT_AGENT_INTERVAL_SECONDS)
1022
+ agent_install.add_argument("--limit", type=int, default=50)
1023
+ agent_install.add_argument("--task-name", default=WINDOWS_AGENT_TASK_NAME)
1024
+ agent_install.add_argument("--method", choices=["auto", "task", "startup"], default="auto")
1025
+ agent_install.add_argument("--console", action="store_true", help="Use python.exe instead of pythonw.exe for the scheduled task.")
1026
+ agent_install.set_defaults(func=command_agent_install)
1027
+
1028
+ agent_uninstall = agent_subparsers.add_parser("uninstall", help="Remove Windows startup task for the sync agent.")
1029
+ agent_uninstall.add_argument("--task-name", default=WINDOWS_AGENT_TASK_NAME)
1030
+ agent_uninstall.set_defaults(func=command_agent_uninstall)
1031
+
1032
+ agent_start = agent_subparsers.add_parser("start", help="Start the installed Windows agent task now.")
1033
+ agent_start.add_argument("--task-name", default=WINDOWS_AGENT_TASK_NAME)
1034
+ agent_start.set_defaults(func=command_agent_start)
1035
+
1036
+ agent_stop = agent_subparsers.add_parser("stop", help="Stop the installed Windows agent task.")
1037
+ agent_stop.add_argument("--task-name", default=WINDOWS_AGENT_TASK_NAME)
1038
+ agent_stop.set_defaults(func=command_agent_stop)
1039
+
1040
+ agent_status = agent_subparsers.add_parser("status", help="Show background agent and Windows task state.")
1041
+ agent_status.add_argument("--task-name", default=WINDOWS_AGENT_TASK_NAME)
1042
+ agent_status.set_defaults(func=command_agent_status)
1043
+
655
1044
  status = subparsers.add_parser("status", help="Show local CLI state.")
656
1045
  status.set_defaults(func=command_status)
657
1046
 
@@ -669,3 +1058,15 @@ def main(argv: list[str] | None = None) -> int:
669
1058
  except KeyboardInterrupt:
670
1059
  print("Interrupted.", file=sys.stderr)
671
1060
  return 130
1061
+
1062
+
1063
+ def watch_main(argv: list[str] | None = None) -> int:
1064
+ parser = argparse.ArgumentParser(prog="watch", description="Start AI Memory terminal capture.")
1065
+ parser.add_argument("--include-excluded", action="store_true")
1066
+ parser.add_argument("--version", action="version", version=f"ai-memory {__version__}")
1067
+ args = parser.parse_args(argv)
1068
+ try:
1069
+ return command_watch(args)
1070
+ except KeyboardInterrupt:
1071
+ print("Interrupted.", file=sys.stderr)
1072
+ return 130
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-memory-cli
3
+ Version: 0.1.5
4
+ Summary: Python CLI for AI Memory terminal capture and offline sync.
5
+ Author: AI Memory
6
+ License-Expression: MIT
7
+ Keywords: ai,memory,cli,terminal,rag,developer-tools
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development
17
+ Classifier: Topic :: Terminals
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # AI Memory CLI
24
+
25
+ Standalone Python CLI for terminal capture, hashing, offline queueing, and sync to the temporary FastAPI backend.
26
+
27
+ ## Install
28
+
29
+ For any user machine after the package is published:
30
+
31
+ ```powershell
32
+ python -m pip install ai-memory-cli
33
+ ```
34
+
35
+ Before PyPI publish, install from GitHub:
36
+
37
+ ```powershell
38
+ python -m pip install "ai-memory-cli @ git+https://github.com/YOUR_ORG/ai-memory-cli.git"
39
+ ```
40
+
41
+ For local development from this CLI repo:
42
+
43
+ ```powershell
44
+ python -m pip install -e .
45
+ ```
46
+
47
+ ## Basic flow
48
+
49
+ ```powershell
50
+ python -m ai_memory_cli auth --token TOKEN_FROM_WEBSITE --api-url https://api.your-domain.com
51
+ python -m ai_memory_cli init --project my-project --repo owner/repo --workspace .
52
+ python -m ai_memory_cli workspace connect --path . --repo owner/repo --editor vscode --package-manager pip
53
+ watch
54
+ ```
55
+
56
+ `watch` is a shortcut for `python -m ai_memory_cli watch`. If Windows Device Guard blocks the generated launcher, keep using `python -m ai_memory_cli watch`.
57
+ On Windows the shortcut is installed as `watch.cmd`; the Python Scripts folder must be on `PATH` for bare `watch` to resolve.
58
+
59
+ Use `python -m ai_memory_cli run -- COMMAND` when you only want to record one command.
60
+
61
+ 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.
62
+
63
+ 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.
64
+
65
+ ## Background agent
66
+
67
+ The background agent starts at Windows logon and keeps syncing queued terminal hashes whenever the API is reachable:
68
+
69
+ ```powershell
70
+ python -m ai_memory_cli agent install
71
+ python -m ai_memory_cli agent start
72
+ python -m ai_memory_cli agent status
73
+ ```
74
+
75
+ The agent does not secretly capture every terminal on the computer. Commands are captured when they run through:
76
+
77
+ ```powershell
78
+ python -m ai_memory_cli watch
79
+ python -m ai_memory_cli run -- python --version
80
+ ```
81
+
82
+ To remove the startup task:
83
+
84
+ ```powershell
85
+ python -m ai_memory_cli agent stop
86
+ python -m ai_memory_cli agent uninstall
87
+ ```
88
+
89
+ Agent logs are written to `%USERPROFILE%\.ai-memory-cli\logs\agent.log`.
90
+
91
+ ## Storage
92
+
93
+ The CLI stores config and unsynced events in a separate folder:
94
+
95
+ - Windows: `%USERPROFILE%\.ai-memory-cli`
96
+ - macOS/Linux: `~/.ai-memory-cli`
97
+
98
+ Set `AI_MEMORY_CLI_HOME` to override this location.
99
+
100
+ Accepted command observations are also written as plain daily hash logs:
101
+
102
+ - Windows: `%USERPROFILE%\.ai-memory-cli\history\YYYY-MM-DD.log`
103
+ - macOS/Linux: `~/.ai-memory-cli/history/YYYY-MM-DD.log`
104
+
105
+ These files include time/date, event hash, command hash, output hash, source, exit code, and working-folder name. They do not store raw command text or raw output.
106
+
107
+ If a command is clearly invalid, such as a pasted prompt (`ai-memory> python --version`) or a shell "not recognized" error, the CLI skips storing it as an event.
108
+
109
+ ## Privacy and dedupe
110
+
111
+ The CLI does not send raw commands or raw output to the backend. It sends:
112
+
113
+ - `command_hash`
114
+ - `output_hash`
115
+ - `event_hash`
116
+ - timestamps, exit code, shell, project, repo, and local metadata
117
+
118
+ If the same command produces the same output again, the CLI keeps one event hash and increments `duplicate_count`.
119
+
120
+ ## Excluded commands
121
+
122
+ Long-running development commands are not captured by default. They still run, but no hash event is stored.
123
+
124
+ Default excluded patterns include:
125
+
126
+ - `npm run ...`
127
+ - `next dev`
128
+ - `vite`
129
+ - `uvicorn --reload`
130
+ - `python -m uvicorn ... --reload`
131
+
132
+ Use `--include-excluded` on `run` or `watch` if you need to capture them anyway.
133
+
134
+ ## Publish
135
+
136
+ After this folder is pushed as its own public GitHub repo, publish to PyPI with:
137
+
138
+ ```powershell
139
+ .\scripts\publish.ps1 -Repository pypi
140
+ ```
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
+ bin/watch.cmd
4
5
  src/ai_memory_cli/__init__.py
5
6
  src/ai_memory_cli/__main__.py
6
7
  src/ai_memory_cli/cli.py
@@ -1,98 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ai-memory-cli
3
- Version: 0.1.0
4
- Summary: Python CLI for AI Memory terminal capture and offline sync.
5
- Author: AI Memory
6
- License-Expression: MIT
7
- Keywords: ai,memory,cli,terminal,rag,developer-tools
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Environment :: Console
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Topic :: Software Development
17
- Classifier: Topic :: Terminals
18
- Requires-Python: >=3.10
19
- Description-Content-Type: text/markdown
20
- License-File: LICENSE
21
- Dynamic: license-file
22
-
23
- # AI Memory CLI
24
-
25
- Standalone Python CLI for terminal capture, hashing, offline queueing, and sync to the temporary FastAPI backend.
26
-
27
- ## Install
28
-
29
- For any user machine after the package is published:
30
-
31
- ```powershell
32
- python -m pip install ai-memory-cli
33
- ```
34
-
35
- Before PyPI publish, install from GitHub:
36
-
37
- ```powershell
38
- python -m pip install "ai-memory-cli @ git+https://github.com/YOUR_ORG/ai-memory-cli.git"
39
- ```
40
-
41
- For local development from this CLI repo:
42
-
43
- ```powershell
44
- python -m pip install -e .
45
- ```
46
-
47
- ## Basic flow
48
-
49
- ```powershell
50
- ai-memory auth --token <app-issued-cli-token> --api-url https://api.your-domain.com
51
- ai-memory init --project my-project --repo owner/repo --workspace .
52
- ai-memory workspace connect --path . --repo owner/repo --editor vscode --package-manager pip
53
- ai-memory watch
54
- ```
55
-
56
- Use `ai-memory run -- <command>` when you only want to record one command.
57
-
58
- ## Storage
59
-
60
- The CLI stores config and unsynced events in a separate folder:
61
-
62
- - Windows: `%USERPROFILE%\.ai-memory-cli`
63
- - macOS/Linux: `~/.ai-memory-cli`
64
-
65
- Set `AI_MEMORY_CLI_HOME` to override this location.
66
-
67
- ## Privacy and dedupe
68
-
69
- The CLI does not send raw commands or raw output to the backend. It sends:
70
-
71
- - `command_hash`
72
- - `output_hash`
73
- - `event_hash`
74
- - timestamps, exit code, shell, project, repo, and local metadata
75
-
76
- If the same command produces the same output again, the CLI keeps one event hash and increments `duplicate_count`.
77
-
78
- ## Excluded commands
79
-
80
- Long-running development commands are not captured by default. They still run, but no hash event is stored.
81
-
82
- Default excluded patterns include:
83
-
84
- - `npm run ...`
85
- - `next dev`
86
- - `vite`
87
- - `uvicorn --reload`
88
- - `python -m uvicorn ... --reload`
89
-
90
- Use `--include-excluded` on `run` or `watch` if you need to capture them anyway.
91
-
92
- ## Publish
93
-
94
- After this folder is pushed as its own public GitHub repo, publish to PyPI with:
95
-
96
- ```powershell
97
- .\scripts\publish.ps1 -Repository pypi
98
- ```
@@ -1,76 +0,0 @@
1
- # AI Memory CLI
2
-
3
- Standalone Python CLI for terminal capture, hashing, offline queueing, and sync to the temporary FastAPI backend.
4
-
5
- ## Install
6
-
7
- For any user machine after the package is published:
8
-
9
- ```powershell
10
- python -m pip install ai-memory-cli
11
- ```
12
-
13
- Before PyPI publish, install from GitHub:
14
-
15
- ```powershell
16
- python -m pip install "ai-memory-cli @ git+https://github.com/YOUR_ORG/ai-memory-cli.git"
17
- ```
18
-
19
- For local development from this CLI repo:
20
-
21
- ```powershell
22
- python -m pip install -e .
23
- ```
24
-
25
- ## Basic flow
26
-
27
- ```powershell
28
- ai-memory auth --token <app-issued-cli-token> --api-url https://api.your-domain.com
29
- ai-memory init --project my-project --repo owner/repo --workspace .
30
- ai-memory workspace connect --path . --repo owner/repo --editor vscode --package-manager pip
31
- ai-memory watch
32
- ```
33
-
34
- Use `ai-memory run -- <command>` when you only want to record one command.
35
-
36
- ## Storage
37
-
38
- The CLI stores config and unsynced events in a separate folder:
39
-
40
- - Windows: `%USERPROFILE%\.ai-memory-cli`
41
- - macOS/Linux: `~/.ai-memory-cli`
42
-
43
- Set `AI_MEMORY_CLI_HOME` to override this location.
44
-
45
- ## Privacy and dedupe
46
-
47
- The CLI does not send raw commands or raw output to the backend. It sends:
48
-
49
- - `command_hash`
50
- - `output_hash`
51
- - `event_hash`
52
- - timestamps, exit code, shell, project, repo, and local metadata
53
-
54
- If the same command produces the same output again, the CLI keeps one event hash and increments `duplicate_count`.
55
-
56
- ## Excluded commands
57
-
58
- Long-running development commands are not captured by default. They still run, but no hash event is stored.
59
-
60
- Default excluded patterns include:
61
-
62
- - `npm run ...`
63
- - `next dev`
64
- - `vite`
65
- - `uvicorn --reload`
66
- - `python -m uvicorn ... --reload`
67
-
68
- Use `--include-excluded` on `run` or `watch` if you need to capture them anyway.
69
-
70
- ## Publish
71
-
72
- After this folder is pushed as its own public GitHub repo, publish to PyPI with:
73
-
74
- ```powershell
75
- .\scripts\publish.ps1 -Repository pypi
76
- ```
@@ -1,98 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ai-memory-cli
3
- Version: 0.1.0
4
- Summary: Python CLI for AI Memory terminal capture and offline sync.
5
- Author: AI Memory
6
- License-Expression: MIT
7
- Keywords: ai,memory,cli,terminal,rag,developer-tools
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Environment :: Console
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Topic :: Software Development
17
- Classifier: Topic :: Terminals
18
- Requires-Python: >=3.10
19
- Description-Content-Type: text/markdown
20
- License-File: LICENSE
21
- Dynamic: license-file
22
-
23
- # AI Memory CLI
24
-
25
- Standalone Python CLI for terminal capture, hashing, offline queueing, and sync to the temporary FastAPI backend.
26
-
27
- ## Install
28
-
29
- For any user machine after the package is published:
30
-
31
- ```powershell
32
- python -m pip install ai-memory-cli
33
- ```
34
-
35
- Before PyPI publish, install from GitHub:
36
-
37
- ```powershell
38
- python -m pip install "ai-memory-cli @ git+https://github.com/YOUR_ORG/ai-memory-cli.git"
39
- ```
40
-
41
- For local development from this CLI repo:
42
-
43
- ```powershell
44
- python -m pip install -e .
45
- ```
46
-
47
- ## Basic flow
48
-
49
- ```powershell
50
- ai-memory auth --token <app-issued-cli-token> --api-url https://api.your-domain.com
51
- ai-memory init --project my-project --repo owner/repo --workspace .
52
- ai-memory workspace connect --path . --repo owner/repo --editor vscode --package-manager pip
53
- ai-memory watch
54
- ```
55
-
56
- Use `ai-memory run -- <command>` when you only want to record one command.
57
-
58
- ## Storage
59
-
60
- The CLI stores config and unsynced events in a separate folder:
61
-
62
- - Windows: `%USERPROFILE%\.ai-memory-cli`
63
- - macOS/Linux: `~/.ai-memory-cli`
64
-
65
- Set `AI_MEMORY_CLI_HOME` to override this location.
66
-
67
- ## Privacy and dedupe
68
-
69
- The CLI does not send raw commands or raw output to the backend. It sends:
70
-
71
- - `command_hash`
72
- - `output_hash`
73
- - `event_hash`
74
- - timestamps, exit code, shell, project, repo, and local metadata
75
-
76
- If the same command produces the same output again, the CLI keeps one event hash and increments `duplicate_count`.
77
-
78
- ## Excluded commands
79
-
80
- Long-running development commands are not captured by default. They still run, but no hash event is stored.
81
-
82
- Default excluded patterns include:
83
-
84
- - `npm run ...`
85
- - `next dev`
86
- - `vite`
87
- - `uvicorn --reload`
88
- - `python -m uvicorn ... --reload`
89
-
90
- Use `--include-excluded` on `run` or `watch` if you need to capture them anyway.
91
-
92
- ## Publish
93
-
94
- After this folder is pushed as its own public GitHub repo, publish to PyPI with:
95
-
96
- ```powershell
97
- .\scripts\publish.ps1 -Repository pypi
98
- ```
File without changes
File without changes