npcsh 0.1.2__py3-none-any.whl → 1.1.13__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 (143) hide show
  1. npcsh/_state.py +3508 -0
  2. npcsh/alicanto.py +65 -0
  3. npcsh/build.py +291 -0
  4. npcsh/completion.py +206 -0
  5. npcsh/config.py +163 -0
  6. npcsh/corca.py +50 -0
  7. npcsh/execution.py +185 -0
  8. npcsh/guac.py +46 -0
  9. npcsh/mcp_helpers.py +357 -0
  10. npcsh/mcp_server.py +299 -0
  11. npcsh/npc.py +323 -0
  12. npcsh/npc_team/alicanto.npc +2 -0
  13. npcsh/npc_team/alicanto.png +0 -0
  14. npcsh/npc_team/corca.npc +12 -0
  15. npcsh/npc_team/corca.png +0 -0
  16. npcsh/npc_team/corca_example.png +0 -0
  17. npcsh/npc_team/foreman.npc +7 -0
  18. npcsh/npc_team/frederic.npc +6 -0
  19. npcsh/npc_team/frederic4.png +0 -0
  20. npcsh/npc_team/guac.png +0 -0
  21. npcsh/npc_team/jinxs/code/python.jinx +11 -0
  22. npcsh/npc_team/jinxs/code/sh.jinx +34 -0
  23. npcsh/npc_team/jinxs/code/sql.jinx +16 -0
  24. npcsh/npc_team/jinxs/modes/alicanto.jinx +194 -0
  25. npcsh/npc_team/jinxs/modes/corca.jinx +249 -0
  26. npcsh/npc_team/jinxs/modes/guac.jinx +317 -0
  27. npcsh/npc_team/jinxs/modes/plonk.jinx +214 -0
  28. npcsh/npc_team/jinxs/modes/pti.jinx +170 -0
  29. npcsh/npc_team/jinxs/modes/spool.jinx +161 -0
  30. npcsh/npc_team/jinxs/modes/wander.jinx +186 -0
  31. npcsh/npc_team/jinxs/modes/yap.jinx +262 -0
  32. npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx +77 -0
  33. npcsh/npc_team/jinxs/utils/agent.jinx +17 -0
  34. npcsh/npc_team/jinxs/utils/chat.jinx +44 -0
  35. npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
  36. npcsh/npc_team/jinxs/utils/compress.jinx +140 -0
  37. npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
  38. npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
  39. npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
  40. npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
  41. npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
  42. npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
  43. npcsh/npc_team/jinxs/utils/edit_file.jinx +94 -0
  44. npcsh/npc_team/jinxs/utils/load_file.jinx +35 -0
  45. npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
  46. npcsh/npc_team/jinxs/utils/roll.jinx +68 -0
  47. npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
  48. npcsh/npc_team/jinxs/utils/search.jinx +130 -0
  49. npcsh/npc_team/jinxs/utils/serve.jinx +26 -0
  50. npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
  51. npcsh/npc_team/jinxs/utils/trigger.jinx +61 -0
  52. npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
  53. npcsh/npc_team/jinxs/utils/vixynt.jinx +144 -0
  54. npcsh/npc_team/kadiefa.npc +3 -0
  55. npcsh/npc_team/kadiefa.png +0 -0
  56. npcsh/npc_team/npcsh.ctx +18 -0
  57. npcsh/npc_team/npcsh_sibiji.png +0 -0
  58. npcsh/npc_team/plonk.npc +2 -0
  59. npcsh/npc_team/plonk.png +0 -0
  60. npcsh/npc_team/plonkjr.npc +2 -0
  61. npcsh/npc_team/plonkjr.png +0 -0
  62. npcsh/npc_team/sibiji.npc +3 -0
  63. npcsh/npc_team/sibiji.png +0 -0
  64. npcsh/npc_team/spool.png +0 -0
  65. npcsh/npc_team/yap.png +0 -0
  66. npcsh/npcsh.py +296 -112
  67. npcsh/parsing.py +118 -0
  68. npcsh/plonk.py +54 -0
  69. npcsh/pti.py +54 -0
  70. npcsh/routes.py +139 -0
  71. npcsh/spool.py +48 -0
  72. npcsh/ui.py +199 -0
  73. npcsh/wander.py +62 -0
  74. npcsh/yap.py +50 -0
  75. npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +17 -0
  76. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +194 -0
  77. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +2 -0
  78. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.png +0 -0
  79. npcsh-1.1.13.data/data/npcsh/npc_team/build.jinx +65 -0
  80. npcsh-1.1.13.data/data/npcsh/npc_team/chat.jinx +44 -0
  81. npcsh-1.1.13.data/data/npcsh/npc_team/cmd.jinx +44 -0
  82. npcsh-1.1.13.data/data/npcsh/npc_team/compile.jinx +50 -0
  83. npcsh-1.1.13.data/data/npcsh/npc_team/compress.jinx +140 -0
  84. npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +249 -0
  85. npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +12 -0
  86. npcsh-1.1.13.data/data/npcsh/npc_team/corca.png +0 -0
  87. npcsh-1.1.13.data/data/npcsh/npc_team/corca_example.png +0 -0
  88. npcsh-1.1.13.data/data/npcsh/npc_team/edit_file.jinx +94 -0
  89. npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +7 -0
  90. npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +6 -0
  91. npcsh-1.1.13.data/data/npcsh/npc_team/frederic4.png +0 -0
  92. npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +317 -0
  93. npcsh-1.1.13.data/data/npcsh/npc_team/guac.png +0 -0
  94. npcsh-1.1.13.data/data/npcsh/npc_team/help.jinx +52 -0
  95. npcsh-1.1.13.data/data/npcsh/npc_team/init.jinx +41 -0
  96. npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +32 -0
  97. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +3 -0
  98. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.png +0 -0
  99. npcsh-1.1.13.data/data/npcsh/npc_team/load_file.jinx +35 -0
  100. npcsh-1.1.13.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
  101. npcsh-1.1.13.data/data/npcsh/npc_team/npcsh.ctx +18 -0
  102. npcsh-1.1.13.data/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  103. npcsh-1.1.13.data/data/npcsh/npc_team/ots.jinx +61 -0
  104. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +214 -0
  105. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +2 -0
  106. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.png +0 -0
  107. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +2 -0
  108. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.png +0 -0
  109. npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +170 -0
  110. npcsh-1.1.13.data/data/npcsh/npc_team/python.jinx +11 -0
  111. npcsh-1.1.13.data/data/npcsh/npc_team/roll.jinx +68 -0
  112. npcsh-1.1.13.data/data/npcsh/npc_team/sample.jinx +56 -0
  113. npcsh-1.1.13.data/data/npcsh/npc_team/search.jinx +130 -0
  114. npcsh-1.1.13.data/data/npcsh/npc_team/serve.jinx +26 -0
  115. npcsh-1.1.13.data/data/npcsh/npc_team/set.jinx +40 -0
  116. npcsh-1.1.13.data/data/npcsh/npc_team/sh.jinx +34 -0
  117. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +3 -0
  118. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.png +0 -0
  119. npcsh-1.1.13.data/data/npcsh/npc_team/sleep.jinx +116 -0
  120. npcsh-1.1.13.data/data/npcsh/npc_team/spool.jinx +161 -0
  121. npcsh-1.1.13.data/data/npcsh/npc_team/spool.png +0 -0
  122. npcsh-1.1.13.data/data/npcsh/npc_team/sql.jinx +16 -0
  123. npcsh-1.1.13.data/data/npcsh/npc_team/trigger.jinx +61 -0
  124. npcsh-1.1.13.data/data/npcsh/npc_team/usage.jinx +33 -0
  125. npcsh-1.1.13.data/data/npcsh/npc_team/vixynt.jinx +144 -0
  126. npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +186 -0
  127. npcsh-1.1.13.data/data/npcsh/npc_team/yap.jinx +262 -0
  128. npcsh-1.1.13.data/data/npcsh/npc_team/yap.png +0 -0
  129. npcsh-1.1.13.dist-info/METADATA +522 -0
  130. npcsh-1.1.13.dist-info/RECORD +135 -0
  131. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/WHEEL +1 -1
  132. npcsh-1.1.13.dist-info/entry_points.txt +9 -0
  133. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info/licenses}/LICENSE +1 -1
  134. npcsh/command_history.py +0 -81
  135. npcsh/helpers.py +0 -36
  136. npcsh/llm_funcs.py +0 -295
  137. npcsh/main.py +0 -5
  138. npcsh/modes.py +0 -343
  139. npcsh/npc_compiler.py +0 -124
  140. npcsh-0.1.2.dist-info/METADATA +0 -99
  141. npcsh-0.1.2.dist-info/RECORD +0 -14
  142. npcsh-0.1.2.dist-info/entry_points.txt +0 -2
  143. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/top_level.txt +0 -0
