npcsh 1.1.12__py3-none-any.whl → 1.1.14__py3-none-any.whl

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 (99) hide show
  1. npcsh/_state.py +700 -377
  2. npcsh/alicanto.py +54 -1153
  3. npcsh/completion.py +206 -0
  4. npcsh/config.py +163 -0
  5. npcsh/corca.py +35 -1462
  6. npcsh/execution.py +185 -0
  7. npcsh/guac.py +31 -1986
  8. npcsh/npc_team/jinxs/code/sh.jinx +11 -15
  9. npcsh/npc_team/jinxs/modes/alicanto.jinx +186 -80
  10. npcsh/npc_team/jinxs/modes/corca.jinx +243 -22
  11. npcsh/npc_team/jinxs/modes/guac.jinx +313 -42
  12. npcsh/npc_team/jinxs/modes/plonk.jinx +209 -48
  13. npcsh/npc_team/jinxs/modes/pti.jinx +167 -25
  14. npcsh/npc_team/jinxs/modes/spool.jinx +158 -37
  15. npcsh/npc_team/jinxs/modes/wander.jinx +179 -74
  16. npcsh/npc_team/jinxs/modes/yap.jinx +258 -21
  17. npcsh/npc_team/jinxs/utils/chat.jinx +39 -12
  18. npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
  19. npcsh/npc_team/jinxs/utils/search.jinx +3 -3
  20. npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
  21. npcsh/npcsh.py +76 -20
  22. npcsh/parsing.py +118 -0
  23. npcsh/plonk.py +41 -329
  24. npcsh/pti.py +41 -201
  25. npcsh/spool.py +34 -239
  26. npcsh/ui.py +199 -0
  27. npcsh/wander.py +54 -542
  28. npcsh/yap.py +38 -570
  29. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +194 -0
  30. npcsh-1.1.14.data/data/npcsh/npc_team/chat.jinx +44 -0
  31. npcsh-1.1.14.data/data/npcsh/npc_team/cmd.jinx +44 -0
  32. npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +249 -0
  33. npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +317 -0
  34. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +214 -0
  35. npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +170 -0
  36. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/search.jinx +3 -3
  37. npcsh-1.1.14.data/data/npcsh/npc_team/sh.jinx +34 -0
  38. npcsh-1.1.14.data/data/npcsh/npc_team/spool.jinx +161 -0
  39. npcsh-1.1.14.data/data/npcsh/npc_team/usage.jinx +33 -0
  40. npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +186 -0
  41. npcsh-1.1.14.data/data/npcsh/npc_team/yap.jinx +262 -0
  42. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/METADATA +1 -1
  43. npcsh-1.1.14.dist-info/RECORD +135 -0
  44. npcsh-1.1.12.data/data/npcsh/npc_team/alicanto.jinx +0 -88
  45. npcsh-1.1.12.data/data/npcsh/npc_team/chat.jinx +0 -17
  46. npcsh-1.1.12.data/data/npcsh/npc_team/corca.jinx +0 -28
  47. npcsh-1.1.12.data/data/npcsh/npc_team/guac.jinx +0 -46
  48. npcsh-1.1.12.data/data/npcsh/npc_team/plonk.jinx +0 -53
  49. npcsh-1.1.12.data/data/npcsh/npc_team/pti.jinx +0 -28
  50. npcsh-1.1.12.data/data/npcsh/npc_team/sh.jinx +0 -38
  51. npcsh-1.1.12.data/data/npcsh/npc_team/spool.jinx +0 -40
  52. npcsh-1.1.12.data/data/npcsh/npc_team/wander.jinx +0 -81
  53. npcsh-1.1.12.data/data/npcsh/npc_team/yap.jinx +0 -25
  54. npcsh-1.1.12.dist-info/RECORD +0 -126
  55. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/agent.jinx +0 -0
  56. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  57. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.png +0 -0
  58. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/build.jinx +0 -0
  59. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compile.jinx +0 -0
  60. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compress.jinx +0 -0
  61. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.npc +0 -0
  62. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.png +0 -0
  63. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca_example.png +0 -0
  64. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  65. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/foreman.npc +0 -0
  66. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic.npc +0 -0
  67. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic4.png +0 -0
  68. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/guac.png +0 -0
  69. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/help.jinx +0 -0
  70. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/init.jinx +0 -0
  71. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
  72. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  73. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  74. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  75. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
  76. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  77. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  78. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/ots.jinx +0 -0
  79. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.npc +0 -0
  80. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.png +0 -0
  81. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  82. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  83. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/python.jinx +0 -0
  84. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/roll.jinx +0 -0
  85. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sample.jinx +0 -0
  86. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/serve.jinx +0 -0
  87. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/set.jinx +0 -0
  88. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  89. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.png +0 -0
  90. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  91. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/spool.png +0 -0
  92. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sql.jinx +0 -0
  93. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  94. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  95. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/yap.png +0 -0
  96. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/WHEEL +0 -0
  97. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/entry_points.txt +0 -0
  98. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/licenses/LICENSE +0 -0
  99. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/top_level.txt +0 -0
