kapsel-cli 0.3.8__tar.gz → 0.5.2__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 (116) hide show
  1. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/PKG-INFO +1 -1
  2. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/__init__.py +1 -1
  3. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/cli.py +8 -14
  4. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/__init__.py +23 -2
  5. kapsel_cli-0.5.2/kapsel/completion/kps/builtins/dir_nav.py +318 -0
  6. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/search.py +11 -4
  7. kapsel_cli-0.5.2/kapsel/completion/kps/builtins/toggle.py +281 -0
  8. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/spec_manager.py +10 -2
  9. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/executor.py +43 -39
  10. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/catalog.py +18 -3
  11. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/manager.py +9 -1
  12. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/storage/config.py +13 -0
  13. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/storage/history.py +29 -10
  14. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/banner.py +8 -2
  15. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/prompt.py +80 -4
  16. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/theme.py +15 -0
  17. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel_cli.egg-info/PKG-INFO +1 -1
  18. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel_cli.egg-info/SOURCES.txt +9 -0
  19. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/pyproject.toml +1 -1
  20. kapsel_cli-0.5.2/tests/test_dir_nav.py +241 -0
  21. kapsel_cli-0.5.2/tests/test_history.py +78 -0
  22. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_plugin_ai.py +2 -3
  23. kapsel_cli-0.5.2/tests/test_plugin_autopilot.py +175 -0
  24. kapsel_cli-0.5.2/tests/test_plugin_commit.py +165 -0
  25. kapsel_cli-0.5.2/tests/test_plugin_lang.py +115 -0
  26. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_plugin_portal.py +85 -1
  27. kapsel_cli-0.5.2/tests/test_plugin_preview.py +508 -0
  28. kapsel_cli-0.5.2/tests/test_plugin_queue.py +78 -0
  29. kapsel_cli-0.5.2/tests/test_plugin_template.py +67 -0
  30. kapsel_cli-0.5.2/tests/test_plugin_trans.py +151 -0
  31. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_search_and_completions.py +1 -0
  32. kapsel_cli-0.5.2/tests/test_toggle.py +115 -0
  33. kapsel_cli-0.3.8/kapsel/completion/kps/builtins/toggle.py +0 -34
  34. kapsel_cli-0.3.8/tests/test_history.py +0 -223
  35. kapsel_cli-0.3.8/tests/test_plugin_autopilot.py +0 -65
  36. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/README.md +0 -0
  37. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/__main__.py +0 -0
  38. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/__init__.py +0 -0
  39. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/carapace_engine.py +0 -0
  40. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/carapace_installer.py +0 -0
  41. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/completer.py +0 -0
  42. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/__init__.py +0 -0
  43. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/add.py +0 -0
  44. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/completion.py +0 -0
  45. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/config.py +0 -0
  46. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/datadir.py +0 -0
  47. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/help.py +0 -0
  48. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/language.py +0 -0
  49. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/plugin_switch.py +0 -0
  50. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/status.py +0 -0
  51. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/builtins/upgrade.py +0 -0
  52. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/dispatcher.py +0 -0
  53. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/completion/kps/registry.py +0 -0
  54. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/__init__.py +0 -0
  55. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/block/__init__.py +0 -0
  56. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/block/model.py +0 -0
  57. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/block/registry.py +0 -0
  58. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/block/runner.py +0 -0
  59. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/detector.py +0 -0
  60. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/engine.py +0 -0
  61. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/i18n.py +0 -0
  62. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/__init__.py +0 -0
  63. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/base.py +0 -0
  64. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/context.py +0 -0
  65. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/fetcher.py +0 -0
  66. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/core/plugin/hooks.py +0 -0
  67. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/i18n.py +0 -0
  68. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/de/LC_MESSAGES/kapsel.mo +0 -0
  69. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/de/LC_MESSAGES/kapsel.po +0 -0
  70. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/de/help.yaml +0 -0
  71. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/de/tips.yaml +0 -0
  72. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/en/help.yaml +0 -0
  73. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/en/tips.yaml +0 -0
  74. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/es/LC_MESSAGES/kapsel.mo +0 -0
  75. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/es/LC_MESSAGES/kapsel.po +0 -0
  76. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/es/help.yaml +0 -0
  77. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/es/tips.yaml +0 -0
  78. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/fr/LC_MESSAGES/kapsel.mo +0 -0
  79. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/fr/LC_MESSAGES/kapsel.po +0 -0
  80. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/fr/help.yaml +0 -0
  81. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/fr/tips.yaml +0 -0
  82. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ja/LC_MESSAGES/kapsel.mo +0 -0
  83. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ja/LC_MESSAGES/kapsel.po +0 -0
  84. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ja/help.yaml +0 -0
  85. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ja/tips.yaml +0 -0
  86. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/kapsel.pot +0 -0
  87. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ru/LC_MESSAGES/kapsel.mo +0 -0
  88. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ru/LC_MESSAGES/kapsel.po +0 -0
  89. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ru/help.yaml +0 -0
  90. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/ru/tips.yaml +0 -0
  91. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/zh_CN/LC_MESSAGES/kapsel.mo +0 -0
  92. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/zh_CN/LC_MESSAGES/kapsel.po +0 -0
  93. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/zh_CN/help.yaml +0 -0
  94. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/locales/zh_CN/tips.yaml +0 -0
  95. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/storage/__init__.py +0 -0
  96. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/storage/logger.py +0 -0
  97. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/storage/migrate.py +0 -0
  98. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/__init__.py +0 -0
  99. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/card.py +0 -0
  100. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/tips.py +0 -0
  101. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel/ui/viewport.py +0 -0
  102. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel_cli.egg-info/dependency_links.txt +0 -0
  103. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel_cli.egg-info/entry_points.txt +0 -0
  104. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel_cli.egg-info/requires.txt +0 -0
  105. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/kapsel_cli.egg-info/top_level.txt +0 -0
  106. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/setup.cfg +0 -0
  107. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_block_system.py +0 -0
  108. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_carapace_installer.py +0 -0
  109. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_config.py +0 -0
  110. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_i18n.py +0 -0
  111. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_plugin_alias.py +0 -0
  112. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_plugin_fetcher.py +0 -0
  113. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_plugin_init.py +0 -0
  114. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_plugin_install.py +0 -0
  115. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_spec_manager.py +0 -0
  116. {kapsel_cli-0.3.8 → kapsel_cli-0.5.2}/tests/test_upgrade.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kapsel-cli
