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/completion.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Readline and tab completion for npcsh
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from typing import List, Any, Optional
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import readline
|
|
10
|
+
except ImportError:
|
|
11
|
+
readline = None
|
|
12
|
+
|
|
13
|
+
from .config import READLINE_HISTORY_FILE
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def setup_readline() -> str:
|
|
17
|
+
"""Set up readline with history and completion"""
|
|
18
|
+
if readline is None:
|
|
19
|
+
return ""
|
|
20
|
+
|
|
21
|
+
history_file = READLINE_HISTORY_FILE
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
readline.read_history_file(history_file)
|
|
25
|
+
except FileNotFoundError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
readline.set_history_length(10000)
|
|
29
|
+
readline.parse_and_bind("tab: complete")
|
|
30
|
+
|
|
31
|
+
return history_file
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def save_readline_history():
|
|
35
|
+
"""Save readline history to file"""
|
|
36
|
+
if readline is None:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
readline.write_history_file(READLINE_HISTORY_FILE)
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_path_executables() -> List[str]:
|
|
46
|
+
"""Get list of executables in PATH"""
|
|
47
|
+
executables = set()
|
|
48
|
+
|
|
49
|
+
path_dirs = os.environ.get("PATH", "").split(os.pathsep)
|
|
50
|
+
|
|
51
|
+
for path_dir in path_dirs:
|
|
52
|
+
if os.path.isdir(path_dir):
|
|
53
|
+
try:
|
|
54
|
+
for entry in os.listdir(path_dir):
|
|
55
|
+
full_path = os.path.join(path_dir, entry)
|
|
56
|
+
if os.access(full_path, os.X_OK):
|
|
57
|
+
executables.add(entry)
|
|
58
|
+
except PermissionError:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
return sorted(executables)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_file_completions(text: str) -> List[str]:
|
|
65
|
+
"""Get file/directory completions for text"""
|
|
66
|
+
completions = []
|
|
67
|
+
|
|
68
|
+
if text.startswith("~"):
|
|
69
|
+
expanded = os.path.expanduser(text)
|
|
70
|
+
prefix = "~"
|
|
71
|
+
search_path = expanded
|
|
72
|
+
else:
|
|
73
|
+
prefix = ""
|
|
74
|
+
search_path = text
|
|
75
|
+
|
|
76
|
+
# Get directory to search
|
|
77
|
+
if os.path.isdir(search_path):
|
|
78
|
+
dir_path = search_path
|
|
79
|
+
name_prefix = ""
|
|
80
|
+
else:
|
|
81
|
+
dir_path = os.path.dirname(search_path) or "."
|
|
82
|
+
name_prefix = os.path.basename(search_path)
|
|
83
|
+
|
|
84
|
+
if not os.path.isdir(dir_path):
|
|
85
|
+
return completions
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
for entry in os.listdir(dir_path):
|
|
89
|
+
if entry.startswith(name_prefix):
|
|
90
|
+
full_path = os.path.join(dir_path, entry)
|
|
91
|
+
if os.path.isdir(full_path):
|
|
92
|
+
completions.append(entry + "/")
|
|
93
|
+
else:
|
|
94
|
+
completions.append(entry)
|
|
95
|
+
except PermissionError:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
return completions
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_slash_commands(state: Any, router: Any) -> List[str]:
|
|
102
|
+
"""Get list of available slash commands"""
|
|
103
|
+
commands = set()
|
|
104
|
+
|
|
105
|
+
# Built-in commands and modes
|
|
106
|
+
commands.update([
|
|
107
|
+
'/help', '/set', '/agent', '/chat', '/cmd',
|
|
108
|
+
'/sq', '/quit', '/exit', '/clear',
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
# Team jinxs
|
|
112
|
+
if state.team and hasattr(state.team, 'jinxs_dict'):
|
|
113
|
+
for name in state.team.jinxs_dict:
|
|
114
|
+
commands.add(f'/{name}')
|
|
115
|
+
|
|
116
|
+
# Router jinxs
|
|
117
|
+
if router and hasattr(router, 'jinx_routes'):
|
|
118
|
+
for name in router.jinx_routes:
|
|
119
|
+
commands.add(f'/{name}')
|
|
120
|
+
|
|
121
|
+
return sorted(commands)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_npc_mentions(state: Any) -> List[str]:
|
|
125
|
+
"""Get list of available @npc mentions"""
|
|
126
|
+
npcs = set()
|
|
127
|
+
|
|
128
|
+
# Team NPCs
|
|
129
|
+
if state.team and hasattr(state.team, 'npcs'):
|
|
130
|
+
for name in state.team.npcs:
|
|
131
|
+
npcs.add(f'@{name}')
|
|
132
|
+
|
|
133
|
+
# Also add forenpc if available
|
|
134
|
+
if state.team and hasattr(state.team, 'forenpc') and state.team.forenpc:
|
|
135
|
+
npcs.add(f'@{state.team.forenpc.name}')
|
|
136
|
+
|
|
137
|
+
# Default NPCs if team not loaded yet
|
|
138
|
+
if not npcs:
|
|
139
|
+
npcs.update(['@sibiji', '@guac', '@corca', '@kadiefa', '@plonk', '@forenpc'])
|
|
140
|
+
|
|
141
|
+
return sorted(npcs)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_command_position(buffer: str, begidx: int) -> bool:
|
|
145
|
+
"""Check if we're completing a command (vs argument)"""
|
|
146
|
+
# If we're at the start or after a pipe, it's command position
|
|
147
|
+
before = buffer[:begidx].strip()
|
|
148
|
+
return not before or before.endswith('|')
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def make_completer(shell_state: Any, router: Any):
|
|
152
|
+
"""Create a completer function for readline"""
|
|
153
|
+
|
|
154
|
+
executables = get_path_executables()
|
|
155
|
+
|
|
156
|
+
def completer(text: str, state: int):
|
|
157
|
+
if readline is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
buffer = readline.get_line_buffer()
|
|
162
|
+
begidx = readline.get_begidx()
|
|
163
|
+
|
|
164
|
+
# Build completion options
|
|
165
|
+
options = []
|
|
166
|
+
|
|
167
|
+
# Refresh slash commands and NPC mentions each time (they may change)
|
|
168
|
+
slash_commands = get_slash_commands(shell_state, router)
|
|
169
|
+
npc_mentions = get_npc_mentions(shell_state)
|
|
170
|
+
|
|
171
|
+
if text.startswith('/'):
|
|
172
|
+
# Slash command completion
|
|
173
|
+
options = [c for c in slash_commands if c.startswith(text)]
|
|
174
|
+
|
|
175
|
+
elif text.startswith('@'):
|
|
176
|
+
# @npc mention completion
|
|
177
|
+
options = [n for n in npc_mentions if n.startswith(text)]
|
|
178
|
+
|
|
179
|
+
elif text.startswith('~') or '/' in text or text.startswith('.'):
|
|
180
|
+
# File path completion
|
|
181
|
+
options = get_file_completions(text)
|
|
182
|
+
|
|
183
|
+
elif is_command_position(buffer, begidx):
|
|
184
|
+
# Command completion
|
|
185
|
+
options = [e for e in executables if e.startswith(text)]
|
|
186
|
+
|
|
187
|
+
else:
|
|
188
|
+
# Default to file completion
|
|
189
|
+
options = get_file_completions(text)
|
|
190
|
+
|
|
191
|
+
if state < len(options):
|
|
192
|
+
return options[state]
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
except Exception:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
return completer
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def readline_safe_prompt(prompt: str) -> str:
|
|
202
|
+
"""Make prompt safe for readline (escape ANSI codes)"""
|
|
203
|
+
if readline is None:
|
|
204
|
+
return prompt
|
|
205
|
+
# Wrap non-printing characters
|
|
206
|
+
return prompt.replace('\x1b[', '\x01\x1b[').replace('m', 'm\x02')
|
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
|