npcsh/ui.py ADDED
@@ -0,0 +1,199 @@
1
+ """
2
+ UI helpers for npcsh - spinners, colors, formatting
3
+ """
4
+ import sys
5
+ import threading
6
+ import time
7
+ from termcolor import colored
8
+
9
+
10
+ class SpinnerContext:
11
+ """Context manager for showing a spinner during long operations.
12
+
13
+ Supports ESC key to interrupt (raises KeyboardInterrupt).
14
+ Tracks elapsed time and token counts.
15
+ """
16
+
17
+ SPINNER_CHARS = {
18
+ "dots": "⣾⣽⣻⢿⡿⣟⣯⣷",
19
+ "dots_pulse": "⣾⣽⣻⢿⡿⣟⣯⣷",
20
+ "line": "-\\|/",
21
+ "arrow": "←↖↑↗→↘↓↙",
22
+ "brain": "🧠💭💡✨",
23
+ }
24
+
25
+ def __init__(self, message: str, style: str = "dots", delay: float = 0.1):
26
+ self.message = message
27
+ self.style = style
28
+ self.delay = delay
29
+ self.spinner = self.SPINNER_CHARS.get(style, self.SPINNER_CHARS["dots"])
30
+ self._stop = False
31
+ self._thread = None
32
+ self._key_thread = None
33
+ self._interrupted = False
34
+ self._old_settings = None
35
+ self._start_time = None
36
+ self._tokens_in = 0
37
+ self._tokens_out = 0
38
+ self._status_msg = ""
39
+
40
+ def update_tokens(self, tokens_in: int = 0, tokens_out: int = 0):
41
+ """Update token counts displayed in spinner."""
42
+ self._tokens_in += tokens_in
43
+ self._tokens_out += tokens_out
44
+
45
+ def set_status(self, msg: str):
46
+ """Set additional status message."""
47
+ self._status_msg = msg
48
+
49
+ def __enter__(self):
50
+ self._stop = False
51
+ self._interrupted = False
52
+ self._start_time = time.time()
53
+ self._thread = threading.Thread(target=self._spin, daemon=True)
54
+ self._thread.start()
55
+ # Start key listener for ESC
56
+ self._key_thread = threading.Thread(target=self._listen_for_esc, daemon=True)
57
+ self._key_thread.start()
58
+ return self
59
+
60
+ def __exit__(self, *args):
61
+ self._stop = True
62
+ if self._thread:
63
+ self._thread.join(timeout=0.5)
64
+ # Clear spinner line
65
+ sys.stdout.write('\r' + ' ' * (len(self.message) + 20) + '\r')
66
+ sys.stdout.flush()
67
+ # Check if we were interrupted by ESC
68
+ if self._interrupted:
69
+ raise KeyboardInterrupt("ESC pressed")
70
+
71
+ def _listen_for_esc(self):
72
+ """Listen for ESC key press to interrupt processing."""
73
+ try:
74
+ import termios
75
+ import tty
76
+ import select
77
+
78
+ fd = sys.stdin.fileno()
79
+ self._old_settings = termios.tcgetattr(fd)
80
+ try:
81
+ tty.setcbreak(fd)
82
+ while not self._stop:
83
+ # Check if input is available (non-blocking)
84
+ if select.select([sys.stdin], [], [], 0.1)[0]:
85
+ ch = sys.stdin.read(1)
86
+ if ch == '\x1b': # ESC key
87
+ self._interrupted = True
88
+ self._stop = True
89
+ break
90
+ finally:
91
+ termios.tcsetattr(fd, termios.TCSADRAIN, self._old_settings)
92
+ except Exception:
93
+ # If we can't set up terminal raw mode (e.g., not a tty), just skip ESC detection
94
+ pass
95
+
96
+ def _spin(self):
97
+ idx = 0
98
+ while not self._stop:
99
+ char = self.spinner[idx % len(self.spinner)]
100
+
101
+ # Build status line with timer
102
+ elapsed = time.time() - self._start_time if self._start_time else 0
103
+ mins, secs = divmod(int(elapsed), 60)
104
+ timer_str = f"{mins}:{secs:02d}" if mins else f"{secs}s"
105
+
106
+ # Token info if available
107
+ token_str = ""
108
+ if self._tokens_in or self._tokens_out:
109
+ token_str = colored(f" [{self._tokens_in}→{self._tokens_out} tok]", "cyan")
110
+
111
+ # Additional status
112
+ status_str = ""
113
+ if self._status_msg:
114
+ status_str = colored(f" {self._status_msg}", "yellow")
115
+
116
+ hint = colored(" (ESC to cancel)", "white", attrs=["dark"])
117
+ timer_display = colored(f" [{timer_str}]", "blue")
118
+
119
+ line = f'\r{char} {self.message}...{timer_display}{token_str}{status_str}{hint}'
120
+ # Clear rest of line
121
+ sys.stdout.write(line + ' ' * 10)
122
+ sys.stdout.flush()
123
+ idx += 1
124
+ time.sleep(self.delay)
125
+
126
+
127
+ def show_thinking_animation(message="Thinking", duration=None):
128
+ """Show a thinking animation for a fixed duration or until interrupted"""
129
+ spinner = SpinnerContext(message)
130
+ with spinner:
131
+ if duration:
132
+ time.sleep(duration)
133
+ else:
134
+ # Run until interrupted
135
+ try:
136
+ while True:
137
+ time.sleep(0.1)
138
+ except KeyboardInterrupt:
139
+ pass
140
+
141
+
142
+ def orange(text: str) -> str:
143
+ """Return text colored orange using colorama"""
144
+ from colorama import Fore, Style
145
+ return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
146
+
147
+
148
+ def get_file_color(filepath: str) -> tuple:
149
+ """Get color for file listing based on file type"""
150
+ import os
151
+ from colorama import Fore, Style
152
+
153
+ if os.path.isdir(filepath):
154
+ return Fore.BLUE, Style.BRIGHT
155
+ elif os.path.islink(filepath):
156
+ return Fore.CYAN, ""
157
+ elif os.access(filepath, os.X_OK):
158
+ return Fore.GREEN, Style.BRIGHT
159
+ elif filepath.endswith(('.py', '.sh', '.bash', '.zsh')):
160
+ return Fore.GREEN, ""
161
+ elif filepath.endswith(('.md', '.txt', '.rst')):
162
+ return Fore.WHITE, ""
163
+ elif filepath.endswith(('.json', '.yaml', '.yml', '.toml')):
164
+ return Fore.YELLOW, ""
165
+ elif filepath.endswith(('.jpg', '.png', '.gif', '.svg', '.ico')):
166
+ return Fore.MAGENTA, ""
167
+ else:
168
+ return "", ""
169
+
170
+
171
+ def format_file_listing(output: str) -> str:
172
+ """Format file listing output with colors"""
173
+ import os
174
+ from colorama import Style
175
+
176
+ lines = output.strip().split('\n')
177
+ formatted = []
178
+
179
+ for line in lines:
180
+ if not line.strip():
181
+ formatted.append(line)
182
+ continue
183
+
184
+ # Try to color the file part
185
+ parts = line.rsplit('/', 1)
186
+ if len(parts) == 2:
187
+ path, filename = parts
188
+ fg, style = get_file_color(line)
189
+ formatted.append(f"{path}/{fg}{style}{filename}{Style.RESET_ALL}")
190
+ else:
191
+ formatted.append(line)
192
+
193
+ return '\n'.join(formatted)
194
+
195
+
196
+ def wrap_text(text: str, width: int = 80) -> str:
197
+ """Wrap text to specified width"""
198
+ import textwrap
199
+ return textwrap.fill(text, width=width)