npcsh/routes.py ADDED
@@ -0,0 +1,139 @@
1
+ from typing import Callable, Dict, Any, List, Optional
2
+ import functools
3
+ import os
4
+ import traceback
5
+ import sys
6
+ import inspect
7
+ from pathlib import Path
8
+
9
+ from npcpy.npc_compiler import Jinx, load_jinxs_from_directory, extract_jinx_inputs
10
+
11
+
12
+ class CommandRouter:
13
+ def __init__(self):
14
+ self.routes = {}
15
+ self.help_info = {}
16
+ self.jinx_routes = {}
17
+
18
+ def route(self, command: str, help_text: str = "") -> Callable:
19
+ def wrapper(func):
20
+ self.routes[command] = func
21
+ self.help_info[command] = help_text
22
+
23
+ @functools.wraps(func)
24
+ def wrapped_func(*args, **kwargs):
25
+ return func(*args, **kwargs)
26
+
27
+ return wrapped_func
28
+ return wrapper
29
+
30
+ def load_jinx_routes(self, jinxs_dir: str):
31
+ if not os.path.exists(jinxs_dir):
32
+ print(f"Jinxs directory not found: {jinxs_dir}")
33
+ return
34
+
35
+ for root, dirs, files in os.walk(jinxs_dir):
36
+ for filename in files:
37
+ if filename.endswith('.jinx'):
38
+ jinx_path = os.path.join(root, filename)
39
+ try:
40
+ jinx = Jinx(jinx_path=jinx_path)
41
+ self.register_jinx(jinx)
42
+ except Exception as e:
43
+ print(f"Error loading jinx {filename}: {e}")
44
+
45
+ def register_jinx(self, jinx: Jinx):
46
+ command_name = jinx.jinx_name
47
+
48
+ def jinx_handler(command: str, **kwargs):
49
+ return self._execute_jinx(jinx, command, **kwargs)
50
+
51
+ self.jinx_routes[command_name] = jinx_handler
52
+ self.help_info[command_name] = jinx.description or "Jinx command"
53
+
54
+ def _execute_jinx(self, jinx: Jinx, command: str, **kwargs):
55
+ messages = kwargs.get("messages", [])
56
+ npc = kwargs.get('npc')
57
+
58
+ try:
59
+ import shlex
60
+
61
+ parts = shlex.split(command)
62
+ args = parts[1:] if len(parts) > 1 else []
63
+
64
+ # Use extract_jinx_inputs
65
+ input_values = extract_jinx_inputs(args, jinx)
66
+
67
+ # Build extra_globals for jinx execution
68
+ from npcpy.memory.command_history import CommandHistory, load_kg_from_db
69
+ from npcpy.memory.search import execute_rag_command, execute_brainblast_command
70
+ from npcpy.data.load import load_file_contents
71
+ from npcpy.data.web import search_web
72
+
73
+ application_globals_for_jinx = {
74
+ "CommandHistory": CommandHistory,
75
+ "load_kg_from_db": load_kg_from_db,
76
+ "execute_rag_command": execute_rag_command,
77
+ "execute_brainblast_command": execute_brainblast_command,
78
+ "load_file_contents": load_file_contents,
79
+ "search_web": search_web,
80
+ 'state': kwargs.get('state')
81
+ }
82
+
83
+ # Add functions from _state module if available
84
+ try:
85
+ from npcsh import _state
86
+ for name, func in inspect.getmembers(_state, inspect.isfunction):
87
+ application_globals_for_jinx[name] = func
88
+ except:
89
+ pass
90
+
91
+ jinx_output = jinx.execute(
92
+ input_values=input_values,
93
+ npc=npc,
94
+ messages=messages,
95
+ extra_globals=application_globals_for_jinx
96
+ )
97
+
98
+ if isinstance(jinx_output, dict):
99
+ return {
100
+ "output": jinx_output.get('output', str(jinx_output)),
101
+ "messages": jinx_output.get('messages', messages)
102
+ }
103
+ else:
104
+ return {"output": str(jinx_output), "messages": messages}
105
+
106
+ except Exception as e:
107
+ traceback.print_exc()
108
+ return {
109
+ "output": f"Error executing jinx '{jinx.jinx_name}': {e}",
110
+ "messages": messages
111
+ }
112
+
113
+ def get_route(self, command: str) -> Optional[Callable]:
114
+ if command in self.routes:
115
+ return self.routes[command]
116
+ elif command in self.jinx_routes:
117
+ return self.jinx_routes[command]
118
+ return None
119
+
120
+ def execute(self, command_str: str, **kwargs) -> Any:
121
+ command_name = command_str.split()[0].lstrip('/')
122
+ route_func = self.get_route(command_name)
123
+ if route_func:
124
+ return route_func(command=command_str, **kwargs)
125
+ return None
126
+
127
+ def get_commands(self) -> List[str]:
128
+ all_commands = list(self.routes.keys()) + list(self.jinx_routes.keys())
129
+ return sorted(set(all_commands))
130
+
131
+ def get_help(self, command: str = None) -> Dict[str, str]:
132
+ if command:
133
+ if command in self.help_info:
134
+ return {command: self.help_info[command]}
135
+ return {}
136
+ return self.help_info
137
+
138
+
139
+ router = CommandRouter()
npcsh/spool.py ADDED
@@ -0,0 +1,48 @@
1
+ """
2
+ spool - Interactive chat mode CLI entry point
3
+
4
+ This is a thin wrapper that executes the spool.jinx through the jinx mechanism.
5
+ """
6
+ import argparse
7
+ import os
8
+ import sys
9
+
10
+ from npcsh._state import setup_shell
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="spool - Interactive chat mode")
15
+ parser.add_argument("--model", "-m", type=str, help="LLM model to use")
16
+ parser.add_argument("--provider", "-p", type=str, help="LLM provider to use")
17
+ parser.add_argument("--files", "-f", nargs="*", help="Files to load for RAG context")
18
+ parser.add_argument("--no-stream", action="store_true", help="Disable streaming")
19
+ args = parser.parse_args()
20
+
21
+ # Setup shell to get team and default NPC
22
+ command_history, team, default_npc = setup_shell()
23
+
24
+ if not team or "spool" not in team.jinxs_dict:
25
+ print("Error: spool jinx not found. Ensure npc_team/jinxs/modes/spool.jinx exists.")
26
+ sys.exit(1)
27
+
28
+ # Build context for jinx execution
29
+ context = {
30
+ "npc": default_npc,
31
+ "team": team,
32
+ "messages": [],
33
+ "model": args.model,
34
+ "provider": args.provider,
35
+ "attachments": ",".join(args.files) if args.files else None,
36
+ "stream": not args.no_stream,
37
+ }
38
+
39
+ # Execute the jinx
40
+ spool_jinx = team.jinxs_dict["spool"]
41
+ result = spool_jinx.execute(context=context, npc=default_npc)
42
+
43
+ if isinstance(result, dict) and result.get("output"):
44
+ print(result["output"])
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
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)
npcsh/wander.py ADDED
@@ -0,0 +1,62 @@
1
+ """
2
+ wander - Experimental wandering mode CLI entry point
3
+
4
+ This is a thin wrapper that executes the wander.jinx through the jinx mechanism.
5
+ """
6
+ import argparse
7
+ import os
8
+ import sys
9
+
10
+ from npcsh._state import setup_shell
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="wander - Creative exploration with varied temperatures")
15
+ parser.add_argument("problem", nargs="*", help="Problem to explore through wandering")
16
+ parser.add_argument("--model", "-m", type=str, help="LLM model to use")
17
+ parser.add_argument("--provider", "-p", type=str, help="LLM provider to use")
18
+ parser.add_argument("--environment", type=str, help="Metaphorical environment for wandering")
19
+ parser.add_argument("--low-temp", type=float, default=0.5, help="Low temperature setting")
20
+ parser.add_argument("--high-temp", type=float, default=1.9, help="High temperature setting")
21
+ parser.add_argument("--n-streams", type=int, default=5, help="Number of exploration streams")
22
+ parser.add_argument("--include-events", action="store_true", help="Include random events")
23
+ parser.add_argument("--num-events", type=int, default=3, help="Number of events per stream")
24
+ args = parser.parse_args()
25
+
26
+ if not args.problem:
27
+ parser.print_help()
28
+ sys.exit(1)
29
+
30
+ # Setup shell to get team and default NPC
31
+ command_history, team, default_npc = setup_shell()
32
+
33
+ if not team or "wander" not in team.jinxs_dict:
34
+ print("Error: wander jinx not found. Ensure npc_team/jinxs/modes/wander.jinx exists.")
35
+ sys.exit(1)
36
+
37
+ # Build context for jinx execution
38
+ context = {
39
+ "npc": default_npc,
40
+ "team": team,
41
+ "messages": [],
42
+ "problem": " ".join(args.problem),
43
+ "model": args.model,
44
+ "provider": args.provider,
45
+ "environment": args.environment,
46
+ "low_temp": args.low_temp,
47
+ "high_temp": args.high_temp,
48
+ "n_streams": args.n_streams,
49
+ "include_events": args.include_events,
50
+ "num_events": args.num_events,
51
+ }
52
+
53
+ # Execute the jinx
54
+ wander_jinx = team.jinxs_dict["wander"]
55
+ result = wander_jinx.execute(context=context, npc=default_npc)
56
+
57
+ if isinstance(result, dict) and result.get("output"):
58
+ print(result["output"])
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
npcsh/yap.py ADDED
@@ -0,0 +1,50 @@
1
+ """
2
+ yap - Voice chat mode CLI entry point
3
+
4
+ This is a thin wrapper that executes the yap.jinx through the jinx mechanism.
5
+ """
6
+ import argparse
7
+ import os
8
+ import sys
9
+
10
+ from npcsh._state import setup_shell
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="yap - Voice chat mode")
15
+ parser.add_argument("--model", "-m", type=str, help="LLM model to use")
16
+ parser.add_argument("--provider", "-p", type=str, help="LLM provider to use")
17
+ parser.add_argument("--files", "-f", nargs="*", help="Files to load for RAG context")
18
+ parser.add_argument("--tts-model", type=str, default="kokoro", help="TTS model to use")
19
+ parser.add_argument("--voice", type=str, default="af_heart", help="Voice for TTS")
20
+ args = parser.parse_args()
21
+
22
+ # Setup shell to get team and default NPC
23
+ command_history, team, default_npc = setup_shell()
24
+
25
+ if not team or "yap" not in team.jinxs_dict:
26
+ print("Error: yap jinx not found. Ensure npc_team/jinxs/modes/yap.jinx exists.")
27
+ sys.exit(1)
28
+
29
+ # Build context for jinx execution
30
+ context = {
31
+ "npc": default_npc,
32
+ "team": team,
33
+ "messages": [],
34
+ "model": args.model,
35
+ "provider": args.provider,
36
+ "files": ",".join(args.files) if args.files else None,
37
+ "tts_model": args.tts_model,
38
+ "voice": args.voice,
39
+ }
40
+
41
+ # Execute the jinx
42
+ yap_jinx = team.jinxs_dict["yap"]
43
+ result = yap_jinx.execute(context=context, npc=default_npc)
44
+
45
+ if isinstance(result, dict) and result.get("output"):
46
+ print(result["output"])
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -0,0 +1,17 @@
1
+ jinx_name: agent
2
+ description: Provides an LLM response with tool use enabled.
3
+ inputs:
4
+ - query
5
+ - auto_process_tool_calls: True
6
+ - use_core_tools: True
7
+ steps:
8
+ - name: get_agent_response
9
+ engine: python
10
+ code: |
11
+ response = npc.get_llm_response(
12
+ request=query,
13
+ messages=context.get('messages', []),
14
+ auto_process_tool_calls={{ auto_process_tool_calls | default(True) }},
15
+ use_core_tools={{ use_core_tools | default(True) }}
16
+ )
17
+ output = response.get('response', '')