cli-mem 0.4.0__tar.gz → 0.4.1__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.
Files changed (47) hide show
  1. {cli_mem-0.4.0 → cli_mem-0.4.1}/PKG-INFO +1 -1
  2. {cli_mem-0.4.0 → cli_mem-0.4.1}/pyproject.toml +1 -1
  3. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/__init__.py +1 -1
  4. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/groups.py +30 -2
  5. {cli_mem-0.4.0 → cli_mem-0.4.1}/tests/test_groups.py +85 -0
  6. {cli_mem-0.4.0 → cli_mem-0.4.1}/.github/workflows/ci.yml +0 -0
  7. {cli_mem-0.4.0 → cli_mem-0.4.1}/.github/workflows/release.yml +0 -0
  8. {cli_mem-0.4.0 → cli_mem-0.4.1}/.gitignore +0 -0
  9. {cli_mem-0.4.0 → cli_mem-0.4.1}/ARCHITECTURE.md +0 -0
  10. {cli_mem-0.4.0 → cli_mem-0.4.1}/LICENSE +0 -0
  11. {cli_mem-0.4.0 → cli_mem-0.4.1}/PHILOSOPHY.md +0 -0
  12. {cli_mem-0.4.0 → cli_mem-0.4.1}/README.md +0 -0
  13. {cli_mem-0.4.0 → cli_mem-0.4.1}/assets/.gitkeep +0 -0
  14. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/databases/postgres-debug.json +0 -0
  15. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/databases/redis-ops.json +0 -0
  16. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/devops/docker-cleanup.json +0 -0
  17. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/devops/incident-response.json +0 -0
  18. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/devops/k8s-debug.json +0 -0
  19. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/git/cleanup.json +0 -0
  20. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/git/forensics.json +0 -0
  21. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/networking/dns-debug.json +0 -0
  22. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/networking/network-debug.json +0 -0
  23. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/performance/system-profile.json +0 -0
  24. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/security/headers-audit.json +0 -0
  25. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/security/recon.json +0 -0
  26. {cli_mem-0.4.0 → cli_mem-0.4.1}/command-groups/security/ssl-audit.json +0 -0
  27. {cli_mem-0.4.0 → cli_mem-0.4.1}/docs/decisions/001-jsonl-over-sqlite.md +0 -0
  28. {cli_mem-0.4.0 → cli_mem-0.4.1}/docs/decisions/002-apple-fm-sdk-for-patterns.md +0 -0
  29. {cli_mem-0.4.0 → cli_mem-0.4.1}/docs/decisions/003-no-daemon.md +0 -0
  30. {cli_mem-0.4.0 → cli_mem-0.4.1}/docs/decisions/004-per-repo-jsonl.md +0 -0
  31. {cli_mem-0.4.0 → cli_mem-0.4.1}/hooks/mem.bash +0 -0
  32. {cli_mem-0.4.0 → cli_mem-0.4.1}/hooks/mem.fish +0 -0
  33. {cli_mem-0.4.0 → cli_mem-0.4.1}/hooks/mem.zsh +0 -0
  34. {cli_mem-0.4.0 → cli_mem-0.4.1}/install.sh +0 -0
  35. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/_generable.py +0 -0
  36. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/capture.py +0 -0
  37. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/cli.py +0 -0
  38. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/models.py +0 -0
  39. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/patterns.py +0 -0
  40. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/search.py +0 -0
  41. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/storage.py +0 -0
  42. {cli_mem-0.4.0 → cli_mem-0.4.1}/src/mem/variables.py +0 -0
  43. {cli_mem-0.4.0 → cli_mem-0.4.1}/tests/conftest.py +0 -0
  44. {cli_mem-0.4.0 → cli_mem-0.4.1}/tests/test_capture.py +0 -0
  45. {cli_mem-0.4.0 → cli_mem-0.4.1}/tests/test_patterns.py +0 -0
  46. {cli_mem-0.4.0 → cli_mem-0.4.1}/tests/test_search.py +0 -0
  47. {cli_mem-0.4.0 → cli_mem-0.4.1}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-mem
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Privacy-first CLI that turns shell history into searchable memory
5
5
  Project-URL: GitHub, https://github.com/matinsaurralde/mem
6
6
  Author: Matias Insaurralde
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cli-mem"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Privacy-first CLI that turns shell history into searchable memory"
9
9
  license = "MIT"
10
10
  readme = "README.md"
@@ -1,3 +1,3 @@
1
1
  """mem — your shell history, understood."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.1"
@@ -24,7 +24,11 @@ import click
24
24
  from mem import storage
25
25
  from mem.capture import get_git_repo
26
26
  from mem.models import Group, GroupCommand, GroupFile, SavedCommand, VarDeclaration
27
- from mem.variables import merge_var_declarations, parse_variables, process_escapes
27
+ from mem.variables import (
28
+ merge_var_declarations,
29
+ parse_variables,
30
+ process_escapes,
31
+ )
28
32
 
29
33
  GROUP_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9-]*$")
30
34
 
@@ -239,6 +243,21 @@ def export_json(group_name: str, group: Group) -> str:
239
243
  )
240
244
 
241
245
 
246
+ def _auto_detect_vars(commands: list[GroupCommand]) -> None:
247
+ """Populate the vars field on imported commands by scanning for $VAR_NAME tokens.
248
+
249
+ Commands that already have vars declared (e.g., from a full mem export)
250
+ are left untouched. This ensures imported runbooks work with mem run
251
+ variable substitution out of the box.
252
+ """
253
+ for cmd in commands:
254
+ if cmd.vars is not None:
255
+ continue
256
+ detected = parse_variables(cmd.cmd)
257
+ if detected:
258
+ cmd.vars = [VarDeclaration(name=n) for n in detected]
259
+
260
+
242
261
  def import_from_json_str(content: str) -> tuple[str | None, list[GroupCommand]]:
243
262
  """Parse a JSON string and extract commands.
