cli-mem 0.2.0__tar.gz → 0.2.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.
- {cli_mem-0.2.0 → cli_mem-0.2.1}/PKG-INFO +1 -1
- {cli_mem-0.2.0 → cli_mem-0.2.1}/pyproject.toml +1 -1
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/__init__.py +1 -1
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/cli.py +28 -3
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/groups.py +2 -4
- {cli_mem-0.2.0 → cli_mem-0.2.1}/tests/test_groups.py +59 -6
- {cli_mem-0.2.0 → cli_mem-0.2.1}/.github/workflows/ci.yml +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/.github/workflows/release.yml +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/.gitignore +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/ARCHITECTURE.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/LICENSE +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/PHILOSOPHY.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/README.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/assets/.gitkeep +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/docs/decisions/001-jsonl-over-sqlite.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/docs/decisions/002-apple-fm-sdk-for-patterns.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/docs/decisions/003-no-daemon.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/docs/decisions/004-per-repo-jsonl.md +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/hooks/mem.zsh +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/install.sh +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/capture.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/models.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/patterns.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/search.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/src/mem/storage.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/tests/conftest.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/tests/test_capture.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/tests/test_patterns.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/tests/test_search.py +0 -0
- {cli_mem-0.2.0 → cli_mem-0.2.1}/tests/test_storage.py +0 -0
|
@@ -400,10 +400,11 @@ def save(command: str, group_name: str | None, global_flag: bool, comment: str |
|
|
|
400
400
|
|
|
401
401
|
|
|
402
402
|
@cli.command(name="list")
|
|
403
|
+
@click.argument("group_name", required=False, default=None)
|
|
403
404
|
@click.option("--global", "global_flag", is_flag=True, help="Show only global scope")
|
|
404
405
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
405
|
-
def list_cmd(global_flag: bool, as_json: bool) -> None:
|
|
406
|
-
"""List saved commands and groups."""
|
|
406
|
+
def list_cmd(group_name: str | None, global_flag: bool, as_json: bool) -> None:
|
|
407
|
+
"""List saved commands and groups, or show a group's commands."""
|
|
407
408
|
from mem import groups, storage
|
|
408
409
|
|
|
409
410
|
global_path = storage.GROUPS_GLOBAL_FILE
|
|
@@ -415,6 +416,28 @@ def list_cmd(global_flag: bool, as_json: bool) -> None:
|
|
|
415
416
|
sanitized = storage.sanitize_repo_name(repo)
|
|
416
417
|
repo_path = storage.group_file_path(sanitized)
|
|
417
418
|
|
|
419
|
+
# Show a specific group's commands
|
|
420
|
+
if group_name is not None:
|
|
421
|
+
grp, scope_label, _file_path, shadows = groups.resolve_group(
|
|
422
|
+
group_name, repo_path, global_path, force_global=global_flag,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if as_json:
|
|
426
|
+
click.echo(groups.export_json(group_name, grp))
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
console.print(f"\n● {scope_label} / {group_name}")
|
|
430
|
+
if grp.description:
|
|
431
|
+
console.print(f' "{grp.description}"')
|
|
432
|
+
if group_name in shadows and scope_label != "global":
|
|
433
|
+
console.print(" [dim](global group with same name exists — use --global to see it)[/]")
|
|
434
|
+
console.print(" " + "─" * 50)
|
|
435
|
+
for i, cmd in enumerate(grp.commands, 1):
|
|
436
|
+
comment_str = f" # {cmd.comment}" if cmd.comment else ""
|
|
437
|
+
console.print(f" {i}. {cmd.cmd}{comment_str}")
|
|
438
|
+
console.print()
|
|
439
|
+
return
|
|
440
|
+
|
|
418
441
|
result = groups.list_all(repo_path, global_path)
|
|
419
442
|
|
|
420
443
|
if as_json:
|
|
@@ -528,6 +551,7 @@ def run(group_name: str, global_flag: bool, yes: bool) -> None:
|
|
|
528
551
|
return
|
|
529
552
|
|
|
530
553
|
# Determine which commands to run
|
|
554
|
+
run_all = yes
|
|
531
555
|
if not yes:
|
|
532
556
|
choice = click.prompt(
|
|
533
557
|
f" Run all? [y/N] or pick [1-{len(grp.commands)}]",
|
|
@@ -539,6 +563,7 @@ def run(group_name: str, global_flag: bool, yes: bool) -> None:
|
|
|
539
563
|
return
|
|
540
564
|
|
|
541
565
|
if choice.lower() == "y":
|
|
566
|
+
run_all = True
|
|
542
567
|
commands_to_run = list(enumerate(grp.commands, 1))
|
|
543
568
|
else:
|
|
544
569
|
try:
|
|
@@ -557,7 +582,7 @@ def run(group_name: str, global_flag: bool, yes: bool) -> None:
|
|
|
557
582
|
# Execute
|
|
558
583
|
console.print()
|
|
559
584
|
for i, cmd in commands_to_run:
|
|
560
|
-
if not
|
|
585
|
+
if not run_all and len(commands_to_run) > 1:
|
|
561
586
|
if not click.confirm(f" Run [{i}] {cmd.cmd}?", default=True, err=True):
|
|
562
587
|
continue
|
|
563
588
|
|
|
@@ -32,16 +32,14 @@ def resolve_scope(global_flag: bool) -> Path:
|
|
|
32
32
|
"""Return the path to the data file for the target scope.
|
|
33
33
|
|
|
34
34
|
Uses the current git repo for scoping unless --global is set.
|
|
35
|
-
|
|
35
|
+
Falls back to global scope when outside a git repo.
|
|
36
36
|
"""
|
|
37
37
|
if global_flag:
|
|
38
38
|
return storage.GROUPS_GLOBAL_FILE
|
|
39
39
|
|
|
40
40
|
repo = get_git_repo(os.getcwd())
|
|
41
41
|
if repo is None:
|
|
42
|
-
|
|
43
|
-
"Not in a git repository. Use --global to save globally."
|
|
44
|
-
)
|
|
42
|
+
return storage.GROUPS_GLOBAL_FILE
|
|
45
43
|
sanitized = storage.sanitize_repo_name(repo)
|
|
46
44
|
return storage.group_file_path(sanitized)
|
|
47
45
|
|
|
@@ -603,13 +603,14 @@ class TestSaveCLI:
|
|
|
603
603
|
assert result.exit_code == 0
|
|
604
604
|
assert "Already saved" in result.output
|
|
605
605
|
|
|
606
|
-
def
|
|
606
|
+
def test_save_outside_repo_falls_back_to_global(self, tmp_mem_dir: Path):
|
|
607
607
|
runner = CliRunner()
|
|
608
608
|
with patch("mem.cli._current_repo", return_value=None), \
|
|
609
609
|
patch("mem.groups.get_git_repo", return_value=None):
|
|
610
610
|
result = runner.invoke(cli, ["save", "echo hello"])
|
|
611
|
-
assert result.exit_code
|
|
612
|
-
|
|
611
|
+
assert result.exit_code == 0
|
|
612
|
+
data = storage.read_group_file(storage.GROUPS_GLOBAL_FILE)
|
|
613
|
+
assert any(s.cmd == "echo hello" for s in data.saved)
|
|
613
614
|
|
|
614
615
|
def test_save_in_repo(self, tmp_mem_dir: Path):
|
|
615
616
|
runner = CliRunner()
|
|
@@ -675,6 +676,44 @@ class TestListCLI:
|
|
|
675
676
|
assert result.exit_code == 0
|
|
676
677
|
assert "shadowed" in result.output
|
|
677
678
|
|
|
679
|
+
def test_list_specific_group(self, tmp_mem_dir: Path):
|
|
680
|
+
storage.write_group_file(
|
|
681
|
+
storage.GROUPS_GLOBAL_FILE,
|
|
682
|
+
GroupFile(groups={"deploy": Group(
|
|
683
|
+
description="deploy steps",
|
|
684
|
+
commands=[
|
|
685
|
+
GroupCommand(cmd="make build", comment="compile"),
|
|
686
|
+
GroupCommand(cmd="make push"),
|
|
687
|
+
],
|
|
688
|
+
)}),
|
|
689
|
+
)
|
|
690
|
+
runner = CliRunner()
|
|
691
|
+
result = runner.invoke(cli, ["list", "deploy", "--global"])
|
|
692
|
+
assert result.exit_code == 0
|
|
693
|
+
assert "deploy" in result.output
|
|
694
|
+
assert "make build" in result.output
|
|
695
|
+
assert "compile" in result.output
|
|
696
|
+
assert "make push" in result.output
|
|
697
|
+
|
|
698
|
+
def test_list_specific_group_not_found(self, tmp_mem_dir: Path):
|
|
699
|
+
runner = CliRunner()
|
|
700
|
+
result = runner.invoke(cli, ["list", "nope", "--global"])
|
|
701
|
+
assert result.exit_code != 0
|
|
702
|
+
assert "not found" in result.output.lower()
|
|
703
|
+
|
|
704
|
+
def test_list_specific_group_json(self, tmp_mem_dir: Path):
|
|
705
|
+
storage.write_group_file(
|
|
706
|
+
storage.GROUPS_GLOBAL_FILE,
|
|
707
|
+
GroupFile(groups={"g": Group(
|
|
708
|
+
commands=[GroupCommand(cmd="echo hi")],
|
|
709
|
+
)}),
|
|
710
|
+
)
|
|
711
|
+
runner = CliRunner()
|
|
712
|
+
result = runner.invoke(cli, ["list", "g", "--global", "--json"])
|
|
713
|
+
assert result.exit_code == 0
|
|
714
|
+
data = json.loads(result.output)
|
|
715
|
+
assert "g" in data
|
|
716
|
+
|
|
678
717
|
|
|
679
718
|
class TestRunCLI:
|
|
680
719
|
def _setup_group(self, tmp_mem_dir: Path):
|
|
@@ -723,6 +762,20 @@ class TestRunCLI:
|
|
|
723
762
|
result = runner.invoke(cli, ["run", "test", "--global"], input="n\n")
|
|
724
763
|
assert result.exit_code == 0
|
|
725
764
|
|
|
765
|
+
def test_run_all_skips_per_command_confirm(self, tmp_mem_dir: Path):
|
|
766
|
+
self._setup_group(tmp_mem_dir)
|
|
767
|
+
runner = CliRunner()
|
|
768
|
+
with patch("mem.cli._is_interactive", return_value=True):
|
|
769
|
+
# "y\n" = run all — should NOT prompt for each command
|
|
770
|
+
result = runner.invoke(cli, ["run", "test", "--global"], input="y\n")
|
|
771
|
+
assert result.exit_code == 0
|
|
772
|
+
# All 3 commands should appear as executed ($ echo N)
|
|
773
|
+
assert "$ echo 1" in result.output
|
|
774
|
+
assert "$ echo 2" in result.output
|
|
775
|
+
assert "$ echo 3" in result.output
|
|
776
|
+
# Should NOT contain per-command confirmation prompts
|
|
777
|
+
assert "Run [1]" not in result.output
|
|
778
|
+
|
|
726
779
|
def test_run_non_tty_without_yes_errors(self, tmp_mem_dir: Path):
|
|
727
780
|
self._setup_group(tmp_mem_dir)
|
|
728
781
|
runner = CliRunner()
|
|
@@ -1159,10 +1212,10 @@ class TestResolveScope:
|
|
|
1159
1212
|
expected_name = storage.sanitize_repo_name(FAKE_REPO)
|
|
1160
1213
|
assert path == storage.group_file_path(expected_name)
|
|
1161
1214
|
|
|
1162
|
-
def
|
|
1215
|
+
def test_outside_repo_falls_back_to_global(self, tmp_mem_dir: Path):
|
|
1163
1216
|
with patch("mem.groups.get_git_repo", return_value=None):
|
|
1164
|
-
|
|
1165
|
-
|
|
1217
|
+
path = groups.resolve_scope(global_flag=False)
|
|
1218
|
+
assert path == storage.GROUPS_GLOBAL_FILE
|
|
1166
1219
|
|
|
1167
1220
|
|
|
1168
1221
|
class TestSaveNonInteractive:
|
|
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
|
|
File without changes
|