3
- Version: 0.3.8
3
+ Version: 0.5.2
4
4
  Summary: Next-generation cross-platform terminal capsule & ergonomic shell multiplexer with context-aware command translation and 1,000+ CLI completions.
5
5
  Author-email: MrEiu <k648888@vip.qq.com>
6
6
  License: MIT
@@ -3,4 +3,4 @@ Kapsel: Cross-platform adaptive smart terminal capsule.
3
3
  Wrap complexity, expose simplicity.
4
4
  """
5
5
 
6
- __version__ = "0.3.8"
6
+ __version__ = "0.5.2"
@@ -48,13 +48,8 @@ def main(args: Optional[List[str]] = None) -> int:
48
48
  )
49
49
  return result.execution.exit_code
50
50
 
51
- is_toggle_start = False
52
- if clean_args and clean_args[0] == "toggle":
53
- is_toggle_start = True
54
- clean_args = clean_args[1:]
55
-
56
- # If other positional subcommands passed (e.g. kapsel status, kapsel help, kapsel add, kapsel config, kapsel datadir)
57
- if clean_args and not is_toggle_start:
51
+ # If positional subcommands passed (e.g. kapsel toggle, kapsel status, kapsel help, kapsel add, kapsel config, kapsel datadir)
52
+ if clean_args:
58
53
  engine = DualStateEngine()
59
54
  full_cmd = "kapsel " + " ".join(clean_args)
60
55
  result = engine.dispatch(full_cmd)
@@ -66,6 +61,10 @@ def main(args: Optional[List[str]] = None) -> int:
66
61
  )
67
62
  return result.execution.exit_code
68
63
 
64
+ # Do not start interactive shell in non-interactive / piped environments
65
+ if not sys.stdin.isatty() and not os.environ.get("KAPSEL_FORCE_TTY"):
66
+ return 0
67
+
69
68
  # Launch interactive shell (Kapsel Default Mode)
70
69
  engine = DualStateEngine()
71
70
  console = Console(legacy_windows=False)
@@ -76,9 +75,7 @@ def main(args: Optional[List[str]] = None) -> int:
76
75
 
77
76
  os.environ["KAPSEL_ACTIVE"] = "1"
78
77
 
79
- if is_toggle_start:
80
- console.print("[bold #10b981]✔ Kapsel active.[/] [dim]Type 'toggle' or 'exit' to quit.[/]\n")
81
- elif engine.config.enable_banner and not no_banner:
78
+ if engine.config.enable_banner and not no_banner:
82
79
  render_banner()
83
80
 
84
81
  prompt_session = KapselPrompt(engine)
@@ -101,7 +98,7 @@ def main(args: Optional[List[str]] = None) -> int:
101
98
  continue
102
99
 
103
100
  normalized = " ".join(stripped.lower().split())
104
- if normalized in ("exit", "quit", "toggle", "kapsel toggle", "kps toggle"):
101
+ if normalized in ("exit", "quit", "bye"):
105
102
  os.environ.pop("KAPSEL_ACTIVE", None)
106
103
  console.print("[dim]Exited Kapsel.[/]")
107
104
  break
@@ -143,9 +140,6 @@ def kps_cli() -> int:
143
140
  print(f"Kapsel v{__version__}")
144
141
  return 0
145
142
 
146
- if argv[0] == "toggle":
147
- return main(["toggle"])
148
-
149
143
  # 1. Fast dispatch to registered built-in or plugin kps commands
150
144
  cmd_line = " ".join(argv)
151
145
  builtin_exit = dispatch_kps(cmd_line)
@@ -114,8 +114,13 @@ def register_builtins(registry: "KpsCommandRegistry") -> None:
114
114
  registry.register(
115
115
  name="toggle",
116
116
  handler=handle_toggle_command,
117
- help_text="Toggle Kapsel as default terminal mode (open on first call, close on second)",
118
- usage="kapsel toggle",
117
+ help_text="Toggle Kapsel as default terminal startup shell (auto-launch on opening terminal)",
118
+ subcommands={
119
+ "on": "Enable Kapsel as default terminal startup shell in shell profile",
120
+ "off": "Disable Kapsel default startup and restore host shell",
121
+ "status": "Check current default terminal startup configuration",
122
+ },
123
+ usage="kapsel toggle [on|off|status]",
119
124
  scope="system",
120
125
  )
121
126
 
@@ -187,4 +192,20 @@ def register_builtins(registry: "KpsCommandRegistry") -> None:
187
192
  scope="system",
188
193
  )
189
194
 
195
+ # 11. dir & rm (Directional navigation & file management)
196
+ from kapsel.completion.kps.builtins.dir_nav import handle_dir_command, handle_rm_command
197
+ registry.register(
198
+ name="dir",
199
+ handler=handle_dir_command,
200
+ help_text="Preview directories and view Ctrl+Arrow navigation shortcuts",
201
+ usage="kapsel dir",
202
+ scope="system",
203
+ )
204
+ registry.register(
205
+ name="rm",
206
+ handler=handle_rm_command,
207
+ help_text="Safely permanently delete a file or directory with explicit confirmation",
208
+ usage="kapsel rm <file_or_directory>",
209
+ scope="system",
210
+ )
190
211
 
@@ -0,0 +1,318 @@
1
+ """
2
+ Kapsel Directional Navigation & Native File Management Suite (方向键原生交互与文件管理).
3
+ Completely avoids bottom_toolbar for maximum terminal compatibility.
4
+ Uses native floating completion menus and prompt-line interactions:
5
+ - Ctrl+Right: Opens native floating completion dropdown for subdirectories (cd <dir>/).
6
+ - Ctrl+Left: Double-tap returns to parent directory ('cd ..').
7
+ - Ctrl+Up: Populates 'touch ' on prompt line for cross-platform empty file creation.
8
+ - Ctrl+Down: Double-tap populates 'kapsel rm ' with native completion dropdown + explicit terminal confirmation.
9
+ All comments and docstrings are in English.
10
+ """
11
+
12
+ from pathlib import Path
13
+ import os
14
+ import shutil
15
+ import time
16
+ from typing import List, Optional, Tuple
17
+
18
+ from prompt_toolkit.application.run_in_terminal import run_in_terminal
19
+ from prompt_toolkit.buffer import CompletionState
20
+ from prompt_toolkit.completion import Completion
21
+ from rich.console import Console
22
+ from rich.table import Table
23
+
24
+
25
+ # Double-tap timers
26
+ _LAST_CTRL_LEFT_TIME: float = 0.0
27
+ _LAST_CTRL_DOWN_TIME: float = 0.0
28
+ DOUBLE_TAP_TIMEOUT: float = 2.0
29
+
30
+
31
+ def list_subdirectories(cwd: Optional[Path] = None, show_hidden: bool = False) -> List[Path]:
32
+ """Lists subdirectories within cwd, sorted alphabetically case-insensitively."""
33
+ target = cwd or Path.cwd()
34
+ try:
35
+ dirs = [
36
+ p for p in target.iterdir()
37
+ if p.is_dir() and (show_hidden or not p.name.startswith("."))
38
+ ]
39
+ dirs.sort(key=lambda p: p.name.lower())
40
+ return dirs
41
+ except Exception:
42
+ return []
43
+
44
+
45
+ def list_all_items(cwd: Optional[Path] = None, show_hidden: bool = False) -> List[Path]:
46
+ """Lists all files and subdirectories within cwd for deletion selection."""
47
+ target = cwd or Path.cwd()
48
+ try:
49
+ items = [
50
+ p for p in target.iterdir()
51
+ if (show_hidden or not p.name.startswith("."))
52
+ ]
53
+ items.sort(key=lambda p: (not p.is_dir(), p.name.lower()))
54
+ return items
55
+ except Exception:
56
+ return []
57
+
58
+
59
+ def create_empty_file(cwd: Path, filename: str) -> Tuple[bool, str]:
60
+ """Safely creates a new empty file in the given directory."""
61
+ clean_name = filename.strip().strip("\"'")
62
+ if not clean_name:
63
+ return False, "文件名不能为空 (Filename cannot be empty)"
64
+
65
+ target = cwd / clean_name
66
+ try:
67
+ if target.exists():
68
+ return False, f"目标已存在 (Item already exists): {clean_name}"
69
+ target.parent.mkdir(parents=True, exist_ok=True)
70
+ target.touch()
71
+ return True, f"✔ 空文件已创建 (Created): {clean_name}"
72
+ except Exception as e:
73
+ return False, f"✖ 创建失败 (Creation error): {e}"
74
+
75
+
76
+ def delete_item_permanently(target_path: Path) -> Tuple[bool, str]:
77
+ """Permanently deletes a file or directory without backup."""
78
+ try:
79
+ if not target_path.exists():
80
+ return False, f"目标不存在 (Item not found): {target_path.name}"
81
+ if target_path.is_dir():
82
+ shutil.rmtree(target_path)
83
+ else:
84
+ target_path.unlink()
85
+ return True, f"✔ 已永久删除 (Permanently deleted): {target_path.name}"
86
+ except Exception as e:
87
+ return False, f"✖ 删除失败 (Deletion error): {e}"
88
+
89
+
90
+ # -----------------------------------------------------------------------------
91
+ # 1. Ctrl+Right: Native Floating Completion Dropdown for Subdirectories
92
+ # -----------------------------------------------------------------------------
93
+ def trigger_subfolder_completion(buffer, cwd: Optional[Path] = None) -> bool:
94
+ """
95
+ Populates 'cd ' into buffer and immediately activates prompt_toolkit's
96
+ native floating completion dropdown populated with subdirectories.
97
+ """
98
+ target_cwd = cwd or Path.cwd()
99
+ dirs = list_subdirectories(target_cwd)
100
+
101
+ if not dirs:
102
+ run_in_terminal(lambda: print("\n\x1b[38;5;245m[📁 提示: 当前目录下暂无子文件夹]\x1b[0m"))
103
+ return False
104
+
105
+ # Ensure buffer has 'cd ' prefix
106
+ raw = buffer.text.strip()
107
+ if not raw or raw == "cd":
108
+ buffer.text = "cd "
109
+ buffer.cursor_position = 3
110
+ elif not buffer.text.endswith(" "):
111
+ buffer.text += " "
112
+ buffer.cursor_position = len(buffer.text)
113
+
114
+ completions = []
115
+ for d in dirs:
116
+ name = d.name
117
+ text_insert = f'"{name}"' if " " in name else name
118
+ completions.append(Completion(
119
+ text=text_insert,
120
+ start_position=0,
121
+ display=f"📁 {name}/",
122
+ display_meta="[目录 - Enter选入,再按Enter进入]",
123
+ ))
124
+
125
+ buffer.complete_state = CompletionState(
126
+ original_document=buffer.document,
127
+ completions=completions,
128
+ complete_index=0,
129
+ )
130
+ return True
131
+
132
+
133
+ # -----------------------------------------------------------------------------
134
+ # 2. Ctrl+Left: Double-Tap Parent Directory Return (cd ..)
135
+ # -----------------------------------------------------------------------------
136
+ def handle_ctrl_left_double_tap(buffer, on_cd_parent=None) -> bool:
137
+ """
138
+ Double-tap check for returning to parent directory (cd ..).
139
+ 1st tap: Shows light inline notice.
140
+ 2nd tap (< 2.0s): Executes 'cd ..'.
141
+ """
142
+ global _LAST_CTRL_LEFT_TIME
143
+ now = time.time()
144
+ if now - _LAST_CTRL_LEFT_TIME <= DOUBLE_TAP_TIMEOUT:
145
+ _LAST_CTRL_LEFT_TIME = 0.0
146
+ if on_cd_parent is not None:
147
+ on_cd_parent()
148
+ else:
149
+ buffer.text = "cd .."
150
+ buffer.cursor_position = len(buffer.text)
151
+ buffer.validate_and_handle()
152
+ return True
153
+ else:
154
+ _LAST_CTRL_LEFT_TIME = now
155
+ run_in_terminal(lambda: print("\n\x1b[38;5;245m[📁 提示: 再按一次 Ctrl+Left 返回父目录 (..)]\x1b[0m"))
156
+ return False
157
+
158
+
159
+ # -----------------------------------------------------------------------------
160
+ # 3. Ctrl+Up: Create New Empty File Prompt Line
161
+ # -----------------------------------------------------------------------------
162
+ def trigger_create_file(buffer) -> None:
163
+ """Populates 'touch ' into prompt line for easy cross-platform file creation."""
164
+ buffer.text = "touch "
165
+ buffer.cursor_position = 6
166
+
167
+
168
+ # -----------------------------------------------------------------------------
169
+ # 4. Ctrl+Down: Double-Tap Item Deletion with Native Completion & Confirmation
170
+ # -----------------------------------------------------------------------------
171
+ def handle_ctrl_down_double_tap(buffer, cwd: Optional[Path] = None) -> bool:
172
+ """
173
+ Double-tap check for item deletion.
174
+ 1st tap: Shows caution notice.
175
+ 2nd tap (< 2.0s): Populates 'kapsel rm ' and triggers native floating completions.
176
+ """
177
+ global _LAST_CTRL_DOWN_TIME
178
+ now = time.time()
179
+ if now - _LAST_CTRL_DOWN_TIME <= DOUBLE_TAP_TIMEOUT:
180
+ _LAST_CTRL_DOWN_TIME = 0.0
181
+ target_cwd = cwd or Path.cwd()
182
+ items = list_all_items(target_cwd)
183
+
184
+ if not items:
185
+ run_in_terminal(lambda: print("\n\x1b[38;5;203m[⚠️ 提示: 当前目录下无任何文件或文件夹]\x1b[0m"))
186
+ return False
187
+
188
+ buffer.text = "kapsel rm "
189
+ buffer.cursor_position = len(buffer.text)
190
+
191
+ completions = []
192
+ for it in items:
193
+ name = it.name
194
+ icon = "📁 " if it.is_dir() else "📄 "
195
+ type_label = "[目录]" if it.is_dir() else "[文件]"
196
+ text_insert = f'"{name}"' if " " in name else name
197
+ completions.append(Completion(
198
+ text=text_insert,
199
+ start_position=0,
200
+ display=f"{icon}{name}",
201
+ display_meta=f"{type_label} 永久删除",
202
+ ))
203
+
204
+ buffer.complete_state = CompletionState(
205
+ original_document=buffer.document,
206
+ completions=completions,
207
+ complete_index=0,
208
+ )
209
+ return True
210
+ else:
211
+ _LAST_CTRL_DOWN_TIME = now
212
+ run_in_terminal(lambda: print("\n\x1b[38;5;203m[⚠️ 谨慎操作: 再按一次 Ctrl+Down 进入删除选择]\x1b[0m"))
213
+ return False
214
+
215
+
216
+ # -----------------------------------------------------------------------------
217
+ # CLI Handlers: 'kapsel rm' (strict confirmation) and 'kapsel dir'
218
+ # -----------------------------------------------------------------------------
219
+ def handle_rm_command(args: List[str], console: Optional[Console] = None) -> int:
220
+ """CLI handler for 'kapsel rm' with strict interactive double confirmation."""
221
+ con = console or Console(legacy_windows=False)
222
+ if not args or args[0] in ("-h", "--help", "help"):
223
+ con.print("[dim]Usage: kapsel rm <file_or_directory>[/]")
224
+ return 0
225
+
226
+ raw_path = " ".join(args).strip().strip("\"'")
227
+ target = Path(raw_path)
228
+ if not target.is_absolute():
229
+ target = (Path.cwd() / target).resolve()
230
+
231
+ if not target.exists():
232
+ con.print(f"[bold #f43f5e]✖ 目标不存在: {raw_path}[/]")
233
+ return 1
234
+
235
+ item_type = "目录 (及其中所有内容)" if target.is_dir() else "文件"
236
+ con.print(f"\n[bold #f43f5e]🚨 永久删除警告[/]: 即将永久彻底删除 {item_type} [bold white]{target.name}[/]!")
237
+ con.print("[dim]该操作不可逆,且不保留回收站备份。[/]")
238
+
239
+ try:
240
+ reply = input("👉 请输入 y 确认删除 (输入其他键放弃): ").strip().lower()
241
+ except (KeyboardInterrupt, EOFError):
242
+ con.print("\n[dim]操作已取消。[/]")
243
+ return 0
244
+
245
+ if reply in ("y", "yes"):
246
+ ok, msg = delete_item_permanently(target)
247
+ if ok:
248
+ con.print(f"[bold #10b981]{msg}[/]\n")
249
+ return 0
250
+ else:
251
+ con.print(f"[bold #f43f5e]{msg}[/]\n")
252
+ return 1
253
+ else:
254
+ con.print("[dim]操作已放弃,未做任何更改。[/]\n")
255
+ return 0
256
+
257
+
258
+ def handle_dir_command(args: List[str], console: Optional[Console] = None) -> int:
259
+ """CLI entry point for 'kapsel dir' / 'kps dir'."""
260
+ con = console or Console(legacy_windows=False)
261
+
262
+ if args and args[0] in ("-h", "--help", "help"):
263
+ con.print("\n[bold #00f0ff]📁 Kapsel Directional Navigation & File Management (`kapsel dir`)[/]")
264
+ con.print("[dim]Inspect directories and view keyboard shortcuts for rapid prompt navigation.[/]\n")
265
+ table = Table(border_style="#0891b2", title="[bold #38bdf8]⚡ Directional Hotkeys (Ctrl + Arrows)[/]")
266
+ table.add_column("Shortcut", style="bold #00f0ff", width=16)
267
+ table.add_column("Action", style="white", width=24)
268
+ table.add_column("Details & Safety Mechanism", style="dim", overflow="fold")
269
+
270
+ table.add_row(
271
+ "Ctrl + Right (→)",
272
+ "Browse & Enter Subfolder",
273
+ "Opens native floating dropdown; ↑/↓ select; 1st Enter loads 'cd', 2nd Enter enters folder.",
274
+ )
275
+ table.add_row(
276
+ "Ctrl + Left (←)",
277
+ "Return to Parent (cd ..)",
278
+ "Double-tap safety (按两下): 1st tap warns, 2nd tap within 2s returns to parent.",
279
+ )
280
+ table.add_row(
281
+ "Ctrl + Up (↑)",
282
+ "Create Empty File",
283
+ "Single press populates 'touch '; type filename and Enter to create.",
284
+ )
285
+ table.add_row(
286
+ "Ctrl + Down (↓)",
287
+ "Permanent Deletion",
288
+ "Double-tap opens delete dropdown; terminal prompts explicit (y/N) confirmation.",
289
+ )
290
+ con.print(table)
291
+ con.print()
292
+ return 0
293
+
294
+ cwd = Path.cwd()
295
+ dirs = list_subdirectories(cwd)
296
+ all_items = list_all_items(cwd)
297
+ files = [p for p in all_items if not p.is_dir()]
298
+
299
+ con.print(f"\n[bold #00f0ff]📂 Current Directory:[/] [bold white]{cwd}[/]")
300
+ con.print(f"[dim]Contains {len(dirs)} subdirectories and {len(files)} files.[/]\n")
301
+
302
+ table = Table(border_style="#0891b2")
303
+ table.add_column("Type", justify="center", width=8)
304
+ table.add_column("Name", style="bold white")
305
+ table.add_column("Size / Info", style="dim", justify="right", width=14)
306
+
307
+ for d in dirs:
308
+ table.add_row("[bold #38bdf8]📁 DIR[/]", f"[bold #00f0ff]{d.name}/[/]", "-")
309
+ for f in files[:25]:
310
+ size = f"{f.stat().st_size} B" if f.exists() else "-"
311
+ table.add_row("[dim]📄 FILE[/]", f.name, size)
312
+
313
+ if len(files) > 25:
314
+ table.add_row("[dim]...[/]", f"[dim]({len(files) - 25} more files not shown)[/]", "")
315
+
316
+ con.print(table)
317
+ con.print("[dim]💡 Tip: Press [bold #00f0ff]Ctrl+Right[/] in prompt to open the floating subfolder dropdown![/]\n")
318
+ return 0
@@ -61,12 +61,19 @@ PLUGIN_METADATA: Dict[str, Dict[str, Any]] = {
61
61
  "tags": ["ai", "copilot", "chat", "llm", "aichat", "openai", "claude", "gemini", "deepseek", "ollama"],
62
62
  "summary": "Terminal AI copilot with guided setup wizard, multi-model dialogue, and code synthesis.",
63
63
  },
64
- "autopilot": {
65
- "title": "Autopilot Queue",
64
+ "queue": {
65
+ "title": "Queue Manager",
66
66
  "tool": "pueue (Rust)",
67
67
  "category": "Task Management",
68
- "tags": ["task", "queue", "daemon", "background", "pueue", "pueued", "async", "worker", "job"],
69
- "summary": "Autonomous background task queue and daemon execution manager (powered by Pueue).",
68
+ "tags": ["task", "queue", "daemon", "background", "pueue", "pueued", "async", "worker", "job", "auto"],
69
+ "summary": "Autonomous background task queue and execution supervisor (powered by Pueue).",
70
+ },
71
+ "autopilot": {
72
+ "title": "Autopilot Daemon (PM2)",
73
+ "tool": "pm2 (Node.js)",
74
+ "category": "Process Manager",
75
+ "tags": ["process", "daemon", "pm2", "service", "cluster", "supervisor", "monitor"],
76
+ "summary": "Production process manager and daemon supervisor powered by PM2.",
70
77
  },
71
78
  "fuck": {
72
79
  "title": "Auto-Correction",