npcsh 1.1.5__py3-none-any.whl → 1.1.6__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 +438 -319
- npcsh/npc_team/jinxs/code/sh.jinx +0 -1
- npcsh/npc_team/jinxs/code/sql.jinx +1 -3
- npcsh/npc_team/jinxs/utils/npc-studio.jinx +33 -38
- npcsh/npc_team/jinxs/utils/ots.jinx +34 -65
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +33 -45
- npcsh/routes.py +32 -14
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npc-studio.jinx +33 -38
- npcsh-1.1.6.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/search.jinx +130 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sh.jinx +0 -1
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sql.jinx +1 -3
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/vixynt.jinx +33 -45
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/METADATA +1 -10
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/RECORD +65 -73
- npcsh/npc_team/jinxs/utils/search/brainblast.jinx +0 -51
- npcsh/npc_team/jinxs/utils/search/kg_search.jinx +0 -43
- npcsh/npc_team/jinxs/utils/search/memory_search.jinx +0 -36
- npcsh/npc_team/jinxs/utils/search/rag.jinx +0 -70
- npcsh/npc_team/jinxs/utils/search/search.jinx +0 -192
- npcsh-1.1.5.data/data/npcsh/npc_team/brainblast.jinx +0 -51
- npcsh-1.1.5.data/data/npcsh/npc_team/kg_search.jinx +0 -43
- npcsh-1.1.5.data/data/npcsh/npc_team/memory_search.jinx +0 -36
- npcsh-1.1.5.data/data/npcsh/npc_team/ots.jinx +0 -92
- npcsh-1.1.5.data/data/npcsh/npc_team/rag.jinx +0 -70
- npcsh-1.1.5.data/data/npcsh/npc_team/search.jinx +0 -192
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/breathe.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/flush.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plan.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/wander.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/WHEEL +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
jinx_name: "npc-studio"
|
|
2
2
|
description: "Start npc studio"
|
|
3
3
|
inputs:
|
|
4
|
-
- user_command: ""
|
|
4
|
+
- user_command: ""
|
|
5
5
|
steps:
|
|
6
6
|
- name: "launch_npc_studio"
|
|
7
7
|
engine: "python"
|
|
@@ -13,9 +13,12 @@ steps:
|
|
|
13
13
|
import traceback
|
|
14
14
|
|
|
15
15
|
NPC_STUDIO_DIR = Path.home() / ".npcsh" / "npc-studio"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
|
|
17
|
+
user_command = context.get('user_command')
|
|
18
|
+
output_messages = context.get('messages', [])
|
|
19
|
+
output_result = ""
|
|
20
|
+
|
|
21
|
+
try:
|
|
19
22
|
if not NPC_STUDIO_DIR.exists():
|
|
20
23
|
os.makedirs(NPC_STUDIO_DIR.parent, exist_ok=True)
|
|
21
24
|
subprocess.check_call([
|
|
@@ -28,55 +31,47 @@ steps:
|
|
|
28
31
|
["git", "pull"],
|
|
29
32
|
cwd=NPC_STUDIO_DIR
|
|
30
33
|
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
subprocess.check_call(
|
|
36
|
+
["npm", "install"],
|
|
37
|
+
cwd=NPC_STUDIO_DIR
|
|
38
|
+
)
|
|
39
|
+
|
|
36
40
|
req_file = NPC_STUDIO_DIR / "requirements.txt"
|
|
37
41
|
if req_file.exists():
|
|
38
|
-
subprocess.check_call([
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
subprocess.check_call([
|
|
43
|
+
sys.executable,
|
|
44
|
+
"-m",
|
|
45
|
+
"pip",
|
|
46
|
+
"install",
|
|
47
|
+
"-r",
|
|
48
|
+
str(req_file)
|
|
49
|
+
])
|
|
50
|
+
|
|
48
51
|
backend = subprocess.Popen(
|
|
49
52
|
[sys.executable, "npc_studio_serve.py"],
|
|
50
|
-
cwd=NPC_STUDIO_DIR
|
|
51
|
-
shell = False
|
|
53
|
+
cwd=NPC_STUDIO_DIR
|
|
52
54
|
)
|
|
53
55
|
|
|
54
|
-
# npm run dev is typically for the frontend development server
|
|
55
56
|
dev_server = subprocess.Popen(
|
|
56
57
|
["npm", "run", "dev"],
|
|
57
|
-
cwd=NPC_STUDIO_DIR
|
|
58
|
-
shell=False
|
|
58
|
+
cwd=NPC_STUDIO_DIR
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
# npm start is typically for electron or other packaged frontend
|
|
62
61
|
frontend = subprocess.Popen(
|
|
63
62
|
["npm", "start"],
|
|
64
|
-
cwd=NPC_STUDIO_DIR
|
|
65
|
-
|
|
63
|
+
cwd=NPC_STUDIO_DIR
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
output_result = (
|
|
67
|
+
f"NPC Studio started!\n"
|
|
68
|
+
f"Backend PID={backend.pid}, "
|
|
69
|
+
f"Dev Server PID={dev_server.pid}, "
|
|
70
|
+
f"Frontend PID={frontend.pid}"
|
|
66
71
|
)
|
|
67
|
-
|
|
68
|
-
return backend, dev_server, frontend
|
|
69
|
-
|
|
70
|
-
user_command = context.get('user_command')
|
|
71
|
-
output_messages = context.get('messages', [])
|
|
72
|
-
output_result = ""
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
backend, electron, frontend = launch_npc_studio(user_command or None)
|
|
76
|
-
output_result = f"NPC Studio started!\nBackend PID={backend.pid}, Electron PID={electron.pid} Frontend PID={frontend.pid}"
|
|
77
72
|
except Exception as e:
|
|
78
73
|
traceback.print_exc()
|
|
79
74
|
output_result = f"Failed to start NPC Studio: {e}"
|
|
80
75
|
|
|
81
76
|
context['output'] = output_result
|
|
82
|
-
context['messages'] = output_messages
|
|
77
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
jinx_name: "ots"
|
|
2
|
+
description: "Take screenshot and analyze with vision model. Usage: /ots <prompt>"
|
|
3
|
+
inputs:
|
|
4
|
+
- prompt
|
|
5
|
+
- image_paths_args: ""
|
|
6
|
+
- vmodel: ""
|
|
7
|
+
- vprovider: ""
|
|
8
|
+
steps:
|
|
9
|
+
- name: "analyze_screenshot_or_image"
|
|
10
|
+
engine: "python"
|
|
11
|
+
code: |
|
|
12
|
+
import os
|
|
13
|
+
import traceback
|
|
14
|
+
from npcpy.llm_funcs import get_llm_response
|
|
15
|
+
from npcpy.data.image import capture_screenshot
|
|
16
|
+
|
|
17
|
+
user_prompt = context.get('prompt') or ""
|
|
18
|
+
image_paths_args_str = context.get('image_paths_args') or ""
|
|
19
|
+
vision_model = context.get('vmodel') or ""
|
|
20
|
+
vision_provider = context.get('vprovider') or ""
|
|
21
|
+
stream_output = context.get('stream') or False
|
|
22
|
+
api_url = context.get('api_url') or ""
|
|
23
|
+
api_key = context.get('api_key') or ""
|
|
24
|
+
output_messages = context.get('messages', [])
|
|
25
|
+
current_npc = context.get('npc')
|
|
26
|
+
|
|
27
|
+
image_paths = []
|
|
28
|
+
if image_paths_args_str.strip():
|
|
29
|
+
for img_path_arg in image_paths_args_str.split(','):
|
|
30
|
+
full_path = os.path.abspath(os.path.expanduser(img_path_arg.strip()))
|
|
31
|
+
if os.path.exists(full_path):
|
|
32
|
+
image_paths.append(full_path)
|
|
33
|
+
|
|
34
|
+
if not image_paths:
|
|
35
|
+
screenshot_info = capture_screenshot(full=False)
|
|
36
|
+
if screenshot_info and "file_path" in screenshot_info:
|
|
37
|
+
image_paths.append(screenshot_info["file_path"])
|
|
38
|
+
print(f"📸 Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
|
|
39
|
+
|
|
40
|
+
if not vision_model:
|
|
41
|
+
vision_model = getattr(current_npc, 'model', 'gpt-4o-mini')
|
|
42
|
+
|
|
43
|
+
if not vision_provider:
|
|
44
|
+
vision_provider = getattr(current_npc, 'provider', 'openai')
|
|
45
|
+
|
|
46
|
+
response_data = get_llm_response(
|
|
47
|
+
prompt=user_prompt,
|
|
48
|
+
model=vision_model,
|
|
49
|
+
provider=vision_provider,
|
|
50
|
+
messages=output_messages,
|
|
51
|
+
images=image_paths,
|
|
52
|
+
stream=stream_output,
|
|
53
|
+
npc=current_npc,
|
|
54
|
+
api_url=api_url or None,
|
|
55
|
+
api_key=api_key or None
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
context['output'] = response_data.get('response', 'No response received')
|
|
59
|
+
context['messages'] = response_data.get('messages', output_messages)
|
|
60
|
+
context['model'] = vision_model
|
|
61
|
+
context['provider'] = vision_provider
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
jinx_name: "search"
|
|
2
|
+
description: >
|
|
3
|
+
Executes a search across various sources.
|
|
4
|
+
Usage:
|
|
5
|
+
/search <query> (Default: Web Search)
|
|
6
|
+
/search --memory <query> (Search approved memories)
|
|
7
|
+
/search --kg <query> (Search the knowledge graph)
|
|
8
|
+
/search --rag [-f <paths>] <query> (Execute a RAG search)
|
|
9
|
+
/search --brainblast <query> (Advanced history search)
|
|
10
|
+
inputs:
|
|
11
|
+
- query: ""
|
|
12
|
+
- memory: false
|
|
13
|
+
- kg: false
|
|
14
|
+
- rag: false
|
|
15
|
+
- brainblast: false
|
|
16
|
+
- file_paths: ""
|
|
17
|
+
- history_db_path: "~/npcsh_history.db"
|
|
18
|
+
- vector_db_path: "~/npcsh_chroma.db"
|
|
19
|
+
- sprovider: ""
|
|
20
|
+
- emodel: ""
|
|
21
|
+
- eprovider: ""
|
|
22
|
+
steps:
|
|
23
|
+
- name: "execute_unified_search"
|
|
24
|
+
engine: "python"
|
|
25
|
+
code: |
|
|
26
|
+
import os
|
|
27
|
+
import traceback
|
|
28
|
+
|
|
29
|
+
# Access query from context
|
|
30
|
+
query = context.get('query')
|
|
31
|
+
if not query or not query.strip():
|
|
32
|
+
context['output'] = "Usage: /search [--memory|--kg|--rag|--brainblast] <query>"
|
|
33
|
+
else:
|
|
34
|
+
# state is available as a GLOBAL variable (from extra_globals)
|
|
35
|
+
# Access it directly, not from context
|
|
36
|
+
try:
|
|
37
|
+
current_state = state # This should work now
|
|
38
|
+
except NameError:
|
|
39
|
+
context['output'] = "Error: Shell state not available in jinx context"
|
|
40
|
+
raise
|
|
41
|
+
|
|
42
|
+
current_npc = current_state.npc
|
|
43
|
+
current_team = current_state.team
|
|
44
|
+
|
|
45
|
+
npc_name = getattr(current_npc, 'name', '__none__') if current_npc else '__none__'
|
|
46
|
+
team_name = getattr(current_team, 'name', '__none__') if current_team else '__none__'
|
|
47
|
+
current_path = os.getcwd()
|
|
48
|
+
db_path = os.path.expanduser(context.get("history_db_path"))
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
cmd_history = CommandHistory(db_path)
|
|
52
|
+
|
|
53
|
+
if context.get('memory'):
|
|
54
|
+
memories = get_relevant_memories(
|
|
55
|
+
command_history=cmd_history,
|
|
56
|
+
npc_name=npc_name,
|
|
57
|
+
team_name=team_name,
|
|
58
|
+
path=current_path,
|
|
59
|
+
query=query,
|
|
60
|
+
max_memories=10,
|
|
61
|
+
state=current_state # Pass the state object
|
|
62
|
+
)
|
|
63
|
+
print(memories)
|
|
64
|
+
|
|
65
|
+
if not memories:
|
|
66
|
+
output = f"No memories found for query: '{query}'"
|
|
67
|
+
else:
|
|
68
|
+
output = f"Found {len(memories)} memories:\n\n" + "\n".join(
|
|
69
|
+
f"{i}. [{mem.get('timestamp', 'unknown')}] {mem.get('final_memory') or mem.get('initial_memory')}"
|
|
70
|
+
for i, mem in enumerate(memories, 1)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
elif context.get('kg'):
|
|
74
|
+
facts = search_kg_facts(
|
|
75
|
+
cmd_history,
|
|
76
|
+
npc_name,
|
|
77
|
+
team_name,
|
|
78
|
+
current_path,
|
|
79
|
+
query
|
|
80
|
+
)
|
|
81
|
+
print(facts)
|
|
82
|
+
|
|
83
|
+
if not facts:
|
|
84
|
+
output = f"No KG facts found for query: '{query}'"
|
|
85
|
+
else:
|
|
86
|
+
output = f"Found {len(facts)} KG facts:\n\n" + "\n".join(
|
|
87
|
+
f"{i}. {fact.get('statement')}" for i, fact in enumerate(facts, 1)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
elif context.get('rag'):
|
|
91
|
+
file_paths_str = context.get('file_paths', '')
|
|
92
|
+
file_paths = [os.path.abspath(os.path.expanduser(p.strip())) for p in file_paths_str.split(',') if p.strip()]
|
|
93
|
+
emodel = context.get('emodel') or current_state.embedding_model
|
|
94
|
+
eprovider = context.get('eprovider') or current_state.embedding_provider
|
|
95
|
+
|
|
96
|
+
file_contents = []
|
|
97
|
+
for path in file_paths:
|
|
98
|
+
chunks = load_file_contents(path)
|
|
99
|
+
basename = os.path.basename(path)
|
|
100
|
+
file_contents.extend([f"{basename}: {chunk}" for chunk in chunks])
|
|
101
|
+
|
|
102
|
+
result = execute_rag_command(
|
|
103
|
+
command=query,
|
|
104
|
+
vector_db_path=os.path.expanduser(context.get('vector_db_path')),
|
|
105
|
+
embedding_model=emodel,
|
|
106
|
+
embedding_provider=eprovider,
|
|
107
|
+
file_contents=file_contents or None
|
|
108
|
+
)
|
|
109
|
+
print(result)
|
|
110
|
+
output = result.get('response', 'No response from RAG.')
|
|
111
|
+
|
|
112
|
+
elif context.get('brainblast'):
|
|
113
|
+
result = execute_brainblast_command(
|
|
114
|
+
command=query,
|
|
115
|
+
command_history=cmd_history,
|
|
116
|
+
**context
|
|
117
|
+
)
|
|
118
|
+
print(result)
|
|
119
|
+
output = result.get('output', 'Brainblast search executed.')
|
|
120
|
+
|
|
121
|
+
else:
|
|
122
|
+
# Default to web search
|
|
123
|
+
provider = context.get('sprovider') or current_state.search_provider
|
|
124
|
+
results = search_web(query, provider=provider)
|
|
125
|
+
output = "\n".join([f"- {res}" for res in results]) if results else "No web results found."
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
output = f"An error occurred in the search jinx: {e}\n{traceback.format_exc()}"
|
|
129
|
+
|
|
130
|
+
context['output'] = output
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
jinx_name: sql_executor
|
|
2
2
|
description: Execute queries on the ~/npcsh_history.db to pull data. The database
|
|
3
3
|
contains only information about conversations and other user-provided data. It does
|
|
4
|
-
not store any information about individual files. Avoid using percent signs unless absolutely necessary.
|
|
4
|
+
not store any information about individual files. Avoid using percent signs unless absolutely necessary.
|
|
5
5
|
inputs:
|
|
6
6
|
- sql_query
|
|
7
|
-
- user_query
|
|
8
|
-
- interpret: true
|
|
9
7
|
steps:
|
|
10
8
|
- engine: python
|
|
11
9
|
code: |
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
jinx_name: "vixynt"
|
|
2
2
|
description: "Generates images from text descriptions or edits existing ones."
|
|
3
3
|
inputs:
|
|
4
|
-
- prompt
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
4
|
+
- prompt
|
|
5
|
+
- model: ""
|
|
6
|
+
- provider: ""
|
|
7
|
+
- output_name: ""
|
|
8
|
+
- attachments: ""
|
|
9
|
+
- n_images: 1
|
|
10
|
+
- height: 1024
|
|
11
|
+
- width: 1024
|
|
12
12
|
steps:
|
|
13
13
|
- name: "generate_or_edit_image"
|
|
14
14
|
engine: "python"
|
|
@@ -20,13 +20,13 @@ steps:
|
|
|
20
20
|
from PIL import Image
|
|
21
21
|
from npcpy.llm_funcs import gen_image
|
|
22
22
|
|
|
23
|
-
# Extract inputs from context
|
|
23
|
+
# Extract inputs from context
|
|
24
24
|
image_prompt = context.get('prompt', '').strip()
|
|
25
|
-
|
|
25
|
+
output_name = context.get('output_name')
|
|
26
26
|
attachments_str = context.get('attachments')
|
|
27
|
-
n_images = int(context.get('n_images', 1))
|
|
28
|
-
height = int(context.get('height', 1024))
|
|
29
|
-
width = int(context.get('width', 1024))
|
|
27
|
+
n_images = int(context.get('n_images', 1))
|
|
28
|
+
height = int(context.get('height', 1024))
|
|
29
|
+
width = int(context.get('width', 1024))
|
|
30
30
|
model = context.get('model')
|
|
31
31
|
provider = context.get('provider')
|
|
32
32
|
|
|
@@ -34,29 +34,28 @@ steps:
|
|
|
34
34
|
if attachments_str and attachments_str.strip():
|
|
35
35
|
input_images = [p.strip() for p in attachments_str.split(',')]
|
|
36
36
|
|
|
37
|
-
# Use NPC's model/provider as fallback
|
|
37
|
+
# Use NPC's model/provider as fallback
|
|
38
38
|
if not model and npc and npc.model:
|
|
39
39
|
model = npc.model
|
|
40
40
|
if not provider and npc and npc.provider:
|
|
41
41
|
provider = npc.provider
|
|
42
42
|
|
|
43
|
-
# Final fallbacks
|
|
43
|
+
# Final fallbacks
|
|
44
44
|
if not model:
|
|
45
45
|
model = "runwayml/stable-diffusion-v1-5"
|
|
46
46
|
if not provider:
|
|
47
47
|
provider = "diffusers"
|
|
48
|
-
|
|
49
48
|
|
|
50
49
|
output_messages = context.get('messages', [])
|
|
51
50
|
|
|
52
51
|
if not image_prompt:
|
|
53
|
-
context['output'] = "
|
|
52
|
+
context['output'] = "Error: No prompt provided for image generation."
|
|
54
53
|
context['messages'] = output_messages
|
|
55
|
-
exit()
|
|
54
|
+
exit()
|
|
56
55
|
|
|
57
56
|
try:
|
|
58
|
-
# Generate image(s)
|
|
59
|
-
|
|
57
|
+
# Generate image(s)
|
|
58
|
+
result = gen_image(
|
|
60
59
|
prompt=image_prompt,
|
|
61
60
|
model=model,
|
|
62
61
|
provider=provider,
|
|
@@ -67,20 +66,22 @@ steps:
|
|
|
67
66
|
input_images=input_images if input_images else None
|
|
68
67
|
)
|
|
69
68
|
|
|
69
|
+
# Ensure we have a list of images
|
|
70
|
+
if not isinstance(result, list):
|
|
71
|
+
images_list = [result] if result is not None else []
|
|
72
|
+
else:
|
|
73
|
+
images_list = result
|
|
74
|
+
|
|
70
75
|
saved_files = []
|
|
71
|
-
compressed_images = []
|
|
72
76
|
|
|
73
|
-
if not isinstance(images_list, list):
|
|
74
|
-
images_list = [images_list] if images_list is not None else []
|
|
75
|
-
|
|
76
77
|
for i, image in enumerate(images_list):
|
|
77
78
|
if image is None:
|
|
78
79
|
continue
|
|
79
80
|
|
|
80
81
|
# Determine output filename
|
|
81
|
-
if
|
|
82
|
-
base_name, ext = os.path.splitext(os.path.expanduser(
|
|
83
|
-
if not ext:
|
|
82
|
+
if output_name and output_name.strip():
|
|
83
|
+
base_name, ext = os.path.splitext(os.path.expanduser(output_name))
|
|
84
|
+
if not ext:
|
|
84
85
|
ext = ".png"
|
|
85
86
|
current_output_file = f"{base_name}_{i}{ext}" if len(images_list) > 1 else f"{base_name}{ext}"
|
|
86
87
|
else:
|
|
@@ -93,16 +94,6 @@ steps:
|
|
|
93
94
|
# Save image to file
|
|
94
95
|
image.save(current_output_file)
|
|
95
96
|
saved_files.append(current_output_file)
|
|
96
|
-
|
|
97
|
-
# Create compressed base64 image for HTML rendering
|
|
98
|
-
img_buffer = BytesIO()
|
|
99
|
-
img_copy = image.copy()
|
|
100
|
-
img_copy.thumbnail((800, 600), Image.Resampling.LANCZOS)
|
|
101
|
-
img_copy.save(img_buffer, format='PNG', optimize=True, quality=85)
|
|
102
|
-
img_buffer.seek(0)
|
|
103
|
-
|
|
104
|
-
img_base64 = base64.b64encode(img_buffer.getvalue()).decode('utf-8')
|
|
105
|
-
compressed_images.append(f"data:image/png;base64,{img_base64}")
|
|
106
97
|
|
|
107
98
|
if saved_files:
|
|
108
99
|
if input_images:
|
|
@@ -110,20 +101,17 @@ steps:
|
|
|
110
101
|
else:
|
|
111
102
|
output = f"Image(s) generated and saved to: {', '.join(saved_files)}"
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
html_images += f'<img src="{img_b64}" style="max-width: 400px; margin: 10px;" /><br/>'
|
|
116
|
-
|
|
117
|
-
output += f"\n\nGenerated Images:\n{html_images}"
|
|
104
|
+
# DO NOT include base64 data - just reference the file paths
|
|
105
|
+
output += f"\n\nThe image files have been saved and are ready to view."
|
|
118
106
|
else:
|
|
119
|
-
output = "No images generated."
|
|
107
|
+
output = "No images were generated."
|
|
120
108
|
|
|
121
109
|
except Exception as e:
|
|
122
110
|
import traceback
|
|
123
111
|
traceback.print_exc()
|
|
124
112
|
output = f"Error {'editing' if input_images else 'generating'} image: {str(e)}"
|
|
125
113
|
|
|
126
|
-
context['output'] = output
|
|
127
|
-
context['messages'] = output_messages
|
|
114
|
+
context['output'] = output
|
|
115
|
+
context['messages'] = output_messages
|
|
128
116
|
context['model'] = model
|
|
129
117
|
context['provider'] = provider
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcsh
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.6
|
|
4
4
|
Summary: npcsh is a command-line toolkit for using AI agents in novel ways.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcsh
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -142,15 +142,6 @@ and you will enter the NPC shell. Additionally, the pip installation includes th
|
|
|
142
142
|
npcsh>please read through the markdown files in the docs folder and suggest changes based on the current implementation in the src folder
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
- **Ask a Generic Question**
|
|
146
|
-
```bash
|
|
147
|
-
npcsh> has there ever been a better pasta shape than bucatini?
|
|
148
|
-
```
|
|
149
|
-
```
|
|
150
|
-
Bucatini is certainly a favorite for many due to its unique hollow center, which holds sauces beautifully. Whether it's "better" is subjective and depends on the dish and personal
|
|
151
|
-
preference. Shapes like orecchiette, rigatoni, or trofie excel in different recipes. Bucatini stands out for its versatility and texture, making it a top contender among pasta shapes!
|
|
152
|
-
```
|
|
153
|
-
|
|
154
145
|
|
|
155
146
|
- **Search the Web**
|
|
156
147
|
```bash
|