244
263
 
@@ -276,10 +295,18 @@ def import_from_json_str(content: str) -> tuple[str | None, list[GroupCommand]]:
276
295
  raise click.ClickException("Expected 'commands' to be a list.")
277
296
 
278
297
  try:
279
- commands = [GroupCommand(cmd=c["cmd"], comment=c.get("comment")) for c in cmds]
298
+ commands = [
299
+ GroupCommand(
300
+ cmd=c["cmd"],
301
+ comment=c.get("comment"),
302
+ vars=c.get("vars"),
303
+ )
304
+ for c in cmds
305
+ ]
280
306
  except (KeyError, TypeError) as e:
281
307
  raise click.ClickException(f"Malformed command entry: {e}")
282
308
 
309
+ _auto_detect_vars(commands)
283
310
  return group_name, commands
284
311
 
285
312
 
@@ -338,6 +365,7 @@ def import_from_markdown_str(content: str) -> tuple[str | None, list[GroupComman
338
365
  if not commands:
339
366
  raise click.ClickException("No commands found in markdown table.")
340
367
 
368
+ _auto_detect_vars(commands)
341
369
  return group_name, commands
342
370
 
343
371
 
@@ -2053,6 +2053,91 @@ class TestImportFromMarkdownStr:
2053
2053
  groups.import_from_markdown_str("# Just text\nNo table here.")
2054
2054
 
2055
2055
 
2056
+ class TestImportAutoDetectVars:
2057
+ """Tests for automatic variable detection during import."""
2058
+
2059
+ def test_json_detects_single_var(self):
2060
+ content = json.dumps({"recon": {"commands": [{"cmd": "dig $DOMAIN A +short"}]}})
2061
+ _, cmds = groups.import_from_json_str(content)
2062
+ assert cmds[0].vars is not None
2063
+ assert len(cmds[0].vars) == 1
2064
+ assert cmds[0].vars[0].name == "DOMAIN"
2065
+
2066
+ def test_json_detects_multiple_vars(self):
2067
+ content = json.dumps(
2068
+ {"commands": [{"cmd": "curl -u $USER_NAME:$API_TOKEN https://$HOST/api"}]}
2069
+ )
2070
+ _, cmds = groups.import_from_json_str(content)
2071
+ var_names = [v.name for v in cmds[0].vars]
2072
+ assert "USER_NAME" in var_names
2073
+ assert "API_TOKEN" in var_names
2074
+ assert "HOST" in var_names
2075
+
2076
+ def test_json_no_vars_leaves_none(self):
2077
+ content = json.dumps({"commands": [{"cmd": "ls -la"}]})
2078
+ _, cmds = groups.import_from_json_str(content)
2079
+ assert cmds[0].vars is None
2080
+
2081
+ def test_json_excludes_shell_vars(self):
2082
+ """$HOME and $PATH are common shell vars and should not be detected."""
2083
+ content = json.dumps({"commands": [{"cmd": "ls $HOME/.config && echo $PATH"}]})
2084
+ _, cmds = groups.import_from_json_str(content)
2085
+ assert cmds[0].vars is None
2086
+
2087
+ def test_json_preserves_existing_vars(self):
2088
+ """Commands that already declare vars (from a full export) should not be overwritten."""
2089
+ content = json.dumps(
2090
+ {
2091
+ "commands": [
2092
+ {
2093
+ "cmd": "curl https://$HOST/api",
2094
+ "vars": [{"name": "HOST", "default": "localhost"}],
2095
+ }
2096
+ ]
2097
+ }
2098
+ )
2099
+ _, cmds = groups.import_from_json_str(content)
2100
+ assert len(cmds[0].vars) == 1
2101
+ assert cmds[0].vars[0].name == "HOST"
2102
+ assert cmds[0].vars[0].default == "localhost"
2103
+
2104
+ def test_markdown_detects_vars(self):
2105
+ md = (
2106
+ "## ops\n\n"
2107
+ "| Command | Description |\n"
2108
+ "|---|---|\n"
2109
+ "| `kubectl get pods -n $NAMESPACE` | list pods |\n"
2110
+ )
2111
+ _, cmds = groups.import_from_markdown_str(md)
2112
+ assert cmds[0].vars is not None
2113
+ assert cmds[0].vars[0].name == "NAMESPACE"
2114
+
2115
+ def test_markdown_no_vars(self):
2116
+ md = (
2117
+ "## test\n\n"
2118
+ "| Command | Description |\n"
2119
+ "|---|---|\n"
2120
+ "| `echo hello` | greeting |\n"
2121
+ )
2122
+ _, cmds = groups.import_from_markdown_str(md)
2123
+ assert cmds[0].vars is None
2124
+
2125
+ def test_mixed_commands_some_with_vars(self):
2126
+ content = json.dumps(
2127
+ {
2128
+ "commands": [
2129
+ {"cmd": "echo starting"},
2130
+ {"cmd": "curl https://$HOST:$PORT/health"},
2131
+ {"cmd": "echo done"},
2132
+ ]
2133
+ }
2134
+ )
2135
+ _, cmds = groups.import_from_json_str(content)
2136
+ assert cmds[0].vars is None
2137
+ assert len(cmds[1].vars) == 2
2138
+ assert cmds[2].vars is None
2139
+
2140
+
2056
2141
  class TestImportFromJsonDelegation:
2057
2142
  """Ensure the file-based function still works via delegation."""
2058
2143
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes