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/pti.py
CHANGED
|
@@ -1,214 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pti - Pardon-The-Interruption mode CLI entry point
|
|
3
|
+
|
|
4
|
+
This is a thin wrapper that executes the pti.jinx through the jinx mechanism.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
1
7
|
import os
|
|
2
8
|
import sys
|
|
3
|
-
import shlex
|
|
4
|
-
import argparse
|
|
5
|
-
from typing import Dict, List, Any, Optional
|
|
6
|
-
|
|
7
|
-
from termcolor import colored
|
|
8
|
-
|
|
9
|
-
from npcpy.memory.command_history import CommandHistory, save_conversation_message
|
|
10
|
-
from npcpy.npc_sysenv import (
|
|
11
|
-
render_markdown
|
|
12
|
-
)
|
|
13
|
-
from npcpy.llm_funcs import get_llm_response
|
|
14
|
-
from npcpy.npc_compiler import NPC
|
|
15
|
-
from npcpy.data.load import load_file_contents
|
|
16
|
-
|
|
17
|
-
from npcsh._state import (
|
|
18
|
-
ShellState,
|
|
19
|
-
setup_shell,
|
|
20
|
-
get_multiline_input,
|
|
21
|
-
readline_safe_prompt,
|
|
22
|
-
get_npc_path
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
ice = "🧊"
|
|
26
|
-
bear = "🐻❄️"
|
|
27
|
-
def print_pti_welcome_message():
|
|
28
|
-
|
|
29
|
-
print(f"""
|
|
30
|
-
Welcome to PTI Mode!
|
|
31
|
-
|
|
32
|
-
{ice}{ice}{ice} {ice}{ice}{ice} {bear}
|
|
33
|
-
{ice} {ice} {ice} {bear}
|
|
34
|
-
{ice}{ice}{ice} {ice} {bear}
|
|
35
|
-
{ice} {ice} {bear}
|
|
36
|
-
{ice} {ice} {bear}
|
|
37
|
-
|
|
38
|
-
Pardon-The-Interruption for human-in-the-loop reasoning.
|
|
39
|
-
Type 'exit' or 'quit' to return to the main shell.
|
|
40
|
-
""")
|
|
41
|
-
|
|
42
|
-
def enter_pti_mode(command: str, **kwargs):
|
|
43
|
-
state: ShellState = kwargs.get('shell_state')
|
|
44
|
-
command_history: CommandHistory = kwargs.get('command_history')
|
|
45
|
-
|
|
46
|
-
if not state or not command_history:
|
|
47
|
-
return {"output": "Error: PTI mode requires shell state and history.", "messages": kwargs.get('messages', [])}
|
|
48
|
-
|
|
49
|
-
all_command_parts = shlex.split(command)
|
|
50
|
-
parsed_args_list = all_command_parts[1:]
|
|
51
|
-
|
|
52
|
-
parser = argparse.ArgumentParser(prog="/pti", description="Enter PTI mode for human-in-the-loop reasoning.")
|
|
53
|
-
parser.add_argument('initial_prompt', nargs='*', help="Initial prompt to start the session.")
|
|
54
|
-
parser.add_argument("-f", "--files", nargs="*", default=[], help="Files to load into context.")
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
args = parser.parse_args(parsed_args_list)
|
|
58
|
-
except SystemExit:
|
|
59
|
-
return {"output": "Invalid arguments for /pti. Usage: /pti [initial prompt] [-f file1 file2 ...]", "messages": state.messages}
|
|
60
|
-
|
|
61
|
-
print_pti_welcome_message()
|
|
62
|
-
|
|
63
|
-
frederic_path = get_npc_path("frederic", command_history.db_path)
|
|
64
|
-
state.npc = NPC(file=frederic_path)
|
|
65
|
-
print(colored("Defaulting to NPC: frederic", "cyan"))
|
|
66
|
-
state.npc = NPC(name="frederic")
|
|
67
9
|
|
|
68
|
-
|
|
69
|
-
loaded_content = {}
|
|
70
|
-
|
|
71
|
-
if args.files:
|
|
72
|
-
for file_path in args.files:
|
|
73
|
-
try:
|
|
74
|
-
content_chunks = load_file_contents(file_path)
|
|
75
|
-
loaded_content[file_path] = "\n".join(content_chunks)
|
|
76
|
-
print(colored(f"Successfully loaded content from: {file_path}", "green"))
|
|
77
|
-
except Exception as e:
|
|
78
|
-
print(colored(f"Error loading {file_path}: {e}", "red"))
|
|
79
|
-
|
|
80
|
-
user_input = " ".join(args.initial_prompt)
|
|
10
|
+
from npcsh._state import setup_shell
|
|
81
11
|
|
|
82
|
-
while True:
|
|
83
|
-
try:
|
|
84
|
-
if not user_input:
|
|
85
|
-
npc_name = state.npc.name if state.npc and isinstance(state.npc, NPC) else "frederic"
|
|
86
|
-
model_name = state.reasoning_model
|
|
87
|
-
|
|
88
|
-
prompt_str = f"{colored(os.path.basename(state.current_path), 'blue')}:{npc_name}:{model_name}{bear}> "
|
|
89
|
-
prompt = readline_safe_prompt(prompt_str)
|
|
90
|
-
user_input = get_multiline_input(prompt).strip()
|
|
91
|
-
|
|
92
|
-
if user_input.lower() in ["exit", "quit", "done"]:
|
|
93
|
-
break
|
|
94
|
-
|
|
95
|
-
if not user_input:
|
|
96
|
-
continue
|
|
97
|
-
|
|
98
|
-
prompt_for_llm = user_input
|
|
99
|
-
if loaded_content:
|
|
100
|
-
context_str = "\n".join([f"--- Content from {fname} ---\n{content}" for fname, content in loaded_content.items()])
|
|
101
|
-
prompt_for_llm += f"\n\nUse the following context to inform your answer:\n{context_str}"
|
|
102
|
-
|
|
103
|
-
prompt_for_llm += "\n\nThink step-by-step using <think> tags. When you need more information from me, enclose your question in <request_for_input> tags."
|
|
104
|
-
|
|
105
|
-
save_conversation_message(
|
|
106
|
-
command_history,
|
|
107
|
-
state.conversation_id,
|
|
108
|
-
"user",
|
|
109
|
-
user_input,
|
|
110
|
-
wd=state.current_path,
|
|
111
|
-
model=state.reasoning_model,
|
|
112
|
-
provider=state.reasoning_provider,
|
|
113
|
-
npc=state.npc.name if isinstance(state.npc, NPC) else None,
|
|
114
|
-
)
|
|
115
|
-
pti_messages.append({"role": "user", "content": user_input})
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
response_dict = get_llm_response(
|
|
119
|
-
prompt=prompt_for_llm,
|
|
120
|
-
model=state.reasoning_model,
|
|
121
|
-
provider=state.reasoning_provider,
|
|
122
|
-
messages=pti_messages,
|
|
123
|
-
stream=True,
|
|
124
|
-
npc=state.npc
|
|
125
|
-
)
|
|
126
|
-
stream = response_dict.get('response')
|
|
127
|
-
|
|
128
|
-
response_chunks = []
|
|
129
|
-
request_found = False
|
|
130
|
-
|
|
131
|
-
for chunk in stream:
|
|
132
|
-
chunk_content = ""
|
|
133
|
-
if state.reasoning_provider == "ollama":
|
|
134
|
-
chunk_content = chunk.get("message", {}).get("content", "")
|
|
135
|
-
else:
|
|
136
|
-
chunk_content = "".join(
|
|
137
|
-
choice.delta.content
|
|
138
|
-
for choice in chunk.choices
|
|
139
|
-
if choice.delta.content is not None
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
print(chunk_content, end='')
|
|
143
|
-
sys.stdout.flush()
|
|
144
|
-
response_chunks.append(chunk_content)
|
|
145
|
-
|
|
146
|
-
combined_text = "".join(response_chunks)
|
|
147
|
-
if "</request_for_input>" in combined_text:
|
|
148
|
-
request_found = True
|
|
149
|
-
break
|
|
150
|
-
|
|
151
|
-
full_response_text = "".join(response_chunks)
|
|
152
|
-
|
|
153
|
-
save_conversation_message(
|
|
154
|
-
command_history,
|
|
155
|
-
state.conversation_id,
|
|
156
|
-
"assistant",
|
|
157
|
-
full_response_text,
|
|
158
|
-
wd=state.current_path,
|
|
159
|
-
model=state.reasoning_model,
|
|
160
|
-
provider=state.reasoning_provider,
|
|
161
|
-
npc=state.npc.name if isinstance(state.npc, NPC) else None,
|
|
162
|
-
)
|
|
163
|
-
pti_messages.append({"role": "assistant", "content": full_response_text})
|
|
164
|
-
|
|
165
|
-
print()
|
|
166
|
-
user_input = None
|
|
167
|
-
continue
|
|
168
|
-
|
|
169
|
-
except KeyboardInterrupt:
|
|
170
|
-
print(colored("\n\n--- Stream Interrupted ---", "yellow"))
|
|
171
|
-
interrupt_text = input('🐻❄️> ').strip()
|
|
172
|
-
if interrupt_text:
|
|
173
|
-
user_input = interrupt_text
|
|
174
|
-
else:
|
|
175
|
-
user_input = None
|
|
176
|
-
continue
|
|
177
|
-
|
|
178
|
-
except KeyboardInterrupt:
|
|
179
|
-
print()
|
|
180
|
-
continue
|
|
181
|
-
except EOFError:
|
|
182
|
-
print("\nExiting PTI Mode.")
|
|
183
|
-
break
|
|
184
|
-
|
|
185
|
-
render_markdown("\n# Exiting PTI Mode")
|
|
186
|
-
return {"output": "", "messages": pti_messages}
|
|
187
12
|
|
|
188
13
|
def main():
|
|
189
|
-
parser = argparse.ArgumentParser(description="
|
|
190
|
-
parser.add_argument(
|
|
191
|
-
parser.add_argument("
|
|
14
|
+
parser = argparse.ArgumentParser(description="pti - Human-in-the-loop reasoning mode")
|
|
15
|
+
parser.add_argument("prompt", nargs="*", help="Initial prompt to start with")
|
|
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("--files", "-f", nargs="*", help="Files to load into context")
|
|
19
|
+
parser.add_argument("--reasoning-model", type=str, help="Model for reasoning (may differ from chat)")
|
|
192
20
|
args = parser.parse_args()
|
|
193
21
|
|
|
22
|
+
# Setup shell to get team and default NPC
|
|
194
23
|
command_history, team, default_npc = setup_shell()
|
|
195
24
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
25
|
+
if not team or "pti" not in team.jinxs_dict:
|
|
26
|
+
print("Error: pti jinx not found. Ensure npc_team/jinxs/modes/pti.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
|
+
"reasoning_model": args.reasoning_model,
|
|
209
38
|
}
|
|
210
|
-
|
|
211
|
-
|
|
39
|
+
|
|
40
|
+
# If initial prompt provided, add it
|
|
41
|
+
if args.prompt:
|
|
42
|
+
initial = " ".join(args.prompt)
|
|
43
|
+
context["messages"] = [{"role": "user", "content": initial}]
|
|
44
|
+
|
|
45
|
+
# Execute the jinx
|
|
46
|
+
pti_jinx = team.jinxs_dict["pti"]
|
|
47
|
+
result = pti_jinx.execute(context=context, npc=default_npc)
|
|
48
|
+
|
|
49
|
+
if isinstance(result, dict) and result.get("output"):
|
|
50
|
+
print(result["output"])
|
|
51
|
+
|
|
212
52
|
|
|
213
53
|
if __name__ == "__main__":
|
|
214
|
-
main()
|
|
54
|
+
main()
|
npcsh/spool.py
CHANGED
|
@@ -1,253 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from npcpy.data.image import capture_screenshot
|
|
4
|
-
from npcpy.data.text import rag_search
|
|
1
|
+
"""
|
|
2
|
+
spool - Interactive chat mode CLI entry point
|
|
5
3
|
|
|
4
|
+
This is a thin wrapper that executes the spool.jinx through the jinx mechanism.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
6
7
|
import os
|
|
7
8
|
import sys
|
|
8
|
-
from npcpy.npc_sysenv import (
|
|
9
|
-
print_and_process_stream_with_markdown,
|
|
10
|
-
get_system_message,
|
|
11
|
-
render_markdown,
|
|
12
|
-
)
|
|
13
|
-
from npcsh._state import (
|
|
14
|
-
orange,
|
|
15
|
-
ShellState,
|
|
16
|
-
execute_command,
|
|
17
|
-
get_multiline_input,
|
|
18
|
-
readline_safe_prompt,
|
|
19
|
-
setup_shell,
|
|
20
|
-
get_npc_path,
|
|
21
|
-
process_result,
|
|
22
|
-
initial_state,
|
|
23
|
-
)
|
|
24
|
-
from npcpy.llm_funcs import get_llm_response
|
|
25
|
-
from npcpy.npc_compiler import NPC
|
|
26
|
-
from typing import Any, List, Dict, Union
|
|
27
|
-
from npcsh.yap import enter_yap_mode
|
|
28
|
-
from termcolor import colored
|
|
29
|
-
def print_spool_ascii():
|
|
30
|
-
spool_art = """
|
|
31
|
-
██████╗██████╗ ████████╗ ████████╗ ██╗
|
|
32
|
-
██╔════╝██╔══██╗██╔🧵🧵🧵██ ██╔🧵🧵🧵██ ██║
|
|
33
|
-
╚█████╗ ██████╔╝██║🧵🔴🧵██ ██║🧵🔴🧵██ ██║
|
|
34
|
-
╚═══██╗██╔═══╝ ██║🧵🧵🧵██ ██║🧵🧵🧵██ ██║
|
|
35
|
-
██████╔╝██║ ██╚══════██ ██ ══════██ ██║
|
|
36
|
-
╚═════╝ ╚═╝ ╚═████████ ███████═╝ █████████╗
|
|
37
|
-
"""
|
|
38
|
-
print(spool_art)
|
|
39
|
-
def enter_spool_mode(
|
|
40
|
-
npc: NPC = None,
|
|
41
|
-
team = None,
|
|
42
|
-
model: str = None,
|
|
43
|
-
provider: str = None,
|
|
44
|
-
vmodel: str = None,
|
|
45
|
-
vprovider: str = None,
|
|
46
|
-
attachments: List[str] = None,
|
|
47
|
-
rag_similarity_threshold: float = 0.3,
|
|
48
|
-
messages: List[Dict] = None,
|
|
49
|
-
conversation_id: str = None,
|
|
50
|
-
stream: bool = None,
|
|
51
|
-
**kwargs,
|
|
52
|
-
) -> Dict:
|
|
53
|
-
print_spool_ascii()
|
|
54
|
-
|
|
55
|
-
command_history, state_team, default_npc = setup_shell()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
spool_state = ShellState(
|
|
59
|
-
npc=npc or default_npc,
|
|
60
|
-
team=team or state_team,
|
|
61
|
-
messages=messages.copy() if messages else [],
|
|
62
|
-
conversation_id=conversation_id or start_new_conversation(),
|
|
63
|
-
current_path=os.getcwd(),
|
|
64
|
-
stream_output=stream if stream is not None else initial_state.stream_output,
|
|
65
|
-
attachments=None,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if model:
|
|
70
|
-
spool_state.chat_model = model
|
|
71
|
-
if provider:
|
|
72
|
-
spool_state.chat_provider = provider
|
|
73
|
-
if vmodel:
|
|
74
|
-
spool_state.vision_model = vmodel
|
|
75
|
-
if vprovider:
|
|
76
|
-
spool_state.vision_provider = vprovider
|
|
77
9
|
|
|
78
|
-
|
|
79
|
-
print(f"🧵 Entering spool mode{npc_info}. Type '/sq' to exit spool mode.")
|
|
80
|
-
print("💡 Tip: Press Ctrl+C during streaming to interrupt and continue with a new message.")
|
|
10
|
+
from npcsh._state import setup_shell
|
|
81
11
|
|
|
82
|
-
|
|
83
|
-
loaded_chunks = {}
|
|
84
|
-
if attachments:
|
|
85
|
-
if isinstance(attachments, str):
|
|
86
|
-
attachments = [f.strip() for f in attachments.split(',')]
|
|
87
|
-
|
|
88
|
-
for file_path in attachments:
|
|
89
|
-
file_path = os.path.expanduser(file_path)
|
|
90
|
-
if not os.path.exists(file_path):
|
|
91
|
-
print(colored(f"Error: File not found at {file_path}", "red"))
|
|
92
|
-
continue
|
|
93
|
-
try:
|
|
94
|
-
chunks = load_file_contents(file_path)
|
|
95
|
-
loaded_chunks[file_path] = chunks
|
|
96
|
-
print(colored(f"Loaded {len(chunks)} chunks from: {file_path}", "green"))
|
|
97
|
-
except Exception as e:
|
|
98
|
-
print(colored(f"Error loading {file_path}: {str(e)}", "red"))
|
|
99
12
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
npc_name = spool_state.npc.name if spool_state.npc else "chat"
|
|
109
|
-
display_model = spool_state.npc.model if spool_state.npc and spool_state.npc.model else spool_state.chat_model
|
|
110
|
-
|
|
111
|
-
prompt_str = f"{orange(npc_name)}:{display_model}🧵> "
|
|
112
|
-
prompt = readline_safe_prompt(prompt_str)
|
|
113
|
-
user_input = get_multiline_input(prompt).strip()
|
|
114
|
-
|
|
115
|
-
if not user_input:
|
|
116
|
-
continue
|
|
117
|
-
|
|
118
|
-
if user_input.lower() == "/sq":
|
|
119
|
-
print("Exiting spool mode.")
|
|
120
|
-
break
|
|
121
|
-
|
|
122
|
-
if user_input.lower() == "/yap":
|
|
123
|
-
spool_state.messages = enter_yap_mode(spool_state.messages, spool_state.npc)
|
|
124
|
-
continue
|
|
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()
|
|
125
20
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
command_parts = user_input.split()
|
|
129
|
-
image_paths = []
|
|
130
|
-
|
|
131
|
-
if len(command_parts) > 1:
|
|
132
|
-
for img_path in command_parts[1:]:
|
|
133
|
-
full_path = os.path.expanduser(img_path)
|
|
134
|
-
if os.path.exists(full_path):
|
|
135
|
-
image_paths.append(full_path)
|
|
136
|
-
else:
|
|
137
|
-
print(colored(f"Error: Image file not found at {full_path}", "red"))
|
|
138
|
-
else:
|
|
139
|
-
screenshot = capture_screenshot()
|
|
140
|
-
if screenshot and "file_path" in screenshot:
|
|
141
|
-
image_paths.append(screenshot["file_path"])
|
|
142
|
-
print(colored(f"Screenshot captured: {screenshot['filename']}", "green"))
|
|
143
|
-
|
|
144
|
-
if not image_paths:
|
|
145
|
-
continue
|
|
146
|
-
|
|
147
|
-
vision_prompt = input("Prompt for image(s) (or press Enter): ").strip() or "Describe these images."
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
response = get_llm_response(
|
|
151
|
-
vision_prompt,
|
|
152
|
-
model=spool_state.vision_model,
|
|
153
|
-
provider=spool_state.vision_provider,
|
|
154
|
-
messages=spool_state.messages,
|
|
155
|
-
images=image_paths,
|
|
156
|
-
stream=spool_state.stream_output,
|
|
157
|
-
npc=spool_state.npc,
|
|
158
|
-
**kwargs
|
|
159
|
-
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
spool_state.messages = response.get('messages', spool_state.messages)
|
|
163
|
-
output = response.get('response')
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
process_result(vision_prompt, spool_state, {'output': output}, command_history)
|
|
167
|
-
continue
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
current_prompt = user_input
|
|
171
|
-
if loaded_chunks:
|
|
172
|
-
context_content = ""
|
|
173
|
-
for filename, chunks in loaded_chunks.items():
|
|
174
|
-
full_content_str = "\n".join(chunks)
|
|
175
|
-
retrieved_docs = rag_search(
|
|
176
|
-
user_input,
|
|
177
|
-
full_content_str,
|
|
178
|
-
similarity_threshold=rag_similarity_threshold,
|
|
179
|
-
)
|
|
180
|
-
if retrieved_docs:
|
|
181
|
-
context_content += f"\n\nContext from: {filename}\n{retrieved_docs}\n"
|
|
182
|
-
|
|
183
|
-
if context_content:
|
|
184
|
-
current_prompt += f"\n\n--- Relevant context from loaded files ---\n{context_content}"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
response = get_llm_response(
|
|
188
|
-
current_prompt,
|
|
189
|
-
model=spool_state.npc.model if spool_state.npc and spool_state.npc.model else spool_state.chat_model,
|
|
190
|
-
provider=spool_state.npc.provider if spool_state.npc and spool_state.npc.provider else spool_state.chat_provider,
|
|
191
|
-
messages=spool_state.messages,
|
|
192
|
-
stream=spool_state.stream_output,
|
|
193
|
-
npc=spool_state.npc,
|
|
194
|
-
**kwargs
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
spool_state.messages = response.get('messages', spool_state.messages)
|
|
198
|
-
output = response.get('response')
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
process_result(current_prompt, spool_state, {'output': output}, command_history)
|
|
21
|
+
# Setup shell to get team and default NPC
|
|
22
|
+
command_history, team, default_npc = setup_shell()
|
|
202
23
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
except KeyboardInterrupt:
|
|
207
|
-
print("\n🔄 Use '/sq' to exit or continue with a new message.")
|
|
208
|
-
continue
|
|
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)
|
|
209
27
|
|
|
210
|
-
|
|
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
|
+
}
|
|
211
38
|
|
|
39
|
+
# Execute the jinx
|
|
40
|
+
spool_jinx = team.jinxs_dict["spool"]
|
|
41
|
+
result = spool_jinx.execute(context=context, npc=default_npc)
|
|
212
42
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
parser = argparse.ArgumentParser(description="Enter spool mode for chatting with an LLM")
|
|
216
|
-
parser.add_argument("--model", help="Model to use")
|
|
217
|
-
parser.add_argument("--provider", help="Provider to use")
|
|
218
|
-
parser.add_argument("--attachments", nargs="*", help="Files to load into context")
|
|
219
|
-
parser.add_argument("--stream", default="true", help="Use streaming mode")
|
|
220
|
-
parser.add_argument("--npc", type=str, help="NPC name or path to NPC file", default='sibiji',)
|
|
221
|
-
|
|
222
|
-
args = parser.parse_args()
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
command_history, team, default_npc = setup_shell()
|
|
226
|
-
|
|
227
|
-
npc = None
|
|
228
|
-
if args.npc:
|
|
229
|
-
if os.path.exists(os.path.expanduser(args.npc)):
|
|
230
|
-
npc = NPC(file=args.npc)
|
|
231
|
-
elif team and args.npc in team.npcs:
|
|
232
|
-
npc = team.npcs[args.npc]
|
|
233
|
-
else:
|
|
234
|
-
try:
|
|
235
|
-
npc_path = get_npc_path(args.npc, command_history.db_path)
|
|
236
|
-
npc = NPC(file=npc_path)
|
|
237
|
-
except ValueError:
|
|
238
|
-
print(colored(f"NPC '{args.npc}' not found. Using default.", "yellow"))
|
|
239
|
-
npc = default_npc
|
|
240
|
-
else:
|
|
241
|
-
npc = default_npc
|
|
43
|
+
if isinstance(result, dict) and result.get("output"):
|
|
44
|
+
print(result["output"])
|
|
242
45
|
|
|
243
|
-
enter_spool_mode(
|
|
244
|
-
npc=npc,
|
|
245
|
-
team=team,
|
|
246
|
-
model=args.model,
|
|
247
|
-
provider=args.provider,
|
|
248
|
-
attachments=args.attachments,
|
|
249
|
-
stream=args.stream.lower() == "true",
|
|
250
|
-
)
|
|
251
46
|
|
|
252
47
|
if __name__ == "__main__":
|
|
253
|
-
main()
|
|
48
|
+
main()
|