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.
- npcsh/_state.py +700 -377
- npcsh/alicanto.py +54 -1153
- npcsh/completion.py +206 -0
- npcsh/config.py +163 -0
- npcsh/corca.py +35 -1462
- npcsh/execution.py +185 -0
- npcsh/guac.py +31 -1986
- npcsh/npc_team/jinxs/code/sh.jinx +11 -15
- npcsh/npc_team/jinxs/modes/alicanto.jinx +186 -80
- npcsh/npc_team/jinxs/modes/corca.jinx +243 -22
- npcsh/npc_team/jinxs/modes/guac.jinx +313 -42
- npcsh/npc_team/jinxs/modes/plonk.jinx +209 -48
- npcsh/npc_team/jinxs/modes/pti.jinx +167 -25
- npcsh/npc_team/jinxs/modes/spool.jinx +158 -37
- npcsh/npc_team/jinxs/modes/wander.jinx +179 -74
- npcsh/npc_team/jinxs/modes/yap.jinx +258 -21
- npcsh/npc_team/jinxs/utils/chat.jinx +39 -12
- npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
- npcsh/npc_team/jinxs/utils/search.jinx +3 -3
- npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
- npcsh/npcsh.py +76 -20
- npcsh/parsing.py +118 -0
- npcsh/plonk.py +41 -329
- npcsh/pti.py +41 -201
- npcsh/spool.py +34 -239
- npcsh/ui.py +199 -0
- npcsh/wander.py +54 -542
- npcsh/yap.py +38 -570
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +194 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/chat.jinx +44 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/cmd.jinx +44 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +249 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +317 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +214 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +170 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/search.jinx +3 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/sh.jinx +34 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/spool.jinx +161 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/usage.jinx +33 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +186 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/yap.jinx +262 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/METADATA +1 -1
- npcsh-1.1.14.dist-info/RECORD +135 -0
- npcsh-1.1.12.data/data/npcsh/npc_team/alicanto.jinx +0 -88
- npcsh-1.1.12.data/data/npcsh/npc_team/chat.jinx +0 -17
- npcsh-1.1.12.data/data/npcsh/npc_team/corca.jinx +0 -28
- npcsh-1.1.12.data/data/npcsh/npc_team/guac.jinx +0 -46
- npcsh-1.1.12.data/data/npcsh/npc_team/plonk.jinx +0 -53
- npcsh-1.1.12.data/data/npcsh/npc_team/pti.jinx +0 -28
- npcsh-1.1.12.data/data/npcsh/npc_team/sh.jinx +0 -38
- npcsh-1.1.12.data/data/npcsh/npc_team/spool.jinx +0 -40
- npcsh-1.1.12.data/data/npcsh/npc_team/wander.jinx +0 -81
- npcsh-1.1.12.data/data/npcsh/npc_team/yap.jinx +0 -25
- npcsh-1.1.12.dist-info/RECORD +0 -126
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/agent.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sql.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/WHEEL +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/licenses/LICENSE +0 -0
- {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)
|