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.
- npcsh/_state.py +3508 -0
- npcsh/alicanto.py +65 -0
- npcsh/build.py +291 -0
- npcsh/completion.py +206 -0
- npcsh/config.py +163 -0
- npcsh/corca.py +50 -0
- npcsh/execution.py +185 -0
- npcsh/guac.py +46 -0
- npcsh/mcp_helpers.py +357 -0
- npcsh/mcp_server.py +299 -0
- npcsh/npc.py +323 -0
- npcsh/npc_team/alicanto.npc +2 -0
- npcsh/npc_team/alicanto.png +0 -0
- npcsh/npc_team/corca.npc +12 -0
- npcsh/npc_team/corca.png +0 -0
- npcsh/npc_team/corca_example.png +0 -0
- npcsh/npc_team/foreman.npc +7 -0
- npcsh/npc_team/frederic.npc +6 -0
- npcsh/npc_team/frederic4.png +0 -0
- npcsh/npc_team/guac.png +0 -0
- npcsh/npc_team/jinxs/code/python.jinx +11 -0
- npcsh/npc_team/jinxs/code/sh.jinx +34 -0
- npcsh/npc_team/jinxs/code/sql.jinx +16 -0
- npcsh/npc_team/jinxs/modes/alicanto.jinx +194 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +249 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +317 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +214 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +170 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +161 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +186 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +262 -0
- npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx +77 -0
- npcsh/npc_team/jinxs/utils/agent.jinx +17 -0
- npcsh/npc_team/jinxs/utils/chat.jinx +44 -0
- npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
- npcsh/npc_team/jinxs/utils/compress.jinx +140 -0
- npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
- npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
- npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
- npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
- npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
- npcsh/npc_team/jinxs/utils/edit_file.jinx +94 -0
- npcsh/npc_team/jinxs/utils/load_file.jinx +35 -0
- npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
- npcsh/npc_team/jinxs/utils/roll.jinx +68 -0
- npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/serve.jinx +26 -0
- npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
- npcsh/npc_team/jinxs/utils/trigger.jinx +61 -0
- npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +144 -0
- npcsh/npc_team/kadiefa.npc +3 -0
- npcsh/npc_team/kadiefa.png +0 -0
- npcsh/npc_team/npcsh.ctx +18 -0
- npcsh/npc_team/npcsh_sibiji.png +0 -0
- npcsh/npc_team/plonk.npc +2 -0
- npcsh/npc_team/plonk.png +0 -0
- npcsh/npc_team/plonkjr.npc +2 -0
- npcsh/npc_team/plonkjr.png +0 -0
- npcsh/npc_team/sibiji.npc +3 -0
- npcsh/npc_team/sibiji.png +0 -0
- npcsh/npc_team/spool.png +0 -0
- npcsh/npc_team/yap.png +0 -0
- npcsh/npcsh.py +296 -112
- npcsh/parsing.py +118 -0
- npcsh/plonk.py +54 -0
- npcsh/pti.py +54 -0
- npcsh/routes.py +139 -0
- npcsh/spool.py +48 -0
- npcsh/ui.py +199 -0
- npcsh/wander.py +62 -0
- npcsh/yap.py +50 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +17 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +194 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/build.jinx +65 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/chat.jinx +44 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/cmd.jinx +44 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/compile.jinx +50 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/compress.jinx +140 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +249 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +12 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca_example.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/edit_file.jinx +94 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +7 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +6 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic4.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +317 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/help.jinx +52 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/init.jinx +41 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +32 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +3 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/load_file.jinx +35 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npcsh.ctx +18 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +214 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +170 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/python.jinx +11 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/roll.jinx +68 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sample.jinx +56 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/search.jinx +130 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/serve.jinx +26 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/set.jinx +40 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sh.jinx +34 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +3 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sleep.jinx +116 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/spool.jinx +161 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/spool.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sql.jinx +16 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/trigger.jinx +61 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/usage.jinx +33 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/vixynt.jinx +144 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +186 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/yap.jinx +262 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/yap.png +0 -0
- npcsh-1.1.13.dist-info/METADATA +522 -0
- npcsh-1.1.13.dist-info/RECORD +135 -0
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/WHEEL +1 -1
- npcsh-1.1.13.dist-info/entry_points.txt +9 -0
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info/licenses}/LICENSE +1 -1
- npcsh/command_history.py +0 -81
- npcsh/helpers.py +0 -36
- npcsh/llm_funcs.py +0 -295
- npcsh/main.py +0 -5
- npcsh/modes.py +0 -343
- npcsh/npc_compiler.py +0 -124
- npcsh-0.1.2.dist-info/METADATA +0 -99
- npcsh-0.1.2.dist-info/RECORD +0 -14
- npcsh-0.1.2.dist-info/entry_points.txt +0 -2
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/top_level.txt +0 -0
npcsh/config.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
npcsh configuration management
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
from typing import Optional, Dict, Any
|
|
7
|
+
|
|
8
|
+
# Version
|
|
9
|
+
try:
|
|
10
|
+
VERSION = importlib.metadata.version("npcsh")
|
|
11
|
+
except importlib.metadata.PackageNotFoundError:
|
|
12
|
+
VERSION = "unknown"
|
|
13
|
+
|
|
14
|
+
# Default paths
|
|
15
|
+
DEFAULT_NPC_TEAM_PATH = "~/.npcsh/npc_team"
|
|
16
|
+
PROJECT_NPC_TEAM_PATH = "./npc_team"
|
|
17
|
+
HISTORY_DB_DEFAULT_PATH = "~/.npcsh_history.db"
|
|
18
|
+
READLINE_HISTORY_FILE = os.path.expanduser("~/.npcsh_history")
|
|
19
|
+
|
|
20
|
+
# Environment defaults
|
|
21
|
+
NPCSH_CHAT_MODEL = os.environ.get("NPCSH_CHAT_MODEL", "gemma3:4b")
|
|
22
|
+
NPCSH_CHAT_PROVIDER = os.environ.get("NPCSH_CHAT_PROVIDER", "ollama")
|
|
23
|
+
NPCSH_DB_PATH = os.path.expanduser(
|
|
24
|
+
os.environ.get("NPCSH_DB_PATH", "~/npcsh_history.db")
|
|
25
|
+
)
|
|
26
|
+
NPCSH_VECTOR_DB_PATH = os.path.expanduser(
|
|
27
|
+
os.environ.get("NPCSH_VECTOR_DB_PATH", "~/npcsh_chroma.db")
|
|
28
|
+
)
|
|
29
|
+
NPCSH_DEFAULT_MODE = os.environ.get("NPCSH_DEFAULT_MODE", "agent")
|
|
30
|
+
NPCSH_VISION_MODEL = os.environ.get("NPCSH_VISION_MODEL", "gemma3:4b")
|
|
31
|
+
NPCSH_VISION_PROVIDER = os.environ.get("NPCSH_VISION_PROVIDER", "ollama")
|
|
32
|
+
NPCSH_IMAGE_GEN_MODEL = os.environ.get(
|
|
33
|
+
"NPCSH_IMAGE_GEN_MODEL", "runwayml/stable-diffusion-v1-5"
|
|
34
|
+
)
|
|
35
|
+
NPCSH_IMAGE_GEN_PROVIDER = os.environ.get("NPCSH_IMAGE_GEN_PROVIDER", "diffusers")
|
|
36
|
+
NPCSH_VIDEO_GEN_MODEL = os.environ.get(
|
|
37
|
+
"NPCSH_VIDEO_GEN_MODEL", "damo-vilab/text-to-video-ms-1.7b"
|
|
38
|
+
)
|
|
39
|
+
NPCSH_VIDEO_GEN_PROVIDER = os.environ.get("NPCSH_VIDEO_GEN_PROVIDER", "diffusers")
|
|
40
|
+
NPCSH_EMBEDDING_MODEL = os.environ.get("NPCSH_EMBEDDING_MODEL", "nomic-embed-text")
|
|
41
|
+
NPCSH_EMBEDDING_PROVIDER = os.environ.get("NPCSH_EMBEDDING_PROVIDER", "ollama")
|
|
42
|
+
NPCSH_REASONING_MODEL = os.environ.get("NPCSH_REASONING_MODEL", "deepseek-r1")
|
|
43
|
+
NPCSH_REASONING_PROVIDER = os.environ.get("NPCSH_REASONING_PROVIDER", "ollama")
|
|
44
|
+
NPCSH_STREAM_OUTPUT = os.environ.get("NPCSH_STREAM_OUTPUT", "0") == "1"
|
|
45
|
+
NPCSH_API_URL = os.environ.get("NPCSH_API_URL", None)
|
|
46
|
+
NPCSH_SEARCH_PROVIDER = os.environ.get("NPCSH_SEARCH_PROVIDER", "duckduckgo")
|
|
47
|
+
NPCSH_BUILD_KG = os.environ.get("NPCSH_BUILD_KG") == "1"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_shell_config_file() -> str:
|
|
51
|
+
"""Get the path to the user's shell config file"""
|
|
52
|
+
shell = os.environ.get("SHELL", "/bin/bash")
|
|
53
|
+
|
|
54
|
+
if "zsh" in shell:
|
|
55
|
+
return os.path.expanduser("~/.zshrc")
|
|
56
|
+
elif "fish" in shell:
|
|
57
|
+
return os.path.expanduser("~/.config/fish/config.fish")
|
|
58
|
+
else:
|
|
59
|
+
return os.path.expanduser("~/.bashrc")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_npcshrc_path() -> str:
|
|
63
|
+
"""Get path to npcshrc file"""
|
|
64
|
+
return os.path.expanduser("~/.npcshrc")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_npcshrc_path_windows():
|
|
68
|
+
"""Get npcshrc path on Windows"""
|
|
69
|
+
return os.path.expanduser("~/.npcshrc")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def ensure_npcshrc_exists() -> str:
|
|
73
|
+
"""Ensure npcshrc file exists and return its path"""
|
|
74
|
+
npcshrc_path = get_npcshrc_path()
|
|
75
|
+
|
|
76
|
+
if not os.path.exists(npcshrc_path):
|
|
77
|
+
default_content = f"""# npcsh configuration file
|
|
78
|
+
export NPCSH_CHAT_MODEL="{NPCSH_CHAT_MODEL}"
|
|
79
|
+
export NPCSH_CHAT_PROVIDER="{NPCSH_CHAT_PROVIDER}"
|
|
80
|
+
export NPCSH_VISION_MODEL="{NPCSH_VISION_MODEL}"
|
|
81
|
+
export NPCSH_VISION_PROVIDER="{NPCSH_VISION_PROVIDER}"
|
|
82
|
+
export NPCSH_EMBEDDING_MODEL="{NPCSH_EMBEDDING_MODEL}"
|
|
83
|
+
export NPCSH_EMBEDDING_PROVIDER="{NPCSH_EMBEDDING_PROVIDER}"
|
|
84
|
+
export NPCSH_SEARCH_PROVIDER="{NPCSH_SEARCH_PROVIDER}"
|
|
85
|
+
export NPCSH_DEFAULT_MODE="{NPCSH_DEFAULT_MODE}"
|
|
86
|
+
export NPCSH_STREAM_OUTPUT="0"
|
|
87
|
+
"""
|
|
88
|
+
with open(npcshrc_path, 'w') as f:
|
|
89
|
+
f.write(default_content)
|
|
90
|
+
|
|
91
|
+
return npcshrc_path
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def add_npcshrc_to_shell_config() -> None:
|
|
95
|
+
"""Add sourcing of npcshrc to shell config if not present"""
|
|
96
|
+
shell_config = get_shell_config_file()
|
|
97
|
+
npcshrc_path = get_npcshrc_path()
|
|
98
|
+
|
|
99
|
+
source_line = f'source "{npcshrc_path}"'
|
|
100
|
+
|
|
101
|
+
if os.path.exists(shell_config):
|
|
102
|
+
with open(shell_config, 'r') as f:
|
|
103
|
+
content = f.read()
|
|
104
|
+
if npcshrc_path not in content and '.npcshrc' not in content:
|
|
105
|
+
with open(shell_config, 'a') as f:
|
|
106
|
+
f.write(f"\n# Source npcsh configuration\n{source_line}\n")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def setup_npcsh_config() -> None:
|
|
110
|
+
"""Set up npcsh configuration"""
|
|
111
|
+
ensure_npcshrc_exists()
|
|
112
|
+
add_npcshrc_to_shell_config()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_npcsh_initialized() -> bool:
|
|
116
|
+
"""Check if npcsh has been initialized"""
|
|
117
|
+
marker = os.path.expanduser("~/.npcsh/.initialized")
|
|
118
|
+
return os.path.exists(marker)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def set_npcsh_initialized() -> None:
|
|
122
|
+
"""Mark npcsh as initialized"""
|
|
123
|
+
npcsh_dir = os.path.expanduser("~/.npcsh")
|
|
124
|
+
os.makedirs(npcsh_dir, exist_ok=True)
|
|
125
|
+
marker = os.path.join(npcsh_dir, ".initialized")
|
|
126
|
+
with open(marker, 'w') as f:
|
|
127
|
+
f.write("1")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def set_npcsh_config_value(key: str, value: str) -> None:
|
|
131
|
+
"""Set a value in npcshrc"""
|
|
132
|
+
npcshrc_path = ensure_npcshrc_exists()
|
|
133
|
+
|
|
134
|
+
with open(npcshrc_path, 'r') as f:
|
|
135
|
+
lines = f.readlines()
|
|
136
|
+
|
|
137
|
+
found = False
|
|
138
|
+
for i, line in enumerate(lines):
|
|
139
|
+
if line.strip().startswith(f'export {key}='):
|
|
140
|
+
lines[i] = f'export {key}="{value}"\n'
|
|
141
|
+
found = True
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
if not found:
|
|
145
|
+
lines.append(f'export {key}="{value}"\n')
|
|
146
|
+
|
|
147
|
+
with open(npcshrc_path, 'w') as f:
|
|
148
|
+
f.writelines(lines)
|
|
149
|
+
|
|
150
|
+
# Also set in current environment
|
|
151
|
+
os.environ[key] = value
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_setting_windows(key, default=None):
|
|
155
|
+
"""Get setting on Windows"""
|
|
156
|
+
npcshrc_path = get_npcshrc_path_windows()
|
|
157
|
+
if os.path.exists(npcshrc_path):
|
|
158
|
+
with open(npcshrc_path, 'r') as f:
|
|
159
|
+
for line in f:
|
|
160
|
+
if line.strip().startswith(f'export {key}='):
|
|
161
|
+
value = line.split('=', 1)[1].strip().strip('"').strip("'")
|
|
162
|
+
return value
|
|
163
|
+
return default
|
npcsh/corca.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
corca - MCP-powered agentic shell CLI entry point
|
|
3
|
+
|
|
4
|
+
This is a thin wrapper that executes the corca.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="corca - MCP-powered agentic shell")
|
|
15
|
+
parser.add_argument("command", nargs="*", help="Optional one-shot command to execute")
|
|
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("--mcp-server", type=str, help="Path to MCP server script")
|
|
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 "corca" not in team.jinxs_dict:
|
|
25
|
+
print("Error: corca jinx not found. Ensure npc_team/jinxs/modes/corca.jinx exists.")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
# Build context for jinx execution
|
|
29
|
+
initial_command = " ".join(args.command) if args.command else None
|
|
30
|
+
|
|
31
|
+
context = {
|
|
32
|
+
"npc": default_npc,
|
|
33
|
+
"team": team,
|
|
34
|
+
"messages": [],
|
|
35
|
+
"model": args.model,
|
|
36
|
+
"provider": args.provider,
|
|
37
|
+
"mcp_server_path": args.mcp_server,
|
|
38
|
+
"initial_command": initial_command,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Execute the jinx
|
|
42
|
+
corca_jinx = team.jinxs_dict["corca"]
|
|
43
|
+
result = corca_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()
|
npcsh/execution.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command execution utilities for npcsh
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from typing import List, Tuple, Any, Optional
|
|
9
|
+
|
|
10
|
+
from termcolor import colored
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Commands that require interactive terminal handling
|
|
14
|
+
TERMINAL_EDITORS = ['vim', 'nvim', 'nano', 'vi', 'emacs', 'less', 'more', 'man']
|
|
15
|
+
|
|
16
|
+
# Interactive commands that need special handling (command -> args)
|
|
17
|
+
INTERACTIVE_COMMANDS = {
|
|
18
|
+
'ipython': ['ipython'],
|
|
19
|
+
'python': ['python', '-i'],
|
|
20
|
+
'python3': ['python3', '-i'],
|
|
21
|
+
'node': ['node'],
|
|
22
|
+
'irb': ['irb'],
|
|
23
|
+
'ghci': ['ghci'],
|
|
24
|
+
'mysql': ['mysql'],
|
|
25
|
+
'psql': ['psql'],
|
|
26
|
+
'sqlite3': ['sqlite3'],
|
|
27
|
+
'redis-cli': ['redis-cli'],
|
|
28
|
+
'mongo': ['mongo'],
|
|
29
|
+
'ssh': ['ssh'],
|
|
30
|
+
'telnet': ['telnet'],
|
|
31
|
+
'ftp': ['ftp'],
|
|
32
|
+
'sftp': ['sftp'],
|
|
33
|
+
'top': ['top'],
|
|
34
|
+
'htop': ['htop'],
|
|
35
|
+
'watch': ['watch'],
|
|
36
|
+
'r': ['R', '--interactive'],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def validate_bash_command(command_parts: List[str]) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Check if the command is a valid bash command.
|
|
43
|
+
|
|
44
|
+
Returns True if the command exists in PATH or is a shell builtin.
|
|
45
|
+
"""
|
|
46
|
+
if not command_parts:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
cmd = command_parts[0]
|
|
50
|
+
|
|
51
|
+
# Check shell builtins
|
|
52
|
+
builtins = {'cd', 'pwd', 'echo', 'export', 'source', 'alias', 'unalias',
|
|
53
|
+
'history', 'set', 'unset', 'read', 'eval', 'exec', 'exit',
|
|
54
|
+
'return', 'shift', 'trap', 'wait', 'jobs', 'fg', 'bg',
|
|
55
|
+
'kill', 'ulimit', 'umask', 'type', 'hash', 'true', 'false'}
|
|
56
|
+
|
|
57
|
+
if cmd in builtins:
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
# Check if command exists in PATH
|
|
61
|
+
return shutil.which(cmd) is not None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def handle_bash_command(
|
|
65
|
+
cmd_parts: List[str],
|
|
66
|
+
full_cmd: str,
|
|
67
|
+
stdin_input: Optional[str],
|
|
68
|
+
state: Any
|
|
69
|
+
) -> Tuple[bool, str]:
|
|
70
|
+
"""
|
|
71
|
+
Execute a bash command and return the result.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
cmd_parts: Parsed command parts
|
|
75
|
+
full_cmd: Full command string
|
|
76
|
+
stdin_input: Input to pipe to command
|
|
77
|
+
state: Shell state
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Tuple of (success, output)
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
result = subprocess.run(
|
|
84
|
+
full_cmd,
|
|
85
|
+
shell=True,
|
|
86
|
+
capture_output=True,
|
|
87
|
+
text=True,
|
|
88
|
+
cwd=state.current_path,
|
|
89
|
+
input=stdin_input,
|
|
90
|
+
timeout=300
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if result.returncode == 0:
|
|
94
|
+
output = result.stdout
|
|
95
|
+
if result.stderr:
|
|
96
|
+
output += f"\n{result.stderr}"
|
|
97
|
+
return True, output.strip()
|
|
98
|
+
else:
|
|
99
|
+
error = result.stderr or result.stdout or f"Command exited with code {result.returncode}"
|
|
100
|
+
return False, error.strip()
|
|
101
|
+
|
|
102
|
+
except subprocess.TimeoutExpired:
|
|
103
|
+
return False, "Command timed out after 5 minutes"
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return False, str(e)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def open_terminal_editor(command: str) -> str:
|
|
109
|
+
"""Open a terminal editor command interactively"""
|
|
110
|
+
try:
|
|
111
|
+
subprocess.run(command, shell=True)
|
|
112
|
+
return "Editor session completed"
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return f"Editor error: {e}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def handle_cd_command(cmd_parts: List[str], state: Any) -> Tuple[Any, str]:
|
|
118
|
+
"""Handle the cd command"""
|
|
119
|
+
if len(cmd_parts) < 2:
|
|
120
|
+
new_path = os.path.expanduser("~")
|
|
121
|
+
else:
|
|
122
|
+
new_path = os.path.expanduser(cmd_parts[1])
|
|
123
|
+
|
|
124
|
+
if not os.path.isabs(new_path):
|
|
125
|
+
new_path = os.path.join(state.current_path, new_path)
|
|
126
|
+
|
|
127
|
+
new_path = os.path.normpath(new_path)
|
|
128
|
+
|
|
129
|
+
if os.path.isdir(new_path):
|
|
130
|
+
state.current_path = new_path
|
|
131
|
+
os.chdir(new_path)
|
|
132
|
+
return state, f"Changed to: {new_path}"
|
|
133
|
+
else:
|
|
134
|
+
return state, colored(f"Directory not found: {new_path}", "red")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def handle_interactive_command(cmd_parts: List[str], state: Any) -> Tuple[Any, str]:
|
|
138
|
+
"""Handle interactive commands by running them in a subprocess"""
|
|
139
|
+
command = ' '.join(cmd_parts)
|
|
140
|
+
try:
|
|
141
|
+
subprocess.run(command, shell=True, cwd=state.current_path)
|
|
142
|
+
return state, f"Interactive session ({cmd_parts[0]}) completed"
|
|
143
|
+
except KeyboardInterrupt:
|
|
144
|
+
return state, colored("Session interrupted", "yellow")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
return state, colored(f"Error: {e}", "red")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def start_interactive_session(command: str) -> int:
|
|
150
|
+
"""Start an interactive shell session"""
|
|
151
|
+
try:
|
|
152
|
+
return subprocess.call(command, shell=True)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(colored(f"Error starting session: {e}", "red"))
|
|
155
|
+
return 1
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def list_directory(args: List[str]) -> None:
|
|
159
|
+
"""List directory contents with formatting"""
|
|
160
|
+
path = args[0] if args else "."
|
|
161
|
+
path = os.path.expanduser(path)
|
|
162
|
+
|
|
163
|
+
if not os.path.exists(path):
|
|
164
|
+
print(colored(f"Path not found: {path}", "red"))
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if os.path.isfile(path):
|
|
168
|
+
print(path)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
entries = os.listdir(path)
|
|
173
|
+
entries.sort()
|
|
174
|
+
|
|
175
|
+
for entry in entries:
|
|
176
|
+
full_path = os.path.join(path, entry)
|
|
177
|
+
if os.path.isdir(full_path):
|
|
178
|
+
print(colored(entry + "/", "blue", attrs=["bold"]))
|
|
179
|
+
elif os.access(full_path, os.X_OK):
|
|
180
|
+
print(colored(entry, "green", attrs=["bold"]))
|
|
181
|
+
else:
|
|
182
|
+
print(entry)
|
|
183
|
+
|
|
184
|
+
except PermissionError:
|
|
185
|
+
print(colored(f"Permission denied: {path}", "red"))
|
npcsh/guac.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
guac - Python data analysis mode CLI entry point
|
|
3
|
+
|
|
4
|
+
This is a thin wrapper that executes the guac.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="guac - Python data analysis 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("--plots-dir", type=str, help="Directory to save plots")
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
# Setup shell to get team and default NPC
|
|
21
|
+
command_history, team, default_npc = setup_shell()
|
|
22
|
+
|
|
23
|
+
if not team or "guac" not in team.jinxs_dict:
|
|
24
|
+
print("Error: guac jinx not found. Ensure npc_team/jinxs/modes/guac.jinx exists.")
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
# Build context for jinx execution
|
|
28
|
+
context = {
|
|
29
|
+
"npc": default_npc,
|
|
30
|
+
"team": team,
|
|
31
|
+
"messages": [],
|
|
32
|
+
"model": args.model,
|
|
33
|
+
"provider": args.provider,
|
|
34
|
+
"plots_dir": args.plots_dir,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Execute the jinx
|
|
38
|
+
guac_jinx = team.jinxs_dict["guac"]
|
|
39
|
+
result = guac_jinx.execute(context=context, npc=default_npc)
|
|
40
|
+
|
|
41
|
+
if isinstance(result, dict) and result.get("output"):
|
|
42
|
+
print(result["output"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|